/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2010, Digium, Inc. * * David Vossel * Mark Spencer * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! * \file * \brief Format API * * \author David Vossel * \author Mark Spencer */ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision$"); #include "asterisk/_private.h" #include "asterisk/version.h" #include "asterisk/format.h" #include "asterisk/astobj2.h" #include "asterisk/lock.h" #include "asterisk/frame.h" #include "asterisk/utils.h" #include "asterisk/cli.h" #include "asterisk/rtp_engine.h" #include "asterisk/config.h" #define FORMAT_CONFIG "codecs.conf" /*! This is the container for all the format attribute interfaces. * An ao2 container was chosen for fast lookup. */ static struct ao2_container *interfaces; /*! This is the lock used to protect the interfaces container. Yes, ao2_containers * do have their own locking, but we need the capability of performing read/write * locks on this specific container. */ static ast_rwlock_t ilock; /*! a wrapper is used put interfaces into the ao2 container. */ struct interface_ao2_wrapper { enum ast_format_id id; const struct ast_format_attr_interface *interface; /*! a read write lock must be used to protect the wrapper instead * of the ao2 lock. */ ast_rwlock_t wraplock; }; /*! \brief Format List container, This container is never directly accessed outside * of this file, and It only exists for building the format_list_array. */ static struct ao2_container *format_list; /*! \brief Format List array is a read only array protected by a read write lock. * This array may be used outside this file with the use of reference counting to * guarantee safety for access by multiple threads. */ static struct ast_format_list *format_list_array; static size_t format_list_array_len = 0; /*! \brief Locks the format list array so a reference can be taken safely. */ static ast_rwlock_t format_list_array_lock; static int interface_cmp_cb(void *obj, void *arg, int flags) { struct interface_ao2_wrapper *wrapper1 = obj; struct interface_ao2_wrapper *wrapper2 = arg; return (wrapper2->id == wrapper1->id) ? CMP_MATCH | CMP_STOP : 0; } static int interface_hash_cb(const void *obj, const int flags) { const struct interface_ao2_wrapper *wrapper = obj; return wrapper->id; } static void interface_destroy_cb(void *obj) { struct interface_ao2_wrapper *wrapper = obj; ast_rwlock_destroy(&wrapper->wraplock); } void ast_format_copy(struct ast_format *dst, const struct ast_format *src) { memcpy(dst, src, sizeof(struct ast_format)); } void ast_format_set_video_mark(struct ast_format *format) { format->fattr.rtp_marker_bit = 1; } int ast_format_get_video_mark(const struct ast_format *format) { return format->fattr.rtp_marker_bit; } static int has_interface(const struct ast_format *format) { struct interface_ao2_wrapper *wrapper; struct interface_ao2_wrapper tmp_wrapper = { .id = format->id, }; ast_rwlock_rdlock(&ilock); if (!(wrapper = ao2_find(interfaces, &tmp_wrapper, (OBJ_POINTER | OBJ_NOLOCK)))) { ast_rwlock_unlock(&ilock); return 0; } ast_rwlock_unlock(&ilock); ao2_ref(wrapper, -1); return 1; } static struct interface_ao2_wrapper *find_interface(const struct ast_format *format) { struct interface_ao2_wrapper *wrapper; struct interface_ao2_wrapper tmp_wrapper = { .id = format->id, }; ast_rwlock_rdlock(&ilock); if (!(wrapper = ao2_find(interfaces, &tmp_wrapper, (OBJ_POINTER | OBJ_NOLOCK)))) { ast_rwlock_unlock(&ilock); return NULL; } ast_rwlock_unlock(&ilock); return wrapper; } /*! \internal * \brief set format attributes using an interface */ static int format_set_helper(struct ast_format *format, va_list ap) { struct interface_ao2_wrapper *wrapper; if (!(wrapper = find_interface(format))) { ast_log(LOG_WARNING, "Could not find format interface to set.\n"); return -1; } ast_rwlock_rdlock(&wrapper->wraplock); if (!wrapper->interface || !wrapper->interface->format_attr_set) { ast_rwlock_unlock(&wrapper->wraplock); ao2_ref(wrapper, -1); return -1; } wrapper->interface->format_attr_set(&format->fattr, ap); ast_rwlock_unlock(&wrapper->wraplock); ao2_ref(wrapper, -1); return 0; } struct ast_format *ast_format_append(struct ast_format *format, ... ) { va_list ap; va_start(ap, format); format_set_helper(format, ap); va_end(ap); return format; } struct ast_format *ast_format_set(struct ast_format *format, enum ast_format_id id, int set_attributes, ... ) { /* initialize the structure before setting it. */ ast_format_clear(format); format->id = id; if (set_attributes) { va_list ap; va_start(ap, set_attributes); format_set_helper(format, ap); va_end(ap); } return format; } void ast_format_clear(struct ast_format *format) { format->id = 0; memset(&format->fattr, 0, sizeof(format->fattr)); } /*! \internal * \brief determine if a list of attribute key value pairs are set on a format */ static int format_isset_helper(const struct ast_format *format, va_list ap) { int res; struct interface_ao2_wrapper *wrapper; struct ast_format tmp = { .id = format->id, .fattr = { { 0, }, }, }; if (!(wrapper = find_interface(format))) { return -1; } ast_rwlock_rdlock(&wrapper->wraplock); if (!wrapper->interface || !wrapper->interface->format_attr_set || !wrapper->interface->format_attr_cmp) { ast_rwlock_unlock(&wrapper->wraplock); ao2_ref(wrapper, -1); return -1; } /* if isset is present, use that function, else just build a new * format and use the cmp function */ if (wrapper->interface->format_attr_isset) { res = wrapper->interface->format_attr_isset(&format->fattr, ap); } else { wrapper->interface->format_attr_set(&tmp.fattr, ap); /* use our tmp structure to tell if the attributes are set or not */ res = wrapper->interface->format_attr_cmp(&tmp.fattr, &format->fattr); res = (res == AST_FORMAT_CMP_NOT_EQUAL) ? -1 : 0; } ast_rwlock_unlock(&wrapper->wraplock); ao2_ref(wrapper, -1); return res; } int ast_format_isset(const struct ast_format *format, ... ) { va_list ap; int res; va_start(ap, format); res = format_isset_helper(format, ap); va_end(ap); return res; } int ast_format_get_value(const struct ast_format *format, int key, void *value) { int res = 0; struct interface_ao2_wrapper *wrapper; if (!(wrapper = find_interface(format))) { return -1; } ast_rwlock_rdlock(&wrapper->wraplock); if (!wrapper->interface || !wrapper->interface->format_attr_get_val) { ast_rwlock_unlock(&wrapper->wraplock); ao2_ref(wrapper, -1); return -1; } res = wrapper->interface->format_attr_get_val(&format->fattr, key, value); ast_rwlock_unlock(&wrapper->wraplock); ao2_ref(wrapper, -1); return res; } /*! \internal * \brief cmp format attributes using an interface */ static enum ast_format_cmp_res format_cmp_helper(const struct ast_format *format1, const struct ast_format *format2) { enum ast_format_cmp_res res = AST_FORMAT_CMP_EQUAL; struct interface_ao2_wrapper *wrapper; if (!(wrapper = find_interface(format1))) { return res; } ast_rwlock_rdlock(&wrapper->wraplock); if (!wrapper->interface || !wrapper->interface->format_attr_cmp) { ast_rwlock_unlock(&wrapper->wraplock); ao2_ref(wrapper, -1); return res; } res = wrapper->interface->format_attr_cmp(&format1->fattr, &format2->fattr); ast_rwlock_unlock(&wrapper->wraplock); ao2_ref(wrapper, -1); return res; } enum ast_format_cmp_res ast_format_cmp(const struct ast_format *format1, const struct ast_format *format2) { if (format1->id != format2->id) { return AST_FORMAT_CMP_NOT_EQUAL; } return format_cmp_helper(format1, format2); } /*! \internal * \brief get joint format attributes using an interface */ static int format_joint_helper(const struct ast_format *format1, const struct ast_format *format2, struct ast_format *result) { int res = 0; struct interface_ao2_wrapper *wrapper; if (!(wrapper = find_interface(format1))) { /* if no interface is present, we assume formats are joint by id alone */ return res; } ast_rwlock_rdlock(&wrapper->wraplock); if (wrapper->interface && wrapper->interface->format_attr_get_joint) { res = wrapper->interface->format_attr_get_joint(&format1->fattr, &format2->fattr, &result->fattr); } ast_rwlock_unlock(&wrapper->wraplock); ao2_ref(wrapper, -1); return res; } int ast_format_joint(const struct ast_format *format1, const struct ast_format *format2, struct ast_format *result) { if (format1->id != format2->id) { return -1; } result->id = format1->id; return format_joint_helper(format1, format2, result); } uint64_t ast_format_id_to_old_bitfield(enum ast_format_id id) { switch (id) { /*! G.723.1 compression */ case AST_FORMAT_G723_1: return (1ULL << 0); /*! GSM compression */ case AST_FORMAT_GSM: return (1ULL << 1); /*! Raw mu-law data (G.711) */ case AST_FORMAT_ULAW: return (1ULL << 2); /*! Raw A-law data (G.711) */ case AST_FORMAT_ALAW: return (1ULL << 3); /*! ADPCM (G.726, 32kbps, AAL2 codeword packing) */ case AST_FORMAT_G726_AAL2: return (1ULL << 4); /*! ADPCM (IMA) */ case AST_FORMAT_ADPCM: return (1ULL << 5); /*! Raw 16-bit Signed Linear (8000 Hz) PCM */ case AST_FORMAT_SLINEAR: return (1ULL << 6); /*! LPC10, 180 samples/frame */ case AST_FORMAT_LPC10: return (1ULL << 7); /*! G.729A audio */ case AST_FORMAT_G729A: return (1ULL << 8); /*! SpeeX Free Compression */ case AST_FORMAT_SPEEX: return (1ULL << 9); /*! iLBC Free Compression */ case AST_FORMAT_ILBC: return (1ULL << 10); /*! ADPCM (G.726, 32kbps, RFC3551 codeword packing) */ case AST_FORMAT_G726: return (1ULL << 11); /*! G.722 */ case AST_FORMAT_G722: return (1ULL << 12); /*! G.722.1 (also known as Siren7, 32kbps assumed) */ case AST_FORMAT_SIREN7: return (1ULL << 13); /*! G.722.1 Annex C (also known as Siren14, 48kbps assumed) */ case AST_FORMAT_SIREN14: return (1ULL << 14); /*! Raw 16-bit Signed Linear (16000 Hz) PCM */ case AST_FORMAT_SLINEAR16: return (1ULL << 15); /*! G.719 (64 kbps assumed) */ case AST_FORMAT_G719: return (1ULL << 32); /*! SpeeX Wideband (16kHz) Free Compression */ case AST_FORMAT_SPEEX16: return (1ULL << 33); /*! Raw mu-law data (G.711) */ case AST_FORMAT_TESTLAW: return (1ULL << 47); /*! H.261 Video */ case AST_FORMAT_H261: return (1ULL << 18); /*! H.263 Video */ case AST_FORMAT_H263: return (1ULL << 19); /*! H.263+ Video */ case AST_FORMAT_H263_PLUS: return (1ULL << 20); /*! H.264 Video */ case AST_FORMAT_H264: return (1ULL << 21); /*! MPEG4 Video */ case AST_FORMAT_MP4_VIDEO: return (1ULL << 22); /*! JPEG Images */ case AST_FORMAT_JPEG: return (1ULL << 16); /*! PNG Images */ case AST_FORMAT_PNG: return (1ULL << 17); /*! T.140 RED Text format RFC 4103 */ case AST_FORMAT_T140RED: return (1ULL << 26); /*! T.140 Text format - ITU T.140, RFC 4103 */ case AST_FORMAT_T140: return (1ULL << 27); default: return 0; /* not supported by old bitfield. */ } return 0; } uint64_t ast_format_to_old_bitfield(const struct ast_format *format) { return ast_format_id_to_old_bitfield(format->id); } struct ast_format *ast_format_from_old_bitfield(struct ast_format *dst, uint64_t src) { switch (src) { /*! G.723.1 compression */ case (1ULL << 0): return ast_format_set(dst, AST_FORMAT_G723_1, 0); /*! GSM compression */ case (1ULL << 1): return ast_format_set(dst, AST_FORMAT_GSM, 0); /*! Raw mu-law data (G.711) */ case (1ULL << 2): return ast_format_set(dst, AST_FORMAT_ULAW, 0); /*! Raw A-law data (G.711) */ case (1ULL << 3): return ast_format_set(dst, AST_FORMAT_ALAW, 0); /*! ADPCM (G.726, 32kbps, AAL2 codeword packing) */ case (1ULL << 4): return ast_format_set(dst, AST_FORMAT_G726_AAL2, 0); /*! ADPCM (IMA) */ case (1ULL << 5): return ast_format_set(dst, AST_FORMAT_ADPCM, 0); /*! Raw 16-bit Signed Linear (8000 Hz) PCM */ case (1ULL << 6): return ast_format_set(dst, AST_FORMAT_SLINEAR, 0); /*! LPC10, 180 samples/frame */ case (1ULL << 7): return ast_format_set(dst, AST_FORMAT_LPC10, 0); /*! G.729A audio */ case (1ULL << 8): return ast_format_set(dst, AST_FORMAT_G729A, 0); /*! SpeeX Free Compression */ case (1ULL << 9): return ast_format_set(dst, AST_FORMAT_SPEEX, 0); /*! iLBC Free Compression */ case (1ULL << 10): return ast_format_set(dst, AST_FORMAT_ILBC, 0); /*! ADPCM (G.726, 32kbps, RFC3551 codeword packing) */ case (1ULL << 11): return ast_format_set(dst, AST_FORMAT_G726, 0); /*! G.722 */ case (1ULL << 12): return ast_format_set(dst, AST_FORMAT_G722, 0); /*! G.722.1 (also known as Siren7, 32kbps assumed) */ case (1ULL << 13): return ast_format_set(dst, AST_FORMAT_SIREN7, 0); /*! G.722.1 Annex C (also known as Siren14, 48kbps assumed) */ case (1ULL << 14): return ast_format_set(dst, AST_FORMAT_SIREN14, 0); /*! Raw 16-bit Signed Linear (16000 Hz) PCM */ case (1ULL << 15): return ast_format_set(dst, AST_FORMAT_SLINEAR16, 0); /*! G.719 (64 kbps assumed) */ case (1ULL << 32): return ast_format_set(dst, AST_FORMAT_G719, 0); /*! SpeeX Wideband (16kHz) Free Compression */ case (1ULL << 33): return ast_format_set(dst, AST_FORMAT_SPEEX16, 0); /*! Raw mu-law data (G.711) */ case (1ULL << 47): return ast_format_set(dst, AST_FORMAT_TESTLAW, 0); /*! H.261 Video */ case (1ULL << 18): return ast_format_set(dst, AST_FORMAT_H261, 0); /*! H.263 Video */ case (1ULL << 19): return ast_format_set(dst, AST_FORMAT_H263, 0); /*! H.263+ Video */ case (1ULL << 20): return ast_format_set(dst, AST_FORMAT_H263_PLUS, 0); /*! H.264 Video */ case (1ULL << 21): return ast_format_set(dst, AST_FORMAT_H264, 0); /*! MPEG4 Video */ case (1ULL << 22): return ast_format_set(dst, AST_FORMAT_MP4_VIDEO, 0); /*! JPEG Images */ case (1ULL << 16): return ast_format_set(dst, AST_FORMAT_JPEG, 0); /*! PNG Images */ case (1ULL << 17): return ast_format_set(dst, AST_FORMAT_PNG, 0); /*! T.140 RED Text format RFC 4103 */ case (1ULL << 26): return ast_format_set(dst, AST_FORMAT_T140RED, 0); /*! T.140 Text format - ITU T.140, RFC 4103 */ case (1ULL << 27): return ast_format_set(dst, AST_FORMAT_T140, 0); } ast_format_clear(dst); return NULL; } enum ast_format_id ast_format_id_from_old_bitfield(uint64_t src) { struct ast_format dst; if (ast_format_from_old_bitfield(&dst, src)) { return dst.id; } return 0; } int ast_format_is_slinear(const struct ast_format *format) { if (format->id == AST_FORMAT_SLINEAR || format->id == AST_FORMAT_SLINEAR12 || format->id == AST_FORMAT_SLINEAR16 || format->id == AST_FORMAT_SLINEAR24 || format->id == AST_FORMAT_SLINEAR32 || format->id == AST_FORMAT_SLINEAR44 || format->id == AST_FORMAT_SLINEAR48 || format->id == AST_FORMAT_SLINEAR96 || format->id == AST_FORMAT_SLINEAR192) { return 1; } return 0; } enum ast_format_id ast_format_slin_by_rate(unsigned int rate) { if (rate >= 192000) { return AST_FORMAT_SLINEAR192; } else if (rate >= 96000) { return AST_FORMAT_SLINEAR96; } else if (rate >= 48000) { return AST_FORMAT_SLINEAR48; } else if (rate >= 44100) { return AST_FORMAT_SLINEAR44; } else if (rate >= 32000) { return AST_FORMAT_SLINEAR32; } else if (rate >= 24000) { return AST_FORMAT_SLINEAR24; } else if (rate >= 16000) { return AST_FORMAT_SLINEAR16; } else if (rate >= 12000) { return AST_FORMAT_SLINEAR12; } return AST_FORMAT_SLINEAR; } const char* ast_getformatname(const struct ast_format *format) { int x; const char *ret = "unknown"; size_t f_len; const struct ast_format_list *f_list = ast_format_list_get(&f_len); for (x = 0; x < f_len; x++) { if (ast_format_cmp(&f_list[x].format, format) == AST_FORMAT_CMP_EQUAL) { ret = f_list[x].name; break; } } f_list = ast_format_list_destroy(f_list); return ret; } char *ast_getformatname_multiple_byid(char *buf, size_t size, enum ast_format_id id) { int x; unsigned len; char *start, *end = buf; size_t f_len; const struct ast_format_list *f_list = ast_format_list_get(&f_len); if (!size) { f_list = ast_format_list_destroy(f_list); return buf; } snprintf(end, size, "("); len = strlen(end); end += len; size -= len; start = end; for (x = 0; x < f_len; x++) { if (f_list[x].format.id == id) { snprintf(end, size, "%s|", f_list[x].name); len = strlen(end); end += len; size -= len; } } if (start == end) { ast_copy_string(start, "nothing)", size); } else if (size > 1) { *(end - 1) = ')'; } f_list = ast_format_list_destroy(f_list); return buf; } static struct ast_codec_alias_table { const char *alias; const char *realname; } ast_codec_alias_table[] = { { "slinear", "slin"}, { "slinear16", "slin16"}, { "g723.1", "g723"}, { "g722.1", "siren7"}, { "g722.1c", "siren14"}, }; static const char *ast_expand_codec_alias(const char *in) { int x; for (x = 0; x < ARRAY_LEN(ast_codec_alias_table); x++) { if (!strcmp(in,ast_codec_alias_table[x].alias)) return ast_codec_alias_table[x].realname; } return in; } struct ast_format *ast_getformatbyname(const char *name, struct ast_format *result) { int x; size_t f_len; const struct ast_format_list *f_list = ast_format_list_get(&f_len); for (x = 0; x < f_len; x++) { if (!strcasecmp(f_list[x].name, name) || !strcasecmp(f_list[x].name, ast_expand_codec_alias(name))) { ast_format_copy(result, &f_list[x].format); f_list = ast_format_list_destroy(f_list); return result; } } f_list = ast_format_list_destroy(f_list); return NULL; } const char *ast_codec2str(struct ast_format *format) { int x; const char *ret = "unknown"; size_t f_len; const struct ast_format_list *f_list = ast_format_list_get(&f_len); for (x = 0; x < f_len; x++) { if (ast_format_cmp(&f_list[x].format, format) == AST_FORMAT_CMP_EQUAL) { ret = f_list[x].desc; break; } } f_list = ast_format_list_destroy(f_list); return ret; } int ast_format_rate(const struct ast_format *format) { switch (format->id) { case AST_FORMAT_SLINEAR12: return 12000; case AST_FORMAT_SLINEAR24: return 24000; case AST_FORMAT_SLINEAR32: return 32000; case AST_FORMAT_SLINEAR44: return 44100; case AST_FORMAT_SLINEAR48: return 48000; case AST_FORMAT_SLINEAR96: return 96000; case AST_FORMAT_SLINEAR192: return 192000; case AST_FORMAT_G722: case AST_FORMAT_SLINEAR16: case AST_FORMAT_SIREN7: case AST_FORMAT_SPEEX16: return 16000; case AST_FORMAT_SIREN14: case AST_FORMAT_SPEEX32: return 32000; case AST_FORMAT_G719: return 48000; case AST_FORMAT_SILK: if (!(ast_format_isset(format, SILK_ATTR_KEY_SAMP_RATE, SILK_ATTR_VAL_SAMP_24KHZ, AST_FORMAT_ATTR_END))) { return 24000; } else if (!(ast_format_isset(format, SILK_ATTR_KEY_SAMP_RATE, SILK_ATTR_VAL_SAMP_16KHZ, AST_FORMAT_ATTR_END))) { return 16000; } else if (!(ast_format_isset(format, SILK_ATTR_KEY_SAMP_RATE, SILK_ATTR_VAL_SAMP_12KHZ, AST_FORMAT_ATTR_END))) { return 12000; } else { return 8000; } default: return 8000; } } static char *show_codecs(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int x, found=0; size_t f_len; const struct ast_format_list *f_list; switch (cmd) { case CLI_INIT: e->command = "core show codecs [audio|video|image|text]"; e->usage = "Usage: core show codecs [audio|video|image|text]\n" " Displays codec mapping\n"; return NULL; case CLI_GENERATE: return NULL; } if ((a->argc < 3) || (a->argc > 4)) { return CLI_SHOWUSAGE; } f_list = ast_format_list_get(&f_len); if (!ast_opt_dont_warn) { ast_cli(a->fd, "Disclaimer: this command is for informational purposes only.\n" "\tIt does not indicate anything about your configuration.\n"); } ast_cli(a->fd, "%8s %5s %8s %s\n","ID","TYPE","NAME","DESCRIPTION"); ast_cli(a->fd, "-----------------------------------------------------------------------------------\n"); for (x = 0; x < f_len; x++) { if (a->argc == 4) { if (!strcasecmp(a->argv[3], "audio")) { if (AST_FORMAT_GET_TYPE(f_list[x].format.id) != AST_FORMAT_TYPE_AUDIO) { continue; } } else if (!strcasecmp(a->argv[3], "video")) { if (AST_FORMAT_GET_TYPE(f_list[x].format.id) != AST_FORMAT_TYPE_VIDEO) { continue; } } else if (!strcasecmp(a->argv[3], "image")) { if (AST_FORMAT_GET_TYPE(f_list[x].format.id) != AST_FORMAT_TYPE_IMAGE) { continue; } } else if (!strcasecmp(a->argv[3], "text")) { if (AST_FORMAT_GET_TYPE(f_list[x].format.id) != AST_FORMAT_TYPE_TEXT) { continue; } } else { continue; } } ast_cli(a->fd, "%8u %5s %8s (%s)\n", f_list[x].format.id, (AST_FORMAT_GET_TYPE(f_list[x].format.id) == AST_FORMAT_TYPE_AUDIO) ? "audio" : (AST_FORMAT_GET_TYPE(f_list[x].format.id) == AST_FORMAT_TYPE_IMAGE) ? "image" : (AST_FORMAT_GET_TYPE(f_list[x].format.id) == AST_FORMAT_TYPE_VIDEO) ? "video" : (AST_FORMAT_GET_TYPE(f_list[x].format.id) == AST_FORMAT_TYPE_TEXT) ? "text" : "(unk)", f_list[x].name, f_list[x].desc); found = 1; } f_list = ast_format_list_destroy(f_list); if (!found) { return CLI_SHOWUSAGE; } else { return CLI_SUCCESS; } } static char *show_codec_n(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { enum ast_format_id format_id; int x, found = 0; int type_punned_codec; size_t f_len; const struct ast_format_list *f_list; switch (cmd) { case CLI_INIT: e->command = "core show codec"; e->usage = "Usage: core show codec \n" " Displays codec mapping\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 4) { return CLI_SHOWUSAGE; } if (sscanf(a->argv[3], "%30d", &type_punned_codec) != 1) { return CLI_SHOWUSAGE; } format_id = type_punned_codec; f_list = ast_format_list_get(&f_len); for (x = 0; x < f_len; x++) { if (f_list[x].format.id == format_id) { found = 1; ast_cli(a->fd, "%11u %s\n", (unsigned int) format_id, f_list[x].desc); break; } } if (!found) { ast_cli(a->fd, "Codec %d not found\n", format_id); } f_list = ast_format_list_destroy(f_list); return CLI_SUCCESS; } /* Builtin Asterisk CLI-commands for debugging */ static struct ast_cli_entry my_clis[] = { AST_CLI_DEFINE(show_codecs, "Displays a list of codecs"), AST_CLI_DEFINE(show_codec_n, "Shows a specific codec"), }; int init_framer(void) { ast_cli_register_multiple(my_clis, ARRAY_LEN(my_clis)); return 0; } static int format_list_add_custom(struct ast_format_list *new) { struct ast_format_list *entry; if (!(entry = ao2_alloc(sizeof(*entry), NULL))) { return -1; } memcpy(entry, new, sizeof(struct ast_format_list)); entry->custom_entry = 1; ao2_link(format_list, entry); return 0; } static int format_list_add_static( const struct ast_format *format, const char *name, int samplespersecond, const char *description, int fr_len, int min_ms, int max_ms, int inc_ms, int def_ms, unsigned int flags, int cur_ms) { struct ast_format_list *entry; if (!(entry = ao2_alloc(sizeof(*entry), NULL))) { return -1; } ast_format_copy(&entry->format, format); ast_copy_string(entry->name, name, sizeof(entry->name)); ast_copy_string(entry->desc, description, sizeof(entry->desc)); entry->samplespersecond = samplespersecond; entry->fr_len = fr_len; entry->min_ms = min_ms; entry->max_ms = max_ms; entry->inc_ms = inc_ms; entry->def_ms = def_ms; entry->flags = flags; entry->cur_ms = cur_ms; entry->custom_entry = 0; ao2_link(format_list, entry); return 0; } static int list_all_custom(void *obj, void *arg, int flag) { struct ast_format_list *entry = obj; return entry->custom_entry ? CMP_MATCH : 0; } static int list_cmp_cb(void *obj, void *arg, int flags) { struct ast_format_list *entry1 = obj; struct ast_format_list *entry2 = arg; return (ast_format_cmp(&entry1->format, &entry2->format) == AST_FORMAT_CMP_EQUAL) ? CMP_MATCH | CMP_STOP : 0; } static int list_hash_cb(const void *obj, const int flags) { return ao2_container_count(format_list); } const struct ast_format_list *ast_format_list_get(size_t *size) { struct ast_format_list *list; ast_rwlock_rdlock(&format_list_array_lock); ao2_ref(format_list_array, 1); list = format_list_array; *size = format_list_array_len; ast_rwlock_unlock(&format_list_array_lock); return list; } const struct ast_format_list *ast_format_list_destroy(const struct ast_format_list *list) { ao2_ref((void *) list, -1); return NULL; } static int build_format_list_array(void) { struct ast_format_list *tmp; size_t arraysize = sizeof(struct ast_format_list) * ao2_container_count(format_list); int i = 0; struct ao2_iterator it; ast_rwlock_wrlock(&format_list_array_lock); tmp = format_list_array; if (!(format_list_array = ao2_alloc(arraysize, NULL))) { format_list_array = tmp; ast_rwlock_unlock(&format_list_array_lock); return -1; } format_list_array_len = ao2_container_count(format_list); if (tmp) { ao2_ref(tmp, -1); } /* walk through the container adding elements to the static array */ it = ao2_iterator_init(format_list, 0); while ((tmp = ao2_iterator_next(&it)) && (i < format_list_array_len)) { memcpy(&format_list_array[i], tmp, sizeof(struct ast_format_list)); ao2_ref(tmp, -1); i++; } ao2_iterator_destroy(&it); ast_rwlock_unlock(&format_list_array_lock); return 0; } static int format_list_init(void) { struct ast_format tmpfmt; if (!(format_list = ao2_container_alloc(283, list_hash_cb, list_cmp_cb))) { return -1; } /* initiate static entries XXX DO NOT CHANGE THIS ORDER! */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_G723_1, 0), "g723", 8000, "G.723.1", 20, 30, 300, 30, 30, 0, 0); /*!< G723.1 */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_GSM, 0), "gsm", 8000, "GSM", 33, 20, 300, 20, 20, 0, 0); /*!< codec_gsm.c */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_ULAW, 0), "ulaw", 8000, "G.711 u-law", 80, 10, 150, 10, 20, 0, 0); /*!< codec_ulaw.c */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_ALAW, 0), "alaw", 8000, "G.711 A-law", 80, 10, 150, 10, 20, 0, 0); /*!< codec_alaw.c */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_G726, 0), "g726", 8000, "G.726 RFC3551", 40, 10, 300, 10, 20, 0, 0); /*!< codec_g726.c */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_ADPCM, 0), "adpcm" , 8000, "ADPCM", 40, 10, 300, 10, 20, 0, 0); /*!< codec_adpcm.c */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0), "slin", 8000, "16 bit Signed Linear PCM", 160, 10, 70, 10, 20, AST_SMOOTHER_FLAG_BE, 0); /*!< Signed linear */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_LPC10, 0), "lpc10", 8000, "LPC10", 7, 20, 20, 20, 20, 0, 0); /*!< codec_lpc10.c */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_G729A, 0), "g729", 8000, "G.729A", 10, 10, 230, 10, 20, AST_SMOOTHER_FLAG_G729, 0); /*!< Binary commercial distribution */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_SPEEX, 0), "speex", 8000, "SpeeX", 10, 10, 60, 10, 20, 0, 0); /*!< codec_speex.c */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_SPEEX16, 0), "speex16", 16000, "SpeeX 16khz", 10, 10, 60, 10, 20, 0, 0); /*!< codec_speex.c */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_ILBC, 0), "ilbc", 8000, "iLBC", 50, 30, 30, 30, 30, 0, 0); /*!< codec_ilbc.c */ /* inc=30ms - workaround */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_G726_AAL2, 0), "g726aal2", 8000, "G.726 AAL2", 40, 10, 300, 10, 20, 0, 0); /*!< codec_g726.c */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_G722, 0), "g722", 16000, "G722", 80, 10, 150, 10, 20, 0, 0); /*!< codec_g722.c */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR16, 0), "slin16", 16000, "16 bit Signed Linear PCM (16kHz)", 320, 10, 70, 10, 20, AST_SMOOTHER_FLAG_BE, 0);/*!< Signed linear (16kHz) */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_JPEG, 0), "jpeg", 0, "JPEG image", 0, 0, 0, 0 ,0 ,0 ,0); /*!< See format_jpeg.c */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_PNG, 0), "png", 0, "PNG image", 0, 0, 0, 0 ,0 ,0 ,0); /*!< PNG Image format */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_H261, 0), "h261", 0, "H.261 Video", 0, 0, 0, 0 ,0 ,0 ,0); /*!< H.261 Video Passthrough */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_H263, 0), "h263", 0, "H.263 Video", 0, 0, 0, 0 ,0 ,0 ,0); /*!< H.263 Passthrough support, see format_h263.c */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_H263_PLUS, 0), "h263p", 0, "H.263+ Video", 0, 0, 0,0 ,0 ,0, 0); /*!< H.263plus passthrough support See format_h263.c */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_H264, 0), "h264", 0, "H.264 Video", 0, 0, 0, 0 ,0 ,0, 0); /*!< Passthrough support, see format_h263.c */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_MP4_VIDEO, 0), "mpeg4", 0, "MPEG4 Video", 0, 0, 0, 0, 0 ,0, 0); /*!< Passthrough support for MPEG4 */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_T140RED, 0), "red", 1, "T.140 Realtime Text with redundancy", 0, 0, 0,0 ,0 ,0, 0); /*!< Redundant T.140 Realtime Text */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_T140, 0), "t140", 0, "Passthrough T.140 Realtime Text", 0, 0, 0, 0 ,0 ,0, 0); /*!< Passthrough support for T.140 Realtime Text */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_SIREN7, 0), "siren7", 16000, "ITU G.722.1 (Siren7, licensed from Polycom)", 80, 20, 80, 20, 20, 0, 0); /*!< Binary commercial distribution */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_SIREN14, 0), "siren14", 32000, "ITU G.722.1 Annex C, (Siren14, licensed from Polycom)", 120, 20, 80, 20, 20, 0, 0); /*!< Binary commercial distribution */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_TESTLAW, 0), "testlaw", 8000, "G.711 test-law", 80, 10, 150, 10, 20, 0, 0); /*!< codec_ulaw.c */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_G719, 0), "g719", 48000, "ITU G.719", 160, 20, 80, 20, 20, 0, 0); /* ORDER MAY CHANGE AFTER THIS POINT IN THE LIST */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_SPEEX32, 0), "speex32", 32000, "SpeeX 32khz", 10, 10, 60, 10, 20, 0, 0); /*!< codec_speex.c */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR12, 0), "slin12", 12000, "16 bit Signed Linear PCM (12kHz)", 240, 10, 70, 10, 20, AST_SMOOTHER_FLAG_BE, 0);/*!< Signed linear (12kHz) */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR24, 0), "slin24", 24000, "16 bit Signed Linear PCM (24kHz)", 480, 10, 70, 10, 20, AST_SMOOTHER_FLAG_BE, 0);/*!< Signed linear (24kHz) */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR32, 0), "slin32", 32000, "16 bit Signed Linear PCM (32kHz)", 640, 10, 70, 10, 20, AST_SMOOTHER_FLAG_BE, 0);/*!< Signed linear (32kHz) */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR44, 0), "slin44", 44100, "16 bit Signed Linear PCM (44kHz)", 882, 10, 70, 10, 20, AST_SMOOTHER_FLAG_BE, 0);/*!< Signed linear (44.1kHz) */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR48, 0), "slin48", 48000, "16 bit Signed Linear PCM (48kHz)", 960, 10, 70, 10, 20, AST_SMOOTHER_FLAG_BE, 0);/*!< Signed linear (48kHz) */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR96, 0), "slin96", 96000, "16 bit Signed Linear PCM (96kHz)", 1920, 10, 70, 10, 20, AST_SMOOTHER_FLAG_BE, 0);/*!< Signed linear (96kHz) */ format_list_add_static(ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR192, 0), "slin192", 192000, "16 bit Signed Linear PCM (192kHz)", 3840, 10, 70, 10, 20, AST_SMOOTHER_FLAG_BE, 0);/*!< Signed linear (192kHz) */ return 0; } int ast_format_list_init() { if (ast_rwlock_init(&format_list_array_lock)) { return -1; } if (format_list_init()) { goto init_list_cleanup; } if (build_format_list_array()) { goto init_list_cleanup; } return 0; init_list_cleanup: ast_rwlock_destroy(&format_list_array_lock); ao2_ref(format_list, -1); if (format_list_array) { ao2_ref(format_list_array, -1); } return -1; } int ast_format_attr_init() { ast_cli_register_multiple(my_clis, ARRAY_LEN(my_clis)); if (ast_rwlock_init(&ilock)) { return -1; } if (!(interfaces = ao2_container_alloc(283, interface_hash_cb, interface_cmp_cb))) { ast_rwlock_destroy(&ilock); goto init_cleanup; } return 0; init_cleanup: ast_rwlock_destroy(&ilock); if (interfaces) { ao2_ref(interfaces, -1); } return -1; } static int custom_silk_format(struct ast_format_list *entry, unsigned int maxbitrate, int usedtx, int usefec, int packetloss_percentage) { if (!entry->samplespersecond) { ast_log(LOG_WARNING, "Custom SILK format definition '%s' requires sample rate to be defined.\n", entry->name); } ast_format_set(&entry->format, AST_FORMAT_SILK, 0); if (!has_interface(&entry->format)) { return -1; } switch (entry->samplespersecond) { case 8000: ast_copy_string(entry->desc, "SILK Custom Format 8khz", sizeof(entry->desc)); ast_format_append(&entry->format, SILK_ATTR_KEY_SAMP_RATE, SILK_ATTR_VAL_SAMP_8KHZ, AST_FORMAT_ATTR_END); break; case 12000: ast_copy_string(entry->desc, "SILK Custom Format 12khz", sizeof(entry->desc)); ast_format_append(&entry->format, SILK_ATTR_KEY_SAMP_RATE, SILK_ATTR_VAL_SAMP_12KHZ, AST_FORMAT_ATTR_END); break; case 16000: ast_copy_string(entry->desc, "SILK Custom Format 16khz", sizeof(entry->desc)); ast_format_append(&entry->format, SILK_ATTR_KEY_SAMP_RATE, SILK_ATTR_VAL_SAMP_16KHZ, AST_FORMAT_ATTR_END); break; case 24000: ast_copy_string(entry->desc, "SILK Custom Format 24khz", sizeof(entry->desc)); ast_format_append(&entry->format, SILK_ATTR_KEY_SAMP_RATE, SILK_ATTR_VAL_SAMP_24KHZ, AST_FORMAT_ATTR_END); break; default: ast_log(LOG_WARNING, "Custom SILK format definition '%s' can not support sample rate %d\n", entry->name, entry->samplespersecond); return -1; } ast_format_append(&entry->format, SILK_ATTR_KEY_MAX_BITRATE, maxbitrate, SILK_ATTR_KEY_DTX, usedtx ? 1 : 0, SILK_ATTR_KEY_FEC, usefec ? 1 : 0, SILK_ATTR_KEY_PACKETLOSS_PERCENTAGE, packetloss_percentage, AST_FORMAT_ATTR_END); entry->fr_len = 80; entry->min_ms = 20; entry->max_ms = 20; entry->inc_ms = 20; entry->def_ms = 20; return 0; } static int conf_process_format_name(const char *name, enum ast_format_id *id) { if (!strcasecmp(name, "silk")) { *id = AST_FORMAT_SILK; } else { *id = 0; return -1; } return 0; } static int conf_process_sample_rate(const char *rate, unsigned int *result) { if (!strcasecmp(rate, "8000")) { *result = 8000; } else if (!strcasecmp(rate, "12000")) { *result = 12000; } else if (!strcasecmp(rate, "16000")) { *result = 16000; } else if (!strcasecmp(rate, "24000")) { *result = 24000; } else if (!strcasecmp(rate, "32000")) { *result = 32000; } else if (!strcasecmp(rate, "48000")) { *result = 48000; } else { *result = 0; return -1; } return 0; } static int load_format_config(void) { struct ast_flags config_flags = { 0, }; struct ast_config *cfg = ast_config_load(FORMAT_CONFIG, config_flags); struct ast_format_list entry; struct ast_variable *var; char *cat = NULL; int add_it = 0; struct { enum ast_format_id id; unsigned int maxbitrate; unsigned int packetloss_percentage; int usefec; int usedtx; } settings; if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEINVALID) { return 0; } /* remove all custom formats from the AO2 Container. Note, this has no affect on the * global format list until the list is rebuild. That is why this is okay to do while * reloading the config. */ ao2_callback(format_list, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, list_all_custom, NULL); while ((cat = ast_category_browse(cfg, cat))) { memset(&entry, 0, sizeof(entry)); memset(&settings, 0, sizeof(settings)); add_it = 0; if (!(ast_variable_retrieve(cfg, cat, "type"))) { continue; } ast_copy_string(entry.name, cat, sizeof(entry.name)); var = ast_variable_browse(cfg, cat); for (var = ast_variable_browse(cfg, cat); var; var = var->next) { if (!strcasecmp(var->name, "type") && conf_process_format_name(var->value, &settings.id)) { ast_log(LOG_WARNING, "Can not make custom format type for '%s' at line %d of %s\n", var->value, var->lineno, FORMAT_CONFIG); continue; } else if (!strcasecmp(var->name, "samprate") && conf_process_sample_rate(var->value, &entry.samplespersecond)) { ast_log(LOG_WARNING, "Sample rate '%s' at line %d of %s is not supported.\n", var->value, var->lineno, FORMAT_CONFIG); } else if (!strcasecmp(var->name, "maxbitrate")) { if (sscanf(var->value, "%30u", &settings.maxbitrate) != 1) { ast_log(LOG_WARNING, "maxbitrate '%s' at line %d of %s is not supported.\n", var->value, var->lineno, FORMAT_CONFIG); } } else if (!strcasecmp(var->name, "dtx")) { settings.usedtx = ast_true(var->value) ? 1 : 0; } else if (!strcasecmp(var->name, "fec")) { settings.usefec = ast_true(var->value) ? 1 : 0; } else if (!strcasecmp(var->name, "packetloss_percentage")) { if ((sscanf(var->value, "%30u", &settings.packetloss_percentage) != 1) || (settings.packetloss_percentage > 100)) { ast_log(LOG_WARNING, "packetloss_percentage '%s' at line %d of %s is not supported.\n", var->value, var->lineno, FORMAT_CONFIG); } } } switch (settings.id) { case AST_FORMAT_SILK: if (!(custom_silk_format(&entry, settings.maxbitrate, settings.usedtx, settings.usefec, settings.packetloss_percentage))) { add_it = 1; } break; default: ast_log(LOG_WARNING, "Can not create custom format %s\n", entry.name); } if (add_it) { format_list_add_custom(&entry); } } ast_config_destroy(cfg); build_format_list_array(); return 0; } int ast_format_attr_reg_interface(const struct ast_format_attr_interface *interface) { int x; size_t f_len; const struct ast_format_list *f_list; struct interface_ao2_wrapper *wrapper; struct interface_ao2_wrapper tmp_wrapper = { .id = interface->id, }; /* check for duplicates first*/ ast_rwlock_wrlock(&ilock); if ((wrapper = ao2_find(interfaces, &tmp_wrapper, (OBJ_POINTER | OBJ_NOLOCK)))) { ast_rwlock_unlock(&ilock); ast_log(LOG_WARNING, "Can not register attribute interface for format id %d, interface already exists.\n", interface->id); ao2_ref(wrapper, -1); return -1; } ast_rwlock_unlock(&ilock); if (!(wrapper = ao2_alloc(sizeof(*wrapper), interface_destroy_cb))) { return -1; } wrapper->interface = interface; wrapper->id = interface->id; ast_rwlock_init(&wrapper->wraplock); /* use the write lock whenever the interface container is modified */ ast_rwlock_wrlock(&ilock); ao2_link(interfaces, wrapper); ast_rwlock_unlock(&ilock); ao2_ref(wrapper, -1); /* This will find all custom formats in codecs.conf for this new registered interface */ load_format_config(); /* update the RTP engine to all custom formats created for this interface */ f_list = ast_format_list_get(&f_len); for (x = 0; x < f_len; x++) { if (f_list[x].format.id == tmp_wrapper.id) { ast_rtp_engine_load_format(&f_list[x].format); } } return 0; } int ast_format_attr_unreg_interface(const struct ast_format_attr_interface *interface) { int x; size_t f_len; const struct ast_format_list *f_list; struct interface_ao2_wrapper *wrapper; struct interface_ao2_wrapper tmp_wrapper = { .id = interface->id, }; /* use the write lock whenever the interface container is modified */ ast_rwlock_wrlock(&ilock); if (!(wrapper = ao2_find(interfaces, &tmp_wrapper, (OBJ_POINTER | OBJ_UNLINK | OBJ_NOLOCK)))) { ast_rwlock_unlock(&ilock); return -1; } ast_rwlock_unlock(&ilock); ast_rwlock_wrlock(&wrapper->wraplock); wrapper->interface = NULL; ast_rwlock_unlock(&wrapper->wraplock); ao2_ref(wrapper, -1); /* update the RTP engine to remove all custom formats created for this interface */ f_list = ast_format_list_get(&f_len); for (x = 0; x < f_len; x++) { if (f_list[x].format.id == tmp_wrapper.id) { ast_rtp_engine_unload_format(&f_list[x].format); } } /* This will remove all custom formats previously created for this interface */ load_format_config(); return 0; }