]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
[mod_sndfile] fix playing "raw" and other formats ("r8", "r16", "r24", "r32"), add... 217/head
authorDragos Oancea <dragos@signalwire.com>
Thu, 9 Jan 2020 20:17:42 +0000 (20:17 +0000)
committerDragos Oancea <dragos@signalwire.com>
Tue, 21 Jan 2020 22:12:08 +0000 (22:12 +0000)
[mod_sndfile] add stereo to mono support for file formats that need it (eg: gsm, vox), add unit test for stereo

[mod_sndfile] mono to stereo and stereo to mono unit tests.

[unit-tests] mod_sndfile: don't wait for recording file to be flushed to disk by the core, use switch_ivr_stop_record_session()

[mod_sndfile] fix format "wve" (Psion Series 3)

[mod_sndfile] fix format "htk" (Hidden Markov Model Tool Kit)

[mod_sndfile] fix file format "iff" (AIFF)

[mod_sndfile] fix file format "xi" (FastTracker 2)

[mod_sndfile] fix file format "sds" (Midi Sample Dump Standard)

[uni-tests] add more audio file extensions to sndfile unit tests.

[mod_sndfile] add config file support (with one param currently: "allowed-extensions")

[mod_sndfile] add sample file sndfile.conf.xml to 'testing' and 'vanilla' cfg trees.

[mod_sndfile] free() cfg xml

[unit-tests] mod_sndfile: unload module test.

[unit-tests] mod_sndfile: add conf file unit-test (param that allows only certain file extensions)

[unit-tests] adjusts Makefile.am for new CI (tests)

[unit-tests] adjust path to sound file.

[unit-tests] [mod_sndfile] remove Makefile.am from test/

[unit-tests] [mod_sndfile] fix build (Andrey)

conf/testing/autoload_configs/sndfile.conf.xml [new file with mode: 0644]
conf/vanilla/autoload_configs/sndfile.conf.xml [new file with mode: 0644]
src/mod/formats/mod_sndfile/Makefile.am
src/mod/formats/mod_sndfile/mod_sndfile.c
src/mod/formats/mod_sndfile/test/sounds/hello_stereo.wav [new file with mode: 0644]
src/mod/formats/mod_sndfile/test/sounds/hi.wav [new file with mode: 0644]
src/mod/formats/mod_sndfile/test/test_conf/freeswitch.xml [new file with mode: 0644]
src/mod/formats/mod_sndfile/test/test_conf/freeswitch.xml.fsxml [new file with mode: 0644]
src/mod/formats/mod_sndfile/test/test_formats_and_muxing/freeswitch.xml [new file with mode: 0644]
src/mod/formats/mod_sndfile/test/test_sndfile.c [new file with mode: 0644]
src/mod/formats/mod_sndfile/test/test_sndfile_conf.c [new file with mode: 0644]

diff --git a/conf/testing/autoload_configs/sndfile.conf.xml b/conf/testing/autoload_configs/sndfile.conf.xml
new file mode 100644 (file)
index 0000000..d9e0cff
--- /dev/null
@@ -0,0 +1,9 @@
+<configuration name="sndfile.conf">
+       <settings>
+               <!-- Allow only these file extensions. Default: allow all sndfile provided extensions + FS custom extra -->
+               <!--
+               <param name="allowed-extensions" value="wav,raw,r8,r16"/>
+               -->
+       </settings>
+</configuration>
+
diff --git a/conf/vanilla/autoload_configs/sndfile.conf.xml b/conf/vanilla/autoload_configs/sndfile.conf.xml
new file mode 100644 (file)
index 0000000..d9e0cff
--- /dev/null
@@ -0,0 +1,9 @@
+<configuration name="sndfile.conf">
+       <settings>
+               <!-- Allow only these file extensions. Default: allow all sndfile provided extensions + FS custom extra -->
+               <!--
+               <param name="allowed-extensions" value="wav,raw,r8,r16"/>
+               -->
+       </settings>
+</configuration>
+
index e94b9cb100714206c3fd3c6096414956d8cf4421..27f77440e3127304eb0fc4984365a983fcfd2988 100644 (file)
@@ -3,12 +3,30 @@ MODNAME=mod_sndfile
 
 if HAVE_SNDFILE
 
+noinst_LTLIBRARIES = libsndfilemod.la
+libsndfilemod_la_SOURCES  = mod_sndfile.c
+libsndfilemod_la_CFLAGS   = $(AM_CFLAGS) $(SNDFILE_CFLAGS)
+
 mod_LTLIBRARIES = mod_sndfile.la
-mod_sndfile_la_SOURCES  = mod_sndfile.c
+mod_sndfile_la_SOURCES  = 
 mod_sndfile_la_CFLAGS   = $(AM_CFLAGS) $(SNDFILE_CFLAGS)
-mod_sndfile_la_LIBADD   = $(switch_builddir)/libfreeswitch.la $(SNDFILE_LIBS)
+mod_sndfile_la_LIBADD   = libsndfilemod.la $(switch_builddir)/libfreeswitch.la $(SNDFILE_LIBS)
 mod_sndfile_la_LDFLAGS  = -avoid-version -module -no-undefined -shared
 
