上次写了存储float类型的wav文件,其实存储PCM的相关数据相对操作更少,难度更小。 首先,和存储flaot类型的数据类似,定义一个文件头结构体。
type waveHeader struct {
RIFFID [4]byte //内容为""RIFF
DwSize uint32 //最后填写,WAVE格式音频的大小
FccType [4]byte //内容为"WAVE""
FmtID [4]byte //内容为"fmt "
FmtDwSize uint32 //内容为WAVE_FMT占的字节数,为16
WFormatTag uint16 //如果为PCM,值为 1
WChannels uint16 //通道数,单通道=1,双通道=2
DwSamplesPerSec uint32 //采样率
DwAvgBytesPerSec uint32 /* ==dwSamplesPerSec*wChannels*uiBitsPerSample/8 */
WBlockAlign uint16 //==wChannels*uiBitsPerSample/8
UiBitsPerSample uint16 //每个采样点的bit数,8bits=8, 16bits=16
DataID [4]byte //内容为data
DataDwSize uint32 //data的大小
}
写入文件分两部分,分别是创建文件头和写入文件数据。 下面是我们创建文件并将部分PCM数据写入文件的过程。
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 := createNewHeader(channels, bits_per_sample,sample_rate)
headerPointer := &header
headerPointer.DataDwSize += uint32(inputLen)
headerPointer.DwSize += uint32(inputLen)
headerBytes := *(*[]byte)(unsafe.Pointer(&headerPointer))
// headerBytes := []byte(&header)
f.Write(headerBytes[0:44])
fmt.Println(headerBytes[0:44])
// f.Seek(0, 2)
f.Write(inputContext)
}
其中input为需要写入的PCM数据,url为存储路径。channels 为声道数, bits_per_sample 为采样点大小, sample_rate 为采样率。
PCM尾添加至已存在的wav文件。
func p2w(input []byte, output string) {
inputContent := input
inputSize := 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, 44)
size, err := fout.Read(buff)
if size != 44 {
fmt.Println("文件损坏")
}
var header *waveHeader = *(**waveHeader)(unsafe.Pointer(&buff))
header.DataDwSize += uint32(inputSize)
header.DwSize += uint32(inputSize)
resHeader := header
headerBytes := *(*[]byte)(unsafe.Pointer(&resHeader))
fout.Seek(0, 0)
fout.Write(headerBytes[0:44])
fout.Seek(0, 2)
fout.Write(inputContent)
}
里面涉及的知识点和上一篇文章大同小异。如果有感兴趣的同学可以留言一起讨论。