Commit ef59e937 authored by johan's avatar johan Committed by jehan
Browse files

Opus filter integration completed

limitations:
- 48kHz sampling rate only
- packet size from 20 to 120ms by 20ms steps
parent ab49b348
......@@ -21,13 +21,19 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "mediastreamer2/msfilter.h"
#include "mediastreamer2/mscodecutils.h"
#include "mediastreamer2/msfilter.h"
#include "mediastreamer2/msticker.h"
#include <opus/opus.h>
#define SIGNAL_SAMPLE_SIZE 2 // 2 bytes per sample
/* Define codec specific settings */
#define FRAME_LENGTH 20 // ptime may be 20, 40, 60, 80, 100 or 120, packets composed of multiples 20ms frames
#define MAX_BYTES_PER_FRAME 250 // Equals peak bitrate of 100 kbps
#define MAX_INPUT_FRAMES 5
#define MAX_INPUT_FRAMES 6
/**
......@@ -40,13 +46,26 @@ typedef struct _OpusEncData {
int samplerate;
int channels;
int application;
int vbr;
int inbandfec;
int max_network_bitrate;
int bitrate;
int maxplaybackrate;
int maxptime;
int ptime;
int minptime;
int maxaveragebitrate;
int stereo;
int vbr;
int useinbandfec;
int usedtx;
} OpusEncData;
/* Local functions headers */
static void apply_max_bitrate(OpusEncData *d);
static int ms_opus_enc_set_vbr(MSFilter *f);
static int ms_opus_enc_set_inbandfec(MSFilter *f);
static int ms_opus_enc_set_dtx(MSFilter *f);
/******************************************************************************
* Methods to (de)initialize and run the opus encoder *
......@@ -57,67 +76,116 @@ static void ms_opus_enc_init(MSFilter *f) {
d->bufferizer = ms_bufferizer_new();
d->state = NULL;
d->ts = 0;
d->samplerate = 8000;
d->channels = 1;
d->samplerate = 48000;
d->application = OPUS_APPLICATION_VOIP;
d->vbr = 1;
d->inbandfec = 1;
d->bitrate = -1;
d->bitrate = 30000;
d->max_network_bitrate = 46000;
/* set default parameters according to draft RFC RTP Payload Format for Opus codec section 6.1 */
d->maxplaybackrate = 48000;
d->maxptime = 120;
d->ptime = 20;
d->minptime = 20; // defaut shall be 3, but we never use less than 20.
d->maxaveragebitrate = -1;
d->stereo = 1;
d->channels = 1;
d->vbr = 1;
d->useinbandfec = 0;
d->usedtx = 0;
f->data = d;
}
static void ms_opus_enc_preprocess(MSFilter *f) {
int error;
OpusEncData *d = (OpusEncData *)f->data;
/* create the encoder */
d->state = opus_encoder_create(d->samplerate, d->channels, d->application, &error);
if (error != OPUS_OK) {
ms_error("Opus encoder creation failed: %s", opus_strerror(error));
return;
}
error = opus_encoder_ctl(d->state, OPUS_SET_VBR(d->vbr));
if (error != OPUS_OK) {
ms_error("Could not set vbr mode to opus encoder: %s", opus_strerror(error));
}
error = opus_encoder_ctl(d->state, OPUS_SET_INBAND_FEC(d->inbandfec));
if (error != OPUS_OK) {
ms_error("could not set inband fec to opus encoder: %s", opus_strerror(error));
/* set the encoder parameters: VBR, IN_BAND_FEC, DTX and bitrate settings */
ms_opus_enc_set_vbr(f);
ms_opus_enc_set_inbandfec(f);
ms_opus_enc_set_dtx(f);
/* if decoder prefers mono signal, force encoder to output mono signal */
if (d->stereo == 0) {
error = opus_encoder_ctl(d->state, OPUS_SET_FORCE_CHANNELS(1));
if (error != OPUS_OK) {
ms_error("could not force mono channel to opus encoder: %s", opus_strerror(error));
}
}
apply_max_bitrate(d);
}
static void ms_opus_enc_process(MSFilter *f) {
OpusEncData *d = (OpusEncData *)f->data;
mblk_t *im;
mblk_t *om = NULL;
uint8_t *buff = NULL;
opus_int32 ret;
opus_int32 nbytes;
int i;
int frameNumber = d->ptime/FRAME_LENGTH; /* encode 20ms frames, ptime is a multiple of 20ms */
uint8_t *codedFrameBuffer[frameNumber];
uint8_t *signalFrameBuffer = NULL;
OpusRepacketizer *rp = opus_repacketizer_create();
opus_int32 ret = 0;
opus_int32 totalLength = 0;
int packet_size = d->samplerate * d->ptime / 1000; /* in samples */
int frame_size = d->samplerate * FRAME_LENGTH / 1000; /* in samples */
while ((im = ms_queue_get(f->inputs[0])) != NULL) {
ms_bufferizer_put(d->bufferizer, im);
}
while (ms_bufferizer_get_avail(d->bufferizer) >= (packet_size * 2)) {
/* max payload size */
nbytes = MAX_BYTES_PER_FRAME * MAX_INPUT_FRAMES;
om = allocb(nbytes, 0);
if (!buff) buff = ms_malloc(packet_size * 2);
ms_bufferizer_read(d->bufferizer, buff, packet_size * 2);
ret = opus_encode(d->state, (opus_int16 *)buff, packet_size, om->b_wptr, nbytes);
if (ret < 0) {
ms_error("Opus encoder error: %s", opus_strerror(ret));
freeb(om);
for (i=0; i<frameNumber; i++) {
codedFrameBuffer[i]=NULL;
}
while (ms_bufferizer_get_avail(d->bufferizer) >= (d->channels * packet_size * SIGNAL_SAMPLE_SIZE)) {
totalLength = 0;
opus_repacketizer_init(rp);
for (i=0; i<frameNumber; i++) { /* encode 20ms by 20ms and repacketize all of them together */
if (!codedFrameBuffer[i]) codedFrameBuffer[i] = ms_malloc(MAX_BYTES_PER_FRAME); /* the repacketizer need the pointer to packet to remain valid, so we shall have a buffer for each coded frame */
if (!signalFrameBuffer) signalFrameBuffer = ms_malloc(frame_size * SIGNAL_SAMPLE_SIZE * d->channels);
ms_bufferizer_read(d->bufferizer, signalFrameBuffer, frame_size * SIGNAL_SAMPLE_SIZE * d->channels);
ret = opus_encode(d->state, (opus_int16 *)signalFrameBuffer, frame_size, codedFrameBuffer[i], MAX_BYTES_PER_FRAME);
if (ret < 0) {
ms_error("Opus encoder error: %s", opus_strerror(ret));
break;
}
if (ret > 0) {
int err = opus_repacketizer_cat(rp, codedFrameBuffer[i], ret); /* add the encoded frame into the current packet */
if (err != OPUS_OK) {
ms_error("Opus repacketizer error: %s", opus_strerror(err));
break;
}
totalLength += ret;
}
}
if (ret > 0) {
d->ts += packet_size;
om->b_wptr += nbytes;
om = allocb(totalLength+frameNumber + 1, 0); /* opus repacktizer API: allocate at leat number of frame + size of all data added before */
ret = opus_repacketizer_out(rp, om->b_wptr, totalLength+frameNumber);
om->b_wptr += ret;
mblk_set_timestamp_info(om, d->ts);
ms_queue_put(f->outputs[0], om);
om = NULL;
d->ts += packet_size*48000/d->samplerate; /* RFC payload RTP opus 03 - section 4: RTP timestamp multiplier : WARNING works only with sr at 48000 */
ret = 0;
}
}
if (buff != NULL) {
ms_free(buff);
opus_repacketizer_destroy(rp);
if (signalFrameBuffer != NULL) {
ms_free(signalFrameBuffer);
}
for (i=0; i<frameNumber; i++) {
if (codedFrameBuffer[i] != NULL) {
ms_free(codedFrameBuffer[i]);
}
}
}
......@@ -145,39 +213,99 @@ static void ms_opus_enc_uninit(MSFilter *f) {
*****************************************************************************/
static void apply_max_bitrate(OpusEncData *d) {
int inital_cbr = 0;
int normalized_cbr = 0;
int pps = 1000 / d->ptime;
normalized_cbr = inital_cbr = (int)(((((float)d->max_network_bitrate) / (pps * 8)) - 20 - 12 -8) * pps * 8);
switch (d->samplerate) {
case 8000:
normalized_cbr = MIN(normalized_cbr, 20000);
normalized_cbr = MAX(normalized_cbr, 5000);
break;
case 12000:
normalized_cbr = MIN(normalized_cbr, 25000);
normalized_cbr = MAX(normalized_cbr, 7000);
break;
case 16000:
normalized_cbr = MIN(normalized_cbr, 32000);
normalized_cbr = MAX(normalized_cbr, 8000);
break;
case 24000:
normalized_cbr = MIN(normalized_cbr, 40000);
normalized_cbr = MAX(normalized_cbr, 20000);
break;
normalized_cbr = (int)(((((float)d->max_network_bitrate) / (pps * 8)) - 20 - 12 -8) * pps * 8);
/* check if bitrate is in range [6,510kbps] */
if (normalized_cbr<6000) {
ms_warning("Opus encoder doesn't support bitrate [%i] set to 6kbps", normalized_cbr);
normalized_cbr = 6000;
}
if (normalized_cbr != inital_cbr) {
ms_warning("Opus encoder doesn't support codec bitrate [%i], normalizing", inital_cbr);
if (normalized_cbr>510000) {
ms_warning("Opus encoder doesn't support bitrate [%i] set to 510kbps", normalized_cbr);
normalized_cbr = 510000;
}
d->bitrate = normalized_cbr;
/* check maxaveragesampling rate parameter */
if (d->maxaveragebitrate>0 && d->maxaveragebitrate<d->bitrate) {
ms_warning("Opus encoder can't apply codec bitrate [%i] from network bitrate [%i] because of maxaveragebitrate [%i] requested by fmtp line. Fall back to fmtp bitrate setting.", d->bitrate, d->max_network_bitrate, d->maxaveragebitrate);
d->bitrate = d->maxaveragebitrate;
d->max_network_bitrate = ((d->bitrate*d->ptime/8000) + 12 + 8 + 20) *8000/d->ptime;
}
ms_message("Setting opus codec birate to [%i] from network bitrate [%i] with ptime [%i]", d->bitrate, d->max_network_bitrate, d->ptime);
/* give the bitrate to the encoder if exists*/
if (d->state) {
int error = opus_encoder_ctl(d->state, OPUS_SET_BITRATE(d->bitrate));
if (error != OPUS_OK) {
ms_error("could not set bit rate to opus encoder: %s", opus_strerror(error));
}
/* set output sampling rate and mode according to bitrate and RFC section 3.1.1 */
opus_int32 maxBandwidth;
if (d->bitrate<12000) {
d->application = OPUS_APPLICATION_VOIP;
maxBandwidth = OPUS_BANDWIDTH_NARROWBAND;
} else if (d->bitrate<20000) {
d->application = OPUS_APPLICATION_VOIP;
maxBandwidth = OPUS_BANDWIDTH_WIDEBAND;
} else if (d->bitrate<40000) {
d->application = OPUS_APPLICATION_VOIP;
maxBandwidth = OPUS_BANDWIDTH_FULLBAND;
} else if (d->bitrate<64000) {
d->application = OPUS_APPLICATION_AUDIO;
maxBandwidth = OPUS_BANDWIDTH_FULLBAND;
} else {
d->application = OPUS_APPLICATION_AUDIO;
maxBandwidth = OPUS_BANDWIDTH_FULLBAND;
}
/* check if selected maxBandwidth is compatible with the maxplaybackrate parameter */
if (d->maxplaybackrate < 12000) {
maxBandwidth = OPUS_BANDWIDTH_NARROWBAND;
} else if (d->maxplaybackrate < 16000) {
if (maxBandwidth != OPUS_BANDWIDTH_NARROWBAND) {
maxBandwidth = OPUS_BANDWIDTH_MEDIUMBAND;
}
} else if (d->maxplaybackrate < 24000) {
if (maxBandwidth != OPUS_BANDWIDTH_NARROWBAND) {
maxBandwidth = OPUS_BANDWIDTH_WIDEBAND;
}
} else if (d->maxplaybackrate < 48000) {
if (maxBandwidth == OPUS_BANDWIDTH_FULLBAND) {
maxBandwidth = OPUS_BANDWIDTH_SUPERWIDEBAND;
}
}
error = opus_encoder_ctl(d->state, OPUS_SET_APPLICATION(d->application));
if (error != OPUS_OK) {
ms_error("could not set application to opus encoder: %s", opus_strerror(error));
}
error = opus_encoder_ctl(d->state, OPUS_SET_MAX_BANDWIDTH(maxBandwidth));
if (error != OPUS_OK) {
ms_error("could not set application to opus encoder: %s", opus_strerror(error));
}
}
}
static int ms_opus_enc_set_sample_rate(MSFilter *f, void *arg) {
OpusEncData *d = (OpusEncData *)f->data;
d->samplerate = *((int *)arg);
int samplerate = *((int *)arg);
/* check values: supported are 8, 12, 16, 24 and 48 kHz */
switch (samplerate) {
case 8000:case 12000:case 16000:case 24000:case 48000:
d->samplerate=samplerate;
break;
default:
ms_error("Opus encoder got unsupported sample rate of %d, switch to default 48kHz",samplerate);
d->samplerate=48000;
}
return 0;
}
......@@ -200,28 +328,106 @@ static int ms_opus_enc_get_bitrate(MSFilter *f, void *arg) {
return 0;
}
static int ms_opus_enc_add_fmtp(MSFilter *f, void *arg) {
static int ms_opus_enc_set_ptime(MSFilter *f, void *arg){
OpusEncData *d = (OpusEncData *)f->data;
const char *fmtp = (const char *)arg;
char buf[32];
int ptime = *(int*)arg;
memset(buf, '\0', sizeof(buf));
if (fmtp_get_value(fmtp, "app", buf, sizeof(buf))) {
if (strcmp(buf, "voip") == 0) d->application = OPUS_APPLICATION_VOIP;
else if (strcmp(buf, "audio") == 0) d->application = OPUS_APPLICATION_AUDIO;
else if (strcmp(buf, "lowdelay") == 0) d->application = OPUS_APPLICATION_RESTRICTED_LOWDELAY;
/* ptime value can be 20, 40, 60, 80, 100 or 120 */
if ((ptime%20 != 0) || (ptime>120) || (ptime<20)) {
d->ptime = ptime-ptime%20;
if (d->ptime<20) {
d->ptime=20;
}
if (d->ptime>120) {
d->ptime=120;
}
ms_warning("Opus encoder doesn't support ptime [%i]( 20 multiple in range [20,120] only) set to %d", ptime, d->ptime);
} else {
d->ptime=ptime;
ms_message ( "Opus enc: got ptime=%i",d->ptime );
}
/*new encoder bitrate must be computed*/
if (d->state) apply_max_bitrate(d);
return 0;
}
static int ms_opus_enc_set_nchannels(MSFilter *f, void *arg) {
OpusEncData *d = (OpusEncData *)f->data;
d->channels = *(int*)arg;
return 0;
}
static int ms_opus_enc_set_vbr(MSFilter *f) {
OpusEncData *d = (OpusEncData *)f->data;
int error;
if (d->state) {
error = opus_encoder_ctl(d->state, OPUS_SET_VBR(d->vbr));
if (error != OPUS_OK) {
ms_error("could not set VBR to opus encoder: %s", opus_strerror(error));
}
}
memset(buf, '\0', sizeof(buf));
if (fmtp_get_value(fmtp, "vbr", buf, sizeof(buf))) {
if (strcmp(buf, "off") == 0) d->vbr = 0;
else if (strcmp(buf, "on") == 0) d->vbr = 1;
return 0;
}
static int ms_opus_enc_set_inbandfec(MSFilter *f) {
OpusEncData *d = (OpusEncData *)f->data;
int error;
if (d->state) {
error = opus_encoder_ctl(d->state, OPUS_SET_INBAND_FEC(d->useinbandfec));
if (error != OPUS_OK) {
ms_error("could not set inband FEC to opus encoder: %s", opus_strerror(error));
}
}
memset(buf, '\0', sizeof(buf));
if (fmtp_get_value(fmtp, "inbandfec", buf, sizeof(buf))) {
if (strcmp(buf, "off") == 0) d->inbandfec = 0;
else if (strcmp(buf, "on") == 0) d->inbandfec = 1;
return 0;
}
static int ms_opus_enc_set_dtx(MSFilter *f) {
OpusEncData *d = (OpusEncData *)f->data;
int error;
if (d->state) {
error = opus_encoder_ctl(d->state, OPUS_SET_DTX(d->usedtx));
if (error != OPUS_OK) {
ms_error("could not set use DTX to opus encoder: %s", opus_strerror(error));
}
}
return 0;
}
static int ms_opus_enc_add_fmtp(MSFilter *f, void *arg) {
OpusEncData *d = (OpusEncData *)f->data;
const char *fmtp = (const char *)arg;
char buf[64]= {0};
if ( fmtp_get_value ( fmtp,"maxplaybackrate",buf,sizeof ( buf ) ) ) {
d->maxplaybackrate=atoi(buf);
} else if ( fmtp_get_value ( fmtp,"maxptime",buf,sizeof ( buf ) ) ) {
d->maxptime=MIN(atoi(buf),120);
} else if ( fmtp_get_value ( fmtp,"ptime",buf,sizeof ( buf ) ) ) {
int val=atoi(buf);
ms_opus_enc_set_ptime(f,&val);
} else if ( fmtp_get_value ( fmtp,"minptime",buf,sizeof ( buf ) ) ) {
d->minptime=MAX(atoi(buf),20); // minimum shall be 3 but we do not provide less than 20ms ptime.
} else if ( fmtp_get_value ( fmtp,"maxaveragebitrate",buf,sizeof ( buf ) ) ) {
d->maxaveragebitrate = atoi(buf);
} else if ( fmtp_get_value ( fmtp,"stereo",buf,sizeof ( buf ) ) ) {
d->stereo = atoi(buf);
} else if ( fmtp_get_value ( fmtp,"cbr",buf,sizeof ( buf ) ) ) {
if (atoi(buf) == 1 ) {
d->vbr = 0;
} else {
d->vbr = 1;
}
ms_opus_enc_set_vbr(f);
} else if ( fmtp_get_value ( fmtp,"useinbandfec",buf,sizeof ( buf ) ) ) {
d->useinbandfec = atoi(buf);
} else if ( fmtp_get_value ( fmtp,"usedtx",buf,sizeof ( buf ) ) ) {
d->usedtx = atoi(buf);
}
return 0;
......@@ -232,7 +438,9 @@ static MSFilterMethod ms_opus_enc_methods[] = {
{ MS_FILTER_GET_SAMPLE_RATE, ms_opus_enc_get_sample_rate },
{ MS_FILTER_SET_BITRATE, ms_opus_enc_set_bitrate },
{ MS_FILTER_GET_BITRATE, ms_opus_enc_get_bitrate },
{ MS_FILTER_ADD_FMTP, ms_opus_enc_add_fmtp },
{ MS_FILTER_ADD_FMTP, ms_opus_enc_add_fmtp },
{ MS_AUDIO_ENCODER_SET_PTIME, ms_opus_enc_set_ptime },
{ MS_FILTER_SET_NCHANNELS , ms_opus_enc_set_nchannels},
{ 0, NULL }
};
......@@ -301,8 +509,14 @@ typedef struct _OpusDecData {
OpusDecoder *state;
int samplerate;
int channels;
int frame_size;
/* concealment properties */
MSConcealerContext *concealer;
MSRtpPayloadPickerContext rtp_picker_context;
int sequence_number;
int lastPacketLength;
bool_t plc;
} OpusDecData;
......@@ -313,9 +527,9 @@ typedef struct _OpusDecData {
static void ms_opus_dec_init(MSFilter *f) {
OpusDecData *d = (OpusDecData *)ms_new(OpusDecData, 1);
d->state = NULL;
d->samplerate = 8000;
d->samplerate = 48000;
d->channels = 1;
d->frame_size = 20 * d->samplerate;
d->lastPacketLength = 0;
f->data = d;
}
......@@ -326,27 +540,61 @@ static void ms_opus_dec_preprocess(MSFilter *f) {
if (error != OPUS_OK) {
ms_error("Opus decoder creation failed: %s", opus_strerror(error));
}
/* initialise the concealer context */
d->concealer = ms_concealer_context_new(UINT32_MAX);
}
static void ms_opus_dec_process(MSFilter *f) {
OpusDecData *d = (OpusDecData *)f->data;
mblk_t *im;
mblk_t *om;
int bytes = d->frame_size * d->channels * 2;
int frames;
/* decode available packets */
while ((im = ms_queue_get(f->inputs[0])) != NULL) {
om = allocb(bytes, 0);
frames = opus_decode(d->state, (const unsigned char *)im->b_rptr, im->b_wptr - im->b_rptr, (opus_int16 *)om->b_wptr, d->frame_size, 0);
om = allocb(5760 * d->channels * SIGNAL_SAMPLE_SIZE, 0); /* 5760 is the maximum number of sample in a packet (120ms at 48KHz) */
frames = opus_decode(d->state, (const unsigned char *)im->b_rptr, im->b_wptr - im->b_rptr, (opus_int16 *)om->b_wptr, 5760, 0);
if (frames < 0) {
ms_warning("Opus decoder error: %s", opus_strerror(frames));
freemsg(om);
} else {
om->b_wptr += frames * d->channels * 2;
d->lastPacketLength = frames; // store the packet length for eventual PLC if next two packets are missing
om->b_wptr += frames * d->channels * SIGNAL_SAMPLE_SIZE;
ms_queue_put(f->outputs[0], om);
d->sequence_number = mblk_get_cseq(im); // used to get eventual FEC information if next packet is missing
ms_concealer_inc_sample_time(d->concealer,f->ticker->time, frames*1000/d->samplerate, 1);
}
freemsg(im);
}
/* Concealment if needed */
if (ms_concealer_context_is_concealement_required(d->concealer, f->ticker->time)) {
im = NULL;
int imLength = 0;
// try fec : info are stored in the next packet, do we have it?
if (d->rtp_picker_context.picker) {
im = d->rtp_picker_context.picker(&d->rtp_picker_context,d->sequence_number+1);
if (im) {
ms_message("opus dec, got fec from jitter buffer");
imLength = im->b_wptr - im->b_rptr;
}
}
om = allocb(5760 * d->channels * SIGNAL_SAMPLE_SIZE, 0); /* 5760 is the maximum number of sample in a packet (120ms at 48KHz) */
/* call to the decoder, we'll have either FEC or PLC, do it on the same length that last received packet */
frames = opus_decode(d->state, (im)?(const unsigned char *)im->b_rptr:NULL, imLength, (opus_int16 *)om->b_wptr, d->lastPacketLength, 1);
if (frames < 0) {
ms_warning("Opus decoder error in concealment: %s", opus_strerror(frames));
freemsg(om);
} else {
om->b_wptr += frames * d->channels * SIGNAL_SAMPLE_SIZE;
ms_queue_put(f->outputs[0], om);
d->sequence_number++;
ms_concealer_inc_sample_time(d->concealer,f->ticker->time, frames*1000/d->samplerate, 0);
}
}
}
static void ms_opus_dec_postprocess(MSFilter *f) {
......@@ -373,7 +621,6 @@ static void ms_opus_dec_uninit(MSFilter *f) {
static int ms_opus_dec_set_sample_rate(MSFilter *f, void *arg) {
OpusDecData *d = (OpusDecData *)f->data;
d->samplerate = *((int *)arg);
d->frame_size = 20 * d->samplerate;
return 0;
}
......@@ -395,16 +642,30 @@ static int ms_opus_dec_add_fmtp(MSFilter *f, void *arg) {
return 0;
}
static int ms_opus_set_rtp_picker(MSFilter *f, void *arg) {
OpusDecData *d = (OpusDecData *)f->data;
d->rtp_picker_context=*(MSRtpPayloadPickerContext*)arg;
return 0;
}
static int ms_opus_dec_have_plc(MSFilter *f, void *arg) {
*((int *)arg) = 1;
return 0;
}
static int ms_opus_dec_set_nchannels(MSFilter *f, void *arg) {
OpusDecData *d = (OpusDecData *)f->data;
d->channels=*(int*)arg;
return 0;
}
static MSFilterMethod ms_opus_dec_methods[] = {
{ MS_FILTER_SET_SAMPLE_RATE, ms_opus_dec_set_sample_rate },
{ MS_FILTER_GET_SAMPLE_RATE, ms_opus_dec_get_sample_rate },
{ MS_FILTER_ADD_FMTP, ms_opus_dec_add_fmtp },
{ MS_FILTER_SET_RTP_PAYLOAD_PICKER, ms_opus_set_rtp_picker },
{ MS_DECODER_HAVE_PLC, ms_opus_dec_have_plc },
{ MS_FILTER_SET_NCHANNELS , ms_opus_dec_set_nchannels},
{ 0, NULL }
};
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment