From b1d8c3888c2087d04b0cd78b5872c151fd124389 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 23 Jan 2019 16:33:28 +1030 Subject: [PATCH 01/32] adpcm test working with wav files --- cmd/adpcm/main.go | 80 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 cmd/adpcm/main.go diff --git a/cmd/adpcm/main.go b/cmd/adpcm/main.go new file mode 100644 index 00000000..1c4b05bf --- /dev/null +++ b/cmd/adpcm/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/bovarysme/adpcm" + "github.com/go-audio/audio" + "github.com/go-audio/wav" + "github.com/pkg/errors" +) + +// This program is for compare data size of pcm and adpcm, it takes a wav file and compresses and encodes it to adpcm, +// the adpcm byte array is stored as a file and then the adpcm is decoded and the result saved as final.wav + +func main() { + //open wav + wavPath := "out.wav" + f, err := os.Open(wavPath) + if err != nil { + panic(err) + } + defer f.Close() + //decode wav + dec := wav.NewDecoder(f) + if !dec.IsValidFile() { + panic(errors.Errorf("invalid WAV file %q", wavPath)) + } + sampleRate, nchannels, bps := int(dec.SampleRate), int(dec.NumChans), int(dec.BitDepth) + buf, err := dec.FullPCMBuffer() + if err != nil { + panic(err) + } + //encode adpcm + var comp []byte + adpcm.Encode(buf.Data, &comp) + // save to file + pcmPath := "out.adpcm" + err = ioutil.WriteFile(pcmPath, comp, 0644) + if err != nil { + panic(err) + } + //decode adpcm + var decoded []int + deco := adpcm.NewDecoder(nchannels) + deco.Decode(comp, &decoded) + + //encode wav and save to file + finalWavPath := "final.wav" + of, err := os.Create(finalWavPath) + if err != nil { + panic(err) + } + defer of.Close() + + sampleBytes := bps / 8 + + // normal uncompressed WAV format + wavformat := 1 + + enc := wav.NewEncoder(of, sampleRate, bps, nchannels, wavformat) + + format := &audio.Format{ + NumChannels: nchannels, + SampleRate: sampleRate, + } + + intBuf := &audio.IntBuffer{Data: decoded, Format: format, SourceBitDepth: sampleBytes * 8} + + if err := enc.Write(intBuf); err != nil { + panic(err) + } + + if err := enc.Close(); err != nil { + panic(err) + } + fmt.Printf("Saved audio to %s\n", finalWavPath) + +} From 2f24c84a1305481dcd845dcdd87410d53e8b5ebd Mon Sep 17 00:00:00 2001 From: Trek Date: Fri, 1 Feb 2019 01:07:24 +1030 Subject: [PATCH 02/32] adpcm: saving raw pcm before conversion --- cmd/adpcm/main.go | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/cmd/adpcm/main.go b/cmd/adpcm/main.go index 1c4b05bf..e3b852b0 100644 --- a/cmd/adpcm/main.go +++ b/cmd/adpcm/main.go @@ -1,6 +1,8 @@ package main import ( + "encoding/binary" + "flag" "fmt" "io/ioutil" "os" @@ -16,7 +18,9 @@ import ( func main() { //open wav - wavPath := "out.wav" + var wavPath string + flag.StringVar(&wavPath, "path", "before_compression.wav", "file path of input data") + flag.Parse() f, err := os.Open(wavPath) if err != nil { panic(err) @@ -32,12 +36,34 @@ func main() { if err != nil { panic(err) } + fmt.Println("Decoded wav. First 8 samples: ") + for i := 0; i < len(buf.Data) && i < 8; i++ { + fmt.Print(buf.Data[i], ", ") + } + fmt.Println() + // save pcm to file + var pcmBytes []byte + for _, sample := range buf.Data { + bs := make([]byte, 2) + binary.LittleEndian.PutUint16(bs, uint16(sample)) + pcmBytes = append(pcmBytes, bs[0], bs[1]) + } + pcmPath := "out.pcm" + err = ioutil.WriteFile(pcmPath, pcmBytes, 0644) + if err != nil { + panic(err) + } + fmt.Println("Saved raw PCM to " + pcmPath + ". First 8 bytes: ") + for i := 0; i < len(pcmBytes) && i < 8; i++ { + fmt.Print(pcmBytes[i], ", ") + } + fmt.Println() //encode adpcm var comp []byte adpcm.Encode(buf.Data, &comp) - // save to file - pcmPath := "out.adpcm" - err = ioutil.WriteFile(pcmPath, comp, 0644) + // save adpcm to file + adpcmPath := "out.adpcm" + err = ioutil.WriteFile(adpcmPath, comp, 0644) if err != nil { panic(err) } From 98b72bca12b5f33f52511cf1d3a3b66ac2f5c24e Mon Sep 17 00:00:00 2001 From: Trek Date: Tue, 5 Feb 2019 16:36:03 +1030 Subject: [PATCH 03/32] adpcmgo: added ported encode and decode functions --- cmd/adpcmgo/cmd/decode/decode.go | 70 +++++++++ .../cmd/decode_sample/decode_sample.go | 30 ++++ cmd/adpcmgo/cmd/encode/encode.go | 31 ++++ .../cmd/encode_decode/encode_decode.go} | 5 + .../cmd/encode_sample/encode_sample.go | 30 ++++ cmd/adpcmgo/codec.go | 137 ++++++++++++++++++ 6 files changed, 303 insertions(+) create mode 100644 cmd/adpcmgo/cmd/decode/decode.go create mode 100644 cmd/adpcmgo/cmd/decode_sample/decode_sample.go create mode 100644 cmd/adpcmgo/cmd/encode/encode.go rename cmd/{adpcm/main.go => adpcmgo/cmd/encode_decode/encode_decode.go} (94%) create mode 100644 cmd/adpcmgo/cmd/encode_sample/encode_sample.go create mode 100644 cmd/adpcmgo/codec.go diff --git a/cmd/adpcmgo/cmd/decode/decode.go b/cmd/adpcmgo/cmd/decode/decode.go new file mode 100644 index 00000000..087f3a3a --- /dev/null +++ b/cmd/adpcmgo/cmd/decode/decode.go @@ -0,0 +1,70 @@ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + + "github.com/bovarysme/adpcm" + "github.com/go-audio/audio" + "github.com/go-audio/wav" +) + +func main() { + var inPath string + var nChannels int + var rate int + var bps int + flag.StringVar(&inPath, "path", "out.adpcm", "file path of input data") + flag.IntVar(&nChannels, "channels", 1, "number of channels in adpcm data") + flag.IntVar(&rate, "rate", 8000, "number of channels in adpcm data") + flag.IntVar(&bps, "bps", 16, "number of channels in adpcm data") + flag.Parse() + //read adpcm + comp, err := ioutil.ReadFile(inPath) + if err != nil { + panic(err) + } + fmt.Println("Read ADPCM from " + inPath + ". First 8 bytes: ") + for i := 0; i < len(comp) && i < 16; i++ { + fmt.Print(comp[i], ", ") + } + fmt.Println() + //decode adpcm + var decoded []int + deco := adpcm.NewDecoder(nChannels) + deco.Decode(comp, &decoded) + + //encode wav and save to file + finalWavPath := "decoded.wav" + of, err := os.Create(finalWavPath) + if err != nil { + panic(err) + } + defer of.Close() + + sampleBytes := bps / 8 + + // normal uncompressed WAV format + wavformat := 1 + + enc := wav.NewEncoder(of, rate, bps, nChannels, wavformat) + + format := &audio.Format{ + NumChannels: nChannels, + SampleRate: rate, + } + + intBuf := &audio.IntBuffer{Data: decoded, Format: format, SourceBitDepth: sampleBytes * 8} + + if err := enc.Write(intBuf); err != nil { + panic(err) + } + + if err := enc.Close(); err != nil { + panic(err) + } + fmt.Printf("Saved audio to %s\n", finalWavPath) + +} diff --git a/cmd/adpcmgo/cmd/decode_sample/decode_sample.go b/cmd/adpcmgo/cmd/decode_sample/decode_sample.go new file mode 100644 index 00000000..9a487058 --- /dev/null +++ b/cmd/adpcmgo/cmd/decode_sample/decode_sample.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + + "bitbucket.org/ausocean/av/cmd/adpcmgo" +) + +func main() { + + var input byte = 4 + sample := adpcmgo.DecodeSample(input) + fmt.Println(sample) + + input = 5 + sample = adpcmgo.DecodeSample(input) + fmt.Println(sample) + + input = 6 + sample = adpcmgo.DecodeSample(input) + fmt.Println(sample) + + input = 16 + sample = adpcmgo.DecodeSample(input) + fmt.Println(sample) + + input = 235 + sample = adpcmgo.DecodeSample(input) + fmt.Println(sample) +} diff --git a/cmd/adpcmgo/cmd/encode/encode.go b/cmd/adpcmgo/cmd/encode/encode.go new file mode 100644 index 00000000..befb0aff --- /dev/null +++ b/cmd/adpcmgo/cmd/encode/encode.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "io/ioutil" + + "github.com/bovarysme/adpcm" +) + +// WIP +func main() { + //read pcm + pcm, err := ioutil.ReadFile(inPath) + if err != nil { + panic(err) + } + //encode adpcm + var comp []byte + adpcm.Encode(pcm, &comp) + // save adpcm to file + adpcmPath := "out.adpcm" + err = ioutil.WriteFile(adpcmPath, comp, 0644) + if err != nil { + panic(err) + } + fmt.Println("Saved ADPCM to " + adpcmPath + ". First 8 bytes: ") + for i := 0; i < len(comp) && i < 16; i++ { + fmt.Print(comp[i], ", ") + } + fmt.Println() +} diff --git a/cmd/adpcm/main.go b/cmd/adpcmgo/cmd/encode_decode/encode_decode.go similarity index 94% rename from cmd/adpcm/main.go rename to cmd/adpcmgo/cmd/encode_decode/encode_decode.go index e3b852b0..79c886f9 100644 --- a/cmd/adpcm/main.go +++ b/cmd/adpcmgo/cmd/encode_decode/encode_decode.go @@ -67,6 +67,11 @@ func main() { if err != nil { panic(err) } + fmt.Println("Saved ADPCM to " + adpcmPath + ". First 8 bytes: ") + for i := 0; i < len(comp) && i < 16; i++ { + fmt.Print(comp[i], ", ") + } + fmt.Println() //decode adpcm var decoded []int deco := adpcm.NewDecoder(nchannels) diff --git a/cmd/adpcmgo/cmd/encode_sample/encode_sample.go b/cmd/adpcmgo/cmd/encode_sample/encode_sample.go new file mode 100644 index 00000000..4dca508e --- /dev/null +++ b/cmd/adpcmgo/cmd/encode_sample/encode_sample.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + + "bitbucket.org/ausocean/av/cmd/adpcmgo" +) + +func main() { + + input := -370 + nibble := adpcmgo.EncodeSample(input) + fmt.Println(nibble) + + input = 8 + nibble = adpcmgo.EncodeSample(input) + fmt.Println(nibble) + + input = 1 + nibble = adpcmgo.EncodeSample(input) + fmt.Println(nibble) + + input = 31793 + nibble = adpcmgo.EncodeSample(input) + fmt.Println(nibble) + + input = -7386 + nibble = adpcmgo.EncodeSample(input) + fmt.Println(nibble) +} diff --git a/cmd/adpcmgo/codec.go b/cmd/adpcmgo/codec.go new file mode 100644 index 00000000..98e9196c --- /dev/null +++ b/cmd/adpcmgo/codec.go @@ -0,0 +1,137 @@ +package adpcmgo + +import ( + "fmt" + "math" +) + +// table of index changes +var indexTable = []int{ + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8, +} + +// quantize step size table +var stepTable = []int{ + 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 = 0 + encIndex = 0 + encStep = 7 + decPred = 0 + decIndex = 0 + decStep = 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 int) byte { + + // find difference from predicted sample + delta := sample - encPred + + // set sign bit and find absolute value of difference + var newSample byte + if delta < 0 { + newSample = 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 { + newSample |= mask + delta -= step + diff += step + } + mask >>= 1 + step >>= 1 + } + + // adjust predicted sample based on calculated difference + if newSample&8 != 0 { + encPred -= diff + } else { + encPred += diff + } + + // check for underflow and overflow + fmt.Print("max, min: ", math.MaxInt16) + fmt.Println(math.MinInt16) + if encPred < math.MinInt16 { + encPred = math.MinInt16 + } else if encPred > math.MaxInt16 { + encPred = math.MaxInt16 + } + + encIndex += indexTable[newSample&7] + + // check for underflow and overflow + if encIndex < 0 { + encIndex = 0 + } else if encIndex > len(stepTable)-1 { + encIndex = len(stepTable) - 1 + } + + return newSample +} + +// DecodeSample takes a byte, the last 4 bits of which contain a single +// 4 bit ADPCM value/sample, and returns a 16 bit decoded PCM sample +func DecodeSample(nibble byte) int { + + diff := 0 + + if nibble&4 != 0 { + diff += decStep + } + + if nibble&2 != 0 { + diff += decStep >> 1 + } + + if nibble&1 != 0 { + diff += decStep >> 2 + } + + diff += decStep >> 3 + + if nibble&8 != 0 { + diff = -diff + } + + decPred += diff + + if decPred > math.MaxInt16 { + decPred = math.MaxInt16 + } else if decPred < math.MinInt16 { + decPred = math.MinInt16 + } + + decIndex += indexTable[nibble] + if decIndex < 0 { + decIndex = 0 + } else if decIndex > len(stepTable)-1 { + decIndex = len(stepTable) - 1 + } + decStep = stepTable[decIndex] + + return decPred +} From a8d4e0db6ae8bd3fe04d8ca2563fe655d585389e Mon Sep 17 00:00:00 2001 From: Trek Date: Wed, 6 Feb 2019 15:39:14 +1030 Subject: [PATCH 04/32] adpcmgo: encodeblock function added --- cmd/adpcmgo/cmd/encode/encode.go | 22 +++++++- cmd/adpcmgo/codec.go | 96 ++++++++++++++++++++++---------- 2 files changed, 86 insertions(+), 32 deletions(-) diff --git a/cmd/adpcmgo/cmd/encode/encode.go b/cmd/adpcmgo/cmd/encode/encode.go index befb0aff..73a7e38d 100644 --- a/cmd/adpcmgo/cmd/encode/encode.go +++ b/cmd/adpcmgo/cmd/encode/encode.go @@ -1,22 +1,38 @@ package main import ( + "flag" "fmt" "io/ioutil" - "github.com/bovarysme/adpcm" + "bitbucket.org/ausocean/av/cmd/adpcmgo" ) -// WIP func main() { + var inPath string + flag.StringVar(&inPath, "path", "test.pcm", "file path of input data") + flag.Parse() //read pcm pcm, err := ioutil.ReadFile(inPath) if err != nil { panic(err) } + //encode adpcm var comp []byte - adpcm.Encode(pcm, &comp) + start := 0 + for i := 0; i < len(pcm); i++ { + if i%1010 == 1009 { + block := pcm[start : i+1] + encBlock, err := adpcmgo.EncodeBlock(block) + if err != nil { + //todo: use correct logging of error + panic(err) + } + comp = append(comp, encBlock...) + start = i + 1 + } + } // save adpcm to file adpcmPath := "out.adpcm" err = ioutil.WriteFile(adpcmPath, comp, 0644) diff --git a/cmd/adpcmgo/codec.go b/cmd/adpcmgo/codec.go index 98e9196c..e79d981f 100644 --- a/cmd/adpcmgo/codec.go +++ b/cmd/adpcmgo/codec.go @@ -1,18 +1,19 @@ package adpcmgo import ( + "encoding/binary" "fmt" "math" ) // table of index changes -var indexTable = []int{ +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 = []int{ +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, @@ -28,25 +29,24 @@ var stepTable = []int{ } var ( - encPred = 0 - encIndex = 0 - encStep = 7 - decPred = 0 - decIndex = 0 - decStep = 7 + 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 int) byte { +func EncodeSample(sample int16) byte { // find difference from predicted sample delta := sample - encPred // set sign bit and find absolute value of difference - var newSample byte + var nibble byte if delta < 0 { - newSample = 8 + nibble = 8 delta = -delta } @@ -57,7 +57,7 @@ func EncodeSample(sample int) byte { // quantize delta down to four bits for i := 0; i < 3; i++ { if delta > step { - newSample |= mask + nibble |= mask delta -= step diff += step } @@ -66,72 +66,110 @@ func EncodeSample(sample int) byte { } // adjust predicted sample based on calculated difference - if newSample&8 != 0 { + if nibble&8 != 0 { encPred -= diff } else { encPred += diff } // check for underflow and overflow - fmt.Print("max, min: ", math.MaxInt16) - fmt.Println(math.MinInt16) if encPred < math.MinInt16 { encPred = math.MinInt16 } else if encPred > math.MaxInt16 { encPred = math.MaxInt16 } - encIndex += indexTable[newSample&7] + encIndex += indexTable[nibble&7] // check for underflow and overflow if encIndex < 0 { encIndex = 0 - } else if encIndex > len(stepTable)-1 { - encIndex = len(stepTable) - 1 + } else if encIndex > int16(len(stepTable)-1) { + encIndex = int16(len(stepTable) - 1) } - return newSample + return nibble } // DecodeSample takes a byte, the last 4 bits of which contain a single -// 4 bit ADPCM value/sample, and returns a 16 bit decoded PCM sample -func DecodeSample(nibble byte) int { +// 4 bit ADPCM nibble, and returns a 16 bit decoded PCM sample +func DecodeSample(nibble byte) int16 { - diff := 0 + // 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 > len(stepTable)-1 { - decIndex = len(stepTable) - 1 + } 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 { + fmt.Println(sample[0], sample[1]) + intSample := int16(binary.LittleEndian.Uint16(sample)) + + fmt.Println("before enc", intSample) + + EncodeSample(intSample) + head := sample + + fmt.Println(encIndex) + + head = append(head, byte(uint16(encIndex))) + head = append(head, byte(0x00)) + return head +} + +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 := calcHead(block[0:2]) + + 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 +} From f03c8052d67ee7518711573f7f760aabf99460fc Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 10:02:24 +1030 Subject: [PATCH 05/32] ADPCM: Encoding and decoding blocks fully functional. --- cmd/adpcm/codec.go | 265 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 cmd/adpcm/codec.go diff --git a/cmd/adpcm/codec.go b/cmd/adpcm/codec.go new file mode 100644 index 00000000..b5042f83 --- /dev/null +++ b/cmd/adpcm/codec.go @@ -0,0 +1,265 @@ +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 + + pCount int + pNum = 8 +) + +// 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)) + + // if pCount < pNum { + // fmt.Println("calcHead enc", intSample) + // fmt.Println(encIndex, encPred) + // } + + EncodeSample(intSample) + + head := make([]byte, 2) + + head[0] = sample[0] + head[1] = sample[1] + + // if pCount < pNum { + // fmt.Println("after calcHead enc") + // fmt.Println(encIndex, encPred) + // } + + head = append(head, byte(uint16(encIndex))) + head = append(head, byte(0x00)) + return head, nil +} + +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 { + + // var intsample int16 + // if pCount < pNum { + // fmt.Println(block[2], block[3]) + // fmt.Println("first enc", block[i-1:i+1]) + // fmt.Println(encIndex, encPred) + // } + sample2 := EncodeSample(int16(binary.LittleEndian.Uint16(block[i-1 : i+1]))) + + // if pCount < pNum { + // intsample = int16(binary.LittleEndian.Uint16(block[i+1 : i+3])) + // fmt.Println("second enc", int16(intsample)) + // fmt.Println(encIndex, encPred) + // } + sample := EncodeSample(int16(binary.LittleEndian.Uint16(block[i+1 : i+3]))) + + // if pCount < pNum { + // fmt.Println("after enc") + // fmt.Println(encIndex, encPred) + // fmt.Println() + // } + + result = append(result, byte((sample<<4)|sample2)) + + pCount++ + + } + } + + return result, nil +} + +func DecodeBlock(block []byte) ([]byte, error) { + + if len(block) != 256 { + return nil, fmt.Errorf("unsupported block size. Given: %v, expected: 256", len(block)) + } + + if pCount < pNum { + fmt.Println("pre dec", block[0:8]) + fmt.Println(decIndex, decPred, decStep) + } + + 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) + + if pCount < pNum { + fmt.Println("first dec", firstSample, originalSample, len(block)) + fmt.Println(decIndex, decPred, decStep) + } + + firstBytes := make([]byte, 2) + binary.LittleEndian.PutUint16(firstBytes, uint16(DecodeSample(firstSample))) + result = append(result, firstBytes...) + + if pCount < pNum { + fmt.Println("second dec", secondSample) + fmt.Println(decIndex, decPred, decStep) + fmt.Println() + } + + secondBytes := make([]byte, 2) + binary.LittleEndian.PutUint16(secondBytes, uint16(DecodeSample(secondSample))) + result = append(result, secondBytes...) + + pCount++ + + } + + return result, nil +} From 5a2129f00f275faf12505c620fb6eca002478962 Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 10:10:12 +1030 Subject: [PATCH 06/32] ADPCM: added programs to test encoding and decoding of raw PCM files --- cmd/adpcm/cmd/decode/decode.go | 59 ++++++++++++++++++++++++++++++++++ cmd/adpcm/cmd/encode/encode.go | 53 ++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 cmd/adpcm/cmd/decode/decode.go create mode 100644 cmd/adpcm/cmd/encode/encode.go diff --git a/cmd/adpcm/cmd/decode/decode.go b/cmd/adpcm/cmd/decode/decode.go new file mode 100644 index 00000000..69f55231 --- /dev/null +++ b/cmd/adpcm/cmd/decode/decode.go @@ -0,0 +1,59 @@ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + + "bitbucket.org/ausocean/av/cmd/adpcm" +) + +func main() { + var inPath string + var outPath string + var nChannels int + var rate int + var bps int + flag.StringVar(&inPath, "in", "encoded.adpcm", "file path of input") + flag.StringVar(&outPath, "out", "decoded.pcm", "file path of output data") + flag.IntVar(&nChannels, "channels", 1, "number of channels in adpcm data") + flag.IntVar(&rate, "rate", 8000, "number of channels in adpcm data") + flag.IntVar(&bps, "bps", 16, "number of channels in adpcm data") + flag.Parse() + //read adpcm + comp, err := ioutil.ReadFile(inPath) + if err != nil { + panic(err) + } + fmt.Println("Read ADPCM from " + inPath + ". First 8 bytes: ") + for i := 0; i < len(comp) && i < 16; i++ { + fmt.Print(comp[i], ", ") + } + fmt.Println() + //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) + } + fmt.Println("Saved PCM to " + outPath + ". First 16 bytes: ") + for i := 0; i < len(decoded) && i < 16; i++ { + fmt.Print(decoded[i], " ") + } + fmt.Println() + +} diff --git a/cmd/adpcm/cmd/encode/encode.go b/cmd/adpcm/cmd/encode/encode.go new file mode 100644 index 00000000..84da6a39 --- /dev/null +++ b/cmd/adpcm/cmd/encode/encode.go @@ -0,0 +1,53 @@ +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 + // tCount := 0 + // tNum := 1 + for i := 0; i < len(pcm); i++ { + if i%1010 == 1009 { + block := pcm[start : i+1] + // if tCount < tNum { + // fmt.Println(block[0:16]) + // tCount++ + // } + 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) + } + // fmt.Println("Saved ADPCM to " + adpcmPath + ". First 16 bytes: ") + // for i := 0; i < len(comp) && i < 16; i++ { + // fmt.Println(comp[i]) + // } + // fmt.Println() +} From eabea6ce267a176181e165fb5e00c621eaecb757 Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 11:01:38 +1030 Subject: [PATCH 07/32] ADPCM: removed unneccessary code for commands and updated their file names --- cmd/adpcm/cmd/decode-pcm/decode-pcm.go | 45 ++++++++++++++++++++++++++ cmd/adpcm/cmd/encode-pcm/encode-pcm.go | 43 ++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 cmd/adpcm/cmd/decode-pcm/decode-pcm.go create mode 100644 cmd/adpcm/cmd/encode-pcm/encode-pcm.go diff --git a/cmd/adpcm/cmd/decode-pcm/decode-pcm.go b/cmd/adpcm/cmd/decode-pcm/decode-pcm.go new file mode 100644 index 00000000..3c375de1 --- /dev/null +++ b/cmd/adpcm/cmd/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/cmd/encode-pcm/encode-pcm.go b/cmd/adpcm/cmd/encode-pcm/encode-pcm.go new file mode 100644 index 00000000..57c9fd22 --- /dev/null +++ b/cmd/adpcm/cmd/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) + } +} From 2c24f7270317f557cd84783816645fe950c4c4b8 Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 11:35:16 +1030 Subject: [PATCH 08/32] ADPCM: removed unneccessary debug code and added comments for exported functions --- cmd/adpcm/adpcm.go | 216 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 cmd/adpcm/adpcm.go diff --git a/cmd/adpcm/adpcm.go b/cmd/adpcm/adpcm.go new file mode 100644 index 00000000..27be2aa7 --- /dev/null +++ b/cmd/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 +} From a447a2022989e8194939a766fa0d98096a53dcdd Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 12:11:26 +1030 Subject: [PATCH 09/32] 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 +} From a68b8ec5de14ede3988aca0b0d1f281c83c50bae Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 15:25:10 +1030 Subject: [PATCH 10/32] ADPCM: removed todo comments --- cmd/adpcm/decode-pcm/decode-pcm.go | 4 +-- cmd/adpcm/encode-pcm/encode-pcm.go | 6 ++--- stream/adpcm/adpcm_test.go | 42 ++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 stream/adpcm/adpcm_test.go diff --git a/cmd/adpcm/decode-pcm/decode-pcm.go b/cmd/adpcm/decode-pcm/decode-pcm.go index 3c375de1..81b2d969 100644 --- a/cmd/adpcm/decode-pcm/decode-pcm.go +++ b/cmd/adpcm/decode-pcm/decode-pcm.go @@ -4,7 +4,7 @@ import ( "flag" "io/ioutil" - "bitbucket.org/ausocean/av/cmd/adpcm" + "bitbucket.org/ausocean/av/stream/adpcm" ) // This program accepts an input file encoded in adpcm and outputs a decoded pcm file. @@ -29,7 +29,6 @@ func main() { 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...) @@ -41,5 +40,4 @@ func main() { if err != nil { panic(err) } - } diff --git a/cmd/adpcm/encode-pcm/encode-pcm.go b/cmd/adpcm/encode-pcm/encode-pcm.go index 57c9fd22..f8812dd3 100644 --- a/cmd/adpcm/encode-pcm/encode-pcm.go +++ b/cmd/adpcm/encode-pcm/encode-pcm.go @@ -4,7 +4,7 @@ import ( "flag" "io/ioutil" - "bitbucket.org/ausocean/av/cmd/adpcm" + "bitbucket.org/ausocean/av/stream/adpcm" ) func main() { @@ -22,13 +22,13 @@ func main() { //encode adpcm var comp []byte start := 0 + bSize := 1010 for i := 0; i < len(pcm); i++ { - if i%1010 == 1009 { + if i%bSize == bSize-1 { 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...) diff --git a/stream/adpcm/adpcm_test.go b/stream/adpcm/adpcm_test.go new file mode 100644 index 00000000..96c4a556 --- /dev/null +++ b/stream/adpcm/adpcm_test.go @@ -0,0 +1,42 @@ +package adpcm + +import ( + "bytes" + "io/ioutil" + "testing" +) + +func TestEncodeBlock(t *testing.T) { + //read input pcm + pcm, err := ioutil.ReadFile("../../../test/test-data/av/input/raw-voice.pcm") + if err != nil { + t.Errorf("Unable to read input PCM file: %v", 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 := EncodeBlock(block) + if err != nil { + //todo: use correct logging of error + t.Errorf("Unable to encode block: %v", err) + } + comp = append(comp, encBlock...) + start = i + 1 + } + } + + //read expected adpcm file + exp, err := ioutil.ReadFile("../../../test/test-data/av/output/encoded-voice.adpcm") + if err != nil { + t.Errorf("Unable to read expected ADPCM file: %v", err) + } + + if !bytes.Equal(comp, exp) { + t.Error("ADPCM generated does not match expected ADPCM") + } +} From 317376cd54381b14b106d77155e815096fd0536c Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 15:47:45 +1030 Subject: [PATCH 11/32] ADPCM: removed unneeded test programs --- cmd/adpcm/cmd/decode-pcm/decode-pcm.go | 45 -------------------- cmd/adpcm/cmd/decode/decode.go | 59 -------------------------- cmd/adpcm/cmd/encode-pcm/encode-pcm.go | 43 ------------------- cmd/adpcm/cmd/encode/encode.go | 53 ----------------------- 4 files changed, 200 deletions(-) delete mode 100644 cmd/adpcm/cmd/decode-pcm/decode-pcm.go delete mode 100644 cmd/adpcm/cmd/decode/decode.go delete mode 100644 cmd/adpcm/cmd/encode-pcm/encode-pcm.go delete mode 100644 cmd/adpcm/cmd/encode/encode.go diff --git a/cmd/adpcm/cmd/decode-pcm/decode-pcm.go b/cmd/adpcm/cmd/decode-pcm/decode-pcm.go deleted file mode 100644 index 3c375de1..00000000 --- a/cmd/adpcm/cmd/decode-pcm/decode-pcm.go +++ /dev/null @@ -1,45 +0,0 @@ -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/cmd/decode/decode.go b/cmd/adpcm/cmd/decode/decode.go deleted file mode 100644 index 69f55231..00000000 --- a/cmd/adpcm/cmd/decode/decode.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "io/ioutil" - - "bitbucket.org/ausocean/av/cmd/adpcm" -) - -func main() { - var inPath string - var outPath string - var nChannels int - var rate int - var bps int - flag.StringVar(&inPath, "in", "encoded.adpcm", "file path of input") - flag.StringVar(&outPath, "out", "decoded.pcm", "file path of output data") - flag.IntVar(&nChannels, "channels", 1, "number of channels in adpcm data") - flag.IntVar(&rate, "rate", 8000, "number of channels in adpcm data") - flag.IntVar(&bps, "bps", 16, "number of channels in adpcm data") - flag.Parse() - //read adpcm - comp, err := ioutil.ReadFile(inPath) - if err != nil { - panic(err) - } - fmt.Println("Read ADPCM from " + inPath + ". First 8 bytes: ") - for i := 0; i < len(comp) && i < 16; i++ { - fmt.Print(comp[i], ", ") - } - fmt.Println() - //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) - } - fmt.Println("Saved PCM to " + outPath + ". First 16 bytes: ") - for i := 0; i < len(decoded) && i < 16; i++ { - fmt.Print(decoded[i], " ") - } - fmt.Println() - -} diff --git a/cmd/adpcm/cmd/encode-pcm/encode-pcm.go b/cmd/adpcm/cmd/encode-pcm/encode-pcm.go deleted file mode 100644 index 57c9fd22..00000000 --- a/cmd/adpcm/cmd/encode-pcm/encode-pcm.go +++ /dev/null @@ -1,43 +0,0 @@ -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/cmd/adpcm/cmd/encode/encode.go b/cmd/adpcm/cmd/encode/encode.go deleted file mode 100644 index 84da6a39..00000000 --- a/cmd/adpcm/cmd/encode/encode.go +++ /dev/null @@ -1,53 +0,0 @@ -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 - // tCount := 0 - // tNum := 1 - for i := 0; i < len(pcm); i++ { - if i%1010 == 1009 { - block := pcm[start : i+1] - // if tCount < tNum { - // fmt.Println(block[0:16]) - // tCount++ - // } - 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) - } - // fmt.Println("Saved ADPCM to " + adpcmPath + ". First 16 bytes: ") - // for i := 0; i < len(comp) && i < 16; i++ { - // fmt.Println(comp[i]) - // } - // fmt.Println() -} From ec06c0759d98a60763e1708b24066773f00d4e78 Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 15:58:33 +1030 Subject: [PATCH 12/32] ADPCM: removed unneeded files and renamed files --- cmd/adpcm/codec.go | 265 ------------------ cmd/adpcmgo/cmd/decode/decode.go | 70 ----- .../cmd/decode_sample/decode_sample.go | 30 -- cmd/adpcmgo/cmd/encode/encode.go | 47 ---- .../cmd/encode_decode/encode_decode.go | 111 -------- .../cmd/encode_sample/encode_sample.go | 30 -- cmd/adpcmgo/codec.go | 175 ------------ 7 files changed, 728 deletions(-) delete mode 100644 cmd/adpcm/codec.go delete mode 100644 cmd/adpcmgo/cmd/decode/decode.go delete mode 100644 cmd/adpcmgo/cmd/decode_sample/decode_sample.go delete mode 100644 cmd/adpcmgo/cmd/encode/encode.go delete mode 100644 cmd/adpcmgo/cmd/encode_decode/encode_decode.go delete mode 100644 cmd/adpcmgo/cmd/encode_sample/encode_sample.go delete mode 100644 cmd/adpcmgo/codec.go diff --git a/cmd/adpcm/codec.go b/cmd/adpcm/codec.go deleted file mode 100644 index b5042f83..00000000 --- a/cmd/adpcm/codec.go +++ /dev/null @@ -1,265 +0,0 @@ -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 - - pCount int - pNum = 8 -) - -// 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)) - - // if pCount < pNum { - // fmt.Println("calcHead enc", intSample) - // fmt.Println(encIndex, encPred) - // } - - EncodeSample(intSample) - - head := make([]byte, 2) - - head[0] = sample[0] - head[1] = sample[1] - - // if pCount < pNum { - // fmt.Println("after calcHead enc") - // fmt.Println(encIndex, encPred) - // } - - head = append(head, byte(uint16(encIndex))) - head = append(head, byte(0x00)) - return head, nil -} - -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 { - - // var intsample int16 - // if pCount < pNum { - // fmt.Println(block[2], block[3]) - // fmt.Println("first enc", block[i-1:i+1]) - // fmt.Println(encIndex, encPred) - // } - sample2 := EncodeSample(int16(binary.LittleEndian.Uint16(block[i-1 : i+1]))) - - // if pCount < pNum { - // intsample = int16(binary.LittleEndian.Uint16(block[i+1 : i+3])) - // fmt.Println("second enc", int16(intsample)) - // fmt.Println(encIndex, encPred) - // } - sample := EncodeSample(int16(binary.LittleEndian.Uint16(block[i+1 : i+3]))) - - // if pCount < pNum { - // fmt.Println("after enc") - // fmt.Println(encIndex, encPred) - // fmt.Println() - // } - - result = append(result, byte((sample<<4)|sample2)) - - pCount++ - - } - } - - return result, nil -} - -func DecodeBlock(block []byte) ([]byte, error) { - - if len(block) != 256 { - return nil, fmt.Errorf("unsupported block size. Given: %v, expected: 256", len(block)) - } - - if pCount < pNum { - fmt.Println("pre dec", block[0:8]) - fmt.Println(decIndex, decPred, decStep) - } - - 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) - - if pCount < pNum { - fmt.Println("first dec", firstSample, originalSample, len(block)) - fmt.Println(decIndex, decPred, decStep) - } - - firstBytes := make([]byte, 2) - binary.LittleEndian.PutUint16(firstBytes, uint16(DecodeSample(firstSample))) - result = append(result, firstBytes...) - - if pCount < pNum { - fmt.Println("second dec", secondSample) - fmt.Println(decIndex, decPred, decStep) - fmt.Println() - } - - secondBytes := make([]byte, 2) - binary.LittleEndian.PutUint16(secondBytes, uint16(DecodeSample(secondSample))) - result = append(result, secondBytes...) - - pCount++ - - } - - return result, nil -} diff --git a/cmd/adpcmgo/cmd/decode/decode.go b/cmd/adpcmgo/cmd/decode/decode.go deleted file mode 100644 index 087f3a3a..00000000 --- a/cmd/adpcmgo/cmd/decode/decode.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "io/ioutil" - "os" - - "github.com/bovarysme/adpcm" - "github.com/go-audio/audio" - "github.com/go-audio/wav" -) - -func main() { - var inPath string - var nChannels int - var rate int - var bps int - flag.StringVar(&inPath, "path", "out.adpcm", "file path of input data") - flag.IntVar(&nChannels, "channels", 1, "number of channels in adpcm data") - flag.IntVar(&rate, "rate", 8000, "number of channels in adpcm data") - flag.IntVar(&bps, "bps", 16, "number of channels in adpcm data") - flag.Parse() - //read adpcm - comp, err := ioutil.ReadFile(inPath) - if err != nil { - panic(err) - } - fmt.Println("Read ADPCM from " + inPath + ". First 8 bytes: ") - for i := 0; i < len(comp) && i < 16; i++ { - fmt.Print(comp[i], ", ") - } - fmt.Println() - //decode adpcm - var decoded []int - deco := adpcm.NewDecoder(nChannels) - deco.Decode(comp, &decoded) - - //encode wav and save to file - finalWavPath := "decoded.wav" - of, err := os.Create(finalWavPath) - if err != nil { - panic(err) - } - defer of.Close() - - sampleBytes := bps / 8 - - // normal uncompressed WAV format - wavformat := 1 - - enc := wav.NewEncoder(of, rate, bps, nChannels, wavformat) - - format := &audio.Format{ - NumChannels: nChannels, - SampleRate: rate, - } - - intBuf := &audio.IntBuffer{Data: decoded, Format: format, SourceBitDepth: sampleBytes * 8} - - if err := enc.Write(intBuf); err != nil { - panic(err) - } - - if err := enc.Close(); err != nil { - panic(err) - } - fmt.Printf("Saved audio to %s\n", finalWavPath) - -} diff --git a/cmd/adpcmgo/cmd/decode_sample/decode_sample.go b/cmd/adpcmgo/cmd/decode_sample/decode_sample.go deleted file mode 100644 index 9a487058..00000000 --- a/cmd/adpcmgo/cmd/decode_sample/decode_sample.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "fmt" - - "bitbucket.org/ausocean/av/cmd/adpcmgo" -) - -func main() { - - var input byte = 4 - sample := adpcmgo.DecodeSample(input) - fmt.Println(sample) - - input = 5 - sample = adpcmgo.DecodeSample(input) - fmt.Println(sample) - - input = 6 - sample = adpcmgo.DecodeSample(input) - fmt.Println(sample) - - input = 16 - sample = adpcmgo.DecodeSample(input) - fmt.Println(sample) - - input = 235 - sample = adpcmgo.DecodeSample(input) - fmt.Println(sample) -} diff --git a/cmd/adpcmgo/cmd/encode/encode.go b/cmd/adpcmgo/cmd/encode/encode.go deleted file mode 100644 index 73a7e38d..00000000 --- a/cmd/adpcmgo/cmd/encode/encode.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "io/ioutil" - - "bitbucket.org/ausocean/av/cmd/adpcmgo" -) - -func main() { - var inPath string - flag.StringVar(&inPath, "path", "test.pcm", "file path of input data") - 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 := adpcmgo.EncodeBlock(block) - if err != nil { - //todo: use correct logging of error - panic(err) - } - comp = append(comp, encBlock...) - start = i + 1 - } - } - // save adpcm to file - adpcmPath := "out.adpcm" - err = ioutil.WriteFile(adpcmPath, comp, 0644) - if err != nil { - panic(err) - } - fmt.Println("Saved ADPCM to " + adpcmPath + ". First 8 bytes: ") - for i := 0; i < len(comp) && i < 16; i++ { - fmt.Print(comp[i], ", ") - } - fmt.Println() -} diff --git a/cmd/adpcmgo/cmd/encode_decode/encode_decode.go b/cmd/adpcmgo/cmd/encode_decode/encode_decode.go deleted file mode 100644 index 79c886f9..00000000 --- a/cmd/adpcmgo/cmd/encode_decode/encode_decode.go +++ /dev/null @@ -1,111 +0,0 @@ -package main - -import ( - "encoding/binary" - "flag" - "fmt" - "io/ioutil" - "os" - - "github.com/bovarysme/adpcm" - "github.com/go-audio/audio" - "github.com/go-audio/wav" - "github.com/pkg/errors" -) - -// This program is for compare data size of pcm and adpcm, it takes a wav file and compresses and encodes it to adpcm, -// the adpcm byte array is stored as a file and then the adpcm is decoded and the result saved as final.wav - -func main() { - //open wav - var wavPath string - flag.StringVar(&wavPath, "path", "before_compression.wav", "file path of input data") - flag.Parse() - f, err := os.Open(wavPath) - if err != nil { - panic(err) - } - defer f.Close() - //decode wav - dec := wav.NewDecoder(f) - if !dec.IsValidFile() { - panic(errors.Errorf("invalid WAV file %q", wavPath)) - } - sampleRate, nchannels, bps := int(dec.SampleRate), int(dec.NumChans), int(dec.BitDepth) - buf, err := dec.FullPCMBuffer() - if err != nil { - panic(err) - } - fmt.Println("Decoded wav. First 8 samples: ") - for i := 0; i < len(buf.Data) && i < 8; i++ { - fmt.Print(buf.Data[i], ", ") - } - fmt.Println() - // save pcm to file - var pcmBytes []byte - for _, sample := range buf.Data { - bs := make([]byte, 2) - binary.LittleEndian.PutUint16(bs, uint16(sample)) - pcmBytes = append(pcmBytes, bs[0], bs[1]) - } - pcmPath := "out.pcm" - err = ioutil.WriteFile(pcmPath, pcmBytes, 0644) - if err != nil { - panic(err) - } - fmt.Println("Saved raw PCM to " + pcmPath + ". First 8 bytes: ") - for i := 0; i < len(pcmBytes) && i < 8; i++ { - fmt.Print(pcmBytes[i], ", ") - } - fmt.Println() - //encode adpcm - var comp []byte - adpcm.Encode(buf.Data, &comp) - // save adpcm to file - adpcmPath := "out.adpcm" - err = ioutil.WriteFile(adpcmPath, comp, 0644) - if err != nil { - panic(err) - } - fmt.Println("Saved ADPCM to " + adpcmPath + ". First 8 bytes: ") - for i := 0; i < len(comp) && i < 16; i++ { - fmt.Print(comp[i], ", ") - } - fmt.Println() - //decode adpcm - var decoded []int - deco := adpcm.NewDecoder(nchannels) - deco.Decode(comp, &decoded) - - //encode wav and save to file - finalWavPath := "final.wav" - of, err := os.Create(finalWavPath) - if err != nil { - panic(err) - } - defer of.Close() - - sampleBytes := bps / 8 - - // normal uncompressed WAV format - wavformat := 1 - - enc := wav.NewEncoder(of, sampleRate, bps, nchannels, wavformat) - - format := &audio.Format{ - NumChannels: nchannels, - SampleRate: sampleRate, - } - - intBuf := &audio.IntBuffer{Data: decoded, Format: format, SourceBitDepth: sampleBytes * 8} - - if err := enc.Write(intBuf); err != nil { - panic(err) - } - - if err := enc.Close(); err != nil { - panic(err) - } - fmt.Printf("Saved audio to %s\n", finalWavPath) - -} diff --git a/cmd/adpcmgo/cmd/encode_sample/encode_sample.go b/cmd/adpcmgo/cmd/encode_sample/encode_sample.go deleted file mode 100644 index 4dca508e..00000000 --- a/cmd/adpcmgo/cmd/encode_sample/encode_sample.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "fmt" - - "bitbucket.org/ausocean/av/cmd/adpcmgo" -) - -func main() { - - input := -370 - nibble := adpcmgo.EncodeSample(input) - fmt.Println(nibble) - - input = 8 - nibble = adpcmgo.EncodeSample(input) - fmt.Println(nibble) - - input = 1 - nibble = adpcmgo.EncodeSample(input) - fmt.Println(nibble) - - input = 31793 - nibble = adpcmgo.EncodeSample(input) - fmt.Println(nibble) - - input = -7386 - nibble = adpcmgo.EncodeSample(input) - fmt.Println(nibble) -} diff --git a/cmd/adpcmgo/codec.go b/cmd/adpcmgo/codec.go deleted file mode 100644 index e79d981f..00000000 --- a/cmd/adpcmgo/codec.go +++ /dev/null @@ -1,175 +0,0 @@ -package adpcmgo - -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 { - fmt.Println(sample[0], sample[1]) - intSample := int16(binary.LittleEndian.Uint16(sample)) - - fmt.Println("before enc", intSample) - - EncodeSample(intSample) - head := sample - - fmt.Println(encIndex) - - head = append(head, byte(uint16(encIndex))) - head = append(head, byte(0x00)) - return head -} - -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 := calcHead(block[0:2]) - - 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 -} From eed8cf7b3fb2423e49b0be23e73649ad5d99d87b Mon Sep 17 00:00:00 2001 From: Trek H Date: Tue, 12 Feb 2019 14:49:16 +1030 Subject: [PATCH 13/32] ADPCM: added decode test, named constants and added comments --- cmd/adpcm/adpcm.go | 216 ------------------------------------- stream/adpcm/adpcm.go | 24 +++-- stream/adpcm/adpcm_test.go | 37 ++++++- 3 files changed, 48 insertions(+), 229 deletions(-) delete mode 100644 cmd/adpcm/adpcm.go diff --git a/cmd/adpcm/adpcm.go b/cmd/adpcm/adpcm.go deleted file mode 100644 index 27be2aa7..00000000 --- a/cmd/adpcm/adpcm.go +++ /dev/null @@ -1,216 +0,0 @@ -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 -} diff --git a/stream/adpcm/adpcm.go b/stream/adpcm/adpcm.go index 27be2aa7..61259de0 100644 --- a/stream/adpcm/adpcm.go +++ b/stream/adpcm/adpcm.go @@ -40,11 +40,11 @@ var ( // 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 + + // set sign bit and find absolute value of difference if delta < 0 { nibble = 8 delta = -delta @@ -54,7 +54,6 @@ func encodeSample(sample int16) byte { diff := step >> 3 var mask byte = 4 - // quantize delta down to four bits for i := 0; i < 3; i++ { if delta > step { nibble |= mask @@ -140,16 +139,17 @@ func decodeSample(nibble byte) int16 { } 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)) + + // check that we are given 1 16-bit sample (2 bytes) + sampSize := 2 + if len(sample) != sampSize { + return nil, fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), sampSize) } intSample := int16(binary.LittleEndian.Uint16(sample)) - encodeSample(intSample) head := make([]byte, 2) - head[0] = sample[0] head[1] = sample[1] @@ -162,8 +162,9 @@ func calcHead(sample []byte) ([]byte, error) { // 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)) + bSize := 1010 + if len(block) != bSize { + return nil, fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), bSize) } result, err := calcHead(block[0:2]) @@ -187,8 +188,9 @@ func EncodeBlock(block []byte) ([]byte, error) { // 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)) + bSize := 256 + if len(block) != bSize { + return nil, fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), bSize) } var result []byte diff --git a/stream/adpcm/adpcm_test.go b/stream/adpcm/adpcm_test.go index 96c4a556..c83732c3 100644 --- a/stream/adpcm/adpcm_test.go +++ b/stream/adpcm/adpcm_test.go @@ -6,13 +6,14 @@ import ( "testing" ) +// TestEncodeBlock will read PCM data, encode it in blocks and generate ADPCM +// then compare the result with expected ADPCM. func TestEncodeBlock(t *testing.T) { //read input pcm pcm, err := ioutil.ReadFile("../../../test/test-data/av/input/raw-voice.pcm") if err != nil { t.Errorf("Unable to read input PCM file: %v", err) } - //encode adpcm var comp []byte start := 0 @@ -29,7 +30,6 @@ func TestEncodeBlock(t *testing.T) { start = i + 1 } } - //read expected adpcm file exp, err := ioutil.ReadFile("../../../test/test-data/av/output/encoded-voice.adpcm") if err != nil { @@ -40,3 +40,36 @@ func TestEncodeBlock(t *testing.T) { t.Error("ADPCM generated does not match expected ADPCM") } } + +// TestDecodeBlock will read encoded ADPCM, decode it in blocks and then compare the +// resulting PCM with the expected decoded PCM. +func TestDecodeBlock(t *testing.T) { + //read adpcm + comp, err := ioutil.ReadFile("../../../test/test-data/av/input/encoded-voice.adpcm") + if err != nil { + t.Errorf("Unable to read input ADPCM file: %v", 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 := DecodeBlock(block) + if err != nil { + t.Errorf("Unable to decode block: %v", err) + } + decoded = append(decoded, decBlock...) + start = i + 1 + } + } + //read expected pcm file + exp, err := ioutil.ReadFile("../../../test/test-data/av/output/decoded-voice.pcm") + if err != nil { + t.Errorf("Unable to read expected PCM file: %v", err) + } + + if !bytes.Equal(decoded, exp) { + t.Error("PCM generated does not match expected PCM") + } +} From c30b3de7c31c4bc0a381f8e6f3ca45caf952d0e0 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 13 Feb 2019 11:53:06 +1030 Subject: [PATCH 14/32] ADPCM: fixed spacing and overflow check --- cmd/adpcm/decode-pcm/decode-pcm.go | 30 +++++++++++++ cmd/adpcm/encode-pcm/encode-pcm.go | 29 +++++++++++++ stream/adpcm/adpcm.go | 69 ++++++++++++++++++++++-------- stream/adpcm/adpcm_test.go | 32 +++++++++++++- 4 files changed, 140 insertions(+), 20 deletions(-) diff --git a/cmd/adpcm/decode-pcm/decode-pcm.go b/cmd/adpcm/decode-pcm/decode-pcm.go index 81b2d969..64973e3f 100644 --- a/cmd/adpcm/decode-pcm/decode-pcm.go +++ b/cmd/adpcm/decode-pcm/decode-pcm.go @@ -1,3 +1,30 @@ +/* +NAME + decode-pcm.go + +DESCRIPTION + See Readme.md + +AUTHOR + Trek Hopton + +LICENSE + decode-pcm.go is Copyright (C) 2018 the Australian Ocean Lab (AusOcean) + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses). +*/ + package main import ( @@ -16,11 +43,13 @@ func main() { 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 @@ -35,6 +64,7 @@ func main() { start = i + 1 } } + // save pcm to file err = ioutil.WriteFile(outPath, decoded, 0644) if err != nil { diff --git a/cmd/adpcm/encode-pcm/encode-pcm.go b/cmd/adpcm/encode-pcm/encode-pcm.go index f8812dd3..47ba6e07 100644 --- a/cmd/adpcm/encode-pcm/encode-pcm.go +++ b/cmd/adpcm/encode-pcm/encode-pcm.go @@ -1,3 +1,30 @@ +/* +NAME + encode-pcm.go + +DESCRIPTION + See Readme.md + +AUTHOR + Trek Hopton + +LICENSE + encode-pcm.go is Copyright (C) 2018 the Australian Ocean Lab (AusOcean) + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses). +*/ + package main import ( @@ -13,6 +40,7 @@ func main() { 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 { @@ -35,6 +63,7 @@ func main() { start = i + 1 } } + // save adpcm to file err = ioutil.WriteFile(adpcmPath, comp, 0644) if err != nil { diff --git a/stream/adpcm/adpcm.go b/stream/adpcm/adpcm.go index 61259de0..b4963efb 100644 --- a/stream/adpcm/adpcm.go +++ b/stream/adpcm/adpcm.go @@ -1,3 +1,35 @@ +/* +NAME + adpcm.go + +DESCRIPTION + See Readme.md + +AUTHOR + Trek Hopton + +LICENSE + adpcm.go is Copyright (C) 2018 the Australian Ocean Lab (AusOcean) + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses). +*/ + +/* + Original IMA/DVI ADPCM specification: (http://www.cs.columbia.edu/~hgs/audio/dvi/IMA_ADPCM.pdf). + Reference algorithms for ADPCM compression and decompression are in part 6. +*/ + package adpcm import ( @@ -6,13 +38,13 @@ import ( "math" ) -// table of index changes +// table of index changes (see spec) var indexTable = []int16{ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8, } -// quantize step size table +// quantize step size table (see spec) var stepTable = []int16{ 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, @@ -39,7 +71,6 @@ var ( // 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 { - delta := sample - encPred var nibble byte @@ -93,9 +124,6 @@ func encodeSample(sample int16) byte { // 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 { @@ -108,30 +136,37 @@ func decodeSample(nibble byte) int16 { 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 + // adjust predicted sample based on calculated difference, check for overflow + if diff > 0 { + if decPred > math.MaxInt16-diff { + decPred = math.MaxInt16 + } else { + decPred += diff + } + } else { + if decPred < math.MaxInt16-diff { + decPred = math.MaxInt16 + } else { + decPred += diff + } } - // 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] @@ -139,7 +174,6 @@ func decodeSample(nibble byte) int16 { } func calcHead(sample []byte) ([]byte, error) { - // check that we are given 1 16-bit sample (2 bytes) sampSize := 2 if len(sample) != sampSize { @@ -161,7 +195,6 @@ func calcHead(sample []byte) ([]byte, error) { // 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) { - bSize := 1010 if len(block) != bSize { return nil, fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), bSize) @@ -187,7 +220,6 @@ func EncodeBlock(block []byte) ([]byte, error) { // 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) { - bSize := 256 if len(block) != bSize { return nil, fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), bSize) @@ -211,7 +243,6 @@ func DecodeBlock(block []byte) ([]byte, error) { secondBytes := make([]byte, 2) binary.LittleEndian.PutUint16(secondBytes, uint16(decodeSample(secondSample))) result = append(result, secondBytes...) - } return result, nil diff --git a/stream/adpcm/adpcm_test.go b/stream/adpcm/adpcm_test.go index c83732c3..4d40f3e9 100644 --- a/stream/adpcm/adpcm_test.go +++ b/stream/adpcm/adpcm_test.go @@ -1,3 +1,30 @@ +/* +NAME + adpcm_test.go + +DESCRIPTION + See Readme.md + +AUTHOR + Trek Hopton + +LICENSE + adpcm_test.go is Copyright (C) 2018 the Australian Ocean Lab (AusOcean) + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses). +*/ + package adpcm import ( @@ -14,6 +41,7 @@ func TestEncodeBlock(t *testing.T) { if err != nil { t.Errorf("Unable to read input PCM file: %v", err) } + //encode adpcm var comp []byte start := 0 @@ -23,13 +51,13 @@ func TestEncodeBlock(t *testing.T) { encBlock, err := EncodeBlock(block) if err != nil { - //todo: use correct logging of error t.Errorf("Unable to encode block: %v", err) } comp = append(comp, encBlock...) start = i + 1 } } + //read expected adpcm file exp, err := ioutil.ReadFile("../../../test/test-data/av/output/encoded-voice.adpcm") if err != nil { @@ -49,6 +77,7 @@ func TestDecodeBlock(t *testing.T) { if err != nil { t.Errorf("Unable to read input ADPCM file: %v", err) } + //decode adpcm var decoded []byte start := 0 @@ -63,6 +92,7 @@ func TestDecodeBlock(t *testing.T) { start = i + 1 } } + //read expected pcm file exp, err := ioutil.ReadFile("../../../test/test-data/av/output/decoded-voice.pcm") if err != nil { From 43c6027888950981feb0b5dfd1f0b044d1705ba9 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 13 Feb 2019 12:43:13 +1030 Subject: [PATCH 15/32] ADPCM: added helper function for adding int16s without overflowing --- stream/adpcm/adpcm.go | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/stream/adpcm/adpcm.go b/stream/adpcm/adpcm.go index b4963efb..5359253e 100644 --- a/stream/adpcm/adpcm.go +++ b/stream/adpcm/adpcm.go @@ -97,9 +97,9 @@ func encodeSample(sample int16) byte { // adjust predicted sample based on calculated difference if nibble&8 != 0 { - encPred -= diff + encPred = capAdd16(encPred, -diff) } else { - encPred += diff + encPred = capAdd16(encPred, diff) } // check for underflow and overflow @@ -142,20 +142,8 @@ func decodeSample(nibble byte) int16 { diff = -diff } - // adjust predicted sample based on calculated difference, check for overflow - if diff > 0 { - if decPred > math.MaxInt16-diff { - decPred = math.MaxInt16 - } else { - decPred += diff - } - } else { - if decPred < math.MaxInt16-diff { - decPred = math.MaxInt16 - } else { - decPred += diff - } - } + // adjust predicted sample based on calculated difference + decPred = capAdd16(decPred, diff) // adjust index into step size lookup table using nibble decIndex += indexTable[nibble] @@ -173,6 +161,19 @@ func decodeSample(nibble byte) int16 { return decPred } +// capAdd16 adds two int16s together and caps at max/min int16 instead of overflowing +func capAdd16(a, b int16) int16 { + c := int32(a) + int32(b) + switch { + case c < math.MinInt16: + return math.MinInt16 + case c > math.MaxInt16: + return math.MaxInt16 + default: + return int16(c) + } +} + func calcHead(sample []byte) ([]byte, error) { // check that we are given 1 16-bit sample (2 bytes) sampSize := 2 From 7d3829d19f5128a59cb8d3ddfb88aacbb875d570 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 13 Feb 2019 16:53:25 +1030 Subject: [PATCH 16/32] ADPCM: pre-allocating memory for entire known length of output byte slice --- cmd/adpcm/decode-pcm/decode-pcm.go | 30 +++++++++++++------------ cmd/adpcm/encode-pcm/encode-pcm.go | 29 +++++++++++++----------- stream/adpcm/adpcm_test.go | 36 +++++++++++++----------------- 3 files changed, 48 insertions(+), 47 deletions(-) diff --git a/cmd/adpcm/decode-pcm/decode-pcm.go b/cmd/adpcm/decode-pcm/decode-pcm.go index 64973e3f..fded5005 100644 --- a/cmd/adpcm/decode-pcm/decode-pcm.go +++ b/cmd/adpcm/decode-pcm/decode-pcm.go @@ -29,6 +29,7 @@ package main import ( "flag" + "fmt" "io/ioutil" "bitbucket.org/ausocean/av/stream/adpcm" @@ -39,30 +40,30 @@ import ( 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 + // read adpcm comp, err := ioutil.ReadFile(inPath) if err != nil { panic(err) } + fmt.Println("Read", len(comp), "bytes from file", inPath) - //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 { - panic(err) - } - decoded = append(decoded, decBlock...) - start = i + 1 + // decode adpcm + inBSize := 256 + numBlocks := int(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 := make([]byte, 0, outBSize*numBlocks) + for i, start := inBSize-1, 0; i < len(comp); i += inBSize { + block := comp[start : i+1] + decBlock, err := adpcm.DecodeBlock(block) + if err != nil { + panic(err) } + decoded = append(decoded, decBlock...) + start = i + 1 } // save pcm to file @@ -70,4 +71,5 @@ func main() { if err != nil { panic(err) } + fmt.Println("Decoded and wrote", len(decoded), "bytes to file", outPath) } diff --git a/cmd/adpcm/encode-pcm/encode-pcm.go b/cmd/adpcm/encode-pcm/encode-pcm.go index 47ba6e07..002e2c22 100644 --- a/cmd/adpcm/encode-pcm/encode-pcm.go +++ b/cmd/adpcm/encode-pcm/encode-pcm.go @@ -29,11 +29,14 @@ package main import ( "flag" + "fmt" "io/ioutil" "bitbucket.org/ausocean/av/stream/adpcm" ) +// This program accepts an input pcm file and outputs an encoded adpcm file. +// Input and output file names can be specified as arguments. func main() { var inPath string var adpcmPath string @@ -46,22 +49,21 @@ func main() { if err != nil { panic(err) } + fmt.Println("Read", len(pcm), "bytes from file", inPath) //encode adpcm - var comp []byte - start := 0 - bSize := 1010 - for i := 0; i < len(pcm); i++ { - if i%bSize == bSize-1 { - block := pcm[start : i+1] - - encBlock, err := adpcm.EncodeBlock(block) - if err != nil { - panic(err) - } - comp = append(comp, encBlock...) - start = i + 1 + inBSize := 1010 + numBlocks := int(len(pcm) / inBSize) + outBSize := int(float32(inBSize/4) + float32(3.5)) // compression is 4:1 and 3.5 bytes of info are added to each block + comp := make([]byte, 0, outBSize*numBlocks) + for i, start := inBSize-1, 0; i < len(pcm); i += inBSize { + block := pcm[start : i+1] + encBlock, err := adpcm.EncodeBlock(block) + if err != nil { + panic(err) } + comp = append(comp, encBlock...) + start = i + 1 } // save adpcm to file @@ -69,4 +71,5 @@ func main() { if err != nil { panic(err) } + fmt.Println("Encoded and wrote", len(comp), "bytes to file", adpcmPath) } diff --git a/stream/adpcm/adpcm_test.go b/stream/adpcm/adpcm_test.go index 4d40f3e9..bbda1d31 100644 --- a/stream/adpcm/adpcm_test.go +++ b/stream/adpcm/adpcm_test.go @@ -44,18 +44,16 @@ func TestEncodeBlock(t *testing.T) { //encode adpcm var comp []byte - start := 0 - for i := 0; i < len(pcm); i++ { - if i%1010 == 1009 { - block := pcm[start : i+1] + bSize := 1010 + for i, start := bSize-1, 0; i < len(pcm); i += bSize { + block := pcm[start : i+1] - encBlock, err := EncodeBlock(block) - if err != nil { - t.Errorf("Unable to encode block: %v", err) - } - comp = append(comp, encBlock...) - start = i + 1 + encBlock, err := EncodeBlock(block) + if err != nil { + t.Errorf("Unable to encode block: %v", err) } + comp = append(comp, encBlock...) + start = i + 1 } //read expected adpcm file @@ -80,17 +78,15 @@ func TestDecodeBlock(t *testing.T) { //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 := DecodeBlock(block) - if err != nil { - t.Errorf("Unable to decode block: %v", err) - } - decoded = append(decoded, decBlock...) - start = i + 1 + bSize := 256 + for i, start := bSize-1, 0; i < len(comp); i += bSize { + block := comp[start : i+1] + decBlock, err := DecodeBlock(block) + if err != nil { + t.Errorf("Unable to decode block: %v", err) } + decoded = append(decoded, decBlock...) + start = i + 1 } //read expected pcm file From 78a3127632687342b3d473c37693889978fce91a Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 13 Feb 2019 16:58:21 +1030 Subject: [PATCH 17/32] ADPCM: changed adpcm_test.go to use pre-allocation of byte slices --- stream/adpcm/adpcm_test.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/stream/adpcm/adpcm_test.go b/stream/adpcm/adpcm_test.go index bbda1d31..a2c9c034 100644 --- a/stream/adpcm/adpcm_test.go +++ b/stream/adpcm/adpcm_test.go @@ -43,9 +43,11 @@ func TestEncodeBlock(t *testing.T) { } //encode adpcm - var comp []byte - bSize := 1010 - for i, start := bSize-1, 0; i < len(pcm); i += bSize { + inBSize := 1010 + numBlocks := int(len(pcm) / inBSize) + outBSize := int(float32(inBSize/4) + float32(3.5)) // compression is 4:1 and 3.5 bytes of info are added to each block + comp := make([]byte, 0, outBSize*numBlocks) + for i, start := inBSize-1, 0; i < len(pcm); i += inBSize { block := pcm[start : i+1] encBlock, err := EncodeBlock(block) @@ -77,9 +79,11 @@ func TestDecodeBlock(t *testing.T) { } //decode adpcm - var decoded []byte - bSize := 256 - for i, start := bSize-1, 0; i < len(comp); i += bSize { + inBSize := 256 + numBlocks := int(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 := make([]byte, 0, outBSize*numBlocks) + for i, start := inBSize-1, 0; i < len(comp); i += inBSize { block := comp[start : i+1] decBlock, err := DecodeBlock(block) if err != nil { From bd70144debed61000efd2fba7b454db1ad7f07a0 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 13 Feb 2019 17:30:11 +1030 Subject: [PATCH 18/32] ADPCM: Simplified for loops for creating and encoding/decoding blocks --- cmd/adpcm/decode-pcm/decode-pcm.go | 12 ++++++------ cmd/adpcm/encode-pcm/encode-pcm.go | 12 ++++++------ stream/adpcm/adpcm.go | 2 +- stream/adpcm/adpcm_test.go | 22 +++++++++++----------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/cmd/adpcm/decode-pcm/decode-pcm.go b/cmd/adpcm/decode-pcm/decode-pcm.go index fded5005..793893ef 100644 --- a/cmd/adpcm/decode-pcm/decode-pcm.go +++ b/cmd/adpcm/decode-pcm/decode-pcm.go @@ -31,6 +31,7 @@ import ( "flag" "fmt" "io/ioutil" + "log" "bitbucket.org/ausocean/av/stream/adpcm" ) @@ -47,7 +48,7 @@ func main() { // read adpcm comp, err := ioutil.ReadFile(inPath) if err != nil { - panic(err) + log.Fatal(err) } fmt.Println("Read", len(comp), "bytes from file", inPath) @@ -56,20 +57,19 @@ func main() { numBlocks := int(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 := make([]byte, 0, outBSize*numBlocks) - for i, start := inBSize-1, 0; i < len(comp); i += inBSize { - block := comp[start : i+1] + for i := 0; i < numBlocks; i++ { + block := comp[inBSize*i : inBSize*(i+1)] decBlock, err := adpcm.DecodeBlock(block) if err != nil { - panic(err) + log.Fatal(err) } decoded = append(decoded, decBlock...) - start = i + 1 } // save pcm to file err = ioutil.WriteFile(outPath, decoded, 0644) if err != nil { - panic(err) + log.Fatal(err) } fmt.Println("Decoded and wrote", len(decoded), "bytes to file", outPath) } diff --git a/cmd/adpcm/encode-pcm/encode-pcm.go b/cmd/adpcm/encode-pcm/encode-pcm.go index 002e2c22..b2af502a 100644 --- a/cmd/adpcm/encode-pcm/encode-pcm.go +++ b/cmd/adpcm/encode-pcm/encode-pcm.go @@ -31,6 +31,7 @@ import ( "flag" "fmt" "io/ioutil" + "log" "bitbucket.org/ausocean/av/stream/adpcm" ) @@ -47,7 +48,7 @@ func main() { //read pcm pcm, err := ioutil.ReadFile(inPath) if err != nil { - panic(err) + log.Fatal(err) } fmt.Println("Read", len(pcm), "bytes from file", inPath) @@ -56,20 +57,19 @@ func main() { numBlocks := int(len(pcm) / inBSize) outBSize := int(float32(inBSize/4) + float32(3.5)) // compression is 4:1 and 3.5 bytes of info are added to each block comp := make([]byte, 0, outBSize*numBlocks) - for i, start := inBSize-1, 0; i < len(pcm); i += inBSize { - block := pcm[start : i+1] + for i := 0; i < numBlocks; i++ { + block := pcm[inBSize*i : inBSize*(i+1)] encBlock, err := adpcm.EncodeBlock(block) if err != nil { - panic(err) + log.Fatal(err) } comp = append(comp, encBlock...) - start = i + 1 } // save adpcm to file err = ioutil.WriteFile(adpcmPath, comp, 0644) if err != nil { - panic(err) + log.Fatal(err) } fmt.Println("Encoded and wrote", len(comp), "bytes to file", adpcmPath) } diff --git a/stream/adpcm/adpcm.go b/stream/adpcm/adpcm.go index 5359253e..a55936d9 100644 --- a/stream/adpcm/adpcm.go +++ b/stream/adpcm/adpcm.go @@ -184,7 +184,7 @@ func calcHead(sample []byte) ([]byte, error) { intSample := int16(binary.LittleEndian.Uint16(sample)) encodeSample(intSample) - head := make([]byte, 2) + head := make([]byte, 2, 4) head[0] = sample[0] head[1] = sample[1] diff --git a/stream/adpcm/adpcm_test.go b/stream/adpcm/adpcm_test.go index a2c9c034..c420eee6 100644 --- a/stream/adpcm/adpcm_test.go +++ b/stream/adpcm/adpcm_test.go @@ -30,7 +30,10 @@ package adpcm import ( "bytes" "io/ioutil" + "log" "testing" + + "bitbucket.org/ausocean/av/stream/adpcm" ) // TestEncodeBlock will read PCM data, encode it in blocks and generate ADPCM @@ -47,15 +50,13 @@ func TestEncodeBlock(t *testing.T) { numBlocks := int(len(pcm) / inBSize) outBSize := int(float32(inBSize/4) + float32(3.5)) // compression is 4:1 and 3.5 bytes of info are added to each block comp := make([]byte, 0, outBSize*numBlocks) - for i, start := inBSize-1, 0; i < len(pcm); i += inBSize { - block := pcm[start : i+1] - - encBlock, err := EncodeBlock(block) + for i := 0; i < numBlocks; i++ { + block := pcm[inBSize*i : inBSize*(i+1)] + encBlock, err := adpcm.EncodeBlock(block) if err != nil { - t.Errorf("Unable to encode block: %v", err) + log.Fatal(err) } comp = append(comp, encBlock...) - start = i + 1 } //read expected adpcm file @@ -83,14 +84,13 @@ func TestDecodeBlock(t *testing.T) { numBlocks := int(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 := make([]byte, 0, outBSize*numBlocks) - for i, start := inBSize-1, 0; i < len(comp); i += inBSize { - block := comp[start : i+1] - decBlock, err := DecodeBlock(block) + for i := 0; i < numBlocks; i++ { + block := comp[inBSize*i : inBSize*(i+1)] + decBlock, err := adpcm.DecodeBlock(block) if err != nil { - t.Errorf("Unable to decode block: %v", err) + log.Fatal(err) } decoded = append(decoded, decBlock...) - start = i + 1 } //read expected pcm file From 14f8e7d29ca22be5382c144e72a4a3c9b9168f2e Mon Sep 17 00:00:00 2001 From: Trek H Date: Fri, 15 Feb 2019 13:31:16 +1030 Subject: [PATCH 19/32] ADPCM: correct calls to adpcm functions in test --- stream/adpcm/adpcm_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/stream/adpcm/adpcm_test.go b/stream/adpcm/adpcm_test.go index c420eee6..a2b82956 100644 --- a/stream/adpcm/adpcm_test.go +++ b/stream/adpcm/adpcm_test.go @@ -32,8 +32,6 @@ import ( "io/ioutil" "log" "testing" - - "bitbucket.org/ausocean/av/stream/adpcm" ) // TestEncodeBlock will read PCM data, encode it in blocks and generate ADPCM @@ -52,7 +50,7 @@ func TestEncodeBlock(t *testing.T) { comp := make([]byte, 0, outBSize*numBlocks) for i := 0; i < numBlocks; i++ { block := pcm[inBSize*i : inBSize*(i+1)] - encBlock, err := adpcm.EncodeBlock(block) + encBlock, err := EncodeBlock(block) if err != nil { log.Fatal(err) } @@ -86,7 +84,7 @@ func TestDecodeBlock(t *testing.T) { decoded := make([]byte, 0, outBSize*numBlocks) for i := 0; i < numBlocks; i++ { block := comp[inBSize*i : inBSize*(i+1)] - decBlock, err := adpcm.DecodeBlock(block) + decBlock, err := DecodeBlock(block) if err != nil { log.Fatal(err) } From 936677f7471bceb26542dea4dbfe832b826a195e Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 25 Feb 2019 11:55:13 +1030 Subject: [PATCH 20/32] ADPCM: added descriptions to file headers and author to readme --- Readme.md | 1 + cmd/adpcm/decode-pcm/decode-pcm.go | 2 +- cmd/adpcm/encode-pcm/encode-pcm.go | 2 +- stream/adpcm/adpcm.go | 2 +- stream/adpcm/adpcm_test.go | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index 95602640..1c3489d4 100644 --- a/Readme.md +++ b/Readme.md @@ -5,6 +5,7 @@ av is a collection of tools and packages written in Go for audio-video processin # Authors Alan Noble Saxon A. Nelson-Milton +Trek Hopton # Description diff --git a/cmd/adpcm/decode-pcm/decode-pcm.go b/cmd/adpcm/decode-pcm/decode-pcm.go index 793893ef..45a27f90 100644 --- a/cmd/adpcm/decode-pcm/decode-pcm.go +++ b/cmd/adpcm/decode-pcm/decode-pcm.go @@ -3,7 +3,7 @@ NAME decode-pcm.go DESCRIPTION - See Readme.md + decode-pcm.go is a program for decoding/decompressing an adpcm file to a pcm file. AUTHOR Trek Hopton diff --git a/cmd/adpcm/encode-pcm/encode-pcm.go b/cmd/adpcm/encode-pcm/encode-pcm.go index b2af502a..a014ef53 100644 --- a/cmd/adpcm/encode-pcm/encode-pcm.go +++ b/cmd/adpcm/encode-pcm/encode-pcm.go @@ -3,7 +3,7 @@ NAME encode-pcm.go DESCRIPTION - See Readme.md + encode-pcm.go is a program for encoding/compressing a pcm file to an adpcm file. AUTHOR Trek Hopton diff --git a/stream/adpcm/adpcm.go b/stream/adpcm/adpcm.go index a55936d9..57a667d2 100644 --- a/stream/adpcm/adpcm.go +++ b/stream/adpcm/adpcm.go @@ -3,7 +3,7 @@ NAME adpcm.go DESCRIPTION - See Readme.md + adpcm.go contains functions for encoding/compressing pcm into adpcm and decoding/decompressing back to pcm. AUTHOR Trek Hopton diff --git a/stream/adpcm/adpcm_test.go b/stream/adpcm/adpcm_test.go index a2b82956..e9cb05aa 100644 --- a/stream/adpcm/adpcm_test.go +++ b/stream/adpcm/adpcm_test.go @@ -3,7 +3,7 @@ NAME adpcm_test.go DESCRIPTION - See Readme.md + adpcm_test.go contains tests for the adpcm package. AUTHOR Trek Hopton From d95ce7efe68bd2249c34e673b6c73160d12b0dbd Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 25 Feb 2019 14:43:26 +1030 Subject: [PATCH 21/32] ADPCM: fixed conversions and calculations for block variables --- cmd/adpcm/decode-pcm/decode-pcm.go | 2 +- cmd/adpcm/encode-pcm/encode-pcm.go | 4 ++-- stream/adpcm/adpcm_test.go | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/adpcm/decode-pcm/decode-pcm.go b/cmd/adpcm/decode-pcm/decode-pcm.go index 45a27f90..f664141f 100644 --- a/cmd/adpcm/decode-pcm/decode-pcm.go +++ b/cmd/adpcm/decode-pcm/decode-pcm.go @@ -54,7 +54,7 @@ func main() { // decode adpcm inBSize := 256 - numBlocks := int(len(comp) / inBSize) + 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 := make([]byte, 0, outBSize*numBlocks) for i := 0; i < numBlocks; i++ { diff --git a/cmd/adpcm/encode-pcm/encode-pcm.go b/cmd/adpcm/encode-pcm/encode-pcm.go index a014ef53..8da0aa77 100644 --- a/cmd/adpcm/encode-pcm/encode-pcm.go +++ b/cmd/adpcm/encode-pcm/encode-pcm.go @@ -54,8 +54,8 @@ func main() { //encode adpcm inBSize := 1010 - numBlocks := int(len(pcm) / inBSize) - outBSize := int(float32(inBSize/4) + float32(3.5)) // compression is 4:1 and 3.5 bytes of info are added to each block + 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 := make([]byte, 0, outBSize*numBlocks) for i := 0; i < numBlocks; i++ { block := pcm[inBSize*i : inBSize*(i+1)] diff --git a/stream/adpcm/adpcm_test.go b/stream/adpcm/adpcm_test.go index e9cb05aa..5e991ddc 100644 --- a/stream/adpcm/adpcm_test.go +++ b/stream/adpcm/adpcm_test.go @@ -45,8 +45,8 @@ func TestEncodeBlock(t *testing.T) { //encode adpcm inBSize := 1010 - numBlocks := int(len(pcm) / inBSize) - outBSize := int(float32(inBSize/4) + float32(3.5)) // compression is 4:1 and 3.5 bytes of info are added to each block + 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 := make([]byte, 0, outBSize*numBlocks) for i := 0; i < numBlocks; i++ { block := pcm[inBSize*i : inBSize*(i+1)] @@ -79,7 +79,7 @@ func TestDecodeBlock(t *testing.T) { //decode adpcm inBSize := 256 - numBlocks := int(len(comp) / inBSize) + 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 := make([]byte, 0, outBSize*numBlocks) for i := 0; i < numBlocks; i++ { From 1ac4b16b5b80a505be0db5ce54ab984ce14960b9 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 27 Feb 2019 16:46:15 +1030 Subject: [PATCH 22/32] ADPCM: removed reference to revid in headers --- cmd/adpcm/decode-pcm/decode-pcm.go | 6 +++--- cmd/adpcm/encode-pcm/encode-pcm.go | 6 +++--- stream/adpcm/adpcm.go | 6 +++--- stream/adpcm/adpcm_test.go | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cmd/adpcm/decode-pcm/decode-pcm.go b/cmd/adpcm/decode-pcm/decode-pcm.go index f664141f..5d50dfe1 100644 --- a/cmd/adpcm/decode-pcm/decode-pcm.go +++ b/cmd/adpcm/decode-pcm/decode-pcm.go @@ -19,10 +19,10 @@ LICENSE It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - for more details. + for more details. - You should have received a copy of the GNU General Public License - along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses). + You should have received a copy of the GNU General Public License in gpl.txt. + If not, see [GNU licenses](http://www.gnu.org/licenses). */ package main diff --git a/cmd/adpcm/encode-pcm/encode-pcm.go b/cmd/adpcm/encode-pcm/encode-pcm.go index 8da0aa77..ae19a505 100644 --- a/cmd/adpcm/encode-pcm/encode-pcm.go +++ b/cmd/adpcm/encode-pcm/encode-pcm.go @@ -19,10 +19,10 @@ LICENSE It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - for more details. + for more details. - You should have received a copy of the GNU General Public License - along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses). + You should have received a copy of the GNU General Public License in gpl.txt. + If not, see [GNU licenses](http://www.gnu.org/licenses). */ package main diff --git a/stream/adpcm/adpcm.go b/stream/adpcm/adpcm.go index 57a667d2..a8f97559 100644 --- a/stream/adpcm/adpcm.go +++ b/stream/adpcm/adpcm.go @@ -19,10 +19,10 @@ LICENSE It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - for more details. + for more details. - You should have received a copy of the GNU General Public License - along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses). + You should have received a copy of the GNU General Public License in gpl.txt. + If not, see [GNU licenses](http://www.gnu.org/licenses). */ /* diff --git a/stream/adpcm/adpcm_test.go b/stream/adpcm/adpcm_test.go index 5e991ddc..eaada3d5 100644 --- a/stream/adpcm/adpcm_test.go +++ b/stream/adpcm/adpcm_test.go @@ -19,10 +19,10 @@ LICENSE It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - for more details. + for more details. - You should have received a copy of the GNU General Public License - along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses). + You should have received a copy of the GNU General Public License in gpl.txt. + If not, see [GNU licenses](http://www.gnu.org/licenses). */ package adpcm From 03c84fdbaf8fd2d6e42b94231cc08d879de9b455 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 27 Feb 2019 16:51:06 +1030 Subject: [PATCH 23/32] ADPCM: fixed header indentation --- cmd/adpcm/decode-pcm/decode-pcm.go | 2 +- cmd/adpcm/encode-pcm/encode-pcm.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/adpcm/decode-pcm/decode-pcm.go b/cmd/adpcm/decode-pcm/decode-pcm.go index 5d50dfe1..e19993f5 100644 --- a/cmd/adpcm/decode-pcm/decode-pcm.go +++ b/cmd/adpcm/decode-pcm/decode-pcm.go @@ -22,7 +22,7 @@ LICENSE for more details. You should have received a copy of the GNU General Public License in gpl.txt. - If not, see [GNU licenses](http://www.gnu.org/licenses). + If not, see [GNU licenses](http://www.gnu.org/licenses). */ package main diff --git a/cmd/adpcm/encode-pcm/encode-pcm.go b/cmd/adpcm/encode-pcm/encode-pcm.go index ae19a505..7e6bcfa6 100644 --- a/cmd/adpcm/encode-pcm/encode-pcm.go +++ b/cmd/adpcm/encode-pcm/encode-pcm.go @@ -21,8 +21,8 @@ LICENSE FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License in gpl.txt. - If not, see [GNU licenses](http://www.gnu.org/licenses). + You should have received a copy of the GNU General Public License in gpl.txt. + If not, see [GNU licenses](http://www.gnu.org/licenses). */ package main From d1c37569c411f7786f52a6665b49bfbb9e9258c6 Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 28 Feb 2019 11:53:16 +1030 Subject: [PATCH 24/32] ADPCM: resructure to encoder and decoder structs --- stream/adpcm/adpcm.go | 123 +++++++++++++++++++++++-------------- stream/adpcm/adpcm_test.go | 6 +- 2 files changed, 80 insertions(+), 49 deletions(-) diff --git a/stream/adpcm/adpcm.go b/stream/adpcm/adpcm.go index a8f97559..a8cc7de3 100644 --- a/stream/adpcm/adpcm.go +++ b/stream/adpcm/adpcm.go @@ -35,9 +35,29 @@ package adpcm import ( "encoding/binary" "fmt" + "io" "math" ) +// Encoder is used to encode to ADPCM from PCM data. +// pred and index hold state that persists between calls to encodeSample and calcHead. +// dest is the output, ie. where the encoded ADPCM data is written to. +type Encoder struct { + dest io.Writer + pred int16 + index int16 +} + +// Decoder is used to decode from ADPCM to PCM data. +// pred, index, and step hold state that persists between calls to decodeSample. +// dest is the output, ie. where the decoded PCM data is written to. +type Decoder struct { + dest io.Writer + pred int16 + index int16 + step int16 +} + // table of index changes (see spec) var indexTable = []int16{ -1, -1, -1, -1, 2, 4, 6, 8, @@ -60,18 +80,27 @@ var stepTable = []int16{ 32767, } -var ( - encPred int16 - encIndex int16 - decPred int16 - decIndex int16 - decStep int16 = 7 -) +// NewEncoder retuns a new ADPCM encoder. +func NewEncoder(dst io.Writer) *Encoder { + e := Encoder{ + dest: dst, + } + return &e +} + +// NewDecoder retuns a new ADPCM decoder. +func NewDecoder(dst io.Writer) *Decoder { + d := Decoder{ + step: stepTable[0], + dest: dst, + } + return &d +} // 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 { - delta := sample - encPred +func (e *Encoder) encodeSample(sample int16) byte { + delta := sample - e.pred var nibble byte @@ -81,7 +110,7 @@ func encodeSample(sample int16) byte { delta = -delta } - step := stepTable[encIndex] + step := stepTable[e.index] diff := step >> 3 var mask byte = 4 @@ -97,25 +126,25 @@ func encodeSample(sample int16) byte { // adjust predicted sample based on calculated difference if nibble&8 != 0 { - encPred = capAdd16(encPred, -diff) + e.pred = capAdd16(e.pred, -diff) } else { - encPred = capAdd16(encPred, diff) + e.pred = capAdd16(e.pred, diff) } // check for underflow and overflow - if encPred < math.MinInt16 { - encPred = math.MinInt16 - } else if encPred > math.MaxInt16 { - encPred = math.MaxInt16 + if e.pred < math.MinInt16 { + e.pred = math.MinInt16 + } else if e.pred > math.MaxInt16 { + e.pred = math.MaxInt16 } - encIndex += indexTable[nibble&7] + e.index += 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) + if e.index < 0 { + e.index = 0 + } else if e.index > int16(len(stepTable)-1) { + e.index = int16(len(stepTable) - 1) } return nibble @@ -123,19 +152,19 @@ func encodeSample(sample int16) byte { // 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 { +func (d *Decoder) decodeSample(nibble byte) int16 { // calculate difference var diff int16 if nibble&4 != 0 { - diff += decStep + diff += d.step } if nibble&2 != 0 { - diff += decStep >> 1 + diff += d.step >> 1 } if nibble&1 != 0 { - diff += decStep >> 2 + diff += d.step >> 2 } - diff += decStep >> 3 + diff += d.step >> 3 // account for sign bit if nibble&8 != 0 { @@ -143,22 +172,22 @@ func decodeSample(nibble byte) int16 { } // adjust predicted sample based on calculated difference - decPred = capAdd16(decPred, diff) + d.pred = capAdd16(d.pred, diff) // adjust index into step size lookup table using nibble - decIndex += indexTable[nibble] + d.index += indexTable[nibble] // check for overflow and underflow - if decIndex < 0 { - decIndex = 0 - } else if decIndex > int16(len(stepTable)-1) { - decIndex = int16(len(stepTable) - 1) + if d.index < 0 { + d.index = 0 + } else if d.index > int16(len(stepTable)-1) { + d.index = int16(len(stepTable) - 1) } // find new quantizer step size - decStep = stepTable[decIndex] + d.step = stepTable[d.index] - return decPred + return d.pred } // capAdd16 adds two int16s together and caps at max/min int16 instead of overflowing @@ -174,7 +203,7 @@ func capAdd16(a, b int16) int16 { } } -func calcHead(sample []byte) ([]byte, error) { +func (e *Encoder) calcHead(sample []byte) ([]byte, error) { // check that we are given 1 16-bit sample (2 bytes) sampSize := 2 if len(sample) != sampSize { @@ -182,34 +211,34 @@ func calcHead(sample []byte) ([]byte, error) { } intSample := int16(binary.LittleEndian.Uint16(sample)) - encodeSample(intSample) + e.encodeSample(intSample) head := make([]byte, 2, 4) head[0] = sample[0] head[1] = sample[1] - head = append(head, byte(uint16(encIndex))) + head = append(head, byte(uint16(e.index))) 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) { +func (e *Encoder) EncodeBlock(block []byte) ([]byte, error) { bSize := 1010 if len(block) != bSize { return nil, fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), bSize) } - result, err := calcHead(block[0:2]) + result, err := e.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]))) + sample2 := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i-1 : i+1]))) + sample := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i+1 : i+3]))) result = append(result, byte((sample<<4)|sample2)) } @@ -220,16 +249,16 @@ func EncodeBlock(block []byte) ([]byte, error) { // 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) { +func (d *Decoder) DecodeBlock(block []byte) ([]byte, error) { bSize := 256 if len(block) != bSize { return nil, fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), bSize) } var result []byte - decPred = int16(binary.LittleEndian.Uint16(block[0:2])) - decIndex = int16(block[2]) - decStep = stepTable[decIndex] + d.pred = int16(binary.LittleEndian.Uint16(block[0:2])) + d.index = int16(block[2]) + d.step = stepTable[d.index] result = append(result, block[0:2]...) for i := 4; i < len(block); i++ { @@ -238,11 +267,11 @@ func DecodeBlock(block []byte) ([]byte, error) { firstSample := byte((secondSample << 4) ^ originalSample) firstBytes := make([]byte, 2) - binary.LittleEndian.PutUint16(firstBytes, uint16(decodeSample(firstSample))) + binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(firstSample))) result = append(result, firstBytes...) secondBytes := make([]byte, 2) - binary.LittleEndian.PutUint16(secondBytes, uint16(decodeSample(secondSample))) + binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(secondSample))) result = append(result, secondBytes...) } diff --git a/stream/adpcm/adpcm_test.go b/stream/adpcm/adpcm_test.go index eaada3d5..02eb8f37 100644 --- a/stream/adpcm/adpcm_test.go +++ b/stream/adpcm/adpcm_test.go @@ -48,9 +48,10 @@ func TestEncodeBlock(t *testing.T) { 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 := make([]byte, 0, outBSize*numBlocks) + enc := NewEncoder(nil) for i := 0; i < numBlocks; i++ { block := pcm[inBSize*i : inBSize*(i+1)] - encBlock, err := EncodeBlock(block) + encBlock, err := enc.EncodeBlock(block) if err != nil { log.Fatal(err) } @@ -82,9 +83,10 @@ func TestDecodeBlock(t *testing.T) { 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 := make([]byte, 0, outBSize*numBlocks) + dec := NewDecoder(nil) for i := 0; i < numBlocks; i++ { block := comp[inBSize*i : inBSize*(i+1)] - decBlock, err := DecodeBlock(block) + decBlock, err := dec.DecodeBlock(block) if err != nil { log.Fatal(err) } From 3ebf928ffe53d87723c40f912f464c9ff88185d0 Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 28 Feb 2019 12:53:51 +1030 Subject: [PATCH 25/32] ADPCM: encoder now using byte writer instead of returning byte slices --- stream/adpcm/adpcm.go | 35 +++++++++++++++++------------------ stream/adpcm/adpcm_test.go | 9 ++++----- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/stream/adpcm/adpcm.go b/stream/adpcm/adpcm.go index a8cc7de3..de123bf7 100644 --- a/stream/adpcm/adpcm.go +++ b/stream/adpcm/adpcm.go @@ -43,7 +43,7 @@ import ( // pred and index hold state that persists between calls to encodeSample and calcHead. // dest is the output, ie. where the encoded ADPCM data is written to. type Encoder struct { - dest io.Writer + dest io.ByteWriter pred int16 index int16 } @@ -52,7 +52,7 @@ type Encoder struct { // pred, index, and step hold state that persists between calls to decodeSample. // dest is the output, ie. where the decoded PCM data is written to. type Decoder struct { - dest io.Writer + dest io.ByteWriter pred int16 index int16 step int16 @@ -81,7 +81,7 @@ var stepTable = []int16{ } // NewEncoder retuns a new ADPCM encoder. -func NewEncoder(dst io.Writer) *Encoder { +func NewEncoder(dst io.ByteWriter) *Encoder { e := Encoder{ dest: dst, } @@ -89,7 +89,7 @@ func NewEncoder(dst io.Writer) *Encoder { } // NewDecoder retuns a new ADPCM decoder. -func NewDecoder(dst io.Writer) *Decoder { +func NewDecoder(dst io.ByteWriter) *Decoder { d := Decoder{ step: stepTable[0], dest: dst, @@ -203,48 +203,47 @@ func capAdd16(a, b int16) int16 { } } -func (e *Encoder) calcHead(sample []byte) ([]byte, error) { +func (e *Encoder) calcHead(sample []byte) error { // check that we are given 1 16-bit sample (2 bytes) sampSize := 2 if len(sample) != sampSize { - return nil, fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), sampSize) + return fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), sampSize) } intSample := int16(binary.LittleEndian.Uint16(sample)) e.encodeSample(intSample) - head := make([]byte, 2, 4) - head[0] = sample[0] - head[1] = sample[1] + e.dest.WriteByte(sample[0]) + e.dest.WriteByte(sample[1]) - head = append(head, byte(uint16(e.index))) - head = append(head, byte(0x00)) - return head, nil + e.dest.WriteByte(byte(uint16(e.index))) + e.dest.WriteByte(byte(0x00)) + return 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 (e *Encoder) EncodeBlock(block []byte) ([]byte, error) { +func (e *Encoder) EncodeBlock(block []byte) error { bSize := 1010 if len(block) != bSize { - return nil, fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), bSize) + return fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), bSize) } - result, err := e.calcHead(block[0:2]) + err := e.calcHead(block[0:2]) if err != nil { - return nil, err + return err } for i := 2; i < len(block); i++ { if (i+1)%4 == 0 { sample2 := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i-1 : i+1]))) sample := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i+1 : i+3]))) - result = append(result, byte((sample<<4)|sample2)) + e.dest.WriteByte(byte((sample << 4) | sample2)) } } - return result, nil + return nil } // DecodeBlock takes a slice of 256 bytes, each byte should contain two ADPCM encoded nibbles. diff --git a/stream/adpcm/adpcm_test.go b/stream/adpcm/adpcm_test.go index 02eb8f37..9ab1ac55 100644 --- a/stream/adpcm/adpcm_test.go +++ b/stream/adpcm/adpcm_test.go @@ -47,15 +47,14 @@ func TestEncodeBlock(t *testing.T) { 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 := make([]byte, 0, outBSize*numBlocks) - enc := NewEncoder(nil) + comp := bytes.NewBuffer(make([]byte, 0, outBSize*numBlocks)) + enc := NewEncoder(comp) for i := 0; i < numBlocks; i++ { block := pcm[inBSize*i : inBSize*(i+1)] - encBlock, err := enc.EncodeBlock(block) + err := enc.EncodeBlock(block) if err != nil { log.Fatal(err) } - comp = append(comp, encBlock...) } //read expected adpcm file @@ -64,7 +63,7 @@ func TestEncodeBlock(t *testing.T) { t.Errorf("Unable to read expected ADPCM file: %v", err) } - if !bytes.Equal(comp, exp) { + if !bytes.Equal(comp.Bytes(), exp) { t.Error("ADPCM generated does not match expected ADPCM") } } From 16df9e78cbe99b2385e2f473843792eee81e740c Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 28 Feb 2019 13:05:45 +1030 Subject: [PATCH 26/32] ADPCM: encoder now uses bytes.Buffer so that bytes and byte arrays can be written out --- stream/adpcm/adpcm.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/stream/adpcm/adpcm.go b/stream/adpcm/adpcm.go index de123bf7..326f6758 100644 --- a/stream/adpcm/adpcm.go +++ b/stream/adpcm/adpcm.go @@ -33,26 +33,26 @@ LICENSE package adpcm import ( + "bytes" "encoding/binary" "fmt" - "io" "math" ) // Encoder is used to encode to ADPCM from PCM data. // pred and index hold state that persists between calls to encodeSample and calcHead. -// dest is the output, ie. where the encoded ADPCM data is written to. +// 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 io.ByteWriter + dest *bytes.Buffer pred int16 index int16 } // Decoder is used to decode from ADPCM to PCM data. // pred, index, and step hold state that persists between calls to decodeSample. -// dest is the output, ie. where the decoded PCM data is written to. +// 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 io.ByteWriter + dest *bytes.Buffer pred int16 index int16 step int16 @@ -81,7 +81,7 @@ var stepTable = []int16{ } // NewEncoder retuns a new ADPCM encoder. -func NewEncoder(dst io.ByteWriter) *Encoder { +func NewEncoder(dst *bytes.Buffer) *Encoder { e := Encoder{ dest: dst, } @@ -89,7 +89,7 @@ func NewEncoder(dst io.ByteWriter) *Encoder { } // NewDecoder retuns a new ADPCM decoder. -func NewDecoder(dst io.ByteWriter) *Decoder { +func NewDecoder(dst *bytes.Buffer) *Decoder { d := Decoder{ step: stepTable[0], dest: dst, @@ -213,8 +213,7 @@ func (e *Encoder) calcHead(sample []byte) error { intSample := int16(binary.LittleEndian.Uint16(sample)) e.encodeSample(intSample) - e.dest.WriteByte(sample[0]) - e.dest.WriteByte(sample[1]) + e.dest.Write(sample) e.dest.WriteByte(byte(uint16(e.index))) e.dest.WriteByte(byte(0x00)) From 86506b916e5939702e865309beaa875ed925b6a7 Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 28 Feb 2019 13:12:41 +1030 Subject: [PATCH 27/32] ADPCM: decoder now writes to bytes.Buffer instead of returning a byte array, tests updated. --- stream/adpcm/adpcm.go | 13 ++++++------- stream/adpcm/adpcm_test.go | 9 ++++----- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/stream/adpcm/adpcm.go b/stream/adpcm/adpcm.go index 326f6758..9e9bb324 100644 --- a/stream/adpcm/adpcm.go +++ b/stream/adpcm/adpcm.go @@ -247,17 +247,16 @@ func (e *Encoder) EncodeBlock(block []byte) error { // 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 (d *Decoder) DecodeBlock(block []byte) ([]byte, error) { +func (d *Decoder) DecodeBlock(block []byte) error { bSize := 256 if len(block) != bSize { - return nil, fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), bSize) + return fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), bSize) } - var result []byte d.pred = int16(binary.LittleEndian.Uint16(block[0:2])) d.index = int16(block[2]) d.step = stepTable[d.index] - result = append(result, block[0:2]...) + d.dest.Write(block[0:2]) for i := 4; i < len(block); i++ { originalSample := block[i] @@ -266,12 +265,12 @@ func (d *Decoder) DecodeBlock(block []byte) ([]byte, error) { firstBytes := make([]byte, 2) binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(firstSample))) - result = append(result, firstBytes...) + d.dest.Write(firstBytes) secondBytes := make([]byte, 2) binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(secondSample))) - result = append(result, secondBytes...) + d.dest.Write(secondBytes) } - return result, nil + return nil } diff --git a/stream/adpcm/adpcm_test.go b/stream/adpcm/adpcm_test.go index 9ab1ac55..f82b421b 100644 --- a/stream/adpcm/adpcm_test.go +++ b/stream/adpcm/adpcm_test.go @@ -81,15 +81,14 @@ func TestDecodeBlock(t *testing.T) { 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 := make([]byte, 0, outBSize*numBlocks) - dec := NewDecoder(nil) + decoded := bytes.NewBuffer(make([]byte, 0, outBSize*numBlocks)) + dec := NewDecoder(decoded) for i := 0; i < numBlocks; i++ { block := comp[inBSize*i : inBSize*(i+1)] - decBlock, err := dec.DecodeBlock(block) + err := dec.DecodeBlock(block) if err != nil { log.Fatal(err) } - decoded = append(decoded, decBlock...) } //read expected pcm file @@ -98,7 +97,7 @@ func TestDecodeBlock(t *testing.T) { t.Errorf("Unable to read expected PCM file: %v", err) } - if !bytes.Equal(decoded, exp) { + if !bytes.Equal(decoded.Bytes(), exp) { t.Error("PCM generated does not match expected PCM") } } From 3f4de6d288281a54f291a5022fa4b08928c35eda Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 28 Feb 2019 13:32:57 +1030 Subject: [PATCH 28/32] ADPCM: updated encode decode commands to use restructured encoder and decoder --- cmd/adpcm/decode-pcm/decode-pcm.go | 11 ++++++----- cmd/adpcm/encode-pcm/encode-pcm.go | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cmd/adpcm/decode-pcm/decode-pcm.go b/cmd/adpcm/decode-pcm/decode-pcm.go index e19993f5..d661a71d 100644 --- a/cmd/adpcm/decode-pcm/decode-pcm.go +++ b/cmd/adpcm/decode-pcm/decode-pcm.go @@ -28,6 +28,7 @@ LICENSE package main import ( + "bytes" "flag" "fmt" "io/ioutil" @@ -56,20 +57,20 @@ func main() { 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 := make([]byte, 0, outBSize*numBlocks) + decoded := bytes.NewBuffer(make([]byte, 0, outBSize*numBlocks)) + dec := adpcm.NewDecoder(decoded) for i := 0; i < numBlocks; i++ { block := comp[inBSize*i : inBSize*(i+1)] - decBlock, err := adpcm.DecodeBlock(block) + err := dec.DecodeBlock(block) if err != nil { log.Fatal(err) } - decoded = append(decoded, decBlock...) } // save pcm to file - err = ioutil.WriteFile(outPath, decoded, 0644) + err = ioutil.WriteFile(outPath, decoded.Bytes(), 0644) if err != nil { log.Fatal(err) } - fmt.Println("Decoded and wrote", len(decoded), "bytes to file", outPath) + fmt.Println("Decoded and wrote", len(decoded.Bytes()), "bytes to file", outPath) } diff --git a/cmd/adpcm/encode-pcm/encode-pcm.go b/cmd/adpcm/encode-pcm/encode-pcm.go index 7e6bcfa6..f82b5a7f 100644 --- a/cmd/adpcm/encode-pcm/encode-pcm.go +++ b/cmd/adpcm/encode-pcm/encode-pcm.go @@ -28,6 +28,7 @@ LICENSE package main import ( + "bytes" "flag" "fmt" "io/ioutil" @@ -56,20 +57,20 @@ func main() { 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 := make([]byte, 0, outBSize*numBlocks) + comp := bytes.NewBuffer(make([]byte, 0, outBSize*numBlocks)) + enc := adpcm.NewEncoder(comp) for i := 0; i < numBlocks; i++ { block := pcm[inBSize*i : inBSize*(i+1)] - encBlock, err := adpcm.EncodeBlock(block) + err := enc.EncodeBlock(block) if err != nil { log.Fatal(err) } - comp = append(comp, encBlock...) } // save adpcm to file - err = ioutil.WriteFile(adpcmPath, comp, 0644) + err = ioutil.WriteFile(adpcmPath, comp.Bytes(), 0644) if err != nil { log.Fatal(err) } - fmt.Println("Encoded and wrote", len(comp), "bytes to file", adpcmPath) + fmt.Println("Encoded and wrote", len(comp.Bytes()), "bytes to file", adpcmPath) } From 146e993c038d4f86ebc9798c06040bfe7bb0ff89 Mon Sep 17 00:00:00 2001 From: Trek H Date: Fri, 1 Mar 2019 15:14:09 +1030 Subject: [PATCH 29/32] ADPCM: small fixes, comments and style. --- stream/adpcm/adpcm.go | 62 +++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/stream/adpcm/adpcm.go b/stream/adpcm/adpcm.go index 9e9bb324..af28615c 100644 --- a/stream/adpcm/adpcm.go +++ b/stream/adpcm/adpcm.go @@ -100,13 +100,13 @@ func NewDecoder(dst *bytes.Buffer) *Decoder { // encodeSample takes a single 16 bit PCM sample and // returns a byte of which the last 4 bits are an encoded ADPCM nibble func (e *Encoder) encodeSample(sample int16) byte { + // find difference of actual sample from encoder's prediction delta := sample - e.pred - var nibble byte - - // set sign bit and find absolute value of difference + // create and set sign bit for nibble and find absolute value of difference + var nib byte if delta < 0 { - nibble = 8 + nib = 8 delta = -delta } @@ -116,7 +116,7 @@ func (e *Encoder) encodeSample(sample int16) byte { for i := 0; i < 3; i++ { if delta > step { - nibble |= mask + nib |= mask delta -= step diff += step } @@ -125,21 +125,13 @@ func (e *Encoder) encodeSample(sample int16) byte { } // adjust predicted sample based on calculated difference - if nibble&8 != 0 { + if nib&8 != 0 { e.pred = capAdd16(e.pred, -diff) } else { e.pred = capAdd16(e.pred, diff) } - // check for underflow and overflow - if e.pred < math.MinInt16 { - e.pred = math.MinInt16 - } else if e.pred > math.MaxInt16 { - e.pred = math.MaxInt16 - } - - e.index += indexTable[nibble&7] - + e.index += indexTable[nib&7] // check for underflow and overflow if e.index < 0 { e.index = 0 @@ -147,7 +139,7 @@ func (e *Encoder) encodeSample(sample int16) byte { e.index = int16(len(stepTable) - 1) } - return nibble + return nib } // decodeSample takes a byte, the last 4 bits of which contain a single @@ -203,6 +195,8 @@ 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 func (e *Encoder) calcHead(sample []byte) error { // check that we are given 1 16-bit sample (2 bytes) sampSize := 2 @@ -221,7 +215,11 @@ func (e *Encoder) calcHead(sample []byte) error { } // 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). +// It outputs encoded (compressed) bytes (each byte containing two ADPCM nibbles) to the encoder's dest writer. +// note: nibbles are output in little endian order, eg. n1n0 n3n2 n5n4... +// 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 { @@ -233,42 +231,42 @@ func (e *Encoder) EncodeBlock(block []byte) error { return err } - for i := 2; i < len(block); i++ { - if (i+1)%4 == 0 { - sample2 := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i-1 : i+1]))) - sample := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i+1 : i+3]))) - e.dest.WriteByte(byte((sample << 4) | sample2)) - - } + for i := 3; i < bSize; 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)) } return 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. +// 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) } + // initialize decoder with first 4 bytes of the block d.pred = int16(binary.LittleEndian.Uint16(block[0:2])) d.index = int16(block[2]) d.step = stepTable[d.index] d.dest.Write(block[0:2]) - for i := 4; i < len(block); i++ { - originalSample := block[i] - secondSample := byte(originalSample >> 4) - firstSample := byte((secondSample << 4) ^ originalSample) + // 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++ { + twoNibs := block[i] + nib2 := byte(twoNibs >> 4) + nib1 := byte((nib2 << 4) ^ twoNibs) firstBytes := make([]byte, 2) - binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(firstSample))) + binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(nib1))) d.dest.Write(firstBytes) secondBytes := make([]byte, 2) - binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(secondSample))) + binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(nib2))) d.dest.Write(secondBytes) } From 4c64761f987955ee7c2a051f1ed555af148699b5 Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 7 Mar 2019 14:03:51 +1030 Subject: [PATCH 30/32] ADPCM: got rid of buggy overflow checks --- stream/adpcm/adpcm.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stream/adpcm/adpcm.go b/stream/adpcm/adpcm.go index af28615c..825279e3 100644 --- a/stream/adpcm/adpcm.go +++ b/stream/adpcm/adpcm.go @@ -126,9 +126,9 @@ func (e *Encoder) encodeSample(sample int16) byte { // adjust predicted sample based on calculated difference if nib&8 != 0 { - e.pred = capAdd16(e.pred, -diff) + e.pred -= diff } else { - e.pred = capAdd16(e.pred, diff) + e.pred += diff } e.index += indexTable[nib&7] @@ -164,7 +164,7 @@ func (d *Decoder) decodeSample(nibble byte) int16 { } // adjust predicted sample based on calculated difference - d.pred = capAdd16(d.pred, diff) + d.pred += diff // adjust index into step size lookup table using nibble d.index += indexTable[nibble] From d9baabdf025c12d128732acf85bcd15e63d33c67 Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 7 Mar 2019 15:57:40 +1030 Subject: [PATCH 31/32] ADPCM: removed capAdd16 no longer used --- stream/adpcm/adpcm.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/stream/adpcm/adpcm.go b/stream/adpcm/adpcm.go index 825279e3..dbe99b8e 100644 --- a/stream/adpcm/adpcm.go +++ b/stream/adpcm/adpcm.go @@ -36,7 +36,6 @@ import ( "bytes" "encoding/binary" "fmt" - "math" ) // Encoder is used to encode to ADPCM from PCM data. @@ -182,19 +181,6 @@ func (d *Decoder) decodeSample(nibble byte) int16 { return d.pred } -// capAdd16 adds two int16s together and caps at max/min int16 instead of overflowing -func capAdd16(a, b int16) int16 { - c := int32(a) + int32(b) - switch { - case c < math.MinInt16: - return math.MinInt16 - case c > math.MaxInt16: - return math.MaxInt16 - default: - return int16(c) - } -} - // 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 { From 6cfd7c5104d6d39564ad27c8001ae66ab879133b Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 7 Mar 2019 16:08:00 +1030 Subject: [PATCH 32/32] ADPCM: Improved comment consistancy. --- cmd/adpcm/decode-pcm/decode-pcm.go | 6 ++-- cmd/adpcm/encode-pcm/encode-pcm.go | 8 +++--- stream/adpcm/adpcm.go | 46 +++++++++++++++--------------- stream/adpcm/adpcm_test.go | 12 ++++---- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/cmd/adpcm/decode-pcm/decode-pcm.go b/cmd/adpcm/decode-pcm/decode-pcm.go index d661a71d..95aa6b40 100644 --- a/cmd/adpcm/decode-pcm/decode-pcm.go +++ b/cmd/adpcm/decode-pcm/decode-pcm.go @@ -46,14 +46,14 @@ func main() { flag.StringVar(&outPath, "out", "decoded.pcm", "file path of output data") flag.Parse() - // read adpcm + // Read adpcm. comp, err := ioutil.ReadFile(inPath) if err != nil { log.Fatal(err) } fmt.Println("Read", len(comp), "bytes from file", inPath) - // decode adpcm + // 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 @@ -67,7 +67,7 @@ func main() { } } - // save pcm to file + // Save pcm to file. err = ioutil.WriteFile(outPath, decoded.Bytes(), 0644) if err != nil { log.Fatal(err) diff --git a/cmd/adpcm/encode-pcm/encode-pcm.go b/cmd/adpcm/encode-pcm/encode-pcm.go index f82b5a7f..51b5dfb7 100644 --- a/cmd/adpcm/encode-pcm/encode-pcm.go +++ b/cmd/adpcm/encode-pcm/encode-pcm.go @@ -46,17 +46,17 @@ func main() { flag.StringVar(&adpcmPath, "out", "encoded.adpcm", "file path of output") flag.Parse() - //read pcm + // Read pcm. pcm, err := ioutil.ReadFile(inPath) if err != nil { log.Fatal(err) } fmt.Println("Read", len(pcm), "bytes from file", inPath) - //encode adpcm + // 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 + 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) for i := 0; i < numBlocks; i++ { @@ -67,7 +67,7 @@ func main() { } } - // save adpcm to file + // Save adpcm to file. err = ioutil.WriteFile(adpcmPath, comp.Bytes(), 0644) if err != nil { log.Fatal(err) diff --git a/stream/adpcm/adpcm.go b/stream/adpcm/adpcm.go index dbe99b8e..f309b78b 100644 --- a/stream/adpcm/adpcm.go +++ b/stream/adpcm/adpcm.go @@ -57,13 +57,13 @@ type Decoder struct { step int16 } -// table of index changes (see spec) +// Table of index changes (see spec). var indexTable = []int16{ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8, } -// quantize step size table (see spec) +// Quantize step size table (see spec). var stepTable = []int16{ 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, @@ -97,12 +97,12 @@ func NewDecoder(dst *bytes.Buffer) *Decoder { } // encodeSample takes a single 16 bit PCM sample and -// returns a byte of which the last 4 bits are an encoded ADPCM nibble +// returns a byte of which the last 4 bits are an encoded ADPCM nibble. func (e *Encoder) encodeSample(sample int16) byte { - // find difference of actual sample from encoder's prediction + // Find difference of actual sample from encoder's prediction. delta := sample - e.pred - // create and set sign bit for nibble and find absolute value of difference + // Create and set sign bit for nibble and find absolute value of difference. var nib byte if delta < 0 { nib = 8 @@ -123,7 +123,7 @@ func (e *Encoder) encodeSample(sample int16) byte { step >>= 1 } - // adjust predicted sample based on calculated difference + // Adjust predicted sample based on calculated difference. if nib&8 != 0 { e.pred -= diff } else { @@ -131,7 +131,7 @@ func (e *Encoder) encodeSample(sample int16) byte { } e.index += indexTable[nib&7] - // check for underflow and overflow + // Check for underflow and overflow. if e.index < 0 { e.index = 0 } else if e.index > int16(len(stepTable)-1) { @@ -142,9 +142,9 @@ func (e *Encoder) encodeSample(sample int16) byte { } // 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 +// 4 bit ADPCM nibble, and returns a 16 bit decoded PCM sample. func (d *Decoder) decodeSample(nibble byte) int16 { - // calculate difference + // Calculate difference. var diff int16 if nibble&4 != 0 { diff += d.step @@ -157,34 +157,34 @@ func (d *Decoder) decodeSample(nibble byte) int16 { } diff += d.step >> 3 - // account for sign bit + // Account for sign bit. if nibble&8 != 0 { diff = -diff } - // adjust predicted sample based on calculated difference + // Adjust predicted sample based on calculated difference. d.pred += diff - // adjust index into step size lookup table using nibble + // Adjust index into step size lookup table using nibble. d.index += indexTable[nibble] - // check for overflow and underflow + // Check for overflow and underflow. if d.index < 0 { d.index = 0 } else if d.index > int16(len(stepTable)-1) { d.index = int16(len(stepTable) - 1) } - // find new quantizer step size + // Find new quantizer step size. d.step = stepTable[d.index] return d.pred } // 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 { - // check that we are given 1 16-bit sample (2 bytes) + // Check that we are given 1 16-bit sample (2 bytes). sampSize := 2 if len(sample) != sampSize { return fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), sampSize) @@ -202,10 +202,10 @@ func (e *Encoder) calcHead(sample []byte) error { // EncodeBlock takes a slice of 1010 bytes (505 16-bit PCM samples). // It outputs encoded (compressed) bytes (each byte containing two ADPCM nibbles) to the encoder's dest writer. -// note: nibbles are output in little endian order, eg. n1n0 n3n2 n5n4... -// 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 +// Note: nibbles are output in little endian order, eg. n1n0 n3n2 n5n4... +// 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 { @@ -234,14 +234,14 @@ func (d *Decoder) DecodeBlock(block []byte) error { 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. d.pred = int16(binary.LittleEndian.Uint16(block[0:2])) d.index = int16(block[2]) d.step = stepTable[d.index] d.dest.Write(block[0:2]) - // 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 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++ { twoNibs := block[i] nib2 := byte(twoNibs >> 4) diff --git a/stream/adpcm/adpcm_test.go b/stream/adpcm/adpcm_test.go index f82b421b..6cb6ee2f 100644 --- a/stream/adpcm/adpcm_test.go +++ b/stream/adpcm/adpcm_test.go @@ -37,13 +37,13 @@ import ( // TestEncodeBlock will read PCM data, encode it in blocks and generate ADPCM // then compare the result with expected ADPCM. func TestEncodeBlock(t *testing.T) { - //read input pcm + // Read input pcm. pcm, err := ioutil.ReadFile("../../../test/test-data/av/input/raw-voice.pcm") if err != nil { t.Errorf("Unable to read input PCM file: %v", err) } - //encode adpcm + // 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 @@ -57,7 +57,7 @@ func TestEncodeBlock(t *testing.T) { } } - //read expected adpcm file + // Read expected adpcm file. exp, err := ioutil.ReadFile("../../../test/test-data/av/output/encoded-voice.adpcm") if err != nil { t.Errorf("Unable to read expected ADPCM file: %v", err) @@ -71,13 +71,13 @@ func TestEncodeBlock(t *testing.T) { // TestDecodeBlock will read encoded ADPCM, decode it in blocks and then compare the // resulting PCM with the expected decoded PCM. func TestDecodeBlock(t *testing.T) { - //read adpcm + // Read adpcm. comp, err := ioutil.ReadFile("../../../test/test-data/av/input/encoded-voice.adpcm") if err != nil { t.Errorf("Unable to read input ADPCM file: %v", err) } - //decode adpcm + // 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 @@ -91,7 +91,7 @@ func TestDecodeBlock(t *testing.T) { } } - //read expected pcm file + // Read expected pcm file. exp, err := ioutil.ReadFile("../../../test/test-data/av/output/decoded-voice.pcm") if err != nil { t.Errorf("Unable to read expected PCM file: %v", err)