/* 
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * 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 
 */
#include <pjmedia-codec/passthrough.h>
#include <pjmedia-codec/amr_sdp_match.h>
#include <pjmedia/codec.h>
#include <pjmedia/errno.h>
#include <pjmedia/endpoint.h>
#include <pjmedia/port.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/math.h>
#include <pj/pool.h>
#include <pj/string.h>
#include <pj/os.h>

/*
 * Only build this file if PJMEDIA_HAS_PASSTHROUGH_CODECS != 0
 */
#if defined(PJMEDIA_HAS_PASSTHROUGH_CODECS) && PJMEDIA_HAS_PASSTHROUGH_CODECS!=0

#define THIS_FILE   "passthrough.c"


/* Prototypes for passthrough codecs factory */
static pj_status_t test_alloc( pjmedia_codec_factory *factory, 
                               const pjmedia_codec_info *id );
static pj_status_t default_attr( pjmedia_codec_factory *factory, 
                                 const pjmedia_codec_info *id, 
                                 pjmedia_codec_param *attr );
static pj_status_t enum_codecs( pjmedia_codec_factory *factory, 
                                unsigned *count, 
                                pjmedia_codec_info codecs[]);
static pj_status_t alloc_codec( pjmedia_codec_factory *factory, 
                                const pjmedia_codec_info *id, 
                                pjmedia_codec **p_codec);
static pj_status_t dealloc_codec( pjmedia_codec_factory *factory, 
                                  pjmedia_codec *codec );

/* Prototypes for passthrough codecs 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 passthrough codecs operations. */
static pjmedia_codec_op codec_op = 
{
    &codec_init,
    &codec_open,
    &codec_close,
    &codec_modify,
    &codec_parse,
    &codec_encode,
    &codec_decode,
    &codec_recover
};

/* Definition for passthrough codecs factory operations. */
static pjmedia_codec_factory_op codec_factory_op =
{
    &test_alloc,
    &default_attr,
    &enum_codecs,
    &alloc_codec,
    &dealloc_codec,
    &pjmedia_codec_passthrough_deinit
};

/* Passthrough codecs factory */
static struct codec_factory {
    pjmedia_codec_factory    base;
    pjmedia_endpt           *endpt;
    pj_pool_t               *pool;
    pj_mutex_t              *mutex;
} codec_factory;

/* Passthrough codecs private data. */
typedef struct codec_private {
    pj_pool_t           *pool;              /**< Pool for each instance.    */
    int                  codec_idx;         /**< Codec index.               */
    void                *codec_setting;     /**< Specific codec setting.    */
    pj_uint16_t          avg_frame_size;    /**< Average of frame size.     */
    unsigned             samples_per_frame; /**< Samples per frame, for iLBC
                                                 this can be 240 or 160, can
                                                 only be known after codec is
                                                 opened.                    */
} codec_private_t;



/* CUSTOM CALLBACKS */

/* Parse frames from a packet. Default behaviour of frame parsing is 
 * just separating frames based on calculating frame length derived 
 * from bitrate. Implement this callback when the default behaviour is 
 * unapplicable.
 */
typedef pj_status_t (*parse_cb)(codec_private_t *codec_data, void *pkt, 
                                pj_size_t pkt_size, const pj_timestamp *ts,
                                unsigned *frame_cnt, pjmedia_frame frames[]);

/* Pack frames into a packet. Default behaviour of packing frames is 
 * just stacking the frames with octet aligned without adding any 
 * payload header. Implement this callback when the default behaviour is
 * unapplicable.
 */
typedef pj_status_t (*pack_cb)(codec_private_t *codec_data, 
                               const struct pjmedia_frame_ext *input,
                               unsigned output_buf_len, 
                               struct pjmedia_frame *output);


/* Custom callback implementations. */
static pj_status_t parse_amr( codec_private_t *codec_data, void *pkt, 
                              pj_size_t pkt_size, const pj_timestamp *ts,
                              unsigned *frame_cnt, pjmedia_frame frames[]);
static pj_status_t pack_amr ( codec_private_t *codec_data,
                              const struct pjmedia_frame_ext *input,
                              unsigned output_buf_len, 
                              struct pjmedia_frame *output);


