golang创建wav文件及尾添加flaot类型的采样数据

1,009 阅读6分钟

之前做过一次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)

}

}