1248 lines
43 KiB
C
1248 lines
43 KiB
C
/*
|
|
* Copyright (C) 2015-2016 Teluu Inc. (http://www.teluu.com)
|
|
* Copyright (C) 2012-2015 Zaark Technology AB
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
/* This file is the implementation of Opus codec wrapper and was contributed by
|
|
* Zaark Technology AB
|
|
*/
|
|
|
|
#include <pjmedia-codec/opus.h>
|
|
#include <pjmedia/errno.h>
|
|
#include <pjmedia/endpoint.h>
|
|
#include <pj/log.h>
|
|
#include <pj/math.h>
|
|
|
|
#if defined(PJMEDIA_HAS_OPUS_CODEC) && (PJMEDIA_HAS_OPUS_CODEC!=0)
|
|
|
|
#include <opus/opus.h>
|
|
|
|
#define THIS_FILE "opus.c"
|
|
|
|
/* Default packet loss concealment setting. */
|
|
#define OPUS_DEFAULT_PLC 1
|
|
/* Default Voice Activity Detector setting. */
|
|
#define OPUS_DEFAULT_VAD 0
|
|
|
|
/* Maximum size of an encoded packet.
|
|
* If the the actual size is bigger, the encode/parse will fail.
|
|
*/
|
|
#define MAX_ENCODED_PACKET_SIZE 1280
|
|
|
|
/* Default frame time (msec) */
|
|
#define PTIME 20
|
|
#define PTIME_DENUM 1
|
|
|
|
/* Tracing */
|
|
#if 0
|
|
# define TRACE_(expr) PJ_LOG(4,expr)
|
|
#else
|
|
# define TRACE_(expr)
|
|
#endif
|
|
|
|
|
|
/* Prototypes for Opus factory */
|
|
static pj_status_t factory_test_alloc( pjmedia_codec_factory *factory,
|
|
const pjmedia_codec_info *ci );
|
|
static pj_status_t factory_default_attr( pjmedia_codec_factory *factory,
|
|
const pjmedia_codec_info *ci,
|
|
pjmedia_codec_param *attr );
|
|
static pj_status_t factory_enum_codecs( pjmedia_codec_factory *factory,
|
|
unsigned *count,
|
|
pjmedia_codec_info codecs[]);
|
|
static pj_status_t factory_alloc_codec( pjmedia_codec_factory *factory,
|
|
const pjmedia_codec_info *ci,
|
|
pjmedia_codec **p_codec);
|
|
static pj_status_t factory_dealloc_codec( pjmedia_codec_factory *factory,
|
|
pjmedia_codec *codec );
|
|
|
|
|
|
/* Prototypes for Opus implementation. */
|
|
static pj_status_t codec_init( pjmedia_codec *codec,
|
|
pj_pool_t *pool );
|
|
static pj_status_t codec_open( pjmedia_codec *codec,
|
|
pjmedia_codec_param *attr );
|
|
static pj_status_t codec_close( pjmedia_codec *codec );
|
|
static pj_status_t codec_modify( pjmedia_codec *codec,
|
|
const pjmedia_codec_param *attr );
|
|
static pj_status_t codec_parse( pjmedia_codec *codec,
|
|
void *pkt,
|
|
pj_size_t pkt_size,
|
|
const pj_timestamp *ts,
|
|
unsigned *frame_cnt,
|
|
pjmedia_frame frames[]);
|
|
static pj_status_t codec_encode( pjmedia_codec *codec,
|
|
const struct pjmedia_frame *input,
|
|
unsigned output_buf_len,
|
|
struct pjmedia_frame *output);
|
|
static pj_status_t codec_decode( pjmedia_codec *codec,
|
|
const struct pjmedia_frame *input,
|
|
unsigned output_buf_len,
|
|
struct pjmedia_frame *output);
|
|
static pj_status_t codec_recover( pjmedia_codec *codec,
|
|
unsigned output_buf_len,
|
|
struct pjmedia_frame *output);
|
|
|
|
/* Definition for Opus operations. */
|
|
static pjmedia_codec_op opus_op =
|
|
{
|
|
&codec_init,
|
|
&codec_open,
|
|
&codec_close,
|
|
&codec_modify,
|
|
&codec_parse,
|
|
&codec_encode,
|
|
&codec_decode,
|
|
&codec_recover
|
|
};
|
|
|
|
/* Definition for Opus factory operations. */
|
|
static pjmedia_codec_factory_op opus_factory_op =
|
|
{
|
|
&factory_test_alloc,
|
|
&factory_default_attr,
|
|
&factory_enum_codecs,
|
|
&factory_alloc_codec,
|
|
&factory_dealloc_codec,
|
|
&pjmedia_codec_opus_deinit
|
|
};
|
|
|
|
|
|
/* Opus factory */
|
|
struct opus_codec_factory
|
|
{
|
|
pjmedia_codec_factory base;
|
|
pjmedia_endpt *endpt;
|
|
pj_pool_t *pool;
|
|
};
|
|
|
|
/* Opus codec private data. */
|
|
struct opus_data
|
|
{
|
|
pj_pool_t *pool;
|
|
pj_mutex_t *mutex;
|
|
OpusEncoder *enc;
|
|
OpusDecoder *dec;
|
|
OpusRepacketizer *enc_packer;
|
|
OpusRepacketizer *dec_packer;
|
|
pjmedia_codec_opus_config cfg;
|
|
unsigned enc_ptime;
|
|
unsigned enc_ptime_denum;
|
|
unsigned dec_ptime;
|
|
unsigned dec_ptime_denum;
|
|
pjmedia_frame dec_frame[2];
|
|
int dec_frame_index;
|
|
};
|
|
|
|
/* Codec factory instance */
|
|
static struct opus_codec_factory opus_codec_factory;
|
|
|
|
/* Opus default configuration */
|
|
static pjmedia_codec_opus_config opus_cfg =
|
|
{
|
|
PJMEDIA_CODEC_OPUS_DEFAULT_SAMPLE_RATE, /* Sample rate */
|
|
1, /* Channel count */
|
|
PTIME, /* Frame ptime */
|
|
PTIME_DENUM, /* Frame ptime denum */
|
|
PJMEDIA_CODEC_OPUS_DEFAULT_BIT_RATE, /* Bit rate */
|
|
5, /* Expected packet loss */
|
|
PJMEDIA_CODEC_OPUS_DEFAULT_COMPLEXITY, /* Complexity */
|
|
PJMEDIA_CODEC_OPUS_DEFAULT_CBR, /* Constant bit rate */
|
|
};
|
|
|
|
|
|
static int get_opus_bw_constant (unsigned sample_rate)
|
|
{
|
|
if (sample_rate <= 8000)
|
|
return OPUS_BANDWIDTH_NARROWBAND;
|
|
else if (sample_rate <= 12000)
|
|
return OPUS_BANDWIDTH_MEDIUMBAND;
|
|
else if (sample_rate <= 16000)
|
|
return OPUS_BANDWIDTH_WIDEBAND;
|
|
else if (sample_rate <= 24000)
|
|
return OPUS_BANDWIDTH_SUPERWIDEBAND;
|
|
else
|
|
return OPUS_BANDWIDTH_FULLBAND;
|
|
}
|
|
|
|
|
|
/*
|
|
* Initialize and register Opus codec factory to pjmedia endpoint.
|
|
*/
|
|
PJ_DEF(pj_status_t) pjmedia_codec_opus_init( pjmedia_endpt *endpt )
|
|
{
|
|
pj_status_t status;
|
|
pjmedia_codec_mgr *codec_mgr;
|
|
|
|
PJ_ASSERT_RETURN(endpt, PJ_EINVAL);
|
|
|
|
if (opus_codec_factory.pool != NULL)
|
|
return PJ_SUCCESS;
|
|
|
|
/* Create the Opus codec factory */
|
|
opus_codec_factory.base.op = &opus_factory_op;
|
|
opus_codec_factory.base.factory_data = &opus_codec_factory;
|
|
opus_codec_factory.endpt = endpt;
|
|
|
|
opus_codec_factory.pool = pjmedia_endpt_create_pool(endpt, "opus-factory",
|
|
1024, 1024);
|
|
if (!opus_codec_factory.pool) {
|
|
PJ_LOG(2, (THIS_FILE, "Unable to create memory pool for Opus codec"));
|
|
return PJ_ENOMEM;
|
|
}
|
|
|
|
/* Get the codec manager */
|
|
codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
|
|
if (!codec_mgr) {
|
|
PJ_LOG(2, (THIS_FILE, "Unable to get the codec manager"));
|
|
status = PJ_EINVALIDOP;
|
|
goto on_codec_factory_error;
|
|
}
|
|
|
|
/* Register the codec factory */
|
|
status = pjmedia_codec_mgr_register_factory (codec_mgr,
|
|
&opus_codec_factory.base);
|
|
if (status != PJ_SUCCESS) {
|
|
PJ_LOG(2, (THIS_FILE, "Unable to register the codec factory"));
|
|
goto on_codec_factory_error;
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
|
|
on_codec_factory_error:
|
|
pj_pool_release(opus_codec_factory.pool);
|
|
opus_codec_factory.pool = NULL;
|
|
return status;
|
|
}
|
|
|
|
|
|
/*
|
|
* Unregister Opus codec factory from pjmedia endpoint and
|
|
* deinitialize the codec.
|
|
*/
|
|
PJ_DEF(pj_status_t) pjmedia_codec_opus_deinit( void )
|
|
{
|
|
pj_status_t status;
|
|
pjmedia_codec_mgr *codec_mgr;
|
|
|
|
if (opus_codec_factory.pool == NULL)
|
|
return PJ_SUCCESS;
|
|
|
|
/* Get the codec manager */
|
|
codec_mgr = pjmedia_endpt_get_codec_mgr(opus_codec_factory.endpt);
|
|
if (!codec_mgr) {
|
|
PJ_LOG(2, (THIS_FILE, "Unable to get the codec manager"));
|
|
pj_pool_release(opus_codec_factory.pool);
|
|
opus_codec_factory.pool = NULL;
|
|
return PJ_EINVALIDOP;
|
|
}
|
|
|
|
/* Unregister the codec factory */
|
|
status = pjmedia_codec_mgr_unregister_factory(codec_mgr,
|
|
&opus_codec_factory.base);
|
|
if (status != PJ_SUCCESS)
|
|
PJ_LOG(2, (THIS_FILE, "Unable to unregister the codec factory"));
|
|
|
|
/* Release the memory pool */
|
|
pj_pool_release(opus_codec_factory.pool);
|
|
opus_codec_factory.pool = NULL;
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the opus configuration for a specific sample rate.
|
|
*/
|
|
PJ_DEF(pj_status_t)
|
|
pjmedia_codec_opus_get_config( pjmedia_codec_opus_config *cfg )
|
|
{
|
|
PJ_ASSERT_RETURN(cfg, PJ_EINVAL);
|
|
|
|
pj_memcpy(cfg, &opus_cfg, sizeof(pjmedia_codec_opus_config));
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
static pj_str_t STR_MAX_PLAYBACK = {"maxplaybackrate", 15};
|
|
static pj_str_t STR_MAX_CAPTURE = {"sprop-maxcapturerate", 20};
|
|
static pj_str_t STR_STEREO = {"stereo", 6};
|
|
static pj_str_t STR_SPROP_STEREO = {"sprop-stereo", 12};
|
|
static pj_str_t STR_MAX_BIT_RATE = {"maxaveragebitrate", 17};
|
|
static pj_str_t STR_INBAND_FEC = {"useinbandfec", 12};
|
|
static pj_str_t STR_DTX = {"usedtx", 6};
|
|
static pj_str_t STR_CBR = {"cbr", 3};
|
|
|
|
static int find_fmtp(pjmedia_codec_fmtp *fmtp, pj_str_t *name, pj_bool_t add)
|
|
{
|
|
int i;
|
|
for (i = 0; i < fmtp->cnt; i++) {
|
|
if (pj_stricmp(&fmtp->param[i].name, name) == 0)
|
|
return i;
|
|
}
|
|
|
|
if (add && (i < PJMEDIA_CODEC_MAX_FMTP_CNT)) {
|
|
fmtp->param[i].name = *name;
|
|
fmtp->cnt++;
|
|
return i;
|
|
} else
|
|
return -1;
|
|
}
|
|
|
|
static void remove_fmtp(pjmedia_codec_fmtp *fmtp, pj_str_t *name)
|
|
{
|
|
int i, j;
|
|
for (i = 0; i < fmtp->cnt; i++) {
|
|
if (pj_stricmp(&fmtp->param[i].name, name) == 0) {
|
|
fmtp->cnt--;
|
|
for (j = i; j < fmtp->cnt; j++) {
|
|
fmtp->param[i].name = fmtp->param[i+1].name;
|
|
fmtp->param[i].val = fmtp->param[i+1].val;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static pj_status_t generate_fmtp(pjmedia_codec_param *attr)
|
|
{
|
|
int idx;
|
|
static char bitrate_str[12];
|
|
static char clockrate_str[12];
|
|
|
|
if (attr->info.clock_rate != 48000) {
|
|
pj_ansi_snprintf(clockrate_str, sizeof(clockrate_str), "%u",
|
|
attr->info.clock_rate);
|
|
|
|
idx = find_fmtp(&attr->setting.dec_fmtp, &STR_MAX_PLAYBACK, PJ_TRUE);
|
|
if (idx >= 0)
|
|
attr->setting.dec_fmtp.param[idx].val = pj_str(clockrate_str);
|
|
|
|
idx = find_fmtp(&attr->setting.dec_fmtp, &STR_MAX_CAPTURE, PJ_TRUE);
|
|
if (idx >= 0)
|
|
attr->setting.dec_fmtp.param[idx].val = pj_str(clockrate_str);
|
|
} else {
|
|
remove_fmtp(&attr->setting.dec_fmtp, &STR_MAX_PLAYBACK);
|
|
remove_fmtp(&attr->setting.dec_fmtp, &STR_MAX_CAPTURE);
|
|
}
|
|
|
|
/* Check if we need to set parameter 'maxaveragebitrate' */
|
|
if (opus_cfg.bit_rate > 0) {
|
|
idx = find_fmtp(&attr->setting.dec_fmtp, &STR_MAX_BIT_RATE, PJ_TRUE);
|
|
if (idx >= 0) {
|
|
pj_ansi_snprintf(bitrate_str, sizeof(bitrate_str), "%u",
|
|
attr->info.avg_bps);
|
|
attr->setting.dec_fmtp.param[idx].val = pj_str(bitrate_str);
|
|
}
|
|
} else {
|
|
remove_fmtp(&attr->setting.dec_fmtp, &STR_MAX_BIT_RATE);
|
|
}
|
|
|
|
if (attr->info.channel_cnt > 1) {
|
|
idx = find_fmtp(&attr->setting.dec_fmtp, &STR_STEREO, PJ_TRUE);
|
|
if (idx >= 0)
|
|
attr->setting.dec_fmtp.param[idx].val = pj_str("1");
|
|
|
|
idx = find_fmtp(&attr->setting.dec_fmtp, &STR_SPROP_STEREO, PJ_TRUE);
|
|
if (idx >= 0)
|
|
attr->setting.dec_fmtp.param[idx].val = pj_str("1");
|
|
} else {
|
|
remove_fmtp(&attr->setting.dec_fmtp, &STR_STEREO);
|
|
remove_fmtp(&attr->setting.dec_fmtp, &STR_SPROP_STEREO);
|
|
}
|
|
|
|
if (opus_cfg.cbr) {
|
|
idx = find_fmtp(&attr->setting.dec_fmtp, &STR_CBR, PJ_TRUE);
|
|
if (idx >= 0)
|
|
attr->setting.dec_fmtp.param[idx].val = pj_str("1");
|
|
} else {
|
|
remove_fmtp(&attr->setting.dec_fmtp, &STR_CBR);
|
|
}
|
|
|
|
if (attr->setting.plc) {
|
|
idx = find_fmtp(&attr->setting.dec_fmtp, &STR_INBAND_FEC, PJ_TRUE);
|
|
if (idx >= 0)
|
|
attr->setting.dec_fmtp.param[idx].val = pj_str("1");
|
|
} else {
|
|
remove_fmtp(&attr->setting.dec_fmtp, &STR_INBAND_FEC);
|
|
}
|
|
|
|
if (attr->setting.vad) {
|
|
idx = find_fmtp(&attr->setting.dec_fmtp, &STR_DTX, PJ_TRUE);
|
|
if (idx >= 0)
|
|
attr->setting.dec_fmtp.param[idx].val = pj_str("1");
|
|
} else {
|
|
remove_fmtp(&attr->setting.dec_fmtp, &STR_DTX);
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Set the opus configuration and default param.
|
|
*/
|
|
PJ_DEF(pj_status_t)
|
|
pjmedia_codec_opus_set_default_param(const pjmedia_codec_opus_config *cfg,
|
|
pjmedia_codec_param *param )
|
|
{
|
|
const pj_str_t opus_str = {"opus", 4};
|
|
const pjmedia_codec_info *info[1];
|
|
pjmedia_codec_mgr *codec_mgr;
|
|
unsigned count = 1;
|
|
pj_status_t status;
|
|
|
|
TRACE_((THIS_FILE, "%s:%d: - TRACE", __FUNCTION__, __LINE__));
|
|
PJ_ASSERT_RETURN(cfg && param, PJ_EINVAL);
|
|
|
|
codec_mgr = pjmedia_endpt_get_codec_mgr(opus_codec_factory.endpt);
|
|
|
|
status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, &opus_str,
|
|
&count, info, NULL);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
|
|
/* Set sample rate */
|
|
if (cfg->sample_rate != 8000 && cfg->sample_rate != 12000 &&
|
|
cfg->sample_rate != 16000 && cfg->sample_rate != 24000 &&
|
|
cfg->sample_rate != 48000)
|
|
{
|
|
return PJ_EINVAL;
|
|
}
|
|
|
|
param->info.clock_rate = opus_cfg.sample_rate = cfg->sample_rate;
|
|
param->info.max_bps = opus_cfg.sample_rate * 2;
|
|
opus_cfg.frm_ptime = cfg->frm_ptime;
|
|
opus_cfg.frm_ptime_denum = cfg->frm_ptime_denum;
|
|
param->info.frm_ptime = (pj_uint16_t)cfg->frm_ptime;
|
|
param->info.frm_ptime_denum = (pj_uint8_t)cfg->frm_ptime_denum;
|
|
|
|
/* Set channel count */
|
|
if (cfg->channel_cnt != 1 && cfg->channel_cnt != 2)
|
|
return PJ_EINVAL;
|
|
param->info.channel_cnt = opus_cfg.channel_cnt = cfg->channel_cnt;
|
|
|
|
/* Set bit_rate */
|
|
if ((cfg->bit_rate != PJMEDIA_CODEC_OPUS_DEFAULT_BIT_RATE) &&
|
|
(cfg->bit_rate < 6000 || cfg->bit_rate > 510000))
|
|
{
|
|
return PJ_EINVAL;
|
|
}
|
|
opus_cfg.bit_rate = cfg->bit_rate;
|
|
param->info.avg_bps = opus_cfg.bit_rate;
|
|
|
|
/* Set expected packet loss */
|
|
if (cfg->packet_loss >= 100)
|
|
return PJ_EINVAL;
|
|
opus_cfg.packet_loss = cfg->packet_loss;
|
|
|
|
/* Set complexity */
|
|
if (cfg->complexity > 10)
|
|
return PJ_EINVAL;
|
|
opus_cfg.complexity = cfg->complexity;
|
|
|
|
opus_cfg.cbr = cfg->cbr;
|
|
|
|
generate_fmtp(param);
|
|
|
|
status = pjmedia_codec_mgr_set_default_param(codec_mgr, info[0], param);
|
|
return status;
|
|
}
|
|
|
|
|
|
/*
|
|
* Check if factory can allocate the specified codec.
|
|
*/
|
|
static pj_status_t factory_test_alloc( pjmedia_codec_factory *factory,
|
|
const pjmedia_codec_info *ci )
|
|
{
|
|
const pj_str_t opus_tag = {"OPUS", 4};
|
|
|
|
PJ_UNUSED_ARG(factory);
|
|
PJ_ASSERT_RETURN(factory==&opus_codec_factory.base, PJ_EINVAL);
|
|
|
|
/* Type MUST be audio. */
|
|
if (ci->type != PJMEDIA_TYPE_AUDIO)
|
|
return PJMEDIA_CODEC_EUNSUP;
|
|
|
|
/* Check encoding name. */
|
|
if (pj_stricmp(&ci->encoding_name, &opus_tag) != 0)
|
|
return PJMEDIA_CODEC_EUNSUP;
|
|
|
|
/* Check clock rate */
|
|
if (ci->clock_rate != 8000 && ci->clock_rate != 12000 &&
|
|
ci->clock_rate != 16000 && ci->clock_rate != 24000 &&
|
|
ci->clock_rate != 48000)
|
|
{
|
|
return PJMEDIA_CODEC_EUNSUP;
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Generate default attribute.
|
|
*/
|
|
static pj_status_t factory_default_attr( pjmedia_codec_factory *factory,
|
|
const pjmedia_codec_info *ci,
|
|
pjmedia_codec_param *attr )
|
|
{
|
|
PJ_UNUSED_ARG(factory);
|
|
TRACE_((THIS_FILE, "%s:%d: - TRACE", __FUNCTION__, __LINE__));
|
|
|
|
pj_bzero(attr, sizeof(pjmedia_codec_param));
|
|
attr->info.pt = (pj_uint8_t)ci->pt;
|
|
attr->info.clock_rate = opus_cfg.sample_rate;
|
|
attr->info.channel_cnt = opus_cfg.channel_cnt;
|
|
attr->info.avg_bps = opus_cfg.bit_rate;
|
|
attr->info.max_bps = opus_cfg.sample_rate * 2;
|
|
attr->info.frm_ptime = (pj_uint16_t)opus_cfg.frm_ptime;
|
|
attr->info.frm_ptime_denum = (pj_uint8_t)opus_cfg.frm_ptime_denum;
|
|
attr->setting.frm_per_pkt = 1;
|
|
attr->info.pcm_bits_per_sample = 16;
|
|
attr->setting.vad = OPUS_DEFAULT_VAD;
|
|
attr->setting.plc = OPUS_DEFAULT_PLC;
|
|
attr->setting.packet_loss = opus_cfg.packet_loss;
|
|
attr->setting.complexity = opus_cfg.complexity;
|
|
attr->setting.cbr = opus_cfg.cbr;
|
|
|
|
/* Set max RX frame size to 1275 (max Opus frame size) to anticipate
|
|
* possible ptime change on the fly.
|
|
*/
|
|
attr->info.max_rx_frame_size = 1275;
|
|
|
|
generate_fmtp(attr);
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Enum codecs supported by this factory.
|
|
*/
|
|
static pj_status_t factory_enum_codecs( pjmedia_codec_factory *factory,
|
|
unsigned *count,
|
|
pjmedia_codec_info codecs[] )
|
|
{
|
|
PJ_UNUSED_ARG(factory);
|
|
PJ_ASSERT_RETURN(codecs, PJ_EINVAL);
|
|
|
|
if (*count > 0) {
|
|
pj_bzero(&codecs[0], sizeof(pjmedia_codec_info));
|
|
codecs[0].type = PJMEDIA_TYPE_AUDIO;
|
|
codecs[0].pt = PJMEDIA_RTP_PT_OPUS;
|
|
/*
|
|
* RFC 7587, Section 7:
|
|
* The media subtype ("opus") goes in SDP "a=rtpmap" as the encoding
|
|
* name. The RTP clock rate in "a=rtpmap" MUST be 48000 and the
|
|
* number of channels MUST be 2.
|
|
*/
|
|
codecs[0].encoding_name = pj_str("opus");
|
|
codecs[0].clock_rate = 48000;
|
|
codecs[0].channel_cnt = 2;
|
|
*count = 1;
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Allocate a new Opus codec instance.
|
|
*/
|
|
static pj_status_t factory_alloc_codec( pjmedia_codec_factory *factory,
|
|
const pjmedia_codec_info *ci,
|
|
pjmedia_codec **p_codec )
|
|
{
|
|
pjmedia_codec *codec;
|
|
pj_pool_t *pool;
|
|
pj_status_t status;
|
|
struct opus_data *opus_data;
|
|
struct opus_codec_factory *f = (struct opus_codec_factory*) factory;
|
|
|
|
PJ_UNUSED_ARG(ci);
|
|
TRACE_((THIS_FILE, "%s:%d: - TRACE", __FUNCTION__, __LINE__));
|
|
|
|
pool = pjmedia_endpt_create_pool(f->endpt, "opus", 4000, 4000);
|
|
if (!pool) return PJ_ENOMEM;
|
|
|
|
opus_data = PJ_POOL_ZALLOC_T(pool, struct opus_data);
|
|
codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec);
|
|
|
|
status = pj_mutex_create_simple (pool, "opus_mutex", &opus_data->mutex);
|
|
if (status != PJ_SUCCESS) {
|
|
pj_pool_release(pool);
|
|
return status;
|
|
}
|
|
|
|
pj_memcpy(&opus_data->cfg, &opus_cfg, sizeof(pjmedia_codec_opus_config));
|
|
opus_data->pool = pool;
|
|
codec->op = &opus_op;
|
|
codec->factory = factory;
|
|
codec->codec_data = opus_data;
|
|
|
|
*p_codec = codec;
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Free codec.
|
|
*/
|
|
static pj_status_t factory_dealloc_codec( pjmedia_codec_factory *factory,
|
|
pjmedia_codec *codec )
|
|
{
|
|
struct opus_data *opus_data;
|
|
|
|
PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
|
|
PJ_ASSERT_RETURN(factory == &opus_codec_factory.base, PJ_EINVAL);
|
|
|
|
opus_data = (struct opus_data *)codec->codec_data;
|
|
if (opus_data) {
|
|
pj_mutex_destroy(opus_data->mutex);
|
|
opus_data->mutex = NULL;
|
|
pj_pool_release(opus_data->pool);
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Init codec.
|
|
*/
|
|
static pj_status_t codec_init( pjmedia_codec *codec,
|
|
pj_pool_t *pool )
|
|
{
|
|
PJ_UNUSED_ARG(codec);
|
|
PJ_UNUSED_ARG(pool);
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Open codec.
|
|
*/
|
|
static pj_status_t codec_open( pjmedia_codec *codec,
|
|
pjmedia_codec_param *attr )
|
|
{
|
|
struct opus_data *opus_data = (struct opus_data *)codec->codec_data;
|
|
int idx, err;
|
|
pj_bool_t auto_bit_rate = PJ_TRUE;
|
|
|
|
PJ_ASSERT_RETURN(codec && attr && opus_data, PJ_EINVAL);
|
|
|
|
pj_mutex_lock (opus_data->mutex);
|
|
|
|
TRACE_((THIS_FILE, "%s:%d: - TRACE", __FUNCTION__, __LINE__));
|
|
|
|
opus_data->cfg.sample_rate = attr->info.clock_rate;
|
|
opus_data->cfg.channel_cnt = attr->info.channel_cnt;
|
|
opus_data->enc_ptime = opus_data->dec_ptime = attr->info.frm_ptime;
|
|
opus_data->enc_ptime_denum = attr->info.frm_ptime_denum?
|
|
attr->info.frm_ptime_denum: 1;
|
|
opus_data->dec_ptime_denum = opus_data->enc_ptime_denum;
|
|
|
|
/* Allocate memory used by the codec */
|
|
if (!opus_data->enc) {
|
|
/* Allocate memory for max 2 channels */
|
|
opus_data->enc = pj_pool_zalloc(opus_data->pool,
|
|
opus_encoder_get_size(2));
|
|
}
|
|
if (!opus_data->dec) {
|
|
/* Allocate memory for max 2 channels */
|
|
opus_data->dec = pj_pool_zalloc(opus_data->pool,
|
|
opus_decoder_get_size(2));
|
|
}
|
|
if (!opus_data->enc_packer) {
|
|
opus_data->enc_packer = pj_pool_zalloc(opus_data->pool,
|
|
opus_repacketizer_get_size());
|
|
}
|
|
if (!opus_data->dec_packer) {
|
|
opus_data->dec_packer = pj_pool_zalloc(opus_data->pool,
|
|
opus_repacketizer_get_size());
|
|
}
|
|
if (!opus_data->enc || !opus_data->dec ||
|
|
!opus_data->enc_packer || !opus_data->dec_packer)
|
|
{
|
|
PJ_LOG(2, (THIS_FILE, "Unable to allocate memory for the codec"));
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
return PJ_ENOMEM;
|
|
}
|
|
|
|
/* Check max average bit rate */
|
|
idx = find_fmtp(&attr->setting.enc_fmtp, &STR_MAX_BIT_RATE, PJ_FALSE);
|
|
if (idx >= 0) {
|
|
unsigned rate;
|
|
auto_bit_rate = PJ_FALSE;
|
|
rate = (unsigned)pj_strtoul(&attr->setting.enc_fmtp.param[idx].val);
|
|
if (rate < attr->info.avg_bps)
|
|
attr->info.avg_bps = rate;
|
|
}
|
|
|
|
/* Check plc */
|
|
idx = find_fmtp(&attr->setting.enc_fmtp, &STR_INBAND_FEC, PJ_FALSE);
|
|
if (idx >= 0) {
|
|
unsigned plc;
|
|
plc = (unsigned) pj_strtoul(&attr->setting.enc_fmtp.param[idx].val);
|
|
attr->setting.plc = plc > 0? PJ_TRUE: PJ_FALSE;
|
|
}
|
|
|
|
/* Check vad */
|
|
idx = find_fmtp(&attr->setting.enc_fmtp, &STR_DTX, PJ_FALSE);
|
|
if (idx >= 0) {
|
|
unsigned vad;
|
|
vad = (unsigned) pj_strtoul(&attr->setting.enc_fmtp.param[idx].val);
|
|
attr->setting.vad = vad > 0? PJ_TRUE: PJ_FALSE;
|
|
}
|
|
|
|
/* Check cbr */
|
|
idx = find_fmtp(&attr->setting.enc_fmtp, &STR_CBR, PJ_FALSE);
|
|
if (idx >= 0) {
|
|
unsigned cbr;
|
|
cbr = (unsigned) pj_strtoul(&attr->setting.enc_fmtp.param[idx].val);
|
|
opus_data->cfg.cbr = cbr > 0? PJ_TRUE: PJ_FALSE;
|
|
}
|
|
|
|
/* Check max average bit rate */
|
|
idx = find_fmtp(&attr->setting.dec_fmtp, &STR_MAX_BIT_RATE, PJ_FALSE);
|
|
if (idx >= 0) {
|
|
unsigned rate;
|
|
rate = (unsigned) pj_strtoul(&attr->setting.dec_fmtp.param[idx].val);
|
|
if (rate < attr->info.avg_bps)
|
|
attr->info.avg_bps = rate;
|
|
}
|
|
|
|
TRACE_((THIS_FILE, "%s:%d: sample_rate: %u",
|
|
__FUNCTION__, __LINE__, opus_data->cfg.sample_rate));
|
|
|
|
/* Initialize encoder */
|
|
err = opus_encoder_init(opus_data->enc,
|
|
opus_data->cfg.sample_rate,
|
|
attr->info.channel_cnt,
|
|
OPUS_APPLICATION_VOIP);
|
|
if (err != OPUS_OK) {
|
|
PJ_LOG(2, (THIS_FILE, "Unable to create encoder: %s %d",
|
|
opus_strerror(err), err));
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
return PJMEDIA_CODEC_EFAILED;
|
|
}
|
|
|
|
/* Set signal type */
|
|
opus_encoder_ctl(opus_data->enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
|
|
/* Set bitrate */
|
|
opus_encoder_ctl(opus_data->enc, OPUS_SET_BITRATE(auto_bit_rate?
|
|
OPUS_AUTO:
|
|
(int)attr->info.avg_bps));
|
|
/* Set VAD */
|
|
opus_encoder_ctl(opus_data->enc, OPUS_SET_DTX(attr->setting.vad ? 1 : 0));
|
|
/* Set PLC */
|
|
opus_encoder_ctl(opus_data->enc,
|
|
OPUS_SET_INBAND_FEC(attr->setting.plc ? 1 : 0));
|
|
/* Set bandwidth */
|
|
opus_encoder_ctl(opus_data->enc,
|
|
OPUS_SET_MAX_BANDWIDTH(get_opus_bw_constant(
|
|
opus_data->cfg.sample_rate)));
|
|
/* Set expected packet loss */
|
|
opus_encoder_ctl(opus_data->enc,
|
|
OPUS_SET_PACKET_LOSS_PERC(opus_data->cfg.packet_loss));
|
|
/* Set complexity */
|
|
opus_encoder_ctl(opus_data->enc,
|
|
OPUS_SET_COMPLEXITY(opus_data->cfg.complexity));
|
|
/* Set constant bit rate */
|
|
opus_encoder_ctl(opus_data->enc,
|
|
OPUS_SET_VBR(opus_data->cfg.cbr ? 0 : 1));
|
|
|
|
PJ_LOG(4, (THIS_FILE, "Initialize Opus encoder, sample rate: %d, ch: %d, "
|
|
"avg bitrate: %d%s, vad: %d, plc: %d, pkt loss: %d, "
|
|
"complexity: %d, constant bit rate: %d, "
|
|
"ptime: %d/%d",
|
|
opus_data->cfg.sample_rate,
|
|
opus_data->cfg.channel_cnt,
|
|
(auto_bit_rate? 0: attr->info.avg_bps),
|
|
(auto_bit_rate? "(auto)": ""),
|
|
attr->setting.vad?1:0,
|
|
attr->setting.plc?1:0,
|
|
opus_data->cfg.packet_loss,
|
|
opus_data->cfg.complexity,
|
|
opus_data->cfg.cbr?1:0,
|
|
opus_data->enc_ptime,
|
|
opus_data->enc_ptime_denum));
|
|
|
|
/* Initialize decoder */
|
|
err = opus_decoder_init (opus_data->dec,
|
|
opus_data->cfg.sample_rate,
|
|
attr->info.channel_cnt);
|
|
if (err != OPUS_OK) {
|
|
PJ_LOG(2, (THIS_FILE, "Unable to initialize decoder: %s %d",
|
|
opus_strerror(err), err));
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
return PJMEDIA_CODEC_EFAILED;
|
|
}
|
|
|
|
/* Initialize temporary decode frames used for FEC */
|
|
opus_data->dec_frame[0].type = PJMEDIA_FRAME_TYPE_NONE;
|
|
opus_data->dec_frame[0].buf = pj_pool_zalloc(opus_data->pool,
|
|
(opus_data->cfg.sample_rate / 1000)
|
|
* 60 * attr->info.channel_cnt * 2 /* bytes per sample */);
|
|
opus_data->dec_frame[1].type = PJMEDIA_FRAME_TYPE_NONE;
|
|
opus_data->dec_frame[1].buf = pj_pool_zalloc(opus_data->pool,
|
|
(opus_data->cfg.sample_rate / 1000)
|
|
* 60 * attr->info.channel_cnt * 2 /* bytes per sample */);
|
|
opus_data->dec_frame_index = -1;
|
|
|
|
/* Initialize the repacketizers */
|
|
opus_repacketizer_init(opus_data->enc_packer);
|
|
opus_repacketizer_init(opus_data->dec_packer);
|
|
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Close codec.
|
|
*/
|
|
static pj_status_t codec_close( pjmedia_codec *codec )
|
|
{
|
|
PJ_UNUSED_ARG(codec);
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Modify codec settings.
|
|
*/
|
|
static pj_status_t codec_modify( pjmedia_codec *codec,
|
|
const pjmedia_codec_param *attr )
|
|
{
|
|
struct opus_data *opus_data = (struct opus_data *)codec->codec_data;
|
|
|
|
pj_mutex_lock (opus_data->mutex);
|
|
|
|
TRACE_((THIS_FILE, "%s:%d: - TRACE", __FUNCTION__, __LINE__));
|
|
|
|
/* Set encoder ptime */
|
|
opus_data->enc_ptime = attr->info.frm_ptime;
|
|
opus_data->enc_ptime_denum = attr->info.frm_ptime_denum?
|
|
attr->info.frm_ptime_denum: 1;
|
|
|
|
/* Set bitrate */
|
|
opus_data->cfg.bit_rate = attr->info.avg_bps;
|
|
opus_encoder_ctl(opus_data->enc, OPUS_SET_BITRATE(attr->info.avg_bps?
|
|
(int)attr->info.avg_bps:
|
|
OPUS_AUTO));
|
|
/* Set VAD */
|
|
opus_encoder_ctl(opus_data->enc, OPUS_SET_DTX(attr->setting.vad ? 1 : 0));
|
|
/* Set PLC */
|
|
opus_encoder_ctl(opus_data->enc,
|
|
OPUS_SET_INBAND_FEC(attr->setting.plc ? 1 : 0));
|
|
|
|
/* Set bandwidth */
|
|
opus_encoder_ctl(opus_data->enc,
|
|
OPUS_SET_MAX_BANDWIDTH(get_opus_bw_constant(
|
|
attr->info.clock_rate)));
|
|
/* Set expected packet loss */
|
|
opus_encoder_ctl(opus_data->enc,
|
|
OPUS_SET_PACKET_LOSS_PERC(attr->setting.packet_loss));
|
|
/* Set complexity */
|
|
opus_encoder_ctl(opus_data->enc,
|
|
OPUS_SET_COMPLEXITY(attr->setting.complexity));
|
|
/* Set constant bit rate */
|
|
opus_encoder_ctl(opus_data->enc,
|
|
OPUS_SET_VBR(attr->setting.cbr ? 0 : 1));
|
|
|
|
PJ_LOG(4, (THIS_FILE, "Modifying Opus encoder, sample rate: %d, ch: %d, "
|
|
"avg bitrate: %d%s, vad: %d, plc: %d, pkt loss: %d, "
|
|
"complexity: %d, constant bit rate: %d, "
|
|
"ptime: %d/%d ms",
|
|
attr->info.clock_rate,
|
|
attr->info.channel_cnt,
|
|
(attr->info.avg_bps? attr->info.avg_bps: 0),
|
|
(attr->info.avg_bps? "": "(auto)"),
|
|
attr->setting.vad?1:0,
|
|
attr->setting.plc?1:0,
|
|
attr->setting.packet_loss,
|
|
attr->setting.complexity,
|
|
attr->setting.cbr?1:0,
|
|
opus_data->enc_ptime,
|
|
opus_data->enc_ptime_denum));
|
|
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Get frames in the packet.
|
|
*/
|
|
static pj_status_t codec_parse( pjmedia_codec *codec,
|
|
void *pkt,
|
|
pj_size_t pkt_size,
|
|
const pj_timestamp *ts,
|
|
unsigned *frame_cnt,
|
|
pjmedia_frame frames[] )
|
|
{
|
|
struct opus_data *opus_data = (struct opus_data *)codec->codec_data;
|
|
unsigned char tmp_buf[MAX_ENCODED_PACKET_SIZE];
|
|
int i, num_frames;
|
|
int size, out_pos;
|
|
unsigned samples_per_frame = 0;
|
|
#if (USE_INCOMING_WORSE_SETTINGS)
|
|
int bw;
|
|
#endif
|
|
|
|
pj_mutex_lock (opus_data->mutex);
|
|
|
|
if (pkt_size > sizeof(tmp_buf)) {
|
|
PJ_LOG(5, (THIS_FILE, "Encoded size bigger than buffer"));
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
return PJMEDIA_CODEC_EFRMTOOSHORT;
|
|
}
|
|
|
|
pj_memcpy(tmp_buf, pkt, pkt_size);
|
|
|
|
opus_repacketizer_init(opus_data->dec_packer);
|
|
opus_repacketizer_cat(opus_data->dec_packer, tmp_buf, pkt_size);
|
|
|
|
num_frames = opus_repacketizer_get_nb_frames(opus_data->dec_packer);
|
|
if (num_frames == 0) {
|
|
PJ_LOG(2, (THIS_FILE, "No frames retrieved (num_frames = 0)"));
|
|
pj_mutex_unlock(opus_data->mutex);
|
|
return PJMEDIA_CODEC_EFAILED;
|
|
}
|
|
|
|
out_pos = 0;
|
|
for (i = 0; i < num_frames; ++i) {
|
|
size = opus_repacketizer_out_range(opus_data->dec_packer, i, i+1,
|
|
((unsigned char*)pkt) + out_pos,
|
|
sizeof(tmp_buf));
|
|
if (size < 0) {
|
|
PJ_LOG(5, (THIS_FILE, "Parse failed! (pkt_size=%lu, err=%d)",
|
|
(unsigned long)pkt_size, size));
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
return PJMEDIA_CODEC_EFAILED;
|
|
}
|
|
frames[i].type = PJMEDIA_FRAME_TYPE_AUDIO;
|
|
frames[i].buf = ((char*)pkt) + out_pos;
|
|
frames[i].size = size;
|
|
frames[i].bit_info = 0;
|
|
|
|
if (i == 0) {
|
|
int nsamples;
|
|
unsigned ptime;
|
|
unsigned ptime_denum = 1;
|
|
|
|
nsamples = opus_packet_get_nb_samples(frames[i].buf,
|
|
frames[i].size,
|
|
opus_data->cfg.sample_rate);
|
|
if (nsamples <= 0) {
|
|
PJ_LOG(5, (THIS_FILE, "Parse failed to get samples number! "
|
|
"(err=%d)", nsamples));
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
return PJMEDIA_CODEC_EFAILED;
|
|
}
|
|
|
|
if ((nsamples * 1000) % opus_data->cfg.sample_rate != 0) {
|
|
/* The only non-integer ptime that Opus supports is 2.5 ms */
|
|
ptime_denum = 2;
|
|
}
|
|
ptime = nsamples * ptime_denum * 1000 / opus_data->cfg.sample_rate;
|
|
|
|
if (ptime * opus_data->dec_ptime_denum !=
|
|
opus_data->dec_ptime * ptime_denum)
|
|
{
|
|
PJ_LOG(4, (THIS_FILE, "Opus ptime change detected: %d/%d ms "
|
|
"--> %d/%d ms",
|
|
opus_data->dec_ptime,
|
|
opus_data->dec_ptime_denum,
|
|
ptime, ptime_denum));
|
|
opus_data->dec_ptime = ptime;
|
|
opus_data->dec_ptime_denum = ptime_denum;
|
|
opus_data->dec_frame_index = -1;
|
|
|
|
/* Signal to the stream about ptime change. */
|
|
frames[i].bit_info = 0x10000 | nsamples;
|
|
}
|
|
samples_per_frame = nsamples;
|
|
}
|
|
|
|
frames[i].timestamp.u64 = ts->u64 + i * samples_per_frame;
|
|
out_pos += size;
|
|
}
|
|
*frame_cnt = num_frames;
|
|
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Encode frame.
|
|
*/
|
|
static pj_status_t codec_encode( pjmedia_codec *codec,
|
|
const struct pjmedia_frame *input,
|
|
unsigned output_buf_len,
|
|
struct pjmedia_frame *output )
|
|
{
|
|
struct opus_data *opus_data = (struct opus_data *)codec->codec_data;
|
|
opus_int32 size = 0;
|
|
unsigned in_pos = 0;
|
|
unsigned out_pos = 0;
|
|
unsigned frame_size;
|
|
unsigned samples_per_frame;
|
|
unsigned char tmp_buf[MAX_ENCODED_PACKET_SIZE];
|
|
unsigned tmp_bytes_left = sizeof(tmp_buf);
|
|
|
|
pj_mutex_lock (opus_data->mutex);
|
|
|
|
samples_per_frame = (opus_data->cfg.sample_rate *
|
|
opus_data->enc_ptime /
|
|
opus_data->enc_ptime_denum) / 1000;
|
|
frame_size = samples_per_frame * opus_data->cfg.channel_cnt *
|
|
sizeof(opus_int16);
|
|
|
|
opus_repacketizer_init(opus_data->enc_packer);
|
|
while (input->size - in_pos >= frame_size) {
|
|
size = opus_encode(opus_data->enc,
|
|
(const opus_int16*)(((char*)input->buf) + in_pos),
|
|
samples_per_frame,
|
|
tmp_buf + out_pos,
|
|
(tmp_bytes_left < frame_size ?
|
|
tmp_bytes_left : frame_size));
|
|
if (size < 0) {
|
|
PJ_LOG(4, (THIS_FILE, "Encode failed! (%d)", size));
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
return PJMEDIA_CODEC_EFAILED;
|
|
} else if (size > 0) {
|
|
/* Only add packets containing more than the TOC */
|
|
opus_repacketizer_cat(opus_data->enc_packer,
|
|
tmp_buf + out_pos,
|
|
size);
|
|
out_pos += size;
|
|
tmp_bytes_left -= size;
|
|
}
|
|
in_pos += frame_size;
|
|
}
|
|
|
|
if (!opus_repacketizer_get_nb_frames(opus_data->enc_packer)) {
|
|
/* Empty packet */
|
|
output->size = 0;
|
|
output->type = PJMEDIA_FRAME_TYPE_NONE;
|
|
output->timestamp = input->timestamp;
|
|
}
|
|
|
|
if (size) {
|
|
size = opus_repacketizer_out(opus_data->enc_packer,
|
|
output->buf,
|
|
output_buf_len);
|
|
if (size < 0) {
|
|
PJ_LOG(4, (THIS_FILE, "Encode failed! (%d), out_size: %u",
|
|
size, output_buf_len));
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
return PJMEDIA_CODEC_EFAILED;
|
|
}
|
|
}
|
|
|
|
output->size = (unsigned)size;
|
|
output->type = PJMEDIA_FRAME_TYPE_AUDIO;
|
|
output->timestamp = input->timestamp;
|
|
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Decode frame.
|
|
*/
|
|
static pj_status_t codec_decode( pjmedia_codec *codec,
|
|
const struct pjmedia_frame *input,
|
|
unsigned output_buf_len,
|
|
struct pjmedia_frame *output )
|
|
{
|
|
struct opus_data *opus_data = (struct opus_data *)codec->codec_data;
|
|
int decoded_samples;
|
|
pjmedia_frame *inframe;
|
|
int fec = 0;
|
|
int frm_size;
|
|
|
|
PJ_UNUSED_ARG(output_buf_len);
|
|
|
|
pj_mutex_lock (opus_data->mutex);
|
|
|
|
if (opus_data->dec_frame_index == -1) {
|
|
/* First packet, buffer it. */
|
|
opus_data->dec_frame[0].type = input->type;
|
|
opus_data->dec_frame[0].size = input->size;
|
|
opus_data->dec_frame[0].timestamp = input->timestamp;
|
|
pj_memcpy(opus_data->dec_frame[0].buf, input->buf, input->size);
|
|
opus_data->dec_frame_index = 0;
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
|
|
/* Return zero decoded bytes */
|
|
output->size = 0;
|
|
output->type = PJMEDIA_FRAME_TYPE_NONE;
|
|
output->timestamp = input->timestamp;
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
inframe = &opus_data->dec_frame[opus_data->dec_frame_index];
|
|
|
|
if (inframe->type != PJMEDIA_FRAME_TYPE_AUDIO) {
|
|
/* Update current frame index */
|
|
opus_data->dec_frame_index++;
|
|
if (opus_data->dec_frame_index > 1)
|
|
opus_data->dec_frame_index = 0;
|
|
/* Copy original input buffer to current indexed frame */
|
|
inframe = &opus_data->dec_frame[opus_data->dec_frame_index];
|
|
inframe->type = input->type;
|
|
inframe->size = input->size;
|
|
inframe->timestamp = input->timestamp;
|
|
pj_memcpy(inframe->buf, input->buf, input->size);
|
|
fec = 1;
|
|
}
|
|
|
|
/* From Opus doc: In the case of PLC (data==NULL) or FEC(decode_fec=1),
|
|
* then frame_size needs to be exactly the duration of audio that
|
|
* is missing.
|
|
*/
|
|
frm_size = output->size / (sizeof(opus_int16) *
|
|
opus_data->cfg.channel_cnt);
|
|
if (inframe->type != PJMEDIA_FRAME_TYPE_AUDIO || fec) {
|
|
frm_size = PJ_MIN((unsigned)frm_size,
|
|
opus_data->cfg.sample_rate *
|
|
opus_data->dec_ptime /
|
|
opus_data->dec_ptime_denum / 1000);
|
|
}
|
|
decoded_samples = opus_decode( opus_data->dec,
|
|
inframe->type==PJMEDIA_FRAME_TYPE_AUDIO ?
|
|
inframe->buf : NULL,
|
|
inframe->type==PJMEDIA_FRAME_TYPE_AUDIO ?
|
|
inframe->size : 0,
|
|
(opus_int16*)output->buf,
|
|
frm_size,
|
|
fec);
|
|
output->timestamp = inframe->timestamp;
|
|
|
|
if (inframe->type == PJMEDIA_FRAME_TYPE_AUDIO) {
|
|
/* Mark current indexed frame as invalid */
|
|
inframe->type = PJMEDIA_FRAME_TYPE_NONE;
|
|
/* Update current frame index */
|
|
opus_data->dec_frame_index++;
|
|
if (opus_data->dec_frame_index > 1)
|
|
opus_data->dec_frame_index = 0;
|
|
/* Copy original input buffer to current indexed frame */
|
|
inframe = &opus_data->dec_frame[opus_data->dec_frame_index];
|
|
inframe->type = input->type;
|
|
inframe->size = input->size;
|
|
inframe->timestamp = input->timestamp;
|
|
pj_memcpy(inframe->buf, input->buf, input->size);
|
|
}
|
|
|
|
if (decoded_samples < 0) {
|
|
PJ_LOG(4, (THIS_FILE, "Decode failed!"));
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
return PJMEDIA_CODEC_EFAILED;
|
|
}
|
|
|
|
output->size = decoded_samples * sizeof(opus_int16) *
|
|
opus_data->cfg.channel_cnt;
|
|
output->type = PJMEDIA_FRAME_TYPE_AUDIO;
|
|
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Recover lost frame.
|
|
*/
|
|
static pj_status_t codec_recover( pjmedia_codec *codec,
|
|
unsigned output_buf_len,
|
|
struct pjmedia_frame *output )
|
|
{
|
|
struct opus_data *opus_data = (struct opus_data *)codec->codec_data;
|
|
int decoded_samples;
|
|
pjmedia_frame *inframe;
|
|
int frm_size;
|
|
|
|
PJ_UNUSED_ARG(output_buf_len);
|
|
pj_mutex_lock (opus_data->mutex);
|
|
|
|
if (opus_data->dec_frame_index == -1) {
|
|
/* Recover the first packet? Don't think so, fill it with zeroes. */
|
|
unsigned samples_per_frame;
|
|
samples_per_frame = opus_data->cfg.sample_rate * opus_data->dec_ptime/
|
|
opus_data->dec_ptime_denum / 1000;
|
|
output->type = PJMEDIA_FRAME_TYPE_AUDIO;
|
|
output->size = samples_per_frame << 1;
|
|
pjmedia_zero_samples((pj_int16_t*)output->buf, samples_per_frame);
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
inframe = &opus_data->dec_frame[opus_data->dec_frame_index];
|
|
frm_size = output->size / (sizeof(opus_int16) *
|
|
opus_data->cfg.channel_cnt);
|
|
if (inframe->type != PJMEDIA_FRAME_TYPE_AUDIO) {
|
|
frm_size = PJ_MIN((unsigned)frm_size, opus_data->cfg.sample_rate *
|
|
opus_data->dec_ptime / opus_data->dec_ptime_denum /
|
|
1000);
|
|
}
|
|
decoded_samples = opus_decode(opus_data->dec,
|
|
inframe->type==PJMEDIA_FRAME_TYPE_AUDIO ?
|
|
inframe->buf : NULL,
|
|
inframe->type==PJMEDIA_FRAME_TYPE_AUDIO ?
|
|
inframe->size : 0,
|
|
(opus_int16*)output->buf,
|
|
frm_size,
|
|
0);
|
|
|
|
/* Mark current indexed frame as invalid */
|
|
inframe->type = PJMEDIA_FRAME_TYPE_NONE;
|
|
|
|
/* Update current frame index */
|
|
opus_data->dec_frame_index++;
|
|
if (opus_data->dec_frame_index > 1)
|
|
opus_data->dec_frame_index = 0;
|
|
/* Mark current indexed frame as invalid */
|
|
inframe = &opus_data->dec_frame[opus_data->dec_frame_index];
|
|
inframe->type = PJMEDIA_FRAME_TYPE_NONE;
|
|
|
|
if (decoded_samples < 0) {
|
|
PJ_LOG(4, (THIS_FILE, "Recover failed!"));
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
return PJMEDIA_CODEC_EFAILED;
|
|
}
|
|
|
|
output->size = decoded_samples * sizeof(opus_int16) *
|
|
opus_data->cfg.channel_cnt;
|
|
output->type = PJMEDIA_FRAME_TYPE_AUDIO;
|
|
output->timestamp = inframe->timestamp;
|
|
|
|
pj_mutex_unlock (opus_data->mutex);
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
#if defined(_MSC_VER)
|
|
# pragma comment(lib, "libopus.a")
|
|
#endif
|
|
|
|
|
|
#endif /* PJMEDIA_HAS_OPUS_CODEC */
|