/* Passthrough codec implementation descriptions. */
static struct codec_desc {
    int              enabled;           /* Is this codec enabled?           */
    const char      *name;              /* Codec name.                      */
    pj_uint8_t       pt;                /* Payload type.                    */
    pjmedia_format_id fmt_id;           /* Source format.                   */
    unsigned         clock_rate;        /* Codec's clock rate.              */
    unsigned         channel_count;     /* Codec's channel count.           */
    unsigned         samples_per_frame; /* Codec's samples count.           */
    unsigned         def_bitrate;       /* Default bitrate of this codec.   */
    unsigned         max_bitrate;       /* Maximum bitrate of this codec.   */
    pj_uint8_t       frm_per_pkt;       /* Default num of frames per packet.*/
    pj_uint8_t       vad;               /* VAD enabled/disabled.            */
    pj_uint8_t       plc;               /* PLC enabled/disabled.            */
    parse_cb         parse;             /* Callback to parse bitstream.     */
    pack_cb          pack;              /* Callback to pack bitstream.      */
    pjmedia_codec_fmtp dec_fmtp;        /* Decoder's fmtp params.           */
}

codec_desc[] = 
{
#   if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
    {1, "AMR",      PJMEDIA_RTP_PT_AMR,       PJMEDIA_FORMAT_AMR,
                    8000, 1, 160, 
                    7400, 12200, 2, 1, 1,
                    &parse_amr, &pack_amr
                    /*, {1, {{{"octet-align", 11}, {"1", 1}}} } */
    },
#   endif

#   if PJMEDIA_HAS_PASSTHROUGH_CODEC_G722
    {1, "G722",     PJMEDIA_RTP_PT_G722,      PJMEDIA_FORMAT_G722,
                    16000, 1,  160,
                    64000, 64000, 2, 1, 1
    },
#   endif

#   if PJMEDIA_HAS_PASSTHROUGH_CODEC_G729
    {1, "G729",     PJMEDIA_RTP_PT_G729,      PJMEDIA_FORMAT_G729,
                    8000, 1,  80,
                    8000, 8000, 2, 1, 1
    },
#   endif

#   if PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC
    {1, "iLBC",     PJMEDIA_RTP_PT_ILBC,      PJMEDIA_FORMAT_ILBC,
                    8000, 1,  240,
                    13333, 15200, 1, 1, 1,
                    NULL, NULL,
                    {1, {{{"mode", 4}, {"30", 2}}} }
    },
#   endif

#   if PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU
    {1, "PCMU",     PJMEDIA_RTP_PT_PCMU,      PJMEDIA_FORMAT_PCMU,
                    8000, 1,  80,
                    64000, 64000, 2, 1, 1
    },
#   endif

#   if PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA
    {1, "PCMA",     PJMEDIA_RTP_PT_PCMA,      PJMEDIA_FORMAT_PCMA,
                    8000, 1,  80,
                    64000, 64000, 2, 1, 1
    },
#   endif
};


#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR

#include <pjmedia-codec/amr_helper.h>

typedef struct amr_settings_t {
    pjmedia_codec_amr_pack_setting enc_setting;
    pjmedia_codec_amr_pack_setting dec_setting;
    pj_int8_t enc_mode;
} amr_settings_t;


/* Pack AMR payload */
static pj_status_t pack_amr ( codec_private_t *codec_data,
                              const struct pjmedia_frame_ext *input,
                              unsigned output_buf_len, 
                              struct pjmedia_frame *output)
{
    enum {MAX_FRAMES_PER_PACKET = PJMEDIA_MAX_FRAME_DURATION_MS / 20};

    pjmedia_frame frames[MAX_FRAMES_PER_PACKET];
    amr_settings_t* setting = (amr_settings_t*)codec_data->codec_setting;
    pjmedia_codec_amr_pack_setting *enc_setting = &setting->enc_setting;
    pj_uint8_t SID_FT;
    unsigned i;

    pj_assert(input->subframe_cnt <= MAX_FRAMES_PER_PACKET);

    SID_FT = (pj_uint8_t)(enc_setting->amr_nb? 8 : 9);

