之前做过一次PCM转存为wav类型文件的尝试,使用c语言为文件添加了一个文件头。 这次因为业务需求,需要使用golang对功能重写,并且数据源也由PCM转为了float类型的采样数据,过程中遇到了挺多问题,写文章记录一下解决过程。源代码放在文末。 1、定义文件头及转化成[]byte类型 golang语言的文件操作统一把文件内容转化为[]byte或[]rune类型,最好的解决方法是字符串拼接文件头。但是文件头的数据类型不统一,转换起来很麻烦。于是我使用了golang的结构体作为载体。
type waveHeader struct {
RIFFID [4]byte //内容为""RIFF
DwSize uint32 //最后填写,WAVE格式音频的大小
FccType [4]byte //内容为"WAVE""
FmtID [4]byte //内容为"fmt "
FmtDwSize uint32 //内容为WAVE_FMT占的字节数,为18
WFormatTag uint16 //如果为PCM,值为 1,float为3
WChannels uint16 //通道数,单通道=1,双通道=2
DwSamplesPerSec uint32 //采样率
DwAvgBytesPerSec uint32 /* ==dwSamplesPerSec*wChannels*uiBitsPerSample/8 */
WBlockAlign uint16 //==wChannels*uiBitsPerSample/8
UiBitsPerSample uint16 //每个采样点的bit数,8bits=8, 16bits=16
Flag uint16 //标志位,默认为0
}
type factHeader struct {
FactID [4]byte //内容为fact
DataDomainsize uint32 //数据域长度,默认为4
SampleNum uint32 //采样总数
DataID [4]byte //内容为data
DataDwSize uint32 //data的大小
}
使用了两个结构体来编写wav的文件头,因为使用float采样数据的话,wave的请求头和PCM不一致,在UiBitsPerSample元素后面有个两字节的标志位,如果使用一个结构体,会因为数据对齐导致中间存在两个无效字节,所以使用两个结构体,在写入的时候转为[]byte然后拼接。创建文件头:
func strToArr(str string) (arr [4]byte) {
for i, s := range []byte(str) {
arr[i] = s
}
return
}
func createNewHeader(channels int, bits_per_sample int, sample_rate int) (waveHeader, factHeader) {
RIFFID := strToArr("RIFF")
header := waveHeader{
RIFFID: RIFFID,
DwSize: 50, //文件头大小 默认为50
FccType: strToArr("WAVE"), //内容为"WAVE""
FmtID: strToArr("fmt "), //内容为"fmt "
FmtDwSize: 18, //内容为WAVE_FMT占的字节数,为18
WFormatTag: 3, //如果为PCM,值为 1,float为3
WChannels: uint16(channels), //通道数,单通道=1,双通道=2
DwSamplesPerSec: uint32(sample_rate), //采样率
DwAvgBytesPerSec: uint32((sample_rate * channels * bits_per_sample) / 8), /* ==dwSamplesPerSec*wChannels*uiBitsPerSample/8 */
WBlockAlign: uint16((channels * bits_per_sample) / 8), //==wChannels*uiBitsPerSample/8
UiBitsPerSample: uint16(bits_per_sample), //每个采样点的bit数,8bits=8, 16bits=16
Flag: uint16(0), //标志位,默认为0
}
fact := factHeader{
FactID: strToArr("fact"), //内容为fact
DataDomainsize: uint32(4), //数据域长度,默认为4
SampleNum: uint32(0), //采样总数
DataID: strToArr("data"), //内容为data
DataDwSize: 0,
}
return header, fact
}
创建完header就需要将其内容序列化一下,转为[]byte然后写入文件。 查资料得知go指针是强类型的,不允许这样直接获取内存内的值,于是参考了一个两层指针的方法:
header, factHeader := createNewHeader(channels, bits_per_sample, sample_rate)
headerPointer := &header
factPointer := &factHeader
headerBytes := *(*[]byte)(unsafe.Pointer(&headerPointer))
factBytes := *(*[]byte)(unsafe.Pointer(&factPointer))
f.Write(headerBytes[0:38])
f.Write(factBytes[0:20])
因为是二层指针的强制类型转换,headerBytes的len并不准确,所以需要自己控制写入的长度。 "f"为一个打开的文件。 2、尾添加需要读取文件头并将其运算后写回文件。先读取头文件,然后将其值赋给两个文件头结构体指针
fout, err := os.OpenFile(output, os.O_RDWR, os.ModePerm)
if err != nil {
log.Fatal(err.Error())
}
defer fout.Close()
buff := make([]byte, 58)
size, err := fout.Read(buff)
if size != 58 {
fmt.Println("文件损坏")
}
// fmt.Println(buff)
buffWave := buff[0:38]
buffFact := buff[38:58]
var header *waveHeader = *(**waveHeader)(unsafe.Pointer(&buffWave))
var factheader *factHeader = *(**factHeader)(unsafe.Pointer(&buffFact))
ouput为需要操作的文件,对其包含数据大小和采样率的内容进行运算并写回文件。
header.DwSize += uint32(inputLen)
factheader.DataDwSize += uint32(inputLen)
factheader.SampleNum += uint32(inputLen / 8)
headerBytes := *(*[]byte)(unsafe.Pointer(&header))
factBytes := *(*[]byte)(unsafe.Pointer(&factheader))
fout.Seek(0, 0)
fout.Write(headerBytes[0:38])
fmt.Println(factBytes[0:20])
fout.Write(factBytes[0:20])
fout.Seek(0, 2)
fout.Write(inputContent)
inputConten为转为[]byte的flaot数组,inputLen为其长度。来源如下:
func getByteArray(input []float64) []byte {
inputLen := len(input)
var byteArray []byte
for i := 0; i < inputLen; i++ {
bytes := Float64ToByte(input[i])
byteArray = append(byteArray, bytes...)
}
return byteArray
}
3、判断文件是否存在,不存在则创建并添加,否则只尾添加。
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil { //文件或者目录存在
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func save(input []byte, name string) {
exist, _ := PathExists(name)
if exist {
p2w(input, name)
} else {
createNewWave(name, input, 1, 64, 44100)
}
}
至此,正文结束。代码全文如下:
\
import (
"encoding/binary"
"fmt"
"io/ioutil"
"log"
"math"
"os"
"unsafe"
)
\
type waveHeader struct {
RIFFID [4]byte //内容为""RIFF
DwSize uint32 //最后填写,WAVE格式音频的大小
FccType [4]byte //内容为"WAVE""
FmtID [4]byte //内容为"fmt "
FmtDwSize uint32 //内容为WAVE_FMT占的字节数,为18
WFormatTag uint16 //如果为PCM,值为 1,float为3
WChannels uint16 //通道数,单通道=1,双通道=2
DwSamplesPerSec uint32 //采样率
DwAvgBytesPerSec uint32 /* ==dwSamplesPerSec*wChannels*uiBitsPerSample/8 */
WBlockAlign uint16 //==wChannels*uiBitsPerSample/8
UiBitsPerSample uint16 //每个采样点的bit数,8bits=8, 16bits=16
Flag uint16 //标志位,默认为0
}
type factHeader struct {
FactID [4]byte //内容为fact
DataDomainsize uint32 //数据域长度,默认为4
SampleNum uint32 //采样总数
DataID [4]byte //内容为data
DataDwSize uint32 //data的大小
}
func strToArr(str string) (arr [4]byte) {
for i, s := range []byte(str) {
arr[i] = s
}
return
}
func getWaveHeader(url string) {
fout, err := os.OpenFile(url, os.O_RDWR, os.ModePerm)
if err != nil {
log.Fatal(err.Error())
}
defer fout.Close()
buff := make([]byte, 58)
size, err := fout.Read(buff)
if size != 58 {
fmt.Println("文件损坏")
}
buffWave := buff[0:38]
buffFact := buff[38:58]
var headers *waveHeader = *(**waveHeader)(unsafe.Pointer(&buffWave))
var factHeaders *factHeader = *(**factHeader)(unsafe.Pointer(&buffFact))
fmt.Println(headers)
fmt.Println(factHeaders)
// fmt.Println(headers.DataDomainsize)
}
func createNewHeader(channels int, bits_per_sample int, sample_rate int) (waveHeader, factHeader) {
RIFFID := strToArr("RIFF")
header := waveHeader{
RIFFID: RIFFID,
DwSize: 50, //文件头大小 默认为50
FccType: strToArr("WAVE"), //内容为"WAVE""
FmtID: strToArr("fmt "), //内容为"fmt "
FmtDwSize: 18, //内容为WAVE_FMT占的字节数,为18
WFormatTag: 3, //如果为PCM,值为 1,float为3
WChannels: uint16(channels), //通道数,单通道=1,双通道=2
DwSamplesPerSec: uint32(sample_rate), //采样率
DwAvgBytesPerSec: uint32((sample_rate * channels * bits_per_sample) / 8), /* ==dwSamplesPerSec*wChannels*uiBitsPerSample/8 */
WBlockAlign: uint16((channels * bits_per_sample) / 8), //==wChannels*uiBitsPerSample/8
UiBitsPerSample: uint16(bits_per_sample), //每个采样点的bit数,8bits=8, 16bits=16
Flag: uint16(0), //标志位,默认为0
}
fact := factHeader{
FactID: strToArr("fact"), //内容为fact
DataDomainsize: uint32(4), //数据域长度,默认为4
SampleNum: uint32(0), //采样总数
DataID: strToArr("data"), //内容为data
DataDwSize: 0,
}
return header, fact
}
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil { //文件或者目录存在
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func createNewWave(url string, input []byte, channels int, bits_per_sample int, sample_rate int) {
inputContext := input
f, err := os.Create(url)
if err != nil {
fmt.Println(err.Error())
}
defer f.Close()
inputLen := len(inputContext)
header, factHeader := createNewHeader(channels, bits_per_sample, sample_rate)
headerPointer := &header
factPointer := &factHeader
factPointer.DataDwSize += uint32(inputLen)
headerPointer.DwSize += uint32(inputLen)
factHeader.SampleNum += uint32(inputLen / 8)
headerBytes := *(*[]byte)(unsafe.Pointer(&headerPointer))
factBytes := *(*[]byte)(unsafe.Pointer(&factPointer))
f.Write(headerBytes[0:38])
f.Write(factBytes[0:20])
fmt.Println(headerBytes[0:38])
fmt.Println(factBytes[0:20])
f.Write(inputContext)
}
func p2w(input []byte, output string) {
inputContent := input
inputLen := len(inputContent)
fout, err := os.OpenFile(output, os.O_RDWR, os.ModePerm)
if err != nil {
log.Fatal(err.Error())
}
defer fout.Close()
buff := make([]byte, 58)
size, err := fout.Read(buff)
if size != 58 {
fmt.Println("文件损坏")
}
buffWave := buff[0:38]
buffFact := buff[38:58]
var header *waveHeader = *(**waveHeader)(unsafe.Pointer(&buffWave))
var factheader *factHeader = *(**factHeader)(unsafe.Pointer(&buffFact))
header.DwSize += uint32(inputLen)
factheader.DataDwSize += uint32(inputLen)
factheader.SampleNum += uint32(inputLen / 8)
headerBytes := *(*[]byte)(unsafe.Pointer(&header))
factBytes := *(*[]byte)(unsafe.Pointer(&factheader))
fout.Seek(0, 0)
fout.Write(headerBytes[0:38])
fmt.Println(factBytes[0:20])
fout.Write(factBytes[0:20])
fout.Seek(0, 2)
fout.Write(inputContent)
}
func save(input []byte, name string) {
exist, _ := PathExists(name)
if exist {
p2w(input, name)
} else {
createNewWave(name, input, 1, 64, 44100)
}
}