/*
 * 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;
    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", 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"));
        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:
                                                      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, "
                          "avg bitrate: %d%s, vad: %d, plc: %d, pkt loss: %d, "
                          "complexity: %d, constant bit rate: %d",
                          opus_data->cfg.sample_rate,
                          (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));

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

    /* 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, "
                          "avg bitrate: %d%s, vad: %d, plc: %d, pkt loss: %d, "
                          "complexity: %d, constant bit rate: %d",
                          attr->info.clock_rate,
                          (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));

    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=%d, err=%d)",
                       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;

            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;
            }

            ptime = nsamples * 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 | 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) / 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 */
