adpcm: using consts where needed

This commit is contained in:
Trek H 2019-05-06 17:56:34 +09:30
parent c27a726831
commit 0570d7823d
1 changed files with 63 additions and 52 deletions

View File

@ -30,6 +30,7 @@ LICENSE
Reference algorithms for ADPCM compression and decompression are in part 6. 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 package adpcm
import ( import (
@ -39,20 +40,32 @@ import (
"math" "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. // 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 { 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 est int16
index int16 index int16
} }
// decoder is used to decode from ADPCM to PCM data. // 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 { 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 est int16
index int16 index int16
step int16 step int16
@ -91,7 +104,7 @@ var stepTable = []int16{
// NewEncoder retuns a new ADPCM encoder. // NewEncoder retuns a new ADPCM encoder.
func NewEncoder(dst *bytes.Buffer) *encoder { func NewEncoder(dst *bytes.Buffer) *encoder {
e := encoder{ e := encoder{
dest: dst, dst: dst,
} }
return &e return &e
} }
@ -99,7 +112,7 @@ func NewEncoder(dst *bytes.Buffer) *encoder {
// NewDecoder retuns a new ADPCM decoder. // NewDecoder retuns a new ADPCM decoder.
func NewDecoder(dst *bytes.Buffer) *decoder { func NewDecoder(dst *bytes.Buffer) *decoder {
d := decoder{ d := decoder{
dest: dst, dst: dst,
} }
return &d 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 // 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). // 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 (dest) along with any errors. // 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) { func (e *encoder) calcHead(sample []byte, pad bool) (int, error) {
// Check that we are given 1 16-bit sample (2 bytes). // Check that we are given 1 sample.
const sampSize = 2 if len(sample) != byteDepth {
if len(sample) != sampSize { return 0, fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), byteDepth)
return 0, fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), sampSize)
} }
n, err := e.dest.Write(sample) n, err := e.dst.Write(sample)
if err != nil { if err != nil {
return n, err return n, err
} }
err = e.dest.WriteByte(byte(int16(e.index))) err = e.dst.WriteByte(byte(int16(e.index)))
if err != nil { if err != nil {
return n, err return n, err
} }
n++ n++
if pad { if pad {
err = e.dest.WriteByte(0x01) err = e.dst.WriteByte(0x01)
} else { } else {
err = e.dest.WriteByte(0x00) err = e.dst.WriteByte(0x00)
} }
if err != nil { if err != nil {
return n, err 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 // init initializes the encoder's estimation to the first uncompressed sample and the index to
// point to a suitable quantizer step size. // 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) { func (e *encoder) init(samps []byte) {
int1 := int16(binary.LittleEndian.Uint16(samps[0:2])) int1 := int16(binary.LittleEndian.Uint16(samps[:byteDepth]))
int2 := int16(binary.LittleEndian.Uint16(samps[2:4])) int2 := int16(binary.LittleEndian.Uint16(samps[byteDepth:initBytes]))
e.est = int1 e.est = int1
halfDiff := math.Abs(math.Abs(float64(int1)) - math.Abs(float64(int2))/2.0) 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 e.index = cInd
} }
// Write takes a slice of bytes of arbitrary length representing pcm and encodes in into adpcm. // Write takes a slice of bytes of arbitrary length representing pcm and encodes it into adpcm.
// It writes its output to the encoder's dest. // It writes its output to the encoder's dst.
// The number of bytes written out is returned along with any error that occured. // The number of bytes written out is returned along with any error that occured.
func (e *encoder) Write(inPcm []byte) (int, error) { 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) pcmLen := len(inPcm)
if pcmLen < 4 { if pcmLen < initBytes {
return 0, fmt.Errorf("length of given byte array must be greater than 4") 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. // Determine if there will be a byte that won't contain two full nibbles and will need padding.
pad := false pad := false
if (pcmLen-2)%4 != 0 { if (pcmLen-byteDepth)%bytesPerEnc != 0 {
pad = true pad = true
} }
e.init(inPcm[0:4]) e.init(inPcm[:initBytes])
n, err := e.calcHead(inPcm[0:2], pad) n, err := e.calcHead(inPcm[:byteDepth], pad)
if err != nil { if err != nil {
return n, err 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. // 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 := byteDepth; i+bytesPerEnc-1 < pcmLen; i += bytesPerEnc {
for i := 5; i < pcmLen; i += 4 { nib1 := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[i : i+byteDepth])))
nib1 := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[i-3 : i-1]))) nib2 := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[i+byteDepth : i+bytesPerEnc])))
nib2 := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[i-1 : i+1]))) err = e.dst.WriteByte(byte((nib2 << 4) | nib1))
err = e.dest.WriteByte(byte((nib2 << 4) | nib1))
if err != nil { if err != nil {
return n, err return n, err
} }
n++ 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. // compress it to a nibble and leave the first half of the byte padded with 0s.
if pad { if pad {
nib := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[pcmLen-2 : pcmLen]))) nib := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[pcmLen-byteDepth : pcmLen])))
err = e.dest.WriteByte(nib) err = e.dst.WriteByte(nib)
if err != nil { if err != nil {
return n, err return n, err
} }
@ -300,15 +312,15 @@ func (e *encoder) Write(inPcm []byte) (int, error) {
return n, nil return n, nil
} }
// Write takes a slice of bytes of arbitrary length representing adpcm and decodes in into pcm. // Write takes a slice of bytes of arbitrary length representing adpcm and decodes it into pcm.
// It writes its output to the decoder's dest. // It writes its output to the decoder's dst.
// The number of bytes written out is returned along with any error that occured. // The number of bytes written out is returned along with any error that occured.
func (d *decoder) Write(inAdpcm []byte) (int, error) { func (d *decoder) Write(inAdpcm []byte) (int, error) {
// Initialize decoder with first 4 bytes of the inAdpcm. // Initialize decoder with first 4 bytes of the inAdpcm.
d.est = int16(binary.LittleEndian.Uint16(inAdpcm[0:2])) d.est = int16(binary.LittleEndian.Uint16(inAdpcm[:byteDepth]))
d.index = int16(inAdpcm[2]) d.index = int16(inAdpcm[byteDepth])
d.step = stepTable[d.index] d.step = stepTable[d.index]
n, err := d.dest.Write(inAdpcm[0:2]) n, err := d.dst.Write(inAdpcm[:byteDepth])
if err != nil { if err != nil {
return n, err 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), // 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. // 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. // 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] twoNibs := inAdpcm[i]
nib2 := byte(twoNibs >> 4) nib2 := byte(twoNibs >> 4)
nib1 := byte((nib2 << 4) ^ twoNibs) nib1 := byte((nib2 << 4) ^ twoNibs)
firstBytes := make([]byte, 2) firstBytes := make([]byte, byteDepth)
binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(nib1))) binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(nib1)))
_n, err := d.dest.Write(firstBytes) _n, err := d.dst.Write(firstBytes)
n += _n n += _n
if err != nil { if err != nil {
return n, err return n, err
} }
secondBytes := make([]byte, 2) secondBytes := make([]byte, byteDepth)
binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(nib2))) binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(nib2)))
_n, err = d.dest.Write(secondBytes) _n, err = d.dst.Write(secondBytes)
n += _n n += _n
if err != nil { if err != nil {
return n, err return n, err
@ -339,9 +351,9 @@ func (d *decoder) Write(inAdpcm []byte) (int, error) {
} }
if inAdpcm[3] == 0x01 { if inAdpcm[3] == 0x01 {
padNib := inAdpcm[len(inAdpcm)-1] padNib := inAdpcm[len(inAdpcm)-1]
samp := make([]byte, 2) samp := make([]byte, byteDepth)
binary.LittleEndian.PutUint16(samp, uint16(d.decodeSample(padNib))) binary.LittleEndian.PutUint16(samp, uint16(d.decodeSample(padNib)))
_n, err := d.dest.Write(samp) _n, err := d.dst.Write(samp)
n += _n n += _n
if err != nil { if err != nil {
return n, err 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 // 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. // 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. // 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%bytesPerEnc == 0 {
if pcm%2*byteDepth == 0 { // %2 because samples are encoded 2 at a time. return (pcm-byteDepth)/compFact + headBytes + 1
return (pcm-byteDepth)/4 + byteDepth + 1 + 1 + 1
} }
return (pcm-byteDepth)/4 + byteDepth + 1 + 1 return (pcm-byteDepth)/compFact + headBytes
} }