ADPCM: Added a Write function to Encoder so that it implements an io.Writer,

and also so that a byte slice of any length can be encoded.
Added global variables for adpcm and pcm block sizes.
Updated tests, encode and decode pcm commands.
This commit is contained in:
Trek H 2019-03-15 15:47:08 +10:30
parent 2dc6032564
commit c7c7ef75f5
4 changed files with 76 additions and 54 deletions

View File

@ -57,6 +57,14 @@ type Decoder struct {
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,
@ -183,21 +191,33 @@ func (d *Decoder) decodeSample(nibble byte) int16 {
// calcHead sets the state for the encoder by running the first sample through
// the encoder, and writing the first sample.
func (e *Encoder) calcHead(sample []byte) error {
func (e *Encoder) calcHead(sample []byte) (int, error) {
// Check that we are given 1 16-bit sample (2 bytes).
sampSize := 2
const sampSize = 2
if len(sample) != sampSize {
return fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), sampSize)
return 0, fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), sampSize)
}
intSample := int16(binary.LittleEndian.Uint16(sample))
e.encodeSample(intSample)
e.dest.Write(sample)
writ, err := e.dest.Write(sample)
if err != nil {
return writ, err
}
e.dest.WriteByte(byte(uint16(e.index)))
e.dest.WriteByte(byte(0x00))
return nil
err = e.dest.WriteByte(byte(uint16(e.index)))
if err != nil {
return writ, err
}
writ++
err = e.dest.WriteByte(byte(0x00))
if err != nil {
return writ, err
}
writ++
return writ, nil
}
// EncodeBlock takes a slice of 1010 bytes (505 16-bit PCM samples).
@ -206,32 +226,49 @@ func (e *Encoder) calcHead(sample []byte) error {
// Note: first 4 bytes are for initializing the decoder before decoding a block.
// - First two bytes contain the first 16-bit sample uncompressed.
// - Third byte is the decoder's starting index for the block, the fourth is padding and ignored.
func (e *Encoder) EncodeBlock(block []byte) error {
bSize := 1010
if len(block) != bSize {
return fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), bSize)
func (e *Encoder) EncodeBlock(block []byte) (int, error) {
if len(block) != PcmBS {
return 0, fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), PcmBS)
}
err := e.calcHead(block[0:2])
writ, err := e.calcHead(block[0:2])
if err != nil {
return err
return writ, err
}
for i := 3; i < bSize; i += 4 {
for i := 3; i < PcmBS; i += 4 {
nib1 := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i-1 : i+1])))
nib2 := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i+1 : i+3])))
e.dest.WriteByte(byte((nib2 << 4) | nib1))
err = e.dest.WriteByte(byte((nib2 << 4) | nib1))
if err != nil {
return writ, err
}
writ++
}
return nil
return writ, nil
}
func (e *Encoder) Write(inPcm []byte) (int, error) {
numBlocks := len(inPcm) / PcmBS
writ := 0
for i := 0; i < numBlocks; i++ {
block := inPcm[PcmBS*i : PcmBS*(i+1)]
writB, err := e.EncodeBlock(block)
writ += writB
if err != nil {
return writ, err
}
}
return writ, nil
}
// DecodeBlock takes a slice of 256 bytes, each byte after the first 4 should contain two ADPCM encoded nibbles.
// It outputs the resulting decoded (decompressed) 16-bit PCM samples to the decoder's dest writer.
func (d *Decoder) DecodeBlock(block []byte) error {
bSize := 256
if len(block) != bSize {
return fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), bSize)
if len(block) != AdpcmBS {
return fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), AdpcmBS)
}
// Initialize decoder with first 4 bytes of the block.
@ -242,7 +279,7 @@ func (d *Decoder) DecodeBlock(block []byte) 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.
for i := 4; i < bSize; i++ {
for i := 4; i < AdpcmBS; i++ {
twoNibs := block[i]
nib2 := byte(twoNibs >> 4)
nib1 := byte((nib2 << 4) ^ twoNibs)

View File

@ -30,7 +30,6 @@ package adpcm
import (
"bytes"
"io/ioutil"
"log"
"testing"
)
@ -44,17 +43,12 @@ func TestEncodeBlock(t *testing.T) {
}
// Encode adpcm.
inBSize := 1010
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 := bytes.NewBuffer(make([]byte, 0, outBSize*numBlocks))
numBlocks := len(pcm) / PcmBS
comp := bytes.NewBuffer(make([]byte, 0, AdpcmBS*numBlocks))
enc := NewEncoder(comp)
for i := 0; i < numBlocks; i++ {
block := pcm[inBSize*i : inBSize*(i+1)]
err := enc.EncodeBlock(block)
if err != nil {
log.Fatal(err)
}
_, err = enc.Write(pcm)
if err != nil {
t.Errorf("Unable to write to encoder: %v", err)
}
// Read expected adpcm file.
@ -78,16 +72,14 @@ func TestDecodeBlock(t *testing.T) {
}
// Decode adpcm.
inBSize := 256
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 := bytes.NewBuffer(make([]byte, 0, outBSize*numBlocks))
numBlocks := len(comp) / AdpcmBS
decoded := bytes.NewBuffer(make([]byte, 0, PcmBS*numBlocks))
dec := NewDecoder(decoded)
for i := 0; i < numBlocks; i++ {
block := comp[inBSize*i : inBSize*(i+1)]
block := comp[AdpcmBS*i : AdpcmBS*(i+1)]
err := dec.DecodeBlock(block)
if err != nil {
log.Fatal(err)
t.Errorf("Unable to write to decoder: %v", err)
}
}

View File

@ -34,7 +34,7 @@ import (
"io/ioutil"
"log"
"bitbucket.org/ausocean/av/stream/adpcm"
"bitbucket.org/ausocean/av/audio/adpcm"
)
// This program accepts an input file encoded in adpcm and outputs a decoded pcm file.
@ -54,13 +54,11 @@ func main() {
fmt.Println("Read", len(comp), "bytes from file", inPath)
// Decode adpcm.
inBSize := 256
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 := bytes.NewBuffer(make([]byte, 0, outBSize*numBlocks))
numBlocks := len(comp) / adpcm.AdpcmBS
decoded := bytes.NewBuffer(make([]byte, 0, adpcm.PcmBS*numBlocks))
dec := adpcm.NewDecoder(decoded)
for i := 0; i < numBlocks; i++ {
block := comp[inBSize*i : inBSize*(i+1)]
block := comp[adpcm.AdpcmBS*i : adpcm.AdpcmBS*(i+1)]
err := dec.DecodeBlock(block)
if err != nil {
log.Fatal(err)

View File

@ -34,7 +34,7 @@ import (
"io/ioutil"
"log"
"bitbucket.org/ausocean/av/stream/adpcm"
"bitbucket.org/ausocean/av/audio/adpcm"
)
// This program accepts an input pcm file and outputs an encoded adpcm file.
@ -54,17 +54,12 @@ func main() {
fmt.Println("Read", len(pcm), "bytes from file", inPath)
// Encode adpcm.
inBSize := 1010
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 := bytes.NewBuffer(make([]byte, 0, outBSize*numBlocks))
numBlocks := len(pcm) / adpcm.PcmBS
comp := bytes.NewBuffer(make([]byte, 0, adpcm.AdpcmBS*numBlocks))
enc := adpcm.NewEncoder(comp)
for i := 0; i < numBlocks; i++ {
block := pcm[inBSize*i : inBSize*(i+1)]
err := enc.EncodeBlock(block)
if err != nil {
log.Fatal(err)
}
_, err = enc.Write(pcm)
if err != nil {
log.Fatal(err)
}
// Save adpcm to file.