+noinst_PROGRAMS = test/test_sndfile test/test_sndfile_conf
+
+test_test_sndfile_SOURCES = test/test_sndfile.c
+test_test_sndfile_CFLAGS = $(AM_CFLAGS) -I./ -I../ -DSWITCH_TEST_BASE_DIR_FOR_CONF=\"${abs_builddir}/test\" -DSWITCH_TEST_BASE_DIR_OVERRIDE=\"${abs_builddir}/test\"
+test_test_sndfile_LDFLAGS = $(AM_LDFLAGS) -avoid-version -no-undefined $(freeswitch_LDFLAGS) $(switch_builddir)/libfreeswitch.la $(CORE_LIBS) $(APR_LIBS)
+test_test_sndfile_LDADD = libsndfilemod.la
+
+test_test_sndfile_conf_SOURCES = test/test_sndfile_conf.c
+test_test_sndfile_conf_CFLAGS = $(AM_CFLAGS) -I./ -I../ -DSWITCH_TEST_BASE_DIR_FOR_CONF=\"${abs_builddir}/test\" -DSWITCH_TEST_BASE_DIR_OVERRIDE=\"${abs_builddir}/test\"
+test_test_sndfile_conf_LDFLAGS = $(AM_LDFLAGS) -avoid-version -no-undefined $(freeswitch_LDFLAGS) $(switch_builddir)/libfreeswitch.la $(CORE_LIBS) $(APR_LIBS)
+test_test_sndfile_conf_LDADD = libsndfilemod.la
+
+TESTS = $(noinst_PROGRAMS)
+
 else
 install: error
 all: error
