From 0570d7823d27be0e1ffdeb4f271414caff779156 Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 6 May 2019 17:56:34 +0930 Subject: [PATCH] adpcm: using consts where needed --- codec/adpcm/adpcm.go | 115 ++++++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 52 deletions(-) diff --git a/codec/adpcm/adpcm.go b/codec/adpcm/adpcm.go index d4f08ddf..4d426ba7 100644 --- a/codec/adpcm/adpcm.go +++ b/codec/adpcm/adpcm.go @@ -30,6 +30,7 @@ LICENSE Reference algorithms for ADPCM compression and decompression are in part 6. */ +// Package adpcm provides encoder and decoder structs to encode and decode PCM to and from ADPCM. package adpcm import ( @@ -39,20 +40,32 @@ import ( "math" ) +const ( + byteDepth = 2 // TODO(Trek): make configurable. + initSamps = 2 + initBytes = initSamps * byteDepth + headBytes = 4 + sampsPerEnc = 2 + bytesPerEnc = sampsPerEnc * byteDepth + compFact = 4 +) + // encoder is used to encode to ADPCM from PCM data. -// est and index hold state that persists between calls to encodeSample and calcHead. -// dest is the output buffer that implements io.writer and io.bytewriter, ie. where the encoded ADPCM data is written to. type encoder struct { - dest *bytes.Buffer + // dst is the output buffer that implements io.writer and io.bytewriter, ie. where the encoded ADPCM data is written to. + dst *bytes.Buffer + + // est and index hold state that persists between calls to encodeSample and calcHead. est int16 index int16 } // decoder is used to decode from ADPCM to PCM data. -// est, index, and step hold state that persists between calls to decodeSample. -// dest is the output buffer that implements io.writer and io.bytewriter, ie. where the decoded PCM data is written to. type decoder struct { - dest *bytes.Buffer + // dst is the output buffer that implements io.writer and io.bytewriter, ie. where the decoded PCM data is written to. + dst *bytes.Buffer + + // est, index, and step hold state that persists between calls to decodeSample. est int16 index int16 step int16 @@ -91,7 +104,7 @@ var stepTable = []int16{ // NewEncoder retuns a new ADPCM encoder. func NewEncoder(dst *bytes.Buffer) *encoder { e := encoder{ - dest: dst, + dst: dst, } return &e } @@ -99,7 +112,7 @@ func NewEncoder(dst *bytes.Buffer) *encoder { // NewDecoder retuns a new ADPCM decoder. func NewDecoder(dst *bytes.Buffer) *decoder { d := decoder{ - dest: dst, + dst: dst, } return &d } @@ -204,30 +217,29 @@ func capAdd16(a, b int16) int16 { } // calcHead sets the state for the encoder by running the first sample through -// the encoder, and writing the first sample to the encoder's io.Writer (dest). -// It returns the number of bytes written to the encoder's io.Writer (dest) along with any errors. +// the encoder, and writing the first sample to the encoder's io.Writer (dst). +// It returns the number of bytes written to the encoder's io.Writer (dst) along with any errors. func (e *encoder) calcHead(sample []byte, pad bool) (int, error) { - // Check that we are given 1 16-bit sample (2 bytes). - const sampSize = 2 - if len(sample) != sampSize { - return 0, fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), sampSize) + // Check that we are given 1 sample. + if len(sample) != byteDepth { + return 0, fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), byteDepth) } - n, err := e.dest.Write(sample) + n, err := e.dst.Write(sample) if err != nil { return n, err } - err = e.dest.WriteByte(byte(int16(e.index))) + err = e.dst.WriteByte(byte(int16(e.index))) if err != nil { return n, err } n++ if pad { - err = e.dest.WriteByte(0x01) + err = e.dst.WriteByte(0x01) } else { - err = e.dest.WriteByte(0x00) + err = e.dst.WriteByte(0x00) } if err != nil { return n, err @@ -238,9 +250,10 @@ func (e *encoder) calcHead(sample []byte, pad bool) (int, error) { // init initializes the encoder's estimation to the first uncompressed sample and the index to // point to a suitable quantizer step size. +// The suitable step size is the closest step size in the stepTable to half the absolute difference of the first two samples. func (e *encoder) init(samps []byte) { - int1 := int16(binary.LittleEndian.Uint16(samps[0:2])) - int2 := int16(binary.LittleEndian.Uint16(samps[2:4])) + int1 := int16(binary.LittleEndian.Uint16(samps[:byteDepth])) + int2 := int16(binary.LittleEndian.Uint16(samps[byteDepth:initBytes])) e.est = int1 halfDiff := math.Abs(math.Abs(float64(int1)) - math.Abs(float64(int2))/2.0) @@ -255,43 +268,42 @@ func (e *encoder) init(samps []byte) { e.index = cInd } -// Write takes a slice of bytes of arbitrary length representing pcm and encodes in into adpcm. -// It writes its output to the encoder's dest. +// Write takes a slice of bytes of arbitrary length representing pcm and encodes it into adpcm. +// It writes its output to the encoder's dst. // The number of bytes written out is returned along with any error that occured. func (e *encoder) Write(inPcm []byte) (int, error) { - // Check that pcm has enough data to initialize decoder + // Check that pcm has enough data to initialize decoder. pcmLen := len(inPcm) - if pcmLen < 4 { - return 0, fmt.Errorf("length of given byte array must be greater than 4") + if pcmLen < initBytes { + return 0, fmt.Errorf("length of given byte array must be >= %v", initBytes) } // Determine if there will be a byte that won't contain two full nibbles and will need padding. pad := false - if (pcmLen-2)%4 != 0 { + if (pcmLen-byteDepth)%bytesPerEnc != 0 { pad = true } - e.init(inPcm[0:4]) - n, err := e.calcHead(inPcm[0:2], pad) + e.init(inPcm[:initBytes]) + n, err := e.calcHead(inPcm[:byteDepth], pad) if err != nil { return n, err } // Skip the first sample and start at the end of the first two samples, then every two samples encode them into a byte of adpcm. - // TODO: make all hard coded numbers variables so that other bitrates and compression ratios can be used. - for i := 5; i < pcmLen; i += 4 { - nib1 := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[i-3 : i-1]))) - nib2 := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[i-1 : i+1]))) - err = e.dest.WriteByte(byte((nib2 << 4) | nib1)) + for i := byteDepth; i+bytesPerEnc-1 < pcmLen; i += bytesPerEnc { + nib1 := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[i : i+byteDepth]))) + nib2 := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[i+byteDepth : i+bytesPerEnc]))) + err = e.dst.WriteByte(byte((nib2 << 4) | nib1)) if err != nil { return n, err } n++ } - // If we've reached the end of the pcm data and there's a sample (2 bytes) left over, + // If we've reached the end of the pcm data and there's a sample left over, // compress it to a nibble and leave the first half of the byte padded with 0s. if pad { - nib := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[pcmLen-2 : pcmLen]))) - err = e.dest.WriteByte(nib) + nib := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[pcmLen-byteDepth : pcmLen]))) + err = e.dst.WriteByte(nib) if err != nil { return n, err } @@ -300,15 +312,15 @@ func (e *encoder) Write(inPcm []byte) (int, error) { return n, nil } -// Write takes a slice of bytes of arbitrary length representing adpcm and decodes in into pcm. -// It writes its output to the decoder's dest. +// Write takes a slice of bytes of arbitrary length representing adpcm and decodes it into pcm. +// It writes its output to the decoder's dst. // The number of bytes written out is returned along with any error that occured. func (d *decoder) Write(inAdpcm []byte) (int, error) { // Initialize decoder with first 4 bytes of the inAdpcm. - d.est = int16(binary.LittleEndian.Uint16(inAdpcm[0:2])) - d.index = int16(inAdpcm[2]) + d.est = int16(binary.LittleEndian.Uint16(inAdpcm[:byteDepth])) + d.index = int16(inAdpcm[byteDepth]) d.step = stepTable[d.index] - n, err := d.dest.Write(inAdpcm[0:2]) + n, err := d.dst.Write(inAdpcm[:byteDepth]) if err != nil { return n, err } @@ -316,22 +328,22 @@ func (d *decoder) Write(inAdpcm []byte) (int, 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. // If padding flag is true (Adpcm[3]), only decode up until the last byte, then decode that separately. - for i := 4; i < len(inAdpcm)-int(inAdpcm[3]); i++ { + for i := headBytes; i < len(inAdpcm)-int(inAdpcm[3]); i++ { twoNibs := inAdpcm[i] nib2 := byte(twoNibs >> 4) nib1 := byte((nib2 << 4) ^ twoNibs) - firstBytes := make([]byte, 2) + firstBytes := make([]byte, byteDepth) binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(nib1))) - _n, err := d.dest.Write(firstBytes) + _n, err := d.dst.Write(firstBytes) n += _n if err != nil { return n, err } - secondBytes := make([]byte, 2) + secondBytes := make([]byte, byteDepth) binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(nib2))) - _n, err = d.dest.Write(secondBytes) + _n, err = d.dst.Write(secondBytes) n += _n if err != nil { return n, err @@ -339,9 +351,9 @@ func (d *decoder) Write(inAdpcm []byte) (int, error) { } if inAdpcm[3] == 0x01 { padNib := inAdpcm[len(inAdpcm)-1] - samp := make([]byte, 2) + samp := make([]byte, byteDepth) binary.LittleEndian.PutUint16(samp, uint16(d.decodeSample(padNib))) - _n, err := d.dest.Write(samp) + _n, err := d.dst.Write(samp) n += _n if err != nil { return n, err @@ -355,9 +367,8 @@ func BytesOutput(pcm int) int { // for X pcm bytes, byteDepth (eg. 2 bytes) are left uncompressed, the rest is compressed by a factor of 4 // and a start index and padding-flag byte are added. // Also if there are an even number of samples, there will be half a byte of padding added to the last byte. - byteDepth := 2 - if pcm%2*byteDepth == 0 { // %2 because samples are encoded 2 at a time. - return (pcm-byteDepth)/4 + byteDepth + 1 + 1 + 1 + if pcm%bytesPerEnc == 0 { + return (pcm-byteDepth)/compFact + headBytes + 1 } - return (pcm-byteDepth)/4 + byteDepth + 1 + 1 + return (pcm-byteDepth)/compFact + headBytes }