Use libopusenc to encode stream, WiP: tests
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
DataHoarder 2022-02-27 18:56:08 +01:00
parent c9b07c6bec
commit 6cc027f24d
6 changed files with 263 additions and 726 deletions

View file

@ -9,15 +9,28 @@
// plays nice with the CGo rules and avoids any confusion.
#include <opusfile.h>
#include <opusenc.h>
#include <stdint.h>
// 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));
}

View file

@ -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 {

View file

@ -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)
}
}

View file

@ -6,91 +6,60 @@ package opus
import (
"fmt"
"io"
"runtime/cgo"
"unsafe"
)
/*
#cgo pkg-config: opus
#cgo pkg-config: opus libopusenc
#include <opus.h>
#include <opusenc.h>
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).

View file

@ -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)
}

View file

@ -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)
})
}