    /* Get frames */
    for (i = 0; i < input->subframe_cnt; ++i) {
        pjmedia_frame_ext_subframe *sf;
        pjmedia_codec_amr_bit_info *info;
        unsigned len;
        
        sf = pjmedia_frame_ext_get_subframe(input, i);
        len = (sf->bitlen + 7) >> 3;
        
        info = (pjmedia_codec_amr_bit_info*) &frames[i].bit_info;
        pj_bzero(info, sizeof(*info));
        
        if (len == 0) {
            /* DTX */
            info->frame_type = 15;
        } else {
            info->frame_type = pjmedia_codec_amr_get_mode2(enc_setting->amr_nb, 
                                                           len);
        }
        info->good_quality = 1;
        info->mode = setting->enc_mode;
        if (info->frame_type == SID_FT)
            info->STI = (sf->data[4] >> 4) & 1;

        frames[i].buf = sf->data;
        frames[i].size = len;
    }

    output->size = output_buf_len;

    return pjmedia_codec_amr_pack(frames, input->subframe_cnt, enc_setting, 
                                  output->buf, &output->size);
}


/* Parse AMR payload into frames. */
static pj_status_t parse_amr(codec_private_t *codec_data, void *pkt, 
                             pj_size_t pkt_size, const pj_timestamp *ts,
                             unsigned *frame_cnt, pjmedia_frame frames[])
{
    amr_settings_t* s = (amr_settings_t*)codec_data->codec_setting;
    pjmedia_codec_amr_pack_setting *setting;
    pj_status_t status;
    pj_uint8_t cmr;

    setting = &s->dec_setting;

    status = pjmedia_codec_amr_parse(pkt, pkt_size, ts, setting, frames, 
                                     frame_cnt, &cmr);
    if (status != PJ_SUCCESS)
        return status;

    // CMR is not supported for now. 
    /* Check Change Mode Request. */
    //if ((setting->amr_nb && cmr <= 7) || (!setting->amr_nb && cmr <= 8)) {
    //  s->enc_mode = cmr;
    //}

    return PJ_SUCCESS;
}

#endif /* PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR */


/*
 * Initialize and register passthrough codec factory to pjmedia endpoint.
 */
PJ_DEF(pj_status_t) pjmedia_codec_passthrough_init( pjmedia_endpt *endpt )
{
    pjmedia_codec_mgr *codec_mgr;
    pj_str_t codec_name;
    pj_status_t status;

    if (codec_factory.pool != NULL) {
        /* Already initialized. */
        return PJ_EEXISTS;
    }

    /* Create passthrough codec factory. */
    codec_factory.base.op = &codec_factory_op;
    codec_factory.base.factory_data = NULL;
    codec_factory.endpt = endpt;

    codec_factory.pool = pjmedia_endpt_create_pool(endpt, "Passthrough codecs",
                                                   4000, 4000);
    if (!codec_factory.pool)
        return PJ_ENOMEM;

    /* Create mutex. */
    status = pj_mutex_create_simple(codec_factory.pool, "Passthrough codecs",
                                    &codec_factory.mutex);
    if (status != PJ_SUCCESS)
        goto on_error;

    /* Get the codec manager. */
    codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
    if (!codec_mgr) {
        status = PJ_EINVALIDOP;
        goto on_error;
    }

    /* Register format match callback. */
#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
    pj_cstr(&codec_name, "AMR");
    status = pjmedia_sdp_neg_register_fmt_match_cb(
                                        &codec_name,
                                        &pjmedia_codec_amr_match_sdp);
    if (status != PJ_SUCCESS)
        goto on_error;
#endif

    /* Suppress compile warning */
    PJ_UNUSED_ARG(codec_name);

    /* Register codec factory to endpoint. */
    status = pjmedia_codec_mgr_register_factory(codec_mgr, 
                                                &codec_factory.base);
    if (status != PJ_SUCCESS)
        goto on_error;

    /* Done. */
    return PJ_SUCCESS;

on_error:
    pj_pool_release(codec_factory.pool);
    codec_factory.pool = NULL;
    return status;
}

/*
 * Initialize and register passthrough codec factory to pjmedia endpoint.
 */
