/* $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 #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) #define THIS_FILE "vid_codec.c" static pjmedia_vid_codec_mgr *def_vid_codec_mgr; /* Definition of default codecs parameters */ typedef struct pjmedia_vid_codec_default_param { pj_pool_t *pool; pjmedia_vid_codec_param *param; } pjmedia_vid_codec_default_param; /* * Codec manager maintains array of these structs for each supported * codec. */ typedef struct pjmedia_vid_codec_desc { pjmedia_vid_codec_info info; /**< Codec info. */ pjmedia_codec_id id; /**< Fully qualified name */ pjmedia_codec_priority prio; /**< Priority. */ pjmedia_vid_codec_factory *factory; /**< The factory. */ pjmedia_vid_codec_default_param *def_param; /**< Default codecs parameters. */ } pjmedia_vid_codec_desc; /* The declaration of video codec manager */ struct pjmedia_vid_codec_mgr { /** Pool factory instance. */ pj_pool_factory *pf; /** Codec manager mutex. */ pj_mutex_t *mutex; /** List of codec factories registered to codec manager. */ pjmedia_vid_codec_factory factory_list; /** Number of supported codecs. */ unsigned codec_cnt; /** Array of codec descriptor. */ pjmedia_vid_codec_desc codec_desc[PJMEDIA_CODEC_MGR_MAX_CODECS]; }; /* Sort codecs in codec manager based on priorities */ static void sort_codecs(pjmedia_vid_codec_mgr *mgr); /* * Duplicate video codec parameter. */ PJ_DEF(pjmedia_vid_codec_param*) pjmedia_vid_codec_param_clone( pj_pool_t *pool, const pjmedia_vid_codec_param *src) { pjmedia_vid_codec_param *p; unsigned i; PJ_ASSERT_RETURN(pool && src, NULL); p = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_codec_param); /* Update codec param */ pj_memcpy(p, src, sizeof(pjmedia_vid_codec_param)); for (i = 0; i < src->dec_fmtp.cnt; ++i) { pj_strdup(pool, &p->dec_fmtp.param[i].name, &src->dec_fmtp.param[i].name); pj_strdup(pool, &p->dec_fmtp.param[i].val, &src->dec_fmtp.param[i].val); } for (i = 0; i < src->enc_fmtp.cnt; ++i) { pj_strdup(pool, &p->enc_fmtp.param[i].name, &src->enc_fmtp.param[i].name); pj_strdup(pool, &p->enc_fmtp.param[i].val, &src->enc_fmtp.param[i].val); } return p; } /* * Initialize codec manager. */ PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_create( pj_pool_t *pool, pjmedia_vid_codec_mgr **p_mgr) { pjmedia_vid_codec_mgr *mgr; pj_status_t status; PJ_ASSERT_RETURN(pool, PJ_EINVAL); mgr = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_codec_mgr); mgr->pf = pool->factory; pj_list_init (&mgr->factory_list); mgr->codec_cnt = 0; /* Create mutex */ status = pj_mutex_create_recursive(pool, "vid-codec-mgr", &mgr->mutex); if (status != PJ_SUCCESS) return status; if (!def_vid_codec_mgr) def_vid_codec_mgr = mgr; if (p_mgr) *p_mgr = mgr; return PJ_SUCCESS; } /* * Initialize codec manager. */ PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_destroy (pjmedia_vid_codec_mgr *mgr) { if (!mgr) mgr = def_vid_codec_mgr; PJ_ASSERT_RETURN(mgr, PJ_EINVAL); /* Destroy mutex */ if (mgr->mutex) pj_mutex_destroy(mgr->mutex); /* Just for safety, set codec manager states to zero */ pj_bzero(mgr, sizeof(pjmedia_vid_codec_mgr)); if (mgr == def_vid_codec_mgr) def_vid_codec_mgr = NULL; return PJ_SUCCESS; } PJ_DEF(pjmedia_vid_codec_mgr*) pjmedia_vid_codec_mgr_instance(void) { //pj_assert(def_vid_codec_mgr); return def_vid_codec_mgr; } PJ_DEF(void) pjmedia_vid_codec_mgr_set_instance(pjmedia_vid_codec_mgr* mgr) { def_vid_codec_mgr = mgr; } /* * Register a codec factory. */ PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_register_factory( pjmedia_vid_codec_mgr *mgr, pjmedia_vid_codec_factory *factory) { pjmedia_vid_codec_info info[PJMEDIA_CODEC_MGR_MAX_CODECS]; unsigned i, count; pj_status_t status; PJ_ASSERT_RETURN(factory, PJ_EINVAL); if (!mgr) mgr = def_vid_codec_mgr; PJ_ASSERT_RETURN(mgr, PJ_EINVAL); /* 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; icodec_desc[mgr->codec_cnt+i], &info[i], sizeof(pjmedia_vid_codec_info)); mgr->codec_desc[mgr->codec_cnt+i].prio = PJMEDIA_CODEC_PRIO_NORMAL; mgr->codec_desc[mgr->codec_cnt+i].factory = factory; pjmedia_vid_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_vid_codec_mgr_unregister_factory( pjmedia_vid_codec_mgr *mgr, pjmedia_vid_codec_factory *factory) { unsigned i; PJ_ASSERT_RETURN(factory, PJ_EINVAL); if (!mgr) mgr = def_vid_codec_mgr; PJ_ASSERT_RETURN(mgr, 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; icodec_cnt; ) { if (mgr->codec_desc[i].factory == factory) { /* 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_vid_codec_mgr_enum_codecs( pjmedia_vid_codec_mgr *mgr, unsigned *count, pjmedia_vid_codec_info codecs[], unsigned *prio) { unsigned i; PJ_ASSERT_RETURN(count && codecs, PJ_EINVAL); if (!mgr) mgr = def_vid_codec_mgr; PJ_ASSERT_RETURN(mgr, 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_vid_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 the specified payload type. */ PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_get_codec_info( pjmedia_vid_codec_mgr *mgr, unsigned pt, const pjmedia_vid_codec_info **p_info) { unsigned i; PJ_ASSERT_RETURN(p_info, PJ_EINVAL); if (!mgr) mgr = def_vid_codec_mgr; PJ_ASSERT_RETURN(mgr, PJ_EINVAL); pj_mutex_lock(mgr->mutex); for (i=0; icodec_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; } PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_get_codec_info2( pjmedia_vid_codec_mgr *mgr, pjmedia_format_id fmt_id, const pjmedia_vid_codec_info **p_info) { unsigned i; PJ_ASSERT_RETURN(p_info, PJ_EINVAL); if (!mgr) mgr = def_vid_codec_mgr; PJ_ASSERT_RETURN(mgr, PJ_EINVAL); pj_mutex_lock(mgr->mutex); for (i=0; icodec_cnt; ++i) { if (mgr->codec_desc[i].info.fmt_id == fmt_id) { *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 "H263/34". */ PJ_DEF(char*) pjmedia_vid_codec_info_to_id( const pjmedia_vid_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", (int)info->encoding_name.slen, info->encoding_name.ptr, info->pt); if (len < 1 || len >= (int)max_len) { id[0] = '\0'; return NULL; } return id; } /* * Find codecs by the unique codec identifier. */ PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_find_codecs_by_id( pjmedia_vid_codec_mgr *mgr, const pj_str_t *codec_id, unsigned *count, const pjmedia_vid_codec_info *p_info[], unsigned prio[]) { unsigned i, found = 0; PJ_ASSERT_RETURN(codec_id && count && *count, PJ_EINVAL); if (!mgr) mgr = def_vid_codec_mgr; PJ_ASSERT_RETURN(mgr, PJ_EINVAL); pj_mutex_lock(mgr->mutex); for (i=0; icodec_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_vid_codec_mgr *mgr, unsigned i, unsigned j) { pjmedia_vid_codec_desc tmp; pj_memcpy(&tmp, &mgr->codec_desc[i], sizeof(pjmedia_vid_codec_desc)); pj_memcpy(&mgr->codec_desc[i], &mgr->codec_desc[j], sizeof(pjmedia_vid_codec_desc)); pj_memcpy(&mgr->codec_desc[j], &tmp, sizeof(pjmedia_vid_codec_desc)); } /* Sort codecs in codec manager based on priorities */ static void sort_codecs(pjmedia_vid_codec_mgr *mgr) { unsigned i; /* Re-sort */ for (i=0; icodec_cnt; ++i) { unsigned j, max; for (max=i, j=i+1; jcodec_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; icodec_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_vid_codec_mgr_set_codec_priority( pjmedia_vid_codec_mgr *mgr, const pj_str_t *codec_id, pj_uint8_t prio) { unsigned i, found = 0; PJ_ASSERT_RETURN(codec_id, PJ_EINVAL); if (!mgr) mgr = def_vid_codec_mgr; PJ_ASSERT_RETURN(mgr, PJ_EINVAL); pj_mutex_lock(mgr->mutex); /* Update the priorities of affected codecs */ for (i=0; icodec_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_vid_codec_mgr_alloc_codec( pjmedia_vid_codec_mgr *mgr, const pjmedia_vid_codec_info *info, pjmedia_vid_codec **p_codec) { pjmedia_vid_codec_factory *factory; pj_status_t status; PJ_ASSERT_RETURN(info && p_codec, PJ_EINVAL); if (!mgr) mgr = def_vid_codec_mgr; PJ_ASSERT_RETURN(mgr, 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_vid_codec_mgr_get_default_param( pjmedia_vid_codec_mgr *mgr, const pjmedia_vid_codec_info *info, pjmedia_vid_codec_param *param ) { pjmedia_vid_codec_factory *factory; pj_status_t status; pjmedia_codec_id codec_id; pjmedia_vid_codec_desc *codec_desc = NULL; unsigned i; PJ_ASSERT_RETURN(info && param, PJ_EINVAL); if (!mgr) mgr = def_vid_codec_mgr; PJ_ASSERT_RETURN(mgr, PJ_EINVAL); if (!pjmedia_vid_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->def_param) { pj_memcpy(param, codec_desc->def_param->param, sizeof(pjmedia_vid_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_vid_codec_mgr_set_default_param( pjmedia_vid_codec_mgr *mgr, const pjmedia_vid_codec_info *info, const pjmedia_vid_codec_param *param ) { unsigned i; pjmedia_codec_id codec_id; pjmedia_vid_codec_desc *codec_desc = NULL; pj_pool_t *pool, *old_pool = NULL; pjmedia_vid_codec_default_param *p; PJ_ASSERT_RETURN(info, PJ_EINVAL); if (!mgr) mgr = def_vid_codec_mgr; PJ_ASSERT_RETURN(mgr, PJ_EINVAL); if (!pjmedia_vid_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 */ if (codec_desc->def_param) { pj_assert(codec_desc->def_param->pool); old_pool = codec_desc->def_param->pool; codec_desc->def_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; } /* Create new default codec param instance */ pool = pj_pool_create(mgr->pf, (char*)codec_id, 256, 256, NULL); codec_desc->def_param = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_codec_default_param); p = codec_desc->def_param; p->pool = pool; /* Update codec default param */ p->param = pjmedia_vid_codec_param_clone(pool, param); if (!p->param) { pj_mutex_unlock(mgr->mutex); return PJ_EINVAL; } codec_desc->def_param = p; pj_mutex_unlock(mgr->mutex); /* Release old pool at the very end, as application tends to apply changes * to the existing/old codec param fetched using * pjmedia_vid_codec_mgr_get_default_param() which doesn't do deep clone. */ if (old_pool) pj_pool_release(old_pool); return PJ_SUCCESS; } /* * Dealloc codec. */ PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_dealloc_codec(pjmedia_vid_codec_mgr *mgr, pjmedia_vid_codec *codec) { PJ_ASSERT_RETURN(codec, PJ_EINVAL); if (!mgr) mgr = def_vid_codec_mgr; PJ_ASSERT_RETURN(mgr, PJ_EINVAL); return (*codec->factory->op->dealloc_codec)(codec->factory, codec); } #endif /* PJMEDIA_HAS_VIDEO */