From 37ab323c13ca95a2abefc7e1af863402d5a4307f Mon Sep 17 00:00:00 2001
From: Dragos Oancea <dragos@signalwire.com>
Date: Thu, 12 Mar 2020 14:41:31 +0000
Subject: [PATCH 1/3] [mod_opusfile] add ogg/opus streams, fix Makefile for
 encoding.

[mod_opusfile] add stats, opusctl

[mod_opusfile] add unit-test using teletone
---
 src/mod/formats/mod_opusfile/Makefile.am      |   6 +
 src/mod/formats/mod_opusfile/mod_opusfile.c   | 725 +++++++++++++++++-
 .../formats/mod_opusfile/test/freeswitch.xml  |   1 +
 .../audiocheck.net_sin_1000Hz_-3dBFS_6s.opus  | Bin 0 -> 74858 bytes
 .../audiocheck.net_sin_1000Hz_-3dBFS_6s.wav   | Bin 0 -> 576046 bytes
 .../test/sounds/opusfile-test-ogg.bitstream   | Bin 0 -> 31381 bytes
 .../formats/mod_opusfile/test/test_opusfile.c | 193 +++++
 7 files changed, 905 insertions(+), 20 deletions(-)
 create mode 100644 src/mod/formats/mod_opusfile/test/sounds/audiocheck.net_sin_1000Hz_-3dBFS_6s.opus
 create mode 100644 src/mod/formats/mod_opusfile/test/sounds/audiocheck.net_sin_1000Hz_-3dBFS_6s.wav
 create mode 100644 src/mod/formats/mod_opusfile/test/sounds/opusfile-test-ogg.bitstream

diff --git a/src/mod/formats/mod_opusfile/Makefile.am b/src/mod/formats/mod_opusfile/Makefile.am
index 7c776f9d56..6f4919c5b2 100644
--- a/src/mod/formats/mod_opusfile/Makefile.am
+++ b/src/mod/formats/mod_opusfile/Makefile.am
@@ -22,6 +22,12 @@ noinst_PROGRAMS = test/test_opusfile
 test_test_opusfile_SOURCES = test/test_opusfile.c
 test_test_opusfile_CFLAGS = $(AM_CFLAGS) -I./ -I../ -DSWITCH_TEST_BASE_DIR_FOR_CONF=\"${abs_builddir}/test\" -DSWITCH_TEST_BASE_DIR_OVERRIDE=\"${abs_builddir}/test\" $(OPUSFILE_DECODE_CFLAGS)
 test_test_opusfile_LDFLAGS = $(AM_LDFLAGS) -avoid-version -no-undefined $(freeswitch_LDFLAGS) $(switch_builddir)/libfreeswitch.la $(CORE_LIBS) $(APR_LIBS) $(OPUSFILE_DECODE_LIBS)
+
+if HAVE_OPUSFILE_ENCODE
+test_test_opusfile_CFLAGS += $(OPUSFILE_ENCODE_CFLAGS) -DHAVE_OPUSFILE_ENCODE
+test_test_opusfile_LDFLAGS += $(OPUSFILE_ENCODE_LIBS)
+endif 
+
 test_test_opusfile_LDADD = libopusfilemod.la $(switch_builddir)/libfreeswitch.la
 
 TESTS = $(noinst_PROGRAMS)
diff --git a/src/mod/formats/mod_opusfile/mod_opusfile.c b/src/mod/formats/mod_opusfile/mod_opusfile.c
index 25246a7ced..ea932ed0ff 100644
--- a/src/mod/formats/mod_opusfile/mod_opusfile.c
+++ b/src/mod/formats/mod_opusfile/mod_opusfile.c
@@ -42,6 +42,18 @@
 #define DEFAULT_RATE 48000 /* default fullband */
 #define OPUS_MAX_PCM 5760 /* opus recommended max output buf */
 
+#define OPUSSTREAM_MAX 64*1024 
+#define OGG_MIN_PAGE_SIZE 2400 // this much data buffered before trying to open the incoming stream
+#define OGG_MAX_PAGE_SIZE 65307 // a bit less than 64k, standard ogg
+
+#define PAGES_PER_SEC 4
+
+//#define LIMIT_DROP
+
+#ifdef LIMIT_DROP
+#define MIN_OGG_PAYLOAD 40 // drop incoming frames smaller than this (decoder)
+#endif 
+
 //#undef HAVE_OPUSFILE_ENCODE  /*don't encode anything */
 
 SWITCH_MODULE_LOAD_FUNCTION(mod_opusfile_load);
@@ -87,6 +99,53 @@ struct opus_file_context {
 
 typedef struct opus_file_context opus_file_context;
 
+struct opus_stream_context {
+	switch_file_t *fd;
+	OggOpusFile *of;
+	ogg_int64_t duration;
+	int output_seekable;
+	ogg_int64_t pcm_offset;
+	ogg_int64_t pcm_print_offset;
+	ogg_int64_t next_pcm_offset;
+	opus_int64 raw_offset;
+	ogg_int64_t nsamples;
+	opus_int32  bitrate;
+	int li;
+	int prev_li;
+	switch_mutex_t *audio_mutex;
+	switch_buffer_t *audio_buffer;
+	switch_mutex_t *ogg_mutex;
+	switch_buffer_t *ogg_buffer;
+	unsigned char ogg_data[OGG_MAX_PAGE_SIZE * 2];
+	unsigned int ogg_data_len;
+	switch_bool_t read_stream;
+	switch_bool_t dec_page_ready;
+	opus_int16 decode_buf[OPUS_MAX_PCM];
+	switch_bool_t eof;
+	switch_thread_rwlock_t *rwlock;
+	switch_file_handle_t *handle;
+	size_t samplerate;
+	int frame_size;
+	int dec_channels;
+	size_t err;
+	opus_int16 *opusbuf;
+	switch_size_t opusbuflen;
+#ifdef HAVE_OPUSFILE_ENCODE
+	OggOpusEnc *enc;
+	OggOpusComments *comments;
+	unsigned char encode_buf[OPUSSTREAM_MAX];
+	int encoded_buflen;
+	size_t samples_encode;
+	int enc_channels;
+	unsigned int enc_pagecount;
+#endif
+	unsigned int dec_count;
+	switch_thread_t *read_stream_thread;
+	switch_memory_pool_t *pool;
+};
+
+typedef struct opus_stream_context opus_stream_context_t;
+
 static struct {
 	int debug;
 } globals;
@@ -169,7 +228,6 @@ static switch_status_t switch_opusfile_open(switch_file_handle_t *handle, const
 {
 	opus_file_context *context;
 	char *ext;
-	unsigned int flags = 0;
 	int ret;
 
 	if ((ext = strrchr(path, '.')) == 0) {
@@ -190,22 +248,11 @@ static switch_status_t switch_opusfile_open(switch_file_handle_t *handle, const
 
 	switch_mutex_init(&context->audio_mutex, SWITCH_MUTEX_NESTED, context->pool);
 
-	if (switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) {
-		flags |= SWITCH_FOPEN_WRITE | SWITCH_FOPEN_CREATE;
-		if (switch_test_flag(handle, SWITCH_FILE_WRITE_APPEND) || switch_test_flag(handle, SWITCH_FILE_WRITE_OVER)) {
-			flags |= SWITCH_FOPEN_READ;
-		} else {
-			flags |= SWITCH_FOPEN_TRUNCATE;
-		}
-	}
-
 	if (switch_test_flag(handle, SWITCH_FILE_FLAG_READ)) {
 		if (switch_buffer_create_dynamic(&context->audio_buffer, TC_BUFFER_SIZE, TC_BUFFER_SIZE * 2, 0) != SWITCH_STATUS_SUCCESS) {
 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Memory Error!\n");
 			goto err;
 		}
-
-		flags |= SWITCH_FOPEN_READ;
 	}
 
 	handle->samples = 0;
@@ -289,9 +336,9 @@ static switch_status_t switch_opusfile_open(switch_file_handle_t *handle, const
 			ogg_int64_t duration;
 			opus_int64  size;
 			duration = op_pcm_total(context->of, context->li);
-			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO , "[OGG/OPUS File] Duration (samples): %u", (unsigned int)duration);
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO , "[OGG/OPUS File] Duration (samples): %u\n", (unsigned int)duration);
 			size = op_raw_total(context->of, context->li);
-			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO,"[OGG/OPUS File] Size (bytes): %u", (unsigned int)size);
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO,"[OGG/OPUS File] Size (bytes): %u\n", (unsigned int)size);
 		}
 		tags = op_tags(context->of, context->li);
 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "[OGG/OPUS File] Encoded by: %s\n", tags->vendor);
@@ -343,7 +390,7 @@ static switch_status_t switch_opusfile_seek(switch_file_handle_t *handle, unsign
 		switch_buffer_zero(context->audio_buffer);
 		ret = op_pcm_seek(context->of, samples);
 		if (globals.debug) {
-			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,"[OGG/OPUS File] seek samples: [%u]", (unsigned int)samples);
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,"[OGG/OPUS File] seek samples: [%u]\n", (unsigned int)samples);
 		}
 		if (ret == 0) {
 			handle->pos = *cur_sample = samples;
@@ -416,7 +463,7 @@ static switch_status_t switch_opusfile_write(switch_file_handle_t *handle, void
 	int err;
 	int mapping_family = 0;
 
-	opus_file_context *context = handle->private_info;
+	opus_file_context *context;
 
 	if (!handle) {
 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error no handle\n");
@@ -443,17 +490,19 @@ static switch_status_t switch_opusfile_write(switch_file_handle_t *handle, void
 	}
 
 	if (globals.debug) {
-		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,"[OGG/OPUS File] write nsamples: [%d]", (int)nsamples);
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,"[OGG/OPUS File] write nsamples: [%d]\n", (int)nsamples);
 	}
 
 	err = ope_encoder_write(context->enc, (opus_int16 *)data, nsamples);
 
 	if (err != OPE_OK) {
-		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS File] Can't encode. err: [%d] [%s]", err, ope_strerror(err));
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS File] Can't encode. err: [%d] [%s]\n", err, ope_strerror(err));
 		return SWITCH_STATUS_FALSE;
 	}
 
 	handle->sample_count += *len;
+#else
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "[OGG/OPUS File] Encoding support not built-in, build the module with libopusenc!\n");
 #endif 
 	return SWITCH_STATUS_SUCCESS;
 }
@@ -479,7 +528,7 @@ SWITCH_STANDARD_API(mod_opusfile_debug)
 			globals.debug = 1;
 			stream->write_function(stream, "OPUSFILE Debug: on\n");
 #ifdef HAVE_OPUSFILE_ENCODE
-			stream->write_function(stream, "Library version (encoding): %s\n", ope_get_version_string());
+			stream->write_function(stream, "Library version (encoding): %s ABI: %s\n", ope_get_version_string(), ope_get_abi_version());
 #endif 
 		} else if (!strcasecmp(cmd, "off")) {
 			globals.debug = 0;
@@ -491,6 +540,603 @@ SWITCH_STANDARD_API(mod_opusfile_debug)
 	return SWITCH_STATUS_SUCCESS;
 }
 
+static switch_status_t switch_opusstream_set_initial(opus_stream_context_t *context) 
+{
+	/* https://www.opus-codec.org/docs/opusfile_api-0.5/group__stream__info.html#ga9272a4a6ac9e01fbc549008f5ff58b4c */
+
+	if (context->of) {
+		int ret;
+		/* docs: "Obtain the PCM offset of the next sample to be read. " */
+		ret = op_pcm_tell(context->of);
+		if (ret != OP_EINVAL) {
+			context->pcm_offset = ret;
+		}
+		context->pcm_print_offset = context->pcm_offset - context->samplerate;
+
+		/* docs: "Obtain the current value of the position indicator for _of." */
+		ret = op_raw_tell(context->of);
+		if (ret != OP_EINVAL) {
+			context->raw_offset = ret;
+		}
+
+		/* docs: "Get the channel count of the given link in a (possibly-chained) Ogg Opus stream. " */
+		context->dec_channels = op_channel_count(context->of, -1);
+		if (context->dec_channels == 0) {
+			context->dec_channels = 1;
+		}
+
+		context->samplerate = DEFAULT_RATE;
+		return SWITCH_STATUS_SUCCESS;
+	}
+
+	return SWITCH_STATUS_FALSE;
+}
+
+static switch_status_t switch_opusstream_stream_info(opus_stream_context_t *context) 
+{
+	const OpusHead *head;
+	const OpusTags *tags;
+	opus_int32 bitrate;
+
+	if (context->of) {
+
+		/* docs: "Get the serial number of the given link in a (possibly-chained) Ogg Opus stream. "*/
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "[OGG/OPUS Stream Decode] SerialNO: [%u]\n", op_serialno(context->of, -1));
+		bitrate = op_bitrate_instant(context->of);
+		if (bitrate > 0) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "[OGG/OPUS Stream Decode] Bitrate: [%d]\n", bitrate);
+		}
+
+		if(context->pcm_offset!=0){
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Decode] Non-zero starting PCM offset: [%li]\n", 
+					(long)context->pcm_offset);
+		}
+
+		/* docs: "Retrieve the index of the current link." */
+		context->li = op_current_link(context->of);
+
+		/* docs: "Get the ID header information for the given link in a (possibly chained) Ogg Opus stream. " */
+		head = op_head(context->of, context->li);
+		if (head) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "[OGG/OPUS Stream Decode] Channels: [%i]\n", head->channel_count);
+			if (head->input_sample_rate) {
+				context->samplerate = head->input_sample_rate;
+				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "[OGG/OPUS Stream Decode] Original sampling rate: [%lu] Hz\n", 
+						(unsigned long)head->input_sample_rate);
+			}
+		}
+		/*docs: "Returns whether or not the data source being read is seekable."*/
+		if (op_seekable(context->of)) {
+			opus_int64  size;
+			context->duration = op_pcm_total(context->of, context->li); // page duration 
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO , "[OGG/OPUS Stream Decode] Duration (samples): [%u]\n", (unsigned int)context->duration);
+			size = op_raw_total(context->of, context->li);
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO,"[OGG/OPUS Stream Decode] Size (bytes): [%u]\n", (unsigned int)size);
+		}
+		/* docs: "Get the comment header information for the given link in a (possibly chained) Ogg Opus stream." */
+		tags = op_tags(context->of, context->li);
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "[OGG/OPUS Stream Decode] Encoded by: [%s]\n", tags->vendor);
+		return SWITCH_STATUS_FALSE;
+	}
+	return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_status_t switch_opusstream_stream_decode(opus_stream_context_t *context, void *data, int channels)
+{
+	int ret;
+	size_t buf_inuse;
+	switch_status_t status = SWITCH_STATUS_SUCCESS;
+
+	if (!context->of) {
+		return SWITCH_STATUS_FALSE;
+	}
+	memset(context->decode_buf, 0, sizeof(context->decode_buf));
+	switch_mutex_lock(context->audio_mutex);
+	while (!(context->eof)) {
+
+		if (channels == 1) {
+			ret = op_read(context->of, (opus_int16 *)context->decode_buf, OPUS_MAX_PCM, NULL);
+		} else if (channels > 1) {
+			ret = op_read_stereo(context->of, (opus_int16 *)context->decode_buf, OPUS_MAX_PCM);
+		} else {
+				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS Stream] Invalid number of channels!\n");
+				switch_goto_status(SWITCH_STATUS_FALSE, end);
+		}
+
+		if (ret < 0) {
+			switch(ret) {
+			case OP_HOLE:	/* There was a hole in the data, and some samples may have been skipped. Call this function again to continue decoding past the hole.*/
+				if (!context->dec_page_ready) {
+					if (globals.debug) {
+						switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Decoder]: incomplete ogg page, will retry\n");
+					}
+					switch_goto_status(SWITCH_STATUS_SUCCESS, end);
+				}
+			case OP_EREAD:	/*An underlying read operation failed. This may signal a truncation attack from an <https:> source.*/
+			
+			case OP_EFAULT: /*	An internal memory allocation failed. */
+
+			case OP_EIMPL:	/*An unseekable stream encountered a new link that used a feature that is not implemented, such as an unsupported channel family.*/
+
+			case OP_EINVAL:	/* The stream was only partially open. */
+
+			case OP_ENOTFORMAT: /*	An unseekable stream encountered a new link that did not have any logical Opus streams in it. */
+
+			case OP_EBADHEADER:	/*An unseekable stream encountered a new link with a required header packet that was not properly formatted, contained illegal values, or was missing altogether.*/
+
+			case OP_EVERSION:	/*An unseekable stream encountered a new link with an ID header that contained an unrecognized version number.*/
+
+			case OP_EBADPACKET: /*Failed to properly decode the next packet.*/
+
+			case OP_EBADLINK:		/*We failed to find data we had seen before.*/
+
+			case OP_EBADTIMESTAMP:		/*An unseekable stream encountered a new link with a starting timestamp that failed basic validity checks.*/
+
+			default:
+				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS Decoder]: error decoding stream: [%d]\n", ret);
+				switch_goto_status(SWITCH_STATUS_FALSE, end);
+			}
+		} else if (ret == 0) {
+			/*The number of samples returned may be 0 if the buffer was too small to store even a single sample for both channels, or if end-of-file was reached*/
+			if (globals.debug) {
+				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Decoder]: EOF reached [%d]\n", ret);
+			}
+
+			context->eof = TRUE;
+			break;
+		} else /* (ret > 0)*/ {
+			/*The number of samples read per channel on success*/
+			switch_buffer_write(context->audio_buffer, (opus_int16 *)context->decode_buf, ret * sizeof(opus_int16) * channels);
+			buf_inuse = switch_buffer_inuse(context->audio_buffer);
+
+			if (globals.debug) {
+				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, 
+						"[OGG/OPUS Decoder]: Read samples: %d. Wrote bytes to buffer: [%d] bytes in use: [%u] byte pos stream: [%lu]\n", 
+						ret, (int)(ret * sizeof(int16_t) * channels), (unsigned int)buf_inuse, (long unsigned int)op_raw_tell(context->of));
+			}
+		}
+	}
+
+end:
+	context->eof = FALSE; // for next page 
+
+	switch_mutex_unlock(context->audio_mutex);
+
+	return status;
+}
+
+static switch_status_t switch_opusstream_init(switch_codec_t *codec, switch_codec_flag_t flags, const switch_codec_settings_t *codec_settings)
+{
+	struct opus_stream_context *context = NULL;
+	int encoding, decoding;
+#ifdef HAVE_OPUSFILE_ENCODE
+	int err;
+#endif 
+
+	encoding = (flags & SWITCH_CODEC_FLAG_ENCODE);
+	decoding = (flags & SWITCH_CODEC_FLAG_DECODE);
+
+	if (!(encoding || decoding) || (!(context = switch_core_alloc(codec->memory_pool, sizeof(struct opus_stream_context))))) {
+		return SWITCH_STATUS_FALSE;
+	} else {
+
+		memset(context, 0, sizeof(struct opus_stream_context));
+		codec->private_info = context;
+		context->pool = codec->memory_pool;
+
+		switch_thread_rwlock_create(&(context->rwlock), context->pool);
+
+		switch_thread_rwlock_rdlock(context->rwlock);
+
+		switch_mutex_init(&context->audio_mutex, SWITCH_MUTEX_NESTED, context->pool);
+		switch_mutex_init(&context->ogg_mutex, SWITCH_MUTEX_NESTED, context->pool);
+
+		if (switch_buffer_create_dynamic(&context->audio_buffer, TC_BUFFER_SIZE, TC_BUFFER_SIZE * 2, 0) != SWITCH_STATUS_SUCCESS) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Memory Error!\n");
+			switch_thread_rwlock_unlock(context->rwlock);
+			return SWITCH_STATUS_MEMERR;
+		}
+
+		if (switch_buffer_create_dynamic(&context->ogg_buffer, TC_BUFFER_SIZE, TC_BUFFER_SIZE * 2, 0) != SWITCH_STATUS_SUCCESS) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Memory Error!\n");
+			switch_thread_rwlock_unlock(context->rwlock);
+			return SWITCH_STATUS_MEMERR;
+		}
+
+		context->samplerate = codec->implementation->actual_samples_per_second;
+		context->frame_size = codec->implementation->actual_samples_per_second * (codec->implementation->microseconds_per_packet / 1000) / 1000;
+
+		if (globals.debug) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream] frame_size: [%d]\n", (int)context->frame_size);
+		}
+#ifdef HAVE_OPUSFILE_ENCODE
+		if (encoding) {
+			if (!context->comments) {
+				context->comments = ope_comments_create();
+				ope_comments_add(context->comments, "METADATA", "Freeswitch/mod_opusfile");
+			}
+			if (!context->enc) {
+				int mapping_family = 0;
+				// opus_multistream_surround_encoder_get_size() in libopus will check these
+				if ((context->enc_channels > 2) && (context->enc_channels <= 8)) {
+					mapping_family = 1;
+				} else if ((context->enc_channels > 8) && (context->enc_channels <= 255)) {
+					// multichannel/multistream mapping family . https://people.xiph.org/~giles/2013/draft-ietf-codec-oggopus.html#rfc.section.5.1.1
+					mapping_family = 255;
+				}
+				context->enc = ope_encoder_create_pull(context->comments, !context->samplerate?DEFAULT_RATE:context->samplerate, !context->enc_channels?1:context->enc_channels, mapping_family, &err);
+
+				if (!context->enc) {
+					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS Stream Encode] Can't create stream. err: [%d] [%s]\n", err, ope_strerror(err));
+					switch_thread_rwlock_unlock(context->rwlock);
+					return SWITCH_STATUS_FALSE;
+				} else {
+					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "[OGG/OPUS Stream Encode] Stream opened for encoding\n"); 
+				}
+				ope_encoder_ctl(context->enc, OPUS_SET_COMPLEXITY_REQUEST, 5);
+				ope_encoder_ctl(context->enc, OPUS_SET_APPLICATION_REQUEST, OPUS_APPLICATION_VOIP);
+			}
+		}
+#endif 
+		switch_thread_rwlock_unlock(context->rwlock);
+		return SWITCH_STATUS_SUCCESS;
+	}
+}
+
+static switch_status_t switch_opusstream_destroy(switch_codec_t *codec)
+{
+	struct opus_stream_context *context = codec->private_info;
+	switch_status_t st;
+	
+	switch_thread_rwlock_rdlock(context->rwlock);
+	
+	if (context->read_stream_thread) {
+		switch_thread_join(&st, context->read_stream_thread);
+		if (st == SWITCH_STATUS_SUCCESS) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Encode/Decode] Joined decoding thread\n");
+		} else {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Encode/Decode] Can't join decoding thread\n");
+		}
+	}
+
+	if (context->of) {
+		op_free(context->of);
+	}
+
+#ifdef HAVE_OPUSFILE_ENCODE
+	if (context->enc) {
+		ope_encoder_destroy(context->enc);
+	}
+	if (context->comments) {
+		ope_comments_destroy(context->comments);
+	}
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Encode/Decode] Encoded pages: [%u]\n", context->enc_pagecount);
+#endif 
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Encode/Decode] Decoded chunks: [%u]\n", context->dec_count);
+	if (context->audio_buffer) {
+		switch_buffer_destroy(&context->audio_buffer);
+	}
+	if (context->ogg_buffer) {
+		switch_buffer_destroy(&context->ogg_buffer);
+	}
+	switch_thread_rwlock_unlock(context->rwlock);
+	codec->private_info = NULL;
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Encode/Decode] Stopped processing\n");
+	return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_status_t switch_opusstream_encode(switch_codec_t *codec,
+										switch_codec_t *other_codec,
+										void *decoded_data,
+										uint32_t decoded_data_len,
+										uint32_t decoded_rate,
+										void *encoded_data, 
+										uint32_t *encoded_data_len,
+										uint32_t *encoded_rate,
+										unsigned int *flag)
+{
+	switch_status_t status = SWITCH_STATUS_SUCCESS;
+#ifdef HAVE_OPUSFILE_ENCODE
+	struct opus_stream_context *context = codec->private_info;
+	size_t nsamples = (int)decoded_data_len / sizeof(int16_t);
+	int err, ret;
+	int len = 0; int thres;
+	unsigned char *decode_buf = decoded_data;
+
+	if (!context) {
+		return SWITCH_STATUS_FALSE;
+	}
+
+	globals.debug = 0;
+	switch_thread_rwlock_rdlock(context->rwlock);
+
+	if (globals.debug) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,
+				"[OGG/OPUS Stream Encode] : switch_opusfile_stream_encode() decoded_data [%x][%x][%x][%x] nsamples: [%d]\n", 
+				decode_buf[0], decode_buf[1], decode_buf[2], decode_buf[3], (int)nsamples);
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Encode] stream write nsamples: [%d]\n", (int)nsamples);
+	}
+	if (context->enc_channels == 0) {
+		context->enc_channels = 1;
+	}
+	if (!context->samplerate) {
+		context->samplerate = DEFAULT_RATE;
+	}
+
+	if (context->enc) {
+		// we reach here every 20 ms.
+		// decoded_data - this can be an interleaved buffer, to do multistream. we’ll need the exact number of channels too.
+		err = ope_encoder_write(context->enc, (opus_int16 *)decoded_data, nsamples / context->enc_channels);
+		if (err != OPE_OK) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS Stream Encode] can't encode, ret: [%d] [%s]\n", err, ope_strerror(err));
+			switch_goto_status(SWITCH_STATUS_FALSE, end);
+		}
+		context->samples_encode += nsamples;
+	}
+
+	thres = context->samplerate/PAGES_PER_SEC;
+
+	if (!(context->samples_encode % thres) && context->samples_encode > context->samplerate) {
+		if (context->enc) {
+			unsigned char *vb = context->encode_buf;
+			int req_flush = 1; 
+			/* OPE_EXPORT int ope_encoder_get_page(OggOpusEnc *enc, unsigned char **page, opus_int32 *len, int flush); */
+			ret = ope_encoder_get_page(context->enc, &vb, &len, req_flush);
+			if (ret == 0) {
+				/* ope_encoder_get_page(): ret is 1 if there is a page available, 0 if not. */
+				if (globals.debug) {
+					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Encode] can't retrieve encoded page, page not ready. ret: [%d]\n", ret);
+				}
+				switch_goto_status(SWITCH_STATUS_SUCCESS, end);
+			} else {
+				if (globals.debug) {
+					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Encode] retrieved page from encoder. ret [%d] len: [%d] [%p]\n", 
+							ret, len, context->encode_buf);
+				}
+				if (len > OGG_MAX_PAGE_SIZE) {
+					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS Stream Encode] retrieved page bigger than ogg max size!\n");
+					switch_goto_status(SWITCH_STATUS_FALSE, end);
+				}
+				memcpy(encoded_data, vb, len);
+				*encoded_data_len = len;
+				context->enc_pagecount++;
+				switch_thread_rwlock_unlock(context->rwlock);
+				return SWITCH_STATUS_SUCCESS;
+			}
+		} else {
+			switch_goto_status(SWITCH_STATUS_FALSE, end);
+		}
+	}
+end: 
+	*encoded_data_len = 0;
+	switch_thread_rwlock_unlock(context->rwlock);
+#else
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "[OGG/OPUS Stream Encode] Encoding support not built-in, build the module with libopusenc!\n");
+#endif 
+	return status;
+}
+
+// decode_stream_cb(): nbytes is OP_READ_SIZE (builtin limit - libopusfile).
+// this is being called by op_read() or op_read_stereo() - we’re giving chunks of pages to be decoded. 
+static int decode_stream_cb(void *dcontext, unsigned char *data, int nbytes) 
+{
+	opus_stream_context_t *context = (opus_stream_context_t *)dcontext;
+	unsigned int ret = 0;
+	
+	if (!context) {
+		return 0;
+	}
+
+	if (globals.debug) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Decode] decode CB called: context: %p data: %p packet_len: %d\n", 
+				(void *)context, data, nbytes);
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Decode] decode_stream_cb(): switch_thread_self(): %lx\n",  switch_thread_self());
+	}
+
+	switch_mutex_lock(context->ogg_mutex);
+	ret = switch_buffer_read(context->ogg_buffer, context->ogg_data, nbytes);
+	if (ret == 0) {
+		data = NULL;
+		switch_mutex_unlock(context->ogg_mutex);
+		if (globals.debug) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Decode] No data. Wanted: [%d] bytes\n", nbytes);
+		}
+		return ret;
+	}
+	context->dec_count++;
+	memcpy(data, context->ogg_data, ret);
+
+	if (switch_buffer_inuse(context->ogg_buffer)) {
+		context->dec_page_ready = 0;
+	} else {
+		context->dec_page_ready = 1;
+		if (globals.debug) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Decode] buffer is empty, all pages passed to the decoder\n");
+		}
+
+	}
+	switch_mutex_unlock(context->ogg_mutex);
+
+	if (globals.debug) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Decode] decode_stream_cb(): ret: %u\n",  ret);
+	}
+	return ret;
+}
+
+const OpusFileCallbacks cb={decode_stream_cb, NULL, NULL, NULL};
+
+static void *SWITCH_THREAD_FUNC read_stream_thread(switch_thread_t *thread, void *obj)
+{
+	opus_stream_context_t *context = (opus_stream_context_t *) obj;
+	int err = 0;
+	OggOpusFile *temp_of = NULL;
+	int buffered_ogg_bytes;
+
+	if (globals.debug) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Decode] read_stream_thread(): switch_thread_self(): 0x%lx\n",  switch_thread_self());
+	}
+	switch_thread_rwlock_rdlock(context->rwlock);
+
+	if ((buffered_ogg_bytes = switch_buffer_inuse(context->ogg_buffer))) {
+		if (buffered_ogg_bytes <= OGG_MAX_PAGE_SIZE) {
+			switch_buffer_peek(context->ogg_buffer, context->ogg_data, buffered_ogg_bytes);
+			context->ogg_data_len = buffered_ogg_bytes;
+		}
+	} 
+
+	/* https://mf4.xiph.org/jenkins/view/opus/job/opusfile-unix/ws/doc/html/group__stream__open__close.html#gad183ecf5fbec5add3a5ccf1e3b1d2593  */
+	/* docs: "Open a stream using the given set of callbacks to access it." */
+	temp_of = op_open_callbacks(context, &cb, (const unsigned char *)context->ogg_data, context->ogg_data_len, &err);
+	if (temp_of && (err == 0)) {
+		context->dec_page_ready = 1; 
+		context->of = temp_of;
+		if (globals.debug) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "[OGG/OPUS Stream Decode] Opened stream, installed decoding callback!\n");
+		}
+		switch_opusstream_set_initial(context);
+		switch_opusstream_stream_info(context);
+	} else if (err != 0) {
+		switch (err) {
+			case OP_EREAD:
+			   //  An underlying read, seek, or tell operation failed when it should have succeeded, or we failed to find data in the stream we had seen before. 
+			case OP_EFAULT:
+				//    There was a memory allocation failure, or an internal library error. 
+			case OP_EIMPL:
+				// The stream used a feature that is not implemented, such as an unsupported channel family. 
+			case OP_EINVAL:
+				// seek() was implemented and succeeded on this source, but tell() did not, or the starting position indicator was not equal to _initial_bytes. 
+			case OP_ENOTFORMAT:
+				// The stream contained a link that did not have any logical Opus streams in it. 
+			case OP_EBADHEADER:
+				// A required header packet was not properly formatted, contained illegal values, or was missing altogether. 
+			case OP_EVERSION:
+				// An ID header contained an unrecognized version number. 
+			case OP_EBADLINK:
+				// We failed to find data we had seen before after seeking. 
+			case OP_EBADTIMESTAMP:
+				// The first or last timestamp in a link failed basic validity checks
+			default:
+				context->dec_page_ready = 0;
+				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS Stream Decode] error opening stream: [%d]\n", err);
+		}
+	}
+
+	switch_thread_rwlock_unlock(context->rwlock);
+	return NULL;
+}
+
+static void launch_read_stream_thread(opus_stream_context_t *context)
+{
+	switch_threadattr_t *thd_attr = NULL;
+
+	switch_threadattr_create(&thd_attr, context->pool);
+	switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
+	switch_thread_create(&context->read_stream_thread, thd_attr, read_stream_thread, context, context->pool);
+}
+
+static switch_status_t switch_opusstream_decode(switch_codec_t *codec,
+										  switch_codec_t *other_codec,
+										  void *encoded_data,
+										  uint32_t encoded_data_len,
+										  uint32_t encoded_rate, 
+										  void *decoded_data, 
+										  uint32_t *decoded_data_len, 
+										  uint32_t *decoded_rate,
+										  unsigned int *flag)
+{
+	struct opus_stream_context *context = codec->private_info;
+	size_t bytes = 0; 
+	int ogg_bytes = OGG_MIN_PAGE_SIZE; // min page size before trying to open the incoming stream 
+	size_t rb = 0;
+	unsigned char *encode_buf = encoded_data;
+	size_t buffered_ogg_bytes = 0;
+	switch_status_t status = SWITCH_STATUS_SUCCESS;
+
+	if (!context) {
+		return SWITCH_STATUS_FALSE;
+	}
+	if (globals.debug) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,
+				"[OGG/OPUS Stream Decode] : switch_opusstream_decode() encoded_data [%x][%x][%x][%x] encoded_data_len: [%u]\n", 
+				encode_buf[0], encode_buf[1], encode_buf[2], encode_buf[3], encoded_data_len);
+	}
+#ifdef LIMIT_DROP
+	if ((encoded_data_len <=  MIN_OGG_PAYLOAD) && (encoded_data_len > 0)) {
+		*decoded_data_len = 0;
+		if (globals.debug) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Decode] switch_opusstream_decode(): drop [%u]", (unsigned int)encoded_data_len);
+		}
+		return SWITCH_STATUS_SUCCESS;
+	}
+#endif
+
+	switch_thread_rwlock_rdlock(context->rwlock);
+	memset(context->ogg_data, 0, sizeof(context->ogg_data)); 
+	if (encoded_data_len <= SWITCH_RECOMMENDED_BUFFER_SIZE) {
+		switch_mutex_lock(context->ogg_mutex);
+		switch_buffer_write(context->ogg_buffer, encode_buf, encoded_data_len);
+ 
+		if ((buffered_ogg_bytes = switch_buffer_inuse(context->ogg_buffer)) >= ogg_bytes) {
+			if (globals.debug) {
+				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,
+						"[OGG/OPUS Stream Decode] switch_opusstream_decode() encoded_data [%x][%x][%x][%x] encoded_data_len: %u buffered_ogg_bytes: [%u]\n", 
+						encode_buf[0], encode_buf[1], encode_buf[2], encode_buf[3], encoded_data_len, (unsigned int)buffered_ogg_bytes);
+			}
+			if (buffered_ogg_bytes <= OGG_MAX_PAGE_SIZE) {
+				switch_buffer_peek(context->ogg_buffer, context->ogg_data, buffered_ogg_bytes);
+				context->ogg_data_len = buffered_ogg_bytes;
+			}	else {
+				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS Stream Decode] buffered ogg data bigger than max OGG page size, will flush\n");
+				*decoded_data_len = 0;
+				switch_buffer_zero(context->ogg_buffer);
+				switch_mutex_unlock(context->ogg_mutex);
+				switch_goto_status(SWITCH_STATUS_SUCCESS, end);
+			}
+		}
+
+		switch_mutex_unlock(context->ogg_mutex);
+	} else {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "[OGG/OPUS Stream Decode] too much data to buffer, flushing buffer!\n");
+		*decoded_data_len = 0;
+		switch_buffer_zero(context->ogg_buffer);
+		switch_goto_status(SWITCH_STATUS_SUCCESS, end);
+	}
+
+	if ((buffered_ogg_bytes >= ogg_bytes) && encoded_data_len) {
+
+		if (!(op_test(NULL, context->ogg_data, buffered_ogg_bytes))) {
+			if (!context->read_stream && buffered_ogg_bytes > OGG_MIN_PAGE_SIZE) {
+				if (globals.debug) {
+					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Decode] launching decoding thread\n");
+				}
+				launch_read_stream_thread(context);
+				context->read_stream = 1; // mark thread started
+			}
+		} 
+	}
+	if (context->of) {
+		if (switch_opusstream_stream_decode(context, context->ogg_data, context->dec_channels) == SWITCH_STATUS_FALSE) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS Stream Decode] Cannot decode stream\n");
+			*decoded_data_len = 0;
+			switch_goto_status(SWITCH_STATUS_FALSE, end);
+		}
+	}
+	switch_mutex_lock(context->audio_mutex);
+	bytes = switch_buffer_inuse(context->audio_buffer);
+	rb = switch_buffer_read(context->audio_buffer, decoded_data, context->frame_size * sizeof(int16_t));
+	switch_mutex_unlock(context->audio_mutex);
+
+	if (globals.debug) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Decode] rb (read from audio_buffer): [%d] bytes in audio buffer: [%d]\n", (int)rb, (int)bytes);
+	}
+
+	*decoded_data_len = rb ; // bytes
+end:
+
+	switch_thread_rwlock_unlock(context->rwlock);
+	return status;
+}
+
 /* Registration */
 
 static char *supported_formats[SWITCH_MAX_CODECS] = { 0 };
@@ -499,6 +1145,10 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_opusfile_load)
 {
 	switch_file_interface_t *file_interface;
 	switch_api_interface_t *commands_api_interface;
+	switch_codec_interface_t *codec_interface;
+	int mpf = 10000, spf = 80, bpf = 160, count = 2;
+	int RATES[] = {8000, 16000, 24000, 48000};
+	int i;
 
 	supported_formats[0] = "opus";
 
@@ -522,13 +1172,48 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_opusfile_load)
 	file_interface->file_set_string = switch_opusfile_set_string;
 	file_interface->file_get_string = switch_opusfile_get_string;
 
-	
+	SWITCH_ADD_CODEC(codec_interface, "OPUSSTREAM");
+
+	for (i = 0; i < sizeof(RATES) / sizeof(RATES[0]); i++) {
+// mono
+		switch_core_codec_add_implementation(pool, codec_interface, SWITCH_CODEC_TYPE_AUDIO,
+											 98, 	 /* the IANA code number */ // does not matter
+											 "OPUSSTREAM",  /* the IANA code name */ // we just say OPUSSTREAM is an ogg/opus stream
+											 NULL,   /* default fmtp to send (can be overridden by the init function) */
+											 RATES[i], /* samples transferred per second */ // 48000 !
+											 RATES[i], /* actual samples transferred per second */
+											 16 * RATES[i] / 8000, /* bits transferred per second */
+											 mpf * count,  /* number of microseconds per frame */
+											 spf * RATES[i] / 8000, /* number of samples per frame */
+											 bpf * RATES[i] / 8000, /* number of bytes per frame decompressed */
+											 0,	/* number of bytes per frame compressed */
+											 1, /* number of channels represented */
+											 1,	/* number of frames per network packet */
+											switch_opusstream_init, switch_opusstream_encode, switch_opusstream_decode, switch_opusstream_destroy);
+// stereo
+		switch_core_codec_add_implementation(pool, codec_interface, SWITCH_CODEC_TYPE_AUDIO,
+											 98, 	 /* the IANA code number */ // does not matter
+											 "OPUSSTREAM",  /* the IANA code name */ // we just say OPUSSTREAM is an ogg/opus stream
+											 NULL,   /* default fmtp to send (can be overridden by the init function) */
+											 RATES[i], /* samples transferred per second */
+											 RATES[i], /* actual samples transferred per second */
+											 16 * RATES[i] / 8000 * 2, /* bits transferred per second */
+											 mpf * count,  /* number of microseconds per frame */
+											 spf * RATES[i] / 8000 * 2, /* number of samples per frame */
+											 bpf * RATES[i] / 8000 * 2, /* number of bytes per frame decompressed */
+											 0,	/* number of bytes per frame compressed */
+											 2, /* number of channels represented */
+											 1,	/* number of frames per network packet */
+											switch_opusstream_init, switch_opusstream_encode, switch_opusstream_decode, switch_opusstream_destroy);
+	}
+
 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "mod_opusfile loaded\n");
 
 	/* indicate that the module should continue to be loaded */
 	return SWITCH_STATUS_SUCCESS;
 }
 
