From 7f9a919baaf4006c299eee1a53902d69478c9cc3 Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 12:11:26 +1030 Subject: [PATCH] ADPCM: moved commands and adpcm codec to appropriate folders --- cmd/adpcm/decode-pcm/decode-pcm.go | 45 ++++++ cmd/adpcm/encode-pcm/encode-pcm.go | 43 ++++++ stream/adpcm/adpcm.go | 216 +++++++++++++++++++++++++++++ 3 files changed, 304 insertions(+) create mode 100644 cmd/adpcm/decode-pcm/decode-pcm.go create mode 100644 cmd/adpcm/encode-pcm/encode-pcm.go create mode 100644 stream/adpcm/adpcm.go diff --git a/cmd/adpcm/decode-pcm/decode-pcm.go b/cmd/adpcm/decode-pcm/decode-pcm.go new file mode 100644 index 00000000..3c375de1 --- /dev/null +++ b/cmd/adpcm/decode-pcm/decode-pcm.go @@ -0,0 +1,45 @@ +package main + +import ( + "flag" + "io/ioutil" + + "bitbucket.org/ausocean/av/cmd/adpcm" +) + +// This program accepts an input file encoded in adpcm and outputs a decoded pcm file. +// Input and output file names can be specified as arguments. +func main() { + var inPath string + var outPath string + + flag.StringVar(&inPath, "in", "encoded.adpcm", "file path of input") + flag.StringVar(&outPath, "out", "decoded.pcm", "file path of output data") + flag.Parse() + //read adpcm + comp, err := ioutil.ReadFile(inPath) + if err != nil { + panic(err) + } + //decode adpcm + var decoded []byte + start := 0 + for i := 0; i < len(comp); i++ { + if i%256 == 255 { + block := comp[start : i+1] + decBlock, err := adpcm.DecodeBlock(block) + if err != nil { + //todo: use correct logging of error + panic(err) + } + decoded = append(decoded, decBlock...) + start = i + 1 + } + } + // save pcm to file + err = ioutil.WriteFile(outPath, decoded, 0644) + if err != nil { + panic(err) + } + +} diff --git a/cmd/adpcm/encode-pcm/encode-pcm.go b/cmd/adpcm/encode-pcm/encode-pcm.go new file mode 100644 index 00000000..57c9fd22 --- /dev/null +++ b/cmd/adpcm/encode-pcm/encode-pcm.go @@ -0,0 +1,43 @@ +package main + +import ( + "flag" + "io/ioutil" + + "bitbucket.org/ausocean/av/cmd/adpcm" +) + +func main() { + var inPath string + var adpcmPath string + flag.StringVar(&inPath, "in", "data.pcm", "file path of input data") + flag.StringVar(&adpcmPath, "out", "encoded.adpcm", "file path of output") + flag.Parse() + //read pcm + pcm, err := ioutil.ReadFile(inPath) + if err != nil { + panic(err) + } + + //encode adpcm + var comp []byte + start := 0 + for i := 0; i < len(pcm); i++ { + if i%1010 == 1009 { + block := pcm[start : i+1] + + encBlock, err := adpcm.EncodeBlock(block) + if err != nil { + //todo: use correct logging of error + panic(err) + } + comp = append(comp, encBlock...) + start = i + 1 + } + } + // save adpcm to file + err = ioutil.WriteFile(adpcmPath, comp, 0644) + if err != nil { + panic(err) + } +} diff --git a/stream/adpcm/adpcm.go b/stream/adpcm/adpcm.go new file mode 100644 index 00000000..27be2aa7 --- /dev/null +++ b/stream/adpcm/adpcm.go @@ -0,0 +1,216 @@ +package adpcm + +import ( + "encoding/binary" + "fmt" + "math" +) + +// table of index changes +var indexTable = []int16{ + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8, +} + +// quantize step size table +var stepTable = []int16{ + 7, 8, 9, 10, 11, 12, 13, 14, + 16, 17, 19, 21, 23, 25, 28, 31, + 34, 37, 41, 45, 50, 55, 60, 66, + 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, + 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, + 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, + 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, + 32767, +} + +var ( + encPred int16 + encIndex int16 + decPred int16 + decIndex int16 + decStep int16 = 7 +) + +// encodeSample takes a single 16 bit PCM sample and +// returns a byte of which the last 4 bits are an encoded ADPCM nibble +func encodeSample(sample int16) byte { + + // find difference from predicted sample + delta := sample - encPred + + // set sign bit and find absolute value of difference + var nibble byte + if delta < 0 { + nibble = 8 + delta = -delta + } + + step := stepTable[encIndex] + diff := step >> 3 + var mask byte = 4 + + // quantize delta down to four bits + for i := 0; i < 3; i++ { + if delta > step { + nibble |= mask + delta -= step + diff += step + } + mask >>= 1 + step >>= 1 + } + + // adjust predicted sample based on calculated difference + if nibble&8 != 0 { + encPred -= diff + } else { + encPred += diff + } + + // check for underflow and overflow + if encPred < math.MinInt16 { + encPred = math.MinInt16 + } else if encPred > math.MaxInt16 { + encPred = math.MaxInt16 + } + + encIndex += indexTable[nibble&7] + + // check for underflow and overflow + if encIndex < 0 { + encIndex = 0 + } else if encIndex > int16(len(stepTable)-1) { + encIndex = int16(len(stepTable) - 1) + } + + return nibble +} + +// decodeSample takes a byte, the last 4 bits of which contain a single +// 4 bit ADPCM nibble, and returns a 16 bit decoded PCM sample +func decodeSample(nibble byte) int16 { + + // compute predicted sample estimate (decPred): + + // calculate difference + var diff int16 + if nibble&4 != 0 { + diff += decStep + } + if nibble&2 != 0 { + diff += decStep >> 1 + } + if nibble&1 != 0 { + diff += decStep >> 2 + } + diff += decStep >> 3 + // account for sign bit + if nibble&8 != 0 { + diff = -diff + } + // adjust predicted sample based on calculated difference + decPred += diff + + // check for overflow and underflow + if decPred > math.MaxInt16 { + decPred = math.MaxInt16 + } else if decPred < math.MinInt16 { + decPred = math.MinInt16 + } + + // compute new step size: + + // adjust index into step size lookup table using nibble + decIndex += indexTable[nibble] + // check for overflow and underflow + if decIndex < 0 { + decIndex = 0 + } else if decIndex > int16(len(stepTable)-1) { + decIndex = int16(len(stepTable) - 1) + } + // find new quantizer step size + decStep = stepTable[decIndex] + + return decPred +} + +func calcHead(sample []byte) ([]byte, error) { + if len(sample) != 2 { + return nil, fmt.Errorf("length of given byte array is: %v, expected: 2", len(sample)) + } + + intSample := int16(binary.LittleEndian.Uint16(sample)) + + encodeSample(intSample) + + head := make([]byte, 2) + + head[0] = sample[0] + head[1] = sample[1] + + head = append(head, byte(uint16(encIndex))) + head = append(head, byte(0x00)) + return head, nil +} + +// EncodeBlock takes a slice of 1010 bytes (505 16-bit PCM samples). +// It returns a byte slice containing encoded (compressed) ADPCM nibbles (each byte contains two nibbles). +func EncodeBlock(block []byte) ([]byte, error) { + + if len(block) != 1010 { + return nil, fmt.Errorf("unsupported block size. Given: %v, expected: 1010, ie. 505 16-bit PCM samples", len(block)) + } + + result, err := calcHead(block[0:2]) + if err != nil { + return nil, err + } + + for i := 2; i < len(block); i++ { + if (i+1)%4 == 0 { + sample2 := encodeSample(int16(binary.LittleEndian.Uint16(block[i-1 : i+1]))) + sample := encodeSample(int16(binary.LittleEndian.Uint16(block[i+1 : i+3]))) + result = append(result, byte((sample<<4)|sample2)) + + } + } + + return result, nil +} + +// DecodeBlock takes a slice of 256 bytes, each byte should contain two ADPCM encoded nibbles. +// It returns a byte slice containing the resulting decoded (uncompressed) 16-bit PCM samples. +func DecodeBlock(block []byte) ([]byte, error) { + + if len(block) != 256 { + return nil, fmt.Errorf("unsupported block size. Given: %v, expected: 256", len(block)) + } + + var result []byte + decPred = int16(binary.LittleEndian.Uint16(block[0:2])) + decIndex = int16(block[2]) + decStep = stepTable[decIndex] + result = append(result, block[0:2]...) + + for i := 4; i < len(block); i++ { + originalSample := block[i] + secondSample := byte(originalSample >> 4) + firstSample := byte((secondSample << 4) ^ originalSample) + + firstBytes := make([]byte, 2) + binary.LittleEndian.PutUint16(firstBytes, uint16(decodeSample(firstSample))) + result = append(result, firstBytes...) + + secondBytes := make([]byte, 2) + binary.LittleEndian.PutUint16(secondBytes, uint16(decodeSample(secondSample))) + result = append(result, secondBytes...) + + } + + return result, nil +}