/* 
 * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
 *
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 *
 * The Initial Developer of the Original Code is
 * Anthony Minessale II <anthm@freeswitch.org>
 * Portions created by the Initial Developer are Copyright (C)
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * 
 * Dragos Oancea <dragos.oancea@nexmo.com> (mod_opusfile.c)
 *
 *
 * mod_opusfile.c -- Read and Write OGG/Opus files . Some parts inspired from mod_shout.c, libopusfile, libopusenc
 *
 */
#include <switch.h>

#include <opusfile.h>

#ifdef HAVE_OPUSFILE_ENCODE
#include <opus/opusenc.h>
#endif 

#define OPUSFILE_MAX 32*1024 
#define TC_BUFFER_SIZE 1024 * 256 /* max ammount of decoded audio we can have at a time (bytes)*/
#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);
SWITCH_MODULE_DEFINITION(mod_opusfile, mod_opusfile_load, NULL, NULL);

struct opus_file_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;
	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;
	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 channels;
	size_t buffer_seconds;
	size_t err;
	opus_int16 *opusbuf;
	switch_size_t opusbuflen;
	FILE *fp;
#ifdef HAVE_OPUSFILE_ENCODE
	OggOpusEnc *enc;
	OggOpusComments *comments;
	unsigned char encode_buf[OPUSFILE_MAX];
	int encoded_buflen;
	size_t samples_encode;
#endif
	switch_memory_pool_t *pool;
};

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;

static switch_status_t switch_opusfile_decode(opus_file_context *context, void *data, size_t max_bytes, int channels)
{
	int ret = 0;
	size_t buf_inuse;

	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) && (buf_inuse = switch_buffer_inuse(context->audio_buffer)) <= max_bytes) {

		if (channels == 1) {
			ret = op_read(context->of, (opus_int16 *)context->decode_buf, OPUS_MAX_PCM, NULL);
		} else if (channels == 2) {
			ret = op_read_stereo(context->of, (opus_int16 *)context->decode_buf, OPUS_MAX_PCM);
		} else if (channels > 2) {
			ret = op_read(context->of, (opus_int16 *)context->decode_buf, OPUS_MAX_PCM, NULL);
		} else if ((channels > 255) || (channels < 1)) { 
				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS File] Invalid number of channels");
				switch_mutex_unlock(context->audio_mutex);
				return SWITCH_STATUS_FALSE;
		}
		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.*/
			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 file: [%d]\n", ret);
				switch_mutex_unlock(context->audio_mutex);
				return SWITCH_STATUS_FALSE;
			}
		} 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 = SWITCH_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);

			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]\n", ret, (int)(ret * sizeof(int16_t) * channels), (unsigned int)buf_inuse);
			}
		}
	}
	switch_mutex_unlock(context->audio_mutex);
	return SWITCH_STATUS_SUCCESS;
}


static switch_status_t switch_opusfile_open(switch_file_handle_t *handle, const char *path)
{
	opus_file_context *context;
	char *ext;
	int ret;

	if ((ext = strrchr(path, '.')) == 0) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS File] Invalid Format\n");
		return SWITCH_STATUS_GENERR;
	}
	ext++;

	if ((context = switch_core_alloc(handle->memory_pool, sizeof(*context))) == 0) {
		return SWITCH_STATUS_MEMERR;
	}

	context->pool = handle->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);

	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;
		}
	}

	handle->samples = 0;
	handle->samplerate = context->samplerate = DEFAULT_RATE; /*open files at 48 khz always*/
	handle->format = 0;
	handle->sections = 0;
	handle->seekable = 1;
	handle->speed = 0;
	handle->pos = 0;
	handle->private_info = context;
	context->handle = handle;
	memcpy(handle->file_path, path, strlen(path));

#ifdef HAVE_OPUSFILE_ENCODE
	if (switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) {
		int err; int mapping_family = 0;

		context->channels = handle->channels;
		context->samplerate = handle->samplerate;
		handle->seekable = 0;
		context->comments = ope_comments_create();
		ope_comments_add(context->comments, "METADATA", "Freeswitch/mod_opusfile");
		// opus_multistream_surround_encoder_get_size() in libopus will check these
		if ((context->channels > 2) && (context->channels <= 8)) {
			mapping_family = 1;
		} else if ((context->channels > 8) && (context->channels <= 255)) {
			mapping_family = 255;
		}
		context->enc = ope_encoder_create_file(handle->file_path, context->comments, context->samplerate, context->channels, mapping_family, &err);
		if (!context->enc) {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Can't open file for writing [%d] [%s]\n", err, ope_strerror(err));
			switch_thread_rwlock_unlock(context->rwlock);
			return SWITCH_STATUS_FALSE;
		}
		switch_thread_rwlock_unlock(context->rwlock);
		return SWITCH_STATUS_SUCCESS;
	}
