| /* $Id$ */ |
| /* |
| * 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.h> |
| #include <pjmedia/errno.h> |
| #include <pj/array.h> |
| #include <pj/assert.h> |
| #include <pj/log.h> |
| #include <pj/string.h> |
| |
| #define THIS_FILE "codec.c" |
| |
| |
| |
| /* Definition of default codecs parameters */ |
| struct pjmedia_codec_default_param |
| { |
| pj_pool_t *pool; |
| pjmedia_codec_param *param; |
| }; |
| |
| |
| /* Sort codecs in codec manager based on priorities */ |
| static void sort_codecs(pjmedia_codec_mgr *mgr); |
| |
| |
| /* |
| * Duplicate codec parameter. |
| */ |
| PJ_DEF(pjmedia_codec_param*) pjmedia_codec_param_clone( |
| pj_pool_t *pool, |
| const pjmedia_codec_param *src) |
| { |
| pjmedia_codec_param *p; |
| unsigned i; |
| |
| PJ_ASSERT_RETURN(pool && src, NULL); |
| |
| p = PJ_POOL_ZALLOC_T(pool, pjmedia_codec_param); |
| |
| /* Update codec param */ |
| pj_memcpy(p, src, sizeof(pjmedia_codec_param)); |
| for (i = 0; i < src->setting.dec_fmtp.cnt; ++i) { |
| pj_strdup(pool, &p->setting.dec_fmtp.param[i].name, |
| &src->setting.dec_fmtp.param[i].name); |
| pj_strdup(pool, &p->setting.dec_fmtp.param[i].val, |
| &src->setting.dec_fmtp.param[i].val); |
| } |
| for (i = 0; i < src->setting.enc_fmtp.cnt; ++i) { |
| pj_strdup(pool, &p->setting.enc_fmtp.param[i].name, |
| &src->setting.enc_fmtp.param[i].name); |
| pj_strdup(pool, &p->setting.enc_fmtp.param[i].val, |
| &src->setting.enc_fmtp.param[i].val); |
| } |
| |
| return p; |
| } |
| |
| |
| /* |
| * Initialize codec manager. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_codec_mgr_init (pjmedia_codec_mgr *mgr, |
| pj_pool_factory *pf) |
| { |
| pj_status_t status; |
| |
| PJ_ASSERT_RETURN(mgr && pf, PJ_EINVAL); |
| |
| /* Init codec manager */ |
| pj_bzero(mgr, sizeof(pjmedia_codec_mgr)); |
| mgr->pf = pf; |
| pj_list_init (&mgr->factory_list); |
| mgr->codec_cnt = 0; |
| |
| /* Create pool */ |
| mgr->pool = pj_pool_create(mgr->pf, "codec-mgr", 256, 256, NULL); |
| |
| /* Create mutex */ |
| status = pj_mutex_create_recursive(mgr->pool, "codec-mgr", &mgr->mutex); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| return PJ_SUCCESS; |
| } |
| |
| /* |
| * Initialize codec manager. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_codec_mgr_destroy (pjmedia_codec_mgr *mgr) |
| { |
| pjmedia_codec_factory *factory; |
| unsigned i; |
| |
| PJ_ASSERT_RETURN(mgr, PJ_EINVAL); |
| |
| /* Destroy all factories in the list */ |
| factory = mgr->factory_list.next; |
| while (factory != &mgr->factory_list) { |
| pjmedia_codec_factory *next = factory->next; |
| (*factory->op->destroy)(); |
| factory = next; |
| } |
| |
| /* Cleanup all pools of all codec default params */ |
| for (i=0; i<mgr->codec_cnt; ++i) { |
| if (mgr->codec_desc[i].param) { |
| pj_assert(mgr->codec_desc[i].param->pool); |
| pj_pool_release(mgr->codec_desc[i].param->pool); |
| } |
| } |
| |
| /* Destroy mutex */ |
| if (mgr->mutex) |
| pj_mutex_destroy(mgr->mutex); |
| |
| /* Release pool */ |
| if (mgr->pool) |
| pj_pool_release(mgr->pool); |
| |
| /* Just for safety, set codec manager states to zero */ |
| pj_bzero(mgr, sizeof(pjmedia_codec_mgr)); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Register a codec factory. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_codec_mgr_register_factory( pjmedia_codec_mgr *mgr, |
| pjmedia_codec_factory *factory) |
| { |
| pjmedia_codec_info info[PJMEDIA_CODEC_MGR_MAX_CODECS]; |
| unsigned i, count; |
| pj_status_t status; |
| |
| PJ_ASSERT_RETURN(mgr && factory, PJ_EINVAL); |
| |
| /* Since 2.0 we require codec factory to implement "destroy" op. Please |
| * see: https://trac.pjsip.org/repos/ticket/1294 |
| * |
| * Really! Please do see it. |
| */ |
| PJ_ASSERT_RETURN(factory->op->destroy != NULL, PJ_ENOTSUP); |
| |
| /* Enum codecs */ |
| count = PJ_ARRAY_SIZE(info); |
| status = factory->op->enum_info(factory, &count, info); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| pj_mutex_lock(mgr->mutex); |
| |
| /* Check codec count */ |
| if (count + mgr->codec_cnt > PJ_ARRAY_SIZE(mgr->codec_desc)) { |
| pj_mutex_unlock(mgr->mutex); |
| return PJ_ETOOMANY; |
| } |
| |
| |
| /* Save the codecs */ |
| for (i=0; i<count; ++i) { |
| pj_memcpy( &mgr->codec_desc[mgr->codec_cnt+i], |
| &info[i], sizeof(pjmedia_codec_info)); |
| mgr->codec_desc[mgr->codec_cnt+i].prio = PJMEDIA_CODEC_PRIO_NORMAL; |
| mgr->codec_desc[mgr->codec_cnt+i].factory = factory; |
| pjmedia_codec_info_to_id( &info[i], |
| mgr->codec_desc[mgr->codec_cnt+i].id, |
| sizeof(pjmedia_codec_id)); |
| } |
| |
| /* Update count */ |
| mgr->codec_cnt += count; |
| |
| /* Re-sort codec based on priorities */ |
| sort_codecs(mgr); |
| |
| /* Add factory to the list */ |
| pj_list_push_back(&mgr->factory_list, factory); |
| |
| pj_mutex_unlock(mgr->mutex); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Unregister a codec factory. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_codec_mgr_unregister_factory( |
| pjmedia_codec_mgr *mgr, |
| pjmedia_codec_factory *factory) |
| { |
| unsigned i; |
| PJ_ASSERT_RETURN(mgr && factory, PJ_EINVAL); |
| |
| pj_mutex_lock(mgr->mutex); |
| |
| /* Factory must be registered. */ |
| if (pj_list_find_node(&mgr->factory_list, factory) != factory) { |
| pj_mutex_unlock(mgr->mutex); |
| return PJ_ENOTFOUND; |
| } |
| |
| /* Erase factory from the factory list */ |
| pj_list_erase(factory); |
| |
| |
| /* Remove all supported codecs from the codec manager that were created |
| * by the specified factory. |
| */ |
| for (i=0; i<mgr->codec_cnt; ) { |
| |
| if (mgr->codec_desc[i].factory == factory) { |
| /* Release pool of codec default param */ |
| if (mgr->codec_desc[i].param) { |
| pj_assert(mgr->codec_desc[i].param->pool); |
| pj_pool_release(mgr->codec_desc[i].param->pool); |
| } |
| |
| /* Remove the codec from array of codec descriptions */ |
| pj_array_erase(mgr->codec_desc, sizeof(mgr->codec_desc[0]), |
| mgr->codec_cnt, i); |
| --mgr->codec_cnt; |
| |
| } else { |
| ++i; |
| } |
| } |
| |
| pj_mutex_unlock(mgr->mutex); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Enum all codecs. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_codec_mgr_enum_codecs(pjmedia_codec_mgr *mgr, |
| unsigned *count, |
| pjmedia_codec_info codecs[], |
| unsigned *prio) |
| { |
| unsigned i; |
| |
| PJ_ASSERT_RETURN(mgr && count && codecs, PJ_EINVAL); |
| |
| pj_mutex_lock(mgr->mutex); |
| |
| if (*count > mgr->codec_cnt) |
| *count = mgr->codec_cnt; |
| |
| for (i=0; i<*count; ++i) { |
| pj_memcpy(&codecs[i], |
| &mgr->codec_desc[i].info, |
| sizeof(pjmedia_codec_info)); |
| } |
| |
| if (prio) { |
| for (i=0; i < *count; ++i) |
| prio[i] = mgr->codec_desc[i].prio; |
| } |
| |
| pj_mutex_unlock(mgr->mutex); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Get codec info for static payload type. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_codec_mgr_get_codec_info( pjmedia_codec_mgr *mgr, |
| unsigned pt, |
| const pjmedia_codec_info **p_info) |
| { |
| unsigned i; |
| |
| PJ_ASSERT_RETURN(mgr && p_info && pt>=0 && pt < 96, PJ_EINVAL); |
| |
| pj_mutex_lock(mgr->mutex); |
| |
| for (i=0; i<mgr->codec_cnt; ++i) { |
| if (mgr->codec_desc[i].info.pt == pt) { |
| *p_info = &mgr->codec_desc[i].info; |
| |
| pj_mutex_unlock(mgr->mutex); |
| return PJ_SUCCESS; |
| } |
| } |
| |
| pj_mutex_unlock(mgr->mutex); |
| |
| return PJMEDIA_CODEC_EUNSUP; |
| } |
| |
| |
| /* |
| * Convert codec info struct into a unique codec identifier. |
| * A codec identifier looks something like "L16/44100/2". |
| */ |
| PJ_DEF(char*) pjmedia_codec_info_to_id( const pjmedia_codec_info *info, |
| char *id, unsigned max_len ) |
| { |
| int len; |
| |
| PJ_ASSERT_RETURN(info && id && max_len, NULL); |
| |
| len = pj_ansi_snprintf(id, max_len, "%.*s/%u/%u", |
| (int)info->encoding_name.slen, |
| info->encoding_name.ptr, |
| info->clock_rate, |
| info->channel_cnt); |
| |
| if (len < 1 || len >= (int)max_len) { |
| id[0] = '\0'; |
| return NULL; |
| } |
| |
| return id; |
| } |
| |
| |
| /* |
| * Find codecs by the unique codec identifier. This function will find |
| * all codecs that match the codec identifier prefix. For example, if |
| * "L16" is specified, then it will find "L16/8000/1", "L16/16000/1", |
| * and so on, up to the maximum count specified in the argument. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_codec_mgr_find_codecs_by_id( pjmedia_codec_mgr *mgr, |
| const pj_str_t *codec_id, |
| unsigned *count, |
| const pjmedia_codec_info *p_info[], |
| unsigned prio[]) |
| { |
| unsigned i, found = 0; |
| |
| PJ_ASSERT_RETURN(mgr && codec_id && count && *count, PJ_EINVAL); |
| |
| pj_mutex_lock(mgr->mutex); |
| |
| for (i=0; i<mgr->codec_cnt; ++i) { |
| |
| if (codec_id->slen == 0 || |
| pj_strnicmp2(codec_id, mgr->codec_desc[i].id, |
| codec_id->slen) == 0) |
| { |
| |
| if (p_info) |
| p_info[found] = &mgr->codec_desc[i].info; |
| if (prio) |
| prio[found] = mgr->codec_desc[i].prio; |
| |
| ++found; |
| |
| if (found >= *count) |
| break; |
| } |
| |
| } |
| |
| pj_mutex_unlock(mgr->mutex); |
| |
| *count = found; |
| |
| return found ? PJ_SUCCESS : PJ_ENOTFOUND; |
| } |
| |
| |
| /* Swap two codecs positions in codec manager */ |
| static void swap_codec(pjmedia_codec_mgr *mgr, unsigned i, unsigned j) |
| { |
| struct pjmedia_codec_desc tmp; |
| |
| pj_memcpy(&tmp, &mgr->codec_desc[i], sizeof(struct pjmedia_codec_desc)); |
| |
| pj_memcpy(&mgr->codec_desc[i], &mgr->codec_desc[j], |
| sizeof(struct pjmedia_codec_desc)); |
| |
| pj_memcpy(&mgr->codec_desc[j], &tmp, sizeof(struct pjmedia_codec_desc)); |
| } |
| |
| |
| /* Sort codecs in codec manager based on priorities */ |
| static void sort_codecs(pjmedia_codec_mgr *mgr) |
| { |
| unsigned i; |
| |
| /* Re-sort */ |
| for (i=0; i<mgr->codec_cnt; ++i) { |
| unsigned j, max; |
| |
| for (max=i, j=i+1; j<mgr->codec_cnt; ++j) { |
| if (mgr->codec_desc[j].prio > mgr->codec_desc[max].prio) |
| max = j; |
| } |
| |
| if (max != i) |
| swap_codec(mgr, i, max); |
| } |
| |
| /* Change PJMEDIA_CODEC_PRIO_HIGHEST codecs to NEXT_HIGHER */ |
| for (i=0; i<mgr->codec_cnt; ++i) { |
| if (mgr->codec_desc[i].prio == PJMEDIA_CODEC_PRIO_HIGHEST) |
| mgr->codec_desc[i].prio = PJMEDIA_CODEC_PRIO_NEXT_HIGHER; |
| else |
| break; |
| } |
| } |
| |
| |
| /** |
| * Set codec priority. The codec priority determines the order of |
| * the codec in the SDP created by the endpoint. If more than one codecs |
| * are found with the same codec_id prefix, then the function sets the |
| * priorities of all those codecs. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_codec_mgr_set_codec_priority( |
| pjmedia_codec_mgr *mgr, |
| const pj_str_t *codec_id, |
| pj_uint8_t prio) |
| { |
| unsigned i, found = 0; |
| |
| PJ_ASSERT_RETURN(mgr && codec_id, PJ_EINVAL); |
| |
| pj_mutex_lock(mgr->mutex); |
| |
| /* Update the priorities of affected codecs */ |
| for (i=0; i<mgr->codec_cnt; ++i) |
| { |
| if (codec_id->slen == 0 || |
| pj_strnicmp2(codec_id, mgr->codec_desc[i].id, |
| codec_id->slen) == 0) |
| { |
| mgr->codec_desc[i].prio = (pjmedia_codec_priority) prio; |
| ++found; |
| } |
| } |
| |
| if (!found) { |
| pj_mutex_unlock(mgr->mutex); |
| return PJ_ENOTFOUND; |
| } |
| |
| /* Re-sort codecs */ |
| sort_codecs(mgr); |
| |
| pj_mutex_unlock(mgr->mutex); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Allocate one codec. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_codec_mgr_alloc_codec(pjmedia_codec_mgr *mgr, |
| const pjmedia_codec_info *info, |
| pjmedia_codec **p_codec) |
| { |
| pjmedia_codec_factory *factory; |
| pj_status_t status; |
| |
| PJ_ASSERT_RETURN(mgr && info && p_codec, PJ_EINVAL); |
| |
| *p_codec = NULL; |
| |
| pj_mutex_lock(mgr->mutex); |
| |
| factory = mgr->factory_list.next; |
| while (factory != &mgr->factory_list) { |
| |
| if ( (*factory->op->test_alloc)(factory, info) == PJ_SUCCESS ) { |
| |
| status = (*factory->op->alloc_codec)(factory, info, p_codec); |
| if (status == PJ_SUCCESS) { |
| pj_mutex_unlock(mgr->mutex); |
| return PJ_SUCCESS; |
| } |
| |
| } |
| |
| factory = factory->next; |
| } |
| |
| pj_mutex_unlock(mgr->mutex); |
| |
| return PJMEDIA_CODEC_EUNSUP; |
| } |
| |
| |
| /* |
| * Get default codec parameter. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_codec_mgr_get_default_param( pjmedia_codec_mgr *mgr, |
| const pjmedia_codec_info *info, |
| pjmedia_codec_param *param ) |
| { |
| pjmedia_codec_factory *factory; |
| pj_status_t status; |
| pjmedia_codec_id codec_id; |
| struct pjmedia_codec_desc *codec_desc = NULL; |
| unsigned i; |
| |
| PJ_ASSERT_RETURN(mgr && info && param, PJ_EINVAL); |
| |
| if (!pjmedia_codec_info_to_id(info, (char*)&codec_id, sizeof(codec_id))) |
| return PJ_EINVAL; |
| |
| pj_mutex_lock(mgr->mutex); |
| |
| /* First, lookup default param in codec desc */ |
| for (i=0; i < mgr->codec_cnt; ++i) { |
| if (pj_ansi_stricmp(codec_id, mgr->codec_desc[i].id) == 0) { |
| codec_desc = &mgr->codec_desc[i]; |
| break; |
| } |
| } |
| |
| /* If we found the codec and its default param is set, return it */ |
| if (codec_desc && codec_desc->param) { |
| pj_assert(codec_desc->param->param); |
| pj_memcpy(param, codec_desc->param->param, |
| sizeof(pjmedia_codec_param)); |
| |
| pj_mutex_unlock(mgr->mutex); |
| return PJ_SUCCESS; |
| } |
| |
| /* Otherwise query the default param from codec factory */ |
| factory = mgr->factory_list.next; |
| while (factory != &mgr->factory_list) { |
| |
| if ( (*factory->op->test_alloc)(factory, info) == PJ_SUCCESS ) { |
| |
| status = (*factory->op->default_attr)(factory, info, param); |
| if (status == PJ_SUCCESS) { |
| /* Check for invalid max_bps. */ |
| if (param->info.max_bps < param->info.avg_bps) |
| param->info.max_bps = param->info.avg_bps; |
| |
| pj_mutex_unlock(mgr->mutex); |
| return PJ_SUCCESS; |
| } |
| |
| } |
| |
| factory = factory->next; |
| } |
| |
| pj_mutex_unlock(mgr->mutex); |
| |
| |
| return PJMEDIA_CODEC_EUNSUP; |
| } |
| |
| |
| /* |
| * Set default codec parameter. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_codec_mgr_set_default_param( |
| pjmedia_codec_mgr *mgr, |
| const pjmedia_codec_info *info, |
| const pjmedia_codec_param *param ) |
| { |
| unsigned i; |
| pjmedia_codec_id codec_id; |
| pj_pool_t *pool, *old_pool = NULL; |
| struct pjmedia_codec_desc *codec_desc = NULL; |
| pjmedia_codec_default_param *p; |
| |
| PJ_ASSERT_RETURN(mgr && info, PJ_EINVAL); |
| |
| if (!pjmedia_codec_info_to_id(info, (char*)&codec_id, sizeof(codec_id))) |
| return PJ_EINVAL; |
| |
| pj_mutex_lock(mgr->mutex); |
| |
| /* Lookup codec desc */ |
| for (i=0; i < mgr->codec_cnt; ++i) { |
| if (pj_ansi_stricmp(codec_id, mgr->codec_desc[i].id) == 0) { |
| codec_desc = &mgr->codec_desc[i]; |
| break; |
| } |
| } |
| |
| /* Codec not found */ |
| if (!codec_desc) { |
| pj_mutex_unlock(mgr->mutex); |
| return PJMEDIA_CODEC_EUNSUP; |
| } |
| |
| /* If codec param is previously set, reset the codec param but release |
| * the codec param pool later after the new param is set (ticket #1171). |
| */ |
| if (codec_desc->param) { |
| pj_assert(codec_desc->param->pool); |
| old_pool = codec_desc->param->pool; |
| codec_desc->param = NULL; |
| } |
| |
| /* When param is set to NULL, i.e: setting default codec param to library |
| * default setting, just return PJ_SUCCESS. |
| */ |
| if (NULL == param) { |
| pj_mutex_unlock(mgr->mutex); |
| if (old_pool) |
| pj_pool_release(old_pool); |
| return PJ_SUCCESS; |
| } |
| |
| /* Instantiate and initialize codec param */ |
| pool = pj_pool_create(mgr->pf, (char*)codec_id, 256, 256, NULL); |
| codec_desc->param = PJ_POOL_ZALLOC_T(pool, pjmedia_codec_default_param); |
| p = codec_desc->param; |
| p->pool = pool; |
| |
| /* Update codec param */ |
| p->param = pjmedia_codec_param_clone(pool, param); |
| if (!p->param) { |
| pj_mutex_unlock(mgr->mutex); |
| return PJ_EINVAL; |
| } |
| |
| pj_mutex_unlock(mgr->mutex); |
| |
| if (old_pool) |
| pj_pool_release(old_pool); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Dealloc codec. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_codec_mgr_dealloc_codec(pjmedia_codec_mgr *mgr, |
| pjmedia_codec *codec) |
| { |
| PJ_ASSERT_RETURN(mgr && codec, PJ_EINVAL); |
| |
| return (*codec->factory->op->dealloc_codec)(codec->factory, codec); |
| } |
| |