From 6cc027f24dbad21e0e460550582ff34822875c08 Mon Sep 17 00:00:00 2001 From: WeebDataHoarder <57538841+WeebDataHoarder@users.noreply.github.com> Date: Sun, 27 Feb 2022 18:56:08 +0100 Subject: [PATCH] Use libopusenc to encode stream, WiP: tests --- callbacks.c | 109 +++++++++ decoder.go | 2 +- decoder_test.go | 36 --- encoder.go | 211 +++++++++-------- encoder_test.go | 42 ++-- opus_test.go | 589 +++--------------------------------------------- 6 files changed, 263 insertions(+), 726 deletions(-) diff --git a/callbacks.c b/callbacks.c index 0041ece..7906cc2 100644 --- a/callbacks.c +++ b/callbacks.c @@ -9,15 +9,28 @@ // plays nice with the CGo rules and avoids any confusion. #include +#include #include // Defined in Go. Uses the same signature as Go, no need for proxy function. int go_readcallback(void *p, unsigned char *buf, int nbytes); +int go_writecallback(void *p, const unsigned char *buf, opus_int32 len); + +int closecallback(void *p) { + return 0; +} static struct OpusFileCallbacks callbacks = { .read = go_readcallback, }; +static OpusEncCallbacks encodeCallbacks = { + .write = go_writecallback, + .close = closecallback, +}; + +static OggOpusComments* emptyComment = NULL; + // Proxy function for op_open_callbacks, because it takes a void * context but // we want to pass it non-pointer data, namely an arbitrary uintptr_t // value. This is legal C, but go test -race (-d=checkptr) complains anyway. So @@ -28,3 +41,99 @@ my_open_callbacks(uintptr_t p, int *error) { return op_open_callbacks((void *)p, &callbacks, NULL, 0, error); } + +OpusEncCallbacks * +my_write_callbacks() +{ + return &encodeCallbacks; +} + +OggOpusComments* +my_empty_comment() +{ + if (emptyComment == NULL) { + emptyComment = ope_comments_create(); + } + return emptyComment; +} + + + +int +bridge_encoder_set_dtx(OggOpusEnc *st, opus_int32 use_dtx) +{ + return ope_encoder_ctl(st, OPUS_SET_DTX(use_dtx)); +} + +int +bridge_encoder_get_dtx(OggOpusEnc *st, opus_int32 *dtx) +{ + return ope_encoder_ctl(st, OPUS_GET_DTX(dtx)); +} + +int +bridge_encoder_get_sample_rate(OggOpusEnc *st, opus_int32 *sample_rate) +{ + return ope_encoder_ctl(st, OPUS_GET_SAMPLE_RATE(sample_rate)); +} + + +int +bridge_encoder_set_bitrate(OggOpusEnc *st, opus_int32 bitrate) +{ + return ope_encoder_ctl(st, OPUS_SET_BITRATE(bitrate)); +} + +int +bridge_encoder_get_bitrate(OggOpusEnc *st, opus_int32 *bitrate) +{ + return ope_encoder_ctl(st, OPUS_GET_BITRATE(bitrate)); +} + +int +bridge_encoder_set_complexity(OggOpusEnc *st, opus_int32 complexity) +{ + return ope_encoder_ctl(st, OPUS_SET_COMPLEXITY(complexity)); +} + +int +bridge_encoder_get_complexity(OggOpusEnc *st, opus_int32 *complexity) +{ + return ope_encoder_ctl(st, OPUS_GET_COMPLEXITY(complexity)); +} + +int +bridge_encoder_set_max_bandwidth(OggOpusEnc *st, opus_int32 max_bw) +{ + return ope_encoder_ctl(st, OPUS_SET_MAX_BANDWIDTH(max_bw)); +} + +int +bridge_encoder_get_max_bandwidth(OggOpusEnc *st, opus_int32 *max_bw) +{ + return ope_encoder_ctl(st, OPUS_GET_MAX_BANDWIDTH(max_bw)); +} + +int +bridge_encoder_set_inband_fec(OggOpusEnc *st, opus_int32 fec) +{ + return ope_encoder_ctl(st, OPUS_SET_INBAND_FEC(fec)); +} + +int +bridge_encoder_get_inband_fec(OggOpusEnc *st, opus_int32 *fec) +{ + return ope_encoder_ctl(st, OPUS_GET_INBAND_FEC(fec)); +} + +int +bridge_encoder_set_packet_loss_perc(OggOpusEnc *st, opus_int32 loss_perc) +{ + return ope_encoder_ctl(st, OPUS_SET_PACKET_LOSS_PERC(loss_perc)); +} + +int +bridge_encoder_get_packet_loss_perc(OggOpusEnc *st, opus_int32 *loss_perc) +{ + return ope_encoder_ctl(st, OPUS_GET_PACKET_LOSS_PERC(loss_perc)); +} \ No newline at end of file diff --git a/decoder.go b/decoder.go index da6ac81..c12a3b2 100644 --- a/decoder.go +++ b/decoder.go @@ -92,7 +92,7 @@ func (dec *Decoder) Decode(data []byte, pcm []int16) (int, error) { return n, nil } -// Decode encoded Opus data into the supplied buffer. On success, returns the +// DecodeFloat32 encoded Opus data into the supplied buffer. On success, returns the // number of samples correctly written to the target buffer. func (dec *Decoder) DecodeFloat32(data []byte, pcm []float32) (int, error) { if dec.p == nil { diff --git a/decoder_test.go b/decoder_test.go index afb188f..1328ee0 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -30,39 +30,3 @@ func TestDecoderUnitialized(t *testing.T) { t.Errorf("Expected \"unitialized decoder\" error: %v", err) } } - -func TestDecoder_GetLastPacketDuration(t *testing.T) { - const G4 = 391.995 - const SAMPLE_RATE = 48000 - const FRAME_SIZE_MS = 60 - const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000 - pcm := make([]int16, FRAME_SIZE) - enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP) - if err != nil || enc == nil { - t.Fatalf("Error creating new encoder: %v", err) - } - addSine(pcm, SAMPLE_RATE, G4) - - data := make([]byte, 1000) - n, err := enc.Encode(pcm, data) - if err != nil { - t.Fatalf("Couldn't encode data: %v", err) - } - data = data[:n] - - dec, err := NewDecoder(SAMPLE_RATE, 1) - if err != nil || dec == nil { - t.Fatalf("Error creating new decoder: %v", err) - } - n, err = dec.Decode(data, pcm) - if err != nil { - t.Fatalf("Couldn't decode data: %v", err) - } - samples, err := dec.LastPacketDuration() - if err != nil { - t.Fatalf("Couldn't get last packet duration: %v", err) - } - if samples != n { - t.Fatalf("Wrong duration length. Expected %d. Got %d", n, samples) - } -} diff --git a/encoder.go b/encoder.go index 0939168..e7331b5 100644 --- a/encoder.go +++ b/encoder.go @@ -6,91 +6,60 @@ package opus import ( "fmt" + "io" + "runtime/cgo" "unsafe" ) /* -#cgo pkg-config: opus +#cgo pkg-config: opus libopusenc #include +#include int -bridge_encoder_set_dtx(OpusEncoder *st, opus_int32 use_dtx) -{ - return opus_encoder_ctl(st, OPUS_SET_DTX(use_dtx)); -} +bridge_encoder_set_dtx(OggOpusEnc *st, opus_int32 use_dtx); int -bridge_encoder_get_dtx(OpusEncoder *st, opus_int32 *dtx) -{ - return opus_encoder_ctl(st, OPUS_GET_DTX(dtx)); -} +bridge_encoder_get_dtx(OggOpusEnc *st, opus_int32 *dtx); int -bridge_encoder_get_sample_rate(OpusEncoder *st, opus_int32 *sample_rate) -{ - return opus_encoder_ctl(st, OPUS_GET_SAMPLE_RATE(sample_rate)); -} +bridge_encoder_get_sample_rate(OggOpusEnc *st, opus_int32 *sample_rate); int -bridge_encoder_set_bitrate(OpusEncoder *st, opus_int32 bitrate) -{ - return opus_encoder_ctl(st, OPUS_SET_BITRATE(bitrate)); -} +bridge_encoder_set_bitrate(OggOpusEnc *st, opus_int32 bitrate); int -bridge_encoder_get_bitrate(OpusEncoder *st, opus_int32 *bitrate) -{ - return opus_encoder_ctl(st, OPUS_GET_BITRATE(bitrate)); -} +bridge_encoder_get_bitrate(OggOpusEnc *st, opus_int32 *bitrate); int -bridge_encoder_set_complexity(OpusEncoder *st, opus_int32 complexity) -{ - return opus_encoder_ctl(st, OPUS_SET_COMPLEXITY(complexity)); -} +bridge_encoder_set_complexity(OggOpusEnc *st, opus_int32 complexity); int -bridge_encoder_get_complexity(OpusEncoder *st, opus_int32 *complexity) -{ - return opus_encoder_ctl(st, OPUS_GET_COMPLEXITY(complexity)); -} +bridge_encoder_get_complexity(OggOpusEnc *st, opus_int32 *complexity); int -bridge_encoder_set_max_bandwidth(OpusEncoder *st, opus_int32 max_bw) -{ - return opus_encoder_ctl(st, OPUS_SET_MAX_BANDWIDTH(max_bw)); -} +bridge_encoder_set_max_bandwidth(OggOpusEnc *st, opus_int32 max_bw); int -bridge_encoder_get_max_bandwidth(OpusEncoder *st, opus_int32 *max_bw) -{ - return opus_encoder_ctl(st, OPUS_GET_MAX_BANDWIDTH(max_bw)); -} +bridge_encoder_get_max_bandwidth(OggOpusEnc *st, opus_int32 *max_bw); int -bridge_encoder_set_inband_fec(OpusEncoder *st, opus_int32 fec) -{ - return opus_encoder_ctl(st, OPUS_SET_INBAND_FEC(fec)); -} +bridge_encoder_set_inband_fec(OggOpusEnc *st, opus_int32 fec); int -bridge_encoder_get_inband_fec(OpusEncoder *st, opus_int32 *fec) -{ - return opus_encoder_ctl(st, OPUS_GET_INBAND_FEC(fec)); -} +bridge_encoder_get_inband_fec(OggOpusEnc *st, opus_int32 *fec); int -bridge_encoder_set_packet_loss_perc(OpusEncoder *st, opus_int32 loss_perc) -{ - return opus_encoder_ctl(st, OPUS_SET_PACKET_LOSS_PERC(loss_perc)); -} +bridge_encoder_set_packet_loss_perc(OggOpusEnc *st, opus_int32 loss_perc); int -bridge_encoder_get_packet_loss_perc(OpusEncoder *st, opus_int32 *loss_perc) -{ - return opus_encoder_ctl(st, OPUS_GET_PACKET_LOSS_PERC(loss_perc)); -} +bridge_encoder_get_packet_loss_perc(OggOpusEnc *st, opus_int32 *loss_perc); + + +OpusEncCallbacks * my_write_callbacks(); + +OggOpusComments* my_empty_comment(); */ import "C" @@ -114,18 +83,32 @@ 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_OggOpusEnc 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 + handle cgo.Handle + writer io.Writer } -// NewEncoder allocates a new Opus encoder and initializes it with the -// appropriate parameters. All related memory is managed by the Go GC. -func NewEncoder(sample_rate int, channels int, application Application) (*Encoder, error) { +//export go_writecallback +func go_writecallback(p unsafe.Pointer, cbuf *C.uchar, length C.int) C.int { + enc := ((*cgo.Handle)(p)).Value().(*Encoder) + if enc == nil { + // This is bad + return 1 + } + + _, err := enc.writer.Write(unsafe.Slice((*byte)(cbuf), int(length))) + + if err != nil { + return 1 + } + return 0 +} + +// NewEncoder allocates a new Opus encoder and initializes it with the appropriate parameters. +func NewEncoder(sampleRate int, channels int, application Application, writer io.Writer) (*Encoder, error) { var enc Encoder - err := enc.Init(sample_rate, channels, application) + err := enc.Init(sampleRate, channels, application, writer) if err != nil { return nil, err } @@ -135,84 +118,94 @@ func NewEncoder(sample_rate int, channels int, application Application) (*Encode // Init initializes a pre-allocated opus encoder. Unless the encoder has been // created using NewEncoder, this method must be called exactly once in the // life-time of this object, before calling any other methods. -func (enc *Encoder) Init(sample_rate int, channels int, application Application) error { +func (enc *Encoder) Init(sampleRate int, channels int, application Application, writer io.Writer) error { if enc.p != nil { return fmt.Errorf("opus encoder already initialized") } if channels != 1 && channels != 2 { - return fmt.Errorf("Number of channels must be 1 or 2: %d", channels) + 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( - enc.p, - C.opus_int32(sample_rate), - C.int(channels), - C.int(application))) + enc.writer = writer + enc.handle = cgo.NewHandle(enc) + + var errno C.int + if channels > 2 { + enc.p = C.ope_encoder_create_callbacks(C.my_write_callbacks(), unsafe.Pointer(&enc.handle), C.my_empty_comment(), C.opus_int32(sampleRate), C.int(channels), C.int(1), &errno) + } else { + enc.p = C.ope_encoder_create_callbacks(C.my_write_callbacks(), unsafe.Pointer(&enc.handle), C.my_empty_comment(), C.opus_int32(sampleRate), C.int(channels), C.int(0), &errno) + } + if errno != 0 { return Error(int(errno)) } return nil } -// Encode raw PCM data and store the result in the supplied buffer. On success, -// returns the number of bytes used up by the encoded data. -func (enc *Encoder) Encode(pcm []int16, data []byte) (int, error) { +// Encode raw PCM data +func (enc *Encoder) Encode(pcm []int16) error { if enc.p == nil { - return 0, errEncUninitialized + return errEncUninitialized } if len(pcm) == 0 { - return 0, fmt.Errorf("opus: no data supplied") + return fmt.Errorf("opus: no data supplied") } - if len(data) == 0 { - return 0, fmt.Errorf("opus: no target buffer") - } - // libopus talks about samples as 1 sample containing multiple channels. So - // e.g. 20 samples of 2-channel data is actually 40 raw data points. + if len(pcm)%enc.channels != 0 { - return 0, fmt.Errorf("opus: input buffer length must be multiple of channels") + return 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(samples), - (*C.uchar)(&data[0]), - C.opus_int32(cap(data)))) - if n < 0 { - return 0, Error(n) + + n := int(C.ope_encoder_write(enc.p, (*C.opus_int16)(&pcm[0]), C.int(samples))) + if n != 0 { + return Error(n) } - return n, nil + return nil } -// Encode raw PCM data and store the result in the supplied buffer. On success, -// returns the number of bytes used up by the encoded data. -func (enc *Encoder) EncodeFloat32(pcm []float32, data []byte) (int, error) { +// FlushHeaders Write out the header now rather than wait for audio to begin. +func (enc *Encoder) FlushHeaders() error { + res := C.ope_encoder_flush_header(enc.p) + if res != C.OPUS_OK { + return Error(res) + } + return nil +} + +// Close finishes stream, then frees allocated resources +func (enc *Encoder) Close() error { + if enc.p != nil { + res := C.ope_encoder_drain(enc.p) + if res != C.OPUS_OK { + return Error(res) + } + C.ope_encoder_destroy(enc.p) + enc.p = nil + enc.handle.Delete() + } + return nil +} + +// EncodeFloat32 raw PCM data +func (enc *Encoder) EncodeFloat32(pcm []float32) error { if enc.p == nil { - return 0, errEncUninitialized + return errEncUninitialized } if len(pcm) == 0 { - return 0, fmt.Errorf("opus: no data supplied") - } - if len(data) == 0 { - return 0, fmt.Errorf("opus: no target buffer") + return fmt.Errorf("opus: no data supplied") } + if len(pcm)%enc.channels != 0 { - return 0, fmt.Errorf("opus: input buffer length must be multiple of channels") + return 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(samples), - (*C.uchar)(&data[0]), - C.opus_int32(cap(data)))) - if n < 0 { - return 0, Error(n) + + n := int(C.ope_encoder_write_float(enc.p, (*C.float)(&pcm[0]), C.int(samples))) + if n != 0 { + return Error(n) } - return n, nil + return nil } // SetDTX configures the encoder's use of discontinuous transmission (DTX). diff --git a/encoder_test.go b/encoder_test.go index 68f7af9..5500ec3 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -4,14 +4,17 @@ package opus -import "testing" +import ( + "bytes" + "testing" +) func TestEncoderNew(t *testing.T) { - enc, err := NewEncoder(48000, 1, AppVoIP) + enc, err := NewEncoder(48000, 1, AppVoIP, new(bytes.Buffer)) if err != nil || enc == nil { t.Errorf("Error creating new encoder: %v", err) } - enc, err = NewEncoder(12345, 1, AppVoIP) + enc, err = NewEncoder(12345, 1, AppVoIP, new(bytes.Buffer)) if err == nil || enc != nil { t.Errorf("Expected error for illegal samplerate 12345") } @@ -19,18 +22,18 @@ func TestEncoderNew(t *testing.T) { func TestEncoderUnitialized(t *testing.T) { var enc Encoder - _, err := enc.Encode(nil, nil) + err := enc.Encode(nil) if err != errEncUninitialized { t.Errorf("Expected \"unitialized encoder\" error: %v", err) } - _, err = enc.EncodeFloat32(nil, nil) + err = enc.EncodeFloat32(nil) if err != errEncUninitialized { t.Errorf("Expected \"unitialized encoder\" error: %v", err) } } func TestEncoderDTX(t *testing.T) { - enc, err := NewEncoder(8000, 1, AppVoIP) + enc, err := NewEncoder(8000, 1, AppVoIP, new(bytes.Buffer)) if err != nil || enc == nil { t.Errorf("Error creating new encoder: %v", err) } @@ -51,9 +54,9 @@ func TestEncoderDTX(t *testing.T) { } func TestEncoderSampleRate(t *testing.T) { - sample_rates := []int{8000, 12000, 16000, 24000, 48000} - for _, f := range sample_rates { - enc, err := NewEncoder(f, 1, AppVoIP) + sampleRates := []int{8000, 12000, 16000, 24000, 48000} + for _, f := range sampleRates { + enc, err := NewEncoder(f, 1, AppVoIP, new(bytes.Buffer)) if err != nil || enc == nil { t.Fatalf("Error creating new encoder with sample_rate %d Hz: %v", f, err) } @@ -68,7 +71,7 @@ func TestEncoderSampleRate(t *testing.T) { } func TestEncoder_SetGetBitrate(t *testing.T) { - enc, err := NewEncoder(8000, 1, AppVoIP) + enc, err := NewEncoder(8000, 1, AppVoIP, new(bytes.Buffer)) if err != nil || enc == nil { t.Errorf("Error creating new encoder: %v", err) } @@ -78,6 +81,7 @@ func TestEncoder_SetGetBitrate(t *testing.T) { if err != nil { t.Error("Error set bitrate:", err) } + enc.FlushHeaders() br, err := enc.Bitrate() if err != nil { t.Error("Error getting bitrate", err) @@ -89,7 +93,7 @@ func TestEncoder_SetGetBitrate(t *testing.T) { } func TestEncoder_SetBitrateToAuto(t *testing.T) { - enc, err := NewEncoder(8000, 1, AppVoIP) + enc, err := NewEncoder(8000, 1, AppVoIP, new(bytes.Buffer)) if err != nil || enc == nil { t.Errorf("Error creating new encoder: %v", err) } @@ -125,7 +129,7 @@ func TestEncoder_SetBitrateToAuto(t *testing.T) { } func TestEncoder_SetBitrateToMax(t *testing.T) { - enc, err := NewEncoder(8000, 1, AppVoIP) + enc, err := NewEncoder(8000, 1, AppVoIP, new(bytes.Buffer)) if err != nil || enc == nil { t.Errorf("Error creating new encoder: %v", err) } @@ -161,7 +165,7 @@ func TestEncoder_SetBitrateToMax(t *testing.T) { } func TestEncoder_SetGetInvalidBitrate(t *testing.T) { - enc, err := NewEncoder(8000, 1, AppVoIP) + enc, err := NewEncoder(8000, 1, AppVoIP, new(bytes.Buffer)) if err != nil || enc == nil { t.Errorf("Error creating new encoder: %v", err) } @@ -183,7 +187,7 @@ func TestEncoder_SetGetInvalidBitrate(t *testing.T) { } func TestEncoder_SetGetComplexity(t *testing.T) { - enc, err := NewEncoder(8000, 1, AppVoIP) + enc, err := NewEncoder(8000, 1, AppVoIP, new(bytes.Buffer)) if err != nil || enc == nil { t.Errorf("Error creating new encoder: %v", err) } @@ -205,7 +209,7 @@ func TestEncoder_SetGetComplexity(t *testing.T) { } func TestEncoder_SetGetInvalidComplexity(t *testing.T) { - enc, err := NewEncoder(8000, 1, AppVoIP) + enc, err := NewEncoder(8000, 1, AppVoIP, new(bytes.Buffer)) if err != nil || enc == nil { t.Errorf("Error creating new encoder: %v", err) } @@ -233,7 +237,7 @@ func TestEncoder_SetGetInvalidComplexity(t *testing.T) { } func TestEncoder_SetGetMaxBandwidth(t *testing.T) { - enc, err := NewEncoder(8000, 1, AppVoIP) + enc, err := NewEncoder(8000, 1, AppVoIP, new(bytes.Buffer)) if err != nil || enc == nil { t.Errorf("Error creating new encoder: %v", err) } @@ -261,7 +265,7 @@ func TestEncoder_SetGetMaxBandwidth(t *testing.T) { } func TestEncoder_SetGetInBandFEC(t *testing.T) { - enc, err := NewEncoder(8000, 1, AppVoIP) + enc, err := NewEncoder(8000, 1, AppVoIP, new(bytes.Buffer)) if err != nil || enc == nil { t.Errorf("Error creating new encoder: %v", err) } @@ -292,7 +296,7 @@ func TestEncoder_SetGetInBandFEC(t *testing.T) { } func TestEncoder_SetGetPacketLossPerc(t *testing.T) { - enc, err := NewEncoder(8000, 1, AppVoIP) + enc, err := NewEncoder(8000, 1, AppVoIP, new(bytes.Buffer)) if err != nil || enc == nil { t.Errorf("Error creating new encoder: %v", err) } @@ -314,7 +318,7 @@ func TestEncoder_SetGetPacketLossPerc(t *testing.T) { } func TestEncoder_SetGetInvalidPacketLossPerc(t *testing.T) { - enc, err := NewEncoder(8000, 1, AppVoIP) + enc, err := NewEncoder(8000, 1, AppVoIP, new(bytes.Buffer)) if err != nil || enc == nil { t.Errorf("Error creating new encoder: %v", err) } diff --git a/opus_test.go b/opus_test.go index 82e30f0..177dde8 100644 --- a/opus_test.go +++ b/opus_test.go @@ -5,6 +5,8 @@ package opus import ( + "bytes" + "io" "strings" "testing" ) @@ -32,27 +34,38 @@ func TestCodec(t *testing.T) { const FRAME_SIZE_MS = 60 const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000 pcm := make([]int16, FRAME_SIZE) - enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP) + + data := new(bytes.Buffer) + enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP, data) if err != nil || enc == nil { t.Fatalf("Error creating new encoder: %v", err) } addSine(pcm, SAMPLE_RATE, G4) - data := make([]byte, 1000) - n, err := enc.Encode(pcm, data) + + err = enc.Encode(pcm) if err != nil { t.Fatalf("Couldn't encode data: %v", err) } - data = data[:n] - dec, err := NewDecoder(SAMPLE_RATE, 1) + enc.Close() + + dec, err := NewStream(bytes.NewReader(data.Bytes())) if err != nil || dec == nil { t.Fatalf("Error creating new decoder: %v", err) } - n, err = dec.Decode(data, pcm) - if err != nil { - t.Fatalf("Couldn't decode data: %v", err) + + toRead := len(pcm) + for toRead != 0 { + n, err := dec.Read(pcm) + if err == io.EOF { + break + } else if err != nil { + t.Fatalf("Couldn't decode data: %v", err) + } + toRead -= n } - if len(pcm) != n { - t.Fatalf("Length mismatch: %d samples in, %d out", len(pcm), n) + + if toRead != 0 { + t.Fatalf("Length mismatch: %d samples in, %d out", len(pcm), len(pcm)-toRead) } // Checking the output programmatically is seriously not easy. I checked it // by hand and by ear, it looks fine. I'll just assume the API faithfully @@ -66,23 +79,23 @@ func TestCodecFloat32(t *testing.T) { const FRAME_SIZE_MS = 60 const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000 pcm := make([]float32, FRAME_SIZE) - enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP) + data := new(bytes.Buffer) + enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP, data) if err != nil || enc == nil { t.Fatalf("Error creating new encoder: %v", err) } addSineFloat32(pcm, SAMPLE_RATE, G4) - data := make([]byte, 1000) - n, err := enc.EncodeFloat32(pcm, data) + err = enc.EncodeFloat32(pcm) if err != nil { t.Fatalf("Couldn't encode data: %v", err) } - dec, err := NewDecoder(SAMPLE_RATE, 1) + dec, err := NewStream(bytes.NewReader(data.Bytes())) 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) + n, err := dec.ReadFloat32(pcm) if err != nil { t.Fatalf("Couldn't decode data: %v", err) } @@ -90,549 +103,3 @@ func TestCodecFloat32(t *testing.T) { t.Fatalf("Length mismatch: %d samples in, %d out", len(pcm), n) } } - -func TestCodecFEC(t *testing.T) { - // Create bogus input sound - const G4 = 391.995 - const SAMPLE_RATE = 48000 - const FRAME_SIZE_MS = 10 - const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000 - const NUMBER_OF_FRAMES = 6 - pcm := make([]int16, 0) - - enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP) - if err != nil || enc == nil { - t.Fatalf("Error creating new encoder: %v", err) - } - enc.SetPacketLossPerc(30) - enc.SetInBandFEC(true) - - dec, err := NewDecoder(SAMPLE_RATE, 1) - if err != nil || dec == nil { - t.Fatalf("Error creating new decoder: %v", err) - } - - mono := make([]int16, FRAME_SIZE*NUMBER_OF_FRAMES) - addSine(mono, SAMPLE_RATE, G4) - - encodedData := make([][]byte, NUMBER_OF_FRAMES) - for i, idx := 0, 0; i < len(mono); i += FRAME_SIZE { - data := make([]byte, 1000) - n, err := enc.Encode(mono[i:i+FRAME_SIZE], data) - if err != nil { - t.Fatalf("Couldn't encode data: %v", err) - } - data = data[:n] - encodedData[idx] = data - idx++ - } - - lost := false - for i := 0; i < len(encodedData); i++ { - // "Dropping" packets 2 and 4 - if i == 2 || i == 4 { - lost = true - continue - } - if lost { - samples, err := dec.LastPacketDuration() - if err != nil { - t.Fatalf("Couldn't get last packet duration: %v", err) - } - - pcmBuffer := make([]int16, samples) - if err = dec.DecodeFEC(encodedData[i], pcmBuffer); err != nil { - t.Fatalf("Couldn't decode data: %v", err) - } - pcm = append(pcm, pcmBuffer...) - lost = false - } - - pcmBuffer := make([]int16, NUMBER_OF_FRAMES*FRAME_SIZE) - n, err := dec.Decode(encodedData[i], pcmBuffer) - if err != nil { - t.Fatalf("Couldn't decode data: %v", err) - } - pcmBuffer = pcmBuffer[:n] - if n != FRAME_SIZE { - t.Fatalf("Length mismatch: %d samples expected, %d out", FRAME_SIZE, n) - } - pcm = append(pcm, pcmBuffer...) - } - - if len(mono) != len(pcm) { - t.Fatalf("Input/Output length mismatch: %d samples in, %d out", len(mono), len(pcm)) - } - - // Commented out for the same reason as in TestStereo - /* - fmt.Printf("in,out\n") - for i := range mono { - fmt.Printf("%d,%d\n", mono[i], pcm[i]) - } - */ - -} - -func TestCodecFECFloat32(t *testing.T) { - // Create bogus input sound - const G4 = 391.995 - const SAMPLE_RATE = 48000 - const FRAME_SIZE_MS = 10 - const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000 - const NUMBER_OF_FRAMES = 6 - pcm := make([]float32, 0) - - enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP) - if err != nil || enc == nil { - t.Fatalf("Error creating new encoder: %v", err) - } - enc.SetPacketLossPerc(30) - enc.SetInBandFEC(true) - - dec, err := NewDecoder(SAMPLE_RATE, 1) - if err != nil || dec == nil { - t.Fatalf("Error creating new decoder: %v", err) - } - - mono := make([]float32, FRAME_SIZE*NUMBER_OF_FRAMES) - addSineFloat32(mono, SAMPLE_RATE, G4) - - encodedData := make([][]byte, NUMBER_OF_FRAMES) - for i, idx := 0, 0; i < len(mono); i += FRAME_SIZE { - data := make([]byte, 1000) - n, err := enc.EncodeFloat32(mono[i:i+FRAME_SIZE], data) - if err != nil { - t.Fatalf("Couldn't encode data: %v", err) - } - data = data[:n] - encodedData[idx] = data - idx++ - } - - lost := false - for i := 0; i < len(encodedData); i++ { - // "Dropping" packets 2 and 4 - if i == 2 || i == 4 { - lost = true - continue - } - if lost { - samples, err := dec.LastPacketDuration() - if err != nil { - t.Fatalf("Couldn't get last packet duration: %v", err) - } - - pcmBuffer := make([]float32, samples) - if err = dec.DecodeFECFloat32(encodedData[i], pcmBuffer); err != nil { - t.Fatalf("Couldn't decode data: %v", err) - } - pcm = append(pcm, pcmBuffer...) - lost = false - } - - pcmBuffer := make([]float32, NUMBER_OF_FRAMES*FRAME_SIZE) - n, err := dec.DecodeFloat32(encodedData[i], pcmBuffer) - if err != nil { - t.Fatalf("Couldn't decode data: %v", err) - } - pcmBuffer = pcmBuffer[:n] - if n != FRAME_SIZE { - t.Fatalf("Length mismatch: %d samples expected, %d out", FRAME_SIZE, n) - } - pcm = append(pcm, pcmBuffer...) - } - - if len(mono) != len(pcm) { - t.Fatalf("Input/Output length mismatch: %d samples in, %d out", len(mono), len(pcm)) - } - - // Commented out for the same reason as in TestStereo - /* - fmt.Printf("in,out\n") - for i := 0; i < len(mono); i++ { - fmt.Printf("%f,%f\n", mono[i], pcm[i]) - } - */ -} - -func TestCodecPLC(t *testing.T) { - // Create bogus input sound - const G4 = 391.995 - const SAMPLE_RATE = 48000 - const FRAME_SIZE_MS = 10 - const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000 - const NUMBER_OF_FRAMES = 6 - pcm := make([]int16, 0) - - enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP) - if err != nil || enc == nil { - t.Fatalf("Error creating new encoder: %v", err) - } - - dec, err := NewDecoder(SAMPLE_RATE, 1) - if err != nil || dec == nil { - t.Fatalf("Error creating new decoder: %v", err) - } - - mono := make([]int16, FRAME_SIZE*NUMBER_OF_FRAMES) - addSine(mono, SAMPLE_RATE, G4) - - encodedData := make([][]byte, NUMBER_OF_FRAMES) - for i, idx := 0, 0; i < len(mono); i += FRAME_SIZE { - data := make([]byte, 1000) - n, err := enc.Encode(mono[i:i+FRAME_SIZE], data) - if err != nil { - t.Fatalf("Couldn't encode data: %v", err) - } - data = data[:n] - encodedData[idx] = data - idx++ - } - - lost := false - for i := 0; i < len(encodedData); i++ { - // "Dropping" packets 2 and 4 - if i == 2 || i == 4 { - lost = true - continue - } - if lost { - samples, err := dec.LastPacketDuration() - if err != nil { - t.Fatalf("Couldn't get last packet duration: %v", err) - } - - pcmBuffer := make([]int16, samples) - if err = dec.DecodePLC(pcmBuffer); err != nil { - t.Fatalf("Couldn't decode data: %v", err) - } - nonZero := 0 - for n := range pcmBuffer { - if pcmBuffer[n] != 0 { - nonZero++ - } - } - if nonZero == 0 { - t.Fatalf("DecodePLC produced only zero samples") - } - pcm = append(pcm, pcmBuffer...) - lost = false - } - - pcmBuffer := make([]int16, NUMBER_OF_FRAMES*FRAME_SIZE) - n, err := dec.Decode(encodedData[i], pcmBuffer) - if err != nil { - t.Fatalf("Couldn't decode data: %v", err) - } - pcmBuffer = pcmBuffer[:n] - if n != FRAME_SIZE { - t.Fatalf("Length mismatch: %d samples expected, %d out", FRAME_SIZE, n) - } - pcm = append(pcm, pcmBuffer...) - } - - if len(mono) != len(pcm) { - t.Fatalf("Input/Output length mismatch: %d samples in, %d out", len(mono), len(pcm)) - } - - // Commented out for the same reason as in TestStereo - /* - fmt.Printf("in,out\n") - for i := range mono { - fmt.Printf("%d,%d\n", mono[i], pcm[i]) - } - */ - -} - -func TestCodecPLCFloat32(t *testing.T) { - // Create bogus input sound - const G4 = 391.995 - const SAMPLE_RATE = 48000 - const FRAME_SIZE_MS = 10 - const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000 - const NUMBER_OF_FRAMES = 6 - pcm := make([]float32, 0) - - enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP) - if err != nil || enc == nil { - t.Fatalf("Error creating new encoder: %v", err) - } - - dec, err := NewDecoder(SAMPLE_RATE, 1) - if err != nil || dec == nil { - t.Fatalf("Error creating new decoder: %v", err) - } - - mono := make([]float32, FRAME_SIZE*NUMBER_OF_FRAMES) - addSineFloat32(mono, SAMPLE_RATE, G4) - - encodedData := make([][]byte, NUMBER_OF_FRAMES) - for i, idx := 0, 0; i < len(mono); i += FRAME_SIZE { - data := make([]byte, 1000) - n, err := enc.EncodeFloat32(mono[i:i+FRAME_SIZE], data) - if err != nil { - t.Fatalf("Couldn't encode data: %v", err) - } - data = data[:n] - encodedData[idx] = data - idx++ - } - - lost := false - for i := 0; i < len(encodedData); i++ { - // "Dropping" packets 2 and 4 - if i == 2 || i == 4 { - lost = true - continue - } - if lost { - samples, err := dec.LastPacketDuration() - if err != nil { - t.Fatalf("Couldn't get last packet duration: %v", err) - } - - pcmBuffer := make([]float32, samples) - if err = dec.DecodePLCFloat32(pcmBuffer); err != nil { - t.Fatalf("Couldn't decode data: %v", err) - } - nonZero := 0 - for n := range pcmBuffer { - if pcmBuffer[n] != 0.0 { - nonZero++ - } - } - if nonZero == 0 { - t.Fatalf("DecodePLC produced only zero samples") - } - pcm = append(pcm, pcmBuffer...) - lost = false - } - - pcmBuffer := make([]float32, NUMBER_OF_FRAMES*FRAME_SIZE) - n, err := dec.DecodeFloat32(encodedData[i], pcmBuffer) - if err != nil { - t.Fatalf("Couldn't decode data: %v", err) - } - pcmBuffer = pcmBuffer[:n] - if n != FRAME_SIZE { - t.Fatalf("Length mismatch: %d samples expected, %d out", FRAME_SIZE, n) - } - pcm = append(pcm, pcmBuffer...) - } - - if len(mono) != len(pcm) { - t.Fatalf("Input/Output length mismatch: %d samples in, %d out", len(mono), len(pcm)) - } - - // Commented out for the same reason as in TestStereo - /* - fmt.Printf("in,out\n") - for i := 0; i < len(mono); i++ { - fmt.Printf("%f,%f\n", mono[i], pcm[i]) - } - */ -} - -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, AppVoIP) - 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.Fatalf("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]) - } - */ -} - -func TestBufferSize(t *testing.T) { - const G4 = 391.995 - const SAMPLE_RATE = 48000 - const FRAME_SIZE_MS = 60 - const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000 - const GUARD_SIZE = 100 - - checkGuardInt16 := func(t *testing.T, s []int16) { - for n := range s { - if s[n] != 0 { - t.Fatal("Memory corruption detected") - } - } - } - - checkGuardFloat32 := func(t *testing.T, s []float32) { - for n := range s { - if s[n] != 0 { - t.Fatal("Memory corruption detected") - } - } - } - - checkResult := func(t *testing.T, n int, err error, expectOK bool) { - if expectOK { - if err != nil { - t.Fatalf("Couldn't decode data: %v", err) - } - if n != FRAME_SIZE { - t.Fatalf("Length mismatch: %d samples in, %d out", FRAME_SIZE, n) - } - } else { - if err == nil { - t.Fatalf("Expected decode failure, but it succeeded") - } - } - } - - encodeFrame := func(t *testing.T) []byte { - pcm := make([]int16, FRAME_SIZE) - enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP) - if err != nil || enc == nil { - t.Fatalf("Error creating new encoder: %v", err) - } - addSine(pcm, SAMPLE_RATE, G4) - data := make([]byte, 1000) - n, err := enc.Encode(pcm, data) - if err != nil { - t.Fatalf("Couldn't encode data: %v", err) - } - return data[:n] - } - - createDecoder := func(t *testing.T) *Decoder { - dec, err := NewDecoder(SAMPLE_RATE, 1) - if err != nil || dec == nil { - t.Fatalf("Error creating new decoder: %v", err) - } - return dec - } - - decodeInt16 := func(t *testing.T, data []byte, decodeSize int, expectOK bool) { - dec := createDecoder(t) - decodedMem := make([]int16, decodeSize+GUARD_SIZE*2) - decodedRef := decodedMem[GUARD_SIZE : GUARD_SIZE+decodeSize : GUARD_SIZE+decodeSize] - n, err := dec.Decode(data, decodedRef) - checkGuardInt16(t, decodedMem[:GUARD_SIZE]) - checkGuardInt16(t, decodedMem[decodeSize+GUARD_SIZE:]) - checkResult(t, n, err, expectOK) - } - - decodeFloat32 := func(t *testing.T, data []byte, decodeSize int, expectOK bool) { - dec := createDecoder(t) - decodedMem := make([]float32, decodeSize+GUARD_SIZE*2) - decodedRef := decodedMem[GUARD_SIZE : GUARD_SIZE+decodeSize : GUARD_SIZE+decodeSize] - n, err := dec.DecodeFloat32(data, decodedRef) - checkGuardFloat32(t, decodedMem[:GUARD_SIZE]) - checkGuardFloat32(t, decodedMem[decodeSize+GUARD_SIZE:]) - checkResult(t, n, err, expectOK) - } - - decodeFecInt16 := func(t *testing.T, data []byte, decodeSize int, expectOK bool) { - dec := createDecoder(t) - decodedMem := make([]int16, decodeSize+GUARD_SIZE*2) - decodedRef := decodedMem[GUARD_SIZE : GUARD_SIZE+decodeSize : GUARD_SIZE+decodeSize] - err := dec.DecodeFEC(data, decodedRef) - checkGuardInt16(t, decodedMem[:GUARD_SIZE]) - checkGuardInt16(t, decodedMem[decodeSize+GUARD_SIZE:]) - checkResult(t, FRAME_SIZE, err, expectOK) - } - - decodeFecFloat32 := func(t *testing.T, data []byte, decodeSize int, expectOK bool) { - dec := createDecoder(t) - decodedMem := make([]float32, decodeSize+GUARD_SIZE*2) - decodedRef := decodedMem[GUARD_SIZE : GUARD_SIZE+decodeSize : GUARD_SIZE+decodeSize] - err := dec.DecodeFECFloat32(data, decodedRef) - checkGuardFloat32(t, decodedMem[:GUARD_SIZE]) - checkGuardFloat32(t, decodedMem[decodeSize+GUARD_SIZE:]) - checkResult(t, FRAME_SIZE, err, expectOK) - } - - t.Run("smaller-buffer-int16", func(t *testing.T) { - decodeInt16(t, encodeFrame(t), FRAME_SIZE-1, false) - }) - - t.Run("smaller-buffer-float32", func(t *testing.T) { - decodeFloat32(t, encodeFrame(t), FRAME_SIZE-1, false) - }) - - t.Run("smaller-buffer-int16-fec", func(t *testing.T) { - decodeFecFloat32(t, encodeFrame(t), FRAME_SIZE-1, false) - }) - - t.Run("smaller-buffer-float32-fec", func(t *testing.T) { - decodeFecFloat32(t, encodeFrame(t), FRAME_SIZE-1, false) - }) - - t.Run("exact-buffer-int16", func(t *testing.T) { - decodeInt16(t, encodeFrame(t), FRAME_SIZE, true) - }) - - t.Run("exact-buffer-float32", func(t *testing.T) { - decodeFloat32(t, encodeFrame(t), FRAME_SIZE, true) - }) - - t.Run("exact-buffer-int16-fec", func(t *testing.T) { - decodeFecInt16(t, encodeFrame(t), FRAME_SIZE, true) - }) - - t.Run("exact-buffer-float32-fec", func(t *testing.T) { - decodeFecFloat32(t, encodeFrame(t), FRAME_SIZE, true) - }) - - t.Run("larger-buffer-int16", func(t *testing.T) { - decodeInt16(t, encodeFrame(t), FRAME_SIZE+1, true) - }) - - t.Run("larger-buffer-float32", func(t *testing.T) { - decodeFloat32(t, encodeFrame(t), FRAME_SIZE+1, true) - }) - - t.Run("larger-buffer-int16-fec", func(t *testing.T) { - decodeFecInt16(t, encodeFrame(t), FRAME_SIZE+1, false) - }) - - t.Run("larger-buffer-float32-fec", func(t *testing.T) { - decodeFecFloat32(t, encodeFrame(t), FRAME_SIZE+1, false) - }) -}