PJ_DEF(pj_status_t) pjmedia_codec_passthrough_init2( 
                      pjmedia_endpt *endpt,
                      const pjmedia_codec_passthrough_setting *setting)
{
    if (codec_factory.pool != NULL) {
        /* Already initialized. */
        return PJ_EEXISTS;
    }

    if (setting != NULL) {
        unsigned i;

        /* Enable/disable codecs based on the specified encoding formats */
        for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
            pj_bool_t enabled = PJ_FALSE;
            unsigned j;

            for (j = 0; j < setting->fmt_cnt && !enabled; ++j) {
                if ((pj_uint32_t)codec_desc[i].fmt_id == setting->fmts[j].id)
                    enabled = PJ_TRUE;
            }

            codec_desc[i].enabled = enabled;
        }

#if PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC
        /* Update iLBC codec description based on default mode setting. */
        for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
            if (codec_desc[i].enabled && 
                codec_desc[i].fmt_id == PJMEDIA_FORMAT_ILBC)
            {
                codec_desc[i].samples_per_frame = 
                                (setting->ilbc_mode == 20? 160 : 240);
                codec_desc[i].def_bitrate = 
                                (setting->ilbc_mode == 20? 15200 : 13333);
                pj_strset2(&codec_desc[i].dec_fmtp.param[0].val, 
                                (setting->ilbc_mode == 20? "20" : "30"));
                break;
            }
        }
#endif
    }

    return pjmedia_codec_passthrough_init(endpt);
}

/*
 * Unregister passthrough codecs factory from pjmedia endpoint.
 */
PJ_DEF(pj_status_t) pjmedia_codec_passthrough_deinit(void)
{
    pjmedia_codec_mgr *codec_mgr;
    unsigned i;
    pj_status_t status;

    if (codec_factory.pool == NULL) {
        /* Already deinitialized */
        return PJ_SUCCESS;
    }

    pj_mutex_lock(codec_factory.mutex);

    /* Get the codec manager. */
    codec_mgr = pjmedia_endpt_get_codec_mgr(codec_factory.endpt);
    if (!codec_mgr) {
        pj_pool_release(codec_factory.pool);
        codec_factory.pool = NULL;
        pj_mutex_unlock(codec_factory.mutex);
        return PJ_EINVALIDOP;
    }

    /* Release the factory mutex before calling unregister to avoid lock
     * order inversion with codec manager mutex.
     */
    pj_mutex_unlock(codec_factory.mutex);

    /* Unregister passthrough codecs factory. */
    status = pjmedia_codec_mgr_unregister_factory(codec_mgr,
                                                  &codec_factory.base);
    
    /* Destroy mutex. */
    pj_mutex_destroy(codec_factory.mutex);
    codec_factory.mutex = NULL;

    /* Destroy pool. */
    pj_pool_release(codec_factory.pool);
    codec_factory.pool = NULL;

    /* Re-enable all codecs in the codec_desc. */
    for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
        codec_desc[i].enabled = PJ_TRUE;
    }

    return status;
}

/* 
 * Check if factory can allocate the specified codec. 
 */
static pj_status_t test_alloc( pjmedia_codec_factory *factory, 
                               const pjmedia_codec_info *info )
{
    unsigned i;

    PJ_UNUSED_ARG(factory);

    /* Type MUST be audio. */
    if (info->type != PJMEDIA_TYPE_AUDIO)
        return PJMEDIA_CODEC_EUNSUP;

    for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
        pj_str_t name = pj_str((char*)codec_desc[i].name);
        if ((pj_stricmp(&info->encoding_name, &name) == 0) &&
            (info->clock_rate == (unsigned)codec_desc[i].clock_rate) &&
            (info->channel_cnt == (unsigned)codec_desc[i].channel_count) &&
            (codec_desc[i].enabled))
        {
            return PJ_SUCCESS;
        }
    }
    
    /* Unsupported, or mode is disabled. */
    return PJMEDIA_CODEC_EUNSUP;
}

/*
 * Generate default attribute.
 */
static pj_status_t default_attr ( pjmedia_codec_factory *factory, 
                                  const pjmedia_codec_info *id, 
                                  pjmedia_codec_param *attr )
{
    unsigned i;

    PJ_ASSERT_RETURN(factory==&codec_factory.base, PJ_EINVAL);

    pj_bzero(attr, sizeof(pjmedia_codec_param));

    for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
        pj_str_t name = pj_str((char*)codec_desc[i].name);
        if ((pj_stricmp(&id->encoding_name, &name) == 0) &&
            (id->clock_rate == (unsigned)codec_desc[i].clock_rate) &&
            (id->channel_cnt == (unsigned)codec_desc[i].channel_count) &&
            (id->pt == (unsigned)codec_desc[i].pt))
        {
            attr->info.pt = (pj_uint8_t)id->pt;
            attr->info.channel_cnt = codec_desc[i].channel_count;
            attr->info.clock_rate = codec_desc[i].clock_rate;
            attr->info.avg_bps = codec_desc[i].def_bitrate;
            attr->info.max_bps = codec_desc[i].max_bitrate;
            attr->info.pcm_bits_per_sample = 16;
            attr->info.frm_ptime =  (pj_uint16_t)
                                    (codec_desc[i].samples_per_frame * 1000 / 
                                    codec_desc[i].channel_count / 
                                    codec_desc[i].clock_rate);
            attr->info.fmt_id = codec_desc[i].fmt_id;

            /* Default flags. */
            attr->setting.frm_per_pkt = codec_desc[i].frm_per_pkt;
            attr->setting.plc = codec_desc[i].plc;
            attr->setting.penh= 0;
            attr->setting.vad = codec_desc[i].vad;
            attr->setting.cng = attr->setting.vad;
            attr->setting.dec_fmtp = codec_desc[i].dec_fmtp;

            if (attr->setting.vad == 0) {
#if PJMEDIA_HAS_PASSTHROUGH_CODEC_G729
                if (id->pt == PJMEDIA_RTP_PT_G729) {
                    /* Signal G729 Annex B is being disabled */
                    attr->setting.dec_fmtp.cnt = 1;
                    pj_strset2(&attr->setting.dec_fmtp.param[0].name, "annexb");
                    pj_strset2(&attr->setting.dec_fmtp.param[0].val, "no");
                }
#endif
            }

            return PJ_SUCCESS;
        }
    }

    return PJMEDIA_CODEC_EUNSUP;
}

/*
 * Enum codecs supported by this factory.
 */
static pj_status_t enum_codecs( pjmedia_codec_factory *factory, 
                                unsigned *count, 
                                pjmedia_codec_info codecs[])
{
    unsigned max;
    unsigned i;

    PJ_UNUSED_ARG(factory);
    PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);

    max = *count;
    
    for (i = 0, *count = 0; i < PJ_ARRAY_SIZE(codec_desc) && *count < max; ++i) 
    {
        if (!codec_desc[i].enabled)
            continue;

        pj_bzero(&codecs[*count], sizeof(pjmedia_codec_info));
        codecs[*count].encoding_name = pj_str((char*)codec_desc[i].name);
        codecs[*count].pt = codec_desc[i].pt;
        codecs[*count].type = PJMEDIA_TYPE_AUDIO;
        codecs[*count].clock_rate = codec_desc[i].clock_rate;
        codecs[*count].channel_cnt = codec_desc[i].channel_count;

        ++*count;
    }

    return PJ_SUCCESS;
}

/*
 * Allocate a new codec instance.
 */
static pj_status_t alloc_codec( pjmedia_codec_factory *factory, 
                                const pjmedia_codec_info *id,
                                pjmedia_codec **p_codec)
{
    codec_private_t *codec_data;
    pjmedia_codec *codec;
    int idx;
    pj_pool_t *pool;
    unsigned i;

    PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL);
    PJ_ASSERT_RETURN(factory == &codec_factory.base, PJ_EINVAL);

    pj_mutex_lock(codec_factory.mutex);

    /* Find codec's index */
    idx = -1;
    for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
        pj_str_t name = pj_str((char*)codec_desc[i].name);
        if ((pj_stricmp(&id->encoding_name, &name) == 0) &&
            (id->clock_rate == (unsigned)codec_desc[i].clock_rate) &&
            (id->channel_cnt == (unsigned)codec_desc[i].channel_count) &&
            (codec_desc[i].enabled))
        {
            idx = i;
            break;
        }
    }
    if (idx == -1) {
        *p_codec = NULL;
        pj_mutex_unlock(codec_factory.mutex);
        return PJMEDIA_CODEC_EUNSUP;
    }

    /* Create pool for codec instance */
    pool = pjmedia_endpt_create_pool(codec_factory.endpt, "passthroughcodec",
                                     512, 512);
    codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec);
    codec->op = &codec_op;
    codec->factory = factory;
    codec->codec_data = PJ_POOL_ZALLOC_T(pool, codec_private_t);
    codec_data = (codec_private_t*) codec->codec_data;
    codec_data->pool = pool;
    codec_data->codec_idx = idx;

    pj_mutex_unlock(codec_factory.mutex);

    *p_codec = codec;
    return PJ_SUCCESS;
}

