A previous attempt was made to convert PCM to WAV file, adding a file header for the file using C language. This time, due to business requirements, we need to use Golang to rewrite the function, and the data source has changed from PCM to sample data of float type. We encountered many problems in the process, and wrote an article to record the solution process. The source code is at the end of the article. Golang converts the contents of a file to []byte or []rune. The best way to solve this problem is to concatenate a string header. However, the data type of the file header is not uniform, which is troublesome to convert. So I used golang’s structure as a carrier.

Type waveHeader struct {RIFFID [4] BYTE // content ""RIFF DwSize uint32 WAVE Format Audio size FccType [4] BYTE // The content is "WAVE"" FmtID [4] BYTE // The content is "FMT" FmtDwSize uint32 // The content is the number of bytes of WAVE_FMT, Uint16 WFormatTag uint16 // if PCM, value =1, float = 3 uint16 DwSamplesPerSec uint32 // Sampling rate DwAvgBytesPerSec uint32 /* == DwSamplesPerSec *wChannels*uiBitsPerSample/8 */ WBlockAlign uint16 //==wChannels*uiBitsPerSample/8 uiBitsPerSample uint16 // Uint16 Flag uint16, Default is 0} type factHeader struct {FactID [4] BYTE // Content is fact DataDomainsize uint32 The default value is 4. SampleNum uint32 // Total number of samples DataID [4] BYTE // Data DataDwSize uint32 // Data size}Copy the code

There is a two-byte flag bit after the UiBitsPerSample element. If you use one structure, there will be two invalid bytes in the middle due to data alignment. So use two constructs, converted to []byte and concatenated at write time. Create file header:

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: FccType: strToArr("WAVE"), "WAVE"" FmtID: strToArr(" FMT "), "FMT" FmtDwSize: WFormatTag: 3, WFormatTag: 3, float: 3 DwSamplesPerSec: uint32(samPLE_rate), // 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), // Number of bits per sampling point, 8bits=8, 16bits=16 Flag: uint16(0), // Mark bit, default is 0} fact := factHeader{FactID: StrToArr (" FACT "), // Fact DataDomainsize: uint32(4), // Data field length (default: 4) SampleNum: uint32(0), // Total number of samples DataID: StrToArr ("data"), // data DataDwSize: 0,} return header, fact}Copy the code

After creating the header, you need to serialize its contents, convert them to [] bytes, and write them to the file. The go pointer is strongly typed, so it is not allowed to directly obtain the value in memory in this way. Therefore, we refer to a two-layer pointer method:

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])
Copy the code

Since this is a cast for a layer-2 pointer, len of headerBytes is not accurate, so you need to control the length of the write. “F” is an open file. 2. Tail add requires reading the header and writing it back to the file. The header file is read, and its value is assigned to the two file header structure Pointers

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 ! Println(buffs) := buff[0:38] buffFact := buff[38:58] var header *waveHeader = *(**waveHeader)(unsafe.Pointer(&buffWave)) var factheader *factHeader = *(**factHeader)(unsafe.Pointer(&buffFact))Copy the code

Ouput refers to the file that needs to be operated on. The contents including data size and sample rate are calculated and written back to the file.

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)Copy the code

InputConten is the FLAot array converted to []byte, and inputLen is its length. Here are the sources:

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

}
Copy the code

3. Determine whether the file exists. If it does not exist, create and add it.

Func PathExists(Path string) (bool, error) {_, err := os.stat (path) if err == nil {// Return true if file or directory exists 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) } }Copy the code

At this point, the text ends. The code is as follows:

\ import ( "encoding/binary" "fmt" "io/ioutil" "log" "math" "os" "unsafe" ) \ type waveHeader struct { RIFFID [4]byte // The content is ""RIFF DwSize uint32 WAVE Format Audio size FccType [4] BYTE // The content is "WAVE"" FmtID [4] BYTE // The content is "FMT" FmtDwSize uint32 // The content is the number of bytes of WAVE_FMT, Uint16 WFormatTag uint16 // if PCM, value =1, float = 3 uint16 DwSamplesPerSec uint32 // Sampling rate DwAvgBytesPerSec uint32 /* == DwSamplesPerSec *wChannels*uiBitsPerSample/8 */ WBlockAlign uint16 //==wChannels*uiBitsPerSample/8 uiBitsPerSample uint16 // Uint16 Flag uint16, Default is 0} type factHeader struct {FactID [4] BYTE // Content is fact DataDomainsize uint32 The default value is 4. Parameter Description Value SampleNum uint32 // Total number of samples DataID [4] BYTE // Data DataDwSize uint32 // Data size} 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(" file damaged ")} 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, WaveHeader := waveHeader{RIFFID: RIFFID, DwSize: 50, * * * * * * * * * * * * * * * * * * * * StrToArr ("WAVE"), // the content is "WAVE"" FmtID: strToArr(" FMT "), // the content is "FMT" FmtDwSize: 18, // the content is WAVE_FMT the number of bytes, is 18 WFormatTag: // uint16 WChannels: uint16(uint16) // DwSamplesPerSec: Uint32 (SAMPLE_RATE), // Sampling 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), // Number of bits per sampling point, 8bits=8, 16bits=16 Flag: uint16(0), // Mark bit, default is 0} fact := factHeader{FactID: StrToArr (" FACT "), // Fact DataDomainsize: uint32(4), // Data field length (default: 4) SampleNum: uint32(0), // Total number of samples DataID: StrToArr ("data"), // data DataDwSize: 0, } return header, fact } func PathExists(path string) (bool, error) { _, Err := os.stat (path) if err == nil {// File or directory exists 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(" file damage ")} 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) } }Copy the code