ADPCM: resructure to encoder and decoder structs

This commit is contained in:
Trek H 2019-02-28 11:53:16 +10:30
parent 7af363a3a4
commit 8f282b1200
2 changed files with 80 additions and 49 deletions

View File

@ -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...)
}

View File

@ -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)
}