index a03e28af5c339bb7de14b28ca57980c7f0e88c0e..7e826b7c464576963dc7f42f3f0e1d2e33e73603 100644 (file)
@@ -39,6 +39,9 @@ SWITCH_MODULE_DEFINITION(mod_sndfile, mod_sndfile_load, mod_sndfile_shutdown, NU
 
 static struct {
        switch_hash_t *format_hash;
+       int debug;
+       char *allowed_extensions[100];
+       int allowed_extensions_count;
 } globals;
 
 struct format_map {
@@ -56,6 +59,16 @@ typedef struct sndfile_context sndfile_context;
 
 static switch_status_t sndfile_perform_open(sndfile_context *context, const char *path, int mode, switch_file_handle_t *handle);
 
+static void reverse_channel_count(switch_file_handle_t *handle) {
+       /* for recording stereo conferences and stereo calls in audio file formats that support only 1 channel.
+        * "{force_channels=1}" does similar, but here switch_core_open_file() was already called and we 
+        * have the handle and we chane the count before _read_ or _write_ are called (where muxing is done). */
+       if (handle->channels > 1) {
+               handle->real_channels = handle->channels;
+               handle->channels = handle->mm.channels = 1;
+       }
+}
+
 static switch_status_t sndfile_file_open(switch_file_handle_t *handle, const char *path)
 {
        sndfile_context *context;
@@ -119,41 +132,107 @@ static switch_status_t sndfile_file_open(switch_file_handle_t *handle, const cha
 
        if (!strcmp(ext, "raw")) {
                context->sfinfo.format = SF_FORMAT_RAW | SF_FORMAT_PCM_16;
+               if (mode & SFM_READ) {
+                       context->sfinfo.samplerate = 8000;
+                       context->sfinfo.channels = 1;
+               }
        } else if (!strcmp(ext, "r8")) {
                context->sfinfo.format = SF_FORMAT_RAW | SF_FORMAT_PCM_16;
-               context->sfinfo.samplerate = 8000;
+               if (mode & SFM_READ) {
+                       context->sfinfo.samplerate = 8000;
+                       context->sfinfo.channels = 1;
+               }
        } else if (!strcmp(ext, "r16")) {
                context->sfinfo.format = SF_FORMAT_RAW | SF_FORMAT_PCM_16;
-               context->sfinfo.samplerate = 16000;
+               if (mode & SFM_READ) {
+                       context->sfinfo.samplerate = 16000;
+                       context->sfinfo.channels = 1;
+               }
        } else if (!strcmp(ext, "r24")) {
                context->sfinfo.format = SF_FORMAT_RAW | SF_FORMAT_PCM_24;
-               context->sfinfo.samplerate = 24000;
+               if (mode & SFM_READ) {
+                       context->sfinfo.samplerate = 24000;
+                       context->sfinfo.channels = 1;
+               }
        } else if (!strcmp(ext, "r32")) {
                context->sfinfo.format = SF_FORMAT_RAW | SF_FORMAT_PCM_32;
-               context->sfinfo.samplerate = 32000;
+               if (mode & SFM_READ) {
+                       context->sfinfo.samplerate = 32000;
+                       context->sfinfo.channels = 1;
+               }
        } else if (!strcmp(ext, "gsm")) {
                context->sfinfo.format = SF_FORMAT_RAW | SF_FORMAT_GSM610;
                context->sfinfo.channels = 1;
+               if (mode & SFM_WRITE) {
+                       reverse_channel_count(handle);
+               }
                context->sfinfo.samplerate = 8000;
        } else if (!strcmp(ext, "ul") || !strcmp(ext, "ulaw")) {
                context->sfinfo.format = SF_FORMAT_RAW | SF_FORMAT_ULAW;
-               context->sfinfo.channels = 1;
-               context->sfinfo.samplerate = 8000;
+               if (mode & SFM_READ) {
+                       context->sfinfo.samplerate = 8000;
+                       context->sfinfo.channels = 1;
+               }
        } else if (!strcmp(ext, "al") || !strcmp(ext, "alaw")) {
                context->sfinfo.format = SF_FORMAT_RAW | SF_FORMAT_ALAW;
-               context->sfinfo.channels = 1;
-               context->sfinfo.samplerate = 8000;
+               if (mode & SFM_READ) {
+                       context->sfinfo.samplerate = 8000;
+                       context->sfinfo.channels = 1;
+               }
        } else if (!strcmp(ext, "vox")) {
                context->sfinfo.format = SF_FORMAT_RAW | SF_FORMAT_VOX_ADPCM;
                context->sfinfo.channels = 1;
                context->sfinfo.samplerate = 8000;
+               if (mode & SFM_WRITE) {
+                       reverse_channel_count(handle);
+               }
        } else if (!strcmp(ext, "adpcm")) {
                context->sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_IMA_ADPCM;
                context->sfinfo.channels = 1;
                context->sfinfo.samplerate = 8000;
+               if (mode & SFM_WRITE) {
+                       reverse_channel_count(handle);
+               }
        } else if (!strcmp(ext, "oga") || !strcmp(ext, "ogg")) {
                context->sfinfo.format = SF_FORMAT_OGG | SF_FORMAT_VORBIS;
-               context->sfinfo.samplerate = handle->samplerate;
+               if (mode & SFM_READ) {
+                       context->sfinfo.samplerate = handle->samplerate;
+               }
+       } else if (!strcmp(ext, "wve")) {
+               context->sfinfo.format = SF_FORMAT_WVE | SF_FORMAT_ALAW;
+               context->sfinfo.channels = 1;
+               context->sfinfo.samplerate = 8000;
+               if (mode & SFM_WRITE) {
+                       reverse_channel_count(handle);
+               }
+       } else if (!strcmp(ext, "htk")) {
+               context->sfinfo.format = SF_FORMAT_HTK | SF_FORMAT_PCM_16;
+               context->sfinfo.channels = 1;
+               context->sfinfo.samplerate = 8000;
+               if (mode & SFM_WRITE) {
+                       reverse_channel_count(handle);
+               }
+       } else if (!strcmp(ext, "iff")) {
+               context->sfinfo.format = SF_FORMAT_AIFF | SF_FORMAT_PCM_16;
+               context->sfinfo.channels = 1;
+               context->sfinfo.samplerate = 8000;
+               if (mode & SFM_WRITE) {
+                       reverse_channel_count(handle);
+               }
+       } else if (!strcmp(ext, "xi")) {
+               context->sfinfo.format = SF_FORMAT_XI | SF_FORMAT_DPCM_16;
+               context->sfinfo.channels = 1;
+               context->sfinfo.samplerate = 44100;
+               if (mode & SFM_WRITE) {
+                       reverse_channel_count(handle);
+               }
+       } else if (!strcmp(ext, "sds")) {
+               context->sfinfo.format = SF_FORMAT_SDS | SF_FORMAT_PCM_16;
+               context->sfinfo.channels = 1;
+               context->sfinfo.samplerate = 8000;
+               if (mode & SFM_WRITE) {
+                       reverse_channel_count(handle);
+               }
        }
 
        if ((mode & SFM_WRITE) && sf_format_check(&context->sfinfo) == 0) {
@@ -204,7 +283,10 @@ static switch_status_t sndfile_file_open(switch_file_handle_t *handle, const cha
                        goto end;
                }
        }
-       //switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Opening File [%s] rate %dhz\n", path, context->sfinfo.samplerate);
+       if (globals.debug) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, 
+                               "Opening File [%s] rate [%dhz] channels: [%d]\n", path, context->sfinfo.samplerate, (uint8_t) context->sfinfo.channels);
+       }
        handle->samples = (unsigned int) context->sfinfo.frames;
        handle->samplerate = context->sfinfo.samplerate;
        handle->channels = (uint8_t) context->sfinfo.channels;
@@ -365,6 +447,20 @@ static switch_status_t sndfile_file_get_string(switch_file_handle_t *handle, swi
        return SWITCH_STATUS_FALSE;
 }
 
+static switch_bool_t exten_is_allowed(const char *exten) {
+       int i;
+       if (!globals.allowed_extensions[0]) {
+               // defaults to allowing all extensions if param "allowed-extensions" not set in cfg
+               return SWITCH_TRUE;
+       }
+       for (i = 0 ; i < globals.allowed_extensions_count; i++) {
+               if (exten && globals.allowed_extensions[i] && !strcasecmp(globals.allowed_extensions[i], exten)) {
+                       return SWITCH_TRUE;
+               }
+       }
+       return SWITCH_FALSE;
+}
+
 /* Registration */
 
 static char **supported_formats;
@@ -407,6 +503,9 @@ static switch_status_t setup_formats(switch_memory_pool_t *pool)
                skip = 0;
                info.format = m;
                sf_command(NULL, SFC_GET_FORMAT_MAJOR, &info, sizeof(info));
+               if (!exten_is_allowed(info.extension)) {
+                       continue;
+               }
                switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_INFO, "%s  (extension \"%s\")\n", info.name, info.extension);
                for (x = 0; x < len; x++) {
                        if (supported_formats[x] == info.extension) {
@@ -470,7 +569,9 @@ static switch_status_t setup_formats(switch_memory_pool_t *pool)
                }
        }
        for (m = 0; m < exlen; m++) {
-               supported_formats[len++] = extras[m];
+               if (exten_is_allowed(extras[m])) {
+                       supported_formats[len++] = extras[m];
+               }
        }
 
        switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_NOTICE, "================================================================================\n");
@@ -478,12 +579,49 @@ static switch_status_t setup_formats(switch_memory_pool_t *pool)
        return SWITCH_STATUS_SUCCESS;
 }
 
