From 2bb9d7ff8e91ed7f026d715f27bf187739c5e8ae Mon Sep 17 00:00:00 2001 From: Andre Heider Date: Thu, 27 May 2021 13:46:52 +0200 Subject: [PATCH 1/7] add rewrite support using libopusenc formats.conf is parsed to allow setting two encoder settings: [opus] complexity=4 maxaveragebitrate=20000 --- Makefile | 7 + formats/format_ogg_opus_open_source.c | 178 ++++++++++++++++++++++++-- 2 files changed, 177 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index c8032a1..e610d59 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,9 @@ prefix=/usr/local exec_prefix=$(prefix) libdir=$(exec_prefix)/lib +# build with `make OPUSENC=0` to disable rewrite support using libopusenc +OPUSENC?=1 + CFLAGS=-pthread -D_FORTIFY_SOURCE=2 -fPIC DEBUG=-g3 OPTIMIZE=-O3 @@ -39,6 +42,10 @@ format_ogg_opus_open_source: CPATH+=-I/usr/include/opus format_ogg_opus_open_source: LIBS+=-lopus -lopusfile format_ogg_opus_open_source: DEFS+=-DAST_MODULE=\"format_ogg_opus_open_source\" \ -DAST_MODULE_SELF_SYM=__internal_format_ogg_opus_open_source_self +ifeq ($(OPUSENC),1) +format_ogg_opus_open_source: LIBS+=-lopusenc +format_ogg_opus_open_source: DEFS+=-DHAVE_OPUSENC +endif format_ogg_opus_open_source: formats/format_ogg_opus_open_source.so format_vp8: DEFS+=-DAST_MODULE=\"format_vp8\" \ diff --git a/formats/format_ogg_opus_open_source.c b/formats/format_ogg_opus_open_source.c index 666479b..156e30a 100644 --- a/formats/format_ogg_opus_open_source.c +++ b/formats/format_ogg_opus_open_source.c @@ -32,6 +32,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision: $") #include #include +#if defined(HAVE_OPUSENC) +#include +#endif +#include "asterisk/config.h" +#include "asterisk/opus.h" #include "asterisk/mod_format.h" #include "asterisk/utils.h" #include "asterisk/module.h" @@ -41,8 +46,22 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision: $") #define SAMPLES_MAX 5760 #define BUF_SIZE (2 * SAMPLES_MAX) +#if defined(HAVE_OPUSENC) +/* Variables that can be set in formats.conf */ +static int complexity = 10; /* OPUS default */ +static int maxbitrate = CODEC_OPUS_DEFAULT_BITRATE; +#endif + struct ogg_opus_desc { OggOpusFile *of; + +#if defined(HAVE_OPUSENC) + OggOpusEnc *enc; + OggOpusComments *comments; +#endif + + int writing; + off_t writing_pcm_pos; }; static int fread_wrapper(void *_stream, unsigned char *_ptr, int _nbytes) @@ -92,19 +111,94 @@ static int ogg_opus_open(struct ast_filestream *s) return 0; } -static int ogg_opus_rewrite(struct ast_filestream *s, const char *comment) +#if defined(HAVE_OPUSENC) +static int fwrite_wrapper(void *user_data, const unsigned char *ptr, opus_int32 len) +{ + FILE *stream = user_data; + + return fwrite(ptr, 1, len, stream) != (size_t)len; +} + +static int fclose_wrapper(void *user_data) +{ + return 0; +} + +static const OpusEncCallbacks enc_callbacks = { + .write = fwrite_wrapper, + .close = fclose_wrapper, +}; + +static int ogg_opus_rewrite(struct ast_filestream *fs, const char *comment) +{ + struct ogg_opus_desc *desc = fs->_private; + int err, rate, channels, family; + + desc->writing = 1; + desc->writing_pcm_pos = 0; + + desc->comments = ope_comments_create(); + ope_comments_add(desc->comments, "ENCODER", "Asterisk PBX"); + if (comment) + ope_comments_add(desc->comments, "COMMENT", comment); + + rate = ast_format_get_sample_rate(fs->fmt->format); + channels = ast_format_get_channel_count(fs->fmt->format); + if (channels < 3) + family = 0; + else + family = 1; + + desc->enc = ope_encoder_create_callbacks(&enc_callbacks, fs->f, desc->comments, rate, channels, family, &err); + + if (!desc->enc) { + ast_log(AST_LOG_ERROR, "Error creating the OGG/Opus encoder: %s\n", ope_strerror(err)); + return -1; + } + + ope_encoder_ctl(desc->enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE)); + ope_encoder_ctl(desc->enc, OPUS_SET_COMPLEXITY(complexity)); + ope_encoder_ctl(desc->enc, OPUS_SET_BITRATE(maxbitrate)); + + return 0; +} + +static int ogg_opus_write(struct ast_filestream *fs, struct ast_frame *f) { - /* XXX Unimplemented. We currently only can read from OGG/Opus streams */ - ast_log(LOG_ERROR, "Cannot write OGG/Opus streams. Sorry :(\n"); + struct ogg_opus_desc *desc = fs->_private; + int err; + + if (!desc->writing) { + ast_log(LOG_ERROR, "This OGG/Opus stream is not set up for writing!\n"); + return -1; + } + + if (!f->datalen) + return -1; + + err = ope_encoder_write(desc->enc, f->data.ptr, f->samples); + + if (err) { + ast_log(AST_LOG_ERROR, "Error encoding OGG/Opus frame: %s\n", ope_strerror(err)); + return -1; + } + + desc->writing_pcm_pos += f->samples; + return 0; +} +#else +static int ogg_opus_rewrite(struct ast_filestream *fs, const char *comment) +{ + ast_log(LOG_ERROR, "Writing OGG/Opus streams is not built-in\n"); return -1; } static int ogg_opus_write(struct ast_filestream *fs, struct ast_frame *f) { - /* XXX Unimplemented. We currently only can read from OGG/Opus streams */ - ast_log(LOG_ERROR, "Cannot write OGG/Opus streams. Sorry :(\n"); + ast_log(LOG_ERROR, "Writing OGG/Opus streams is not built-in\n"); return -1; } +#endif static int ogg_opus_seek(struct ast_filestream *fs, off_t sample_offset, int whence) { @@ -112,6 +206,9 @@ static int ogg_opus_seek(struct ast_filestream *fs, off_t sample_offset, int whe off_t relative_pcm_pos; struct ogg_opus_desc *desc = fs->_private; + if (desc->writing) + return -1; + switch (whence) { case SEEK_SET: seek_result = op_pcm_seek(desc->of, sample_offset); @@ -141,8 +238,6 @@ static int ogg_opus_seek(struct ast_filestream *fs, off_t sample_offset, int whe static int ogg_opus_trunc(struct ast_filestream *fs) { - /* XXX Unimplemented. This is only used when recording, and we don't support that right now. */ - ast_log(LOG_ERROR, "Truncation is not supported on OGG/Opus streams!\n"); return -1; } @@ -151,6 +246,9 @@ static off_t ogg_opus_tell(struct ast_filestream *fs) struct ogg_opus_desc *desc = fs->_private; off_t pos; + if (desc->writing) + return desc->writing_pcm_pos / CODEC_OPUS_DEFAULT_SAMPLE_RATE * DEFAULT_SAMPLE_RATE; + pos = (off_t) op_pcm_tell(desc->of); if (pos < 0) { return -1; @@ -165,6 +263,11 @@ static struct ast_frame *ogg_opus_read(struct ast_filestream *fs, int *whennext) int samples_read; opus_int16 *out_buf; + if (desc->writing) { + ast_log(LOG_WARNING, "Reading is not supported on OGG/Opus in writing mode.\n"); + return NULL; + } + AST_FRAME_SET_BUFFER(&fs->fr, fs->buf, AST_FRIENDLY_OFFSET, BUF_SIZE); out_buf = (opus_int16 *) fs->fr.data.ptr; @@ -196,7 +299,15 @@ static void ogg_opus_close(struct ast_filestream *fs) { struct ogg_opus_desc *desc = fs->_private; - op_free(desc->of); + if (desc->writing) { +#if defined(HAVE_OPUSENC) + ope_encoder_drain(desc->enc); + ope_encoder_destroy(desc->enc); + ope_comments_destroy(desc->comments); +#endif + } else { + op_free(desc->of); + } } static struct ast_format_def opus_f = { @@ -214,8 +325,51 @@ static struct ast_format_def opus_f = { .desc_size = sizeof(struct ogg_opus_desc), }; +static int parse_config(int reload) +{ +#if defined(HAVE_OPUSENC) + struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + struct ast_config *cfg = ast_config_load("formats.conf", config_flags); + struct ast_variable *var; + int i, res = 0; + + if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) + return res; + + for (var = ast_variable_browse(cfg, "opus"); var; var = var->next) { + if (!strcasecmp(var->name, "complexity")) { + i = atoi(var->value); + if (i < 0 || i > 10) { + res = 1; + ast_log(LOG_ERROR, "Complexity must be in 0-10\n"); + break; + } + + complexity = i; + } else if (!strcasecmp(var->name, CODEC_OPUS_ATTR_MAX_AVERAGE_BITRATE)) { + i = atoi(var->value); + if (i < 500 || i > 512000) { + res = 1; + ast_log(LOG_ERROR, CODEC_OPUS_ATTR_MAX_AVERAGE_BITRATE " must be in 500-512000\n"); + break; + } + + maxbitrate = i; + } + } + ast_config_destroy(cfg); + + return res; +#else + return 0; +#endif +} + static int load_module(void) { + if (parse_config(0)) + return AST_MODULE_LOAD_DECLINE; + opus_f.format = ast_format_slin48; if (ast_format_def_register(&opus_f)) { return AST_MODULE_LOAD_FAILURE; @@ -223,6 +377,13 @@ static int load_module(void) return AST_MODULE_LOAD_SUCCESS; } +static int reload_module(void) +{ + if (parse_config(1)) + return AST_MODULE_LOAD_DECLINE; + return AST_MODULE_LOAD_SUCCESS; +} + static int unload_module(void) { return ast_format_def_unregister(opus_f.name); @@ -231,6 +392,7 @@ static int unload_module(void) AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "OGG/Opus audio", .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, + .reload = reload_module, .unload = unload_module, .load_pri = AST_MODPRI_APP_DEPEND ); From b8921dc4a0e5d50a4315e9e89112d9a73afe7939 Mon Sep 17 00:00:00 2001 From: Alexander Traud Date: Sat, 30 Oct 2021 19:15:54 +0200 Subject: [PATCH 2/7] Add libopusenc for autotools of the build system libopusenc is an optional library, auto-detected by the script ./configure. That script gets patched by `asterisk.patch`. That patch is changed once this Pull Request is merged. --- formats/format_ogg_opus_open_source.c | 1 + 1 file changed, 1 insertion(+) diff --git a/formats/format_ogg_opus_open_source.c b/formats/format_ogg_opus_open_source.c index 156e30a..79cdf1b 100644 --- a/formats/format_ogg_opus_open_source.c +++ b/formats/format_ogg_opus_open_source.c @@ -18,6 +18,7 @@ /*** MODULEINFO opusfile + opusenc format_ogg_opus yes ***/ From eb4799a8a26b9fd0263d6947aa7c271753f9b616 Mon Sep 17 00:00:00 2001 From: Alexander Traud Date: Sat, 30 Oct 2021 19:21:28 +0200 Subject: [PATCH 3/7] Avoid padding by compiler For example, the compiler Clang 13 warned about padding struct 'struct ogg_opus_desc' with 4 bytes to align 'writing_pcm_pos' [-Wpadded]. --- formats/format_ogg_opus_open_source.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formats/format_ogg_opus_open_source.c b/formats/format_ogg_opus_open_source.c index 79cdf1b..0d023d9 100644 --- a/formats/format_ogg_opus_open_source.c +++ b/formats/format_ogg_opus_open_source.c @@ -61,7 +61,7 @@ struct ogg_opus_desc { OggOpusComments *comments; #endif - int writing; + size_t writing; off_t writing_pcm_pos; }; From cdd85246793b95a633d0cf4023c09c56f50adf6b Mon Sep 17 00:00:00 2001 From: Alexander Traud Date: Sun, 31 Oct 2021 13:49:20 +0100 Subject: [PATCH 4/7] align with the Asterisk Coding Guidelines see --- formats/format_ogg_opus_open_source.c | 28 +++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/formats/format_ogg_opus_open_source.c b/formats/format_ogg_opus_open_source.c index 0d023d9..39f88a3 100644 --- a/formats/format_ogg_opus_open_source.c +++ b/formats/format_ogg_opus_open_source.c @@ -117,7 +117,7 @@ static int fwrite_wrapper(void *user_data, const unsigned char *ptr, opus_int32 { FILE *stream = user_data; - return fwrite(ptr, 1, len, stream) != (size_t)len; + return fwrite(ptr, 1, len, stream) != (size_t) len; } static int fclose_wrapper(void *user_data) @@ -145,10 +145,11 @@ static int ogg_opus_rewrite(struct ast_filestream *fs, const char *comment) rate = ast_format_get_sample_rate(fs->fmt->format); channels = ast_format_get_channel_count(fs->fmt->format); - if (channels < 3) + if (channels < 3) { family = 0; - else + } else { family = 1; + } desc->enc = ope_encoder_create_callbacks(&enc_callbacks, fs->f, desc->comments, rate, channels, family, &err); @@ -174,11 +175,11 @@ static int ogg_opus_write(struct ast_filestream *fs, struct ast_frame *f) return -1; } - if (!f->datalen) + if (!f->datalen) { return -1; + } err = ope_encoder_write(desc->enc, f->data.ptr, f->samples); - if (err) { ast_log(AST_LOG_ERROR, "Error encoding OGG/Opus frame: %s\n", ope_strerror(err)); return -1; @@ -207,8 +208,9 @@ static int ogg_opus_seek(struct ast_filestream *fs, off_t sample_offset, int whe off_t relative_pcm_pos; struct ogg_opus_desc *desc = fs->_private; - if (desc->writing) + if (desc->writing) { return -1; + } switch (whence) { case SEEK_SET: @@ -247,8 +249,9 @@ static off_t ogg_opus_tell(struct ast_filestream *fs) struct ogg_opus_desc *desc = fs->_private; off_t pos; - if (desc->writing) + if (desc->writing) { return desc->writing_pcm_pos / CODEC_OPUS_DEFAULT_SAMPLE_RATE * DEFAULT_SAMPLE_RATE; + } pos = (off_t) op_pcm_tell(desc->of); if (pos < 0) { @@ -334,8 +337,9 @@ static int parse_config(int reload) struct ast_variable *var; int i, res = 0; - if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) + if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) { return res; + } for (var = ast_variable_browse(cfg, "opus"); var; var = var->next) { if (!strcasecmp(var->name, "complexity")) { @@ -368,20 +372,24 @@ static int parse_config(int reload) static int load_module(void) { - if (parse_config(0)) + if (parse_config(0)) { return AST_MODULE_LOAD_DECLINE; + } opus_f.format = ast_format_slin48; if (ast_format_def_register(&opus_f)) { return AST_MODULE_LOAD_FAILURE; } + return AST_MODULE_LOAD_SUCCESS; } static int reload_module(void) { - if (parse_config(1)) + if (parse_config(1)) { return AST_MODULE_LOAD_DECLINE; + } + return AST_MODULE_LOAD_SUCCESS; } From 4c7c6a350b6a4c2f9b496f6e7485c418ae337560 Mon Sep 17 00:00:00 2001 From: Alexander Traud Date: Sun, 31 Oct 2021 14:34:31 +0100 Subject: [PATCH 5/7] leverage latest but stay compatible with 13 The symbol ast_format_get_channel_count got introduced with Asterisk 15. This module has to be compatible with Asterisk 13 and newer. --- formats/format_ogg_opus_open_source.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/formats/format_ogg_opus_open_source.c b/formats/format_ogg_opus_open_source.c index 39f88a3..338c1ce 100644 --- a/formats/format_ogg_opus_open_source.c +++ b/formats/format_ogg_opus_open_source.c @@ -144,7 +144,11 @@ static int ogg_opus_rewrite(struct ast_filestream *fs, const char *comment) ope_comments_add(desc->comments, "COMMENT", comment); rate = ast_format_get_sample_rate(fs->fmt->format); +#if defined(ASTERISK_VERSION_NUM) && (ASTERISK_VERSION_NUM < 150000) + channels = 1; +#else channels = ast_format_get_channel_count(fs->fmt->format); +#endif if (channels < 3) { family = 0; } else { From 46930fce03c867c2804cbb06c881b8cede7310ad Mon Sep 17 00:00:00 2001 From: Alexander Traud Date: Sun, 31 Oct 2021 14:45:37 +0100 Subject: [PATCH 6/7] update asterisk.patch to enable libopusenc Asterisk 13.19.0 added Opusfile with /asterisk/asterisk/commit/b5331af Consequently, there is no need to patch that in anymore. However, libopusenc has to be (optionally) detected now. --- asterisk.patch | 57 +++++++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/asterisk.patch b/asterisk.patch index 0c7d21b..e16f6b5 100644 --- a/asterisk.patch +++ b/asterisk.patch @@ -1,46 +1,37 @@ --- a/build_tools/menuselect-deps.in +++ b/build_tools/menuselect-deps.in -@@ -45,6 +45,7 @@ NEON29=@PBX_NEON29@ - OGG=@PBX_OGG@ - OPENH323=@PBX_OPENH323@ +@@ -44,2 +44,3 @@ OPUS=@PBX_OPUS@ -+OPUSFILE=@PBX_OPUSFILE@ - OSPTK=@PBX_OSPTK@ - OSS=@PBX_OSS@ - PGSQL=@PBX_PGSQL@ ++OPUSENC=@PBX_OPUSENC@ + OPUSFILE=@PBX_OPUSFILE@ --- a/configure.ac +++ b/configure.ac -@@ -468,6 +468,7 @@ AST_EXT_LIB_SETUP([NEWT], [newt], [newt]) - AST_EXT_LIB_SETUP([OGG], [OGG], [ogg]) - AST_EXT_LIB_SETUP([OPENR2], [MFR2], [openr2]) +@@ -523,2 +523,3 @@ AST_EXT_LIB_SETUP([OPUS], [Opus], [opus]) -+AST_EXT_LIB_SETUP([OPUSFILE], [Opusfile], [opusfile]) - AST_EXT_LIB_SETUP([OSPTK], [OSP Toolkit], [osptk]) - AST_EXT_LIB_SETUP([OSS], [Open Sound System], [oss]) - AST_EXT_LIB_SETUP([PGSQL], [PostgreSQL], [postgres]) -@@ -2293,6 +2294,13 @@ AST_EXT_LIB_CHECK([SS7], [ss7], [ss7_set_isup_timer], [libss7.h]) - AST_EXT_LIB_CHECK([OPENR2], [openr2], [openr2_chan_new], [openr2.h]) - - AST_EXT_LIB_CHECK([OPUS], [opus], [opus_encoder_create], [opus/opus.h]) -+# opusfile.h includes so we need to make sure that -+# either $OPUS_INCLUDE or /usr/include/opus is added to the search path. -+__opus_include=${OPUS_INCLUDE} -+if test -z "$__opus_include" -o x"$__opus_include" = x" " ; then -+ __opus_include=-I/usr/include/opus -+fi -+AST_EXT_LIB_CHECK([OPUSFILE], [opusfile], [op_open_callbacks], [opus/opusfile.h], [], [$__opus_include]) - - if test "${USE_PWLIB}" != "no"; then - if test -n "${PWLIB_DIR}"; then ++AST_EXT_LIB_SETUP([OPUSENC], [Opusenc], [opusenc]) + AST_EXT_LIB_SETUP([OPUSFILE], [Opusfile], [opusfile]) +@@ -2524,2 +2525,3 @@ + fi ++AST_PKG_CONFIG_CHECK(OPUSENC, libopusenc) + AST_EXT_LIB_CHECK([OPUSFILE], [opusfile], [op_open_callbacks], [opus/opusfile.h], [], [$__opus_include]) --- a/makeopts.in +++ b/makeopts.in -@@ -223,6 +223,9 @@ OGG_LIB=@OGG_LIB@ +@@ -228,6 +228,9 @@ OPUS_INCLUDE=@OPUS_INCLUDE@ OPUS_LIB=@OPUS_LIB@ -+OPUSFILE_INCLUDE=@OPUSFILE_INCLUDE@ -+OPUSFILE_LIB=@OPUSFILE_LIB@ ++OPUSENC_INCLUDE=@OPUSENC_INCLUDE@ ++OPUSENC_LIB=@OPUSENC_LIB@ + - OSPTK_INCLUDE=@OSPTK_INCLUDE@ - OSPTK_LIB=@OSPTK_LIB@ + OPUSFILE_INCLUDE=@OPUSFILE_INCLUDE@ + OPUSFILE_LIB=@OPUSFILE_LIB@ +--- formats/Makefile ++++ formats/Makefile +@@ -12,4 +12,6 @@ + -include $(ASTTOPDIR)/menuselect.makeopts $(ASTTOPDIR)/menuselect.makedeps + ++_ASTCFLAGS+=-DASTERISK_VERSION_NUM=${ASTERISKVERSIONNUM} ++ + MODULE_PREFIX=format + MENUSELECT_CATEGORY=FORMATS From 7116e1949b84e76b714eee2785101128ce46a0d5 Mon Sep 17 00:00:00 2001 From: Alexander Traud Date: Mon, 1 Nov 2021 09:24:53 +0100 Subject: [PATCH 7/7] completed list of header files * fcntl, frame, and logger were missing before * utils was not used before * utils and config are used only if libopusenc is enabled * format was missing --- formats/format_ogg_opus_open_source.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/formats/format_ogg_opus_open_source.c b/formats/format_ogg_opus_open_source.c index 338c1ce..5857ea2 100644 --- a/formats/format_ogg_opus_open_source.c +++ b/formats/format_ogg_opus_open_source.c @@ -31,17 +31,26 @@ ASTERISK_REGISTER_FILE() ASTERISK_FILE_VERSION(__FILE__, "$Revision: $") #endif +#include /* for SEEK_CUR, SEEK_END, SEEK_SET */ + +#include "asterisk/format_cache.h" /* for ast_format_slin48 */ +#include "asterisk/frame.h" /* for ast_frame, AST_FRIENDLY_OFFSET */ +#include "asterisk/logger.h" /* for ast_log, LOG_ERROR, AST_LOG_ERROR */ +#include "asterisk/mod_format.h" /* for ast_filestream, ast_format_def */ +#include "asterisk/module.h" +#if defined(HAVE_OPUSENC) +#include "asterisk/config.h" /* for ast_variable, ast_config_destroy */ +#include "asterisk/format.h" /* for ast_format_get_... */ +#include "asterisk/utils.h" /* for ast_flags */ +#endif + #include #include #if defined(HAVE_OPUSENC) #include #endif -#include "asterisk/config.h" + #include "asterisk/opus.h" -#include "asterisk/mod_format.h" -#include "asterisk/utils.h" -#include "asterisk/module.h" -#include "asterisk/format_cache.h" /* 120ms of 48KHz audio */ #define SAMPLES_MAX 5760