README.md | 8 ++++++++ encoder.go | 16 +++++++++++++--- opus_test.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++ stream_test.go | 18 ------------------ utils_test.go | 44 +++++++++++++++++++++++++++++++++++++++++++- diff --git a/README.md b/README.md index 7580a421388bb8e15a1190ed1112ead15efbb1f4..4f72eee997c75f2b425ff9a7fce2db716d214bf4 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,14 @@ } data = data[:n] // only the first N bytes are opus data. Just like io.Reader. ``` +Note that you must choose a target buffer size, and this buffer size will affect +the encoding process: + +> Size of the allocated memory for the output payload. This may be used to +> impose an upper limit on the instant bitrate, but should not be used as the +> only bitrate control. Use `OPUS_SET_BITRATE` to control the bitrate. + +- https://opus-codec.org/docs/opus_api-1.1.3/group__opus__encoder.html ### Decoding diff --git a/encoder.go b/encoder.go index bd50405633684c7ce30ff9e5d68561a50e9bbbda..5821d287f3cae77bbafb78c616aa523994ea234b 100644 --- a/encoder.go +++ b/encoder.go @@ -41,7 +41,8 @@ var errEncUninitialized = fmt.Errorf("opus encoder uninitialized") // Encoder contains the state of an Opus encoder for libopus. type Encoder struct { - p *C.struct_OpusEncoder + p *C.struct_OpusEncoder + channels int // Memory for the encoder struct allocated on the Go heap to allow Go GC to // manage it (and obviate need to free()) mem []byte @@ -69,6 +70,7 @@ if channels != 1 && channels != 2 { return fmt.Errorf("Number of channels must be 1 or 2: %d", channels) } size := C.opus_encoder_get_size(C.int(channels)) + enc.channels = channels enc.mem = make([]byte, size) enc.p = (*C.OpusEncoder)(unsafe.Pointer(&enc.mem[0])) errno := int(C.opus_encoder_init( @@ -94,10 +96,14 @@ } if len(data) == 0 { return 0, fmt.Errorf("opus: no target buffer") } + if len(pcm)%enc.channels != 0 { + return 0, fmt.Errorf("opus: input buffer length must be multiple of channels") + } + samples := len(pcm) / enc.channels n := int(C.opus_encode( enc.p, (*C.opus_int16)(&pcm[0]), - C.int(len(pcm)), + C.int(samples), (*C.uchar)(&data[0]), C.opus_int32(cap(data)))) if n < 0 { @@ -118,10 +124,14 @@ } if len(data) == 0 { return 0, fmt.Errorf("opus: no target buffer") } + if len(pcm)%enc.channels != 0 { + return 0, fmt.Errorf("opus: input buffer length must be multiple of channels") + } + samples := len(pcm) / enc.channels n := int(C.opus_encode_float( enc.p, (*C.float)(&pcm[0]), - C.int(len(pcm)), + C.int(samples), (*C.uchar)(&data[0]), C.opus_int32(cap(data)))) if n < 0 { diff --git a/opus_test.go b/opus_test.go index 09966fe12a163978d691eee4171607b6a089a0df..87fe1104eb54b5a614745466add6453a712d9924 100644 --- a/opus_test.go +++ b/opus_test.go @@ -80,6 +80,8 @@ dec, err := NewDecoder(SAMPLE_RATE, 1) if err != nil || dec == nil { t.Fatalf("Error creating new decoder: %v", err) } + // TODO: Uh-oh.. it looks like I forgot to put a data = data[:n] here, yet + // the test is not failing. Why? n, err = dec.DecodeFloat32(data, pcm) if err != nil { t.Fatalf("Couldn't decode data: %v", err) @@ -88,3 +90,55 @@ if len(pcm) != n { t.Fatalf("Length mismatch: %d samples in, %d out", len(pcm), n) } } + +func TestStereo(t *testing.T) { + // Create bogus input sound + const G4 = 391.995 + const E3 = 164.814 + const SAMPLE_RATE = 48000 + const FRAME_SIZE_MS = 60 + const CHANNELS = 2 + const FRAME_SIZE_MONO = SAMPLE_RATE * FRAME_SIZE_MS / 1000 + + enc, err := NewEncoder(SAMPLE_RATE, CHANNELS, APPLICATION_VOIP) + if err != nil || enc == nil { + t.Fatalf("Error creating new encoder: %v", err) + } + dec, err := NewDecoder(SAMPLE_RATE, CHANNELS) + if err != nil || dec == nil { + t.Fatalf("Error creating new decoder: %v", err) + } + + // Source signal (left G4, right E3) + left := make([]int16, FRAME_SIZE_MONO) + right := make([]int16, FRAME_SIZE_MONO) + addSine(left, SAMPLE_RATE, G4) + addSine(right, SAMPLE_RATE, E3) + pcm := interleave(left, right) + + data := make([]byte, 1000) + n, err := enc.Encode(pcm, data) + if err != nil { + t.Fatalf("Couldn't encode data: %v", err) + } + data = data[:n] + n, err = dec.Decode(data, pcm) + if err != nil { + t.Fatal("Couldn't decode data: %v", err) + } + if n*CHANNELS != len(pcm) { + t.Fatalf("Length mismatch: %d samples in, %d out", len(pcm), n*CHANNELS) + } + + // This is hard to check programatically, but I plotted the graphs in a + // spreadsheet and it looked great. The encoded waves both start out with a + // string of zero samples before they pick up, and the G4 is phase shifted + // by half a period, but that's all fine for lossy audio encoding. + /* + leftdec, rightdec := split(pcm) + fmt.Printf("left_in,left_out,right_in,right_out\n") + for i := range left { + fmt.Printf("%d,%d,%d,%d\n", left[i], leftdec[i], right[i], rightdec[i]) + } + */ +} diff --git a/stream_test.go b/stream_test.go index 95bebfb2646bd95ae40a16cb19cb2146873b0535..f9fdff29353b0c8379d3717ae50c15e1addcb2f2 100644 --- a/stream_test.go +++ b/stream_test.go @@ -8,7 +8,6 @@ import ( "fmt" "io" "io/ioutil" - "math" "os" "reflect" "strings" @@ -85,23 +84,6 @@ samples[i] += int16(bytes[wavHeaderSize+i*2]) samples[i] += int16(bytes[wavHeaderSize+i*2+1]) << 8 } return samples -} - -func maxDiff(a []int16, b []int16) int32 { - if len(a) != len(b) { - return math.MaxInt16 - } - var max int32 = 0 - for i := range a { - d := int32(a[i]) - int32(b[i]) - if d < 0 { - d = -d - } - if d > max { - max = d - } - } - return max } func TestStream(t *testing.T) { diff --git a/utils_test.go b/utils_test.go index 77985323e1141392422ca0d71c4f338a7edfe1be..dbd37ea28cee06e36e6c67275530e980f14b4995 100644 --- a/utils_test.go +++ b/utils_test.go @@ -20,6 +20,48 @@ func addSine(buf []int16, sampleRate int, freq float64) { factor := 2 * math.Pi * freq / float64(sampleRate) for i := range buf { - buf[i] += int16(math.Sin(float64(i)*factor) * math.MaxInt16) + buf[i] += int16(math.Sin(float64(i)*factor) * (math.MaxInt16 - 1)) + } +} + +func maxDiff(a []int16, b []int16) int32 { + if len(a) != len(b) { + return math.MaxInt16 } + var max int32 = 0 + for i := range a { + d := int32(a[i]) - int32(b[i]) + if d < 0 { + d = -d + } + if d > max { + max = d + } + } + return max +} + +func interleave(a []int16, b []int16) []int16 { + if len(a) != len(b) { + panic("interleave: buffers must have equal length") + } + result := make([]int16, 2*len(a)) + for i := range a { + result[2*i] = a[i] + result[2*i+1] = b[i] + } + return result +} + +func split(interleaved []int16) ([]int16, []int16) { + if len(interleaved)%2 != 0 { + panic("split: interleaved buffer must have even number of samples") + } + left := make([]int16, len(interleaved)/2) + right := make([]int16, len(interleaved)/2) + for i := range left { + left[i] = interleaved[2*i] + right[i] = interleaved[2*i+1] + } + return left, right }