From 8f282b12008a2b7ef497745b98e2d410a0564bd8 Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 28 Feb 2019 11:53:16 +1030 Subject: [PATCH] ADPCM: resructure to encoder and decoder structs --- stream/adpcm/adpcm.go | 123 +++++++++++++++++++++++-------------- stream/adpcm/adpcm_test.go | 6 +- 2 files changed, 80 insertions(+), 49 deletions(-) diff --git a/stream/adpcm/adpcm.go b/stream/adpcm/adpcm.go index a8f97559..a8cc7de3 100644 --- a/stream/adpcm/adpcm.go +++ b/stream/adpcm/adpcm.go @@ -35,9 +35,29 @@ package adpcm import ( "encoding/binary" "fmt" + "io" "math" ) +// Encoder is used to encode to ADPCM from PCM data. +// pred and index hold state that persists between calls to encodeSample and calcHead. +// dest is the output, ie. where the encoded ADPCM data is written to. +type Encoder struct { + dest io.Writer + pred int16 + index int16 +} + +// Decoder is used to decode from ADPCM to PCM data. +// pred, index, and step hold state that persists between calls to decodeSample. +// dest is the output, ie. where the decoded PCM data is written to. +type Decoder struct { + dest io.Writer + pred int16 + index int16 + step int16 +} + // table of index changes (see spec) var indexTable = []int16{ -1, -1, -1, -1, 2, 4, 6, 8, @@ -60,18 +80,27 @@ var stepTable = []int16{ 32767, } -var ( - encPred int16 - encIndex int16 - decPred int16 - decIndex int16 - decStep int16 = 7 -) +// NewEncoder retuns a new ADPCM encoder. +func NewEncoder(dst io.Writer) *Encoder { + e := Encoder{ + dest: dst, + } + return &e +} + +// NewDecoder retuns a new ADPCM decoder. +func NewDecoder(dst io.Writer) *Decoder { + d := Decoder{ + step: stepTable[0], + dest: dst, + } + return &d +} // encodeSample takes a single 16 bit PCM sample and // returns a byte of which the last 4 bits are an encoded ADPCM nibble -func encodeSample(sample int16) byte { - delta := sample - encPred +func (e *Encoder) encodeSample(sample int16) byte { + delta := sample - e.pred var nibble byte @@ -81,7 +110,7 @@ func encodeSample(sample int16) byte { delta = -delta } - step := stepTable[encIndex] + step := stepTable[e.index] diff := step >> 3 var mask byte = 4 @@ -97,25 +126,25 @@ func encodeSample(sample int16) byte { // adjust predicted sample based on calculated difference if nibble&8 != 0 { - encPred = capAdd16(encPred, -diff) + e.pred = capAdd16(e.pred, -diff) } else { - encPred = capAdd16(encPred, diff) + e.pred = capAdd16(e.pred, diff) } // check for underflow and overflow - if encPred < math.MinInt16 { - encPred = math.MinInt16 - } else if encPred > math.MaxInt16 { - encPred = math.MaxInt16 + if e.pred < math.MinInt16 { + e.pred = math.MinInt16 + } else if e.pred > math.MaxInt16 { + e.pred = math.MaxInt16 } - encIndex += indexTable[nibble&7] + e.index += indexTable[nibble&7] // check for underflow and overflow - if encIndex < 0 { - encIndex = 0 - } else if encIndex > int16(len(stepTable)-1) { - encIndex = int16(len(stepTable) - 1) + if e.index < 0 { + e.index = 0 + } else if e.index > int16(len(stepTable)-1) { + e.index = int16(len(stepTable) - 1) } return nibble @@ -123,19 +152,19 @@ func encodeSample(sample int16) byte { // decodeSample takes a byte, the last 4 bits of which contain a single // 4 bit ADPCM nibble, and returns a 16 bit decoded PCM sample -func decodeSample(nibble byte) int16 { +func (d *Decoder) decodeSample(nibble byte) int16 { // calculate difference var diff int16 if nibble&4 != 0 { - diff += decStep + diff += d.step } if nibble&2 != 0 { - diff += decStep >> 1 + diff += d.step >> 1 } if nibble&1 != 0 { - diff += decStep >> 2 + diff += d.step >> 2 } - diff += decStep >> 3 + diff += d.step >> 3 // account for sign bit if nibble&8 != 0 { @@ -143,22 +172,22 @@ func decodeSample(nibble byte) int16 { } // adjust predicted sample based on calculated difference - decPred = capAdd16(decPred, diff) + d.pred = capAdd16(d.pred, diff) // adjust index into step size lookup table using nibble - decIndex += indexTable[nibble] + d.index += indexTable[nibble] // check for overflow and underflow - if decIndex < 0 { - decIndex = 0 - } else if decIndex > int16(len(stepTable)-1) { - decIndex = int16(len(stepTable) - 1) + if d.index < 0 { + d.index = 0 + } else if d.index > int16(len(stepTable)-1) { + d.index = int16(len(stepTable) - 1) } // find new quantizer step size - decStep = stepTable[decIndex] + d.step = stepTable[d.index] - return decPred + return d.pred } // capAdd16 adds two int16s together and caps at max/min int16 instead of overflowing @@ -174,7 +203,7 @@ func capAdd16(a, b int16) int16 { } } -func calcHead(sample []byte) ([]byte, error) { +func (e *Encoder) calcHead(sample []byte) ([]byte, error) { // check that we are given 1 16-bit sample (2 bytes) sampSize := 2 if len(sample) != sampSize { @@ -182,34 +211,34 @@ func calcHead(sample []byte) ([]byte, error) { } intSample := int16(binary.LittleEndian.Uint16(sample)) - encodeSample(intSample) + e.encodeSample(intSample) head := make([]byte, 2, 4) head[0] = sample[0] head[1] = sample[1] - head = append(head, byte(uint16(encIndex))) + head = append(head, byte(uint16(e.index))) head = append(head, byte(0x00)) return head, nil } // EncodeBlock takes a slice of 1010 bytes (505 16-bit PCM samples). // It returns a byte slice containing encoded (compressed) ADPCM nibbles (each byte contains two nibbles). -func EncodeBlock(block []byte) ([]byte, error) { +func (e *Encoder) EncodeBlock(block []byte) ([]byte, error) { bSize := 1010 if len(block) != bSize { return nil, fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), bSize) } - result, err := calcHead(block[0:2]) + result, err := e.calcHead(block[0:2]) if err != nil { return nil, err } for i := 2; i < len(block); i++ { if (i+1)%4 == 0 { - sample2 := encodeSample(int16(binary.LittleEndian.Uint16(block[i-1 : i+1]))) - sample := encodeSample(int16(binary.LittleEndian.Uint16(block[i+1 : i+3]))) + sample2 := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i-1 : i+1]))) + sample := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i+1 : i+3]))) result = append(result, byte((sample<<4)|sample2)) } @@ -220,16 +249,16 @@ func EncodeBlock(block []byte) ([]byte, error) { // DecodeBlock takes a slice of 256 bytes, each byte should contain two ADPCM encoded nibbles. // It returns a byte slice containing the resulting decoded (uncompressed) 16-bit PCM samples. -func DecodeBlock(block []byte) ([]byte, error) { +func (d *Decoder) DecodeBlock(block []byte) ([]byte, error) { bSize := 256 if len(block) != bSize { return nil, fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), bSize) } var result []byte - decPred = int16(binary.LittleEndian.Uint16(block[0:2])) - decIndex = int16(block[2]) - decStep = stepTable[decIndex] + d.pred = int16(binary.LittleEndian.Uint16(block[0:2])) + d.index = int16(block[2]) + d.step = stepTable[d.index] result = append(result, block[0:2]...) for i := 4; i < len(block); i++ { @@ -238,11 +267,11 @@ func DecodeBlock(block []byte) ([]byte, error) { firstSample := byte((secondSample << 4) ^ originalSample) firstBytes := make([]byte, 2) - binary.LittleEndian.PutUint16(firstBytes, uint16(decodeSample(firstSample))) + binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(firstSample))) result = append(result, firstBytes...) secondBytes := make([]byte, 2) - binary.LittleEndian.PutUint16(secondBytes, uint16(decodeSample(secondSample))) + binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(secondSample))) result = append(result, secondBytes...) } diff --git a/stream/adpcm/adpcm_test.go b/stream/adpcm/adpcm_test.go index eaada3d5..02eb8f37 100644 --- a/stream/adpcm/adpcm_test.go +++ b/stream/adpcm/adpcm_test.go @@ -48,9 +48,10 @@ func TestEncodeBlock(t *testing.T) { 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 := make([]byte, 0, outBSize*numBlocks) + enc := NewEncoder(nil) for i := 0; i < numBlocks; i++ { block := pcm[inBSize*i : inBSize*(i+1)] - encBlock, err := EncodeBlock(block) + encBlock, err := enc.EncodeBlock(block) if err != nil { log.Fatal(err) } @@ -82,9 +83,10 @@ func TestDecodeBlock(t *testing.T) { 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 := make([]byte, 0, outBSize*numBlocks) + dec := NewDecoder(nil) for i := 0; i < numBlocks; i++ { block := comp[inBSize*i : inBSize*(i+1)] - decBlock, err := DecodeBlock(block) + decBlock, err := dec.DecodeBlock(block) if err != nil { log.Fatal(err) }