+
 /* For Emacs:
  * Local Variables:
  * mode:c
diff --git a/src/mod/formats/mod_opusfile/test/freeswitch.xml b/src/mod/formats/mod_opusfile/test/freeswitch.xml
index 80591e86ac..c1fa064524 100644
--- a/src/mod/formats/mod_opusfile/test/freeswitch.xml
+++ b/src/mod/formats/mod_opusfile/test/freeswitch.xml
@@ -5,6 +5,7 @@
       <modules>
         <load module="mod_loopback"/>
         <load module="mod_opusfile"/>
+        <load module="mod_sndfile"/>
       </modules>
     </configuration>
   </section>
diff --git a/src/mod/formats/mod_opusfile/test/sounds/audiocheck.net_sin_1000Hz_-3dBFS_6s.opus b/src/mod/formats/mod_opusfile/test/sounds/audiocheck.net_sin_1000Hz_-3dBFS_6s.opus
new file mode 100644
index 0000000000000000000000000000000000000000..e18be2ec6d5567cc6a29505acc50b5b2605c9532
GIT binary patch
literal 74858
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
Yx%2F0wwUZy##$NhT-~mc)A>680mp%U-~a#s

literal 0
HcmV?d00001

diff --git a/src/mod/formats/mod_opusfile/test/sounds/audiocheck.net_sin_1000Hz_-3dBFS_6s.wav b/src/mod/formats/mod_opusfile/test/sounds/audiocheck.net_sin_1000Hz_-3dBFS_6s.wav
new file mode 100644
index 0000000000000000000000000000000000000000..98d4181e47931b5d0cee4ab08a0f023e026b0984
GIT binary patch
literal 576046
zcmeI$>vs?49RTo0BtIb{K}u-RWEgQNi8j_EF+#hQcIbljGS!oD+O$1ecWkHkuJp2<
zcB`&EOIyyS9LKOyF?VK^B1*I_ja!otnuJtyBa+O2u{WRdee=9{&hz3q&-1-}rra}O
zLjQfSA$RQ0bMo?^xiv9_(B?+GwE4zjUYigV62oJ&o|zSOV^0W&;_|yMNgX?+M`p-A
zIPuZRi>K!2Zkh3inOkP$=PsW5=;VVFLw1kMu|t-m=66377s9aGg%=(=wY|7vZ`hH(
zVbPjdD;F*wzwG&?<CiU5K5ONoHR&6|j*7k8iyu0*@WQZK=er;8$N!Qa<WKcQebhhb
zuk?rdXY+;m$oy!&<^z0y5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AXp#zz6sMAK(LgfDiBiKEMa~03YB3e1H$|0Y1P7_y8Z^
z1AKrF@Bu!+2lxOV-~)Vs5AcEipAYPdo7Vk_)NckaxFaKL=Y*t5@l!r{V0CWR^vStr
zr$zm2_fN;=6pXGM@yMV)DKooe#f4B*d;H@4XJ&tO*MYF>t1ZXYr>xzz`iqqlR}NkE
z;=cxe+JD2eZ3VlJ7M(l#;o0JEr`Lt>VTbEUb-iZ}h#p=v^4+Y_Icx4Yb#LM1^!px|
zoO<ulyDyKsX-r1uoZF7~U*0P!sdI-AuGbB#`mFTgiLHn8_O;!$dfN+|UitUl!fAyA
z){oxs#^&bjuj~#7W*m9Eq-*)W>IL;7<hNhi<%OHS{K3nE^3wB0{%Z8Jv3<sG&gnPd
z!JNm&-JP90s^X5KVdn=<Nlowh&(3ABA*40Dc4_qar%z`eD>^iM|NPIs-kH7QKig()
zOWEGE<NSY*?D^zCUUA9sD`(!Uc=>W+LkL;1lM>q{PwLa!FAN?z{Li;HjQr(YJ4Ypt
zest8bJF7B(JYwT*d1<rz?(em^+th@0?LwH|c&29E#ro2nCo_&cQv8d9nfqJ!ZrbzY
zo}PO*em?HNiZ4zdDLVe}>ED%y$`jv}HHMJbt}gyBNqc%ecWXhv_Guf3d@$@E>7QrB
zjhLHpVffp(JwEu&0io|Vy~p<$-?gdZf=CFjUCsP{Q1$Z_Ii&@s-a3(f^xY#{53f7)
z?4hoQ=M^vh@}*<(B^hVVmbG4(cWHQSmun%E#w?2K**Ux0wVoM$N>a=EjTulr@K9Py
z+S6%i1Ji%_Zr=w}3VJ=<V_VX}gpWJq#)MFK?X9}M*R-wLTM^2)oNa%)rsUbLSDkp{
zMEuvcms~%Yd*-gvf^)Mk&ab}o-Rt$)O(A?5J)!-Q_)VSX-Bgsk>gJBUzv<II<!H)x
zDf3dExb<l7ZMO{VnbG}Z*UZG+j`zk6j1Hl^>3l<S?VB|}sm}ZM+ln*iCYArFY+31h
zrIE7l%VwM#S~2%xs5*D4<CVwi&Ne=IJ%ssfFGYrQ$cV2?$m{ZA((T>mcb}7dD!DxQ
zh2+)Uf8VVlDReDLoY84b+}YULm}^lXbZH)Q?d^udx}qyZHOZIPSFfmQs9apRq_VYg
zZB=1)^yT+!@~+IOeW$*pab(j+%^~DQy%)VcvMTo19YV+Z;~(iXAYny9dBU-TR}(IG
zx}{TM{NcE~4##5C+RcsmsO^C^A&hPL>-CtX_pjd9n9(q?{!ew0y29GO)qYajSo?Hc
zm->?W&l-vvE3fuwnsWW^=KU=p3}}-TmDRR?bWL<YOlsuy$o@!Gq%yKE^2f;V$c~sh
zW0pjR=xJ>~j{3e$PMbv`gr{1oTb^s_)l%GC(EQux**D@h&F?gCZNAbxwB@OmwJo(R
T8Lj!Pi(5Z$t!Ta08p8hoW3;s;

literal 0
HcmV?d00001

diff --git a/src/mod/formats/mod_opusfile/test/sounds/opusfile-test-ogg.bitstream b/src/mod/formats/mod_opusfile/test/sounds/opusfile-test-ogg.bitstream
new file mode 100644
index 0000000000000000000000000000000000000000..65c3889027c1a98fb155fc352e4564c887c5cc47
GIT binary patch
literal 31381
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

literal 0
HcmV?d00001

diff --git a/src/mod/formats/mod_opusfile/test/test_opusfile.c b/src/mod/formats/mod_opusfile/test/test_opusfile.c
index 0cb5e8b00b..f21738bdb7 100644
--- a/src/mod/formats/mod_opusfile/test/test_opusfile.c
+++ b/src/mod/formats/mod_opusfile/test/test_opusfile.c
@@ -32,7 +32,42 @@
 #include <stdlib.h>
 
 #include <test/switch_test.h>
+#include <libteletone_detect.h>
+#include <libteletone.h>
 
+//#undef HAVE_OPUSFILE_ENCODE
+
+#define OGG_MIN_PAGE_SIZE 2400
+
+static switch_status_t test_detect_tone_in_file(const char *filepath, int rate, int freq) {
+	teletone_multi_tone_t mt;
+	teletone_tone_map_t map;
+	int16_t data[SWITCH_RECOMMENDED_BUFFER_SIZE] = { 0 };
+	size_t len = rate * 2 / 100; // in samples
+	switch_status_t status;
+	switch_file_handle_t fh = { 0 };
+
+	status = switch_core_file_open(&fh, filepath, 1, rate, SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT, NULL);
+	if (status != SWITCH_STATUS_SUCCESS) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot open file [%s]\n", filepath);
+		return SWITCH_STATUS_FALSE;
+	} 
+
+	mt.sample_rate = rate;
+	map.freqs[0] = (teletone_process_t)freq;
+
+	teletone_multi_tone_init(&mt, &map);
+
+	while (switch_core_file_read(&fh, &data, &len) == SWITCH_STATUS_SUCCESS) {
+		if (teletone_multi_tone_detect(&mt, data, len)) {
+			switch_core_file_close(&fh);
+			return SWITCH_STATUS_SUCCESS;
+		}
+	}
+
+	switch_core_file_close(&fh);
+	return SWITCH_STATUS_FALSE;
+}
 
 FST_CORE_BEGIN(".")
 {
@@ -42,6 +77,7 @@ FST_CORE_BEGIN(".")
 		{
 			fst_requires_module("mod_loopback");
 			fst_requires_module("mod_opusfile");
+			fst_requires_module("mod_sndfile");
 		}
 		FST_SETUP_END()
 
@@ -115,6 +151,163 @@ FST_CORE_BEGIN(".")
 
 			switch_sleep(1000000);
 		}
+		FST_TEST_END()
+
+		FST_TEST_BEGIN(opusfile_stream) 
+		{
+			switch_codec_t read_codec = { 0 };
+			switch_status_t status;
+			switch_codec_settings_t codec_settings = {{ 0 }};
+			unsigned char buf[SWITCH_RECOMMENDED_BUFFER_SIZE] = { 0 };
+			switch_file_handle_t fhw = { 0 };
+			uint32_t flags = 0;
+			uint32_t rate;
+			static char tmp_filename[] = "/tmp/opusfile-stream-unit_test.wav";
+			char path[4096];
+			uint32_t filerate = 48000;
+			uint32_t torate = 8000;
+			/*decode*/
+			uint32_t decoded_len;
+			size_t write_len;
+			unsigned char decbuf[SWITCH_RECOMMENDED_BUFFER_SIZE] = { 0 };
+#ifdef  HAVE_OPUSFILE_ENCODE
+			switch_file_handle_t fh = { 0 };
+			unsigned char encbuf[SWITCH_RECOMMENDED_BUFFER_SIZE] = { 0 };
+			switch_codec_t write_codec = { 0 };
+			switch_size_t len = 960;
+			uint32_t encoded_len;
+			unsigned int pages = 0;
+			/*
+			General
+			Complete name                            : sounds/audiocheck.net_sin_1000Hz_-3dBFS_6s.wav
+			Format                                   : Wave
+			File size                                : 563 KiB
+			Duration                                 : 6 s 0 ms
+			Overall bit rate mode                    : Constant
+			Overall bit rate                         : 768 kb/s
+
+			Audio
+			Format                                   : PCM
+			Format settings, Endianness              : Little
+			Format settings, Sign                    : Signed
+			Codec ID                                 : 1
+			Duration                                 : 6 s 0 ms
+			Bit rate mode                            : Constant
+			Bit rate                                 : 768 kb/s
+			Channel(s)                               : 1 channel
+			Sampling rate                            : 48.0 kHz
+			Bit depth                                : 16 bits
+			Stream size                              : 563 KiB (100%)
+			*/
+			static char filename[] = "sounds/audiocheck.net_sin_1000Hz_-3dBFS_6s.wav";
+#else 
+			static char opus_filename[] = "sounds/opusfile-test-ogg.bitstream";
+			switch_file_t *fd;
+			switch_size_t flen;
+#endif
+
+			status = switch_core_codec_init(&read_codec,
+			"OPUSSTREAM",
+			"mod_opusfile",
+			NULL,
+			filerate,
+			20,
+			1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
+			&codec_settings, fst_pool);
+			fst_check(status == SWITCH_STATUS_SUCCESS);
+
+#ifdef HAVE_OPUSFILE_ENCODE
+			status = switch_core_codec_init(&write_codec,
+			"OPUSSTREAM",
+			"mod_opusfile",
+			NULL,
+			filerate,
+			20,
+			1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
+			&codec_settings, fst_pool);
+			fst_check(status == SWITCH_STATUS_SUCCESS);
+
+			sprintf(path, "%s%s%s", SWITCH_GLOBAL_dirs.conf_dir, SWITCH_PATH_SEPARATOR, filename);
+
+			status = switch_core_file_open(&fh, path, 1, filerate, SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT, NULL);
+			fst_requires(status == SWITCH_STATUS_SUCCESS);
+
+			status = switch_core_file_open(&fhw, tmp_filename, 1, torate, SWITCH_FILE_FLAG_WRITE | SWITCH_FILE_DATA_SHORT, NULL);
+			fst_requires(status == SWITCH_STATUS_SUCCESS);
+
+			fhw.native_rate = filerate; // make sure we resample to 8000 Hz, because teletone wants this rate
+
+			while (switch_core_file_read(&fh, &buf, &len) == SWITCH_STATUS_SUCCESS) {
+
+				status = switch_core_codec_encode(&write_codec, NULL, &buf, len * sizeof(int16_t), filerate, &encbuf, &encoded_len, &rate, &flags);
+				fst_check(status == SWITCH_STATUS_SUCCESS);
+
+				if (encoded_len) {
+					pages++;
+					status = switch_core_codec_decode(&read_codec, NULL, &encbuf, encoded_len, filerate, &decbuf, &decoded_len, &rate, &flags);
+					fst_check(status == SWITCH_STATUS_SUCCESS);
+					write_len = decoded_len / sizeof(int16_t);
+					if (write_len) switch_core_file_write(&fhw, &decbuf, &write_len);
+				}
+			}
+
+			// continue reading, encoded pages are buffered
+			while (switch_core_codec_decode(&read_codec, NULL, &encbuf, 0, filerate, &decbuf, &decoded_len, &rate, &flags) == SWITCH_STATUS_SUCCESS && decoded_len) {
+				write_len = decoded_len / sizeof(int16_t);
+				status = switch_core_file_write(&fhw, &decbuf, &write_len);
+				fst_check(status == SWITCH_STATUS_SUCCESS);
+			}
+
+			switch_core_codec_destroy(&write_codec);
+
+			status = switch_core_file_close(&fh);
+			fst_check(status == SWITCH_STATUS_SUCCESS);
+
+#else	
+			// the test will perform only decoding
+
+			sprintf(path, "%s%s%s", SWITCH_GLOBAL_dirs.conf_dir, SWITCH_PATH_SEPARATOR, opus_filename);
+
+			// open the file raw and buffer data to the decoder
+			status = switch_file_open(&fd, path, SWITCH_FOPEN_READ, SWITCH_FPROT_UREAD, fst_pool);
+			fst_requires(status == SWITCH_STATUS_SUCCESS);
+
+			status = switch_core_file_open(&fhw, tmp_filename, 1, torate, SWITCH_FILE_FLAG_WRITE | SWITCH_FILE_DATA_SHORT, NULL);
+			fst_requires(status == SWITCH_STATUS_SUCCESS);
+
+			fhw.native_rate = filerate;
+
+			flen = OGG_MIN_PAGE_SIZE;
+			while (switch_file_read(fd, &buf, &flen) == SWITCH_STATUS_SUCCESS || flen != 0) {
+				status = SWITCH_STATUS_SUCCESS;
+				while (status == SWITCH_STATUS_SUCCESS) {
+					status = switch_core_codec_decode(&read_codec, NULL, &buf, flen, filerate, &decbuf, &decoded_len, &rate, &flags);
+					fst_requires(status == SWITCH_STATUS_SUCCESS);
+					write_len = decoded_len / sizeof(int16_t);
+					if (write_len) switch_core_file_write(&fhw, &decbuf, &write_len);
+					else break;
+				}
+			}
+#endif 
+
+			// continue reading, encoded pages are buffered
+			while (switch_core_codec_decode(&read_codec, NULL, &buf, 0, filerate, &decbuf, &decoded_len, &rate, &flags) == SWITCH_STATUS_SUCCESS && decoded_len) {
+				write_len = decoded_len / sizeof(int16_t);
+				status = switch_core_file_write(&fhw, &decbuf, &write_len);
+				fst_check(status == SWITCH_STATUS_SUCCESS);
+			}
+
+			status = switch_core_file_close(&fhw);
+			fst_check(status == SWITCH_STATUS_SUCCESS);
+
+			switch_core_codec_destroy(&read_codec);
+
+			// final test
+			status = test_detect_tone_in_file(tmp_filename, torate, 1000 /*Hz*/);
+			fst_requires(status == SWITCH_STATUS_SUCCESS);
+
+			unlink(tmp_filename);
+		}
 
 		FST_TEST_END()
 	}