/*
 * Free codec.
 */
static pj_status_t dealloc_codec( pjmedia_codec_factory *factory, 
                                  pjmedia_codec *codec )
{
    codec_private_t *codec_data;

    PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
    PJ_ASSERT_RETURN(factory == &codec_factory.base, PJ_EINVAL);

    /* Close codec, if it's not closed. */
    codec_data = (codec_private_t*) codec->codec_data;
    codec_close(codec);

    pj_pool_release(codec_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 )
{
    codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
    struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
    int i, j;

    /* Cache samples per frame value */
    codec_data->samples_per_frame = desc->samples_per_frame;

    /* Calculate bitstream size */
    i = attr->info.avg_bps * codec_data->samples_per_frame;
    j = desc->clock_rate << 3;
    codec_data->avg_frame_size = (pj_uint16_t)(i / j);
    if (i % j) ++codec_data->avg_frame_size;

#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
    /* Init AMR settings */
    if (desc->pt == PJMEDIA_RTP_PT_AMR || desc->pt == PJMEDIA_RTP_PT_AMRWB) {
        amr_settings_t *s;
        pj_uint8_t octet_align = 0;
        pj_int8_t enc_mode;
        
        enc_mode = pjmedia_codec_amr_get_mode(attr->info.avg_bps);
        pj_assert(enc_mode >= 0 && enc_mode <= 8);

        for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) {
            const pj_str_t STR_FMTP_OCTET_ALIGN = {"octet-align", 11};
            
            /* Fetch octet-align setting. It should be fine to fetch only 
             * the decoder, since encoder & decoder must use the same setting 
             * (RFC 4867 section 8.3.1).
             */
            if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name, 
                           &STR_FMTP_OCTET_ALIGN) == 0)
            {
                octet_align=(pj_uint8_t)
                            (pj_strtoul(&attr->setting.dec_fmtp.param[i].val));
                break;
            }
        }

        for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) {
            const pj_str_t STR_FMTP_MODE_SET = {"mode-set", 8};

            /* mode-set, encoding mode is chosen based on local default mode 
             * setting:
             * - if local default mode is included in the mode-set, use it
             * - otherwise, find the closest mode to local default mode;
             *   if there are two closest modes, prefer to use the higher
             *   one, e.g: local default mode is 4, the mode-set param
             *   contains '2,3,5,6', then 5 will be chosen.
             */
            if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name, 
                           &STR_FMTP_MODE_SET) == 0)
            {
                const char *p;
                pj_size_t l;
                pj_int8_t diff = 99;
                
                p = pj_strbuf(&attr->setting.enc_fmtp.param[i].val);
                l = pj_strlen(&attr->setting.enc_fmtp.param[i].val);

                while (l--) {
                    if ((desc->pt==PJMEDIA_RTP_PT_AMR && *p>='0' && *p<='7') ||
                        (desc->pt==PJMEDIA_RTP_PT_AMRWB && *p>='0' && *p<='8'))
                    {
                        pj_int8_t tmp = (pj_int8_t)(*p - '0' - enc_mode);

                        if (PJ_ABS(diff) > PJ_ABS(tmp) || 
                            (PJ_ABS(diff) == PJ_ABS(tmp) && tmp > diff))
                        {
                            diff = tmp;
                            if (diff == 0) break;
                        }
                    }
                    ++p;
                }

                if (diff == 99)
                    return PJMEDIA_CODEC_EFAILED;

                enc_mode = (pj_int8_t)(enc_mode + diff);

                break;
            }
        }

        s = PJ_POOL_ZALLOC_T(codec_data->pool, amr_settings_t);
        codec_data->codec_setting = s;

        s->enc_mode = enc_mode;
        if (s->enc_mode < 0)
            return PJMEDIA_CODEC_EINMODE;

        s->enc_setting.amr_nb = (pj_uint8_t)(desc->pt == PJMEDIA_RTP_PT_AMR);
        s->enc_setting.octet_aligned = octet_align;
        s->enc_setting.reorder = PJ_FALSE; /* Note this! passthrough codec
                                              doesn't do sensitivity bits 
                                              reordering */
        s->enc_setting.cmr = 15;
        
        s->dec_setting.amr_nb = (pj_uint8_t)(desc->pt == PJMEDIA_RTP_PT_AMR);
        s->dec_setting.octet_aligned = octet_align;
        s->dec_setting.reorder = PJ_FALSE; /* Note this! passthrough codec
                                              doesn't do sensitivity bits 
                                              reordering */
        
        /* Return back bitrate info to application */
        attr->info.avg_bps = s->enc_setting.amr_nb?
                             pjmedia_codec_amrnb_bitrates[s->enc_mode]:
                             pjmedia_codec_amrwb_bitrates[s->enc_mode];
    }
