From 98b72bca12b5f33f52511cf1d3a3b66ac2f5c24e Mon Sep 17 00:00:00 2001 From: Trek Date: Tue, 5 Feb 2019 16:36:03 +1030 Subject: [PATCH] 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 +}