+#define SNDFILE_DEBUG_SYNTAX "<on|off>"
+SWITCH_STANDARD_API(mod_sndfile_debug)
+{
+               if (zstr(cmd)) {
+                       stream->write_function(stream, "-USAGE: %s\n", SNDFILE_DEBUG_SYNTAX);
+               } else {
+                       if (!strcasecmp(cmd, "on")) {
+                               globals.debug = 1;
+                               stream->write_function(stream, "Sndfile Debug: on\n");
+                       } else if (!strcasecmp(cmd, "off")) {
+                               globals.debug = 0;
+                               stream->write_function(stream, "Sndfile Debug: off\n");
+                       } else {
+                               stream->write_function(stream, "-USAGE: %s\n", SNDFILE_DEBUG_SYNTAX);
+                       }
+               }
+       return SWITCH_STATUS_SUCCESS;
+}
+
 SWITCH_MODULE_LOAD_FUNCTION(mod_sndfile_load)
 {
        switch_file_interface_t *file_interface;
+       switch_api_interface_t *commands_api_interface;
+       char *cf = "sndfile.conf";
+       switch_xml_t cfg, xml, settings, param;
+
+       memset(&globals, 0, sizeof(globals));
 
        switch_core_hash_init(&globals.format_hash);
 
+       if ((xml = switch_xml_open_cfg(cf, &cfg, NULL))) {
+               if ((settings = switch_xml_child(cfg, "settings"))) {
+                       for (param = switch_xml_child(settings, "param"); param; param = param->next) {
+                               char *var = (char *) switch_xml_attr_soft(param, "name");
+                               char *val = (char *) switch_xml_attr_soft(param, "value");
+                               if (!strcasecmp(var, "allowed-extensions") && val) {
+                                       globals.allowed_extensions_count = switch_separate_string(val, ',', globals.allowed_extensions, (sizeof(globals.allowed_extensions) / sizeof(globals.allowed_extensions[0])));
+                               }
+                       }
+               }
+               switch_xml_free(xml);
+       }
+
        if (setup_formats(pool) != SWITCH_STATUS_SUCCESS) {
                return SWITCH_STATUS_FALSE;
        }
@@ -502,6 +640,11 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_sndfile_load)
        file_interface->file_set_string = sndfile_file_set_string;
        file_interface->file_get_string = sndfile_file_get_string;
 
+       SWITCH_ADD_API(commands_api_interface, "sndfile_debug", "Set sndfile debug", mod_sndfile_debug, SNDFILE_DEBUG_SYNTAX);
+
+       switch_console_set_complete("add sndfile_debug on");
+       switch_console_set_complete("add sndfile_debug off");
+
        /* indicate that the module should continue to be loaded */
        return SWITCH_STATUS_SUCCESS;
 }