From 97a0b0fbaea0e98e973e0e3ca69ebdea0a0f61e8 Mon Sep 17 00:00:00 2001
From: Dragos Oancea <dragos@signalwire.com>
Date: Fri, 13 Mar 2020 18:42:43 +0000
Subject: [PATCH 2/3] [mod_opusfile] protect ogg data buff

---
 src/mod/formats/mod_opusfile/mod_opusfile.c       |  7 ++++---
 src/mod/formats/mod_opusfile/test/test_opusfile.c | 11 +++++++++--
 2 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/src/mod/formats/mod_opusfile/mod_opusfile.c b/src/mod/formats/mod_opusfile/mod_opusfile.c
index ea932ed0ff..38f5d1f442 100644
--- a/src/mod/formats/mod_opusfile/mod_opusfile.c
+++ b/src/mod/formats/mod_opusfile/mod_opusfile.c
@@ -976,6 +976,7 @@ static void *SWITCH_THREAD_FUNC read_stream_thread(switch_thread_t *thread, void
 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Stream Decode] read_stream_thread(): switch_thread_self(): 0x%lx\n",  switch_thread_self());
 	}
 	switch_thread_rwlock_rdlock(context->rwlock);
+	switch_mutex_lock(context->ogg_mutex);
 
 	if ((buffered_ogg_bytes = switch_buffer_inuse(context->ogg_buffer))) {
 		if (buffered_ogg_bytes <= OGG_MAX_PAGE_SIZE) {
@@ -1021,6 +1022,7 @@ static void *SWITCH_THREAD_FUNC read_stream_thread(switch_thread_t *thread, void
 		}
 	}
 
+	switch_mutex_unlock(context->ogg_mutex);
 	switch_thread_rwlock_unlock(context->rwlock);
 	return NULL;
 }
