From 2e79f52d7116e5529ab78972cee8081b6ffe6878 Mon Sep 17 00:00:00 2001 From: Alexander Traud Date: Tue, 19 Jul 2016 20:14:21 +0200 Subject: [PATCH] codecs: Add Codec 2 mode 2400. ASTERISK-26217 #close Change-Id: I1e45d8084683fab5f2b272bf35f4a149cea8b8d6 --- build_tools/menuselect-deps.in | 1 + codecs/codec_codec2.c | 222 +++++++++++++++++++++++++++++++ codecs/ex_codec2.h | 32 +++++ configure | 143 ++++++++++++++++++++ configure.ac | 3 + include/asterisk/autoconfig.h.in | 3 + include/asterisk/format_cache.h | 5 + main/codec_builtin.c | 25 ++++ main/format_cache.c | 10 +- makeopts.in | 3 + 10 files changed, 446 insertions(+), 1 deletion(-) create mode 100644 codecs/codec_codec2.c create mode 100644 codecs/ex_codec2.h diff --git a/build_tools/menuselect-deps.in b/build_tools/menuselect-deps.in index f194482e95..a044409555 100644 --- a/build_tools/menuselect-deps.in +++ b/build_tools/menuselect-deps.in @@ -4,6 +4,7 @@ COROSYNC=@PBX_COROSYNC@ CRYPTO=@PBX_CRYPTO@ BFD=@PBX_BFD@ BISON=@PBX_BISON@ +CODEC2=@PBX_CODEC2@ CURL=@PBX_CURL@ DAHDI=@PBX_DAHDI@ DLADDR=@PBX_DLADDR@ diff --git a/codecs/codec_codec2.c b/codecs/codec_codec2.c new file mode 100644 index 0000000000..e446854c31 --- /dev/null +++ b/codecs/codec_codec2.c @@ -0,0 +1,222 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2016, Alexander Traud + * + * Alexander Traud + * + * 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 Translate between signed linear and Codec 2 + * + * \author Alexander Traud + * + * \note http://www.rowetel.com/codec2.html + * + * \ingroup codecs + */ + +/*** MODULEINFO + codec2 + core + ***/ + +#include "asterisk.h" + +#include "asterisk/codec.h" /* for AST_MEDIA_TYPE_AUDIO */ +#include "asterisk/frame.h" /* for ast_frame */ +#include "asterisk/linkedlists.h" /* for AST_LIST_NEXT, etc */ +#include "asterisk/logger.h" /* for ast_log, etc */ +#include "asterisk/module.h" +#include "asterisk/rtp_engine.h" /* ast_rtp_engine_(un)load_format */ +#include "asterisk/translate.h" /* for ast_trans_pvt, etc */ + +#include + +#define BUFFER_SAMPLES 8000 +#define CODEC2_SAMPLES 160 /* consider codec2_samples_per_frame(.) */ +#define CODEC2_FRAME_LEN 6 /* consider codec2_bits_per_frame(.) */ + +/* Sample frame data */ +#include "asterisk/slin.h" +#include "ex_codec2.h" + +struct codec2_translator_pvt { + struct CODEC2 *state; /* May be encoder or decoder */ + int16_t buf[BUFFER_SAMPLES]; +}; + +static int codec2_new(struct ast_trans_pvt *pvt) +{ + struct codec2_translator_pvt *tmp = pvt->pvt; + + tmp->state = codec2_create(CODEC2_MODE_2400); + + if (!tmp->state) { + ast_log(LOG_ERROR, "Error creating Codec 2 conversion\n"); + return -1; + } + + return 0; +} + +/*! \brief decode and store in outbuf. */ +static int codec2tolin_framein(struct ast_trans_pvt *pvt, struct ast_frame *f) +{ + struct codec2_translator_pvt *tmp = pvt->pvt; + int x; + + for (x = 0; x < f->datalen; x += CODEC2_FRAME_LEN) { + unsigned char *src = f->data.ptr + x; + int16_t *dst = pvt->outbuf.i16 + pvt->samples; + + codec2_decode(tmp->state, dst, src); + + pvt->samples += CODEC2_SAMPLES; + pvt->datalen += CODEC2_SAMPLES * 2; + } + + return 0; +} + +/*! \brief store samples into working buffer for later decode */ +static int lintocodec2_framein(struct ast_trans_pvt *pvt, struct ast_frame *f) +{ + struct codec2_translator_pvt *tmp = pvt->pvt; + + memcpy(tmp->buf + pvt->samples, f->data.ptr, f->datalen); + pvt->samples += f->samples; + + return 0; +} + +/*! \brief encode and produce a frame */ +static struct ast_frame *lintocodec2_frameout(struct ast_trans_pvt *pvt) +{ + struct codec2_translator_pvt *tmp = pvt->pvt; + struct ast_frame *result = NULL; + struct ast_frame *last = NULL; + int samples = 0; /* output samples */ + + while (pvt->samples >= CODEC2_SAMPLES) { + struct ast_frame *current; + + /* Encode a frame of data */ + codec2_encode(tmp->state, pvt->outbuf.uc, tmp->buf + samples); + + samples += CODEC2_SAMPLES; + pvt->samples -= CODEC2_SAMPLES; + + current = ast_trans_frameout(pvt, CODEC2_FRAME_LEN, CODEC2_SAMPLES); + + if (!current) { + continue; + } else if (last) { + AST_LIST_NEXT(last, frame_list) = current; + } else { + result = current; + } + last = current; + } + + /* Move the data at the end of the buffer to the front */ + if (samples) { + memmove(tmp->buf, tmp->buf + samples, pvt->samples * 2); + } + + return result; +} + +static void codec2_destroy_stuff(struct ast_trans_pvt *pvt) +{ + struct codec2_translator_pvt *tmp = pvt->pvt; + + if (tmp->state) { + codec2_destroy(tmp->state); + } +} + +static struct ast_translator codec2tolin = { + .name = "codec2tolin", + .src_codec = { + .name = "codec2", + .type = AST_MEDIA_TYPE_AUDIO, + .sample_rate = 8000, + }, + .dst_codec = { + .name = "slin", + .type = AST_MEDIA_TYPE_AUDIO, + .sample_rate = 8000, + }, + .format = "slin", + .newpvt = codec2_new, + .framein = codec2tolin_framein, + .destroy = codec2_destroy_stuff, + .sample = codec2_sample, + .desc_size = sizeof(struct codec2_translator_pvt), + .buffer_samples = BUFFER_SAMPLES, + .buf_size = BUFFER_SAMPLES * 2, +}; + +static struct ast_translator lintocodec2 = { + .name = "lintocodec2", + .src_codec = { + .name = "slin", + .type = AST_MEDIA_TYPE_AUDIO, + .sample_rate = 8000, + }, + .dst_codec = { + .name = "codec2", + .type = AST_MEDIA_TYPE_AUDIO, + .sample_rate = 8000, + }, + .format = "codec2", + .newpvt = codec2_new, + .framein = lintocodec2_framein, + .frameout = lintocodec2_frameout, + .destroy = codec2_destroy_stuff, + .sample = slin8_sample, + .desc_size = sizeof(struct codec2_translator_pvt), + .buffer_samples = BUFFER_SAMPLES, + .buf_size = (BUFFER_SAMPLES * CODEC2_FRAME_LEN + CODEC2_SAMPLES - 1) / CODEC2_SAMPLES, +}; + +static int unload_module(void) +{ + int res = 0; + + res |= ast_rtp_engine_unload_format(ast_format_codec2); + res |= ast_unregister_translator(&lintocodec2); + res |= ast_unregister_translator(&codec2tolin); + + return res; +} + +static int load_module(void) +{ + int res = 0; + + res |= ast_register_translator(&codec2tolin); + res |= ast_register_translator(&lintocodec2); + res |= ast_rtp_engine_load_format(ast_format_codec2); + + if (res) { + unload_module(); + return AST_MODULE_LOAD_FAILURE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Codec 2 Coder/Decoder"); diff --git a/codecs/ex_codec2.h b/codecs/ex_codec2.h new file mode 100644 index 0000000000..f2f4c9723b --- /dev/null +++ b/codecs/ex_codec2.h @@ -0,0 +1,32 @@ +/*! \file + * \brief 8-bit raw data + * + * Copyright (C) 2016, Alexander Traud + * + * Distributed under the terms of the GNU General Public License + * + */ + +#include "asterisk/format_cache.h" /* for ast_format_codec2 */ +#include "asterisk/frame.h" /* for ast_frame, etc */ + +static uint8_t ex_codec2[] = { + 0xea, 0xca, 0x14, 0x85, 0x91, 0x78, +}; + +static struct ast_frame *codec2_sample(void) +{ + static struct ast_frame f = { + .frametype = AST_FRAME_VOICE, + .datalen = sizeof(ex_codec2), + .samples = CODEC2_SAMPLES, + .mallocd = 0, + .offset = 0, + .src = __PRETTY_FUNCTION__, + .data.ptr = ex_codec2, + }; + + f.subclass.format = ast_format_codec2; + + return &f; +} diff --git a/configure b/configure index 6d3faf2048..64c7683192 100755 --- a/configure +++ b/configure @@ -1152,6 +1152,10 @@ PBX_COROSYNC COROSYNC_DIR COROSYNC_INCLUDE COROSYNC_LIB +PBX_CODEC2 +CODEC2_DIR +CODEC2_INCLUDE +CODEC2_LIB PBX_CAP CAP_DIR CAP_INCLUDE @@ -1326,6 +1330,7 @@ with_bfd with_execinfo with_bluetooth with_cap +with_codec2 with_cpg with_curses with_crypt @@ -2065,6 +2070,7 @@ Optional Packages: --with-execinfo=PATH use Stack Backtrace files in PATH --with-bluetooth=PATH use Bluetooth files in PATH --with-cap=PATH use POSIX 1.e capabilities files in PATH + --with-codec2=PATH use Codec 2 Audio Decoder/Encoder files in PATH --with-cpg=PATH use Corosync files in PATH --with-curses=PATH use curses files in PATH --with-crypt=PATH use password and data encryption files in PATH @@ -8988,6 +8994,38 @@ fi + CODEC2_DESCRIP="Codec 2 Audio Decoder/Encoder" + CODEC2_OPTION="codec2" + PBX_CODEC2=0 + +# Check whether --with-codec2 was given. +if test "${with_codec2+set}" = set; then : + withval=$with_codec2; + case ${withval} in + n|no) + USE_CODEC2=no + # -1 is a magic value used by menuselect to know that the package + # was disabled, other than 'not found' + PBX_CODEC2=-1 + ;; + y|ye|yes) + ac_mandatory_list="${ac_mandatory_list} CODEC2" + ;; + *) + CODEC2_DIR="${withval}" + ac_mandatory_list="${ac_mandatory_list} CODEC2" + ;; + esac + +fi + + + + + + + + COROSYNC_DESCRIP="Corosync" COROSYNC_OPTION="cpg" PBX_COROSYNC=0 @@ -30372,6 +30410,111 @@ fi fi +if test "x${PBX_CODEC2}" != "x1" -a "${USE_CODEC2}" != "no"; then + pbxlibdir="" + # if --with-CODEC2=DIR has been specified, use it. + if test "x${CODEC2_DIR}" != "x"; then + if test -d ${CODEC2_DIR}/lib; then + pbxlibdir="-L${CODEC2_DIR}/lib" + else + pbxlibdir="-L${CODEC2_DIR}" + fi + fi + pbxfuncname="codec2_create" + if test "x${pbxfuncname}" = "x" ; then # empty lib, assume only headers + AST_CODEC2_FOUND=yes + else + ast_ext_lib_check_save_CFLAGS="${CFLAGS}" + CFLAGS="${CFLAGS} " + as_ac_Lib=`$as_echo "ac_cv_lib_codec2_${pbxfuncname}" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${pbxfuncname} in -lcodec2" >&5 +$as_echo_n "checking for ${pbxfuncname} in -lcodec2... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lcodec2 ${pbxlibdir} $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char ${pbxfuncname} (); +int +main () +{ +return ${pbxfuncname} (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + AST_CODEC2_FOUND=yes +else + AST_CODEC2_FOUND=no +fi + + CFLAGS="${ast_ext_lib_check_save_CFLAGS}" + fi + + # now check for the header. + if test "${AST_CODEC2_FOUND}" = "yes"; then + CODEC2_LIB="${pbxlibdir} -lcodec2 " + # if --with-CODEC2=DIR has been specified, use it. + if test "x${CODEC2_DIR}" != "x"; then + CODEC2_INCLUDE="-I${CODEC2_DIR}/include" + fi + CODEC2_INCLUDE="${CODEC2_INCLUDE} " + if test "xcodec2/codec2.h" = "x" ; then # no header, assume found + CODEC2_HEADER_FOUND="1" + else # check for the header + ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} ${CODEC2_INCLUDE}" + ac_fn_c_check_header_mongrel "$LINENO" "codec2/codec2.h" "ac_cv_header_codec2_codec2_h" "$ac_includes_default" +if test "x$ac_cv_header_codec2_codec2_h" = xyes; then : + CODEC2_HEADER_FOUND=1 +else + CODEC2_HEADER_FOUND=0 +fi + + + CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}" + fi + if test "x${CODEC2_HEADER_FOUND}" = "x0" ; then + CODEC2_LIB="" + CODEC2_INCLUDE="" + else + if test "x${pbxfuncname}" = "x" ; then # only checking headers -> no library + CODEC2_LIB="" + fi + PBX_CODEC2=1 + cat >>confdefs.h <<_ACEOF +#define HAVE_CODEC2 1 +_ACEOF + + fi + fi +fi + + + + if test "x${PBX_COROSYNC}" != "x1" -a "${USE_COROSYNC}" != "no"; then pbxlibdir="" # if --with-COROSYNC=DIR has been specified, use it. diff --git a/configure.ac b/configure.ac index dedfd8a2e9..96a20d0c36 100644 --- a/configure.ac +++ b/configure.ac @@ -407,6 +407,7 @@ AST_EXT_LIB_SETUP([BFD], [Debug symbol decoding], [bfd]) AST_EXT_LIB_SETUP([BKTR], [Stack Backtrace], [execinfo]) AST_EXT_LIB_SETUP([BLUETOOTH], [Bluetooth], [bluetooth]) AST_EXT_LIB_SETUP([CAP], [POSIX 1.e capabilities], [cap]) +AST_EXT_LIB_SETUP([CODEC2], [Codec 2 Audio Decoder/Encoder], [codec2]) AST_EXT_LIB_SETUP([COROSYNC], [Corosync], [cpg]) AST_EXT_LIB_SETUP_OPTIONAL([COROSYNC_CFG_STATE_TRACK], [A callback only in corosync 1.x], [COROSYNC], [cfg]) AST_EXT_LIB_SETUP([CURSES], [curses], [curses]) @@ -2336,6 +2337,8 @@ else AST_EXT_LIB_CHECK([RADIUS], [radiusclient-ng], [rc_read_config], [radiusclient-ng.h]) fi +AST_EXT_LIB_CHECK([CODEC2], [codec2], [codec2_create], [codec2/codec2.h]) + AST_EXT_LIB_CHECK([COROSYNC], [cpg], [cpg_join], [corosync/cpg.h], [-lcfg]) AST_EXT_LIB_CHECK([COROSYNC_CFG_STATE_TRACK], [cfg], [corosync_cfg_state_track], [corosync/cfg.h], [-lcfg]) diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in index 51f0f14629..7d2a08c8af 100644 --- a/include/asterisk/autoconfig.h.in +++ b/include/asterisk/autoconfig.h.in @@ -142,6 +142,9 @@ /* Define to 1 if you have the `closefrom' function. */ #undef HAVE_CLOSEFROM +/* Define to 1 if you have the Codec 2 Audio Decoder/Encoder library. */ +#undef HAVE_CODEC2 + /* Define to 1 if you have the Corosync library. */ #undef HAVE_COROSYNC diff --git a/include/asterisk/format_cache.h b/include/asterisk/format_cache.h index 3894ad21bc..6099c59ea9 100644 --- a/include/asterisk/format_cache.h +++ b/include/asterisk/format_cache.h @@ -208,6 +208,11 @@ extern struct ast_format *ast_format_siren7; */ extern struct ast_format *ast_format_opus; +/*! + * \brief Built-in cached Codec 2 format. + */ +extern struct ast_format *ast_format_codec2; + /*! * \brief Built-in cached t140 format. */ diff --git a/main/codec_builtin.c b/main/codec_builtin.c index 50fbf555c0..6fc0fd896c 100644 --- a/main/codec_builtin.c +++ b/main/codec_builtin.c @@ -107,6 +107,30 @@ static struct ast_codec g723 = { .get_length = g723_length, }; +static int codec2_samples(struct ast_frame *frame) +{ + return 160 * (frame->datalen / 6); +} + +static int codec2_length(unsigned int samples) +{ + return (samples / 160) * 6; +} + +static struct ast_codec codec2 = { + .name = "codec2", + .description = "Codec 2", + .type = AST_MEDIA_TYPE_AUDIO, + .sample_rate = 8000, + .minimum_ms = 20, + .maximum_ms = 300, + .default_ms = 20, + .minimum_bytes = 6, + .samples_count = codec2_samples, + .get_length = codec2_length, + .smooth = 1, +}; + static int none_samples(struct ast_frame *frame) { return frame->datalen; @@ -863,6 +887,7 @@ int ast_codec_builtin_init(void) { int res = 0; + res |= CODEC_REGISTER_AND_CACHE(codec2); res |= CODEC_REGISTER_AND_CACHE(g723); res |= CODEC_REGISTER_AND_CACHE(ulaw); res |= CODEC_REGISTER_AND_CACHE(alaw); diff --git a/main/format_cache.c b/main/format_cache.c index b4d4260925..c704f1c379 100644 --- a/main/format_cache.c +++ b/main/format_cache.c @@ -217,6 +217,11 @@ struct ast_format *ast_format_siren7; */ struct ast_format *ast_format_opus; +/*! + * \brief Built-in cached codec2 format. + */ +struct ast_format *ast_format_codec2; + /*! * \brief Built-in cached t140 format. */ @@ -328,6 +333,7 @@ static void format_cache_shutdown(void) ao2_replace(ast_format_testlaw, NULL); ao2_replace(ast_format_g719, NULL); ao2_replace(ast_format_opus, NULL); + ao2_replace(ast_format_codec2, NULL); ao2_replace(ast_format_jpeg, NULL); ao2_replace(ast_format_png, NULL); ao2_replace(ast_format_h261, NULL); @@ -360,7 +366,9 @@ int ast_format_cache_init(void) static void set_cached_format(const char *name, struct ast_format *format) { - if (!strcmp(name, "g723")) { + if (!strcmp(name, "codec2")) { + ao2_replace(ast_format_codec2, format); + } else if (!strcmp(name, "g723")) { ao2_replace(ast_format_g723, format); } else if (!strcmp(name, "ulaw")) { ao2_replace(ast_format_ulaw, format); diff --git a/makeopts.in b/makeopts.in index d4347daf1b..f0b0d0ef51 100644 --- a/makeopts.in +++ b/makeopts.in @@ -126,6 +126,9 @@ BFD_LIB=@BFD_LIB@ BLUETOOTH_INCLUDE=@BLUETOOTH_INCLUDE@ BLUETOOTH_LIB=@BLUETOOTH_LIB@ +CODEC2_INCLUDE=@CODEC2_INCLUDE@ +CODEC2_LIB=@CODEC2_LIB@ + CURL_INCLUDE=@CURL_INCLUDE@ CURL_LIB=@CURL_LIB@