#endif 
	
	context->of = op_open_file(path, &ret);
	if (!context->of) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS File] Error opening %s\n", path);
		switch_thread_rwlock_unlock(context->rwlock);
		return SWITCH_STATUS_GENERR;
	}

	if (switch_test_flag(handle, SWITCH_FILE_WRITE_APPEND)) {
		op_pcm_seek(context->of, 0); // overwrite
		handle->pos = 0;
	}

	context->prev_li = -1;
	context->nsamples = 0;

	handle->channels = context->channels = op_channel_count(context->of, -1);
	context->pcm_offset = op_pcm_tell(context->of);

	if(context->pcm_offset!=0){
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS File] Non-zero starting PCM offset: [%li]\n", (long)context->pcm_offset);
	}

	context->eof = SWITCH_FALSE;
	context->pcm_print_offset = context->pcm_offset - DEFAULT_RATE;
	context->bitrate = 0;
	context->buffer_seconds = 1;

	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "[OGG/OPUS File] Opening File [%s] %dhz\n", path, handle->samplerate);

	context->li = op_current_link(context->of);

	if (context->li != context->prev_li) {
		const OpusHead *head;
		const OpusTags *tags;
		head=op_head(context->of, context->li);
		if (head) {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "[OGG/OPUS File] Channels: %i\n", head->channel_count);
			if (head->input_sample_rate) {
				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "[OGG/OPUS File] Original sampling rate: %lu Hz\n", (unsigned long)head->input_sample_rate);
			}
		}
		if (op_seekable(context->of)) {
			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\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\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);
	}

	switch_thread_rwlock_unlock(context->rwlock);
	return SWITCH_STATUS_SUCCESS;

err:
	switch_thread_rwlock_unlock(context->rwlock);

	return SWITCH_STATUS_FALSE;
}

static switch_status_t switch_opusfile_close(switch_file_handle_t *handle)
{
	opus_file_context *context = handle->private_info;

	switch_thread_rwlock_rdlock(context->rwlock);
	if (context->of) {
		op_free(context->of);
	}
#ifdef HAVE_OPUSFILE_ENCODE
	if (context->enc) {
		ope_encoder_drain(context->enc);
		ope_encoder_destroy(context->enc);
	}
	if (context->comments) {
		ope_comments_destroy(context->comments);
	}
#endif 
	if (context->audio_buffer) {
		switch_buffer_destroy(&context->audio_buffer);
	}
	switch_thread_rwlock_unlock(context->rwlock);
	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t switch_opusfile_seek(switch_file_handle_t *handle, unsigned int *cur_sample, int64_t samples, int whence)
{
	int ret;
	opus_file_context *context = handle->private_info;

	if (handle->handler || switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) {
		return SWITCH_STATUS_FALSE;
	} else {
		if (whence == SWITCH_SEEK_CUR) {
			samples -= switch_buffer_inuse(context->audio_buffer) / sizeof(int16_t);
		}
		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]\n", (unsigned int)samples);
		}
		if (ret == 0) {
			handle->pos = *cur_sample = samples;
			return SWITCH_STATUS_SUCCESS;
		}
	}
	return SWITCH_STATUS_FALSE;
}

static switch_status_t switch_opusfile_read(switch_file_handle_t *handle, void *data, size_t *len)
{
	opus_file_context *context = handle->private_info;
	size_t bytes = *len * sizeof(int16_t) * handle->real_channels;
	size_t rb = 0, newbytes;

	if (!context) {
		return SWITCH_STATUS_FALSE;
	}

	if (!handle->handler) {
		if (switch_opusfile_decode(context, data, bytes, handle->real_channels) == SWITCH_STATUS_FALSE) {
			context->eof = SWITCH_TRUE;
		}
	}
	switch_mutex_lock(context->audio_mutex);
	rb = switch_buffer_read(context->audio_buffer, data, bytes);
	switch_mutex_unlock(context->audio_mutex);

	if (globals.debug) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS File] rb: [%"SWITCH_SIZE_T_FMT"] bytes: [%"SWITCH_SIZE_T_FMT"]\n", rb, bytes);
	}

	if (!rb && (context->eof)) {
		if (globals.debug) {
			// should be same as returned by op_pcm_total()
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS File] EOF. sample count: [%"SWITCH_SIZE_T_FMT"]\n", handle->sample_count);
		}
		*len = 0;
		return SWITCH_STATUS_FALSE;
	}
	if (rb) {
		*len = rb / sizeof(int16_t) / handle->real_channels;
		if (globals.debug) {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS File] rb: [%"SWITCH_SIZE_T_FMT"] *len: [%"SWITCH_SIZE_T_FMT"]\n", rb, *len);
		}
	} else {
		newbytes = (2 * handle->samplerate * handle->real_channels) * context->buffer_seconds;
		if (newbytes < bytes) {
			bytes = newbytes;
		}
		if (globals.debug) {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, 
					"[OGG/OPUS File] Padding with empty audio. seconds: [%d] bytes: [%d] newbytes: [%d] real_channels: [%d]\n", 
					(int)context->buffer_seconds, (int)bytes, (int)newbytes, (int)handle->real_channels);
		}
		memset(data, 255, bytes);
		*len = bytes / sizeof(int16_t) / handle->real_channels;
	}

	handle->pos += *len;
	handle->sample_count += *len;

	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t switch_opusfile_write(switch_file_handle_t *handle, void *data, size_t *len)
{
#ifdef HAVE_OPUSFILE_ENCODE
	size_t nsamples = *len;
	int err;
	int mapping_family = 0;

	opus_file_context *context;

	if (!handle) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error no handle\n");
		return SWITCH_STATUS_FALSE;
	}

	if (!(context = handle->private_info)) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error no context\n");
		return SWITCH_STATUS_FALSE;
	}
	if (!context->comments) {
		context->comments = ope_comments_create();
		ope_comments_add(context->comments, "METADATA", "Freeswitch/mod_opusfile");
	}
	if (context->channels > 2) {
			mapping_family = 1;
	}
	if (!context->enc) {
		context->enc = ope_encoder_create_file(handle->file_path, context->comments, handle->samplerate, handle->channels, mapping_family, &err);
		if (!context->enc) {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Can't open file for writing. err: [%d] [%s]\n", err, ope_strerror(err));
			return SWITCH_STATUS_FALSE;
		}
	}

	if (globals.debug) {
		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]\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;
}

static switch_status_t switch_opusfile_set_string(switch_file_handle_t *handle, switch_audio_col_t col, const char *string)
{
	return SWITCH_STATUS_FALSE;
}

static switch_status_t switch_opusfile_get_string(switch_file_handle_t *handle, switch_audio_col_t col, const char **string)
{
	return SWITCH_STATUS_FALSE;
}


#define OPUSFILE_DEBUG_SYNTAX "<on|off>"
SWITCH_STANDARD_API(mod_opusfile_debug)
{
	if (zstr(cmd)) {
		stream->write_function(stream, "-USAGE: %s\n", OPUSFILE_DEBUG_SYNTAX);
	} else {
		if (!strcasecmp(cmd, "on")) {
			globals.debug = 1;
			stream->write_function(stream, "OPUSFILE Debug: on\n");
#ifdef HAVE_OPUSFILE_ENCODE
			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;
			stream->write_function(stream, "OPUSFILE Debug: off\n");
		} else {
			stream->write_function(stream, "-USAGE: %s\n", OPUSFILE_DEBUG_SYNTAX);
		}
	}
	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);
				}
				break;
			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", (unsigned long)(intptr_t)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", (unsigned long)(intptr_t)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) {
			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_mutex_unlock(context->ogg_mutex);
	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);
	switch_mutex_lock(context->ogg_mutex);
	memset(context->ogg_data, 0, sizeof(context->ogg_data)); 
	if (encoded_data_len <= SWITCH_RECOMMENDED_BUFFER_SIZE) {
		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_goto_status(SWITCH_STATUS_SUCCESS, end);
			}
		}

	} 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);
	switch_mutex_unlock(context->ogg_mutex);
	return status;
}

/* Registration */

static char *supported_formats[SWITCH_MAX_CODECS] = { 0 };

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";

	*module_interface = switch_loadable_module_create_module_interface(pool, modname);

	SWITCH_ADD_API(commands_api_interface, "opusfile_debug", "Set OPUSFILE Debug", mod_opusfile_debug, OPUSFILE_DEBUG_SYNTAX);

	switch_console_set_complete("add opusfile_debug on");
	switch_console_set_complete("add opusfile_debug off");

	globals.debug = 0;

	file_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_FILE_INTERFACE);
	file_interface->interface_name = modname;
	file_interface->extens = supported_formats;
	file_interface->file_open = switch_opusfile_open;
	file_interface->file_close = switch_opusfile_close;
	file_interface->file_read = switch_opusfile_read;
	file_interface->file_write = switch_opusfile_write;
	file_interface->file_seek = switch_opusfile_seek;
	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
 * indent-tabs-mode:t
 * tab-width:4
 * c-basic-offset:4
 * End:
 * For VIM:
 * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
 */

