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/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 diff --git a/formats/format_ogg_opus_open_source.c b/formats/format_ogg_opus_open_source.c index 666479b..5857ea2 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 ***/ @@ -30,19 +31,47 @@ 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 -#include "asterisk/mod_format.h" -#include "asterisk/utils.h" -#include "asterisk/module.h" -#include "asterisk/format_cache.h" +#if defined(HAVE_OPUSENC) +#include +#endif + +#include "asterisk/opus.h" /* 120ms of 48KHz audio */ #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 + + size_t writing; + off_t writing_pcm_pos; }; static int fread_wrapper(void *_stream, unsigned char *_ptr, int _nbytes) @@ -92,19 +121,99 @@ 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); +#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 { + 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 +221,10 @@ 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 +254,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 +262,10 @@ 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 +280,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 +316,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,12 +342,67 @@ 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; } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int reload_module(void) +{ + if (parse_config(1)) { + return AST_MODULE_LOAD_DECLINE; + } + return AST_MODULE_LOAD_SUCCESS; } @@ -231,6 +414,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 );