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 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). // Table of index changes (see spec).
var indexTable = []int16{ var indexTable = []int16{
-1, -1, -1, -1, 2, 4, 6, 8, -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 // calcHead sets the state for the encoder by running the first sample through
// the encoder, and writing the first sample. // 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). // Check that we are given 1 16-bit sample (2 bytes).
sampSize := 2 const sampSize = 2
if len(sample) != sampSize { 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)) intSample := int16(binary.LittleEndian.Uint16(sample))
e.encodeSample(intSample) 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))) err = e.dest.WriteByte(byte(uint16(e.index)))
e.dest.WriteByte(byte(0x00)) if err != nil {
return 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). // 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. // Note: first 4 bytes are for initializing the decoder before decoding a block.
// - First two bytes contain the first 16-bit sample uncompressed. // - 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. // - Third byte is the decoder's starting index for the block, the fourth is padding and ignored.
func (e *Encoder) EncodeBlock(block []byte) error { func (e *Encoder) EncodeBlock(block []byte) (int, error) {
bSize := 1010 if len(block) != PcmBS {
if len(block) != bSize { return 0, fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), PcmBS)
return fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), bSize)
} }
err := e.calcHead(block[0:2]) writ, err := e.calcHead(block[0:2])
if err != nil { 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]))) nib1 := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i-1 : i+1])))
nib2 := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i+1 : i+3]))) 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. // 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. // It outputs the resulting decoded (decompressed) 16-bit PCM samples to the decoder's dest writer.
func (d *Decoder) DecodeBlock(block []byte) error { func (d *Decoder) DecodeBlock(block []byte) error {
bSize := 256 if len(block) != AdpcmBS {
if len(block) != bSize { return fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), AdpcmBS)
return fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), bSize)
} }
// Initialize decoder with first 4 bytes of the block. // 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), // 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.
for i := 4; i < bSize; i++ { for i := 4; i < AdpcmBS; i++ {
twoNibs := block[i] twoNibs := block[i]
nib2 := byte(twoNibs >> 4) nib2 := byte(twoNibs >> 4)
nib1 := byte((nib2 << 4) ^ twoNibs) nib1 := byte((nib2 << 4) ^ twoNibs)

View File

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

View File

@ -34,7 +34,7 @@ import (
"io/ioutil" "io/ioutil"
"log" "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. // 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) fmt.Println("Read", len(comp), "bytes from file", inPath)
// Decode adpcm. // Decode adpcm.
inBSize := 256 numBlocks := len(comp) / adpcm.AdpcmBS
numBlocks := len(comp) / inBSize decoded := bytes.NewBuffer(make([]byte, 0, adpcm.PcmBS*numBlocks))
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))
dec := adpcm.NewDecoder(decoded) dec := adpcm.NewDecoder(decoded)
for i := 0; i < numBlocks; i++ { 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) err := dec.DecodeBlock(block)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -34,7 +34,7 @@ import (
"io/ioutil" "io/ioutil"
"log" "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. // 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) fmt.Println("Read", len(pcm), "bytes from file", inPath)
// Encode adpcm. // Encode adpcm.
inBSize := 1010 numBlocks := len(pcm) / adpcm.PcmBS
numBlocks := len(pcm) / inBSize comp := bytes.NewBuffer(make([]byte, 0, adpcm.AdpcmBS*numBlocks))
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))
enc := adpcm.NewEncoder(comp) enc := adpcm.NewEncoder(comp)
for i := 0; i < numBlocks; i++ { _, err = enc.Write(pcm)
block := pcm[inBSize*i : inBSize*(i+1)] if err != nil {
err := enc.EncodeBlock(block) log.Fatal(err)
if err != nil {
log.Fatal(err)
}
} }
// Save adpcm to file. // Save adpcm to file.