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.
|
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
|
package adpcm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -39,20 +40,32 @@ import (
|
||||||
"math"
|
"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.
|
// 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 {
|
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
|
est int16
|
||||||
index int16
|
index int16
|
||||||
}
|
}
|
||||||
|
|
||||||
// decoder is used to decode from ADPCM to PCM data.
|
// 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 {
|
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
|
est int16
|
||||||
index int16
|
index int16
|
||||||
step int16
|
step int16
|
||||||
|
@ -91,7 +104,7 @@ var stepTable = []int16{
|
||||||
// NewEncoder retuns a new ADPCM encoder.
|
// NewEncoder retuns a new ADPCM encoder.
|
||||||
func NewEncoder(dst *bytes.Buffer) *encoder {
|
func NewEncoder(dst *bytes.Buffer) *encoder {
|
||||||
e := encoder{
|
e := encoder{
|
||||||
dest: dst,
|
dst: dst,
|
||||||
}
|
}
|
||||||
return &e
|
return &e
|
||||||
}
|
}
|
||||||
|
@ -99,7 +112,7 @@ func NewEncoder(dst *bytes.Buffer) *encoder {
|
||||||
// NewDecoder retuns a new ADPCM decoder.
|
// NewDecoder retuns a new ADPCM decoder.
|
||||||
func NewDecoder(dst *bytes.Buffer) *decoder {
|
func NewDecoder(dst *bytes.Buffer) *decoder {
|
||||||
d := decoder{
|
d := decoder{
|
||||||
dest: dst,
|
dst: dst,
|
||||||
}
|
}
|
||||||
return &d
|
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
|
// 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).
|
// 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 (dest) along with any errors.
|
// 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) {
|
func (e *encoder) calcHead(sample []byte, pad bool) (int, error) {
|
||||||
// Check that we are given 1 16-bit sample (2 bytes).
|
// Check that we are given 1 sample.
|
||||||
const sampSize = 2
|
if len(sample) != byteDepth {
|
||||||
if len(sample) != sampSize {
|
return 0, fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), byteDepth)
|
||||||
return 0, fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), sampSize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := e.dest.Write(sample)
|
n, err := e.dst.Write(sample)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = e.dest.WriteByte(byte(int16(e.index)))
|
err = e.dst.WriteByte(byte(int16(e.index)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
n++
|
n++
|
||||||
|
|
||||||
if pad {
|
if pad {
|
||||||
err = e.dest.WriteByte(0x01)
|
err = e.dst.WriteByte(0x01)
|
||||||
} else {
|
} else {
|
||||||
err = e.dest.WriteByte(0x00)
|
err = e.dst.WriteByte(0x00)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, err
|
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
|
// init initializes the encoder's estimation to the first uncompressed sample and the index to
|
||||||
// point to a suitable quantizer step size.
|
// 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) {
|
func (e *encoder) init(samps []byte) {
|
||||||
int1 := int16(binary.LittleEndian.Uint16(samps[0:2]))
|
int1 := int16(binary.LittleEndian.Uint16(samps[:byteDepth]))
|
||||||
int2 := int16(binary.LittleEndian.Uint16(samps[2:4]))
|
int2 := int16(binary.LittleEndian.Uint16(samps[byteDepth:initBytes]))
|
||||||
e.est = int1
|
e.est = int1
|
||||||
|
|
||||||
halfDiff := math.Abs(math.Abs(float64(int1)) - math.Abs(float64(int2))/2.0)
|
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
|
e.index = cInd
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write takes a slice of bytes of arbitrary length representing pcm and encodes in into adpcm.
|
// Write takes a slice of bytes of arbitrary length representing pcm and encodes it into adpcm.
|
||||||
// It writes its output to the encoder's dest.
|
// It writes its output to the encoder's dst.
|
||||||
// The number of bytes written out is returned along with any error that occured.
|
// The number of bytes written out is returned along with any error that occured.
|
||||||
func (e *encoder) Write(inPcm []byte) (int, error) {
|
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)
|
pcmLen := len(inPcm)
|
||||||
if pcmLen < 4 {
|
if pcmLen < initBytes {
|
||||||
return 0, fmt.Errorf("length of given byte array must be greater than 4")
|
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.
|
// Determine if there will be a byte that won't contain two full nibbles and will need padding.
|
||||||
pad := false
|
pad := false
|
||||||
if (pcmLen-2)%4 != 0 {
|
if (pcmLen-byteDepth)%bytesPerEnc != 0 {
|
||||||
pad = true
|
pad = true
|
||||||
}
|
}
|
||||||
|
|
||||||
e.init(inPcm[0:4])
|
e.init(inPcm[:initBytes])
|
||||||
n, err := e.calcHead(inPcm[0:2], pad)
|
n, err := e.calcHead(inPcm[:byteDepth], pad)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, err
|
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.
|
// 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 := byteDepth; i+bytesPerEnc-1 < pcmLen; i += bytesPerEnc {
|
||||||
for i := 5; i < pcmLen; i += 4 {
|
nib1 := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[i : i+byteDepth])))
|
||||||
nib1 := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[i-3 : i-1])))
|
nib2 := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[i+byteDepth : i+bytesPerEnc])))
|
||||||
nib2 := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[i-1 : i+1])))
|
err = e.dst.WriteByte(byte((nib2 << 4) | nib1))
|
||||||
err = e.dest.WriteByte(byte((nib2 << 4) | nib1))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
n++
|
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.
|
// compress it to a nibble and leave the first half of the byte padded with 0s.
|
||||||
if pad {
|
if pad {
|
||||||
nib := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[pcmLen-2 : pcmLen])))
|
nib := e.encodeSample(int16(binary.LittleEndian.Uint16(inPcm[pcmLen-byteDepth : pcmLen])))
|
||||||
err = e.dest.WriteByte(nib)
|
err = e.dst.WriteByte(nib)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
@ -300,15 +312,15 @@ func (e *encoder) Write(inPcm []byte) (int, error) {
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write takes a slice of bytes of arbitrary length representing adpcm and decodes in into pcm.
|
// Write takes a slice of bytes of arbitrary length representing adpcm and decodes it into pcm.
|
||||||
// It writes its output to the decoder's dest.
|
// It writes its output to the decoder's dst.
|
||||||
// The number of bytes written out is returned along with any error that occured.
|
// The number of bytes written out is returned along with any error that occured.
|
||||||
func (d *decoder) Write(inAdpcm []byte) (int, error) {
|
func (d *decoder) Write(inAdpcm []byte) (int, error) {
|
||||||
// Initialize decoder with first 4 bytes of the inAdpcm.
|
// Initialize decoder with first 4 bytes of the inAdpcm.
|
||||||
d.est = int16(binary.LittleEndian.Uint16(inAdpcm[0:2]))
|
d.est = int16(binary.LittleEndian.Uint16(inAdpcm[:byteDepth]))
|
||||||
d.index = int16(inAdpcm[2])
|
d.index = int16(inAdpcm[byteDepth])
|
||||||
d.step = stepTable[d.index]
|
d.step = stepTable[d.index]
|
||||||
n, err := d.dest.Write(inAdpcm[0:2])
|
n, err := d.dst.Write(inAdpcm[:byteDepth])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, err
|
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),
|
// 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.
|
||||||
// If padding flag is true (Adpcm[3]), only decode up until the last byte, then decode that separately.
|
// 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]
|
twoNibs := inAdpcm[i]
|
||||||
nib2 := byte(twoNibs >> 4)
|
nib2 := byte(twoNibs >> 4)
|
||||||
nib1 := byte((nib2 << 4) ^ twoNibs)
|
nib1 := byte((nib2 << 4) ^ twoNibs)
|
||||||
|
|
||||||
firstBytes := make([]byte, 2)
|
firstBytes := make([]byte, byteDepth)
|
||||||
binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(nib1)))
|
binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(nib1)))
|
||||||
_n, err := d.dest.Write(firstBytes)
|
_n, err := d.dst.Write(firstBytes)
|
||||||
n += _n
|
n += _n
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
secondBytes := make([]byte, 2)
|
secondBytes := make([]byte, byteDepth)
|
||||||
binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(nib2)))
|
binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(nib2)))
|
||||||
_n, err = d.dest.Write(secondBytes)
|
_n, err = d.dst.Write(secondBytes)
|
||||||
n += _n
|
n += _n
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, err
|
return n, err
|
||||||
|
@ -339,9 +351,9 @@ func (d *decoder) Write(inAdpcm []byte) (int, error) {
|
||||||
}
|
}
|
||||||
if inAdpcm[3] == 0x01 {
|
if inAdpcm[3] == 0x01 {
|
||||||
padNib := inAdpcm[len(inAdpcm)-1]
|
padNib := inAdpcm[len(inAdpcm)-1]
|
||||||
samp := make([]byte, 2)
|
samp := make([]byte, byteDepth)
|
||||||
binary.LittleEndian.PutUint16(samp, uint16(d.decodeSample(padNib)))
|
binary.LittleEndian.PutUint16(samp, uint16(d.decodeSample(padNib)))
|
||||||
_n, err := d.dest.Write(samp)
|
_n, err := d.dst.Write(samp)
|
||||||
n += _n
|
n += _n
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, err
|
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
|
// 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.
|
// 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.
|
// 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%bytesPerEnc == 0 {
|
||||||
if pcm%2*byteDepth == 0 { // %2 because samples are encoded 2 at a time.
|
return (pcm-byteDepth)/compFact + headBytes + 1
|
||||||
return (pcm-byteDepth)/4 + byteDepth + 1 + 1 + 1
|
|
||||||
}
|
}
|
||||||
return (pcm-byteDepth)/4 + byteDepth + 1 + 1
|
return (pcm-byteDepth)/compFact + headBytes
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue