Add muxmon application (bug #4735 with mods, thanks tony!)
authorMark Spencer <markster@digium.com>
Sun, 16 Oct 2005 02:59:13 +0000 (02:59 +0000)
committerMark Spencer <markster@digium.com>
Sun, 16 Oct 2005 02:59:13 +0000 (02:59 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@6795 65c4cc65-6c06-0410-ace0-fbb531ad65f3

apps/Makefile
apps/app_mixmonitor.c [new file with mode: 0755]
apps/app_muxmon.c [new file with mode: 0755]

index 9063e20..75d7910 100755 (executable)
@@ -28,7 +28,8 @@ APPS=app_dial.so app_playback.so app_voicemail.so app_directory.so app_mp3.so\
      app_test.so app_forkcdr.so app_math.so app_realtime.so \
      app_dumpchan.so app_waitforsilence.so app_while.so app_setrdnis.so \
      app_md5.so app_readfile.so app_chanspy.so app_settransfercapability.so \
-     app_dictate.so app_externalivr.so app_directed_pickup.so app_page.so
+     app_dictate.so app_externalivr.so app_directed_pickup.so app_page.so \
+     app_muxmon.so
 
 #
 # Obsolete things...
diff --git a/apps/app_mixmonitor.c b/apps/app_mixmonitor.c
new file mode 100755 (executable)
index 0000000..852eae8
--- /dev/null
@@ -0,0 +1,528 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * muxmon Application For Asterisk
+ *
+ * Copyright (C) 2005, Anthony Minessale II
+ *
+ * Anthony Minessale II <anthmct@yahoo.com>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+#include <asterisk/file.h>
+#include <asterisk/logger.h>
+#include <asterisk/channel.h>
+#include <asterisk/pbx.h>
+#include <asterisk/module.h>
+#include <asterisk/lock.h>
+#include <asterisk/cli.h>
+#include <asterisk/options.h>
+#include <asterisk/app.h>
+#include <asterisk/translate.h>
+#include <asterisk/slinfactory.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
+#define minmax(x,y) x ? (x > y) ? y : ((x < (y * -1)) ? (y * -1) : x) : 0 
+
+AST_MUTEX_DEFINE_STATIC(modlock);
+
+static char *tdesc = "Native Channel Monitoring Module";
+static char *app = "MuxMon";
+static char *synopsis = "Record A Call Natively";
+static char *desc = ""
+"  MuxMon(<file>.<ext>[|<options>[|<command>]])\n\n"
+"Records The audio on the current channel to the specified file.\n\n"
+"Valid Options:\n"
+" b    - Only save audio to the file while the channel is bridged. Note: does\n"
+"        not include conferences\n"
+" a    - Append to the file instead of overwriting it.\n"
+" v(<x>) - Adjust the heard volume by a factor of <x> (range -4 to 4)\n"       
+" V(<x>) - Adjust the spoken volume by a factor of <x> (range -4 to 4)\n"      
+" W(<x>) - Adjust the both heard and spoken volumes by a factor of <x>\n"
+"         (range -4 to 4)\n\n" 
+"<command> will be executed when the recording is over\n"
+"Any strings matching ^{X} will be unescaped to ${X} and \n"
+"all variables will be evaluated at that time.\n"
+"The variable MUXMON_FILENAME will contain the filename used to record.\n"
+"";
+
+STANDARD_LOCAL_USER;
+
+LOCAL_USER_DECL;
+
+struct muxmon {
+       struct ast_channel *chan;
+       char *filename;
+       char *post_process;
+       unsigned int flags;
+       int readvol;
+       int writevol;
+};
+
+typedef enum {
+    MUXFLAG_RUNNING = (1 << 0),
+    MUXFLAG_APPEND = (1 << 1),
+    MUXFLAG_BRIDGED = (1 << 2),
+    MUXFLAG_VOLUME = (1 << 3),
+    MUXFLAG_READVOLUME = (1 << 4),
+    MUXFLAG_WRITEVOLUME = (1 << 5)
+} muxflags;
+
+
+AST_DECLARE_OPTIONS(muxmon_opts,{
+    ['a'] = { MUXFLAG_APPEND },
+       ['b'] = { MUXFLAG_BRIDGED },
+       ['v'] = { MUXFLAG_READVOLUME, 1 },
+       ['V'] = { MUXFLAG_WRITEVOLUME, 2 },
+       ['W'] = { MUXFLAG_VOLUME, 3 },
+});
+
+
+static void stopmon(struct ast_channel *chan, struct ast_channel_spy *spy) 
+{
+       struct ast_channel_spy *cptr=NULL, *prev=NULL;
+       int count = 0;
+
+       if (chan) {
+               while(ast_mutex_trylock(&chan->lock)) {
+                       if (chan->spiers == spy) {
+                               chan->spiers = NULL;
+                               return;
+                       }
+                       count++;
+                       if (count > 10) {
+                               return;
+                       }
+                       sched_yield();
+               }
+               
+               for(cptr=chan->spiers; cptr; cptr=cptr->next) {
+                       if (cptr == spy) {
+                               if (prev) {
+                                       prev->next = cptr->next;
+                                       cptr->next = NULL;
+                               } else
+                                       chan->spiers = NULL;
+                       }
+                       prev = cptr;
+               }
+
+               ast_mutex_unlock(&chan->lock);
+       }
+}
+
+static void startmon(struct ast_channel *chan, struct ast_channel_spy *spy) 
+{
+
+       struct ast_channel_spy *cptr=NULL;
+       struct ast_channel *peer;
+
+       if (chan) {
+               ast_mutex_lock(&chan->lock);
+               if (chan->spiers) {
+                       for(cptr=chan->spiers;cptr->next;cptr=cptr->next);
+                       cptr->next = spy;
+               } else {
+                       chan->spiers = spy;
+               }
+               ast_mutex_unlock(&chan->lock);
+               
+               if (ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan))) {
+                       ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);  
+               }
+       }
+}
+
+static int spy_queue_translate(struct ast_channel_spy *spy,
+                                                          struct ast_slinfactory *slinfactory0,
+                                                          struct ast_slinfactory *slinfactory1)
+{
+       int res = 0;
+       struct ast_frame *f;
+       
+       ast_mutex_lock(&spy->lock);
+       while((f = spy->queue[0])) {
+               spy->queue[0] = f->next;
+               ast_slinfactory_feed(slinfactory0, f);
+               ast_frfree(f);
+       }
+       ast_mutex_unlock(&spy->lock);
+       ast_mutex_lock(&spy->lock);
+       while((f = spy->queue[1])) {
+               spy->queue[1] = f->next;
+               ast_slinfactory_feed(slinfactory1, f);
+               ast_frfree(f);
+       }
+       ast_mutex_unlock(&spy->lock);
+       return res;
+}
+
+static void *muxmon_thread(void *obj) 
+{
+
+       int len0 = 0, len1 = 0, samp0 = 0, samp1 = 0, framelen, maxsamp = 0, x = 0;
+       short buf0[1280], buf1[1280], buf[1280];
+       struct ast_frame frame;
+       struct muxmon *muxmon = obj;
+       struct ast_channel_spy spy;
+       struct ast_filestream *fs = NULL;
+       char *ext, *name;
+       unsigned int oflags;
+       struct ast_slinfactory slinfactory[2];
+       char post_process[1024] = "";
+       
+       name = ast_strdupa(muxmon->chan->name);
+
+       framelen = 320;
+       frame.frametype = AST_FRAME_VOICE;
+       frame.subclass = AST_FORMAT_SLINEAR;
+       frame.data = buf;
+       ast_set_flag(muxmon, MUXFLAG_RUNNING);
+       oflags = O_CREAT|O_WRONLY;
+       ast_slinfactory_init(&slinfactory[0]);
+       ast_slinfactory_init(&slinfactory[1]);
+       
+
+
+       /* for efficiency, use a flag to bypass volume logic when it's not needed */
+       if (muxmon->readvol || muxmon->writevol) {
+               ast_set_flag(muxmon, MUXFLAG_VOLUME);
+       }
+
+       if ((ext = strchr(muxmon->filename, '.'))) {
+               *(ext++) = '\0';
+       } else {
+               ext = "raw";
+       }
+
+       memset(&spy, 0, sizeof(spy));
+       spy.status = CHANSPY_RUNNING;
+       ast_mutex_init(&spy.lock);
+       startmon(muxmon->chan, &spy);
+       if (ast_test_flag(muxmon, MUXFLAG_RUNNING)) {
+               if (option_verbose > 1) {
+                       ast_verbose(VERBOSE_PREFIX_2 "Begin Muxmon Recording %s\n", name);
+               }
+
+               oflags |= ast_test_flag(muxmon, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
+               
+               if (!(fs = ast_writefile(muxmon->filename, ext, NULL, oflags, 0, 0644))) {
+                       ast_log(LOG_ERROR, "Cannot open %s\n", muxmon->filename);
+                       spy.status = CHANSPY_DONE;
+               }  else {
+
+                       if (ast_test_flag(muxmon, MUXFLAG_APPEND)) {
+                               ast_seekstream(fs, 0, SEEK_END);
+                       }
+
+                       while (ast_test_flag(muxmon, MUXFLAG_RUNNING)) {
+                               samp0 = samp1 = len0 = len1 = 0;
+
+                               if (ast_check_hangup(muxmon->chan) || spy.status != CHANSPY_RUNNING) {
+                                       ast_clear_flag(muxmon, MUXFLAG_RUNNING);
+                                       break;
+                               }
+
+                               if (ast_test_flag(muxmon, MUXFLAG_BRIDGED) && !ast_bridged_channel(muxmon->chan)) {
+                                       usleep(1000);
+                                       sched_yield();
+                                       continue;
+                               }
+                               
+                               spy_queue_translate(&spy, &slinfactory[0], &slinfactory[1]);
+                               
+                               if (slinfactory[0].size < framelen || slinfactory[1].size < framelen) {
+                                       usleep(1000);
+                                       sched_yield();
+                                       continue;
+                               }
+
+                               if ((len0 = ast_slinfactory_read(&slinfactory[0], buf0, framelen))) {
+                                       samp0 = len0 / 2;
+                               }
+                               if((len1 = ast_slinfactory_read(&slinfactory[1], buf1, framelen))) {
+                                       samp1 = len1 / 2;
+                               }
+                               
+                               if (ast_test_flag(muxmon, MUXFLAG_VOLUME)) {
+                                       if (samp0 && muxmon->readvol > 0) {
+                                               for(x=0; x < samp0 / 2; x++) {
+                                                       buf0[x] *= muxmon->readvol;
+                                               }
+                                       } else if (samp0 && muxmon->readvol < 0) {
+                                               for(x=0; x < samp0 / 2; x++) {
+                                                       buf0[x] /= muxmon->readvol;
+                                               }
+                                       }
+                                       if (samp1 && muxmon->writevol > 0) {
+                                               for(x=0; x < samp1 / 2; x++) {
+                                                       buf1[x] *= muxmon->writevol;
+                                               }
+                                       } else if (muxmon->writevol < 0) {
+                                               for(x=0; x < samp1 / 2; x++) {
+                                                       buf1[x] /= muxmon->writevol;
+                                               }
+                                       }
+                               }
+                               
+                               maxsamp = (samp0 > samp1) ? samp0 : samp1;
+
+                               if (samp0 && samp1) {
+                                       for(x=0; x < maxsamp; x++) {
+                                               if (x < samp0 && x < samp1) {
+                                                       buf[x] = buf0[x] + buf1[x];
+                                               } else if (x < samp0) {
+                                                       buf[x] = buf0[x];
+                                               } else if (x < samp1) {
+                                                       buf[x] = buf1[x];
+                                               }
+                                       }
+                               } else if(samp0) {
+                                       memcpy(buf, buf0, len0);
+                                       x = samp0;
+                               } else if(samp1) {
+                                       memcpy(buf, buf1, len1);
+                                       x = samp1;
+                               }
+
+                               frame.samples = x;
+                               frame.datalen = x * 2;
+                               ast_writestream(fs, &frame);
+               
+                               usleep(1000);
+                               sched_yield();
+                       }
+               }
+       }
+
+       if (muxmon->post_process) {
+               char *p;
+               for(p = muxmon->post_process; *p ; p++) {
+                       if (*p == '^' && *(p+1) == '{') {
+                               *p = '$';
+                       }
+               }
+               pbx_substitute_variables_helper(muxmon->chan, muxmon->post_process, post_process, sizeof(post_process) - 1);
+               free(muxmon->post_process);
+               muxmon->post_process = NULL;
+       }
+
+       stopmon(muxmon->chan, &spy);
+       if (option_verbose > 1) {
+               ast_verbose(VERBOSE_PREFIX_2 "Finished Recording %s\n", name);
+       }
+       ast_mutex_destroy(&spy.lock);
+       
+       if(fs) {
+               ast_closestream(fs);
+       }
+       
+       ast_slinfactory_destroy(&slinfactory[0]);
+       ast_slinfactory_destroy(&slinfactory[1]);
+
+       if (muxmon) {
+               if (muxmon->filename) {
+                       free(muxmon->filename);
+               }
+               free(muxmon);
+       }
+
+       if (!ast_strlen_zero(post_process)) {
+               if (option_verbose > 2) {
+                       ast_verbose(VERBOSE_PREFIX_2 "Executing [%s]\n", post_process);
+               }
+               ast_safe_system(post_process);
+       }
+
+       return NULL;
+}
+
+static void launch_monitor_thread(struct ast_channel *chan, char *filename, unsigned int flags, int readvol , int writevol, char *post_process) 
+{
+       pthread_attr_t attr;
+       int result = 0;
+       pthread_t thread;
+       struct muxmon *muxmon;
+
+
+       if (!(muxmon = malloc(sizeof(struct muxmon)))) {
+               ast_log(LOG_ERROR, "Memory Error!\n");
+               return;
+       }
+
+       memset(muxmon, 0, sizeof(struct muxmon));
+       muxmon->chan = chan;
+       muxmon->filename = strdup(filename);
+       if(post_process) {
+               muxmon->post_process = strdup(post_process);
+       }
+       muxmon->readvol = readvol;
+       muxmon->writevol = writevol;
+       muxmon->flags = flags;
+
+       result = pthread_attr_init(&attr);
+       pthread_attr_setschedpolicy(&attr, SCHED_RR);
+       pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+       result = ast_pthread_create(&thread, &attr, muxmon_thread, muxmon);
+       result = pthread_attr_destroy(&attr);
+}
+
+
+static int muxmon_exec(struct ast_channel *chan, void *data)
+{
+       int res = 0, x = 0, readvol = 0, writevol = 0;
+       struct localuser *u;
+       struct ast_flags flags = {0};
+       int argc;
+       char *options = NULL,
+               *args,
+               *argv[3],
+               *filename = NULL,
+               *post_process = NULL;
+       
+       if (!data) {
+               ast_log(LOG_WARNING, "muxmon requires an argument\n");
+               return -1;
+       }
+
+       if (!(args = ast_strdupa(data))) {
+               ast_log(LOG_WARNING, "Memory Error!\n");
+        return -1;
+       }
+       
+
+       if ((argc = ast_separate_app_args(args, '|', argv, sizeof(argv) / sizeof(argv[0])))) {
+               filename = argv[0];
+               if ( argc > 1) {
+                       options = argv[1];
+               }
+               if ( argc > 2) {
+                       post_process = argv[2];
+               }
+       }
+       
+       if (!filename || ast_strlen_zero(filename)) {
+               ast_log(LOG_WARNING, "Muxmon requires an argument (filename)\n");
+        return -1;
+       }
+
+       LOCAL_USER_ADD(u);
+
+       if (options) {
+               char *opts[3] = {};
+               ast_parseoptions(muxmon_opts, &flags, opts, options);
+
+               if (ast_test_flag(&flags, MUXFLAG_READVOLUME) && opts[0]) {
+                       if (sscanf(opts[0], "%d", &x) != 1)
+                               ast_log(LOG_NOTICE, "volume must be a number between -4 and 4\n");
+                       else {
+                               readvol = minmax(x, 4);
+                               x = get_volfactor(readvol);
+                               readvol = minmax(x, 16);
+                       }
+               }
+               
+               if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME) && opts[1]) {
+                       if (sscanf(opts[1], "%d", &x) != 1)
+                               ast_log(LOG_NOTICE, "volume must be a number between -4 and 4\n");
+                       else {
+                               writevol = minmax(x, 4);
+                               x = get_volfactor(writevol);
+                               writevol = minmax(x, 16);
+                       }
+               }
+
+               if (ast_test_flag(&flags, MUXFLAG_VOLUME) && opts[2]) {
+                       if (sscanf(opts[2], "%d", &x) != 1)
+                               ast_log(LOG_NOTICE, "volume must be a number between -4 and 4\n");
+                       else {
+                               readvol = writevol = minmax(x, 4);
+                               x = get_volfactor(readvol);
+                readvol = minmax(x, 16);
+                               x = get_volfactor(writevol);
+                writevol = minmax(x, 16);
+                       }
+               }
+       }
+               pbx_builtin_setvar_helper(chan, "MUXMON_FILENAME", filename);
+       launch_monitor_thread(chan, filename, flags.flags, readvol, writevol, post_process);
+
+       LOCAL_USER_REMOVE(u);
+       return res;
+}
+
+
+static int muxmon_cli(int fd, int argc, char **argv) 
+{
+       char *op, *chan_name = NULL, *args = NULL;
+       struct ast_channel *chan;
+       int count = 0;
+
+       if (argc > 2) {
+               op = argv[1];
+               chan_name = argv[2];
+
+               if (argv[3]) {
+                       args = argv[3];
+               }
+
+               if (!(chan = ast_get_channel_by_name_prefix_locked(chan_name, strlen(chan_name)))) {
+                       ast_cli(fd, "Invalid Channel!\n");
+                       return -1;
+               }
+               if (!strcasecmp(op, "start")) {
+                       muxmon_exec(chan, args);
+               } else if (!strcasecmp(op, "stop")) {
+                       struct ast_channel_spy *cptr=NULL;
+                       for(cptr=chan->spiers; cptr; cptr=cptr->next) {
+                               cptr->status = CHANSPY_DONE;
+                       }
+               }
+               ast_mutex_unlock(&chan->lock);
+               return 0;
+       }
+
+       ast_cli(fd, "Usage: muxmon <start|stop> <chan_name> <args>\n");
+       return -1;
+}
+
+
+static struct ast_cli_entry cli_muxmon = {
+       { "muxmon", NULL, NULL }, muxmon_cli, 
+       "Execute a monitor command", "muxmon <start|stop> <chan_name> <args>"};
+
+
+int unload_module(void)
+{
+       STANDARD_HANGUP_LOCALUSERS;
+       ast_cli_unregister(&cli_muxmon);
+       return ast_unregister_application(app);
+}
+
+int load_module(void)
+{
+       ast_cli_register(&cli_muxmon);
+       return ast_register_application(app, muxmon_exec, synopsis, desc);
+}
+
+char *description(void)
+{
+       return tdesc;
+}
+
+int usecount(void)
+{
+       int res;
+       STANDARD_USECOUNT(res);
+       return res;
+}
+
+char *key()
+{
+       return ASTERISK_GPL_KEY;
+}
+
diff --git a/apps/app_muxmon.c b/apps/app_muxmon.c
new file mode 100755 (executable)
index 0000000..852eae8
--- /dev/null
@@ -0,0 +1,528 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * muxmon Application For Asterisk
+ *
+ * Copyright (C) 2005, Anthony Minessale II
+ *
+ * Anthony Minessale II <anthmct@yahoo.com>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+#include <asterisk/file.h>
+#include <asterisk/logger.h>
+#include <asterisk/channel.h>
+#include <asterisk/pbx.h>
+#include <asterisk/module.h>
+#include <asterisk/lock.h>
+#include <asterisk/cli.h>
+#include <asterisk/options.h>
+#include <asterisk/app.h>
+#include <asterisk/translate.h>
+#include <asterisk/slinfactory.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
+#define minmax(x,y) x ? (x > y) ? y : ((x < (y * -1)) ? (y * -1) : x) : 0 
+
+AST_MUTEX_DEFINE_STATIC(modlock);
+
+static char *tdesc = "Native Channel Monitoring Module";
+static char *app = "MuxMon";
+static char *synopsis = "Record A Call Natively";
+static char *desc = ""
+"  MuxMon(<file>.<ext>[|<options>[|<command>]])\n\n"
+"Records The audio on the current channel to the specified file.\n\n"
+"Valid Options:\n"
+" b    - Only save audio to the file while the channel is bridged. Note: does\n"
+"        not include conferences\n"
+" a    - Append to the file instead of overwriting it.\n"
+" v(<x>) - Adjust the heard volume by a factor of <x> (range -4 to 4)\n"       
+" V(<x>) - Adjust the spoken volume by a factor of <x> (range -4 to 4)\n"      
+" W(<x>) - Adjust the both heard and spoken volumes by a factor of <x>\n"
+"         (range -4 to 4)\n\n" 
+"<command> will be executed when the recording is over\n"
+"Any strings matching ^{X} will be unescaped to ${X} and \n"
+"all variables will be evaluated at that time.\n"
+"The variable MUXMON_FILENAME will contain the filename used to record.\n"
+"";
+
+STANDARD_LOCAL_USER;
+
+LOCAL_USER_DECL;
+
+struct muxmon {
+       struct ast_channel *chan;
+       char *filename;
+       char *post_process;
+       unsigned int flags;
+       int readvol;
+       int writevol;
+};
+
+typedef enum {
+    MUXFLAG_RUNNING = (1 << 0),
+    MUXFLAG_APPEND = (1 << 1),
+    MUXFLAG_BRIDGED = (1 << 2),
+    MUXFLAG_VOLUME = (1 << 3),
+    MUXFLAG_READVOLUME = (1 << 4),
+    MUXFLAG_WRITEVOLUME = (1 << 5)
+} muxflags;
+
+
+AST_DECLARE_OPTIONS(muxmon_opts,{
+    ['a'] = { MUXFLAG_APPEND },
+       ['b'] = { MUXFLAG_BRIDGED },
+       ['v'] = { MUXFLAG_READVOLUME, 1 },
+       ['V'] = { MUXFLAG_WRITEVOLUME, 2 },
+       ['W'] = { MUXFLAG_VOLUME, 3 },
+});
+
+
+static void stopmon(struct ast_channel *chan, struct ast_channel_spy *spy) 
+{
+       struct ast_channel_spy *cptr=NULL, *prev=NULL;
+       int count = 0;
+
+       if (chan) {
+               while(ast_mutex_trylock(&chan->lock)) {
+                       if (chan->spiers == spy) {
+                               chan->spiers = NULL;
+                               return;
+                       }
+                       count++;
+                       if (count > 10) {
+                               return;
+                       }
+                       sched_yield();
+               }
+               
+               for(cptr=chan->spiers; cptr; cptr=cptr->next) {
+                       if (cptr == spy) {
+                               if (prev) {
+                                       prev->next = cptr->next;
+                                       cptr->next = NULL;
+                               } else
+                                       chan->spiers = NULL;
+                       }
+                       prev = cptr;
+               }
+
+               ast_mutex_unlock(&chan->lock);
+       }
+}
+
+static void startmon(struct ast_channel *chan, struct ast_channel_spy *spy) 
+{
+
+       struct ast_channel_spy *cptr=NULL;
+       struct ast_channel *peer;
+
+       if (chan) {
+               ast_mutex_lock(&chan->lock);
+               if (chan->spiers) {
+                       for(cptr=chan->spiers;cptr->next;cptr=cptr->next);
+                       cptr->next = spy;
+               } else {
+                       chan->spiers = spy;
+               }
+               ast_mutex_unlock(&chan->lock);
+               
+               if (ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan))) {
+                       ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);  
+               }
+       }
+}
+
+static int spy_queue_translate(struct ast_channel_spy *spy,
+                                                          struct ast_slinfactory *slinfactory0,
+                                                          struct ast_slinfactory *slinfactory1)
+{
+       int res = 0;
+       struct ast_frame *f;
+       
+       ast_mutex_lock(&spy->lock);
+       while((f = spy->queue[0])) {
+               spy->queue[0] = f->next;
+               ast_slinfactory_feed(slinfactory0, f);
+               ast_frfree(f);
+       }
+       ast_mutex_unlock(&spy->lock);
+       ast_mutex_lock(&spy->lock);
+       while((f = spy->queue[1])) {
+               spy->queue[1] = f->next;
+               ast_slinfactory_feed(slinfactory1, f);
+               ast_frfree(f);
+       }
+       ast_mutex_unlock(&spy->lock);
+       return res;
+}
+
+static void *muxmon_thread(void *obj) 
+{
+
+       int len0 = 0, len1 = 0, samp0 = 0, samp1 = 0, framelen, maxsamp = 0, x = 0;
+       short buf0[1280], buf1[1280], buf[1280];
+       struct ast_frame frame;
+       struct muxmon *muxmon = obj;
+       struct ast_channel_spy spy;
+       struct ast_filestream *fs = NULL;
+       char *ext, *name;
+       unsigned int oflags;
+       struct ast_slinfactory slinfactory[2];
+       char post_process[1024] = "";
+       
+       name = ast_strdupa(muxmon->chan->name);
+
+       framelen = 320;
+       frame.frametype = AST_FRAME_VOICE;
+       frame.subclass = AST_FORMAT_SLINEAR;
+       frame.data = buf;
+       ast_set_flag(muxmon, MUXFLAG_RUNNING);
+       oflags = O_CREAT|O_WRONLY;
+       ast_slinfactory_init(&slinfactory[0]);
+       ast_slinfactory_init(&slinfactory[1]);
+       
+
+
+       /* for efficiency, use a flag to bypass volume logic when it's not needed */
+       if (muxmon->readvol || muxmon->writevol) {
+               ast_set_flag(muxmon, MUXFLAG_VOLUME);
+       }
+
+       if ((ext = strchr(muxmon->filename, '.'))) {
+               *(ext++) = '\0';
+       } else {
+               ext = "raw";
+       }
+
+       memset(&spy, 0, sizeof(spy));
+       spy.status = CHANSPY_RUNNING;
+       ast_mutex_init(&spy.lock);
+       startmon(muxmon->chan, &spy);
+       if (ast_test_flag(muxmon, MUXFLAG_RUNNING)) {
+               if (option_verbose > 1) {
+                       ast_verbose(VERBOSE_PREFIX_2 "Begin Muxmon Recording %s\n", name);
+               }
+
+               oflags |= ast_test_flag(muxmon, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
+               
+               if (!(fs = ast_writefile(muxmon->filename, ext, NULL, oflags, 0, 0644))) {
+                       ast_log(LOG_ERROR, "Cannot open %s\n", muxmon->filename);
+                       spy.status = CHANSPY_DONE;
+               }  else {
+
+                       if (ast_test_flag(muxmon, MUXFLAG_APPEND)) {
+                               ast_seekstream(fs, 0, SEEK_END);
+                       }
+
+                       while (ast_test_flag(muxmon, MUXFLAG_RUNNING)) {
+                               samp0 = samp1 = len0 = len1 = 0;
+
+                               if (ast_check_hangup(muxmon->chan) || spy.status != CHANSPY_RUNNING) {
+                                       ast_clear_flag(muxmon, MUXFLAG_RUNNING);
+                                       break;
+                               }
+
+                               if (ast_test_flag(muxmon, MUXFLAG_BRIDGED) && !ast_bridged_channel(muxmon->chan)) {
+                                       usleep(1000);
+                                       sched_yield();
+                                       continue;
+                               }
+                               
+                               spy_queue_translate(&spy, &slinfactory[0], &slinfactory[1]);
+                               
+                               if (slinfactory[0].size < framelen || slinfactory[1].size < framelen) {
+                                       usleep(1000);
+                                       sched_yield();
+                                       continue;
+                               }
+
+                               if ((len0 = ast_slinfactory_read(&slinfactory[0], buf0, framelen))) {
+                                       samp0 = len0 / 2;
+                               }
+                               if((len1 = ast_slinfactory_read(&slinfactory[1], buf1, framelen))) {
+                                       samp1 = len1 / 2;
+                               }
+                               
+                               if (ast_test_flag(muxmon, MUXFLAG_VOLUME)) {
+                                       if (samp0 && muxmon->readvol > 0) {
+                                               for(x=0; x < samp0 / 2; x++) {
+                                                       buf0[x] *= muxmon->readvol;
+                                               }
+                                       } else if (samp0 && muxmon->readvol < 0) {
+                                               for(x=0; x < samp0 / 2; x++) {
+                                                       buf0[x] /= muxmon->readvol;
+                                               }
+                                       }
+                                       if (samp1 && muxmon->writevol > 0) {
+                                               for(x=0; x < samp1 / 2; x++) {
+                                                       buf1[x] *= muxmon->writevol;
+                                               }
+                                       } else if (muxmon->writevol < 0) {
+                                               for(x=0; x < samp1 / 2; x++) {
+                                                       buf1[x] /= muxmon->writevol;
+                                               }
+                                       }
+                               }
+                               
+                               maxsamp = (samp0 > samp1) ? samp0 : samp1;
+
+                               if (samp0 && samp1) {
+                                       for(x=0; x < maxsamp; x++) {
+                                               if (x < samp0 && x < samp1) {
+                                                       buf[x] = buf0[x] + buf1[x];
+                                               } else if (x < samp0) {
+                                                       buf[x] = buf0[x];
+                                               } else if (x < samp1) {
+                                                       buf[x] = buf1[x];
+                                               }
+                                       }
+                               } else if(samp0) {
+                                       memcpy(buf, buf0, len0);
+                                       x = samp0;
+                               } else if(samp1) {
+                                       memcpy(buf, buf1, len1);
+                                       x = samp1;
+                               }
+
+                               frame.samples = x;
+                               frame.datalen = x * 2;
+                               ast_writestream(fs, &frame);
+               
+                               usleep(1000);
+                               sched_yield();
+                       }
+               }
+       }
+
+       if (muxmon->post_process) {
+               char *p;
+               for(p = muxmon->post_process; *p ; p++) {
+                       if (*p == '^' && *(p+1) == '{') {
+                               *p = '$';
+                       }
+               }
+               pbx_substitute_variables_helper(muxmon->chan, muxmon->post_process, post_process, sizeof(post_process) - 1);
+               free(muxmon->post_process);
+               muxmon->post_process = NULL;
+       }
+
+       stopmon(muxmon->chan, &spy);
+       if (option_verbose > 1) {
+               ast_verbose(VERBOSE_PREFIX_2 "Finished Recording %s\n", name);
+       }
+       ast_mutex_destroy(&spy.lock);
+       
+       if(fs) {
+               ast_closestream(fs);
+       }
+       
+       ast_slinfactory_destroy(&slinfactory[0]);
+       ast_slinfactory_destroy(&slinfactory[1]);
+
+       if (muxmon) {
+               if (muxmon->filename) {
+                       free(muxmon->filename);
+               }
+               free(muxmon);
+       }
+
+       if (!ast_strlen_zero(post_process)) {
+               if (option_verbose > 2) {
+                       ast_verbose(VERBOSE_PREFIX_2 "Executing [%s]\n", post_process);
+               }
+               ast_safe_system(post_process);
+       }
+
+       return NULL;
+}
+
+static void launch_monitor_thread(struct ast_channel *chan, char *filename, unsigned int flags, int readvol , int writevol, char *post_process) 
+{
+       pthread_attr_t attr;
+       int result = 0;
+       pthread_t thread;
+       struct muxmon *muxmon;
+
+
+       if (!(muxmon = malloc(sizeof(struct muxmon)))) {
+               ast_log(LOG_ERROR, "Memory Error!\n");
+               return;
+       }
+
+       memset(muxmon, 0, sizeof(struct muxmon));
+       muxmon->chan = chan;
+       muxmon->filename = strdup(filename);
+       if(post_process) {
+               muxmon->post_process = strdup(post_process);
+       }
+       muxmon->readvol = readvol;
+       muxmon->writevol = writevol;
+       muxmon->flags = flags;
+
+       result = pthread_attr_init(&attr);
+       pthread_attr_setschedpolicy(&attr, SCHED_RR);
+       pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+       result = ast_pthread_create(&thread, &attr, muxmon_thread, muxmon);
+       result = pthread_attr_destroy(&attr);
+}
+
+
+static int muxmon_exec(struct ast_channel *chan, void *data)
+{
+       int res = 0, x = 0, readvol = 0, writevol = 0;
+       struct localuser *u;
+       struct ast_flags flags = {0};
+       int argc;
+       char *options = NULL,
+               *args,
+               *argv[3],
+               *filename = NULL,
+               *post_process = NULL;
+       
+       if (!data) {
+               ast_log(LOG_WARNING, "muxmon requires an argument\n");
+               return -1;
+       }
+
+       if (!(args = ast_strdupa(data))) {
+               ast_log(LOG_WARNING, "Memory Error!\n");
+        return -1;
+       }
+       
+
+       if ((argc = ast_separate_app_args(args, '|', argv, sizeof(argv) / sizeof(argv[0])))) {
+               filename = argv[0];
+               if ( argc > 1) {
+                       options = argv[1];
+               }
+               if ( argc > 2) {
+                       post_process = argv[2];
+               }
+       }
+       
+       if (!filename || ast_strlen_zero(filename)) {
+               ast_log(LOG_WARNING, "Muxmon requires an argument (filename)\n");
+        return -1;
+       }
+
+       LOCAL_USER_ADD(u);
+
+       if (options) {
+               char *opts[3] = {};
+               ast_parseoptions(muxmon_opts, &flags, opts, options);
+
+               if (ast_test_flag(&flags, MUXFLAG_READVOLUME) && opts[0]) {
+                       if (sscanf(opts[0], "%d", &x) != 1)
+                               ast_log(LOG_NOTICE, "volume must be a number between -4 and 4\n");
+                       else {
+                               readvol = minmax(x, 4);
+                               x = get_volfactor(readvol);
+                               readvol = minmax(x, 16);
+                       }
+               }
+               
+               if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME) && opts[1]) {
+                       if (sscanf(opts[1], "%d", &x) != 1)
+                               ast_log(LOG_NOTICE, "volume must be a number between -4 and 4\n");
+                       else {
+                               writevol = minmax(x, 4);
+                               x = get_volfactor(writevol);
+                               writevol = minmax(x, 16);
+                       }
+               }
+
+               if (ast_test_flag(&flags, MUXFLAG_VOLUME) && opts[2]) {
+                       if (sscanf(opts[2], "%d", &x) != 1)
+                               ast_log(LOG_NOTICE, "volume must be a number between -4 and 4\n");
+                       else {
+                               readvol = writevol = minmax(x, 4);
+                               x = get_volfactor(readvol);
+                readvol = minmax(x, 16);
+                               x = get_volfactor(writevol);
+                writevol = minmax(x, 16);
+                       }
+               }
+       }
+               pbx_builtin_setvar_helper(chan, "MUXMON_FILENAME", filename);
+       launch_monitor_thread(chan, filename, flags.flags, readvol, writevol, post_process);
+
+       LOCAL_USER_REMOVE(u);
+       return res;
+}
+
+
+static int muxmon_cli(int fd, int argc, char **argv) 
+{
+       char *op, *chan_name = NULL, *args = NULL;
+       struct ast_channel *chan;
+       int count = 0;
+
+       if (argc > 2) {
+               op = argv[1];
+               chan_name = argv[2];
+
+               if (argv[3]) {
+                       args = argv[3];
+               }
+
+               if (!(chan = ast_get_channel_by_name_prefix_locked(chan_name, strlen(chan_name)))) {
+                       ast_cli(fd, "Invalid Channel!\n");
+                       return -1;
+               }
+               if (!strcasecmp(op, "start")) {
+                       muxmon_exec(chan, args);
+               } else if (!strcasecmp(op, "stop")) {
+                       struct ast_channel_spy *cptr=NULL;
+                       for(cptr=chan->spiers; cptr; cptr=cptr->next) {
+                               cptr->status = CHANSPY_DONE;
+                       }
+               }
+               ast_mutex_unlock(&chan->lock);
+               return 0;
+       }
+
+       ast_cli(fd, "Usage: muxmon <start|stop> <chan_name> <args>\n");
+       return -1;
+}
+
+
+static struct ast_cli_entry cli_muxmon = {
+       { "muxmon", NULL, NULL }, muxmon_cli, 
+       "Execute a monitor command", "muxmon <start|stop> <chan_name> <args>"};
+
+
+int unload_module(void)
+{
+       STANDARD_HANGUP_LOCALUSERS;
+       ast_cli_unregister(&cli_muxmon);
+       return ast_unregister_application(app);
+}
+
+int load_module(void)
+{
+       ast_cli_register(&cli_muxmon);
+       return ast_register_application(app, muxmon_exec, synopsis, desc);
+}
+
+char *description(void)
+{
+       return tdesc;
+}
+
+int usecount(void)
+{
+       int res;
+       STANDARD_USECOUNT(res);
+       return res;
+}
+
+char *key()
+{
+       return ASTERISK_GPL_KEY;
+}
+