From c7c7ef75f53bb1e1d8ab96327ee21a87b0970110 Mon Sep 17 00:00:00 2001 From: Trek H Date: Fri, 15 Mar 2019 15:47:08 +1030 Subject: [PATCH] ADPCM: Added a Write function to Encoder so that it implements an io.Writer, and also so that a byte slice of any length can be encoded. Added global variables for adpcm and pcm block sizes. Updated tests, encode and decode pcm commands. --- audio/adpcm/adpcm.go | 77 ++++++++++++++++++++++-------- audio/adpcm/adpcm_test.go | 26 ++++------ exp/adpcm/decode-pcm/decode-pcm.go | 10 ++-- exp/adpcm/encode-pcm/encode-pcm.go | 17 +++---- 4 files changed, 76 insertions(+), 54 deletions(-) diff --git a/audio/adpcm/adpcm.go b/audio/adpcm/adpcm.go index f309b78b..40613bde 100644 --- a/audio/adpcm/adpcm.go +++ b/audio/adpcm/adpcm.go @@ -57,6 +57,14 @@ type Decoder struct { step int16 } +// PcmBS is the size of the blocks that an Encoder uses. +// 'EncodeBlock' will encode PcmBS bytes at a time and the output will be AdpcmBS bytes long. +const PcmBS = 1010 + +// AdpcmBS is the size of the blocks that a Decoder uses. +// 'DecodeBlock' will decode AdpcmBS bytes at a time and the output will be PcmBS bytes long. +const AdpcmBS = 256 + // Table of index changes (see spec). var indexTable = []int16{ -1, -1, -1, -1, 2, 4, 6, 8, @@ -183,21 +191,33 @@ func (d *Decoder) decodeSample(nibble byte) int16 { // calcHead sets the state for the encoder by running the first sample through // the encoder, and writing the first sample. -func (e *Encoder) calcHead(sample []byte) error { +func (e *Encoder) calcHead(sample []byte) (int, error) { // Check that we are given 1 16-bit sample (2 bytes). - sampSize := 2 + const sampSize = 2 if len(sample) != sampSize { - return fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), sampSize) + return 0, fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), sampSize) } intSample := int16(binary.LittleEndian.Uint16(sample)) e.encodeSample(intSample) - e.dest.Write(sample) + writ, err := e.dest.Write(sample) + if err != nil { + return writ, err + } - e.dest.WriteByte(byte(uint16(e.index))) - e.dest.WriteByte(byte(0x00)) - return nil + err = e.dest.WriteByte(byte(uint16(e.index))) + if err != nil { + return writ, err + } + writ++ + + err = e.dest.WriteByte(byte(0x00)) + if err != nil { + return writ, err + } + writ++ + return writ, nil } // EncodeBlock takes a slice of 1010 bytes (505 16-bit PCM samples). @@ -206,32 +226,49 @@ func (e *Encoder) calcHead(sample []byte) error { // Note: first 4 bytes are for initializing the decoder before decoding a block. // - First two bytes contain the first 16-bit sample uncompressed. // - Third byte is the decoder's starting index for the block, the fourth is padding and ignored. -func (e *Encoder) EncodeBlock(block []byte) error { - bSize := 1010 - if len(block) != bSize { - return fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), bSize) +func (e *Encoder) EncodeBlock(block []byte) (int, error) { + if len(block) != PcmBS { + return 0, fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), PcmBS) } - err := e.calcHead(block[0:2]) + writ, err := e.calcHead(block[0:2]) if err != nil { - return err + return writ, err } - for i := 3; i < bSize; i += 4 { + for i := 3; i < PcmBS; i += 4 { nib1 := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i-1 : i+1]))) nib2 := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i+1 : i+3]))) - e.dest.WriteByte(byte((nib2 << 4) | nib1)) + err = e.dest.WriteByte(byte((nib2 << 4) | nib1)) + if err != nil { + return writ, err + } + writ++ } - return nil + return writ, nil +} + +func (e *Encoder) Write(inPcm []byte) (int, error) { + numBlocks := len(inPcm) / PcmBS + writ := 0 + for i := 0; i < numBlocks; i++ { + block := inPcm[PcmBS*i : PcmBS*(i+1)] + writB, err := e.EncodeBlock(block) + writ += writB + if err != nil { + return writ, err + } + } + + return writ, nil } // DecodeBlock takes a slice of 256 bytes, each byte after the first 4 should contain two ADPCM encoded nibbles. // It outputs the resulting decoded (decompressed) 16-bit PCM samples to the decoder's dest writer. func (d *Decoder) DecodeBlock(block []byte) error { - bSize := 256 - if len(block) != bSize { - return fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), bSize) + if len(block) != AdpcmBS { + return fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), AdpcmBS) } // Initialize decoder with first 4 bytes of the block. @@ -242,7 +279,7 @@ func (d *Decoder) DecodeBlock(block []byte) error { // For each byte, seperate it into two nibbles (each nibble is a compressed sample), // then decode each nibble and output the resulting 16-bit samples. - for i := 4; i < bSize; i++ { + for i := 4; i < AdpcmBS; i++ { twoNibs := block[i] nib2 := byte(twoNibs >> 4) nib1 := byte((nib2 << 4) ^ twoNibs) diff --git a/audio/adpcm/adpcm_test.go b/audio/adpcm/adpcm_test.go index 6cb6ee2f..cf1a4c71 100644 --- a/audio/adpcm/adpcm_test.go +++ b/audio/adpcm/adpcm_test.go @@ -30,7 +30,6 @@ package adpcm import ( "bytes" "io/ioutil" - "log" "testing" ) @@ -44,17 +43,12 @@ func TestEncodeBlock(t *testing.T) { } // Encode adpcm. - inBSize := 1010 - numBlocks := len(pcm) / inBSize - outBSize := int(float64(inBSize)/4 + 3.5) // compression is 4:1 and 3.5 bytes of info are added to each block - comp := bytes.NewBuffer(make([]byte, 0, outBSize*numBlocks)) + numBlocks := len(pcm) / PcmBS + comp := bytes.NewBuffer(make([]byte, 0, AdpcmBS*numBlocks)) enc := NewEncoder(comp) - for i := 0; i < numBlocks; i++ { - block := pcm[inBSize*i : inBSize*(i+1)] - err := enc.EncodeBlock(block) - if err != nil { - log.Fatal(err) - } + _, err = enc.Write(pcm) + if err != nil { + t.Errorf("Unable to write to encoder: %v", err) } // Read expected adpcm file. @@ -78,16 +72,14 @@ func TestDecodeBlock(t *testing.T) { } // Decode adpcm. - inBSize := 256 - numBlocks := len(comp) / inBSize - outBSize := 2 + (inBSize-4)*4 // 2 bytes are copied, 2 are used as block header info, the remaining bytes are decompressed 1:4 - decoded := bytes.NewBuffer(make([]byte, 0, outBSize*numBlocks)) + numBlocks := len(comp) / AdpcmBS + decoded := bytes.NewBuffer(make([]byte, 0, PcmBS*numBlocks)) dec := NewDecoder(decoded) for i := 0; i < numBlocks; i++ { - block := comp[inBSize*i : inBSize*(i+1)] + block := comp[AdpcmBS*i : AdpcmBS*(i+1)] err := dec.DecodeBlock(block) if err != nil { - log.Fatal(err) + t.Errorf("Unable to write to decoder: %v", err) } } diff --git a/exp/adpcm/decode-pcm/decode-pcm.go b/exp/adpcm/decode-pcm/decode-pcm.go index 95aa6b40..2d5f2625 100644 --- a/exp/adpcm/decode-pcm/decode-pcm.go +++ b/exp/adpcm/decode-pcm/decode-pcm.go @@ -34,7 +34,7 @@ import ( "io/ioutil" "log" - "bitbucket.org/ausocean/av/stream/adpcm" + "bitbucket.org/ausocean/av/audio/adpcm" ) // This program accepts an input file encoded in adpcm and outputs a decoded pcm file. @@ -54,13 +54,11 @@ func main() { fmt.Println("Read", len(comp), "bytes from file", inPath) // Decode adpcm. - inBSize := 256 - numBlocks := len(comp) / inBSize - outBSize := 2 + (inBSize-4)*4 // 2 bytes are copied, 2 are used as block header info, the remaining bytes are decompressed 1:4 - decoded := bytes.NewBuffer(make([]byte, 0, outBSize*numBlocks)) + numBlocks := len(comp) / adpcm.AdpcmBS + decoded := bytes.NewBuffer(make([]byte, 0, adpcm.PcmBS*numBlocks)) dec := adpcm.NewDecoder(decoded) for i := 0; i < numBlocks; i++ { - block := comp[inBSize*i : inBSize*(i+1)] + block := comp[adpcm.AdpcmBS*i : adpcm.AdpcmBS*(i+1)] err := dec.DecodeBlock(block) if err != nil { log.Fatal(err) diff --git a/exp/adpcm/encode-pcm/encode-pcm.go b/exp/adpcm/encode-pcm/encode-pcm.go index 51b5dfb7..c9796228 100644 --- a/exp/adpcm/encode-pcm/encode-pcm.go +++ b/exp/adpcm/encode-pcm/encode-pcm.go @@ -34,7 +34,7 @@ import ( "io/ioutil" "log" - "bitbucket.org/ausocean/av/stream/adpcm" + "bitbucket.org/ausocean/av/audio/adpcm" ) // This program accepts an input pcm file and outputs an encoded adpcm file. @@ -54,17 +54,12 @@ func main() { fmt.Println("Read", len(pcm), "bytes from file", inPath) // Encode adpcm. - inBSize := 1010 - numBlocks := len(pcm) / inBSize - outBSize := int(float64(inBSize)/4 + 3.5) // Compression is 4:1 and 3.5 bytes of info are added to each block. - comp := bytes.NewBuffer(make([]byte, 0, outBSize*numBlocks)) + numBlocks := len(pcm) / adpcm.PcmBS + comp := bytes.NewBuffer(make([]byte, 0, adpcm.AdpcmBS*numBlocks)) enc := adpcm.NewEncoder(comp) - for i := 0; i < numBlocks; i++ { - block := pcm[inBSize*i : inBSize*(i+1)] - err := enc.EncodeBlock(block) - if err != nil { - log.Fatal(err) - } + _, err = enc.Write(pcm) + if err != nil { + log.Fatal(err) } // Save adpcm to file.