FS-10530: [mod_opusfile]: multichannel

This commit is contained in:
Dragos Oancea 2018-04-13 04:52:59 -04:00
parent 2a4cf1f04e
commit ed2b3b7bb4
1 changed files with 94 additions and 50 deletions

View File

@ -38,8 +38,11 @@
#endif #endif
#define OPUSFILE_MAX 32*1024 #define OPUSFILE_MAX 32*1024
#define TC_BUFFER_SIZE 1024 * 128 #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 */
//#undef HAVE_OPUSFILE_ENCODE /*don't encode anything */
SWITCH_MODULE_LOAD_FUNCTION(mod_opusfile_load); SWITCH_MODULE_LOAD_FUNCTION(mod_opusfile_load);
SWITCH_MODULE_DEFINITION(mod_opusfile, mod_opusfile_load, NULL, NULL); SWITCH_MODULE_DEFINITION(mod_opusfile, mod_opusfile_load, NULL, NULL);
@ -58,19 +61,26 @@ struct opus_file_context {
int prev_li; int prev_li;
switch_mutex_t *audio_mutex; switch_mutex_t *audio_mutex;
switch_buffer_t *audio_buffer; switch_buffer_t *audio_buffer;
unsigned char decode_buf[OPUSFILE_MAX]; switch_mutex_t *ogg_mutex;
int eof; switch_buffer_t *ogg_buffer;
opus_int16 decode_buf[OPUS_MAX_PCM];
switch_bool_t eof;
switch_thread_rwlock_t *rwlock; switch_thread_rwlock_t *rwlock;
switch_file_handle_t *handle; switch_file_handle_t *handle;
size_t samplerate; size_t samplerate;
int frame_size;
int channels; int channels;
size_t buffer_seconds; size_t buffer_seconds;
size_t err;
opus_int16 *opusbuf; opus_int16 *opusbuf;
switch_size_t opusbuflen; switch_size_t opusbuflen;
FILE *fp; FILE *fp;
#ifdef HAVE_OPUSFILE_ENCODE #ifdef HAVE_OPUSFILE_ENCODE
OggOpusEnc *enc; OggOpusEnc *enc;
OggOpusComments *comments; OggOpusComments *comments;
unsigned char encode_buf[OPUSFILE_MAX];
int encoded_buflen;
size_t samples_encode;
#endif #endif
switch_memory_pool_t *pool; switch_memory_pool_t *pool;
}; };
@ -81,22 +91,30 @@ static struct {
int debug; int debug;
} globals; } globals;
static switch_status_t switch_opusfile_decode(opus_file_context *context, void *data, size_t bytes, int channels) static switch_status_t switch_opusfile_decode(opus_file_context *context, void *data, size_t max_bytes, int channels)
{ {
int ret; int ret = 0;
size_t buf_inuse;
while (!(context->eof) && switch_buffer_inuse(context->audio_buffer) < bytes) { if (!context->of) {
if (channels == 1) {
ret = op_read(context->of, (opus_int16 *)context->decode_buf, sizeof(context->decode_buf), NULL);
} else if (channels > 1) {
ret = op_read_stereo(context->of, (opus_int16 *)context->decode_buf, sizeof(context->decode_buf));
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS File] Invalid number of channels");
return SWITCH_STATUS_FALSE; 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) { if (ret < 0) {
switch(ret) { 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_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.*/
@ -121,33 +139,30 @@ static switch_status_t switch_opusfile_decode(opus_file_context *context, void *
case OP_EBADTIMESTAMP: /*An unseekable stream encountered a new link with a starting timestamp that failed basic validity checks.*/ case OP_EBADTIMESTAMP: /*An unseekable stream encountered a new link with a starting timestamp that failed basic validity checks.*/
default: default:
goto err; switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS Decoder]: error decoding file: [%d]\n", ret);
break; switch_mutex_unlock(context->audio_mutex);
return SWITCH_STATUS_FALSE;
} }
} else if (ret == 0) { } 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*/ /*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*/
context->eof = 1;
if (globals.debug) { if (globals.debug) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS file]: EOF reached\n"); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS Decoder]: EOF reached [%d]\n", ret);
} }
context->eof = TRUE;
break; break;
} else /* (ret > 0)*/ { } else /* (ret > 0)*/ {
/*The number of samples read per channel on success*/ /*The number of samples read per channel on success*/
switch_buffer_write(context->audio_buffer, context->decode_buf, ret * sizeof(int16_t) * channels); switch_buffer_write(context->audio_buffer, (opus_int16 *)context->decode_buf, ret * sizeof(opus_int16) * channels);
if (globals.debug) { if (globals.debug) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,
"[OGG/OPUS File]: Read samples: [%d]. Wrote bytes to audio buffer: [%d]\n", ret, (int)(ret * sizeof(int16_t) * channels)); "[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);
context->eof = FALSE; // for next page
return SWITCH_STATUS_SUCCESS; return SWITCH_STATUS_SUCCESS;
err:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS File]: error decoding file: [%d]\n", ret);
return SWITCH_STATUS_FALSE;
} }
@ -157,7 +172,6 @@ static switch_status_t switch_opusfile_open(switch_file_handle_t *handle, const
char *ext; char *ext;
unsigned int flags = 0; unsigned int flags = 0;
int ret; int ret;
switch_status_t status = SWITCH_STATUS_SUCCESS;
if ((ext = strrchr(path, '.')) == 0) { if ((ext = strrchr(path, '.')) == 0) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS File] Invalid Format\n"); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS File] Invalid Format\n");
@ -189,14 +203,14 @@ static switch_status_t switch_opusfile_open(switch_file_handle_t *handle, const
if (switch_test_flag(handle, SWITCH_FILE_FLAG_READ)) { 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) { 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_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Memory Error!\n");
switch_goto_status(SWITCH_STATUS_GENERR, out); goto err;
} }
flags |= SWITCH_FOPEN_READ; flags |= SWITCH_FOPEN_READ;
} }
handle->samples = 0; handle->samples = 0;
handle->samplerate = context->samplerate = 48000; handle->samplerate = context->samplerate = DEFAULT_RATE; /*open files at 48 khz always*/
handle->format = 0; handle->format = 0;
handle->sections = 0; handle->sections = 0;
handle->seekable = 1; handle->seekable = 1;
@ -208,26 +222,34 @@ static switch_status_t switch_opusfile_open(switch_file_handle_t *handle, const
#ifdef HAVE_OPUSFILE_ENCODE #ifdef HAVE_OPUSFILE_ENCODE
if (switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) { if (switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) {
int err_open; int err; int mapping_family = 0;
context->channels = handle->channels; context->channels = handle->channels;
context->samplerate = handle->samplerate; context->samplerate = handle->samplerate;
handle->seekable = 0; handle->seekable = 0;
context->comments = ope_comments_create(); context->comments = ope_comments_create();
ope_comments_add(context->comments, "METADATA", "Freeswitch/mod_opusfile"); ope_comments_add(context->comments, "METADATA", "Freeswitch/mod_opusfile");
context->enc = ope_encoder_create_file(handle->file_path, context->comments, context->samplerate, context->channels, 0, &err_open); // opus_multistream_surround_encoder_get_size() in libopus will check these
if (!context->enc) { if ((context->channels > 2) && (context->channels <= 8)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS File] Can't open file for writing\n"); mapping_family = 1;
switch_goto_status(SWITCH_STATUS_FALSE, out); } else if ((context->channels > 8) && (context->channels <= 255)) {
mapping_family = 255;
} }
goto out; 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 #endif
context->of = op_open_file(path, &ret); context->of = op_open_file(path, &ret);
if (!context->of) { if (!context->of) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS File] Error opening %s\n", path); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS File] Error opening %s\n", path);
switch_goto_status(SWITCH_STATUS_GENERR, out); return SWITCH_STATUS_GENERR;
} }
if (switch_test_flag(handle, SWITCH_FILE_WRITE_APPEND)) { if (switch_test_flag(handle, SWITCH_FILE_WRITE_APPEND)) {
@ -244,7 +266,7 @@ static switch_status_t switch_opusfile_open(switch_file_handle_t *handle, const
if(context->pcm_offset!=0){ 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); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[OGG/OPUS File] Non-zero starting PCM offset: [%li]\n", (long)context->pcm_offset);
} }
context->pcm_print_offset = context->pcm_offset - 48000; context->pcm_print_offset = context->pcm_offset - DEFAULT_RATE;
context->bitrate = 0; context->bitrate = 0;
context->buffer_seconds = 1; context->buffer_seconds = 1;
@ -260,6 +282,7 @@ static switch_status_t switch_opusfile_open(switch_file_handle_t *handle, const
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "[OGG/OPUS File] Channels: %i\n", head->channel_count); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "[OGG/OPUS File] Channels: %i\n", head->channel_count);
if (head->input_sample_rate) { 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); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "[OGG/OPUS File] Original sampling rate: %lu Hz\n", (unsigned long)head->input_sample_rate);
handle->samplerate = context->samplerate = head->input_sample_rate;
} }
} }
if (op_seekable(context->of)) { if (op_seekable(context->of)) {
@ -274,22 +297,25 @@ static switch_status_t switch_opusfile_open(switch_file_handle_t *handle, const
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "[OGG/OPUS File] Encoded by: %s\n", tags->vendor); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "[OGG/OPUS File] Encoded by: %s\n", tags->vendor);
} }
out: switch_thread_rwlock_unlock(context->rwlock);
return SWITCH_STATUS_SUCCESS;
err:
switch_thread_rwlock_unlock(context->rwlock); switch_thread_rwlock_unlock(context->rwlock);
return status; return SWITCH_STATUS_FALSE;
} }
static switch_status_t switch_opusfile_close(switch_file_handle_t *handle) static switch_status_t switch_opusfile_close(switch_file_handle_t *handle)
{ {
opus_file_context *context = handle->private_info; opus_file_context *context = handle->private_info;
switch_thread_rwlock_rdlock(context->rwlock);
if (context->of) { if (context->of) {
op_free(context->of); op_free(context->of);
} }
#ifdef HAVE_OPUSFILE_ENCODE #ifdef HAVE_OPUSFILE_ENCODE
if (context->enc) { if (context->enc) {
ope_encoder_drain(context->enc);
ope_encoder_destroy(context->enc); ope_encoder_destroy(context->enc);
} }
if (context->comments) { if (context->comments) {
@ -299,6 +325,7 @@ static switch_status_t switch_opusfile_close(switch_file_handle_t *handle)
if (context->audio_buffer) { if (context->audio_buffer) {
switch_buffer_destroy(&context->audio_buffer); switch_buffer_destroy(&context->audio_buffer);
} }
switch_thread_rwlock_unlock(context->rwlock);
return SWITCH_STATUS_SUCCESS; return SWITCH_STATUS_SUCCESS;
} }
@ -337,7 +364,7 @@ static switch_status_t switch_opusfile_read(switch_file_handle_t *handle, void *
} }
if (!handle->handler) { if (!handle->handler) {
if (switch_opusfile_decode(context, data, bytes , handle->real_channels) == SWITCH_STATUS_FALSE) { if (switch_opusfile_decode(context, data, bytes, handle->real_channels) == SWITCH_STATUS_FALSE) {
context->eof = 1; context->eof = 1;
} }
} }
@ -359,7 +386,9 @@ static switch_status_t switch_opusfile_read(switch_file_handle_t *handle, void *
bytes = newbytes; bytes = newbytes;
} }
if (globals.debug) { 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); 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); memset(data, 255, bytes);
*len = bytes / sizeof(int16_t) / handle->real_channels; *len = bytes / sizeof(int16_t) / handle->real_channels;
@ -375,8 +404,8 @@ static switch_status_t switch_opusfile_write(switch_file_handle_t *handle, void
{ {
#ifdef HAVE_OPUSFILE_ENCODE #ifdef HAVE_OPUSFILE_ENCODE
size_t nsamples = *len; size_t nsamples = *len;
int err_open; int err;
int ret; int mapping_family = 0;
opus_file_context *context = handle->private_info; opus_file_context *context = handle->private_info;
@ -389,12 +418,17 @@ static switch_status_t switch_opusfile_write(switch_file_handle_t *handle, void
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error no context\n"); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error no context\n");
return SWITCH_STATUS_FALSE; return SWITCH_STATUS_FALSE;
} }
if (!context->comments) {
context->comments = ope_comments_create(); context->comments = ope_comments_create();
ope_comments_add(context->comments, "METADATA", "Freeswitch/mod_opusfile"); ope_comments_add(context->comments, "METADATA", "Freeswitch/mod_opusfile");
}
if (context->channels > 2) {
mapping_family = 1;
}
if (!context->enc) { if (!context->enc) {
context->enc = ope_encoder_create_file(handle->file_path, context->comments, handle->samplerate, handle->channels, 0, &err_open); context->enc = ope_encoder_create_file(handle->file_path, context->comments, handle->samplerate, handle->channels, mapping_family, &err);
if (!context->enc) { if (!context->enc) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[OGG/OPUS File] Can't open file for writing\n"); 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; return SWITCH_STATUS_FALSE;
} }
} }
@ -403,8 +437,10 @@ static switch_status_t switch_opusfile_write(switch_file_handle_t *handle, void
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]", (int)nsamples);
} }
ret = ope_encoder_write(context->enc, (opus_int16 *)data, nsamples); err = ope_encoder_write(context->enc, (opus_int16 *)data, nsamples);
if (ret != OPE_OK) {
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));
return SWITCH_STATUS_FALSE; return SWITCH_STATUS_FALSE;
} }
@ -423,6 +459,7 @@ static switch_status_t switch_opusfile_get_string(switch_file_handle_t *handle,
return SWITCH_STATUS_FALSE; return SWITCH_STATUS_FALSE;
} }
#define OPUSFILE_DEBUG_SYNTAX "<on|off>" #define OPUSFILE_DEBUG_SYNTAX "<on|off>"
SWITCH_STANDARD_API(mod_opusfile_debug) SWITCH_STANDARD_API(mod_opusfile_debug)
{ {
@ -432,6 +469,9 @@ SWITCH_STANDARD_API(mod_opusfile_debug)
if (!strcasecmp(cmd, "on")) { if (!strcasecmp(cmd, "on")) {
globals.debug = 1; globals.debug = 1;
stream->write_function(stream, "OPUSFILE Debug: on\n"); 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());
#endif
} else if (!strcasecmp(cmd, "off")) { } else if (!strcasecmp(cmd, "off")) {
globals.debug = 0; globals.debug = 0;
stream->write_function(stream, "OPUSFILE Debug: off\n"); stream->write_function(stream, "OPUSFILE Debug: off\n");
@ -460,6 +500,8 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_opusfile_load)
switch_console_set_complete("add opusfile_debug on"); switch_console_set_complete("add opusfile_debug on");
switch_console_set_complete("add opusfile_debug off"); 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 = switch_loadable_module_create_interface(*module_interface, SWITCH_FILE_INTERFACE);
file_interface->interface_name = modname; file_interface->interface_name = modname;
file_interface->extens = supported_formats; file_interface->extens = supported_formats;
@ -471,6 +513,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_opusfile_load)
file_interface->file_set_string = switch_opusfile_set_string; file_interface->file_set_string = switch_opusfile_set_string;
file_interface->file_get_string = switch_opusfile_get_string; file_interface->file_get_string = switch_opusfile_get_string;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "mod_opusfile loaded\n"); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "mod_opusfile loaded\n");
/* indicate that the module should continue to be loaded */ /* indicate that the module should continue to be loaded */
@ -487,3 +530,4 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_opusfile_load)
* For VIM: * For VIM:
* vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet: * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
*/ */