#endif

#if PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC
    /* Init iLBC settings */
    if (desc->pt == PJMEDIA_RTP_PT_ILBC)
    {
        enum { DEFAULT_MODE = 30 };
        static pj_str_t STR_MODE = {"mode", 4};
        pj_uint16_t dec_fmtp_mode = DEFAULT_MODE, 
                    enc_fmtp_mode = DEFAULT_MODE;

        /* Get decoder mode */
        for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) {
            if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name, &STR_MODE) == 0)
            {
                dec_fmtp_mode = (pj_uint16_t)
                                pj_strtoul(&attr->setting.dec_fmtp.param[i].val);
                break;
            }
        }

        /* Decoder mode must be set */
        PJ_ASSERT_RETURN(dec_fmtp_mode == 20 || dec_fmtp_mode == 30, 
                         PJMEDIA_CODEC_EINMODE);

        /* Get encoder mode */
        for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) {
            if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name, &STR_MODE) == 0)
            {
                enc_fmtp_mode = (pj_uint16_t)
                                pj_strtoul(&attr->setting.enc_fmtp.param[i].val);
                break;
            }
        }

        PJ_ASSERT_RETURN(enc_fmtp_mode==20 || enc_fmtp_mode==30, 
                         PJMEDIA_CODEC_EINMODE);

        /* Both sides of a bi-directional session MUST use the same "mode" value.
         * In this point, possible values are only 20 or 30, so when encoder and
         * decoder modes are not same, just use the default mode, it is 30.
         */
        if (enc_fmtp_mode != dec_fmtp_mode) {
            enc_fmtp_mode = dec_fmtp_mode = DEFAULT_MODE;
            PJ_LOG(4,(codec_data->pool->obj_name, 
                      "Normalized iLBC encoder and decoder modes to %d", 
                      DEFAULT_MODE));
        }

        /* Update some attributes based on negotiated mode. */
        attr->info.avg_bps = (dec_fmtp_mode == 30? 13333 : 15200);
        attr->info.frm_ptime = dec_fmtp_mode;

        /* Override average frame size */
        codec_data->avg_frame_size = (dec_fmtp_mode == 30? 50 : 38);

        /* Override samples per frame */
        codec_data->samples_per_frame = (dec_fmtp_mode == 30? 240 : 160);
    }
#endif

    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 )
{
    /* Not supported yet. */
    PJ_UNUSED_ARG(codec);
    PJ_UNUSED_ARG(attr);

    return PJ_ENOTSUP;
}

