Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add rewrite support using libopusenc #23

Merged
merged 7 commits into from
Nov 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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\" \
Expand Down
57 changes: 24 additions & 33 deletions asterisk.patch
Original file line number Diff line number Diff line change
@@ -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 <opus_multistream.h> 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
208 changes: 196 additions & 12 deletions formats/format_ogg_opus_open_source.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

/*** MODULEINFO
<depend>opusfile</depend>
<use type="external">opusenc</use>
<conflict>format_ogg_opus</conflict>
<defaultenabled>yes</defaultenabled>
***/
Expand All @@ -30,19 +31,47 @@ ASTERISK_REGISTER_FILE()
ASTERISK_FILE_VERSION(__FILE__, "$Revision: $")
#endif

#include <fcntl.h> /* 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 <opus/opus.h>
#include <opus/opusfile.h>
#include "asterisk/mod_format.h"
#include "asterisk/utils.h"
#include "asterisk/module.h"
#include "asterisk/format_cache.h"
#if defined(HAVE_OPUSENC)
#include <opus/opusenc.h>
#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)
Expand Down Expand Up @@ -92,26 +121,110 @@ 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)
{
int seek_result = -1;
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);
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 = {
Expand All @@ -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;
}

Expand All @@ -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
);