@@ -1071,9 +1073,9 @@ static switch_status_t switch_opusstream_decode(switch_codec_t *codec,
 #endif
 
 	switch_thread_rwlock_rdlock(context->rwlock);
+	switch_mutex_lock(context->ogg_mutex);
 	memset(context->ogg_data, 0, sizeof(context->ogg_data)); 
 	if (encoded_data_len <= SWITCH_RECOMMENDED_BUFFER_SIZE) {
-		switch_mutex_lock(context->ogg_mutex);
 		switch_buffer_write(context->ogg_buffer, encode_buf, encoded_data_len);
  
 		if ((buffered_ogg_bytes = switch_buffer_inuse(context->ogg_buffer)) >= ogg_bytes) {
@@ -1089,12 +1091,10 @@ static switch_status_t switch_opusstream_decode(switch_codec_t *codec,
 				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS Stream Decode] buffered ogg data bigger than max OGG page size, will flush\n");
 				*decoded_data_len = 0;
 				switch_buffer_zero(context->ogg_buffer);
-				switch_mutex_unlock(context->ogg_mutex);
 				switch_goto_status(SWITCH_STATUS_SUCCESS, end);
 			}
 		}
 
-		switch_mutex_unlock(context->ogg_mutex);
 	} else {
 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "[OGG/OPUS Stream Decode] too much data to buffer, flushing buffer!\n");
 		*decoded_data_len = 0;
