Skip to content

Commit

Permalink
Unit test for stereo sound
Browse files Browse the repository at this point in the history
  • Loading branch information
hraban committed Oct 10, 2016
1 parent 0e63cf1 commit a2ed672
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 22 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ if err != nil {
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

Expand Down
16 changes: 13 additions & 3 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -69,6 +70,7 @@ func (enc *Encoder) Init(sample_rate int, channels int, application Application)
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(
Expand All @@ -94,10 +96,14 @@ func (enc *Encoder) Encode(pcm []int16, data []byte) (int, error) {
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 {
Expand All @@ -118,10 +124,14 @@ func (enc *Encoder) EncodeFloat32(pcm []float32, data []byte) (int, error) {
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 {
Expand Down
54 changes: 54 additions & 0 deletions opus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ func TestCodecFloat32(t *testing.T) {
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)
Expand All @@ -88,3 +90,55 @@ func TestCodecFloat32(t *testing.T) {
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])
}
*/
}
18 changes: 0 additions & 18 deletions stream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"fmt"
"io"
"io/ioutil"
"math"
"os"
"reflect"
"strings"
Expand Down Expand Up @@ -87,23 +86,6 @@ func extractWavPcm(t *testing.T, fname string) []int16 {
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) {
opuspcm := opus2pcm(t, "testdata/speech_8.opus", 10000)
wavpcm := extractWavPcm(t, "testdata/speech_8.wav")
Expand Down
44 changes: 43 additions & 1 deletion utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,48 @@ func addSineFloat32(buf []float32, sampleRate int, freq float64) {
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
}

0 comments on commit a2ed672

Please sign in to comment.