diff --git a/src/mod/formats/mod_sndfile/test/sounds/hello_stereo.wav b/src/mod/formats/mod_sndfile/test/sounds/hello_stereo.wav
new file mode 100644 (file)
index 0000000..83e8388
Binary files /dev/null and b/src/mod/formats/mod_sndfile/test/sounds/hello_stereo.wav differ
diff --git a/src/mod/formats/mod_sndfile/test/sounds/hi.wav b/src/mod/formats/mod_sndfile/test/sounds/hi.wav
new file mode 100644 (file)
index 0000000..fec0d6b
Binary files /dev/null and b/src/mod/formats/mod_sndfile/test/sounds/hi.wav differ
diff --git a/src/mod/formats/mod_sndfile/test/test_conf/freeswitch.xml b/src/mod/formats/mod_sndfile/test/test_conf/freeswitch.xml
new file mode 100644 (file)
index 0000000..c29aed8
--- /dev/null
@@ -0,0 +1,27 @@
+<document type="freeswitch/xml">
+
+  <section name="configuration" description="Various Configuration">
+    <configuration name="modules.conf" description="Modules">
+      <modules>
+        <load module="mod_loopback"/>
+        <load module="mod_sndfile"/>
+      </modules>
+    </configuration>
+    <configuration name="sndfile.conf">
+        <settings>
+                <!-- Allow only these file extensions. Default: allow all sndfile provided extensions + FS custom extra -->               
+                <param name="allowed-extensions" value="wav,raw,r8,r16"/>
+        </settings>
+    </configuration>
+  </section>
+
+  <section name="dialplan" description="Regex/XML Dialplan">
+    <context name="default">
+      <extension name="sample">
+        <condition>
+          <action application="info"/>
+        </condition>
+      </extension>
+    </context>
+  </section>
+</document>
diff --git a/src/mod/formats/mod_sndfile/test/test_conf/freeswitch.xml.fsxml b/src/mod/formats/mod_sndfile/test/test_conf/freeswitch.xml.fsxml
new file mode 100644 (file)
index 0000000..c29aed8
--- /dev/null
@@ -0,0 +1,27 @@
+<document type="freeswitch/xml">
+
+  <section name="configuration" description="Various Configuration">
+    <configuration name="modules.conf" description="Modules">
+      <modules>
+        <load module="mod_loopback"/>
+        <load module="mod_sndfile"/>
+      </modules>
+    </configuration>
+    <configuration name="sndfile.conf">
+        <settings>
+                <!-- Allow only these file extensions. Default: allow all sndfile provided extensions + FS custom extra -->               
+                <param name="allowed-extensions" value="wav,raw,r8,r16"/>
+        </settings>
+    </configuration>
+  </section>
+
+  <section name="dialplan" description="Regex/XML Dialplan">
+    <context name="default">
+      <extension name="sample">
+        <condition>
+          <action application="info"/>
+        </condition>
+      </extension>
+    </context>
+  </section>
+</document>
diff --git a/src/mod/formats/mod_sndfile/test/test_formats_and_muxing/freeswitch.xml b/src/mod/formats/mod_sndfile/test/test_formats_and_muxing/freeswitch.xml
new file mode 100644 (file)
index 0000000..39178db
--- /dev/null
@@ -0,0 +1,21 @@
+<document type="freeswitch/xml">
+
+  <section name="configuration" description="Various Configuration">
+    <configuration name="modules.conf" description="Modules">
+      <modules>
+        <load module="mod_loopback"/>
+        <load module="mod_sndfile"/>
+      </modules>
+    </configuration>
+  </section>
+
+  <section name="dialplan" description="Regex/XML Dialplan">
+    <context name="default">
+      <extension name="sample">
+        <condition>
+          <action application="info"/>
+        </condition>
+      </extension>
+    </context>
+  </section>
+</document>
diff --git a/src/mod/formats/mod_sndfile/test/test_sndfile.c b/src/mod/formats/mod_sndfile/test/test_sndfile.c
new file mode 100644 (file)
index 0000000..a9783ff
--- /dev/null
@@ -0,0 +1,451 @@
+/*
+ * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ * Copyright (C) 2005-2018, 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@signalwire.com>
+ *
+ * test_sndfile.c -- tests mod_sndfile
+ *
+ */
+#include <switch.h>
+#include <stdlib.h>
+
+#include <test/switch_test.h>
+
+/* Media files used:
+ *
+ * 1. hi.wav
+ *
+ * General
+ * Complete name                            : test/sounds/hi.wav
+ * Format                                   : Wave
+ * File size                                : 67.2 KiB
+ * Duration                                 : 2 s 150 ms
+ * Overall bit rate mode                    : Constant
+ * Overall bit rate                         : 256 kb/s
+ *
+ * Audio
+ * Format                                   : PCM
+ * Format settings, Endianness              : Little
+ * Format settings, Sign                    : Signed
+ * Codec ID                                 : 1
+ * Duration                                 : 2 s 150 ms
+ * Bit rate mode                            : Constant
+ * Bit rate                                 : 256 kb/s
+ * Channel(s)                               : 1 channel
+ * Sampling rate                            : 16.0 kHz
+ * Bit depth                                : 16 bits
+ * Stream size                              : 67.2 KiB (100%)
+ *
+ *
+ * 2. hello_stereo.wav 
+ *
+ *
+ * General
+ * Complete name                            : sounds/hello_stereo.wav
+ * Format                                   : Wave
+ * File size                                : 220 KiB
+ * Duration                                 : 1 s 277 ms
+ * Overall bit rate mode                    : Constant
+ * Overall bit rate                         : 1 412 kb/s
+ *
+ * Audio
+ * Format                                   : PCM
+ * Format settings, Endianness              : Little
+ * Format settings, Sign                    : Signed
+ * Codec ID                                 : 1
+ * Duration                                 : 1 s 277 ms
+ * Bit rate mode                            : Constant
+ * Bit rate                                 : 1 411.2 kb/s
+ * Channel(s)                               : 2 channels
+ * Sampling rate                            : 44.1 kHz
+ * Bit depth                                : 16 bits
+ * Stream size                              : 220 KiB (100%)
+ *
+
+*/
+
+char *extensions[] = { 
+       "aiff", "au", "avr", "caf", 
+       "flac", "htk", "iff", "mat", 
+       "mpc", "paf", "pvf", "rf64", 
+       "sd2", "sds", "sf", "voc", 
+       "w64", "wav", "wve", "xi",
+       "raw", "r8", "r16", "r24", 
+       "r32", "ul", "ulaw", "al", 
+       "alaw", "gsm", "vox", "oga", "ogg"};
+
+FST_CORE_BEGIN("test_formats_and_muxing")
+{
+       FST_SUITE_BEGIN(test_sndfile)
+       {
+               FST_SETUP_BEGIN()
+               {
+                       fst_requires_module("mod_loopback");
+                       fst_requires_module("mod_sndfile");
+               }
+               FST_SETUP_END()
+
+               FST_TEARDOWN_BEGIN()
+               {
+               }
+               FST_TEARDOWN_END()
+
+               FST_TEST_BEGIN(sndfile_write_read_mono)
+               {
+                       /* play mono, record mono, open mono */
+                       switch_core_session_t *session = NULL;
+                       switch_channel_t *channel = NULL;
+                       switch_status_t status;
+                       switch_call_cause_t cause;
+                       static char play_filename[] = "../sounds/hi.wav";
+                       char path[4096];
+                       switch_file_handle_t fh = { 0 };
+                       int16_t *audiobuf;
+                       switch_size_t len, rd;
+                       char *recording;
+                       int i, exlen, timeout_sec = 2, duration = 3000; /*ms*/
+                       switch_stream_handle_t stream = { 0 };
+
+                       sprintf(path, "%s%s%s", SWITCH_GLOBAL_dirs.conf_dir, SWITCH_PATH_SEPARATOR, play_filename);
+
+                       SWITCH_STANDARD_STREAM(stream);
+
+                       switch_api_execute("sndfile_debug", "on", session, &stream);
+
+                       switch_safe_free(stream.data);
+
+                       exlen = (sizeof(extensions) / sizeof(extensions[0]));
+
+                       status = switch_ivr_originate(NULL, &session, &cause, "null/+15553334444", timeout_sec, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+                       fst_requires(session);
+                       fst_check(status == SWITCH_STATUS_SUCCESS);
+
+                       for (i = 0; i < exlen; i++) {
+
+                               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Testing media file extension: [%s]\n", extensions[i]);
+
+                               recording = switch_mprintf("/tmp/%s.%s", switch_core_session_get_uuid(session), extensions[i]);
+                               status = switch_ivr_record_session(session, recording, duration, NULL);
+                               fst_check(status == SWITCH_STATUS_SUCCESS);
+
+                               status = switch_ivr_play_file(session, NULL, path, NULL);
+                               fst_check(status == SWITCH_STATUS_SUCCESS);
+
+                               switch_sleep(1000 * duration); // wait for audio to finish playing
+
+                               switch_ivr_stop_record_session(session, "all");
+
+                               status = switch_core_file_open(&fh, recording, 1, 8000, SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT, NULL);
+                               fst_requires(status == SWITCH_STATUS_SUCCESS);
+
+                               rd = 320; // samples
+                               len = rd * sizeof(*audiobuf);
+                               switch_zmalloc(audiobuf, len);
+
+                               status = switch_core_file_read(&fh, audiobuf, &rd);
+                               fst_requires(status == SWITCH_STATUS_SUCCESS);
+                               fst_check(rd = 320); // check that we read the wanted number of samples
+
+                               status = switch_core_file_close(&fh);
+                               fst_requires(status == SWITCH_STATUS_SUCCESS);
+
+                               switch_safe_free(audiobuf);
+
+                               unlink(recording);
+
+                               switch_safe_free(recording);
+
+                               switch_sleep(1000000);
+                       }
+
+                       channel = switch_core_session_get_channel(session);
+                       fst_requires(channel);
+
+                       switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING);
+                       fst_check(!switch_channel_ready(channel));
+
+                       switch_core_session_rwunlock(session);
+               }
+
+               FST_TEST_END()
+
+               FST_TEST_BEGIN(sndfile_write_read_m2s)
+               {
+                       /* play mono file, record mono, open stereo */
+                       switch_core_session_t *session = NULL;
+                       switch_channel_t *channel = NULL;
+                       switch_status_t status;
+                       switch_call_cause_t cause;
+                       static char play_filename[] = "../sounds/hi.wav";
+                       char path[4096];
+                       switch_file_handle_t fh = { 0 };
+                       int16_t *audiobuf;
+                       switch_size_t len, rd;
+                       char *recording;
+                       int i, exlen, channels = 2, timeout_sec = 2, duration = 3000; /*ms*/
+                       switch_stream_handle_t stream = { 0 };
+               
+                       sprintf(path, "%s%s%s", SWITCH_GLOBAL_dirs.conf_dir, SWITCH_PATH_SEPARATOR, play_filename);
+
+                       SWITCH_STANDARD_STREAM(stream);
+
+                       switch_api_execute("sndfile_debug", "on", session, &stream);
+
+                       switch_safe_free(stream.data);
+
+                       exlen = (sizeof(extensions) / sizeof(extensions[0]));
+
+                       for (i = 0; i < exlen; i++) {
+
+                               status = switch_ivr_originate(NULL, &session, &cause, "null/+15553334444", timeout_sec, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+                               fst_requires(session);
+                               fst_check(status == SWITCH_STATUS_SUCCESS);
+
+                               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Testing media file extension: [%s]\n", extensions[i]);
+
+                               recording = switch_mprintf("/tmp/%s.%s", switch_core_session_get_uuid(session), extensions[i]);
+
+                               status = switch_ivr_record_session(session, recording, duration, NULL);
+                               fst_check(status == SWITCH_STATUS_SUCCESS);
+
+                               status = switch_ivr_play_file(session, NULL, path, NULL);
+                               fst_check(status == SWITCH_STATUS_SUCCESS);
+
+                               switch_sleep(1000 * duration); // wait for audio to finish playing
+
+                               switch_ivr_stop_record_session(session, "all");
+
+                               channel = switch_core_session_get_channel(session);
+                               fst_requires(channel);
+
+                               switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING);
+                               fst_check(!switch_channel_ready(channel));
+
+                               switch_core_session_rwunlock(session);
+
+                               status = switch_core_file_open(&fh, recording, channels, 8000, SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT, NULL);
+                               fst_requires(status == SWITCH_STATUS_SUCCESS);
+
+                               rd = 320; // samples
+                               len = rd * sizeof(*audiobuf) * channels;
+                               switch_zmalloc(audiobuf, len);
+
+                               status = switch_core_file_read(&fh, audiobuf, &rd);
+                               fst_requires(status == SWITCH_STATUS_SUCCESS);
+                               fst_check(rd = 320); // check that we read the wanted number of samples
+
+                               status = switch_core_file_close(&fh);
+                               fst_requires(status == SWITCH_STATUS_SUCCESS);
+
+                               switch_safe_free(audiobuf);
+
+                               unlink(recording);
+
+                               switch_safe_free(recording);
+
+                               switch_sleep(1000000);
+                       }
+               }
+               FST_TEST_END()
+
+               FST_TEST_BEGIN(sndfile_write_read_s2m)
+               {
+                       /* play stereo wav, record stereo, open stereo file */
+                       switch_core_session_t *session = NULL;
+                       switch_channel_t *channel = NULL;
+                       switch_status_t status;
+                       switch_call_cause_t cause;
+                       static char play_filename[] = "../sounds/hello_stereo.wav";
+                       char path[4096];
+                       switch_file_handle_t fh = { 0 };
+                       int16_t *audiobuf;
+                       switch_size_t len, rd;
+                       char *recording, *rec_path;
+                       int i, exlen, channels = 2, timeout_sec = 2, duration = 3000; /*ms*/
+                       switch_stream_handle_t stream = { 0 };
+                       
+                       sprintf(path, "%s%s%s", SWITCH_GLOBAL_dirs.conf_dir, SWITCH_PATH_SEPARATOR, play_filename);
+
+                       SWITCH_STANDARD_STREAM(stream);
+
+                       switch_api_execute("sndfile_debug", "on", session, &stream);
+
+                       switch_safe_free(stream.data);
+
+                       exlen = (sizeof(extensions) / sizeof(extensions[0]));
+
+                       for (i = 0; i < exlen; i++) {
+
+                               status = switch_ivr_originate(NULL, &session, &cause, "null/+15553334444", timeout_sec, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+
+                               fst_requires(session);
+                               fst_check(status == SWITCH_STATUS_SUCCESS);
+
+                               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Testing media file extension: [%s]\n", extensions[i]);
+
+                               rec_path = switch_mprintf("/tmp/%s.%s", switch_core_session_get_uuid(session), extensions[i]);
+                               recording = switch_mprintf("{force_channels=2}%s", rec_path);
+
+                               channel = switch_core_session_get_channel(session);
+                               fst_requires(channel);
+
+                               switch_channel_set_variable(channel, "enable_file_write_buffering", "true");
+
+                               status = switch_ivr_record_session(session, recording, duration, NULL);
+                               fst_check(status == SWITCH_STATUS_SUCCESS);
+
+                               status = switch_ivr_play_file(session, NULL, path, NULL);
+                               fst_check(status == SWITCH_STATUS_SUCCESS);
+
+                               switch_sleep(1000 * duration); // wait for audio to finish playing
+
+                               switch_ivr_stop_record_session(session, "all");
+
+                               switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING);
+                               fst_check(!switch_channel_ready(channel));
+
+                               switch_core_session_rwunlock(session);
+
+                               status = switch_core_file_open(&fh, recording, channels, 8000, SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT, NULL);
+                               fst_requires(status == SWITCH_STATUS_SUCCESS);
+
+                               rd = 320; // samples
+                               len = rd * sizeof(*audiobuf) * channels;
+                               switch_zmalloc(audiobuf, len);
+
+                               status = switch_core_file_read(&fh, audiobuf, &rd);
+                               fst_requires(status == SWITCH_STATUS_SUCCESS);
+                               fst_check(rd = 320); // check that we read the wanted number of samples
+
+                               status = switch_core_file_close(&fh);
+                               fst_requires(status == SWITCH_STATUS_SUCCESS);
+
+                               switch_safe_free(audiobuf);
+
+                               unlink(rec_path);
+
+                               switch_safe_free(rec_path);
+                               switch_safe_free(recording);
+
+                               switch_sleep(1000000);
+                       }
+               }
+
+               FST_TEST_END()
+
+               FST_TEST_BEGIN(sndfile_write_read_stereo)
+               {
+                       /* play stereo wav, record stereo, open stereo file */
+                       switch_core_session_t *session = NULL;
+                       switch_channel_t *channel = NULL;
+                       switch_status_t status;
+                       switch_call_cause_t cause;
+                       static char play_filename[] = "../sounds/hello_stereo.wav";
+                       char path[4096];
+                       switch_file_handle_t fh = { 0 };
+                       int16_t *audiobuf;
+                       switch_size_t len, rd;
+                       char *recording, *rec_path;
+                       int i, exlen, channels = 2, timeout_sec = 2, duration = 3000; /*ms*/
+                       switch_stream_handle_t stream = { 0 };
+                       
+                       sprintf(path, "%s%s%s", SWITCH_GLOBAL_dirs.conf_dir, SWITCH_PATH_SEPARATOR, play_filename);
+
+                       SWITCH_STANDARD_STREAM(stream);
+
+                       switch_api_execute("sndfile_debug", "on", session, &stream);
+
+                       switch_safe_free(stream.data);
+
+                       exlen = (sizeof(extensions) / sizeof(extensions[0]));
+
+                       for (i = 0; i < exlen; i++) {
+
+                               status = switch_ivr_originate(NULL, &session, &cause, "null/+15553334444", timeout_sec, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+                               fst_requires(session);
+                               fst_check(status == SWITCH_STATUS_SUCCESS);
+
+                               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Testing media file extension: [%s]\n", extensions[i]);
+
+                               rec_path = switch_mprintf("/tmp/%s.%s", switch_core_session_get_uuid(session), extensions[i]);
+                               recording = switch_mprintf("{force_channels=2}%s", rec_path);
+
+                               channel = switch_core_session_get_channel(session);
+                               fst_requires(channel);
+
+                               switch_channel_set_variable(channel, "RECORD_STEREO", "true");
+                               switch_channel_set_variable(channel, "enable_file_write_buffering", "true");
+
+                               status = switch_ivr_record_session(session, recording, duration, NULL);
+                               fst_check(status == SWITCH_STATUS_SUCCESS);
+
+                               status = switch_ivr_play_file(session, NULL, path, NULL);
+                               fst_check(status == SWITCH_STATUS_SUCCESS);
+
+                               switch_sleep(1000 * duration); // wait for audio to finish playing
+
+                               switch_ivr_stop_record_session(session, "all");
+
+                               switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING);
+                               fst_check(!switch_channel_ready(channel));
+
+                               switch_core_session_rwunlock(session);
+
+                               status = switch_core_file_open(&fh, recording, channels, 8000, SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT, NULL);
+                               fst_requires(status == SWITCH_STATUS_SUCCESS);
+
+                               rd = 320; // samples
+                               len = rd * sizeof(*audiobuf) * channels;
+                               switch_zmalloc(audiobuf, len);
+
+                               status = switch_core_file_read(&fh, audiobuf, &rd);
+                               fst_requires(status == SWITCH_STATUS_SUCCESS);
+                               fst_check(rd = 320); // check that we read the wanted number of samples
+
+                               status = switch_core_file_close(&fh);
+                               fst_requires(status == SWITCH_STATUS_SUCCESS);
+
+                               switch_safe_free(audiobuf);
+
+                               unlink(rec_path);
+
+                               switch_safe_free(rec_path);
+                               switch_safe_free(recording);
+
+                               switch_sleep(1000000);
+                       }
+               }
+               FST_TEST_END()
+
+               FST_TEST_BEGIN(unload_mod_sndfile)
+               {
+                       const char *err = NULL;
+                       switch_sleep(1000000);
+                       fst_check(switch_loadable_module_unload_module((char *)"../.libs", (char *)"mod_sndfile", SWITCH_TRUE, &err) == SWITCH_STATUS_SUCCESS);
+               }
+               FST_TEST_END()
+       }
+       FST_SUITE_END()
+}
+FST_CORE_END()
diff --git a/src/mod/formats/mod_sndfile/test/test_sndfile_conf.c b/src/mod/formats/mod_sndfile/test/test_sndfile_conf.c
new file mode 100644 (file)
index 0000000..5150591
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ * Copyright (C) 2005-2018, 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@signalwire.com>
+ *
+ * test_sndfile_conf.c -- tests mod_sndfile config
+ *
+ */
+#include <switch.h>
+#include <stdlib.h>
+
+#include <test/switch_test.h>
+
+char *extensions_will_fail[] = {  // not allowed through conf file.
+       "ul", "gsm", "vox", "ogg"
+};
+
+char *extensions_will_succeed[] = {  // allowed through conf file.
+       "wav", "raw", "r8", "r16"
+};
+
+FST_CORE_BEGIN("test_conf")
+{
+       FST_SUITE_BEGIN(test_sndfile)
+       {
+               FST_SETUP_BEGIN()
+               {
+                       fst_requires_module("mod_loopback");
+                       fst_requires_module("mod_sndfile");
+               }
+               FST_SETUP_END()
+
+               FST_TEARDOWN_BEGIN()
+               {
+               }
+               FST_TEARDOWN_END()
+
+               FST_TEST_BEGIN(sndfile_exten_not_allowed)
+               {
+                       switch_core_session_t *session = NULL;
+                       switch_channel_t *channel = NULL;
+                       switch_status_t status;
+                       switch_call_cause_t cause;
+                       char *recording;
+                       int i, exlen, timeout_sec = 2;
+                       switch_stream_handle_t stream = { 0 };
+                         
+                       SWITCH_STANDARD_STREAM(stream);
+
+                       switch_api_execute("sndfile_debug", "on", session, &stream);
+
+                       switch_safe_free(stream.data);
+
+                       exlen = (sizeof(extensions_will_fail) / sizeof(extensions_will_fail[0]));
+
+                       status = switch_ivr_originate(NULL, &session, &cause, "null/+15553334444", timeout_sec, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+                       fst_requires(session);
+                       fst_check(status == SWITCH_STATUS_SUCCESS);
+
+                       for (i = 0; i < exlen; i++) {
+                               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Testing media file extension: [%s]\n", extensions_will_fail[i]);
+
+                               recording = switch_mprintf("/tmp/%s.%s", switch_core_session_get_uuid(session), extensions_will_fail[i]);
+                               status = switch_ivr_record_session(session, recording, 3000, NULL);
+                               fst_check(status == SWITCH_STATUS_GENERR);
+                               if (status == SWITCH_STATUS_SUCCESS) {
+                                       // not expected
+                                       unlink(recording);
+                               }
+
+                               switch_safe_free(recording);
+                       }
+
+                       channel = switch_core_session_get_channel(session);
+                       fst_requires(channel);
+
+                       switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING);
+                       fst_check(!switch_channel_ready(channel));
+
+                       switch_core_session_rwunlock(session);
+
+                       switch_sleep(1000000);
+               }
+               FST_TEST_END()
+               FST_TEST_BEGIN(sndfile_exten_allowed)
+               {
+                       switch_core_session_t *session = NULL;
+                       switch_channel_t *channel = NULL;
+                       switch_status_t status;
+                       switch_call_cause_t cause;
+                       char *recording;
+                       int i, exlen, timeout_sec = 2;
+                       switch_stream_handle_t stream = { 0 };
+                         
+                       SWITCH_STANDARD_STREAM(stream);
+
+                       switch_api_execute("sndfile_debug", "on", session, &stream);
+
+                       switch_safe_free(stream.data);
+
+                       exlen = (sizeof(extensions_will_succeed) / sizeof(extensions_will_succeed[0]));
+
+                       status = switch_ivr_originate(NULL, &session, &cause, "null/+15553334444", timeout_sec, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+                       fst_requires(session);
+                       fst_check(status == SWITCH_STATUS_SUCCESS);
+
+                       for (i = 0; i < exlen; i++) {
+                               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Testing media file extension: [%s]\n", extensions_will_succeed[i]);
+
+                               recording = switch_mprintf("/tmp/%s.%s", switch_core_session_get_uuid(session), extensions_will_succeed[i]);
+                               status = switch_ivr_record_session(session, recording, 3000, NULL);
+                               fst_check(status == SWITCH_STATUS_SUCCESS);
+
+                               unlink(recording);
+
+                               switch_safe_free(recording);
+                       }
+
+                       channel = switch_core_session_get_channel(session);
+                       fst_requires(channel);
+
+                       switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING);
+                       fst_check(!switch_channel_ready(channel));
+
+                       switch_core_session_rwunlock(session);
+
+                       switch_sleep(1000000);
+               }
+               FST_TEST_END()
+       }
+       FST_SUITE_END()
+}
+FST_CORE_END()