Merge pull request #18 from stop-start/forward-error-correction
Forward error correction. Closes hraban/opus#17
This commit is contained in:
commit
0f2e0b4fc6
1
AUTHORS
1
AUTHORS
|
@ -5,3 +5,4 @@ relevant license (see the LICENSE file).
|
|||
Hraban Luyat <hraban@0brg.net>
|
||||
Dejian Xu <xudejian2008@gmail.com>
|
||||
Tobias Wellnitz <tobias.wellnitz@gmail.com>
|
||||
Elinor Natanzon <stop.start.dev@gmail.com>
|
||||
|
|
|
@ -108,6 +108,15 @@ for i := 0; i < n; i++ {
|
|||
}
|
||||
```
|
||||
|
||||
Note regarding Forward Error Correction (FEC):
|
||||
> When a packet is considered "lost", `DecodeFEC` and `DecodeFECFloat32` methods
|
||||
> can be called on the next packet in order to try and recover some of the lost
|
||||
> data. The PCM needs to be exactly the duration of audio that is missing.
|
||||
> `LastPacketDuration()` can be used on the decoder to get the length of the
|
||||
> last packet.
|
||||
> Note also that in order to use this feature the encoder needs to be configured
|
||||
> with `SetInBandFEC(true)` and `SetPacketLossPerc(x)` options.
|
||||
|
||||
### Streams (and files)
|
||||
|
||||
To decode a .opus file (or .ogg with Opus data), or to decode a "Opus stream"
|
||||
|
|
70
decoder.go
70
decoder.go
|
@ -12,6 +12,12 @@ import (
|
|||
/*
|
||||
#cgo pkg-config: opus
|
||||
#include <opus.h>
|
||||
|
||||
int
|
||||
bridge_decoder_get_last_packet_duration(OpusDecoder *st, opus_int32 *samples)
|
||||
{
|
||||
return opus_decoder_ctl(st, OPUS_GET_LAST_PACKET_DURATION(samples));
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
|
@ -105,3 +111,67 @@ func (dec *Decoder) DecodeFloat32(data []byte, pcm []float32) (int, error) {
|
|||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// DecodeFEC encoded Opus data into the supplied buffer with forward error
|
||||
// correction. It is to be used on the packet directly following the lost one.
|
||||
// The supplied buffer needs to be exactly the duration of audio that is missing
|
||||
func (dec *Decoder) DecodeFEC(data []byte, pcm []int16) error {
|
||||
if dec.p == nil {
|
||||
return errDecUninitialized
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return fmt.Errorf("opus: no data supplied")
|
||||
}
|
||||
if len(pcm) == 0 {
|
||||
return fmt.Errorf("opus: target buffer empty")
|
||||
}
|
||||
|
||||
n := int(C.opus_decode(
|
||||
dec.p,
|
||||
(*C.uchar)(&data[0]),
|
||||
C.opus_int32(len(data)),
|
||||
(*C.opus_int16)(&pcm[0]),
|
||||
C.int(cap(pcm)),
|
||||
1))
|
||||
if n < 0 {
|
||||
return Error(n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeFECFloat32 encoded Opus data into the supplied buffer with forward error
|
||||
// correction. It is to be used on the packet directly following the lost one.
|
||||
// The supplied buffer needs to be exactly the duration of audio that is missing
|
||||
func (dec *Decoder) DecodeFECFloat32(data []byte, pcm []float32) error {
|
||||
if dec.p == nil {
|
||||
return errDecUninitialized
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return fmt.Errorf("opus: no data supplied")
|
||||
}
|
||||
if len(pcm) == 0 {
|
||||
return fmt.Errorf("opus: target buffer empty")
|
||||
}
|
||||
n := int(C.opus_decode_float(
|
||||
dec.p,
|
||||
(*C.uchar)(&data[0]),
|
||||
C.opus_int32(len(data)),
|
||||
(*C.float)(&pcm[0]),
|
||||
C.int(cap(pcm)),
|
||||
1))
|
||||
if n < 0 {
|
||||
return Error(n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LastPacketDuration gets the duration (in samples)
|
||||
// of the last packet successfully decoded or concealed.
|
||||
func (dec *Decoder) LastPacketDuration() (int, error) {
|
||||
var samples C.opus_int32
|
||||
res := C.bridge_decoder_get_last_packet_duration(dec.p, &samples)
|
||||
if res != C.OPUS_OK {
|
||||
return 0, Error(res)
|
||||
}
|
||||
return int(samples), nil
|
||||
}
|
||||
|
|
|
@ -30,3 +30,39 @@ 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)
|
||||
}
|
||||
}
|
||||
|
|
67
encoder.go
67
encoder.go
|
@ -68,6 +68,30 @@ bridge_encoder_get_max_bandwidth(OpusEncoder *st, opus_int32 *max_bw)
|
|||
return opus_encoder_ctl(st, OPUS_GET_MAX_BANDWIDTH(max_bw));
|
||||
}
|
||||
|
||||
int
|
||||
bridge_encoder_set_inband_fec(OpusEncoder *st, opus_int32 fec)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_SET_INBAND_FEC(fec));
|
||||
}
|
||||
|
||||
int
|
||||
bridge_encoder_get_inband_fec(OpusEncoder *st, opus_int32 *fec)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_GET_INBAND_FEC(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));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
|
@ -301,3 +325,46 @@ func (enc *Encoder) MaxBandwidth() (Bandwidth, error) {
|
|||
}
|
||||
return Bandwidth(maxBw), nil
|
||||
}
|
||||
|
||||
// SetInBandFEC configures the encoder's use of inband forward error
|
||||
// correction (FEC)
|
||||
func (enc *Encoder) SetInBandFEC(fec bool) error {
|
||||
i := 0
|
||||
if fec {
|
||||
i = 1
|
||||
}
|
||||
res := C.bridge_encoder_set_inband_fec(enc.p, C.opus_int32(i))
|
||||
if res != C.OPUS_OK {
|
||||
return Error(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InBandFEC gets the encoder's configured inband forward error correction (FEC)
|
||||
func (enc *Encoder) InBandFEC() (bool, error) {
|
||||
var fec C.opus_int32
|
||||
res := C.bridge_encoder_get_inband_fec(enc.p, &fec)
|
||||
if res != C.OPUS_OK {
|
||||
return false, Error(res)
|
||||
}
|
||||
return fec != 0, nil
|
||||
}
|
||||
|
||||
// SetPacketLossPerc configures the encoder's expected packet loss percentage.
|
||||
func (enc *Encoder) SetPacketLossPerc(lossPerc int) error {
|
||||
res := C.bridge_encoder_set_packet_loss_perc(enc.p, C.opus_int32(lossPerc))
|
||||
if res != C.OPUS_OK {
|
||||
return Error(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PacketLossPerc gets the encoder's configured packet loss percentage.
|
||||
func (enc *Encoder) PacketLossPerc() (int, error) {
|
||||
var lossPerc C.opus_int32
|
||||
res := C.bridge_encoder_get_packet_loss_perc(enc.p, &lossPerc)
|
||||
if res != C.OPUS_OK {
|
||||
return 0, Error(res)
|
||||
}
|
||||
return int(lossPerc), nil
|
||||
}
|
||||
|
|
|
@ -259,3 +259,79 @@ func TestEncoder_SetGetMaxBandwidth(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder_SetGetInBandFEC(t *testing.T) {
|
||||
enc, err := NewEncoder(8000, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Errorf("Error creating new encoder: %v", err)
|
||||
}
|
||||
|
||||
if err := enc.SetInBandFEC(true); err != nil {
|
||||
t.Error("Error setting fec:", err)
|
||||
}
|
||||
|
||||
fec, err := enc.InBandFEC()
|
||||
if err != nil {
|
||||
t.Error("Error getting fec", err)
|
||||
}
|
||||
if !fec {
|
||||
t.Errorf("Wrong fec value. Expected %t", true)
|
||||
}
|
||||
|
||||
if err := enc.SetInBandFEC(false); err != nil {
|
||||
t.Error("Error setting fec:", err)
|
||||
}
|
||||
|
||||
fec, err = enc.InBandFEC()
|
||||
if err != nil {
|
||||
t.Error("Error getting fec", err)
|
||||
}
|
||||
if fec {
|
||||
t.Errorf("Wrong fec value. Expected %t", false)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder_SetGetPacketLossPerc(t *testing.T) {
|
||||
enc, err := NewEncoder(8000, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Errorf("Error creating new encoder: %v", err)
|
||||
}
|
||||
vals := []int{0, 5, 10, 20}
|
||||
for _, lossPerc := range vals {
|
||||
err := enc.SetPacketLossPerc(lossPerc)
|
||||
if err != nil {
|
||||
t.Error("Error setting loss percentage value:", err)
|
||||
}
|
||||
lp, err := enc.PacketLossPerc()
|
||||
if err != nil {
|
||||
t.Error("Error getting loss percentage value", err)
|
||||
}
|
||||
if lp != lossPerc {
|
||||
t.Errorf("Unexpected encoder loss percentage value. Got %d, but expected %d",
|
||||
lp, lossPerc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder_SetGetInvalidPacketLossPerc(t *testing.T) {
|
||||
enc, err := NewEncoder(8000, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Errorf("Error creating new encoder: %v", err)
|
||||
}
|
||||
vals := []int{-1, 101}
|
||||
for _, lossPerc := range vals {
|
||||
err := enc.SetPacketLossPerc(lossPerc)
|
||||
if err == nil {
|
||||
t.Errorf("Expected Error invalid loss percentage: %d", lossPerc)
|
||||
}
|
||||
lp, err := enc.PacketLossPerc()
|
||||
if err != nil {
|
||||
t.Error("Error getting loss percentage value", err)
|
||||
}
|
||||
// default packet loss percentage is 0
|
||||
if lp != 0 {
|
||||
t.Errorf("Unexpected encoder loss percentage value. Got %d, but expected %d",
|
||||
lp, lossPerc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
165
opus_test.go
165
opus_test.go
|
@ -91,6 +91,171 @@ func TestCodecFloat32(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
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 TestStereo(t *testing.T) {
|
||||
// Create bogus input sound
|
||||
const G4 = 391.995
|
||||
|
|
Loading…
Reference in a new issue