/*
 * 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[])
{
    codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
    struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
    unsigned count = 0;

    PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL);

    if (desc->parse != NULL) {
        return desc->parse(codec_data, pkt,  pkt_size, ts, frame_cnt, frames);
    }

    while (pkt_size >= codec_data->avg_frame_size && count < *frame_cnt) {
        frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
        frames[count].buf = pkt;
        frames[count].size = codec_data->avg_frame_size;
        frames[count].timestamp.u64 = ts->u64 + 
                                      count * codec_data->samples_per_frame;

        pkt = (pj_uint8_t*)pkt + codec_data->avg_frame_size;
        pkt_size -= codec_data->avg_frame_size;

        ++count;
    }

    if (pkt_size && count < *frame_cnt) {
        frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
        frames[count].buf = pkt;
        frames[count].size = pkt_size;
        frames[count].timestamp.u64 = ts->u64 + 
                                       count * codec_data->samples_per_frame;
        ++count;
    }

    *frame_cnt = count;
    return PJ_SUCCESS;
}

/*
 * Encode frames.
 */
static pj_status_t codec_encode( pjmedia_codec *codec, 
                                 const struct pjmedia_frame *input,
                                 unsigned output_buf_len, 
                                 struct pjmedia_frame *output)
{
    codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
    struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
    const pjmedia_frame_ext *input_ = (const pjmedia_frame_ext*) input;

    pj_assert(input && input->type == PJMEDIA_FRAME_TYPE_EXTENDED);

    if (desc->pack != NULL) {
        desc->pack(codec_data, input_, output_buf_len, output);
    } else {
        if (input_->subframe_cnt == 0) {
            /* DTX */
            output->buf = NULL;
            output->size = 0;
            output->type = PJMEDIA_FRAME_TYPE_NONE;
        } else {
            unsigned i;
            pj_uint8_t *p = output->buf;

            output->type = PJMEDIA_FRAME_TYPE_AUDIO;
            output->size = 0;
            
            for (i = 0; i < input_->subframe_cnt; ++i) {
                pjmedia_frame_ext_subframe *sf;
                unsigned sf_len;

                sf = pjmedia_frame_ext_get_subframe(input_, i);
                pj_assert(sf);

                sf_len = (sf->bitlen + 7) >> 3;

                pj_memcpy(p, sf->data, sf_len);
                p += sf_len;
                output->size += sf_len;

                /* If there is SID or DTX frame, break the loop. */
                if (desc->pt == PJMEDIA_RTP_PT_G729 && 
                    sf_len < codec_data->avg_frame_size)
                {
                    break;
                }
                
            }
        }
    }

    output->timestamp = input->timestamp;

    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)
{
    codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
    struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
#endif
    pjmedia_frame_ext *output_ = (pjmedia_frame_ext*) output;

    pj_assert(input);
    PJ_UNUSED_ARG(output_buf_len);

#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
    /* Need to rearrange the AMR bitstream, since the bitstream may not be 
     * started from bit 0 or may need to be reordered from sensitivity order 
     * into encoder bits order.
     */
    if (desc->pt == PJMEDIA_RTP_PT_AMR || desc->pt == PJMEDIA_RTP_PT_AMRWB) {
        pjmedia_frame input_;
        pjmedia_codec_amr_pack_setting *setting;

        setting = &((amr_settings_t*)codec_data->codec_setting)->dec_setting;

        input_ = *input;
        pjmedia_codec_amr_predecode(input, setting, &input_);
        
        pjmedia_frame_ext_append_subframe(output_, input_.buf, 
                                          (pj_uint16_t)(input_.size << 3),
                                          (pj_uint16_t)codec_data->samples_per_frame);
        output->timestamp = input->timestamp;
        
        return PJ_SUCCESS;
    }
#endif
    
    pjmedia_frame_ext_append_subframe(output_, input->buf, 
                                      (pj_uint16_t)(input->size << 3),
                                      (pj_uint16_t)codec_data->samples_per_frame);
    output->timestamp = input->timestamp;

    return PJ_SUCCESS;
}

/* 
 * Recover lost frame.
 */
static pj_status_t codec_recover( pjmedia_codec *codec, 
                                  unsigned output_buf_len, 
                                  struct pjmedia_frame *output)
{
    codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
    pjmedia_frame_ext *output_ = (pjmedia_frame_ext*) output;

    PJ_UNUSED_ARG(output_buf_len);

    pjmedia_frame_ext_append_subframe(output_, NULL, 0,
                                      (pj_uint16_t)codec_data->samples_per_frame);

    return PJ_SUCCESS;
}

#endif  /* PJMEDIA_HAS_PASSTHROUGH_CODECS */

