/* $Id$ */
/*
 * 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

/* 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			 dec_ptime;
    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 time 		*/			
    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;
    param->info.frm_ptime = (pj_uint16_t)cfg->frm_ptime;

    /* 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->setting.frm_per_pkt 	   = 1;
    attr->info.pcm_bits_per_sample = 16;
    attr->setting.vad      	   = OPUS_DEFAULT_VAD;
    attr->setting.plc      	   = OPUS_DEFAULT_PLC;

    /* 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", 512, 512);
    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;

    /* 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"));
	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:
    						      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(5, (THIS_FILE, "Initialize Opus encoder, sample rate: %d, "
    			  "avg bitrate: %d, vad: %d, plc: %d, pkt loss: %d, "
    			  "complexity: %d, constant bit rate: %d",
               		  opus_data->cfg.sample_rate,
               		  attr->info.avg_bps, 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));

    /* 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"));
	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 bitrate */
    opus_data->cfg.bit_rate = attr->info.avg_bps;
    opus_encoder_ctl(opus_data->enc, OPUS_SET_BITRATE(attr->info.avg_bps?
    						      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));

    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);
    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! (%d)", pkt_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 = opus_packet_get_nb_samples(frames[i].buf,
			     frames[i].size, opus_data->cfg.sample_rate);

	if (i == 0) {
    	    unsigned ptime = frames[i].bit_info * 1000 /
    	    		     opus_data->cfg.sample_rate;
    	    if (ptime != opus_data->dec_ptime) {
             	PJ_LOG(4, (THIS_FILE, "Opus ptime change detected: %d ms "
             			      "--> %d ms",
             			      opus_data->dec_ptime, ptime));
        	opus_data->dec_ptime = ptime;
        	opus_data->dec_frame_index = -1;

        	/* Signal to the stream about ptime change. */
     	    	frames[i].bit_info |= 0x10000;
    	    }
     	    samples_per_frame = frames[i].bit_info;
   	}

	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) / 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 / 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/
			    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/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 */
