/* $Id$ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * 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 #include #include #include #include #include #include #include #include #include #include #include /* * 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_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 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; } /* Unregister passthrough codecs factory. */ status = pjmedia_codec_mgr_unregister_factory(codec_mgr, &codec_factory.base); /* Destroy mutex. */ pj_mutex_unlock(codec_factory.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; 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]; pj_pool_t *pool; int i, j; pool = codec_data->pool; /* 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(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,(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 */