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.
*/
// 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
}