From b1d8c3888c2087d04b0cd78b5872c151fd124389 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 23 Jan 2019 16:33:28 +1030 Subject: [PATCH 01/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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) From 00cb9bf3f622189e016eb3aa680dc6f821194154 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 23 Jan 2019 16:33:28 +1030 Subject: [PATCH 33/73] 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 6beefc536b62d89b2a56962e0a7e12d3b743354c Mon Sep 17 00:00:00 2001 From: Trek Date: Fri, 1 Feb 2019 01:07:24 +1030 Subject: [PATCH 34/73] 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 33d62672c62ff09832fae08a1fc54790766227d8 Mon Sep 17 00:00:00 2001 From: Trek Date: Tue, 5 Feb 2019 16:36:03 +1030 Subject: [PATCH 35/73] 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 f5f392243628383011f68f110db9281e9747fae1 Mon Sep 17 00:00:00 2001 From: Trek Date: Wed, 6 Feb 2019 15:39:14 +1030 Subject: [PATCH 36/73] 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 ca71a5ed494de2c06e94701eacd0bc6e8e750812 Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 10:02:24 +1030 Subject: [PATCH 37/73] 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 2aa9c3a3606e41bc31835bfaf2ffdcf158344340 Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 10:10:12 +1030 Subject: [PATCH 38/73] 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 07b228fde3cd8511c5de301d99ea33480e25854c Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 11:01:38 +1030 Subject: [PATCH 39/73] 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 102a7736db3efc30ecfacbcdcb0dc65bbf85736a Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 11:35:16 +1030 Subject: [PATCH 40/73] 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 7f9a919baaf4006c299eee1a53902d69478c9cc3 Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 12:11:26 +1030 Subject: [PATCH 41/73] 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 9fdfde6d8739b7ef12a07d9e585cf3398574e0f6 Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 15:25:10 +1030 Subject: [PATCH 42/73] 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 9939de26ac840d90e71a5498b3e8b1592f188bf7 Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 15:47:45 +1030 Subject: [PATCH 43/73] 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 35d98f1a73b3fa8399bc8eb5321e651fa6400e46 Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 11 Feb 2019 15:58:33 +1030 Subject: [PATCH 44/73] 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 1d88c498ca0c7187c73fce75d169abb0a402b771 Mon Sep 17 00:00:00 2001 From: Trek H Date: Tue, 12 Feb 2019 14:49:16 +1030 Subject: [PATCH 45/73] 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 8762b3c74f0b9ff7a1e587dd759aba722b333b8b Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 13 Feb 2019 11:53:06 +1030 Subject: [PATCH 46/73] 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 6edd86f5dae8d8e3edcc144cd174546977d0d179 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 13 Feb 2019 12:43:13 +1030 Subject: [PATCH 47/73] 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 071b16ccf6664a60bb2ba1b87ebbd7e142093f60 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 13 Feb 2019 16:53:25 +1030 Subject: [PATCH 48/73] 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 408a952c9a7dd31b9898f05b0391e8181764f88d Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 13 Feb 2019 16:58:21 +1030 Subject: [PATCH 49/73] 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 ebdd65ea09c71181c53ae46cb9d90cb0da6becea Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 13 Feb 2019 17:30:11 +1030 Subject: [PATCH 50/73] 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 3f8d889ce5dba8a675c00579f24f109ccca055b0 Mon Sep 17 00:00:00 2001 From: Trek H Date: Fri, 15 Feb 2019 13:31:16 +1030 Subject: [PATCH 51/73] 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 48e848b7c43d3f61cb9e4d06a34443535a983f5e Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 25 Feb 2019 11:55:13 +1030 Subject: [PATCH 52/73] 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 1ad219593810fa010da424b7c8d23ef5a3322724 Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 25 Feb 2019 14:43:26 +1030 Subject: [PATCH 53/73] 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 5204b52ecef81d86eb1daae14b2fa02c5350bcd9 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 27 Feb 2019 16:46:15 +1030 Subject: [PATCH 54/73] 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 7af363a3a4b3001184cc79ef248a44a22765b403 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 27 Feb 2019 16:51:06 +1030 Subject: [PATCH 55/73] 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 8f282b12008a2b7ef497745b98e2d410a0564bd8 Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 28 Feb 2019 11:53:16 +1030 Subject: [PATCH 56/73] 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 edb0ec6de131534e240a8685c39658e397c1a1bb Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 28 Feb 2019 12:53:51 +1030 Subject: [PATCH 57/73] 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 e45c67e15706066827504bda638046f1776d33d1 Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 28 Feb 2019 13:05:45 +1030 Subject: [PATCH 58/73] 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 29f4acd7fe1d65eb576eb03139806cecb9ed43d2 Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 28 Feb 2019 13:12:41 +1030 Subject: [PATCH 59/73] 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 d5cf171485317951f5ec2055c93bf6aedee9cf35 Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 28 Feb 2019 13:32:57 +1030 Subject: [PATCH 60/73] 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 89ccf9eac7594c828f1ce66dffadd4bab1844e56 Mon Sep 17 00:00:00 2001 From: Trek H Date: Fri, 1 Mar 2019 15:14:09 +1030 Subject: [PATCH 61/73] 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 8642d1e0879305a4ca30632dcea5f05fc9683ceb Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 7 Mar 2019 14:03:51 +1030 Subject: [PATCH 62/73] 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 c271418f589b7cdb95aa0a6d379dbc7fae4b20f1 Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 7 Mar 2019 15:57:40 +1030 Subject: [PATCH 63/73] 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 7c2fccb50f05194f053ce27f43f31d8e46b907a6 Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 7 Mar 2019 16:08:00 +1030 Subject: [PATCH 64/73] 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) From c234c8b760bcd2730ccf84ed1fc22f242e3c1005 Mon Sep 17 00:00:00 2001 From: Trek H Date: Fri, 8 Mar 2019 15:46:48 +1030 Subject: [PATCH 65/73] ADPCM: changed location of encode decode adpcm file commands --- {cmd => exp}/adpcm/decode-pcm/decode-pcm.go | 0 {cmd => exp}/adpcm/encode-pcm/encode-pcm.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {cmd => exp}/adpcm/decode-pcm/decode-pcm.go (100%) rename {cmd => exp}/adpcm/encode-pcm/encode-pcm.go (100%) diff --git a/cmd/adpcm/decode-pcm/decode-pcm.go b/exp/adpcm/decode-pcm/decode-pcm.go similarity index 100% rename from cmd/adpcm/decode-pcm/decode-pcm.go rename to exp/adpcm/decode-pcm/decode-pcm.go diff --git a/cmd/adpcm/encode-pcm/encode-pcm.go b/exp/adpcm/encode-pcm/encode-pcm.go similarity index 100% rename from cmd/adpcm/encode-pcm/encode-pcm.go rename to exp/adpcm/encode-pcm/encode-pcm.go From ed6b2cc7f12f8068a4f4bb744c7392cba1a2a833 Mon Sep 17 00:00:00 2001 From: Trek H Date: Fri, 8 Mar 2019 15:55:20 +1030 Subject: [PATCH 66/73] ADPCM: changed directory of adpcm package to audio folder --- {stream => audio}/adpcm/adpcm.go | 0 {stream => audio}/adpcm/adpcm_test.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {stream => audio}/adpcm/adpcm.go (100%) rename {stream => audio}/adpcm/adpcm_test.go (100%) diff --git a/stream/adpcm/adpcm.go b/audio/adpcm/adpcm.go similarity index 100% rename from stream/adpcm/adpcm.go rename to audio/adpcm/adpcm.go diff --git a/stream/adpcm/adpcm_test.go b/audio/adpcm/adpcm_test.go similarity index 100% rename from stream/adpcm/adpcm_test.go rename to audio/adpcm/adpcm_test.go From 30711a54fa23e2590f0a990f8a9e51c08f86aba9 Mon Sep 17 00:00:00 2001 From: Dan Kortschak Date: Wed, 13 Mar 2019 17:25:47 +1030 Subject: [PATCH 67/73] rtmp: fix parseURL panic and improve playpath handling --- rtmp/parseurl.go | 34 ++++--- rtmp/parseurl_test.go | 200 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+), 13 deletions(-) create mode 100644 rtmp/parseurl_test.go diff --git a/rtmp/parseurl.go b/rtmp/parseurl.go index 4fea7d82..cf3e4fba 100644 --- a/rtmp/parseurl.go +++ b/rtmp/parseurl.go @@ -78,25 +78,33 @@ func parseURL(addr string) (protocol int32, host string, port uint16, app, playp if !path.IsAbs(u.Path) { return protocol, host, port, app, playpath, nil } + elems := strings.SplitN(u.Path[1:], "/", 3) app = elems[0] - if app == "" { + if len(elems[len(elems)-1]) == 0 { + elems = elems[:len(elems)-1] + } + if app == "" || len(elems) < 2 { return protocol, host, port, app, playpath, errInvalidURL } - playpath = elems[1] - if len(elems) == 3 && len(elems[2]) != 0 { - playpath = path.Join(elems[1:]...) - switch ext := path.Ext(playpath); ext { - case ".f4v", ".mp4": - playpath = "mp4:" + playpath[:len(playpath)-len(ext)] - case ".mp3": - playpath = "mp3:" + playpath[:len(playpath)-len(ext)] - case ".flv": - playpath = playpath[:len(playpath)-len(ext)] + + playpath = path.Join(elems[1:]...) + switch ext := path.Ext(playpath); ext { + case ".f4v", ".mp4": + playpath = playpath[:len(playpath)-len(ext)] + if !strings.HasPrefix(playpath, "mp4:") { + playpath = "mp4:" + playpath } - if u.RawQuery != "" { - playpath += "?" + u.RawQuery + case ".mp3": + playpath = playpath[:len(playpath)-len(ext)] + if !strings.HasPrefix(playpath, "mp3:") { + playpath = "mp3:" + playpath } + case ".flv": + playpath = playpath[:len(playpath)-len(ext)] + } + if u.RawQuery != "" { + playpath += "?" + u.RawQuery } switch { diff --git a/rtmp/parseurl_test.go b/rtmp/parseurl_test.go new file mode 100644 index 00000000..7f5e3029 --- /dev/null +++ b/rtmp/parseurl_test.go @@ -0,0 +1,200 @@ +/* +NAME + parseurl_test.go + +DESCRIPTION + See Readme.md + +AUTHOR + Dan Kortschak + +LICENSE + parseurl.go is Copyright (C) 2019 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 http://www.gnu.org/licenses. +*/ +package rtmp + +import ( + "testing" +) + +var parseURLTests = []struct { + url string + wantProtocol int32 + wantHost string + wantPort uint16 + wantApp string + wantPlaypath string + wantErr error +}{ + { + url: "rtmp://addr", + wantHost: "addr", + }, + { + url: "rtmp://addr/", + wantErr: errInvalidURL, + }, + { + url: "rtmp://addr/live2", + wantErr: errInvalidURL, + }, + { + url: "rtmp://addr/live2/", + wantErr: errInvalidURL, + }, + { + url: "rtmp://addr/appname/key", + wantHost: "addr", + wantPort: 1935, + wantApp: "appname", + wantPlaypath: "key", + }, + { + url: "rtmp://addr/appname/instancename", + wantHost: "addr", + wantPort: 1935, + wantApp: "appname", + wantPlaypath: "instancename", + }, + { + url: "rtmp://addr/appname/instancename/", + wantHost: "addr", + wantPort: 1935, + wantApp: "appname", + wantPlaypath: "instancename", + }, + { + url: "rtmp://addr/appname/mp4:path.f4v", + wantHost: "addr", + wantPort: 1935, + wantApp: "appname", + wantPlaypath: "mp4:path", + }, + { + url: "rtmp://addr/appname/mp4:path.f4v?param1=value1¶m2=value2", + wantHost: "addr", + wantPort: 1935, + wantApp: "appname", + wantPlaypath: "mp4:path?param1=value1¶m2=value2", + }, + { + url: "rtmp://addr/appname/mp4:path/to/file.f4v", + wantHost: "addr", + wantPort: 1935, + wantApp: "appname", + wantPlaypath: "mp4:path/to/file", + }, + { + url: "rtmp://addr/appname/mp4:path/to/file.f4v?param1=value1¶m2=value2", + wantHost: "addr", + wantPort: 1935, + wantApp: "appname", + wantPlaypath: "mp4:path/to/file?param1=value1¶m2=value2", + }, + { + url: "rtmp://addr/appname/path.mp4", + wantHost: "addr", + wantPort: 1935, + wantApp: "appname", + wantPlaypath: "mp4:path", + }, + { + url: "rtmp://addr/appname/path.mp4?param1=value1¶m2=value2", + wantHost: "addr", + wantPort: 1935, + wantApp: "appname", + wantPlaypath: "mp4:path?param1=value1¶m2=value2", + }, + { + url: "rtmp://addr/appname/path/to/file.mp4", + wantHost: "addr", + wantPort: 1935, + wantApp: "appname", + wantPlaypath: "mp4:path/to/file", + }, + { + url: "rtmp://addr/appname/path/to/file.mp4?param1=value1¶m2=value2", + wantHost: "addr", + wantPort: 1935, + wantApp: "appname", + wantPlaypath: "mp4:path/to/file?param1=value1¶m2=value2", + }, + { + url: "rtmp://addr/appname/path.flv", + wantHost: "addr", + wantPort: 1935, + wantApp: "appname", + wantPlaypath: "path", + }, + { + url: "rtmp://addr/appname/path.flv?param1=value1¶m2=value2", + wantHost: "addr", + wantPort: 1935, + wantApp: "appname", + wantPlaypath: "path?param1=value1¶m2=value2", + }, + { + url: "rtmp://addr/appname/path/to/file.flv", + wantHost: "addr", + wantPort: 1935, + wantApp: "appname", + wantPlaypath: "path/to/file", + }, + { + url: "rtmp://addr/appname/path/to/file.flv?param1=value1¶m2=value2", + wantHost: "addr", + wantPort: 1935, + wantApp: "appname", + wantPlaypath: "path/to/file?param1=value1¶m2=value2", + }, +} + +func TestParseURL(t *testing.T) { + for _, test := range parseURLTests { + func() { + defer func() { + p := recover() + if p != nil { + t.Errorf("unexpected panic for %q: %v", test.url, p) + } + }() + + protocol, host, port, app, playpath, err := parseURL(test.url) + if err != test.wantErr { + t.Errorf("unexpected error for %q: got:%v want:%v", test.url, err, test.wantErr) + return + } + if err != nil { + return + } + if protocol != test.wantProtocol { + t.Errorf("unexpected protocol for %q: got:%v want:%v", test.url, protocol, test.wantProtocol) + } + if host != test.wantHost { + t.Errorf("unexpected host for %q: got:%v want:%v", test.url, host, test.wantHost) + } + if port != test.wantPort { + t.Errorf("unexpected port for %q: got:%v want:%v", test.url, port, test.wantPort) + } + if app != test.wantApp { + t.Errorf("unexpected app for %q: got:%v want:%v", test.url, app, test.wantApp) + } + if playpath != test.wantPlaypath { + t.Errorf("unexpected playpath for %q: got:%v want:%v", test.url, playpath, test.wantPlaypath) + } + }() + } +} From 61c1d99c43b89378f230524571ecd72135204886 Mon Sep 17 00:00:00 2001 From: scruzin Date: Wed, 13 Mar 2019 18:47:00 +1030 Subject: [PATCH 68/73] Made parseURL() more robust. --- rtmp/parseurl.go | 18 +++++++----------- rtmp/parseurl_test.go | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/rtmp/parseurl.go b/rtmp/parseurl.go index cf3e4fba..132c9acd 100644 --- a/rtmp/parseurl.go +++ b/rtmp/parseurl.go @@ -75,20 +75,16 @@ func parseURL(addr string) (protocol int32, host string, port uint16, app, playp port = uint16(pi) } - if !path.IsAbs(u.Path) { - return protocol, host, port, app, playpath, nil - } - - elems := strings.SplitN(u.Path[1:], "/", 3) - app = elems[0] - if len(elems[len(elems)-1]) == 0 { - elems = elems[:len(elems)-1] - } - if app == "" || len(elems) < 2 { + if len(u.Path) < 1 || !path.IsAbs(u.Path) { return protocol, host, port, app, playpath, errInvalidURL } - + elems := strings.SplitN(u.Path[1:], "/", 3) + if len(elems) < 2 || elems[0] == "" || elems[1] == "" { + return protocol, host, port, app, playpath, errInvalidURL + } + app = elems[0] playpath = path.Join(elems[1:]...) + switch ext := path.Ext(playpath); ext { case ".f4v", ".mp4": playpath = playpath[:len(playpath)-len(ext)] diff --git a/rtmp/parseurl_test.go b/rtmp/parseurl_test.go index 7f5e3029..47743693 100644 --- a/rtmp/parseurl_test.go +++ b/rtmp/parseurl_test.go @@ -41,7 +41,7 @@ var parseURLTests = []struct { }{ { url: "rtmp://addr", - wantHost: "addr", + wantErr: errInvalidURL, }, { url: "rtmp://addr/", From d2411a07618ceefb8ccb9c44b68ae1ff6efbf308 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 14 Mar 2019 14:16:55 +1030 Subject: [PATCH 69/73] revid: param label RtpAddr => RtpAddress --- revid/revid.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/revid/revid.go b/revid/revid.go index d2426432..41ea03c5 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -399,7 +399,7 @@ func (r *Revid) Update(vars map[string]string) error { r.config.FramesPerClip = uint(f) case "RtmpUrl": r.config.RtmpUrl = value - case "RtpAddr": + case "RtpAddress": r.config.RtpAddress = value case "Bitrate": v, err := strconv.ParseUint(value, 10, 0) From c7c7ef75f53bb1e1d8ab96327ee21a87b0970110 Mon Sep 17 00:00:00 2001 From: Trek H Date: Fri, 15 Mar 2019 15:47:08 +1030 Subject: [PATCH 70/73] ADPCM: Added a Write function to Encoder so that it implements an io.Writer, and also so that a byte slice of any length can be encoded. Added global variables for adpcm and pcm block sizes. Updated tests, encode and decode pcm commands. --- audio/adpcm/adpcm.go | 77 ++++++++++++++++++++++-------- audio/adpcm/adpcm_test.go | 26 ++++------ exp/adpcm/decode-pcm/decode-pcm.go | 10 ++-- exp/adpcm/encode-pcm/encode-pcm.go | 17 +++---- 4 files changed, 76 insertions(+), 54 deletions(-) diff --git a/audio/adpcm/adpcm.go b/audio/adpcm/adpcm.go index f309b78b..40613bde 100644 --- a/audio/adpcm/adpcm.go +++ b/audio/adpcm/adpcm.go @@ -57,6 +57,14 @@ type Decoder struct { step int16 } +// PcmBS is the size of the blocks that an Encoder uses. +// 'EncodeBlock' will encode PcmBS bytes at a time and the output will be AdpcmBS bytes long. +const PcmBS = 1010 + +// AdpcmBS is the size of the blocks that a Decoder uses. +// 'DecodeBlock' will decode AdpcmBS bytes at a time and the output will be PcmBS bytes long. +const AdpcmBS = 256 + // Table of index changes (see spec). var indexTable = []int16{ -1, -1, -1, -1, 2, 4, 6, 8, @@ -183,21 +191,33 @@ func (d *Decoder) decodeSample(nibble byte) int16 { // calcHead sets the state for the encoder by running the first sample through // the encoder, and writing the first sample. -func (e *Encoder) calcHead(sample []byte) error { +func (e *Encoder) calcHead(sample []byte) (int, error) { // Check that we are given 1 16-bit sample (2 bytes). - sampSize := 2 + const sampSize = 2 if len(sample) != sampSize { - return fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), sampSize) + return 0, fmt.Errorf("length of given byte array is: %v, expected: %v", len(sample), sampSize) } intSample := int16(binary.LittleEndian.Uint16(sample)) e.encodeSample(intSample) - e.dest.Write(sample) + writ, err := e.dest.Write(sample) + if err != nil { + return writ, err + } - e.dest.WriteByte(byte(uint16(e.index))) - e.dest.WriteByte(byte(0x00)) - return nil + err = e.dest.WriteByte(byte(uint16(e.index))) + if err != nil { + return writ, err + } + writ++ + + err = e.dest.WriteByte(byte(0x00)) + if err != nil { + return writ, err + } + writ++ + return writ, nil } // EncodeBlock takes a slice of 1010 bytes (505 16-bit PCM samples). @@ -206,32 +226,49 @@ func (e *Encoder) calcHead(sample []byte) error { // Note: first 4 bytes are for initializing the decoder before decoding a block. // - First two bytes contain the first 16-bit sample uncompressed. // - Third byte is the decoder's starting index for the block, the fourth is padding and ignored. -func (e *Encoder) EncodeBlock(block []byte) error { - bSize := 1010 - if len(block) != bSize { - return fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), bSize) +func (e *Encoder) EncodeBlock(block []byte) (int, error) { + if len(block) != PcmBS { + return 0, fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), PcmBS) } - err := e.calcHead(block[0:2]) + writ, err := e.calcHead(block[0:2]) if err != nil { - return err + return writ, err } - for i := 3; i < bSize; i += 4 { + for i := 3; i < PcmBS; i += 4 { nib1 := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i-1 : i+1]))) nib2 := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i+1 : i+3]))) - e.dest.WriteByte(byte((nib2 << 4) | nib1)) + err = e.dest.WriteByte(byte((nib2 << 4) | nib1)) + if err != nil { + return writ, err + } + writ++ } - return nil + return writ, nil +} + +func (e *Encoder) Write(inPcm []byte) (int, error) { + numBlocks := len(inPcm) / PcmBS + writ := 0 + for i := 0; i < numBlocks; i++ { + block := inPcm[PcmBS*i : PcmBS*(i+1)] + writB, err := e.EncodeBlock(block) + writ += writB + if err != nil { + return writ, err + } + } + + return writ, nil } // DecodeBlock takes a slice of 256 bytes, each byte after the first 4 should contain two ADPCM encoded nibbles. // It outputs the resulting decoded (decompressed) 16-bit PCM samples to the decoder's dest writer. func (d *Decoder) DecodeBlock(block []byte) error { - bSize := 256 - if len(block) != bSize { - return fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), bSize) + if len(block) != AdpcmBS { + return fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), AdpcmBS) } // Initialize decoder with first 4 bytes of the block. @@ -242,7 +279,7 @@ func (d *Decoder) DecodeBlock(block []byte) error { // For each byte, seperate it into two nibbles (each nibble is a compressed sample), // then decode each nibble and output the resulting 16-bit samples. - for i := 4; i < bSize; i++ { + for i := 4; i < AdpcmBS; i++ { twoNibs := block[i] nib2 := byte(twoNibs >> 4) nib1 := byte((nib2 << 4) ^ twoNibs) diff --git a/audio/adpcm/adpcm_test.go b/audio/adpcm/adpcm_test.go index 6cb6ee2f..cf1a4c71 100644 --- a/audio/adpcm/adpcm_test.go +++ b/audio/adpcm/adpcm_test.go @@ -30,7 +30,6 @@ package adpcm import ( "bytes" "io/ioutil" - "log" "testing" ) @@ -44,17 +43,12 @@ func TestEncodeBlock(t *testing.T) { } // Encode adpcm. - inBSize := 1010 - numBlocks := len(pcm) / inBSize - outBSize := int(float64(inBSize)/4 + 3.5) // compression is 4:1 and 3.5 bytes of info are added to each block - comp := bytes.NewBuffer(make([]byte, 0, outBSize*numBlocks)) + numBlocks := len(pcm) / PcmBS + comp := bytes.NewBuffer(make([]byte, 0, AdpcmBS*numBlocks)) enc := NewEncoder(comp) - for i := 0; i < numBlocks; i++ { - block := pcm[inBSize*i : inBSize*(i+1)] - err := enc.EncodeBlock(block) - if err != nil { - log.Fatal(err) - } + _, err = enc.Write(pcm) + if err != nil { + t.Errorf("Unable to write to encoder: %v", err) } // Read expected adpcm file. @@ -78,16 +72,14 @@ func TestDecodeBlock(t *testing.T) { } // Decode adpcm. - inBSize := 256 - numBlocks := len(comp) / inBSize - outBSize := 2 + (inBSize-4)*4 // 2 bytes are copied, 2 are used as block header info, the remaining bytes are decompressed 1:4 - decoded := bytes.NewBuffer(make([]byte, 0, outBSize*numBlocks)) + numBlocks := len(comp) / AdpcmBS + decoded := bytes.NewBuffer(make([]byte, 0, PcmBS*numBlocks)) dec := NewDecoder(decoded) for i := 0; i < numBlocks; i++ { - block := comp[inBSize*i : inBSize*(i+1)] + block := comp[AdpcmBS*i : AdpcmBS*(i+1)] err := dec.DecodeBlock(block) if err != nil { - log.Fatal(err) + t.Errorf("Unable to write to decoder: %v", err) } } diff --git a/exp/adpcm/decode-pcm/decode-pcm.go b/exp/adpcm/decode-pcm/decode-pcm.go index 95aa6b40..2d5f2625 100644 --- a/exp/adpcm/decode-pcm/decode-pcm.go +++ b/exp/adpcm/decode-pcm/decode-pcm.go @@ -34,7 +34,7 @@ import ( "io/ioutil" "log" - "bitbucket.org/ausocean/av/stream/adpcm" + "bitbucket.org/ausocean/av/audio/adpcm" ) // This program accepts an input file encoded in adpcm and outputs a decoded pcm file. @@ -54,13 +54,11 @@ func main() { fmt.Println("Read", len(comp), "bytes from file", inPath) // Decode adpcm. - inBSize := 256 - numBlocks := len(comp) / inBSize - outBSize := 2 + (inBSize-4)*4 // 2 bytes are copied, 2 are used as block header info, the remaining bytes are decompressed 1:4 - decoded := bytes.NewBuffer(make([]byte, 0, outBSize*numBlocks)) + numBlocks := len(comp) / adpcm.AdpcmBS + decoded := bytes.NewBuffer(make([]byte, 0, adpcm.PcmBS*numBlocks)) dec := adpcm.NewDecoder(decoded) for i := 0; i < numBlocks; i++ { - block := comp[inBSize*i : inBSize*(i+1)] + block := comp[adpcm.AdpcmBS*i : adpcm.AdpcmBS*(i+1)] err := dec.DecodeBlock(block) if err != nil { log.Fatal(err) diff --git a/exp/adpcm/encode-pcm/encode-pcm.go b/exp/adpcm/encode-pcm/encode-pcm.go index 51b5dfb7..c9796228 100644 --- a/exp/adpcm/encode-pcm/encode-pcm.go +++ b/exp/adpcm/encode-pcm/encode-pcm.go @@ -34,7 +34,7 @@ import ( "io/ioutil" "log" - "bitbucket.org/ausocean/av/stream/adpcm" + "bitbucket.org/ausocean/av/audio/adpcm" ) // This program accepts an input pcm file and outputs an encoded adpcm file. @@ -54,17 +54,12 @@ func main() { fmt.Println("Read", len(pcm), "bytes from file", inPath) // Encode adpcm. - inBSize := 1010 - numBlocks := len(pcm) / inBSize - outBSize := int(float64(inBSize)/4 + 3.5) // Compression is 4:1 and 3.5 bytes of info are added to each block. - comp := bytes.NewBuffer(make([]byte, 0, outBSize*numBlocks)) + numBlocks := len(pcm) / adpcm.PcmBS + comp := bytes.NewBuffer(make([]byte, 0, adpcm.AdpcmBS*numBlocks)) enc := adpcm.NewEncoder(comp) - for i := 0; i < numBlocks; i++ { - block := pcm[inBSize*i : inBSize*(i+1)] - err := enc.EncodeBlock(block) - if err != nil { - log.Fatal(err) - } + _, err = enc.Write(pcm) + if err != nil { + log.Fatal(err) } // Save adpcm to file. From fdc4d880ac835661bcba2763d917ca7248b30962 Mon Sep 17 00:00:00 2001 From: Trek H Date: Fri, 15 Mar 2019 17:07:22 +1030 Subject: [PATCH 71/73] ADPCM: added a Write function to decoder so that it implements io.Writer, and also so that it can decode adpcm of arbitrary length. Updated test and decode command to use Write. --- audio/adpcm/adpcm.go | 86 ++++++++++++++++++++---------- audio/adpcm/adpcm_test.go | 9 ++-- exp/adpcm/decode-pcm/decode-pcm.go | 9 ++-- 3 files changed, 63 insertions(+), 41 deletions(-) diff --git a/audio/adpcm/adpcm.go b/audio/adpcm/adpcm.go index 40613bde..5aaa98b1 100644 --- a/audio/adpcm/adpcm.go +++ b/audio/adpcm/adpcm.go @@ -227,8 +227,9 @@ func (e *Encoder) calcHead(sample []byte) (int, error) { // - 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) (int, error) { + writ := 0 if len(block) != PcmBS { - return 0, fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), PcmBS) + return writ, fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), PcmBS) } writ, err := e.calcHead(block[0:2]) @@ -249,6 +250,50 @@ func (e *Encoder) EncodeBlock(block []byte) (int, error) { return writ, nil } +// DecodeBlock takes a slice of 256 bytes, each byte after the first 4 should contain two ADPCM encoded nibbles. +// It outputs the resulting decoded (decompressed) 16-bit PCM samples to the decoder's dest writer. +func (d *Decoder) DecodeBlock(block []byte) (int, error) { + writ := 0 + if len(block) != AdpcmBS { + return writ, fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), AdpcmBS) + } + + // 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] + writ, err := d.dest.Write(block[0:2]) + if err != nil { + return writ, err + } + + // 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 < AdpcmBS; i++ { + twoNibs := block[i] + nib2 := byte(twoNibs >> 4) + nib1 := byte((nib2 << 4) ^ twoNibs) + + firstBytes := make([]byte, 2) + binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(nib1))) + writS, err := d.dest.Write(firstBytes) + writ += writS + if err != nil { + return writ, err + } + + secondBytes := make([]byte, 2) + binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(nib2))) + writS, err = d.dest.Write(secondBytes) + writ += writS + if err != nil { + return writ, err + } + } + + return writ, nil +} + func (e *Encoder) Write(inPcm []byte) (int, error) { numBlocks := len(inPcm) / PcmBS writ := 0 @@ -264,34 +309,17 @@ func (e *Encoder) Write(inPcm []byte) (int, error) { return writ, nil } -// DecodeBlock takes a slice of 256 bytes, each byte after the first 4 should contain two ADPCM encoded nibbles. -// It outputs the resulting decoded (decompressed) 16-bit PCM samples to the decoder's dest writer. -func (d *Decoder) DecodeBlock(block []byte) error { - if len(block) != AdpcmBS { - return fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), AdpcmBS) +func (d *Decoder) Write(inAdpcm []byte) (int, error) { + numBlocks := len(inAdpcm) / AdpcmBS + writ := 0 + for i := 0; i < numBlocks; i++ { + block := inAdpcm[AdpcmBS*i : AdpcmBS*(i+1)] + writB, err := d.DecodeBlock(block) + writ += writB + if err != nil { + return writ, err + } } - // 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 i := 4; i < AdpcmBS; i++ { - twoNibs := block[i] - nib2 := byte(twoNibs >> 4) - nib1 := byte((nib2 << 4) ^ twoNibs) - - firstBytes := make([]byte, 2) - binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(nib1))) - d.dest.Write(firstBytes) - - secondBytes := make([]byte, 2) - binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(nib2))) - d.dest.Write(secondBytes) - } - - return nil + return writ, nil } diff --git a/audio/adpcm/adpcm_test.go b/audio/adpcm/adpcm_test.go index cf1a4c71..9df24028 100644 --- a/audio/adpcm/adpcm_test.go +++ b/audio/adpcm/adpcm_test.go @@ -75,12 +75,9 @@ func TestDecodeBlock(t *testing.T) { numBlocks := len(comp) / AdpcmBS decoded := bytes.NewBuffer(make([]byte, 0, PcmBS*numBlocks)) dec := NewDecoder(decoded) - for i := 0; i < numBlocks; i++ { - block := comp[AdpcmBS*i : AdpcmBS*(i+1)] - err := dec.DecodeBlock(block) - if err != nil { - t.Errorf("Unable to write to decoder: %v", err) - } + _, err = dec.Write(comp) + if err != nil { + t.Errorf("Unable to write to decoder: %v", err) } // Read expected pcm file. diff --git a/exp/adpcm/decode-pcm/decode-pcm.go b/exp/adpcm/decode-pcm/decode-pcm.go index 2d5f2625..3b739262 100644 --- a/exp/adpcm/decode-pcm/decode-pcm.go +++ b/exp/adpcm/decode-pcm/decode-pcm.go @@ -57,12 +57,9 @@ func main() { numBlocks := len(comp) / adpcm.AdpcmBS decoded := bytes.NewBuffer(make([]byte, 0, adpcm.PcmBS*numBlocks)) dec := adpcm.NewDecoder(decoded) - for i := 0; i < numBlocks; i++ { - block := comp[adpcm.AdpcmBS*i : adpcm.AdpcmBS*(i+1)] - err := dec.DecodeBlock(block) - if err != nil { - log.Fatal(err) - } + _, err = dec.Write(comp) + if err != nil { + log.Fatal(err) } // Save pcm to file. From d4e0c876356a6ca3315e398791600d4b899c9a2c Mon Sep 17 00:00:00 2001 From: Trek H Date: Fri, 15 Mar 2019 17:22:19 +1030 Subject: [PATCH 72/73] ADPCM: unexported encoder and decoder structs, documented Write funcs. --- audio/adpcm/adpcm.go | 53 +++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/audio/adpcm/adpcm.go b/audio/adpcm/adpcm.go index 5aaa98b1..ee6feaf6 100644 --- a/audio/adpcm/adpcm.go +++ b/audio/adpcm/adpcm.go @@ -38,31 +38,31 @@ import ( "fmt" ) -// Encoder is used to encode to ADPCM from PCM data. +// 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 buffer that implements io.writer and io.bytewriter, ie. where the encoded ADPCM data is written to. -type Encoder struct { +type encoder struct { dest *bytes.Buffer pred int16 index int16 } -// Decoder is used to decode from ADPCM to PCM data. +// 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 buffer that implements io.writer and io.bytewriter, ie. where the decoded PCM data is written to. -type Decoder struct { +type decoder struct { dest *bytes.Buffer pred int16 index int16 step int16 } -// PcmBS is the size of the blocks that an Encoder uses. -// 'EncodeBlock' will encode PcmBS bytes at a time and the output will be AdpcmBS bytes long. +// PcmBS is the size of the blocks that an encoder uses. +// 'encodeBlock' will encode PcmBS bytes at a time and the output will be AdpcmBS bytes long. const PcmBS = 1010 -// AdpcmBS is the size of the blocks that a Decoder uses. -// 'DecodeBlock' will decode AdpcmBS bytes at a time and the output will be PcmBS bytes long. +// AdpcmBS is the size of the blocks that a decoder uses. +// 'decodeBlock' will decode AdpcmBS bytes at a time and the output will be PcmBS bytes long. const AdpcmBS = 256 // Table of index changes (see spec). @@ -88,16 +88,16 @@ var stepTable = []int16{ } // NewEncoder retuns a new ADPCM encoder. -func NewEncoder(dst *bytes.Buffer) *Encoder { - e := Encoder{ +func NewEncoder(dst *bytes.Buffer) *encoder { + e := encoder{ dest: dst, } return &e } // NewDecoder retuns a new ADPCM decoder. -func NewDecoder(dst *bytes.Buffer) *Decoder { - d := Decoder{ +func NewDecoder(dst *bytes.Buffer) *decoder { + d := decoder{ step: stepTable[0], dest: dst, } @@ -106,7 +106,7 @@ 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 { +func (e *encoder) encodeSample(sample int16) byte { // Find difference of actual sample from encoder's prediction. delta := sample - e.pred @@ -139,6 +139,7 @@ func (e *Encoder) encodeSample(sample int16) byte { } e.index += indexTable[nib&7] + // Check for underflow and overflow. if e.index < 0 { e.index = 0 @@ -151,7 +152,7 @@ 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. -func (d *Decoder) decodeSample(nibble byte) int16 { +func (d *decoder) decodeSample(nibble byte) int16 { // Calculate difference. var diff int16 if nibble&4 != 0 { @@ -191,7 +192,7 @@ func (d *Decoder) decodeSample(nibble byte) int16 { // calcHead sets the state for the encoder by running the first sample through // the encoder, and writing the first sample. -func (e *Encoder) calcHead(sample []byte) (int, error) { +func (e *encoder) calcHead(sample []byte) (int, error) { // Check that we are given 1 16-bit sample (2 bytes). const sampSize = 2 if len(sample) != sampSize { @@ -220,13 +221,13 @@ func (e *Encoder) calcHead(sample []byte) (int, error) { return writ, nil } -// EncodeBlock takes a slice of 1010 bytes (505 16-bit PCM samples). +// 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. -func (e *Encoder) EncodeBlock(block []byte) (int, error) { +func (e *encoder) encodeBlock(block []byte) (int, error) { writ := 0 if len(block) != PcmBS { return writ, fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), PcmBS) @@ -250,9 +251,9 @@ func (e *Encoder) EncodeBlock(block []byte) (int, error) { return writ, nil } -// DecodeBlock takes a slice of 256 bytes, each byte after the first 4 should contain two ADPCM encoded nibbles. +// 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) (int, error) { +func (d *decoder) decodeBlock(block []byte) (int, error) { writ := 0 if len(block) != AdpcmBS { return writ, fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), AdpcmBS) @@ -294,12 +295,15 @@ func (d *Decoder) DecodeBlock(block []byte) (int, error) { return writ, nil } -func (e *Encoder) Write(inPcm []byte) (int, error) { +// Write takes a slice of bytes of arbitrary length representing pcm and encodes in into adpcm. +// It writes its output to the encoder's dest. +// The number of bytes written out is returned along with any error that occured. +func (e *encoder) Write(inPcm []byte) (int, error) { numBlocks := len(inPcm) / PcmBS writ := 0 for i := 0; i < numBlocks; i++ { block := inPcm[PcmBS*i : PcmBS*(i+1)] - writB, err := e.EncodeBlock(block) + writB, err := e.encodeBlock(block) writ += writB if err != nil { return writ, err @@ -309,12 +313,15 @@ func (e *Encoder) Write(inPcm []byte) (int, error) { return writ, nil } -func (d *Decoder) Write(inAdpcm []byte) (int, error) { +// Write takes a slice of bytes of arbitrary length representing adpcm and decodes in into pcm. +// It writes its output to the decoder's dest. +// The number of bytes written out is returned along with any error that occured. +func (d *decoder) Write(inAdpcm []byte) (int, error) { numBlocks := len(inAdpcm) / AdpcmBS writ := 0 for i := 0; i < numBlocks; i++ { block := inAdpcm[AdpcmBS*i : AdpcmBS*(i+1)] - writB, err := d.DecodeBlock(block) + writB, err := d.decodeBlock(block) writ += writB if err != nil { return writ, err From 14a602f423878594ea492dc0fd2e1abfc76bf6bf Mon Sep 17 00:00:00 2001 From: Trek H Date: Fri, 15 Mar 2019 17:54:24 +1030 Subject: [PATCH 73/73] ADPCM: 'writ' variables changed to 'n' as per convention. Documentation improved. --- audio/adpcm/adpcm.go | 77 ++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/audio/adpcm/adpcm.go b/audio/adpcm/adpcm.go index ee6feaf6..595728a2 100644 --- a/audio/adpcm/adpcm.go +++ b/audio/adpcm/adpcm.go @@ -191,7 +191,8 @@ func (d *decoder) decodeSample(nibble byte) int16 { } // calcHead sets the state for the encoder by running the first sample through -// the encoder, and writing the first sample. +// the encoder, and writing the first sample to the encoder's io.Writer (dest). +// It returns the number of bytes written to the encoder's io.Writer (dest) along with any errors. func (e *encoder) calcHead(sample []byte) (int, error) { // Check that we are given 1 16-bit sample (2 bytes). const sampSize = 2 @@ -202,40 +203,40 @@ func (e *encoder) calcHead(sample []byte) (int, error) { intSample := int16(binary.LittleEndian.Uint16(sample)) e.encodeSample(intSample) - writ, err := e.dest.Write(sample) + n, err := e.dest.Write(sample) if err != nil { - return writ, err + return n, err } err = e.dest.WriteByte(byte(uint16(e.index))) if err != nil { - return writ, err + return n, err } - writ++ + n++ err = e.dest.WriteByte(byte(0x00)) if err != nil { - return writ, err + return n, err } - writ++ - return writ, nil + n++ + return n, nil } // 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. +// It writes encoded (compressed) bytes (each byte containing two ADPCM nibbles) to the encoder's io.Writer (dest). +// The number of bytes written is returned along with any errors. // 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) (int, error) { - writ := 0 if len(block) != PcmBS { - return writ, fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), PcmBS) + return 0, fmt.Errorf("unsupported block size. Given: %v, expected: %v, ie. 505 16-bit PCM samples", len(block), PcmBS) } - writ, err := e.calcHead(block[0:2]) + n, err := e.calcHead(block[0:2]) if err != nil { - return writ, err + return n, err } for i := 3; i < PcmBS; i += 4 { @@ -243,29 +244,29 @@ func (e *encoder) encodeBlock(block []byte) (int, error) { nib2 := e.encodeSample(int16(binary.LittleEndian.Uint16(block[i+1 : i+3]))) err = e.dest.WriteByte(byte((nib2 << 4) | nib1)) if err != nil { - return writ, err + return n, err } - writ++ + n++ } - return writ, nil + return n, nil } // decodeBlock takes a slice of 256 bytes, each byte after the first 4 should contain two ADPCM encoded nibbles. -// It outputs the resulting decoded (decompressed) 16-bit PCM samples to the decoder's dest writer. +// It writes the resulting decoded (decompressed) 16-bit PCM samples to the decoder's io.Writer (dest). +// The number of bytes written is returned along with any errors. func (d *decoder) decodeBlock(block []byte) (int, error) { - writ := 0 if len(block) != AdpcmBS { - return writ, fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), AdpcmBS) + return 0, fmt.Errorf("unsupported block size. Given: %v, expected: %v", len(block), AdpcmBS) } // 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] - writ, err := d.dest.Write(block[0:2]) + n, err := d.dest.Write(block[0:2]) if err != nil { - return writ, err + return n, err } // For each byte, seperate it into two nibbles (each nibble is a compressed sample), @@ -277,22 +278,22 @@ func (d *decoder) decodeBlock(block []byte) (int, error) { firstBytes := make([]byte, 2) binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(nib1))) - writS, err := d.dest.Write(firstBytes) - writ += writS + _n, err := d.dest.Write(firstBytes) + n += _n if err != nil { - return writ, err + return n, err } secondBytes := make([]byte, 2) binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(nib2))) - writS, err = d.dest.Write(secondBytes) - writ += writS + _n, err = d.dest.Write(secondBytes) + n += _n if err != nil { - return writ, err + return n, err } } - return writ, nil + return n, nil } // Write takes a slice of bytes of arbitrary length representing pcm and encodes in into adpcm. @@ -300,17 +301,17 @@ func (d *decoder) decodeBlock(block []byte) (int, error) { // The number of bytes written out is returned along with any error that occured. func (e *encoder) Write(inPcm []byte) (int, error) { numBlocks := len(inPcm) / PcmBS - writ := 0 + n := 0 for i := 0; i < numBlocks; i++ { block := inPcm[PcmBS*i : PcmBS*(i+1)] - writB, err := e.encodeBlock(block) - writ += writB + _n, err := e.encodeBlock(block) + n += _n if err != nil { - return writ, err + return n, err } } - return writ, nil + return n, nil } // Write takes a slice of bytes of arbitrary length representing adpcm and decodes in into pcm. @@ -318,15 +319,15 @@ func (e *encoder) Write(inPcm []byte) (int, error) { // The number of bytes written out is returned along with any error that occured. func (d *decoder) Write(inAdpcm []byte) (int, error) { numBlocks := len(inAdpcm) / AdpcmBS - writ := 0 + n := 0 for i := 0; i < numBlocks; i++ { block := inAdpcm[AdpcmBS*i : AdpcmBS*(i+1)] - writB, err := d.decodeBlock(block) - writ += writB + _n, err := d.decodeBlock(block) + n += _n if err != nil { - return writ, err + return n, err } } - return writ, nil + return n, nil }