@@ -1134,6 +1134,7 @@ static switch_status_t switch_opusstream_decode(switch_codec_t *codec,
 end:
 
 	switch_thread_rwlock_unlock(context->rwlock);
+	switch_mutex_unlock(context->ogg_mutex);
 	return status;
 }
 
diff --git a/src/mod/formats/mod_opusfile/test/test_opusfile.c b/src/mod/formats/mod_opusfile/test/test_opusfile.c
index f21738bdb7..feb74619e5 100644
--- a/src/mod/formats/mod_opusfile/test/test_opusfile.c
+++ b/src/mod/formats/mod_opusfile/test/test_opusfile.c
@@ -170,6 +170,7 @@ FST_CORE_BEGIN(".")
 			uint32_t decoded_len;
 			size_t write_len;
 			unsigned char decbuf[SWITCH_RECOMMENDED_BUFFER_SIZE] = { 0 };
+			switch_stream_handle_t stream = { 0 };
 #ifdef  HAVE_OPUSFILE_ENCODE
 			switch_file_handle_t fh = { 0 };
 			unsigned char encbuf[SWITCH_RECOMMENDED_BUFFER_SIZE] = { 0 };
@@ -216,6 +217,12 @@ FST_CORE_BEGIN(".")
 			&codec_settings, fst_pool);
 			fst_check(status == SWITCH_STATUS_SUCCESS);
 
+			SWITCH_STANDARD_STREAM(stream);
+
+			switch_api_execute("opusfile_debug", "on", NULL, &stream);
+
+			switch_safe_free(stream.data);
+
 #ifdef HAVE_OPUSFILE_ENCODE
 			status = switch_core_codec_init(&write_codec,
 			"OPUSSTREAM",
@@ -277,12 +284,12 @@ FST_CORE_BEGIN(".")
 
 			fhw.native_rate = filerate;
 
-			flen = OGG_MIN_PAGE_SIZE;
+			flen = 4096;
 			while (switch_file_read(fd, &buf, &flen) == SWITCH_STATUS_SUCCESS || flen != 0) {
 				status = SWITCH_STATUS_SUCCESS;
 				while (status == SWITCH_STATUS_SUCCESS) {
 					status = switch_core_codec_decode(&read_codec, NULL, &buf, flen, filerate, &decbuf, &decoded_len, &rate, &flags);
-					fst_requires(status == SWITCH_STATUS_SUCCESS);
+					fst_check(status == SWITCH_STATUS_SUCCESS);
 					write_len = decoded_len / sizeof(int16_t);
 					if (write_len) switch_core_file_write(&fhw, &decbuf, &write_len);
 					else break;

From 0cf323c339c788f71bde4d1b2ecc115e61ccbb1d Mon Sep 17 00:00:00 2001
From: Dragos Oancea <dragos@signalwire.com>
Date: Wed, 15 Apr 2020 11:34:10 +0000
Subject: [PATCH 3/3] [mod_opusfile] unit-tests: add timer to fix random
 failures due to the fact that the decoding callback is not installed
 immediatelly

---
 src/mod/formats/mod_opusfile/test/test_opusfile.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/src/mod/formats/mod_opusfile/test/test_opusfile.c b/src/mod/formats/mod_opusfile/test/test_opusfile.c
index feb74619e5..339047c576 100644
--- a/src/mod/formats/mod_opusfile/test/test_opusfile.c
+++ b/src/mod/formats/mod_opusfile/test/test_opusfile.c
@@ -171,6 +171,7 @@ FST_CORE_BEGIN(".")
 			size_t write_len;
 			unsigned char decbuf[SWITCH_RECOMMENDED_BUFFER_SIZE] = { 0 };
 			switch_stream_handle_t stream = { 0 };
+			switch_timer_t timer;
 #ifdef  HAVE_OPUSFILE_ENCODE
 			switch_file_handle_t fh = { 0 };
 			unsigned char encbuf[SWITCH_RECOMMENDED_BUFFER_SIZE] = { 0 };
@@ -223,6 +224,8 @@ FST_CORE_BEGIN(".")
 
 			switch_safe_free(stream.data);
 
+			switch_core_timer_init(&timer, "soft", 20, 960, fst_pool);
+
 #ifdef HAVE_OPUSFILE_ENCODE
 			status = switch_core_codec_init(&write_codec,
 			"OPUSSTREAM",
@@ -252,6 +255,7 @@ FST_CORE_BEGIN(".")
 				if (encoded_len) {
 					pages++;
 					status = switch_core_codec_decode(&read_codec, NULL, &encbuf, encoded_len, filerate, &decbuf, &decoded_len, &rate, &flags);
+					switch_core_timer_next(&timer);
 					fst_check(status == SWITCH_STATUS_SUCCESS);
 					write_len = decoded_len / sizeof(int16_t);
 					if (write_len) switch_core_file_write(&fhw, &decbuf, &write_len);
@@ -260,6 +264,7 @@ FST_CORE_BEGIN(".")
 
 			// continue reading, encoded pages are buffered
 			while (switch_core_codec_decode(&read_codec, NULL, &encbuf, 0, filerate, &decbuf, &decoded_len, &rate, &flags) == SWITCH_STATUS_SUCCESS && decoded_len) {
+				switch_core_timer_next(&timer);
 				write_len = decoded_len / sizeof(int16_t);
 				status = switch_core_file_write(&fhw, &decbuf, &write_len);
 				fst_check(status == SWITCH_STATUS_SUCCESS);
@@ -288,6 +293,7 @@ FST_CORE_BEGIN(".")
 			while (switch_file_read(fd, &buf, &flen) == SWITCH_STATUS_SUCCESS || flen != 0) {
 				status = SWITCH_STATUS_SUCCESS;
 				while (status == SWITCH_STATUS_SUCCESS) {
+					switch_core_timer_next(&timer);
 					status = switch_core_codec_decode(&read_codec, NULL, &buf, flen, filerate, &decbuf, &decoded_len, &rate, &flags);
 					fst_check(status == SWITCH_STATUS_SUCCESS);
 					write_len = decoded_len / sizeof(int16_t);
@@ -296,14 +302,14 @@ FST_CORE_BEGIN(".")
 				}
 			}
 #endif 
-
 			// continue reading, encoded pages are buffered
 			while (switch_core_codec_decode(&read_codec, NULL, &buf, 0, filerate, &decbuf, &decoded_len, &rate, &flags) == SWITCH_STATUS_SUCCESS && decoded_len) {
+				switch_core_timer_next(&timer);
 				write_len = decoded_len / sizeof(int16_t);
 				status = switch_core_file_write(&fhw, &decbuf, &write_len);
 				fst_check(status == SWITCH_STATUS_SUCCESS);
 			}
-
+			
 			status = switch_core_file_close(&fhw);
 			fst_check(status == SWITCH_STATUS_SUCCESS);
 
@@ -313,6 +319,8 @@ FST_CORE_BEGIN(".")
 			status = test_detect_tone_in_file(tmp_filename, torate, 1000 /*Hz*/);
 			fst_requires(status == SWITCH_STATUS_SUCCESS);
 
+			switch_core_timer_destroy(&timer);
+
 			unlink(tmp_filename);
 		}