/* $Id$ */ /* * Copyright (C)2003-2006 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 #include #include "ilbc/iLBC_encode.h" #include "ilbc/iLBC_decode.h" /* * Only build this file if PJMEDIA_HAS_ILBC_CODEC != 0 */ #if defined(PJMEDIA_HAS_ILBC_CODEC) && PJMEDIA_HAS_ILBC_CODEC != 0 #define THIS_FILE "ilbc.c" #define CLOCK_RATE 8000 #define DEFAULT_MODE 30 /* Prototypes for iLBC factory */ static pj_status_t ilbc_test_alloc(pjmedia_codec_factory *factory, const pjmedia_codec_info *id ); static pj_status_t ilbc_default_attr(pjmedia_codec_factory *factory, const pjmedia_codec_info *id, pjmedia_codec_param *attr ); static pj_status_t ilbc_enum_codecs(pjmedia_codec_factory *factory, unsigned *count, pjmedia_codec_info codecs[]); static pj_status_t ilbc_alloc_codec(pjmedia_codec_factory *factory, const pjmedia_codec_info *id, pjmedia_codec **p_codec); static pj_status_t ilbc_dealloc_codec(pjmedia_codec_factory *factory, pjmedia_codec *codec ); /* Prototypes for iLBC implementation. */ static pj_status_t ilbc_codec_init(pjmedia_codec *codec, pj_pool_t *pool ); static pj_status_t ilbc_codec_open(pjmedia_codec *codec, pjmedia_codec_param *attr ); static pj_status_t ilbc_codec_close(pjmedia_codec *codec ); static pj_status_t ilbc_codec_modify(pjmedia_codec *codec, const pjmedia_codec_param *attr ); static pj_status_t ilbc_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 ilbc_codec_encode(pjmedia_codec *codec, const struct pjmedia_frame *input, unsigned output_buf_len, struct pjmedia_frame *output); static pj_status_t ilbc_codec_decode(pjmedia_codec *codec, const struct pjmedia_frame *input, unsigned output_buf_len, struct pjmedia_frame *output); static pj_status_t ilbc_codec_recover(pjmedia_codec *codec, unsigned output_buf_len, struct pjmedia_frame *output); /* Definition for iLBC codec operations. */ static pjmedia_codec_op ilbc_op = { &ilbc_codec_init, &ilbc_codec_open, &ilbc_codec_close, &ilbc_codec_modify, &ilbc_codec_parse, &ilbc_codec_encode, &ilbc_codec_decode, &ilbc_codec_recover }; /* Definition for iLBC codec factory operations. */ static pjmedia_codec_factory_op ilbc_factory_op = { &ilbc_test_alloc, &ilbc_default_attr, &ilbc_enum_codecs, &ilbc_alloc_codec, &ilbc_dealloc_codec }; /* iLBC factory */ static struct ilbc_factory { pjmedia_codec_factory base; pjmedia_endpt *endpt; int mode; int bps; } ilbc_factory; /* iLBC codec private data. */ struct ilbc_codec { pjmedia_codec base; pj_pool_t *pool; char obj_name[PJ_MAX_OBJ_NAME]; pjmedia_silence_det *vad; pj_bool_t vad_enabled; pj_bool_t plc_enabled; pj_timestamp last_tx; pj_bool_t enc_ready; iLBC_Enc_Inst_t enc; unsigned enc_frame_size; unsigned enc_samples_per_frame; float enc_block[BLOCKL_MAX]; pj_bool_t dec_ready; iLBC_Dec_Inst_t dec; unsigned dec_frame_size; unsigned dec_samples_per_frame; float dec_block[BLOCKL_MAX]; }; /* * Initialize and register iLBC codec factory to pjmedia endpoint. */ PJ_DEF(pj_status_t) pjmedia_codec_ilbc_init( pjmedia_endpt *endpt, int mode ) { pjmedia_codec_mgr *codec_mgr; pj_status_t status; PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL); PJ_ASSERT_RETURN(mode==0 || mode==20 || mode==30, PJ_EINVAL); /* Create iLBC codec factory. */ ilbc_factory.base.op = &ilbc_factory_op; ilbc_factory.base.factory_data = NULL; ilbc_factory.endpt = endpt; if (mode == 0) mode = DEFAULT_MODE; ilbc_factory.mode = mode; if (mode == 20) { ilbc_factory.bps = 15200; } else { ilbc_factory.bps = 13333; } /* Get the codec manager. */ codec_mgr = pjmedia_endpt_get_codec_mgr(endpt); if (!codec_mgr) return PJ_EINVALIDOP; /* Register codec factory to endpoint. */ status = pjmedia_codec_mgr_register_factory(codec_mgr, &ilbc_factory.base); if (status != PJ_SUCCESS) return status; /* Done. */ return PJ_SUCCESS; } /* * Unregister iLBC codec factory from pjmedia endpoint and deinitialize * the iLBC codec library. */ PJ_DEF(pj_status_t) pjmedia_codec_ilbc_deinit(void) { pjmedia_codec_mgr *codec_mgr; pj_status_t status; /* Get the codec manager. */ codec_mgr = pjmedia_endpt_get_codec_mgr(ilbc_factory.endpt); if (!codec_mgr) return PJ_EINVALIDOP; /* Unregister iLBC codec factory. */ status = pjmedia_codec_mgr_unregister_factory(codec_mgr, &ilbc_factory.base); return status; } /* * Check if factory can allocate the specified codec. */ static pj_status_t ilbc_test_alloc( pjmedia_codec_factory *factory, const pjmedia_codec_info *info ) { const pj_str_t ilbc_tag = { "iLBC", 4}; PJ_UNUSED_ARG(factory); PJ_ASSERT_RETURN(factory==&ilbc_factory.base, PJ_EINVAL); /* Type MUST be audio. */ if (info->type != PJMEDIA_TYPE_AUDIO) return PJMEDIA_CODEC_EUNSUP; /* Check encoding name. */ if (pj_stricmp(&info->encoding_name, &ilbc_tag) != 0) return PJMEDIA_CODEC_EUNSUP; /* Check clock-rate */ if (info->clock_rate != CLOCK_RATE) return PJMEDIA_CODEC_EUNSUP; /* Channel count must be one */ if (info->channel_cnt != 1) return PJMEDIA_CODEC_EUNSUP; /* Yes, this should be iLBC! */ return PJ_SUCCESS; } /* * Generate default attribute. */ static pj_status_t ilbc_default_attr (pjmedia_codec_factory *factory, const pjmedia_codec_info *id, pjmedia_codec_param *attr ) { PJ_UNUSED_ARG(factory); PJ_ASSERT_RETURN(factory==&ilbc_factory.base, PJ_EINVAL); PJ_UNUSED_ARG(id); PJ_ASSERT_RETURN(pj_stricmp2(&id->encoding_name, "iLBC")==0, PJ_EINVAL); pj_bzero(attr, sizeof(pjmedia_codec_param)); attr->info.clock_rate = CLOCK_RATE; attr->info.channel_cnt = 1; attr->info.avg_bps = ilbc_factory.bps; attr->info.pcm_bits_per_sample = 16; attr->info.frm_ptime = (short)ilbc_factory.mode; attr->info.pt = PJMEDIA_RTP_PT_ILBC; attr->setting.frm_per_pkt = 1; attr->setting.vad = 1; attr->setting.plc = 1; attr->setting.penh = 1; attr->setting.dec_fmtp_mode = (pj_uint8_t)ilbc_factory.mode; return PJ_SUCCESS; } /* * Enum codecs supported by this factory (i.e. only iLBC!). */ static pj_status_t ilbc_enum_codecs(pjmedia_codec_factory *factory, unsigned *count, pjmedia_codec_info codecs[]) { PJ_UNUSED_ARG(factory); PJ_ASSERT_RETURN(factory==&ilbc_factory.base, PJ_EINVAL); PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL); pj_bzero(&codecs[0], sizeof(pjmedia_codec_info)); codecs[0].encoding_name = pj_str("iLBC"); codecs[0].pt = PJMEDIA_RTP_PT_ILBC; codecs[0].type = PJMEDIA_TYPE_AUDIO; codecs[0].clock_rate = 8000; codecs[0].channel_cnt = 1; *count = 1; return PJ_SUCCESS; } /* * Allocate a new iLBC codec instance. */ static pj_status_t ilbc_alloc_codec(pjmedia_codec_factory *factory, const pjmedia_codec_info *id, pjmedia_codec **p_codec) { pj_pool_t *pool; struct ilbc_codec *codec; PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL); PJ_ASSERT_RETURN(factory == &ilbc_factory.base, PJ_EINVAL); pool = pjmedia_endpt_create_pool(ilbc_factory.endpt, "iLBC%p", 2000, 2000); PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); codec = pj_pool_zalloc(pool, sizeof(struct ilbc_codec)); codec->base.op = &ilbc_op; codec->base.factory = factory; codec->pool = pool; pj_ansi_snprintf(codec->obj_name, sizeof(codec->obj_name), "ilbc%p", codec); *p_codec = &codec->base; return PJ_SUCCESS; } /* * Free codec. */ static pj_status_t ilbc_dealloc_codec( pjmedia_codec_factory *factory, pjmedia_codec *codec ) { struct ilbc_codec *ilbc_codec; PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); PJ_UNUSED_ARG(factory); PJ_ASSERT_RETURN(factory == &ilbc_factory.base, PJ_EINVAL); ilbc_codec = (struct ilbc_codec*) codec; pj_pool_release(ilbc_codec->pool); return PJ_SUCCESS; } /* * Init codec. */ static pj_status_t ilbc_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 ilbc_codec_open(pjmedia_codec *codec, pjmedia_codec_param *attr ) { struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec; pj_status_t status; pj_assert(ilbc_codec != NULL); pj_assert(ilbc_codec->enc_ready == PJ_FALSE && ilbc_codec->dec_ready == PJ_FALSE); /* Decoder mode must be set */ PJ_ASSERT_RETURN(attr->setting.dec_fmtp_mode==20 || attr->setting.dec_fmtp_mode==30, PJMEDIA_CODEC_EINMODE); /* The enc mode must be set in the attribute * (from the mode parameter in fmtp attribute in the SDP * received from remote) */ if (attr->setting.enc_fmtp_mode == 0) attr->setting.enc_fmtp_mode = attr->setting.dec_fmtp_mode; PJ_ASSERT_RETURN(attr->setting.enc_fmtp_mode==20 || attr->setting.enc_fmtp_mode==30, PJMEDIA_CODEC_EINMODE); /* Update enc_ptime in the param */ if (attr->setting.enc_fmtp_mode != attr->setting.dec_fmtp_mode) { attr->info.enc_ptime = attr->setting.enc_fmtp_mode; } else { attr->info.enc_ptime = 0; } /* Create enc */ ilbc_codec->enc_frame_size = initEncode(&ilbc_codec->enc, attr->setting.enc_fmtp_mode); ilbc_codec->enc_samples_per_frame = CLOCK_RATE*attr->setting.enc_fmtp_mode/ 1000; ilbc_codec->enc_ready = PJ_TRUE; /* Create decoder */ ilbc_codec->dec_samples_per_frame = initDecode(&ilbc_codec->dec, attr->setting.dec_fmtp_mode, attr->setting.penh); if (attr->setting.dec_fmtp_mode == 20) ilbc_codec->dec_frame_size = 38; else if (attr->setting.dec_fmtp_mode == 30) ilbc_codec->dec_frame_size = 50; else { pj_assert(!"Invalid iLBC mode"); ilbc_codec->dec_frame_size = ilbc_codec->enc_frame_size; } ilbc_codec->dec_ready = PJ_TRUE; /* Save plc flags */ ilbc_codec->plc_enabled = (attr->setting.plc != 0); /* Create silence detector. */ ilbc_codec->vad_enabled = (attr->setting.vad != 0); status = pjmedia_silence_det_create(ilbc_codec->pool, CLOCK_RATE, ilbc_codec->enc_samples_per_frame, &ilbc_codec->vad); if (status != PJ_SUCCESS) return status; /* Init last_tx (not necessary because of zalloc, but better * be safe in case someone remove zalloc later. */ pj_set_timestamp32(&ilbc_codec->last_tx, 0, 0); PJ_LOG(5,(ilbc_codec->obj_name, "iLBC codec opened, encoder mode=%d, decoder mode=%d", attr->setting.enc_fmtp_mode, attr->setting.dec_fmtp_mode)); return PJ_SUCCESS; } /* * Close codec. */ static pj_status_t ilbc_codec_close( pjmedia_codec *codec ) { struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec; PJ_UNUSED_ARG(codec); PJ_LOG(5,(ilbc_codec->obj_name, "iLBC codec closed")); return PJ_SUCCESS; } /* * Modify codec settings. */ static pj_status_t ilbc_codec_modify(pjmedia_codec *codec, const pjmedia_codec_param *attr ) { struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec; ilbc_codec->plc_enabled = (attr->setting.plc != 0); ilbc_codec->vad_enabled = (attr->setting.vad != 0); return PJ_SUCCESS; } /* * Get frames in the packet. */ static pj_status_t ilbc_codec_parse( pjmedia_codec *codec, void *pkt, pj_size_t pkt_size, const pj_timestamp *ts, unsigned *frame_cnt, pjmedia_frame frames[]) { struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec; unsigned count; PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL); count = 0; while (pkt_size >= ilbc_codec->dec_frame_size && count < *frame_cnt) { frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO; frames[count].buf = pkt; frames[count].size = ilbc_codec->dec_frame_size; frames[count].timestamp.u64 = ts->u64 + count * ilbc_codec->dec_samples_per_frame; pkt = ((char*)pkt) + ilbc_codec->dec_frame_size; pkt_size -= ilbc_codec->dec_frame_size; ++count; } *frame_cnt = count; return PJ_SUCCESS; } /* * Encode frame. */ static pj_status_t ilbc_codec_encode(pjmedia_codec *codec, const struct pjmedia_frame *input, unsigned output_buf_len, struct pjmedia_frame *output) { struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec; unsigned i; pj_assert(ilbc_codec != NULL); PJ_ASSERT_RETURN(input && output, PJ_EINVAL); if (output_buf_len < ilbc_codec->enc_frame_size) return PJMEDIA_CODEC_EFRMTOOSHORT; if (input->size != (ilbc_codec->enc_samples_per_frame << 1)) return PJMEDIA_CODEC_EPCMFRMINLEN; /* Detect silence */ if (ilbc_codec->vad_enabled) { pj_bool_t is_silence; pj_int32_t silence_period; silence_period = pj_timestamp_diff32(&ilbc_codec->last_tx, &input->timestamp); is_silence = pjmedia_silence_det_detect(ilbc_codec->vad, input->buf, (input->size >> 1), NULL); if (is_silence && PJMEDIA_CODEC_MAX_SILENCE_PERIOD != -1 && silence_period < PJMEDIA_CODEC_MAX_SILENCE_PERIOD) { output->type = PJMEDIA_FRAME_TYPE_NONE; output->buf = NULL; output->size = 0; output->timestamp = input->timestamp; return PJ_SUCCESS; } else { ilbc_codec->last_tx = input->timestamp; } } /* Convert to float */ for (i=0; ienc_samples_per_frame; ++i) { ilbc_codec->enc_block[i] = (float) (((pj_int16_t*)input->buf)[i]); } /* Encode */ iLBC_encode((unsigned char *)output->buf, ilbc_codec->enc_block, &ilbc_codec->enc); output->type = PJMEDIA_FRAME_TYPE_AUDIO; output->size = ilbc_codec->enc.no_of_bytes; output->timestamp = input->timestamp; return PJ_SUCCESS; } /* * Decode frame. */ static pj_status_t ilbc_codec_decode(pjmedia_codec *codec, const struct pjmedia_frame *input, unsigned output_buf_len, struct pjmedia_frame *output) { struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec; unsigned i; pj_assert(ilbc_codec != NULL); PJ_ASSERT_RETURN(input && output, PJ_EINVAL); if (output_buf_len < (ilbc_codec->dec_samples_per_frame << 1)) return PJMEDIA_CODEC_EPCMTOOSHORT; if (input->size != ilbc_codec->dec_frame_size) return PJMEDIA_CODEC_EFRMINLEN; /* Decode to temporary buffer */ iLBC_decode(ilbc_codec->dec_block, input->buf, &ilbc_codec->dec, 1); /* Convert decodec samples from float to short */ for (i=0; idec_samples_per_frame; ++i) { ((short*)output->buf)[i] = (short)ilbc_codec->dec_block[i]; } output->size = (ilbc_codec->dec_samples_per_frame << 1); output->type = PJMEDIA_FRAME_TYPE_AUDIO; output->timestamp = input->timestamp; return PJ_SUCCESS; } /* * Recover lost frame. */ static pj_status_t ilbc_codec_recover(pjmedia_codec *codec, unsigned output_buf_len, struct pjmedia_frame *output) { struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec; unsigned i; pj_assert(ilbc_codec != NULL); PJ_ASSERT_RETURN(output, PJ_EINVAL); if (output_buf_len < (ilbc_codec->dec_samples_per_frame << 1)) return PJMEDIA_CODEC_EPCMTOOSHORT; /* Decode to temporary buffer */ iLBC_decode(ilbc_codec->dec_block, NULL, &ilbc_codec->dec, 0); /* Convert decodec samples from float to short */ for (i=0; idec_samples_per_frame; ++i) { ((short*)output->buf)[i] = (short)ilbc_codec->dec_block[i]; } output->size = (ilbc_codec->dec_samples_per_frame << 1); output->type = PJMEDIA_FRAME_TYPE_AUDIO; return PJ_SUCCESS; } #endif /* PJMEDIA_HAS_ILBC_CODEC */