adpcm: changed enc and dec to use io.Writer as dst

also reordered encoder and decoder functions and remove old block consts
This commit is contained in:
Trek H 2019-05-17 00:52:36 +09:30
parent 29e49a7a1c
commit a6d6a22b82
2 changed files with 101 additions and 111 deletions

View File

@ -31,9 +31,9 @@ LICENSE
package adpcm
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"math"
)
@ -47,35 +47,6 @@ const (
compFact = 4
)
// encoder is used to encode to ADPCM from PCM data.
type encoder struct {
// 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.
type decoder struct {
// 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
}
// 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,
@ -98,22 +69,35 @@ var stepTable = []int16{
32767,
}
// encoder is used to encode to ADPCM from PCM data.
type encoder struct {
// dst is the destination for encoded data.
dst io.Writer
// 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.
type decoder struct {
// dst is the output buffer that implements io.Writer and io.Bytewriter, ie. where the decoded PCM data is written to.
dst io.Writer
// est, index, and step hold state that persists between calls to decodeSample.
est int16
index int16
step int16
}
// NewEncoder retuns a new ADPCM encoder.
func NewEncoder(dst *bytes.Buffer) *encoder {
func NewEncoder(dst io.Writer) *encoder {
e := encoder{
dst: dst,
}
return &e
}
// NewDecoder retuns a new ADPCM decoder.
func NewDecoder(dst *bytes.Buffer) *decoder {
d := decoder{
dst: 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 (e *encoder) encodeSample(sample int16) byte {
@ -160,59 +144,6 @@ func (e *encoder) encodeSample(sample int16) byte {
return nib
}
// 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 (d *decoder) decodeSample(nibble byte) int16 {
// Calculate difference.
var diff int16
if nibble&4 != 0 {
diff = capAdd16(diff, d.step)
}
if nibble&2 != 0 {
diff = capAdd16(diff, d.step>>1)
}
if nibble&1 != 0 {
diff = capAdd16(diff, d.step>>2)
}
diff = capAdd16(diff, d.step>>3)
// Account for sign bit.
if nibble&8 != 0 {
diff = -diff
}
// Adjust estimated sample based on calculated difference.
d.est = capAdd16(d.est, diff)
// Adjust index into step size lookup table using nibble.
d.index += indexTable[nibble]
// Check for overflow and underflow.
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.
d.step = stepTable[d.index]
return d.est
}
// capAdd16 adds two int16s together and caps at max/min int16 instead of overflowing
func capAdd16(a, b int16) int16 {
c := int32(a) + int32(b)
switch {
case c < math.MinInt16:
return math.MinInt16
case c > math.MaxInt16:
return math.MaxInt16
default:
return int16(c)
}
}
// 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 (dst).
// It returns the number of bytes written to the encoder's io.Writer (dst) along with any errors.
@ -227,21 +158,21 @@ func (e *encoder) calcHead(sample []byte, pad bool) (int, error) {
return n, err
}
err = e.dst.WriteByte(byte(int16(e.index)))
_n, err := e.dst.Write([]byte{byte(int16(e.index))})
if err != nil {
return n, err
}
n++
n += _n
if pad {
err = e.dst.WriteByte(0x01)
_n, err = e.dst.Write([]byte{0x01})
} else {
err = e.dst.WriteByte(0x00)
_n, err = e.dst.Write([]byte{0x00})
}
n += _n
if err != nil {
return n, err
}
n++
return n, nil
}
@ -290,25 +221,73 @@ func (e *encoder) Write(inPcm []byte) (int, error) {
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))
_n, err := e.dst.Write([]byte{byte((nib2 << 4) | nib1)})
n += _n
if err != nil {
return n, err
}
n++
}
// 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-byteDepth : pcmLen])))
err = e.dst.WriteByte(nib)
_n, err := e.dst.Write([]byte{nib})
n += _n
if err != nil {
return n, err
}
n++
}
return n, nil
}
// NewDecoder retuns a new ADPCM decoder.
func NewDecoder(dst io.Writer) *decoder {
d := decoder{
dst: dst,
}
return &d
}
// 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 (d *decoder) decodeSample(nibble byte) int16 {
// Calculate difference.
var diff int16
if nibble&4 != 0 {
diff = capAdd16(diff, d.step)
}
if nibble&2 != 0 {
diff = capAdd16(diff, d.step>>1)
}
if nibble&1 != 0 {
diff = capAdd16(diff, d.step>>2)
}
diff = capAdd16(diff, d.step>>3)
// Account for sign bit.
if nibble&8 != 0 {
diff = -diff
}
// Adjust estimated sample based on calculated difference.
d.est = capAdd16(d.est, diff)
// Adjust index into step size lookup table using nibble.
d.index += indexTable[nibble]
// Check for overflow and underflow.
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.
d.step = stepTable[d.index]
return d.est
}
// 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.
@ -359,13 +338,26 @@ func (d *decoder) Write(chunk []byte) (int, error) {
return n, nil
}
// BytesOutput will return the number of adpcm bytes that will be generated for the given pcm data byte size.
func BytesOutput(pcm int) int {
// For X pcm bytes, 1 sample is left uncompressed, the rest is compressed by a factor of 4
// capAdd16 adds two int16s together and caps at max/min int16 instead of overflowing
func capAdd16(a, b int16) int16 {
c := int32(a) + int32(b)
switch {
case c < math.MinInt16:
return math.MinInt16
case c > math.MaxInt16:
return math.MaxInt16
default:
return int16(c)
}
}
// EncBytes will return the number of adpcm bytes that will be generated when encoding the given amount of pcm bytes (len).
func EncBytes(len int) int {
// For 'len' pcm bytes, 1 sample is 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.
if pcm%bytesPerEnc == 0 {
return (pcm-byteDepth)/compFact + headBytes + 1
if len%bytesPerEnc == 0 {
return (len-byteDepth)/compFact + headBytes + 1
}
return (pcm-byteDepth)/compFact + headBytes
return (len-byteDepth)/compFact + headBytes
}

View File

@ -43,8 +43,7 @@ func TestEncodeBlock(t *testing.T) {
}
// Encode adpcm.
numBlocks := len(pcm) / PcmBS
comp := bytes.NewBuffer(make([]byte, 0, AdpcmBS*numBlocks))
comp := bytes.NewBuffer(make([]byte, 0, EncBytes(len(pcm))))
enc := NewEncoder(comp)
_, err = enc.Write(pcm)
if err != nil {
@ -72,8 +71,7 @@ func TestDecodeBlock(t *testing.T) {
}
// Decode adpcm.
numBlocks := len(comp) / AdpcmBS
decoded := bytes.NewBuffer(make([]byte, 0, PcmBS*numBlocks))
decoded := bytes.NewBuffer(make([]byte, 0, len(comp)*4))
dec := NewDecoder(decoded)
_, err = dec.Write(comp)
if err != nil {