mirror of https://bitbucket.org/ausocean/av.git
adpcm: using consts where needed
This commit is contained in:
parent
c27a726831
commit
0570d7823d
|
@ -30,6 +30,7 @@ LICENSE
|
|||
Reference algorithms for ADPCM compression and decompression are in part 6.
|
||||
*/
|
||||
|
||||
// Package adpcm provides encoder and decoder structs to encode and decode PCM to and from ADPCM.
|
||||
package adpcm
|
||||
|
||||
import (
|
||||
|
@ -39,20 +40,32 @@ import (
|
|||
"math"
|
||||
)
|
||||
|
||||
const (
|
||||
byteDepth = 2 // TODO(Trek): make configurable.
|
||||
initSamps = 2
|
||||
initBytes = initSamps * byteDepth
|
||||
headBytes = 4
|
||||
sampsPerEnc = 2
|
||||
bytesPerEnc = sampsPerEnc * byteDepth
|
||||
compFact = 4
|
||||
)
|
||||
|
||||
// encoder is used to encode to ADPCM from PCM data.
|
||||
// est and index hold state that persists between calls to encodeSample and calcHead.
|
||||
// dest is the output buffer that implements io.writer and io.bytewriter, ie. where the encoded ADPCM data is written to.
|
||||
type encoder struct {
|
||||
dest *bytes.Buffer
|
||||
// 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.
|
||||
// est, index, and step hold state that persists between calls to decodeSample.
|
||||
// dest is the output buffer that implements io.writer and io.bytewriter, ie. where the decoded PCM data is written to.
|
||||
type decoder struct {
|
||||
dest *bytes.Buffer
|
||||
// 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
|
||||
|
@ -91,7 +104,7 @@ var stepTable = []int16{
|
|||
// NewEncoder retuns a new ADPCM encoder.
|
||||
func NewEncoder(dst *bytes.Buffer) *encoder {
|
||||
e := encoder{
|
||||
dest: dst,
|
||||
dst: dst,
|
||||
}
|
||||
return &e
|
||||
}
|
||||
|
@ -99,7 +112,7 @@ func NewEncoder(dst *bytes.Buffer) *encoder {
|
|||
// NewDecoder retuns a new ADPCM decoder.
|
||||
func NewDecoder(dst *bytes.Buffer) *decoder {
|
||||
d := decoder{
|
||||
dest: dst,
|
||||
dst: dst,
|
||||
}
|
||||
return &d
|
||||
}
|
||||
|
@ -204,30 +217,29 @@ func capAdd16(a, b int16) int16 {
|
|||
}
|
||||
|
||||
// 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 (dest).
|
||||
// It returns the number of bytes written to the encoder's io.Writer (dest) along with any errors.
|
||||
// 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.
|
||||
func (e *encoder) calcHead(sample []byte, pad bool) (int, error) {
|
||||
// Check that we are given 1 16-bit sample (2 bytes).
|
||||
const sampSize = 2
|
||||
if len(sample) != sampSize {
|
||||
return 0, fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), sampSize)
|
||||
// Check that we are given 1 sample.
|
||||
if len(sample) != byteDepth {
|
||||
return 0, fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), byteDepth)
|
||||
}
|
||||
|
||||
n, err := e.dest.Write(sample)
|
||||
n, err := e.dst.Write(sample)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
err = e.dest.WriteByte(byte(int16(e.index)))
|
||||
err = e.dst.WriteByte(byte(int16(e.index)))
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
|
||||
if pad {
|
||||
err = e.dest.WriteByte(0x01)
|
||||
err = e.dst.WriteByte(0x01)
|
||||
} else {
|
||||
err = e.dest.WriteByte(0x00)
|
||||
err = e.dst.WriteByte(0x00)
|
||||
}
|
||||
if err != nil {
|
||||
return n, err
|
||||
|
@ -238,9 +250,10 @@ func (e *encoder) calcHead(sample []byte, pad bool) (int, error) {
|
|||
|
||||
// init initializes the encoder's estimation to the first uncompressed sample and the index to
|
||||
// point to a suitable quantizer step size.
|
||||
// The suitable step size is the closest step size in the stepTable to half the absolute difference of the first two samples.
|
||||
func (e *encoder) init(samps []byte) {
|
||||
int1 := int16(binary.LittleEndian.Uint16(samps[0:2]))
|
||||
int2 := int16(binary.LittleEndian.Uint16(samps[2:4]))
|
||||
int1 := int16(binary.LittleEndian.Uint16(samps[:byteDepth]))
|
||||
int2 := int16(binary.LittleEndian.Uint16(samps[byteDepth:initBytes]))
|
||||
e.est = int1
|
||||
|
||||
halfDiff := math.Abs(math.Abs(float64(int1)) - math.Abs(float64(int2))/2.0)
|
||||
|
@ -255,43 +268,42 @@ func (e *encoder) init(samps []byte) {
|
|||
e.index = cInd
|
||||
}
|
||||
|
||||
// Write takes a slice of bytes of arbitrary length representing pcm and encodes in into adpcm.
|
||||
// It writes its output to the encoder's dest.
|
||||
// Write takes a slice of bytes of arbitrary length representing pcm and encodes it into adpcm.
|
||||
// It writes its output to the encoder's dst.
|
||||
// The number of bytes written out is returned along with any error that occured.
|
||||
func (e *encoder) Write(inPcm []byte) (int, error) {
|
||||
// Check that pcm has enough data to initialize decoder
|
||||
// Check that pcm has enough data to initialize decoder.
|
||||
pcmLen := len(inPcm)
|
||||
if pcmLen < 4 {
|
||||
return 0, fmt.Errorf("length of given byte array must be greater than 4")
|
||||
if pcmLen < initBytes {
|
||||
return 0, fmt.Errorf("length of given byte array must be >= %v", initBytes)
|
||||
}
|
||||
|
||||
// Determine if there will be a byte that won't contain two full nibbles and will need padding.
|
||||
pad := false
|
||||
if (pcmLen-2)%4 != 0 {
|
||||
if (pcmLen-byteDepth)%bytesPerEnc != 0 {
|
||||
pad = true
|
||||
}
|
||||
|
||||
e.init(inPcm[0:4])
|
||||
n, err := e.calcHead(inPcm[0:2], pad)
|
||||
e.init(inPcm[:initBytes])
|
||||
n, err := e.calcHead(inPcm[:byteDepth], pad)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
// Skip the first sample and start at the end of the first two samples, then every two samples encode them into a byte of adpcm.
|
||||
// TODO: make all hard coded numbers variables so that other bitrates and compression ratios can be used.
|
||||
for i := 5; i < pcmLen; i += 4 {
|
||||
nib1 := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[i-3 : i-1])))
|
||||
nib2 := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[i-1 : i+1])))
|
||||
err = e.dest.WriteByte(byte((nib2 << 4) | nib1))
|
||||
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))
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
}
|
||||
// If we've reached the end of the pcm data and there's a sample (2 bytes) left over,
|
||||
// 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-2 : pcmLen])))
|
||||
err = e.dest.WriteByte(nib)
|
||||
nib := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[pcmLen-byteDepth : pcmLen])))
|
||||
err = e.dst.WriteByte(nib)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
@ -300,15 +312,15 @@ func (e *encoder) Write(inPcm []byte) (int, error) {
|
|||
return n, nil
|
||||
}
|
||||
|
||||
// Write takes a slice of bytes of arbitrary length representing adpcm and decodes in into pcm.
|
||||
// It writes its output to the decoder's dest.
|
||||
// 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.
|
||||
func (d *decoder) Write(inAdpcm []byte) (int, error) {
|
||||
// Initialize decoder with first 4 bytes of the inAdpcm.
|
||||
d.est = int16(binary.LittleEndian.Uint16(inAdpcm[0:2]))
|
||||
d.index = int16(inAdpcm[2])
|
||||
d.est = int16(binary.LittleEndian.Uint16(inAdpcm[:byteDepth]))
|
||||
d.index = int16(inAdpcm[byteDepth])
|
||||
d.step = stepTable[d.index]
|
||||
n, err := d.dest.Write(inAdpcm[0:2])
|
||||
n, err := d.dst.Write(inAdpcm[:byteDepth])
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
@ -316,22 +328,22 @@ func (d *decoder) Write(inAdpcm []byte) (int, 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.
|
||||
// If padding flag is true (Adpcm[3]), only decode up until the last byte, then decode that separately.
|
||||
for i := 4; i < len(inAdpcm)-int(inAdpcm[3]); i++ {
|
||||
for i := headBytes; i < len(inAdpcm)-int(inAdpcm[3]); i++ {
|
||||
twoNibs := inAdpcm[i]
|
||||
nib2 := byte(twoNibs >> 4)
|
||||
nib1 := byte((nib2 << 4) ^ twoNibs)
|
||||
|
||||
firstBytes := make([]byte, 2)
|
||||
firstBytes := make([]byte, byteDepth)
|
||||
binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(nib1)))
|
||||
_n, err := d.dest.Write(firstBytes)
|
||||
_n, err := d.dst.Write(firstBytes)
|
||||
n += _n
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
secondBytes := make([]byte, 2)
|
||||
secondBytes := make([]byte, byteDepth)
|
||||
binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(nib2)))
|
||||
_n, err = d.dest.Write(secondBytes)
|
||||
_n, err = d.dst.Write(secondBytes)
|
||||
n += _n
|
||||
if err != nil {
|
||||
return n, err
|
||||
|
@ -339,9 +351,9 @@ func (d *decoder) Write(inAdpcm []byte) (int, error) {
|
|||
}
|
||||
if inAdpcm[3] == 0x01 {
|
||||
padNib := inAdpcm[len(inAdpcm)-1]
|
||||
samp := make([]byte, 2)
|
||||
samp := make([]byte, byteDepth)
|
||||
binary.LittleEndian.PutUint16(samp, uint16(d.decodeSample(padNib)))
|
||||
_n, err := d.dest.Write(samp)
|
||||
_n, err := d.dst.Write(samp)
|
||||
n += _n
|
||||
if err != nil {
|
||||
return n, err
|
||||
|
@ -355,9 +367,8 @@ func BytesOutput(pcm int) int {
|
|||
// for X pcm bytes, byteDepth (eg. 2 bytes) are 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.
|
||||
byteDepth := 2
|
||||
if pcm%2*byteDepth == 0 { // %2 because samples are encoded 2 at a time.
|
||||
return (pcm-byteDepth)/4 + byteDepth + 1 + 1 + 1
|
||||
if pcm%bytesPerEnc == 0 {
|
||||
return (pcm-byteDepth)/compFact + headBytes + 1
|
||||
}
|
||||
return (pcm-byteDepth)/4 + byteDepth + 1 + 1
|
||||
return (pcm-byteDepth)/compFact + headBytes
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue