mirror of https://bitbucket.org/ausocean/av.git
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:
parent
2dc6032564
commit
c7c7ef75f5
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue