之前做过一次PCM转存为wav类型文件的尝试,使用c语言为文件添加了一个文件头。 这次因为业务需求,需要使用golang对功能重写,并且数据源也由PCM转为了float类型的采样数据,过程中遇到了挺多问题,写文章记录一下解决过程。源代码放在文末。
1、定义文件头及转化成[]byte类型 golang语言的文件操作统一把文件内容转化为[]byte或[]rune类型,最好的解决方法是字符串拼接文件头。但是文件头的数据类型不统一,转换起来很麻烦。于是我使用了golang的结构体作为载体。
typewaveHeaderstruct{RIFFID[4]byte//内容为""RIFFDwSizeuint32//最后填写,WAVE格式音频的大小FccType[4]byte//内容为"WAVE""FmtID[4]byte//内容为"fmt"FmtDwSizeuint32//内容为WAVE_FMT占的字节数,为18WFormatTaguint16//如果为PCM,值为1,float为3WChannelsuint16//通道数,单通道=1,双通道=2DwSamplesPerSecuint32//采样率DwAvgBytesPerSecuint32/*==dwSamplesPerSec*wChannels*uiBitsPerSample/8*/WBlockAlignuint16//==wChannels*uiBitsPerSample/8UiBitsPerSampleuint16//每个采样点的bit数,8bits=8,16bits=16Flaguint16//标志位,默认为0}typefactHeaderstruct{FactID[4]byte//内容为factDataDomainsizeuint32//数据域长度,默认为4SampleNumuint32//采样总数DataID[4]byte//内容为dataDataDwSizeuint32//data的大小}
使用了两个结构体来编写wav的文件头,因为使用float采样数据的话,wave的请求头和PCM不一致,在UiBitsPerSample元素后面有个两字节的标志位,如果使用一个结构体,会因为数据对齐导致中间存在两个无效字节,所以使用两个结构体,在写入的时候转为[]byte然后拼接。
创建文件头:
funcstrToArr(strstring)(arr[4]byte){fori,s:=range[]byte(str){arr[i]=s}return}funccreateNewHeader(channelsint,bits_per_sampleint,sample_rateint)(waveHeader,factHeader){RIFFID:=strToArr("RIFF")header:=waveHeader{RIFFID:RIFFID,DwSize:50,//文件头大小默认为50FccType:strToArr("WAVE"),//内容为"WAVE""FmtID:strToArr("fmt"),//内容为"fmt"FmtDwSize:18,//内容为WAVE_FMT占的字节数,为18WFormatTag:3,//如果为PCM,值为1,float为3WChannels:uint16(channels),//通道数,单通道=1,双通道=2DwSamplesPerSec: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/8UiBitsPerSample:uint16(bits_per_sample),//每个采样点的bit数,8bits=8,16bits=16Flag:uint16(0),//标志位,默认为0}fact:=factHeader{FactID:strToArr("fact"),//内容为factDataDomainsize:uint32(4),//数据域长度,默认为4SampleNum:uint32(0),//采样总数DataID:strToArr("data"),//内容为dataDataDwSize:0,}returnheader,fact}
创建完header就需要将其内容序列化一下,转为[]byte然后写入文件。 查资料得知go指针是强类型的,不允许这样直接获取内存内的值,于是参考了一个两层指针的方法:
header,factHeader:=createNewHeader(channels,bits_per_sample,sample_rate)headerPointer:=&headerfactPointer:=&factHeaderheaderBytes:=*(*[]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)iferr!=nil{log.Fatal(err.Error())}deferfout.Close()buff:=make([]byte,58)size,err:=fout.Read(buff)ifsize!=58{fmt.Println("文件损坏")}//fmt.Println(buff)buffWave:=buff[0:38]buffFact:=buff[38:58]varheader*waveHeader=*(**waveHeader)(unsafe.Pointer(&buffWave))varfactheader*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为其长度。
来源如下:
funcgetByteArray(input[]float64)[]byte{inputLen:=len(input)varbyteArray[]bytefori:=0;i<inputLen;i++{bytes:=Float64ToByte(input[i])byteArray=append(byteArray,bytes...)}returnbyteArray}
3、判断文件是否存在,不存在则创建并添加,否则只尾添加。
funcPathExists(pathstring)(bool,error){_,err:=os.Stat(path)iferr==nil{//文件或者目录存在returntrue,nil}ifos.IsNotExist(err){returnfalse,nil}returnfalse,err}funcsave(input[]byte,namestring){exist,_:=PathExists(name)ifexist{p2w(input,name)}else{createNewWave(name,input,1,64,44100)}}
至此,正文结束。
代码全文如下:
\import("encoding/binary""fmt""io/ioutil""log""math""os""unsafe")\typewaveHeaderstruct{RIFFID[4]byte//内容为""RIFFDwSizeuint32//最后填写,WAVE格式音频的大小FccType[4]byte//内容为"WAVE""FmtID[4]byte//内容为"fmt"FmtDwSizeuint32//内容为WAVE_FMT占的字节数,为18WFormatTaguint16//如果为PCM,值为1,float为3WChannelsuint16//通道数,单通道=1,双通道=2DwSamplesPerSecuint32//采样率DwAvgBytesPerSecuint32/*==dwSamplesPerSec*wChannels*uiBitsPerSample/8*/WBlockAlignuint16//==wChannels*uiBitsPerSample/8UiBitsPerSampleuint16//每个采样点的bit数,8bits=8,16bits=16Flaguint16//标志位,默认为0}typefactHeaderstruct{FactID[4]byte//内容为factDataDomainsizeuint32//数据域长度,默认为4SampleNumuint32//采样总数DataID[4]byte//内容为dataDataDwSizeuint32//data的大小}funcstrToArr(strstring)(arr[4]byte){fori,s:=range[]byte(str){arr[i]=s}return}funcgetWaveHeader(urlstring){fout,err:=os.OpenFile(url,os.O_RDWR,os.ModePerm)iferr!=nil{log.Fatal(err.Error())}deferfout.Close()buff:=make([]byte,58)size,err:=fout.Read(buff)ifsize!=58{fmt.Println("文件损坏")}buffWave:=buff[0:38]buffFact:=buff[38:58]varheaders*waveHeader=*(**waveHeader)(unsafe.Pointer(&buffWave))varfactHeaders*factHeader=*(**factHeader)(unsafe.Pointer(&buffFact))fmt.Println(headers)fmt.Println(factHeaders)//fmt.Println(headers.DataDomainsize)}funccreateNewHeader(channelsint,bits_per_sampleint,sample_rateint)(waveHeader,factHeader){RIFFID:=strToArr("RIFF")header:=waveHeader{RIFFID:RIFFID,DwSize:50,//文件头大小默认为50FccType:strToArr("WAVE"),//内容为"WAVE""FmtID:strToArr("fmt"),//内容为"fmt"FmtDwSize:18,//内容为WAVE_FMT占的字节数,为18WFormatTag:3,//如果为PCM,值为1,float为3WChannels:uint16(channels),//通道数,单通道=1,双通道=2DwSamplesPerSec: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/8UiBitsPerSample:uint16(bits_per_sample),//每个采样点的bit数,8bits=8,16bits=16Flag:uint16(0),//标志位,默认为0}fact:=factHeader{FactID:strToArr("fact"),//内容为factDataDomainsize:uint32(4),//数据域长度,默认为4SampleNum:uint32(0),//采样总数DataID:strToArr("data"),//内容为dataDataDwSize:0,}returnheader,fact}funcPathExists(pathstring)(bool,error){_,err:=os.Stat(path)iferr==nil{//文件或者目录存在returntrue,nil}ifos.IsNotExist(err){returnfalse,nil}returnfalse,err}funccreateNewWave(urlstring,input[]byte,channelsint,bits_per_sampleint,sample_rateint){inputContext:=inputf,err:=os.Create(url)iferr!=nil{fmt.Println(err.Error())}deferf.Close()inputLen:=len(inputContext)header,factHeader:=createNewHeader(channels,bits_per_sample,sample_rate)headerPointer:=&headerfactPointer:=&factHeaderfactPointer.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)}funcp2w(input[]byte,outputstring){inputContent:=inputinputLen:=len(inputContent)fout,err:=os.OpenFile(output,os.O_RDWR,os.ModePerm)iferr!=nil{log.Fatal(err.Error())}deferfout.Close()buff:=make([]byte,58)size,err:=fout.Read(buff)ifsize!=58{fmt.Println("文件损坏")}buffWave:=buff[0:38]buffFact:=buff[38:58]varheader*waveHeader=*(**waveHeader)(unsafe.Pointer(&buffWave))varfactheader*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)}funcsave(input[]byte,namestring){exist,_:=PathExists(name)ifexist{p2w(input,name)}else{createNewWave(name,input,1,64,44100)}}