diff --git a/.gitignore b/.gitignore index 1fc3016d3f..5da9566192 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,12 @@ configure.lineno config.log config.status core.* +*.2010.log +*.Build.CppClean.log +*.tlog +*.unsuccessfulbuild +*.cache +*.lastbuildstate /w32/Library/lastversion /w32/Library/tmpVersion.Bat @@ -83,6 +89,7 @@ core.* /build/config/missing /build/freeswitch.pc /build/getlib.sh +/build/getg729.sh /build/getsounds.sh /build/Makefile /build/Makefile.in @@ -104,6 +111,7 @@ core.* /libs/freetdm/testtones /libs/fsg729-*-installer /libs/g729/ +/libs/libcodec2/compile /libs/libcodec2/config.guess /libs/libcodec2/config.sub /libs/libcodec2/configure @@ -118,6 +126,7 @@ core.* /libs/libcodec2/src/Makefile.in /libs/libcodec2/unittest/Makefile /libs/libcodec2/unittest/Makefile.in +/libs/mpg123-1.13.2/ /scripts/fsxs /scripts/gentls_cert @@ -147,3 +156,17 @@ core.* /src/mod/say/mod_say_th/Makefile /src/mod/say/mod_say_zh/Makefile +BuildLog.htm +/w32/Console/Debug/ +/w32/Console/Release/ +/w32/Library/Debug/ +/w32/Library/Release/ + +Freeswitch.2010.sdf + +/Win32/ +/x64/ + +src/mod/codecs/mod_celt/*/*/mod_celt.log +src/mod/endpoints/mod_skinny/*/*/mod_skinny_2010.log +src/mod/formats/mod_shout/*/*/mod_shout.log diff --git a/build/modules.conf.in b/build/modules.conf.in index 63a0ab491e..8171773fe3 100644 --- a/build/modules.conf.in +++ b/build/modules.conf.in @@ -36,6 +36,7 @@ applications/mod_valet_parking #applications/mod_snipe_hunt #applications/mod_callcenter #applications/mod_fsk +#applications/mod_ladspa codecs/mod_g723_1 codecs/mod_amr #codecs/mod_amrwb diff --git a/conf/README_IMPORTANT.txt b/conf/README_IMPORTANT.txt new file mode 100644 index 0000000000..8d82b404f6 --- /dev/null +++ b/conf/README_IMPORTANT.txt @@ -0,0 +1,35 @@ + -= PLEASE READ THIS BEFORE YOU PUT A FreeSWITCH BOX INTO PRODUCTION =- + +This configuration, generally known as the "default configuration" for FreeSWITCH, is *NOT* designed to be put into a production environment without some important modifications. Please keep in mind that the default configuration is designed to demonstrate what FreeSWITCH *can* do, not what it *should* do in your specific scenario. + +*** SECURING YOUR SERVER *** + +By default, FreeSWITCH starts up and does a NATPMP and UPnP request to your router. If your router supports either of these protocols then FreeSWITCH does two things: +#1 - It gets the external IP address, which it uses for SIP communications +#2 - It causes there to be a "pinhole" opened up in the router allowing inbound communications to your FreeSWITCH server + +Please re-read #2. Now, please re-read #2 again. If you do not want a pinhole coming through your router then DO NOT USE the "auto-nat" tools. The way to disable the auto-nat (that is, UPnP/NATPMP) checking is to start FreeSWITCH with the "-nonat" flag. Easy enough. + +If you are planning on putting a system into production then you had better pay attention to security in other areas as well. If you are behind a firewall then make sure your firewall is actually protecting you. If you have your server on a public-facing Internet connection then we recommend a few things: +#1 - Consider using iptables (Linux/Unix) +#2 - Consider using fail2ban (see http://wiki.freeswitch.org/wiki/Fail2ban) + +*** SECURING YOUR USERS *** + +By default, the static XML files have 20 "directory users" in conf/directory/10xx.xml, numbered 1000-1019. Also, the default dialplan has routing for calls to those same extension numbers. (NOTE: the directory and the dialplan are 100% separate concepts. Check out chapters 3-5 of the awesome FreeSWITCH book for details.) + +The default users all have *very* simple passwords for SIP credentials and voicemail. If you put those into a production system then you are either brave, ignorant, or stupid. Please don't be any of those three things! You have a few choices for handling your users: + +#1 - Delete the static XML files and use mod_xml_curl for dynamic users from a back-end database +#2 - Manually edit the static XML user directory files and modify the passwords +#3 - Run the handy randomize-passwords.pl script found in scripts/perl/ subdirectory under the main FreeSWITCH source directory + +*** GETTING HELP *** + +FreeSWITCH has a thriving on-line community - we welcome you to join us! +IRC: #freeswitch on irc.freenode.net +Mailing List: freeswitch-users on lists.freeswitch.org + +You can also get professional FreeSWITCH assistance by visiting http://www.freeswitchsolutions.com or sending an email to consulting@freeswitch.org. + +Happy FreeSWITCHing! diff --git a/conf/autoload_configs/modules.conf.xml b/conf/autoload_configs/modules.conf.xml index 0e485b24e3..52f054994c 100644 --- a/conf/autoload_configs/modules.conf.xml +++ b/conf/autoload_configs/modules.conf.xml @@ -64,6 +64,9 @@ + + + diff --git a/conf/dialplan/default/00_ladspa.xml b/conf/dialplan/default/00_ladspa.xml new file mode 100644 index 0000000000..a26b193ef5 --- /dev/null +++ b/conf/dialplan/default/00_ladspa.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/sip_profiles/internal.xml b/conf/sip_profiles/internal.xml index 426264a1c5..b78ea20070 100644 --- a/conf/sip_profiles/internal.xml +++ b/conf/sip_profiles/internal.xml @@ -342,6 +342,13 @@ + + + + diff --git a/debian/freeswitch-python.install b/debian/freeswitch-python.install index 2773f0c780..247c26d672 100644 --- a/debian/freeswitch-python.install +++ b/debian/freeswitch-python.install @@ -1,3 +1,5 @@ opt/freeswitch/conf/autoload_configs/python.conf.xml opt/freeswitch/mod/mod_python.so* usr/lib/python2.*/*-packages/freeswitch.py* +usr/lib/python2.*/*-packages/ESL.py* +usr/lib/python2.*/*-packages/_ESL.so* diff --git a/debian/rules b/debian/rules index 94aee73a00..0490d546f4 100755 --- a/debian/rules +++ b/debian/rules @@ -208,6 +208,7 @@ install: build dh_installdirs -A VERBOSE=1 $(MAKE) DESTDIR=$(CURDIR)/debian/tmp install + cd libs/esl && VERBOSE=1 $(MAKE) DESTDIR=$(CURDIR)/debian/tmp install && cd - # Build architecture-independent files here. diff --git a/docs/phrase/phrase_en.xml b/docs/phrase/phrase_en.xml index 41bc69cde1..36d986bb61 100644 --- a/docs/phrase/phrase_en.xml +++ b/docs/phrase/phrase_en.xml @@ -428,6 +428,7 @@ + diff --git a/libs/.gitignore b/libs/.gitignore index b31646500e..df284b71b6 100644 --- a/libs/.gitignore +++ b/libs/.gitignore @@ -1104,3 +1104,25 @@ /tiff-3.8.2/a.out.dSYM/ /tiff-3.8.2/tiff-Makefile.tgz /unimrcp/a.out.dSYM/ +/esl/fs_cli.dSYM/ +/esl/ivrd.dSYM/ +/esl/testclient.dSYM/ +/esl/testserver.dSYM/ + +/openssl-1.0.0a/ +/esl/Debug/ +/esl/Release/ +/freetdm/msvc/Release/ +/freetdm/msvc/Debug/ + +/spandsp/src/msvc/All/BuildLog make_at_dictionary.htm +/spandsp/src/msvc/All/BuildLog make_modem_filter.htm + +BuildLog*.htm +/win32/tmp*.bat + +/speex/win32/VS2008/libspeex/*/*/libspeex.log +/speex/win32/VS2008/libspeexdsp/*/*/libspeexdsp.log +/win32/celt/*/*/libcelt.log +/win32/libg722_1/*/*/libg722_1.log +/win32/libshout/*/*/libshout.log diff --git a/libs/esl/Makefile b/libs/esl/Makefile index fbc870ac24..da7926d49b 100644 --- a/libs/esl/Makefile +++ b/libs/esl/Makefile @@ -16,7 +16,9 @@ SOLINK=-shared -Xlinker -x # comment the next line to disable c++ (no swig mods for you then) OBJS += src/esl_oop.o -all: $(MYLIB) fs_cli testclient testserver ivrd +all: $(MYLIB) fs_cli testclient testserver ivrd pymod + +install: pymod-install $(MYLIB): $(OBJS) $(HEADERS) $(SRC) ar rcs $(MYLIB) $(OBJS) @@ -92,6 +94,9 @@ javamod: $(MYLIB) managedmod: $(MYLIB) $(MAKE) MYLIB="../$(MYLIB)" SOLINK="$(SOLINK)" CFLAGS="$(CFLAGS)" CXXFLAGS="$(CXXFLAGS)" CXX_CFLAGS="$(CXX_CFLAGS)" -C managed +perlmod-install: perlmod + $(MAKE) -C perl install + phpmod-install: phpmod $(MAKE) -C php install diff --git a/libs/esl/fs_cli.c b/libs/esl/fs_cli.c index f5975342cc..2985bca2af 100644 --- a/libs/esl/fs_cli.c +++ b/libs/esl/fs_cli.c @@ -577,7 +577,7 @@ static int usage(char *name){ printf(" -P, --port=port Port to connect (1 - 65535)\n"); printf(" -u, --user=user@domain user@domain\n"); printf(" -p, --password=password Password\n"); - printf(" -i, --interrupt Allow Control-c to interrupt"); + printf(" -i, --interrupt Allow Control-c to interrupt\n"); printf(" -x, --execute=command Execute Command and Exit\n"); printf(" -l, --loglevel=command Log Level\n"); printf(" -q, --quiet Disable logging\n"); diff --git a/libs/esl/perl/Makefile b/libs/esl/perl/Makefile index 058e0d3849..a76e9c5e6d 100644 --- a/libs/esl/perl/Makefile +++ b/libs/esl/perl/Makefile @@ -1,4 +1,5 @@ PERL=$(shell which perl) +PERL_SITEDIR=$(shell perl -MConfig -e 'print $$Config{sitelibexp}') PERL_LIBDIR=-L$(shell perl -MConfig -e 'print $$Config{archlib}')/CORE PERL_LIBS=$(shell perl -MConfig -e 'print $$Config{libs}') LOCAL_CFLAGS=-w -DMULTIPLICITY $(shell $(PERL) -MExtUtils::Embed -e ccopts) -DEMBED_PERL @@ -30,3 +31,8 @@ swigclean: reswig: swigclean esl_wrap.cpp perlxsi.c +install: ESL.so + install -m 755 ESL.so $(PERL_SITEDIR) + install -m 755 ESL.pm $(PERL_SITEDIR) + install -d -m 755 ESL $(PERL_SITEDIR)/ESL + install -m 755 ESL/* $(PERL_SITEDIR)/ESL \ No newline at end of file diff --git a/libs/esl/src/esl.c b/libs/esl/src/esl.c index b0bc311ce0..27000f6071 100644 --- a/libs/esl/src/esl.c +++ b/libs/esl/src/esl.c @@ -934,7 +934,6 @@ ESL_DECLARE(esl_status_t) esl_recv_event(esl_handle_t *handle, int check_q, esl_ goto fail; } - esl_event_safe_destroy(&handle->last_event); esl_event_safe_destroy(&handle->last_ievent); if (check_q && handle->race_event) { @@ -1053,6 +1052,7 @@ ESL_DECLARE(esl_status_t) esl_recv_event(esl_handle_t *handle, int check_q, esl_ *save_event = revent; revent = NULL; } else { + esl_event_safe_destroy(&handle->last_event); handle->last_event = revent; } diff --git a/libs/freetdm/.gitignore b/libs/freetdm/.gitignore index 2b06b30218..ff2a46a071 100644 --- a/libs/freetdm/.gitignore +++ b/libs/freetdm/.gitignore @@ -29,3 +29,8 @@ testtones !/sample/boost/Makefile !/sample/dso/Makefile +freetdm.2010.sdf +/mod_freetdm/Win32/ +/msvc/Win32/ +/src/ftmod/*/Win32/ +/src/ftmod/*/x64/ diff --git a/libs/freetdm/conf/freetdm.conf b/libs/freetdm/conf/freetdm.conf index 2f9643dedd..cd269b7736 100644 --- a/libs/freetdm/conf/freetdm.conf +++ b/libs/freetdm/conf/freetdm.conf @@ -20,6 +20,9 @@ cpu_reset_alarm_threshold => 70 ; cpu_alarm_action => warn,reject cpu_alarm_action => warn +; Where to dump DTMF debug files (see per span debugdtmf=yes option) +debugdtmf_directory=/full/path/to/dtmf/directory + ; spans are defined with [span ] ; the span type can either be zt, wanpipe or pika ; the span name can be any unique string diff --git a/libs/freetdm/conf/wanpipe.conf b/libs/freetdm/conf/wanpipe.conf index ba609ac42e..3784eaf17a 100644 --- a/libs/freetdm/conf/wanpipe.conf +++ b/libs/freetdm/conf/wanpipe.conf @@ -1,4 +1,13 @@ [defaults] +; User space interval at which data will be delivered codec_ms => 20 + +; wink and flash interval wink_ms => 150 flash_ms => 750 + +; size of the driver queue of elements of MTU size +; typical case is 10 elements of 80 bytes each (10ms of ulaw/alaw) +; don't mess with this if you don't know what you're doing +; txqueue_size => 10 +; rxqueue_size => 10 diff --git a/libs/freetdm/mod_freetdm/mod_freetdm.c b/libs/freetdm/mod_freetdm/mod_freetdm.c index 0652a8e128..4fd79f8a03 100755 --- a/libs/freetdm/mod_freetdm/mod_freetdm.c +++ b/libs/freetdm/mod_freetdm/mod_freetdm.c @@ -3621,8 +3621,9 @@ void dump_chan_xml(ftdm_span_t *span, uint32_t chan_id, switch_stream_handle_t * "ftdm trace []\n" \ "ftdm notrace []\n" \ "ftdm q931_pcap on|off [pcapfilename without suffix]\n" \ -"ftdm gains []\n" \ +"ftdm gains []\n" \ "ftdm dtmf on|off []\n" \ +"ftdm queuesize []\n" \ "--------------------------------------------------------------------------------\n" SWITCH_STANDARD_API(ft_function) { @@ -4059,7 +4060,7 @@ SWITCH_STANDARD_API(ft_function) ftdm_channel_t *chan; ftdm_span_t *span = NULL; if (argc < 4) { - stream->write_function(stream, "-ERR Usage: ft gains []\n"); + stream->write_function(stream, "-ERR Usage: ftdm gains []\n"); goto end; } ftdm_span_find_by_name(argv[3], &span); @@ -4094,6 +4095,50 @@ SWITCH_STANDARD_API(ft_function) } } stream->write_function(stream, "+OK gains set to Rx %f and Tx %f\n", rxgain, txgain); + } else if (!strcasecmp(argv[0], "queuesize")) { + unsigned int i = 0; + uint32_t rxsize = 10; + uint32_t txsize = 10; + uint32_t chan_id = 0; + uint32_t ccount = 0; + ftdm_channel_t *chan; + ftdm_span_t *span = NULL; + if (argc < 4) { + stream->write_function(stream, "-ERR Usage: ftdm queuesize []\n"); + goto end; + } + ftdm_span_find_by_name(argv[3], &span); + if (!span) { + stream->write_function(stream, "-ERR invalid span\n"); + goto end; + } + if (argc > 4) { + chan_id = atoi(argv[4]); + if (chan_id > ftdm_span_get_chan_count(span)) { + stream->write_function(stream, "-ERR invalid chan\n"); + goto end; + } + } + i = sscanf(argv[1], "%u", &rxsize); + i += sscanf(argv[2], "%u", &txsize); + if (i != 2) { + stream->write_function(stream, "-ERR invalid queue sizes provided\n"); + goto end; + } + + if (chan_id) { + chan = ftdm_span_get_channel(span, chan_id); + ftdm_channel_command(chan, FTDM_COMMAND_SET_RX_QUEUE_SIZE, &rxsize); + ftdm_channel_command(chan, FTDM_COMMAND_SET_TX_QUEUE_SIZE, &txsize); + } else { + ccount = ftdm_span_get_chan_count(span); + for (i = 1; i < ccount; i++) { + chan = ftdm_span_get_channel(span, i); + ftdm_channel_command(chan, FTDM_COMMAND_SET_RX_QUEUE_SIZE, &rxsize); + ftdm_channel_command(chan, FTDM_COMMAND_SET_TX_QUEUE_SIZE, &txsize); + } + } + stream->write_function(stream, "+OK queue sizes set to Rx %d and Tx %d\n", rxsize, txsize); } else if (!strcasecmp(argv[0], "restart")) { uint32_t chan_id = 0; ftdm_channel_t *chan; @@ -4243,6 +4288,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_freetdm_load) switch_console_set_complete("add ftdm notrace"); switch_console_set_complete("add ftdm q931_pcap"); switch_console_set_complete("add ftdm gains"); + switch_console_set_complete("add ftdm queuesize"); switch_console_set_complete("add ftdm dtmf on"); switch_console_set_complete("add ftdm dtmf off"); switch_console_set_complete("add ftdm core state"); diff --git a/libs/freetdm/src/ftdm_io.c b/libs/freetdm/src/ftdm_io.c index ff62dc38bb..96fd235ad2 100644 --- a/libs/freetdm/src/ftdm_io.c +++ b/libs/freetdm/src/ftdm_io.c @@ -244,6 +244,7 @@ static struct { ftdm_caller_data_t *call_ids[MAX_CALLIDS+1]; ftdm_mutex_t *call_id_mutex; uint32_t last_call_id; + char dtmfdebug_directory[1024]; } globals; enum ftdm_enum_cpu_alarm_action_flags @@ -3458,7 +3459,7 @@ FT_DECLARE(ftdm_status_t) ftdm_channel_queue_dtmf(ftdm_channel_t *ftdmchan, cons if (!ftdmchan->dtmfdbg.file) { struct tm currtime; time_t currsec; - char dfile[512]; + char dfile[1024]; currsec = time(NULL); @@ -3469,10 +3470,18 @@ FT_DECLARE(ftdm_status_t) ftdm_channel_queue_dtmf(ftdm_channel_t *ftdmchan, cons localtime_r(&currsec, &currtime); #endif - snprintf(dfile, sizeof(dfile), "dtmf-s%dc%d-20%d-%d-%d-%d:%d:%d.%s", - ftdmchan->span_id, ftdmchan->chan_id, - currtime.tm_year-100, currtime.tm_mon+1, currtime.tm_mday, - currtime.tm_hour, currtime.tm_min, currtime.tm_sec, ftdmchan->native_codec == FTDM_CODEC_ULAW ? "ulaw" : ftdmchan->native_codec == FTDM_CODEC_ALAW ? "alaw" : "sln"); + if (ftdm_strlen_zero(globals.dtmfdebug_directory)) { + snprintf(dfile, sizeof(dfile), "dtmf-s%dc%d-20%d-%d-%d-%d:%d:%d.%s", + ftdmchan->span_id, ftdmchan->chan_id, + currtime.tm_year-100, currtime.tm_mon+1, currtime.tm_mday, + currtime.tm_hour, currtime.tm_min, currtime.tm_sec, ftdmchan->native_codec == FTDM_CODEC_ULAW ? "ulaw" : ftdmchan->native_codec == FTDM_CODEC_ALAW ? "alaw" : "sln"); + } else { + snprintf(dfile, sizeof(dfile), "%s/dtmf-s%dc%d-20%d-%d-%d-%d:%d:%d.%s", + globals.dtmfdebug_directory, + ftdmchan->span_id, ftdmchan->chan_id, + currtime.tm_year-100, currtime.tm_mon+1, currtime.tm_mday, + currtime.tm_hour, currtime.tm_min, currtime.tm_sec, ftdmchan->native_codec == FTDM_CODEC_ULAW ? "ulaw" : ftdmchan->native_codec == FTDM_CODEC_ALAW ? "alaw" : "sln"); + } ftdmchan->dtmfdbg.file = fopen(dfile, "wb"); if (!ftdmchan->dtmfdbg.file) { ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "failed to open debug dtmf file %s\n", dfile); @@ -4686,6 +4695,9 @@ static ftdm_status_t load_config(void) globals.cpu_monitor.alarm_action_flags |= FTDM_CPU_ALARM_ACTION_WARN; } } + } else if (!strncasecmp(var, "debugdtmf_directory", sizeof("debugdtmf_directory")-1)) { + ftdm_set_string(globals.dtmfdebug_directory, val); + ftdm_log(FTDM_LOG_DEBUG, "Debug DTMF directory set to '%s'\n", globals.dtmfdebug_directory); } else if (!strncasecmp(var, "cpu_monitoring_interval", sizeof("cpu_monitoring_interval")-1)) { if (atoi(val) > 0) { globals.cpu_monitor.interval = atoi(val); diff --git a/libs/freetdm/src/ftmod/ftmod_libpri/ftmod_libpri.c b/libs/freetdm/src/ftmod/ftmod_libpri/ftmod_libpri.c index e051477346..6653a923aa 100644 --- a/libs/freetdm/src/ftmod/ftmod_libpri/ftmod_libpri.c +++ b/libs/freetdm/src/ftmod/ftmod_libpri/ftmod_libpri.c @@ -943,12 +943,12 @@ static int on_proceeding(lpwrap_pri_t *spri, lpwrap_pri_event_t event_type, pri_ if (chan) { /* Open channel if inband information is available */ - if ((pevent->proceeding.progressmask & PRI_PROG_INBAND_AVAILABLE) && !ftdm_test_flag(chan, FTDM_CHANNEL_OPEN)) { - ftdm_log(FTDM_LOG_DEBUG, "-- In-band information available, opening B-Channel %d:%d\n", + if (pevent->proceeding.progressmask & PRI_PROG_INBAND_AVAILABLE) { + ftdm_log(FTDM_LOG_DEBUG, "-- In-band information available, B-Channel %d:%d\n", ftdm_channel_get_span_id(chan), ftdm_channel_get_id(chan)); - if (ftdm_channel_open_chan(chan) != FTDM_SUCCESS) { + if (!ftdm_test_flag(chan, FTDM_CHANNEL_OPEN) && (ftdm_channel_open_chan(chan) != FTDM_SUCCESS)) { ftdm_caller_data_t *caller_data = ftdm_channel_get_caller_data(chan); ftdm_log(FTDM_LOG_ERROR, "-- Error opening channel %d:%d\n", @@ -985,12 +985,12 @@ static int on_progress(lpwrap_pri_t *spri, lpwrap_pri_event_t event_type, pri_ev if (chan) { /* Open channel if inband information is available */ - if ((pevent->proceeding.progressmask & PRI_PROG_INBAND_AVAILABLE) && !ftdm_test_flag(chan, FTDM_CHANNEL_OPEN)) { - ftdm_log(FTDM_LOG_DEBUG, "-- In-band information available, opening B-Channel %d:%d\n", + if (pevent->proceeding.progressmask & PRI_PROG_INBAND_AVAILABLE) { + ftdm_log(FTDM_LOG_DEBUG, "-- In-band information available, B-Channel %d:%d\n", ftdm_channel_get_span_id(chan), ftdm_channel_get_id(chan)); - if (ftdm_channel_open_chan(chan) != FTDM_SUCCESS) { + if (!ftdm_test_flag(chan, FTDM_CHANNEL_OPEN) && (ftdm_channel_open_chan(chan) != FTDM_SUCCESS)) { ftdm_caller_data_t *caller_data = ftdm_channel_get_caller_data(chan); ftdm_log(FTDM_LOG_ERROR, "-- Error opening channel %d:%d\n", @@ -1028,8 +1028,6 @@ static int on_ringing(lpwrap_pri_t *spri, lpwrap_pri_event_t event_type, pri_eve ftdm_channel_t *chan = ftdm_span_get_channel(span, pevent->ringing.channel); if (chan) { - ftdm_log(FTDM_LOG_DEBUG, "-- Ringing on channel %d:%d\n", ftdm_span_get_id(span), pevent->ringing.channel); - /* we may get on_ringing even when we're already in FTDM_CHANNEL_STATE_PROGRESS_MEDIA */ // if (ftdm_channel_get_state(chan) == FTDM_CHANNEL_STATE_PROGRESS_MEDIA) { // /* dont try to move to STATE_PROGRESS to avoid annoying veto warning */ @@ -1037,12 +1035,12 @@ static int on_ringing(lpwrap_pri_t *spri, lpwrap_pri_event_t event_type, pri_eve // } /* Open channel if inband information is available */ - if ((pevent->ringing.progressmask & PRI_PROG_INBAND_AVAILABLE) && !ftdm_test_flag(chan, FTDM_CHANNEL_OPEN)) { - ftdm_log(FTDM_LOG_DEBUG, "-- In-band information available, opening B-Channel %d:%d\n", + if ((pevent->ringing.progressmask & PRI_PROG_INBAND_AVAILABLE)) { + ftdm_log(FTDM_LOG_DEBUG, "-- In-band information available, B-Channel %d:%d\n", ftdm_channel_get_span_id(chan), ftdm_channel_get_id(chan)); - if (ftdm_channel_open_chan(chan) != FTDM_SUCCESS) { + if (!ftdm_test_flag(chan, FTDM_CHANNEL_OPEN) && (ftdm_channel_open_chan(chan) != FTDM_SUCCESS)) { ftdm_caller_data_t *caller_data = ftdm_channel_get_caller_data(chan); ftdm_log(FTDM_LOG_ERROR, "-- Error opening channel %d:%d\n", @@ -1053,9 +1051,13 @@ static int on_ringing(lpwrap_pri_t *spri, lpwrap_pri_event_t event_type, pri_eve ftdm_set_state_locked(chan, FTDM_CHANNEL_STATE_TERMINATING); goto out; } + ftdm_log(FTDM_LOG_DEBUG, "-- Ringing on channel %d:%d with media\n", ftdm_span_get_id(span), pevent->proceeding.channel); + ftdm_set_state_locked(chan, FTDM_CHANNEL_STATE_PROGRESS_MEDIA); + } else { +// ftdm_set_state_locked(chan, FTDM_CHANNEL_STATE_PROGRESS); + ftdm_log(FTDM_LOG_DEBUG, "-- Ringing on channel %d:%d\n", ftdm_span_get_id(span), pevent->proceeding.channel); + ftdm_set_state_locked(chan, FTDM_CHANNEL_STATE_RINGING); } -// ftdm_set_state_locked(chan, FTDM_CHANNEL_STATE_PROGRESS); - ftdm_set_state_locked(chan, FTDM_CHANNEL_STATE_RINGING); } else { ftdm_log(FTDM_LOG_DEBUG, "-- Ringing on channel %d:%d but it's not in the span?\n", ftdm_span_get_id(span), pevent->ringing.channel); diff --git a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.2008.vcproj b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.2008.vcproj index 2e7fb82041..01395cb1db 100644 --- a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.2008.vcproj +++ b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.2008.vcproj @@ -118,7 +118,7 @@ - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDLL @@ -157,7 +157,7 @@ - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDLL diff --git a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn_cfg.c b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn_cfg.c index 9fe28190e1..ee0cdbe1ef 100644 --- a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn_cfg.c +++ b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn_cfg.c @@ -333,7 +333,8 @@ ftdm_status_t ftmod_isdn_parse_cfg(ftdm_conf_parameter_t *ftdm_parameters, ftdm_ parse_yesno(var, val, &signal_data->setup_arb); } else if (!strcasecmp(var, "facility")) { parse_yesno(var, val, &signal_data->facility); - } else if (!strcasecmp(var, "min_digits")) { + } else if (!strcasecmp(var, "min-digits") || + !strcasecmp(var, "min_digits")) { signal_data->min_digits = atoi(val); } else if (!strcasecmp(var, "outbound-called-ton")) { ftdm_set_ton(val, &span->default_caller_data.dnis.type); @@ -347,11 +348,11 @@ ftdm_status_t ftmod_isdn_parse_cfg(ftdm_conf_parameter_t *ftdm_parameters, ftdm_ ftdm_set_ton(val, &span->default_caller_data.rdnis.type); } else if (!strcasecmp(var, "outbound-rdnis-npi")) { ftdm_set_npi(val, &span->default_caller_data.rdnis.plan); - } else if (!strcasecmp(var, "outbound-bearer_cap") || - !strcasecmp(var, "outbound-bc-transfer-cap")) { + } else if (!strcasecmp(var, "outbound-bc-transfer-cap") || + !strcasecmp(var, "outbound-bearer_cap")) { ftdm_set_bearer_capability(val, (uint8_t*)&span->default_caller_data.bearer_capability); - } else if (!strcasecmp(var, "outbound-bearer_layer1") || - !strcasecmp(var, "outbound-bc-user-layer1")) { + } else if (!strcasecmp(var, "outbound-bc-user-layer1") || + !strcasecmp(var, "outbound-bearer_layer1")) { ftdm_set_bearer_layer1(val, (uint8_t*)&span->default_caller_data.bearer_layer1); } else if (!strcasecmp(var, "channel-restart-on-link-up")) { parse_yesno(var, val, &signal_data->restart_opt); diff --git a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn_stack_rcv.c b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn_stack_rcv.c index 791b65f0f6..7b9989dd4c 100644 --- a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn_stack_rcv.c +++ b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn_stack_rcv.c @@ -677,20 +677,20 @@ void sngisdn_rcv_q921_ind(BdMngmt *status) } switch (status->t.usta.alarm.category) { - case (LCM_CATEGORY_INTERFACE): - ftdm_log(FTDM_LOG_INFO, "[SNGISDN Q921] %s: %s: %s(%d): %s(%d)\n", - ftdmspan->name, - DECODE_LCM_CATEGORY(status->t.usta.alarm.category), - DECODE_LCM_EVENT(status->t.usta.alarm.event), status->t.usta.alarm.event, - DECODE_LCM_CAUSE(status->t.usta.alarm.cause), status->t.usta.alarm.cause); + case (LCM_CATEGORY_PROTOCOL): + ftdm_log(FTDM_LOG_DEBUG, "[SNGISDN Q921] %s: %s: %s(%d): %s(%d)\n", + ftdmspan->name, + DECODE_LCM_CATEGORY(status->t.usta.alarm.category), + DECODE_LLD_EVENT(status->t.usta.alarm.event), status->t.usta.alarm.event, + DECODE_LLD_CAUSE(status->t.usta.alarm.cause), status->t.usta.alarm.cause); break; default: ftdm_log(FTDM_LOG_INFO, "[SNGISDN Q921] %s: %s: %s(%d): %s(%d)\n", - ftdmspan->name, - DECODE_LCM_CATEGORY(status->t.usta.alarm.category), - DECODE_LLD_EVENT(status->t.usta.alarm.event), status->t.usta.alarm.event, - DECODE_LLD_CAUSE(status->t.usta.alarm.cause), status->t.usta.alarm.cause); - + ftdmspan->name, + DECODE_LCM_CATEGORY(status->t.usta.alarm.category), + DECODE_LLD_EVENT(status->t.usta.alarm.event), status->t.usta.alarm.event, + DECODE_LLD_CAUSE(status->t.usta.alarm.cause), status->t.usta.alarm.cause); + switch (status->t.usta.alarm.event) { case ENTR_CONG: /* Entering Congestion */ ftdm_log(FTDM_LOG_WARNING, "s%d: Entering Congestion\n", ftdmspan->span_id); diff --git a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn_trace.c b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn_trace.c index c2e9b5c3a1..8c8c8d5159 100644 --- a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn_trace.c +++ b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn_trace.c @@ -114,7 +114,7 @@ void sngisdn_trace_interpreted_q921(sngisdn_span_data_t *signal_data, ftdm_trace { char *data_str = ftdm_calloc(1,200); /* TODO Find a proper size */ sngisdn_decode_q921(data_str, data, data_len); - ftdm_log(FTDM_LOG_INFO, "[SNGISDN Q921] s%d FRAME %s:%s\n", signal_data->ftdm_span->name, ftdm_trace_dir2str(dir), data_str); + ftdm_log(FTDM_LOG_INFO, "[SNGISDN Q921] %s FRAME %s:%s\n", signal_data->ftdm_span->name, ftdm_trace_dir2str(dir), data_str); ftdm_safe_free(data_str); } diff --git a/libs/freetdm/src/ftmod/ftmod_wanpipe/ftmod_wanpipe.c b/libs/freetdm/src/ftmod/ftmod_wanpipe/ftmod_wanpipe.c index b648daadaf..20a173abb2 100644 --- a/libs/freetdm/src/ftmod/ftmod_wanpipe/ftmod_wanpipe.c +++ b/libs/freetdm/src/ftmod/ftmod_wanpipe/ftmod_wanpipe.c @@ -91,6 +91,8 @@ typedef enum { */ static struct { uint32_t codec_ms; + uint32_t rxqueue_size; + uint32_t txqueue_size; uint32_t wink_ms; uint32_t flash_ms; uint32_t ring_on_ms; @@ -406,6 +408,20 @@ static FIO_CONFIGURE_FUNCTION(wanpipe_configure) } else { wp_globals.codec_ms = num; } + } else if (!strcasecmp(var, "rxqueue_size")) { + num = atoi(val); + if (num < 1 || num > 1000) { + ftdm_log(FTDM_LOG_WARNING, "invalid rx queue size at line %d\n", lineno); + } else { + wp_globals.rxqueue_size = num; + } + } else if (!strcasecmp(var, "txqueue_size")) { + num = atoi(val); + if (num < 1 || num > 1000) { + ftdm_log(FTDM_LOG_WARNING, "invalid tx queue size at line %d\n", lineno); + } else { + wp_globals.txqueue_size = num; + } } else if (!strcasecmp(var, "wink_ms")) { num = atoi(val); if (num < 50 || num > 3000) { @@ -544,6 +560,13 @@ static FIO_OPEN_FUNCTION(wanpipe_open) ftdm_channel_set_feature(ftdmchan, FTDM_CHANNEL_FEATURE_INTERVAL); ftdmchan->effective_interval = ftdmchan->native_interval = wp_globals.codec_ms; ftdmchan->packet_len = ftdmchan->native_interval * 8; + + if (wp_globals.txqueue_size > 0) { + ftdm_channel_command(ftdmchan, FTDM_COMMAND_SET_TX_QUEUE_SIZE, &wp_globals.txqueue_size); + } + if (wp_globals.rxqueue_size > 0) { + ftdm_channel_command(ftdmchan, FTDM_COMMAND_SET_RX_QUEUE_SIZE, &wp_globals.rxqueue_size); + } } return FTDM_SUCCESS; @@ -1315,14 +1338,12 @@ static __inline__ ftdm_status_t wanpipe_channel_process_event(ftdm_channel_t *fc status = FTDM_BREAK; } else { ftdm_status_t status; - wanpipe_tdm_api_t onhook_tdm_api; - memset(&onhook_tdm_api, 0, sizeof(onhook_tdm_api)); - status = sangoma_tdm_txsig_onhook(fchan->sockfd, &onhook_tdm_api); + status = sangoma_tdm_txsig_onhook(fchan->sockfd, tdm_api); if (status) { snprintf(fchan->last_error, sizeof(fchan->last_error), "ONHOOK Failed"); return FTDM_FAIL; } - *event_id = onhook_tdm_api.wp_tdm_cmd.event.wp_tdm_api_event_hook_state & WP_TDMAPI_EVENT_RXHOOK_OFF ? FTDM_OOB_ONHOOK : FTDM_OOB_NOOP; + *event_id = tdm_api->wp_tdm_cmd.event.wp_tdm_api_event_hook_state & WP_TDMAPI_EVENT_RXHOOK_OFF ? FTDM_OOB_ONHOOK : FTDM_OOB_NOOP; } } break; @@ -1575,6 +1596,9 @@ static FIO_IO_LOAD_FUNCTION(wanpipe_init) wp_globals.flash_ms = 750; wp_globals.ring_on_ms = 2000; wp_globals.ring_off_ms = 4000; + /* 0 for queue size will leave driver defaults */ + wp_globals.txqueue_size = 0; + wp_globals.rxqueue_size = 0; wanpipe_interface.name = "wanpipe"; wanpipe_interface.configure_span = wanpipe_configure_span; wanpipe_interface.configure = wanpipe_configure; diff --git a/libs/freetdm/src/ftmod/ftmod_zt/ftmod_zt.c b/libs/freetdm/src/ftmod/ftmod_zt/ftmod_zt.c index 1b55b739fc..9c01b41299 100644 --- a/libs/freetdm/src/ftmod/ftmod_zt/ftmod_zt.c +++ b/libs/freetdm/src/ftmod/ftmod_zt/ftmod_zt.c @@ -1064,6 +1064,20 @@ static __inline__ ftdm_status_t zt_channel_process_event(ftdm_channel_t *fchan, fchan->rx_cas_bits = bits; } break; + case ZT_EVENT_BADFCS: + { + ftdm_log_chan_msg(fchan, FTDM_LOG_ERROR, "Bad frame checksum (ZT_EVENT_BADFCS)!\n"); + /* What else could we do? */ + *event_id = FTDM_OOB_NOOP; + } + break; + case ZT_EVENT_OVERRUN: + { + ftdm_log_chan_msg(fchan, FTDM_LOG_ERROR, "Driver overrun! (ZT_EVENT_OVERRUN)\n"); + /* What else could we do? */ + *event_id = FTDM_OOB_NOOP; + } + break; case ZT_EVENT_NONE: { ftdm_log_chan_msg(fchan, FTDM_LOG_DEBUG, "No event\n"); diff --git a/libs/libdingaling/src/libdingaling.c b/libs/libdingaling/src/libdingaling.c index 44c4589429..6793c3df61 100644 --- a/libs/libdingaling/src/libdingaling.c +++ b/libs/libdingaling/src/libdingaling.c @@ -384,9 +384,12 @@ static ldl_status parse_session_code(ldl_handle_t *handle, char *id, char *from, } while(xml) { - char *type = xtype ? xtype : iks_find_attrib(xml, "type"); + char *type = NULL; iks *tag; - + + if (iks_type(xml)!=IKS_CDATA) + type = xtype ? xtype : iks_find_attrib(xml, "type"); + if (type) { if (!strcasecmp(type, "redirect")) { @@ -994,9 +997,9 @@ static int on_commands(void *user_data, ikspak *pak) uint8_t is_result = strcasecmp(type, "result") ? 0 : 1; uint8_t is_error = strcasecmp(type, "error") ? 0 : 1; iks *xml, *xsession, *xerror = NULL, *xredir = NULL; - + struct iks_tag* tmp; xml = iks_child (pak->x); - + tmp = (struct iks_tag*) xml; if (is_error) { if ((xerror = working_find(xml, "error"))) { char *code = iks_find_attrib(xerror, "code"); diff --git a/libs/sofia-sip/.update b/libs/sofia-sip/.update index aacfc996d3..a21313983b 100644 --- a/libs/sofia-sip/.update +++ b/libs/sofia-sip/.update @@ -1 +1 @@ -Wed Nov 3 13:53:34 EDT 2010 +Tue Mar 8 12:40:45 CST 2011 diff --git a/libs/sofia-sip/libsofia-sip-ua/nta/nta.c b/libs/sofia-sip/libsofia-sip-ua/nta/nta.c index 13454c1a8e..543f0e135a 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nta/nta.c +++ b/libs/sofia-sip/libsofia-sip-ua/nta/nta.c @@ -7781,7 +7781,7 @@ nta_outgoing_t *outgoing_create(nta_agent_t *agent, if (tpn) { /* CANCEL or ACK to [3456]XX */ invalid = tport_name_dup(home, orq->orq_tpn, tpn); -#if HAVE_SOFIA_SRESOLV +#if 0 //HAVE_SOFIA_SRESOLV /* We send ACK or CANCEL only if original request was really sent */ assert(tport_name_is_resolved(orq->orq_tpn)); #endif diff --git a/libs/spandsp/src/t4_rx.c b/libs/spandsp/src/t4_rx.c index dfdf914804..131bfc0179 100644 --- a/libs/spandsp/src/t4_rx.c +++ b/libs/spandsp/src/t4_rx.c @@ -652,6 +652,7 @@ static __inline__ void force_drop_rx_bits(t4_state_t *s, int bits) static int rx_put_bits(t4_state_t *s, uint32_t bit_string, int quantity) { int bits; + int old_a0; /* We decompress bit by bit, as the data stream is received. We need to scan continuously for EOLs, so we might as well work this way. */ @@ -809,8 +810,23 @@ static int rx_put_bits(t4_state_t *s, uint32_t bit_string, int quantity) s->t4_t6_rx.a0, s->t4_t6_rx.b1, s->t4_t6_rx.run_length); - s->t4_t6_rx.run_length += (s->t4_t6_rx.b1 - s->t4_t6_rx.a0 + t4_2d_table[bits].param); + old_a0 = s->t4_t6_rx.a0; s->t4_t6_rx.a0 = s->t4_t6_rx.b1 + t4_2d_table[bits].param; + /* We need to check if a bad or malicious image is failing to move forward along the row. + Going back is obviously bad. We also need to avoid a stall on the spot, except for the + special case of the start of the row. Zero movement as the very first element in the + row is perfectly normal. */ + if (s->t4_t6_rx.a0 <= old_a0) + { + if (s->t4_t6_rx.a0 < old_a0 || s->t4_t6_rx.b_cursor > 1) + { + /* Undo the update we just started, and carry on as if this code does not exist */ + /* TODO: we really should record that something wasn't right at this point. */ + s->t4_t6_rx.a0 = old_a0; + break; + } + } + s->t4_t6_rx.run_length += (s->t4_t6_rx.a0 - old_a0); add_run_to_row(s); /* We need to move one step in one direction or the other, to change to the opposite colour */ @@ -832,8 +848,9 @@ static int rx_put_bits(t4_state_t *s, uint32_t bit_string, int quantity) s->ref_runs[s->t4_t6_rx.b_cursor], s->ref_runs[s->t4_t6_rx.b_cursor + 1]); s->t4_t6_rx.b1 += s->ref_runs[s->t4_t6_rx.b_cursor++]; - s->t4_t6_rx.run_length += (s->t4_t6_rx.b1 - s->t4_t6_rx.a0); + old_a0 = s->t4_t6_rx.a0; s->t4_t6_rx.a0 = s->t4_t6_rx.b1; + s->t4_t6_rx.run_length += (s->t4_t6_rx.a0 - old_a0); s->t4_t6_rx.b1 += s->ref_runs[s->t4_t6_rx.b_cursor++]; break; case S_Ext: diff --git a/libs/stfu/stfu.c b/libs/stfu/stfu.c index f00dfafa39..d8797f7cd0 100644 --- a/libs/stfu/stfu.c +++ b/libs/stfu/stfu.c @@ -428,6 +428,12 @@ stfu_status_t stfu_n_add_data(stfu_instance_t *i, uint32_t ts, uint32_t pt, void i->ts_drift = ts + (i->ts_offset - timer_ts); + if (i->ts_offset && i->ts_drift > 0) { + i->ts_offset = timer_ts - ts; + i->ts_drift = ts + (i->ts_offset - timer_ts); + } + + if (i->max_drift) { if (i->ts_drift < i->max_drift) { if (++i->drift_dropped_packets < i->drift_max_dropped) { @@ -518,8 +524,8 @@ stfu_status_t stfu_n_add_data(stfu_instance_t *i, uint32_t ts, uint32_t pt, void if (stfu_log != null_logger && i->debug) { - stfu_log(STFU_LOG_EMERG, "I: %s %u i=%u/%u - g:%u/%u c:%u/%u b:%u - %u:%u - %u %d %u %u %d %d %d/%d\n", i->name, - i->qlen, i->period_packet_in_count, i->period_time, i->consecutive_good_count, + stfu_log(STFU_LOG_EMERG, "I: %s %u/%u i=%u/%u - g:%u/%u c:%u/%u b:%u - %u:%u - %u %d %u %u %d %d %d/%d\n", i->name, + i->qlen, i->max_qlen, i->period_packet_in_count, i->period_time, i->consecutive_good_count, i->decrement_time, i->period_clean_count, i->decrement_time, i->consecutive_bad_count, ts, ts / i->samples_per_packet, i->period_missing_count, i->period_need_range_avg, diff --git a/scripts/perl/FreeSWITCH/Client.pm b/scripts/perl/FreeSWITCH/Client.pm index 9e97218580..01748a8180 100644 --- a/scripts/perl/FreeSWITCH/Client.pm +++ b/scripts/perl/FreeSWITCH/Client.pm @@ -143,7 +143,13 @@ sub sendmsg($$$) { } $self->output("\n"); - return $self->readhash($to); + for(;;) { + $e = $self->readhash(undef); + last if $e->{socketerror} or $e->{'content-type'} eq 'command/reply'; + push @{$self->{events}}, $e; + } + + return $e; } sub command($$) { diff --git a/src/include/switch_channel.h b/src/include/switch_channel.h index 25273bafc4..fdbbc9ab91 100644 --- a/src/include/switch_channel.h +++ b/src/include/switch_channel.h @@ -315,6 +315,7 @@ SWITCH_DECLARE(switch_status_t) switch_channel_caller_extension_masquerade(switc */ SWITCH_DECLARE(void) switch_channel_set_caller_extension(switch_channel_t *channel, switch_caller_extension_t *caller_extension); +SWITCH_DECLARE(void) switch_channel_flip_cid(switch_channel_t *channel); SWITCH_DECLARE(void) switch_channel_sort_cid(switch_channel_t *channel, switch_bool_t in); /*! diff --git a/src/include/switch_types.h b/src/include/switch_types.h index 4253d0e542..3383a9d88c 100644 --- a/src/include/switch_types.h +++ b/src/include/switch_types.h @@ -542,7 +542,7 @@ typedef enum { SWITCH_RTP_FLAG_BREAK = (1 << 10), SWITCH_RTP_FLAG_UDPTL = (1 << 11), SWITCH_RTP_FLAG_DATAWAIT = (1 << 12), - SWITCH_RTP_FLAG_BUGGY_2833 = (1 << 13), + SWITCH_RTP_FLAG_BYTESWAP = (1 << 13), SWITCH_RTP_FLAG_PASS_RFC2833 = (1 << 14), SWITCH_RTP_FLAG_AUTO_CNG = (1 << 15), SWITCH_RTP_FLAG_SECURE_SEND_RESET = (1 << 16), @@ -1107,6 +1107,8 @@ typedef enum { CF_DIALPLAN, CF_BLOCK_BROADCAST_UNTIL_MEDIA, CF_CNG_PLC, + CF_ATTENDED_TRANSFER, + CF_LAZY_ATTENDED_TRANSFER, /* WARNING: DO NOT ADD ANY FLAGS BELOW THIS LINE */ CF_FLAG_MAX } switch_channel_flag_t; diff --git a/src/mod/.gitignore b/src/mod/.gitignore index b6e7842a50..b75dbcb9cf 100644 --- a/src/mod/.gitignore +++ b/src/mod/.gitignore @@ -31,6 +31,7 @@ /endpoints/mod_portaudio/Makefile.in /endpoints/mod_skinny/Makefile /endpoints/mod_skinny/Makefile.in +/endpoints/mod_skinny/mod_skinny.log /endpoints/mod_skypopen/Makefile /endpoints/mod_skypopen/Makefile.in /endpoints/mod_sofia/Makefile diff --git a/src/mod/applications/mod_callcenter/mod_callcenter.c b/src/mod/applications/mod_callcenter/mod_callcenter.c index 12e1960841..b8c5eede74 100644 --- a/src/mod/applications/mod_callcenter/mod_callcenter.c +++ b/src/mod/applications/mod_callcenter/mod_callcenter.c @@ -160,13 +160,15 @@ struct cc_member_cancel_reason_table { typedef enum { CC_MEMBER_CANCEL_REASON_NONE, CC_MEMBER_CANCEL_REASON_TIMEOUT, - CC_MEMBER_CANCEL_REASON_NO_AGENT_TIMEOUT + CC_MEMBER_CANCEL_REASON_NO_AGENT_TIMEOUT, + CC_MEMBER_CANCEL_REASON_BREAK_OUT } cc_member_cancel_reason_t; static struct cc_member_cancel_reason_table MEMBER_CANCEL_REASON_CHART[] = { {"NONE", CC_MEMBER_CANCEL_REASON_NONE}, {"TIMEOUT", CC_MEMBER_CANCEL_REASON_TIMEOUT}, {"NO_AGENT_TIMEOUT", CC_MEMBER_CANCEL_REASON_NO_AGENT_TIMEOUT}, + {"BREAK_OUT", CC_MEMBER_CANCEL_REASON_BREAK_OUT}, {NULL, 0} }; @@ -216,6 +218,7 @@ static char agents_sql[] = " wrap_up_time INTEGER NOT NULL DEFAULT 0,\n" " reject_delay_time INTEGER NOT NULL DEFAULT 0,\n" " busy_delay_time INTEGER NOT NULL DEFAULT 0,\n" +" no_answer_delay_time INTEGER NOT NULL DEFAULT 0,\n" " last_bridge_start INTEGER NOT NULL DEFAULT 0,\n" " last_bridge_end INTEGER NOT NULL DEFAULT 0,\n" " last_offered_call INTEGER NOT NULL DEFAULT 0,\n" @@ -705,6 +708,7 @@ static cc_queue_t *load_queue(const char *queue_name) switch_cache_db_test_reactive(dbh, "select count(ready_time) from agents", NULL, "alter table agents add ready_time integer not null default 0;" "alter table agents add reject_delay_time integer not null default 0;" "alter table agents add busy_delay_time integer not null default 0;"); + switch_cache_db_test_reactive(dbh, "select count(no_answer_delay_time) from agents", NULL, "alter table agents add no_answer_delay_time integer not null default 0;"); switch_cache_db_test_reactive(dbh, "select count(ready_time) from agents", "drop table agents", agents_sql); switch_cache_db_test_reactive(dbh, "select count(queue) from tiers", "drop table tiers" , tiers_sql); switch_mutex_init(&queue->mutex, SWITCH_MUTEX_NESTED, queue->pool); @@ -768,6 +772,7 @@ struct call_helper { int max_no_answer; int reject_delay_time; int busy_delay_time; + int no_answer_delay_time; switch_memory_pool_t *pool; }; @@ -1007,6 +1012,12 @@ cc_status_t cc_agent_update(const char *key, const char *value, const char *agen cc_execute_sql(NULL, sql, NULL); switch_safe_free(sql); + result = CC_STATUS_SUCCESS; + } else if (!strcasecmp(key, "no_answer_delay_time")) { + sql = switch_mprintf("UPDATE agents SET no_answer_delay_time = '%ld', system = 'single_box' WHERE name = '%q'", atol(value), agent); + cc_execute_sql(NULL, sql, NULL); + switch_safe_free(sql); + result = CC_STATUS_SUCCESS; } else if (!strcasecmp(key, "type")) { if (strcasecmp(value, CC_AGENT_TYPE_CALLBACK) && strcasecmp(value, CC_AGENT_TYPE_UUID_STANDBY)) { @@ -1203,6 +1214,7 @@ static switch_status_t load_agent(const char *agent_name) const char *wrap_up_time = switch_xml_attr(x_agent, "wrap-up-time"); const char *reject_delay_time = switch_xml_attr(x_agent, "reject-delay-time"); const char *busy_delay_time = switch_xml_attr(x_agent, "busy-delay-time"); + const char *no_answer_delay_time = switch_xml_attr(x_agent, "no-answer-delay-time"); if (type) { cc_status_t res = cc_agent_add(agent_name, type); @@ -1225,6 +1237,9 @@ static switch_status_t load_agent(const char *agent_name) if (busy_delay_time) { cc_agent_update("busy_delay_time", busy_delay_time, agent_name); } + if (no_answer_delay_time) { + cc_agent_update("no_answer_delay_time", no_answer_delay_time, agent_name); + } if (type && res == CC_STATUS_AGENT_ALREADY_EXIST) { cc_agent_update("type", type, agent_name); @@ -1384,8 +1399,6 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa switch_event_add_header(ovars, SWITCH_STACK_BOTTOM, "cc_agent", "%s", h->agent_name); switch_event_add_header(ovars, SWITCH_STACK_BOTTOM, "cc_agent_type", "%s", h->agent_type); switch_event_add_header(ovars, SWITCH_STACK_BOTTOM, "ignore_early_media", "true"); - /* Force loopback to remain live, if not, the loop will detect the actual channel to gone */ - switch_event_add_header(ovars, SWITCH_STACK_BOTTOM, "loopback_bowout", "false"); t_agent_called = switch_epoch_time_now(NULL); dialstr = switch_mprintf("%s", h->originate_string); @@ -1430,9 +1443,36 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa const char *agent_uuid = switch_core_session_get_uuid(agent_session); switch_channel_t *member_channel = switch_core_session_get_channel(member_session); switch_channel_t *agent_channel = switch_core_session_get_channel(agent_session); + const char *other_loopback_leg_uuid = switch_channel_get_variable(agent_channel, "other_loopback_leg_uuid"); switch_channel_set_variable(agent_channel, "cc_member_pre_answer_uuid", NULL); + /* Loopback special case */ + if (other_loopback_leg_uuid) { + switch_core_session_t *other_loopback_session = switch_core_session_locate(other_loopback_leg_uuid); + if (other_loopback_session) { + switch_channel_t *other_loopback_channel = switch_core_session_get_channel(other_loopback_session); + const char *real_uuid = switch_channel_get_variable(other_loopback_channel, SWITCH_SIGNAL_BOND_VARIABLE); + + switch_channel_set_variable(other_loopback_channel, "cc_member_pre_answer_uuid", NULL); + + /* Switch the agent session */ + if (real_uuid) { + switch_core_session_rwunlock(agent_session); + agent_uuid = real_uuid; + agent_session = switch_core_session_locate(agent_uuid); + agent_channel = switch_core_session_get_channel(agent_session); + + switch_channel_set_variable(agent_channel, "cc_queue", h->queue_name); + switch_channel_set_variable(agent_channel, "cc_agent", h->agent_name); + switch_channel_set_variable(agent_channel, "cc_agent_type", h->agent_type); + switch_channel_set_variable(agent_channel, "cc_member_uuid", h->member_uuid); + } + switch_core_session_rwunlock(other_loopback_session); + } + } + + if (!strcasecmp(h->queue_strategy,"ring-all")) { char res[256]; /* Map the Agent to the member */ @@ -1466,6 +1506,9 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent", h->agent_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent-System", h->agent_system); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent-UUID", agent_uuid); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Agent-Called-Time", "%ld", (long) t_agent_called); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Agent-Answered-Time", "%ld", (long) t_agent_answered); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Caller-Joined-Time", "%ld", (long) t_member_called); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-UUID", h->member_uuid); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Name", h->member_caller_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Number", h->member_caller_number); @@ -1508,15 +1551,20 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa while(switch_channel_up(member_channel) && switch_channel_up(agent_channel) && globals.running) { switch_yield(100000); } - tiers_state = CC_TIER_STATE_READY; + tiers_state = CC_TIER_STATE_READY; if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CALLCENTER_EVENT) == SWITCH_STATUS_SUCCESS) { switch_channel_event_set_data(agent_channel, event); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Queue", h->queue_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Action", "bridge-agent-end"); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Hangup-Cause", switch_channel_cause2str(cause)); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent", h->agent_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent-System", h->agent_system); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent-UUID", agent_uuid); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Agent-Called-Time", "%ld", (long) t_agent_called); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Agent-Answered-Time", "%ld", (long) t_agent_answered); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Caller-Joined-Time", "%ld", (long) t_member_called); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Bridge-Terminated-Time", "%ld", (long) switch_epoch_time_now(NULL)); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-UUID", h->member_uuid); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Name", h->member_caller_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Number", h->member_caller_number); @@ -1542,23 +1590,25 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa switch_channel_event_set_data(member_channel, event); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Queue", h->queue_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Action", "member-queue-end"); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Hangup-Cause", switch_channel_cause2str(cause)); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Cause", "Terminated"); - switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Agent-Answer-Time", "%ld", (long) (t_agent_answered - t_agent_called)); - switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Wait-Time", "%ld", (long) (t_agent_answered - t_member_called)); - switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Talk-Time", "%ld", (long) (switch_epoch_time_now(NULL) - t_agent_answered)); - switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Total-Time", "%ld", (long) (switch_epoch_time_now(NULL) - t_member_called)); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent", h->agent_name); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent-System", h->agent_system); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent-UUID", agent_uuid); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Agent-Called-Time", "%ld", (long) t_agent_called); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Agent-Answered-Time", "%ld", (long) t_agent_answered); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Caller-Leaving-Time", "%ld", (long) switch_epoch_time_now(NULL)); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Caller-Joined-Time", "%ld", (long) t_member_called); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-UUID", h->member_uuid); - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Name", - switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_name"))); - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Number", - switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number"))); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Name", h->member_caller_name); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Number", h->member_caller_number); switch_event_fire(&event); } } else { - int delay_next_agent_call = 0; /* Agent didn't answer or originate failed */ + int delay_next_agent_call = 0; sql = switch_mprintf("UPDATE members SET state = '%q', serving_agent = '', serving_system = ''" " WHERE serving_agent = '%q' AND serving_system = '%q' AND uuid = '%q' AND system = 'single_box'", cc_member_state2str(CC_MEMBER_STATE_WAITING), @@ -1566,33 +1616,25 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa cc_execute_sql(NULL, sql, NULL); switch_safe_free(sql); - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Agent %s Origination Canceled : %s\n",h->agent_name, switch_channel_cause2str(cause)); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Agent %s Origination Canceled : %s\n", h->agent_name, switch_channel_cause2str(cause)); switch (cause) { - case SWITCH_CAUSE_USER_NOT_REGISTERED: /* When we are calling a unregistred device */ - case SWITCH_CAUSE_USER_BUSY: /* Could be the phone is in Do Not Disturb */ - delay_next_agent_call = (h->busy_delay_time > delay_next_agent_call?h->busy_delay_time:delay_next_agent_call); - break; - case SWITCH_CAUSE_CALL_REJECTED: /* User could have press the reject call on their phone */ - delay_next_agent_call = (h->reject_delay_time > delay_next_agent_call?h->reject_delay_time:delay_next_agent_call); - break; - default: - break; - } - - switch (cause) { - case SWITCH_CAUSE_USER_NOT_REGISTERED: /* When we are calling a unregistred device */ - case SWITCH_CAUSE_USER_BUSY: /* Could be the phone is in Do Not Disturb */ - case SWITCH_CAUSE_CALL_REJECTED: /* User could have press the reject call on their phone */ + /* When we hang-up agents that did not answer in ring-all strategy */ case SWITCH_CAUSE_ORIGINATOR_CANCEL: - if (delay_next_agent_call > 0) { - char ready_epoch[64]; - switch_snprintf(ready_epoch, sizeof(ready_epoch), "%" SWITCH_TIME_T_FMT, switch_epoch_time_now(NULL) + delay_next_agent_call); /* Make the time configurable */ - cc_agent_update("ready_time", ready_epoch , h->agent_name); - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Agent %s Sleeping for %d secondes\n", h->agent_name, delay_next_agent_call); - } break; + /* Busy: Do Not Disturb, Circuit congestion */ + case SWITCH_CAUSE_NORMAL_CIRCUIT_CONGESTION: + case SWITCH_CAUSE_USER_BUSY: + delay_next_agent_call = (h->busy_delay_time > delay_next_agent_call? h->busy_delay_time : delay_next_agent_call); + break; + /* Reject: User rejected the call */ + case SWITCH_CAUSE_CALL_REJECTED: + delay_next_agent_call = (h->reject_delay_time > delay_next_agent_call? h->reject_delay_time : delay_next_agent_call); + break; + /* No answer: Destination does not answer for some other reason */ default: + delay_next_agent_call = (h->no_answer_delay_time > delay_next_agent_call? h->no_answer_delay_time : delay_next_agent_call); + tiers_state = CC_TIER_STATE_NO_ANSWER; /* Update Agent NO Answer count */ @@ -1607,6 +1649,28 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa h->agent_name, h->max_no_answer); cc_agent_update("status", cc_agent_status2str(CC_AGENT_STATUS_ON_BREAK), h->agent_name); } + break; + } + + /* Put agent to sleep for some time if necessary */ + if (delay_next_agent_call > 0) { + char ready_epoch[64]; + switch_snprintf(ready_epoch, sizeof(ready_epoch), "%" SWITCH_TIME_T_FMT, switch_epoch_time_now(NULL) + delay_next_agent_call); + cc_agent_update("ready_time", ready_epoch , h->agent_name); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Agent %s sleeping for %d seconds\n", h->agent_name, delay_next_agent_call); + } + + /* Fire up event when contact agent fails */ + if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CALLCENTER_EVENT) == SWITCH_STATUS_SUCCESS) { + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Queue", h->queue_name); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Action", "bridge-agent-fail"); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Hangup-Cause", switch_channel_cause2str(cause)); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent", h->agent_name); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent-System", h->agent_system); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-UUID", h->member_uuid); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Name", h->member_caller_name); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Number", h->member_caller_number); + switch_event_fire(&event); } } @@ -1670,14 +1734,14 @@ static int agents_callback(void *pArg, int argc, char **argv, char **columnNames char *sql = NULL; char res[256]; char *agent_status = argv[2]; - char *agent_tier_state = argv[8]; - char *agent_last_bridge_end = argv[9]; - char *agent_wrap_up_time = argv[10]; - char *agent_state = argv[11]; - char *agent_ready_time = argv[12]; - char *agent_tier_level = argv[13]; - char *agent_type = argv[14]; - char *agent_uuid = argv[15]; + char *agent_tier_state = argv[9]; + char *agent_last_bridge_end = argv[10]; + char *agent_wrap_up_time = argv[11]; + char *agent_state = argv[12]; + char *agent_ready_time = argv[13]; + char *agent_tier_level = argv[14]; + char *agent_type = argv[15]; + char *agent_uuid = argv[16]; switch_bool_t contact_agent = SWITCH_TRUE; @@ -1787,6 +1851,7 @@ static int agents_callback(void *pArg, int argc, char **argv, char **columnNames h->max_no_answer = atoi(argv[5]); h->reject_delay_time = atoi(argv[6]); h->busy_delay_time = atoi(argv[7]); + h->no_answer_delay_time = atoi(argv[8]); cc_agent_update("state", cc_agent_state2str(CC_AGENT_STATE_RECEIVING), h->agent_name); @@ -1895,13 +1960,13 @@ static int members_callback(void *pArg, int argc, char **argv, char **columnName switch_safe_free(sql); sql_order_by = switch_mprintf("level, position"); } else if(!strcasecmp(queue_strategy, "sequentially-by-agent-order")) { - sql_order_by = switch_mprintf("level, position"); + sql_order_by = switch_mprintf("level, position, agents.last_offered_call"); /* Default to last_offered_call, let add new strategy if needing it differently */ } else { /* If the strategy doesn't exist, just fallback to the following */ - sql_order_by = switch_mprintf("level, position"); + sql_order_by = switch_mprintf("level, position, agents.last_offered_call"); } - sql = switch_mprintf("SELECT system, name, status, contact, no_answer_count, max_no_answer, reject_delay_time, busy_delay_time,tiers.state, agents.last_bridge_end, agents.wrap_up_time, agents.state, agents.ready_time, tiers.level, agents.type, agents.uuid FROM agents LEFT JOIN tiers ON (agents.name = tiers.agent)" + sql = switch_mprintf("SELECT system, name, status, contact, no_answer_count, max_no_answer, reject_delay_time, busy_delay_time, no_answer_delay_time, tiers.state, agents.last_bridge_end, agents.wrap_up_time, agents.state, agents.ready_time, tiers.level, agents.type, agents.uuid FROM agents LEFT JOIN tiers ON (agents.name = tiers.agent)" " WHERE tiers.queue = '%q'" " AND (agents.status = '%q' OR agents.status = '%q' OR agents.status = '%q')" " ORDER BY %q", @@ -2045,7 +2110,7 @@ void *SWITCH_THREAD_FUNC cc_member_thread_run(switch_thread_t *thread, void *obj switch_channel_set_flag_value(member_channel, CF_BREAK, 2); } - /* Will drop the caller if no agent was found for more than X secondes */ + /* Will drop the caller if no agent was found for more than X seconds */ if (queue->max_wait_time_with_no_agent > 0 && m->t_member_called < queue->last_agent_exist_check - queue->max_wait_time_with_no_agent_time_reached && queue->last_agent_exist_check - queue->last_agent_exist >= queue->max_wait_time_with_no_agent) { m->member_cancel_reason = CC_MEMBER_CANCEL_REASON_NO_AGENT_TIMEOUT; @@ -2301,52 +2366,63 @@ SWITCH_STANDARD_APP(callcenter_function) h->running = 0; } - /* Hangup any agents been callback */ - if (!switch_channel_up(member_channel)) { /* If channel is still up, it mean that the member didn't hangup, so we should leave the agent alone */ - switch_core_session_hupall_matching_var("cc_member_uuid", member_uuid, SWITCH_CAUSE_ORIGINATOR_CANCEL); + /* Check if we were removed be cause FS Core(BREAK) asked us too */ + if (h->member_cancel_reason == CC_MEMBER_CANCEL_REASON_NONE && !switch_channel_get_variable(member_channel, "cc_agent_uuid")) { + h->member_cancel_reason = CC_MEMBER_CANCEL_REASON_BREAK_OUT; + } + + /* Canceled for some reason */ + if (!switch_channel_up(member_channel) || h->member_cancel_reason != CC_MEMBER_CANCEL_REASON_NONE) { + /* Update member state */ sql = switch_mprintf("UPDATE members SET state = '%q', uuid = '', abandoned_epoch = '%ld' WHERE system = 'single_box' AND uuid = '%q'", cc_member_state2str(CC_MEMBER_STATE_ABANDONED), (long) switch_epoch_time_now(NULL), member_uuid); cc_execute_sql(NULL, sql, NULL); switch_safe_free(sql); - /* Generate an Event and update some channel variable */ + /* Hangup any callback agents */ + switch_core_session_hupall_matching_var("cc_member_uuid", member_uuid, SWITCH_CAUSE_ORIGINATOR_CANCEL); + + /* Generate an event */ if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CALLCENTER_EVENT) == SWITCH_STATUS_SUCCESS) { switch_channel_event_set_data(member_channel, event); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Queue", queue_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Action", "member-queue-end"); - switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Wait-Time", "%ld", (long) (switch_epoch_time_now(NULL) - t_member_called)); - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Member \"%s\" <%s> exit queue %s due to %s\n", - switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_name")), - switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number")), - queue_name, cc_member_cancel_reason2str(h->member_cancel_reason)); - - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Cause", cc_member_cancel_reason2str(h->member_cancel_reason)); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Caller-Leaving-Time", "%ld", (long) switch_epoch_time_now(NULL)); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Caller-Joined-Time", "%ld", (long) t_member_called); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Cause", "Cancel"); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Cancel-Reason", cc_member_cancel_reason2str(h->member_cancel_reason)); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-UUID", member_uuid); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Name", switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_name"))); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Number", switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number"))); switch_event_fire(&event); } - /* for xml_cdr needs */ + /* Update some channel variables for xml_cdr needs */ switch_channel_set_variable_printf(member_channel, "cc_queue_canceled_epoch", "%ld", (long) switch_epoch_time_now(NULL)); - switch_channel_set_variable_printf(member_channel, "cc_cause", "%s", cc_member_cancel_reason2str(h->member_cancel_reason)); + switch_channel_set_variable_printf(member_channel, "cc_cause", "%s", "cancel"); + switch_channel_set_variable_printf(member_channel, "cc_cancel_reason", "%s", cc_member_cancel_reason2str(h->member_cancel_reason)); - - /* Send Event with queue count */ - cc_queue_count(queue_name); + /* Print some debug log information */ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Member \"%s\" <%s> exit queue %s due to %s\n", + switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_name")), + switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number")), + queue_name, cc_member_cancel_reason2str(h->member_cancel_reason)); } else { - switch_channel_set_variable_printf(member_channel, "cc_cause", "%s", "answered"); + /* Update member state */ sql = switch_mprintf("UPDATE members SET state = '%q', bridge_epoch = '%ld' WHERE system = 'single_box' AND uuid = '%q'", cc_member_state2str(CC_MEMBER_STATE_ANSWERED), (long) switch_epoch_time_now(NULL), member_uuid); cc_execute_sql(NULL, sql, NULL); switch_safe_free(sql); - /* Send Event with queue count */ - cc_queue_count(queue_name); + /* Update some channel variables for xml_cdr needs */ + switch_channel_set_variable_printf(member_channel, "cc_cause", "%s", "answered"); } + /* Send Event with queue count */ + cc_queue_count(queue_name); + end: return; @@ -2394,18 +2470,26 @@ static int list_result_callback(void *pArg, int argc, char **argv, char **column "callcenter_config agent set ready_time [agent_name] [wait till epoch] | "\ "callcenter_config agent set reject_delay_time [agent_name] [wait second] | "\ "callcenter_config agent set busy_delay_time [agent_name] [wait second] | "\ +"callcenter_config agent set no_answer_delay_time [agent_name] [wait second] | "\ "callcenter_config agent get status [agent_name] | " \ +"callcenter_config agent list | " \ "callcenter_config tier add [queue_name] [agent_name] [level] [position] | " \ "callcenter_config tier set state [queue_name] [agent_name] [state] | " \ "callcenter_config tier set level [queue_name] [agent_name] [level] | " \ "callcenter_config tier set position [queue_name] [agent_name] [position] | " \ "callcenter_config tier del [queue_name] [agent_name] | " \ +"callcenter_config tier list | " \ "callcenter_config queue load [queue_name] | " \ "callcenter_config queue unload [queue_name] | " \ "callcenter_config queue reload [queue_name] | " \ -"callcenter_config tier list [queue_name] | " \ -"callcenter_config queue list [queue_name] | " \ -"callcenter_config queue count [queue_name]" +"callcenter_config queue list | " \ +"callcenter_config queue list agents [queue_name] [status] | " \ +"callcenter_config queue list members [queue_name] | " \ +"callcenter_config queue list tiers [queue_name] | " \ +"callcenter_config queue count | " \ +"callcenter_config queue count agents [queue_name] [status] | " \ +"callcenter_config queue count members [queue_name] | " \ +"callcenter_config queue count tiers [queue_name]" SWITCH_STANDARD_API(cc_config_api_function) { @@ -2513,6 +2597,7 @@ SWITCH_STANDARD_API(cc_config_api_function) } } + } else if (action && !strcasecmp(action, "get")) { if (argc-initial_argc < 2) { stream->write_function(stream, "%s", "-ERR Invalid!\n"); @@ -2538,6 +2623,7 @@ SWITCH_STANDARD_API(cc_config_api_function) } } + } else if (action && !strcasecmp(action, "list")) { struct list_result cbt; cbt.row_process = 0; @@ -2616,6 +2702,7 @@ SWITCH_STANDARD_API(cc_config_api_function) goto done; } } + } else if (action && !strcasecmp(action, "del")) { if (argc-initial_argc < 2) { stream->write_function(stream, "%s", "-ERR Invalid!\n"); @@ -2635,19 +2722,13 @@ SWITCH_STANDARD_API(cc_config_api_function) } } else if (action && !strcasecmp(action, "list")) { - if (argc-initial_argc < 1) { - stream->write_function(stream, "%s", "-ERR Invalid!\n"); - goto done; - } else { - const char *queue = argv[0 + initial_argc]; - struct list_result cbt; - cbt.row_process = 0; - cbt.stream = stream; - sql = switch_mprintf("SELECT * FROM tiers WHERE queue = '%q' ORDER BY level, position", queue); - cc_execute_sql_callback(NULL /* queue */, NULL /* mutex */, sql, list_result_callback, &cbt /* Call back variables */); - switch_safe_free(sql); - stream->write_function(stream, "%s", "+OK\n"); - } + struct list_result cbt; + cbt.row_process = 0; + cbt.stream = stream; + sql = switch_mprintf("SELECT * FROM tiers ORDER BY level, position"); + cc_execute_sql_callback(NULL /* queue */, NULL /* mutex */, sql, list_result_callback, &cbt /* Call back variables */); + switch_safe_free(sql); + stream->write_function(stream, "%s", "+OK\n"); } } else if (section && !strcasecmp(section, "queue")) { if (action && !strcasecmp(action, "load")) { @@ -2664,6 +2745,7 @@ SWITCH_STANDARD_API(cc_config_api_function) stream->write_function(stream, "%s", "-ERR Invalid Queue not found!\n"); } } + } else if (action && !strcasecmp(action, "unload")) { if (argc-initial_argc < 1) { stream->write_function(stream, "%s", "-ERR Invalid!\n"); @@ -2674,6 +2756,7 @@ SWITCH_STANDARD_API(cc_config_api_function) stream->write_function(stream, "%s", "+OK\n"); } + } else if (action && !strcasecmp(action, "reload")) { if (argc-initial_argc < 1) { stream->write_function(stream, "%s", "-ERR Invalid!\n"); @@ -2689,7 +2772,9 @@ SWITCH_STANDARD_API(cc_config_api_function) stream->write_function(stream, "%s", "-ERR Invalid Queue not found!\n"); } } + } else if (action && !strcasecmp(action, "list")) { + /* queue list */ if (argc-initial_argc < 1) { switch_hash_index_t *hi; stream->write_function(stream, "%s", "name|strategy|moh_sound|time_base_score|tier_rules_apply|tier_rule_wait_second|tier_rule_wait_multiply_level|tier_rule_no_agent_no_wait|discard_abandoned_after|abandoned_resume_allowed|max_wait_time|max_wait_time_with_no_agent|max_wait_time_with_no_agent_time_reached|record_template\n"); @@ -2708,35 +2793,81 @@ SWITCH_STANDARD_API(cc_config_api_function) stream->write_function(stream, "%s", "+OK\n"); goto done; } else { - const char *queue_name = argv[0 + initial_argc]; + const char *sub_action = argv[0 + initial_argc]; + const char *queue_name = argv[1 + initial_argc]; + const char *status = NULL; struct list_result cbt; + + /* queue list agents */ + if (sub_action && !strcasecmp(sub_action, "agents")) { + if (argc-initial_argc > 2) { + status = argv[2 + initial_argc]; + } + if (status) { + sql = switch_mprintf("SELECT agents.* FROM agents,tiers WHERE tiers.agent = agents.name AND tiers.queue = '%q' AND agents.status = '%q'", queue_name, status); + } else { + sql = switch_mprintf("SELECT agents.* FROM agents,tiers WHERE tiers.agent = agents.name AND tiers.queue = '%q'", queue_name); + } + /* queue list members */ + } else if (sub_action && !strcasecmp(sub_action, "members")) { + sql = switch_mprintf("SELECT * FROM members WHERE queue = '%q';", queue_name); + /* queue list tiers */ + } else if (sub_action && !strcasecmp(sub_action, "tiers")) { + sql = switch_mprintf("SELECT * FROM tiers WHERE queue = '%q';", queue_name); + } else { + stream->write_function(stream, "%s", "-ERR Invalid!\n"); + goto done; + } + cbt.row_process = 0; cbt.stream = stream; - sql = switch_mprintf("SELECT * FROM members WHERE queue = '%q'", queue_name); cc_execute_sql_callback(NULL /* queue */, NULL /* mutex */, sql, list_result_callback, &cbt /* Call back variables */); switch_safe_free(sql); stream->write_function(stream, "%s", "+OK\n"); } + } else if (action && !strcasecmp(action, "count")) { + /* queue count */ if (argc-initial_argc < 1) { - stream->write_function(stream, "%s", "-ERR Invalid!\n"); + switch_hash_index_t *hi; + int queue_count = 0; + switch_mutex_lock(globals.mutex); + for (hi = switch_hash_first(NULL, globals.queue_hash); hi; hi = switch_hash_next(hi)) { + queue_count++; + } + switch_mutex_unlock(globals.mutex); + stream->write_function(stream, "%d\n", queue_count); goto done; } else { - const char *queue_name = argv[0 + initial_argc]; + const char *sub_action = argv[0 + initial_argc]; + const char *queue_name = argv[1 + initial_argc]; + const char *status = NULL; char res[256] = ""; - switch_event_t *event; - /* Check to see if agent already exist */ - sql = switch_mprintf("SELECT count(*) FROM members WHERE queue = '%q'", queue_name); + + /* queue count agents */ + if (sub_action && !strcasecmp(sub_action, "agents")) { + if (argc-initial_argc > 2) { + status = argv[2 + initial_argc]; + } + if (status) { + sql = switch_mprintf("SELECT count(*) FROM agents,tiers WHERE tiers.agent = agents.name AND tiers.queue = '%q' AND agents.status = '%q'", queue_name, status); + } else { + sql = switch_mprintf("SELECT count(*) FROM agents,tiers WHERE tiers.agent = agents.name AND tiers.queue = '%q'", queue_name); + } + /* queue count members */ + } else if (sub_action && !strcasecmp(sub_action, "members")) { + sql = switch_mprintf("SELECT count(*) FROM members WHERE queue = '%q';", queue_name); + /* queue count tiers */ + } else if (sub_action && !strcasecmp(sub_action, "tiers")) { + sql = switch_mprintf("SELECT count(*) FROM tiers WHERE queue = '%q';", queue_name); + } else { + stream->write_function(stream, "%s", "-ERR Invalid!\n"); + goto done; + } + cc_execute_sql2str(NULL, NULL, sql, res, sizeof(res)); switch_safe_free(sql); stream->write_function(stream, "%d\n", atoi(res)); - - if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CALLCENTER_EVENT) == SWITCH_STATUS_SUCCESS) { - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Queue", queue_name); - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Action", "members-count"); - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Count", res); - switch_event_fire(&event); - } } } } @@ -2789,10 +2920,10 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_callcenter_load) switch_console_set_complete("add callcenter_config agent set ready_time"); switch_console_set_complete("add callcenter_config agent set reject_delay_time"); switch_console_set_complete("add callcenter_config agent set busy_delay_time"); + switch_console_set_complete("add callcenter_config agent set no_answer_delay_time"); switch_console_set_complete("add callcenter_config agent get status"); switch_console_set_complete("add callcenter_config agent list"); - switch_console_set_complete("add callcenter_config tier add"); switch_console_set_complete("add callcenter_config tier del"); switch_console_set_complete("add callcenter_config tier set state"); @@ -2804,7 +2935,13 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_callcenter_load) switch_console_set_complete("add callcenter_config queue unload"); switch_console_set_complete("add callcenter_config queue reload"); switch_console_set_complete("add callcenter_config queue list"); + switch_console_set_complete("add callcenter_config queue list agents"); + switch_console_set_complete("add callcenter_config queue list members"); + switch_console_set_complete("add callcenter_config queue list tiers"); switch_console_set_complete("add callcenter_config queue count"); + switch_console_set_complete("add callcenter_config queue count agents"); + switch_console_set_complete("add callcenter_config queue count members"); + switch_console_set_complete("add callcenter_config queue count tiers"); /* indicate that the module should continue to be loaded */ return SWITCH_STATUS_SUCCESS; diff --git a/src/mod/applications/mod_conference/mod_conference.c b/src/mod/applications/mod_conference/mod_conference.c index d0f5fac081..0f5ea26e09 100644 --- a/src/mod/applications/mod_conference/mod_conference.c +++ b/src/mod/applications/mod_conference/mod_conference.c @@ -37,6 +37,7 @@ */ #include #define DEFAULT_AGC_LEVEL 1100 +#define CONFERENCE_UUID_VARIABLE "conference_uuid" SWITCH_MODULE_LOAD_FUNCTION(mod_conference_load); SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_conference_shutdown); @@ -564,7 +565,10 @@ static conference_member_t *conference_member_get(conference_obj_t *conference, member = NULL; } - switch_thread_rwlock_rdlock(member->rwlock); + if (member) { + switch_thread_rwlock_rdlock(member->rwlock); + } + switch_mutex_unlock(conference->member_mutex); return member; @@ -702,6 +706,7 @@ static switch_status_t conference_add_member(conference_obj_t *conference, confe channel = switch_core_session_get_channel(member->session); switch_channel_set_variable_printf(channel, "conference_member_id", "%d", member->id); + switch_channel_set_variable(channel, CONFERENCE_UUID_VARIABLE, conference->uuid_str); if (conference->count > 1) { if (conference->moh_sound && !switch_test_flag(conference, CFLAG_WAIT_MOD)) { @@ -4625,6 +4630,7 @@ static switch_status_t conf_api_sub_transfer(conference_obj_t *conference, switc /* move the member from the old conference to the new one */ lock_member(member); + switch_thread_rwlock_unlock(member->rwlock); if (conference != new_conference) { conference_del_member(conference, member); @@ -4660,10 +4666,6 @@ static switch_status_t conf_api_sub_transfer(conference_obj_t *conference, switc switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "transfer"); switch_event_fire(&event); } - - if (member) { - switch_thread_rwlock_unlock(member->rwlock); - } } if (new_conference) { diff --git a/src/mod/applications/mod_ladspa/Makefile b/src/mod/applications/mod_ladspa/Makefile new file mode 100644 index 0000000000..1a77c52a0d --- /dev/null +++ b/src/mod/applications/mod_ladspa/Makefile @@ -0,0 +1,5 @@ +BASE=../../../.. + +LOCAL_OBJS += load.o +include $(BASE)/build/modmake.rules + diff --git a/src/mod/applications/mod_ladspa/load.c b/src/mod/applications/mod_ladspa/load.c new file mode 100644 index 0000000000..652ecfa0f2 --- /dev/null +++ b/src/mod/applications/mod_ladspa/load.c @@ -0,0 +1,173 @@ +/* load.c + + Free software by Richard W.E. Furse. Do with as you will. No + warranty. */ + +/*****************************************************************************/ + +#include +#include +#include +#include + +/*****************************************************************************/ + +#include "ladspa.h" +#include "utils.h" +#include "inttypes.h" +#include "switch.h" +/*****************************************************************************/ + +/* This function provides a wrapping of dlopen(). When the filename is + not an absolute path (i.e. does not begin with / character), this + routine will search the LADSPA_PATH for the file. */ +static void *dlopenLADSPA(const char *pcFilename, int iFlag) +{ + + char *pcBuffer; + const char *pcEnd; + const char *pcLADSPAPath; + const char *pcStart; + int iEndsInSO; + int iNeedSlash; + size_t iFilenameLength; + void *pvResult; + + iFilenameLength = strlen(pcFilename); + pvResult = NULL; + + if (pcFilename[0] == '/') { + + /* The filename is absolute. Assume the user knows what he/she is + doing and simply dlopen() it. */ + + pvResult = dlopen(pcFilename, iFlag); + if (pvResult != NULL) + return pvResult; + + } else { + + /* If the filename is not absolute then we wish to check along the + LADSPA_PATH path to see if we can find the file there. We do + NOT call dlopen() directly as this would find plugins on the + LD_LIBRARY_PATH, whereas the LADSPA_PATH is the correct place + to search. */ + + pcLADSPAPath = getenv("LADSPA_PATH"); + + if (pcLADSPAPath) { + + pcStart = pcLADSPAPath; + while (*pcStart != '\0') { + pcEnd = pcStart; + while (*pcEnd != ':' && *pcEnd != '\0') + pcEnd++; + + pcBuffer = malloc(iFilenameLength + 2 + (pcEnd - pcStart)); + if (pcEnd > pcStart) + strncpy(pcBuffer, pcStart, pcEnd - pcStart); + iNeedSlash = 0; + if (pcEnd > pcStart) + if (*(pcEnd - 1) != '/') { + iNeedSlash = 1; + pcBuffer[pcEnd - pcStart] = '/'; + } + strcpy(pcBuffer + iNeedSlash + (pcEnd - pcStart), pcFilename); + + pvResult = dlopen(pcBuffer, iFlag); + + free(pcBuffer); + if (pvResult != NULL) + return pvResult; + + pcStart = pcEnd; + if (*pcStart == ':') + pcStart++; + } + } + } + + /* As a last ditch effort, check if filename does not end with + ".so". In this case, add this suffix and recurse. */ + iEndsInSO = 0; + if (iFilenameLength > 3) + iEndsInSO = (strcmp(pcFilename + iFilenameLength - 3, ".so") == 0); + if (!iEndsInSO) { + pcBuffer = malloc(iFilenameLength + 4); + strcpy(pcBuffer, pcFilename); + strcat(pcBuffer, ".so"); + pvResult = dlopenLADSPA(pcBuffer, iFlag); + free(pcBuffer); + } + + if (pvResult != NULL) + return pvResult; + + /* If nothing has worked, then at least we can make sure we set the + correct error message - and this should correspond to a call to + dlopen() with the actual filename requested. The dlopen() manual + page does not specify whether the first or last error message + will be kept when multiple calls are made to dlopen(). We've + covered the former case - now we can handle the latter by calling + dlopen() again here. */ + return dlopen(pcFilename, iFlag); +} + +/*****************************************************************************/ + +void *loadLADSPAPluginLibrary(const char *pcPluginFilename) +{ + + void *pvPluginHandle; + + pvPluginHandle = dlopenLADSPA(pcPluginFilename, RTLD_NOW); + if (!pvPluginHandle) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to load plugin \"%s\": %s\n", pcPluginFilename, dlerror()); + } + + return pvPluginHandle; +} + +/*****************************************************************************/ + +void unloadLADSPAPluginLibrary(void *pvLADSPAPluginLibrary) +{ + dlclose(pvLADSPAPluginLibrary); +} + +/*****************************************************************************/ + +const LADSPA_Descriptor *findLADSPAPluginDescriptor(void *pvLADSPAPluginLibrary, const char *pcPluginLibraryFilename, const char *pcPluginLabel) +{ + + const LADSPA_Descriptor *psDescriptor; + LADSPA_Descriptor_Function pfDescriptorFunction; + unsigned long lPluginIndex; + + dlerror(); + pfDescriptorFunction = (LADSPA_Descriptor_Function) (intptr_t)dlsym(pvLADSPAPluginLibrary, "ladspa_descriptor"); + if (!pfDescriptorFunction) { + const char *pcError = dlerror(); + if (pcError) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, + "Unable to find ladspa_descriptor() function in plugin " + "library file \"%s\": %s.\n" "Are you sure this is a LADSPA plugin file?\n", pcPluginLibraryFilename, pcError); + return NULL; + } + } + + for (lPluginIndex = 0;; lPluginIndex++) { + psDescriptor = pfDescriptorFunction(lPluginIndex); + if (psDescriptor == NULL) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, + "Unable to find label \"%s\" in plugin library file \"%s\".\n", pcPluginLabel, pcPluginLibraryFilename); + return NULL; + } + if (strcmp(psDescriptor->Label, pcPluginLabel) == 0) + return psDescriptor; + } +} + +/*****************************************************************************/ + +/* EOF */ diff --git a/src/mod/applications/mod_ladspa/mod_ladspa.c b/src/mod/applications/mod_ladspa/mod_ladspa.c new file mode 100644 index 0000000000..43ea87a3ca --- /dev/null +++ b/src/mod/applications/mod_ladspa/mod_ladspa.c @@ -0,0 +1,674 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2011, Anthony Minessale II + * + * 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 + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Anthony Minessale II + * + * mod_ladspa.c -- LADSPA + * + */ +#include +#include "ladspa.h" +#include "utils.h" + +/* Prototypes */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_ladspa_shutdown); +SWITCH_MODULE_RUNTIME_FUNCTION(mod_ladspa_runtime); +SWITCH_MODULE_LOAD_FUNCTION(mod_ladspa_load); + +/* SWITCH_MODULE_DEFINITION(name, load, shutdown, runtime) + * Defines a switch_loadable_module_function_table_t and a static const char[] modname + */ +SWITCH_MODULE_DEFINITION(mod_ladspa, mod_ladspa_load, mod_ladspa_shutdown, NULL); + +#define MAX_INDEX 256 + +typedef struct { + switch_core_session_t *session; + char *plugin_name; + char *label_name; + void *library_handle; + const LADSPA_Descriptor *ldesc; + LADSPA_Handle handle; + LADSPA_Data config[MAX_INDEX]; + int num_idx; + char *str_config[MAX_INDEX]; + int str_idx; + uint8_t has_config[MAX_INDEX]; + int skip; + LADSPA_Data in_buf[SWITCH_RECOMMENDED_BUFFER_SIZE]; + LADSPA_Data file_buf[SWITCH_RECOMMENDED_BUFFER_SIZE]; + LADSPA_Data out_buf[SWITCH_RECOMMENDED_BUFFER_SIZE]; + LADSPA_Data out_ports[MAX_INDEX]; + switch_file_handle_t fh; +} switch_ladspa_t; + + + +int check_range(const LADSPA_Descriptor *ldesc, int i, LADSPA_Data val) +{ + if (ldesc->PortRangeHints[i].LowerBound && ldesc->PortRangeHints[i].UpperBound && + (val < ldesc->PortRangeHints[i].LowerBound || val > ldesc->PortRangeHints[i].UpperBound)) { + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_ERROR, "Param %f out of bounds %f-%f\n", + val, ldesc->PortRangeHints[i].LowerBound, ldesc->PortRangeHints[i].UpperBound); + return 0; + } + + return 1; +} + +int find_default(const LADSPA_Descriptor *ldesc, int i, LADSPA_Data *ptr) + +{ + LADSPA_Data dftval = 0; + int fail = 0; + + LADSPA_PortRangeHintDescriptor port_hint = ldesc->PortRangeHints[i].HintDescriptor; + + switch (port_hint & LADSPA_HINT_DEFAULT_MASK) { + case LADSPA_HINT_DEFAULT_NONE: + break; + case LADSPA_HINT_DEFAULT_MINIMUM: + dftval = ldesc->PortRangeHints[i].LowerBound; + break; + case LADSPA_HINT_DEFAULT_LOW: + if (LADSPA_IS_HINT_LOGARITHMIC(port_hint)) { + dftval = exp(log(ldesc->PortRangeHints[i].LowerBound) + * 0.75 + log(ldesc->PortRangeHints[i].UpperBound) + * 0.25); + } else { + dftval = (ldesc->PortRangeHints[i].LowerBound * 0.75 + ldesc->PortRangeHints[i].UpperBound * 0.25); + } + break; + case LADSPA_HINT_DEFAULT_MIDDLE: + if (LADSPA_IS_HINT_LOGARITHMIC(port_hint)) { + dftval = sqrt(ldesc->PortRangeHints[i].LowerBound * ldesc->PortRangeHints[i].UpperBound); + } else { + dftval = 0.5 * (ldesc->PortRangeHints[i].LowerBound + ldesc->PortRangeHints[i].UpperBound); + } + break; + case LADSPA_HINT_DEFAULT_HIGH: + if (LADSPA_IS_HINT_LOGARITHMIC(port_hint)) { + dftval = exp(log(ldesc->PortRangeHints[i].LowerBound) + * 0.25 + log(ldesc->PortRangeHints[i].UpperBound) + * 0.75); + } else { + dftval = (ldesc->PortRangeHints[i].LowerBound * 0.25 + ldesc->PortRangeHints[i].UpperBound * 0.75); + } + break; + case LADSPA_HINT_DEFAULT_MAXIMUM: + dftval = ldesc->PortRangeHints[i].UpperBound; + break; + case LADSPA_HINT_DEFAULT_0: + dftval = 0; + break; + case LADSPA_HINT_DEFAULT_1: + dftval = 1; + break; + case LADSPA_HINT_DEFAULT_100: + dftval = 100; + break; + case LADSPA_HINT_DEFAULT_440: + dftval = 440; + break; + default: + fail = 1; + break; + } + + if (!fail) { + *ptr = dftval; + } + + return !fail; +} + +static void dump_info(const LADSPA_Descriptor *ldesc) +{ + int i = 0; + + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Plugin Name: \"%s\"\n", ldesc->Name); + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Plugin Label: \"%s\"\n", ldesc->Label); + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Plugin Unique ID: %lu\n", ldesc->UniqueID); + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Maker: \"%s\"\n", ldesc->Maker); + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Copyright: \"%s\"\n", ldesc->Copyright); + + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Must Run Real-Time: "); + if (LADSPA_IS_REALTIME(ldesc->Properties)) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Yes\n"); + else + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "No\n"); + + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Has activate() Function: "); + if (ldesc->activate != NULL) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Yes\n"); + else + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "No\n"); + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Has deactivate() Function: "); + if (ldesc->deactivate != NULL) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Yes\n"); + else + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "No\n"); + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Has run_adding() Function: "); + if (ldesc->run_adding != NULL) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Yes\n"); + else + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "No\n"); + + if (ldesc->instantiate == NULL) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "ERROR: PLUGIN HAS NO INSTANTIATE FUNCTION.\n"); + if (ldesc->connect_port == NULL) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "ERROR: PLUGIN HAS NO CONNECT_PORT FUNCTION.\n"); + if (ldesc->run == NULL) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "ERROR: PLUGIN HAS NO RUN FUNCTION.\n"); + if (ldesc->run_adding != NULL && ldesc->set_run_adding_gain == NULL) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "ERROR: PLUGIN HAS RUN_ADDING FUNCTION BUT " "NOT SET_RUN_ADDING_GAIN.\n"); + if (ldesc->run_adding == NULL && ldesc->set_run_adding_gain != NULL) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "ERROR: PLUGIN HAS SET_RUN_ADDING_GAIN FUNCTION BUT " "NOT RUN_ADDING.\n"); + if (ldesc->cleanup == NULL) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "ERROR: PLUGIN HAS NO CLEANUP FUNCTION.\n"); + + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Environment: "); + if (LADSPA_IS_HARD_RT_CAPABLE(ldesc->Properties)) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Normal or Hard Real-Time\n"); + else + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Normal\n"); + + if (LADSPA_IS_INPLACE_BROKEN(ldesc->Properties)) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "This plugin cannot use in-place processing. " "It will not work with all hosts.\n"); + + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "Ports:"); + + if (ldesc->PortCount == 0) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "\tERROR: PLUGIN HAS NO PORTS.\n"); + + for (i = 0; i < ldesc->PortCount; i++) { + LADSPA_Data dft = 0.0f; + int found = 0; + + if (LADSPA_IS_PORT_CONTROL(ldesc->PortDescriptors[i])) { + found = find_default(ldesc, i, &dft); + } + + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "\n \"%s\" ", ldesc->PortNames[i]); + + if (LADSPA_IS_PORT_INPUT(ldesc->PortDescriptors[i]) + && LADSPA_IS_PORT_OUTPUT(ldesc->PortDescriptors[i])) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "ERROR: INPUT AND OUTPUT"); + else if (LADSPA_IS_PORT_INPUT(ldesc->PortDescriptors[i])) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "input"); + else if (LADSPA_IS_PORT_OUTPUT(ldesc->PortDescriptors[i])) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "output"); + else + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "ERROR: NEITHER INPUT NOR OUTPUT"); + + if (LADSPA_IS_PORT_CONTROL(ldesc->PortDescriptors[i]) + && LADSPA_IS_PORT_AUDIO(ldesc->PortDescriptors[i])) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, ", ERROR: CONTROL AND AUDIO"); + else if (LADSPA_IS_PORT_CONTROL(ldesc->PortDescriptors[i])) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, ", control"); + else if (LADSPA_IS_PORT_AUDIO(ldesc->PortDescriptors[i])) + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, ", audio"); + else + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, ", ERROR: NEITHER CONTROL NOR AUDIO"); + + if (LADSPA_IS_PORT_CONTROL(ldesc->PortDescriptors[i])) { + if (found) { + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "\n RANGE: %f-%f DEFAULT: %f\n", + ldesc->PortRangeHints[i].LowerBound, ldesc->PortRangeHints[i].UpperBound, dft); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "\n RANGE: %f-%f DEFAULT: none.\n", + ldesc->PortRangeHints[i].LowerBound, ldesc->PortRangeHints[i].UpperBound); + } + } + + + + } + + switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "\n\n"); +} + + + + + +static switch_bool_t ladspa_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type) +{ + switch_ladspa_t *pvt = (switch_ladspa_t *) user_data; + //switch_frame_t *frame = NULL; + switch_channel_t *channel = switch_core_session_get_channel(pvt->session); + + switch (type) { + case SWITCH_ABC_TYPE_INIT: + { + switch_codec_implementation_t read_impl = { 0 }; + LADSPA_PortDescriptor port_desc; + int i = 0, j = 0, k = 0, str_idx = 0; + + switch_core_session_get_read_impl(pvt->session, &read_impl); + + if (!(pvt->library_handle = loadLADSPAPluginLibrary(pvt->plugin_name))) { + return SWITCH_FALSE; + } + + if (!(pvt->ldesc = findLADSPAPluginDescriptor(pvt->library_handle, pvt->plugin_name, pvt->label_name))) { + return SWITCH_FALSE; + } + + + pvt->handle = pvt->ldesc->instantiate(pvt->ldesc, read_impl.actual_samples_per_second); + + dump_info(pvt->ldesc); + + + for (i = 0; i < pvt->ldesc->PortCount; i++) { + port_desc = pvt->ldesc->PortDescriptors[i]; + + if (LADSPA_IS_PORT_CONTROL(port_desc) && LADSPA_IS_PORT_INPUT(port_desc)) { + LADSPA_Data dft = 0.0f; + int found = find_default(pvt->ldesc, i, &dft); + + if (found && !pvt->has_config[j]) { + pvt->config[j] = dft; + pvt->has_config[j] = 1; + } + + if (pvt->has_config[j]) { + if (!check_range(pvt->ldesc, i, pvt->config[j])) { + pvt->config[j] = dft; + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pvt->session), SWITCH_LOG_WARNING, "FALLING TO DEFAULT PARAM %d [%s] (%f)\n", + j+1, + pvt->ldesc->PortNames[i], + pvt->config[j]); + } + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pvt->session), SWITCH_LOG_DEBUG, "ADDING PARAM %d [%s] (%f)\n", + j+1, + pvt->ldesc->PortNames[i], + pvt->config[j]); + pvt->ldesc->connect_port(pvt->handle, i, &pvt->config[j++]); + usleep(10000); + } + } + + if (LADSPA_IS_PORT_INPUT(port_desc) && LADSPA_IS_PORT_AUDIO(port_desc)) { + int mapped = 0; + + if (pvt->str_idx && !zstr(pvt->str_config[str_idx])) { + + if (!strcasecmp(pvt->str_config[str_idx], "none")) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pvt->session), SWITCH_LOG_DEBUG, "CONNECT NOTHING to port: %s\n", + pvt->ldesc->PortNames[i] + ); + mapped = 1; + } else if (!strncasecmp(pvt->str_config[str_idx], "file:", 5)) { + char *file = pvt->str_config[str_idx] + 5; + + if (switch_test_flag((&pvt->fh), SWITCH_FILE_OPEN)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pvt->session), + SWITCH_LOG_ERROR, "CAN'T CONNECT FILE [%s] File already mapped\n", file); + } else { + if (switch_core_file_open(&pvt->fh, + file, + read_impl.number_of_channels, + read_impl.actual_samples_per_second, + SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT, NULL) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pvt->session), SWITCH_LOG_ERROR, "Cannot open file: %s\n", file); + return SWITCH_FALSE; + } + + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pvt->session), SWITCH_LOG_DEBUG, "CONNECT FILE [%s] to port: %s\n", + file, + pvt->ldesc->PortNames[i] + ); + + pvt->ldesc->connect_port(pvt->handle, i, pvt->file_buf); + mapped = 1; + } + } + + str_idx++; + } + + if (!mapped) { + pvt->ldesc->connect_port(pvt->handle, i, pvt->in_buf); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pvt->session), SWITCH_LOG_DEBUG, "CONNECT CHANNEL AUDIO to port: %s\n", + pvt->ldesc->PortNames[i] + ); + } + + } + + if (LADSPA_IS_PORT_OUTPUT(port_desc)) { + if (LADSPA_IS_PORT_AUDIO(port_desc)) { + pvt->ldesc->connect_port(pvt->handle, i, pvt->out_buf); + } else if (k < MAX_INDEX) { + pvt->ldesc->connect_port(pvt->handle, i, &pvt->out_ports[k++]); + } + } + } + } + + break; + + case SWITCH_ABC_TYPE_CLOSE: + { + + if (switch_test_flag((&pvt->fh), SWITCH_FILE_OPEN)) { + switch_core_file_close(&pvt->fh); + } + + if (pvt->handle && pvt->ldesc) { + pvt->ldesc->cleanup(pvt->handle); + } + + if (pvt->library_handle) { + unloadLADSPAPluginLibrary(pvt->library_handle); + } + } + break; + + case SWITCH_ABC_TYPE_WRITE_REPLACE: + case SWITCH_ABC_TYPE_READ_REPLACE: + { + switch_frame_t *rframe; + int16_t *slin, abuf[SWITCH_RECOMMENDED_BUFFER_SIZE] = { 0 }; + switch_size_t olen = 0; + + + if (type == SWITCH_ABC_TYPE_READ_REPLACE) { + rframe = switch_core_media_bug_get_read_replace_frame(bug); + } else { + rframe = switch_core_media_bug_get_write_replace_frame(bug); + } + + slin = rframe->data; + + if (switch_channel_media_ready(channel)) { + switch_short_to_float(slin, pvt->in_buf, rframe->samples); + + if (switch_test_flag((&pvt->fh), SWITCH_FILE_OPEN)) { + olen = rframe->samples; + if (switch_core_file_read(&pvt->fh, abuf, &olen) != SWITCH_STATUS_SUCCESS) { + switch_codec_implementation_t read_impl = { 0 }; + char *file = switch_core_session_strdup(pvt->session, pvt->fh.file_path); + switch_core_session_get_read_impl(pvt->session, &read_impl); + + switch_core_file_close(&pvt->fh); + + if (switch_core_file_open(&pvt->fh, + file, + read_impl.number_of_channels, + read_impl.actual_samples_per_second, + SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT, NULL) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pvt->session), SWITCH_LOG_ERROR, "Cannot open file: %s\n", file); + return SWITCH_FALSE; + } + + olen = rframe->samples; + if (switch_core_file_read(&pvt->fh, abuf, &olen) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pvt->session), SWITCH_LOG_ERROR, "Cannot READ file: %s\n", file); + return SWITCH_FALSE; + } + } + + switch_short_to_float(abuf, pvt->file_buf, olen); + } + + pvt->ldesc->run(pvt->handle, rframe->samples); + + switch_float_to_short(pvt->out_buf, slin, rframe->samples); + } + + if (type == SWITCH_ABC_TYPE_READ_REPLACE) { + switch_core_media_bug_set_read_replace_frame(bug, rframe); + } else { + switch_core_media_bug_set_write_replace_frame(bug, rframe); + } + + if (pvt->skip && !--pvt->skip) { + return SWITCH_FALSE; + } + + } + break; + case SWITCH_ABC_TYPE_WRITE: + default: + break; + } + + return SWITCH_TRUE; +} + +switch_status_t stop_ladspa_session(switch_core_session_t *session) +{ + switch_media_bug_t *bug; + switch_channel_t *channel = switch_core_session_get_channel(session); + + if ((bug = switch_channel_get_private(channel, "ladspa"))) { + switch_channel_set_private(channel, "ladspa", NULL); + switch_core_media_bug_remove(session, &bug); + return SWITCH_STATUS_SUCCESS; + } + + return SWITCH_STATUS_FALSE; +} + +switch_status_t ladspa_session(switch_core_session_t *session, const char *flags, const char *plugin_name, const char *label, const char *params) +{ + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_media_bug_t *bug; + switch_status_t status; + switch_ladspa_t *pvt = { 0 }; + switch_codec_implementation_t read_impl = { 0 }; + int i, bflags = SMBF_READ_REPLACE | SMBF_ANSWER_REQ; + char *pstr; + int argc; + char *argv[50]; + char *dparams = NULL; + + if (zstr(plugin_name)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "%s INVALID PLUGIN\n", switch_channel_get_name(channel)); + return SWITCH_STATUS_FALSE; + } + + if (zstr(flags)) { + flags = "r"; + } + + if (strchr(flags, 'w')) { + bflags = SMBF_WRITE_REPLACE; + } + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "FLAGS: %s PLUGIN: %s LABEL: %s PARAMS: %s\n", + flags, plugin_name, label, params); + + switch_core_session_get_read_impl(session, &read_impl); + + pvt = switch_core_session_alloc(session, sizeof(*pvt)); + + pvt->session = session; + if (!zstr(label)) { + pvt->label_name = switch_core_session_strdup(session, label); + } else { + char *p; + pvt->label_name = switch_core_session_strdup(session, plugin_name); + if ((p = strrchr(pvt->label_name, '.'))) { + *p = '\0'; + } + } + + if (strstr(plugin_name, ".so")) { + pvt->plugin_name = switch_core_session_strdup(session, plugin_name); + } else { + pvt->plugin_name = switch_core_session_sprintf(session, "%s.so", plugin_name); + } + + dparams = switch_core_session_strdup(session, params); + + argc = switch_split(dparams, ' ', argv); + + for (i = 0; i < argc; i++) { + if (switch_is_number(argv[i])) { + if (pvt->num_idx < MAX_INDEX) { + pvt->config[pvt->num_idx] = atof(argv[i]); + pvt->has_config[pvt->num_idx] = 1; + pvt->num_idx++; + } + } else { + if (pvt->str_idx < MAX_INDEX) { + pvt->str_config[pvt->str_idx++] = switch_core_session_strdup(session, argv[i]); + } + } + } + + if (switch_channel_pre_answer(channel) != SWITCH_STATUS_SUCCESS) { + return SWITCH_STATUS_FALSE; + } + + pstr = switch_core_session_sprintf(session, "%s|%s|%s|%s", flags, plugin_name, label, params); + + if ((status = switch_core_media_bug_add(session, "ladspa", pstr, + ladspa_callback, pvt, 0, bflags | SMBF_NO_PAUSE, &bug)) != SWITCH_STATUS_SUCCESS) { + return status; + } + + switch_channel_set_private(channel, "ladspa", bug); + + return SWITCH_STATUS_SUCCESS; +} + + +static void ladspa_parse(switch_core_session_t *session, const char *data) +{ + char *argv[5] = { 0 }; + int argc; + char *lbuf; + + if (data) { + lbuf = strdup(data); + argc = switch_separate_string(lbuf, '|', argv, (sizeof(argv) / sizeof(argv[0]))); + ladspa_session(session, argv[0], argv[1], argv[2], argv[3]); + free(lbuf); + } +} + +#define APP_SYNTAX "||