Sat Mar 24 23:25:59 2007

Asterisk developer's documentation


app_mixmonitor.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 2005, Anthony Minessale II
00005  * Copyright (C) 2005, Digium, Inc.
00006  *
00007  * Mark Spencer <markster@digium.com>
00008  * Kevin P. Fleming <kpfleming@digium.com>
00009  *
00010  * Based on app_muxmon.c provided by
00011  * Anthony Minessale II <anthmct@yahoo.com>
00012  *
00013  * See http://www.asterisk.org for more information about
00014  * the Asterisk project. Please do not directly contact
00015  * any of the maintainers of this project for assistance;
00016  * the project provides a web site, mailing lists and IRC
00017  * channels for your use.
00018  *
00019  * This program is free software, distributed under the terms of
00020  * the GNU General Public License Version 2. See the LICENSE file
00021  * at the top of the source tree.
00022  */
00023 
00024 /*! \file
00025  * \brief MixMonitor() - Record a call and mix the audio during the recording
00026  * \ingroup applications
00027  */
00028 
00029 #include <stdlib.h>
00030 #include <stdio.h>
00031 #include <string.h>
00032 #include <unistd.h>
00033 
00034 #include "asterisk.h"
00035 
00036 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 11778 $")
00037 
00038 #include "asterisk/file.h"
00039 #include "asterisk/logger.h"
00040 #include "asterisk/channel.h"
00041 #include "asterisk/chanspy.h"
00042 #include "asterisk/pbx.h"
00043 #include "asterisk/module.h"
00044 #include "asterisk/lock.h"
00045 #include "asterisk/cli.h"
00046 #include "asterisk/options.h"
00047 #include "asterisk/app.h"
00048 #include "asterisk/linkedlists.h"
00049 
00050 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
00051 
00052 static const char *tdesc = "Mixed Audio Monitoring Application";
00053 static const char *app = "MixMonitor";
00054 static const char *synopsis = "Record a call and mix the audio during the recording";
00055 static const char *desc = ""
00056 "  MixMonitor(<file>.<ext>[|<options>[|<command>]])\n\n"
00057 "Records the audio on the current channel to the specified file.\n"
00058 "If the filename is an absolute path, uses that path, otherwise\n"
00059 "creates the file in the configured monitoring directory from\n"
00060 "asterisk.conf.\n\n"
00061 "Valid options:\n"
00062 " a      - Append to the file instead of overwriting it.\n"
00063 " b      - Only save audio to the file while the channel is bridged.\n"
00064 "          Note: does not include conferences.\n"
00065 " v(<x>) - Adjust the heard volume by a factor of <x> (range -4 to 4)\n"   
00066 " V(<x>) - Adjust the spoken volume by a factor of <x> (range -4 to 4)\n"  
00067 " W(<x>) - Adjust the both heard and spoken volumes by a factor of <x>\n"
00068 "         (range -4 to 4)\n\n"   
00069 "<command> will be executed when the recording is over\n"
00070 "Any strings matching ^{X} will be unescaped to ${X} and \n"
00071 "all variables will be evaluated at that time.\n"
00072 "The variable MIXMONITOR_FILENAME will contain the filename used to record.\n"
00073 "";
00074 
00075 STANDARD_LOCAL_USER;
00076 
00077 LOCAL_USER_DECL;
00078 
00079 static const char *mixmonitor_spy_type = "MixMonitor";
00080 
00081 struct mixmonitor {
00082    struct ast_channel *chan;
00083    char *filename;
00084    char *post_process;
00085    unsigned int flags;
00086    int readvol;
00087    int writevol;
00088 };
00089 
00090 enum {
00091    MUXFLAG_APPEND = (1 << 1),
00092    MUXFLAG_BRIDGED = (1 << 2),
00093    MUXFLAG_VOLUME = (1 << 3),
00094    MUXFLAG_READVOLUME = (1 << 4),
00095    MUXFLAG_WRITEVOLUME = (1 << 5),
00096 } mixmonitor_flags;
00097 
00098 enum {
00099    OPT_ARG_READVOLUME = 0,
00100    OPT_ARG_WRITEVOLUME,
00101    OPT_ARG_VOLUME,
00102    OPT_ARG_ARRAY_SIZE,
00103 } mixmonitor_args;
00104 
00105 AST_APP_OPTIONS(mixmonitor_opts, {
00106    AST_APP_OPTION('a', MUXFLAG_APPEND),
00107    AST_APP_OPTION('b', MUXFLAG_BRIDGED),
00108    AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
00109    AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
00110    AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
00111 });
00112 
00113 static void stopmon(struct ast_channel *chan, struct ast_channel_spy *spy) 
00114 {
00115    /* If our status has changed to DONE, then the channel we're spying on is gone....
00116       DON'T TOUCH IT!!!  RUN AWAY!!! */
00117    if (spy->status == CHANSPY_DONE)
00118       return;
00119 
00120    if (!chan)
00121       return;
00122 
00123    ast_mutex_lock(&chan->lock);
00124    ast_channel_spy_remove(chan, spy);
00125    ast_mutex_unlock(&chan->lock);
00126 }
00127 
00128 static int startmon(struct ast_channel *chan, struct ast_channel_spy *spy) 
00129 {
00130    struct ast_channel *peer;
00131    int res;
00132 
00133    if (!chan)
00134       return -1;
00135 
00136    ast_mutex_lock(&chan->lock);
00137    res = ast_channel_spy_add(chan, spy);
00138    ast_mutex_unlock(&chan->lock);
00139       
00140    if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
00141       ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);  
00142 
00143    return res;
00144 }
00145 
00146 #define SAMPLES_PER_FRAME 160
00147 
00148 static void *mixmonitor_thread(void *obj) 
00149 {
00150    struct mixmonitor *mixmonitor = obj;
00151    struct ast_channel_spy spy;
00152    struct ast_filestream *fs = NULL;
00153    char *ext, *name;
00154    unsigned int oflags;
00155    struct ast_frame *f;
00156    char post_process[1024] = "";
00157    
00158    STANDARD_INCREMENT_USECOUNT;
00159 
00160    name = ast_strdupa(mixmonitor->chan->name);
00161 
00162    oflags = O_CREAT|O_WRONLY;
00163    oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
00164       
00165    if ((ext = strrchr(mixmonitor->filename, '.'))) {
00166       *(ext++) = '\0';
00167    } else {
00168       ext = "raw";
00169    }
00170 
00171    fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0644);
00172    if (!fs) {
00173       ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
00174       goto out;
00175    }
00176 
00177    if (ast_test_flag(mixmonitor, MUXFLAG_APPEND))
00178       ast_seekstream(fs, 0, SEEK_END);
00179    
00180    memset(&spy, 0, sizeof(spy));
00181    ast_set_flag(&spy, CHANSPY_FORMAT_AUDIO);
00182    ast_set_flag(&spy, CHANSPY_MIXAUDIO);
00183    spy.type = mixmonitor_spy_type;
00184    spy.status = CHANSPY_RUNNING;
00185    spy.read_queue.format = AST_FORMAT_SLINEAR;
00186    spy.write_queue.format = AST_FORMAT_SLINEAR;
00187    if (mixmonitor->readvol) {
00188       ast_set_flag(&spy, CHANSPY_READ_VOLADJUST);
00189       spy.read_vol_adjustment = mixmonitor->readvol;
00190    }
00191    if (mixmonitor->writevol) {
00192       ast_set_flag(&spy, CHANSPY_WRITE_VOLADJUST);
00193       spy.write_vol_adjustment = mixmonitor->writevol;
00194    }
00195    ast_mutex_init(&spy.lock);
00196 
00197    if (startmon(mixmonitor->chan, &spy)) {
00198       ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
00199          spy.type, mixmonitor->chan->name);
00200       goto out2;
00201    }
00202 
00203    if (option_verbose > 1)
00204       ast_verbose(VERBOSE_PREFIX_2 "Begin MixMonitor Recording %s\n", name);
00205    
00206    if (mixmonitor->post_process) {
00207       char *p;
00208 
00209       for (p = mixmonitor->post_process; *p ; p++) {
00210          if (*p == '^' && *(p+1) == '{') {
00211             *p = '$';
00212          }
00213       }
00214       pbx_substitute_variables_helper(mixmonitor->chan, mixmonitor->post_process, post_process, sizeof(post_process) - 1);
00215    }
00216 
00217    while (1) {
00218       struct ast_frame *next;
00219       int write;
00220 
00221       ast_mutex_lock(&spy.lock);
00222 
00223       ast_channel_spy_trigger_wait(&spy);
00224       
00225       if (ast_check_hangup(mixmonitor->chan) || spy.status != CHANSPY_RUNNING) {
00226          ast_mutex_unlock(&spy.lock);
00227          break;
00228       }
00229       
00230       while (1) {
00231          if (!(f = ast_channel_spy_read_frame(&spy, SAMPLES_PER_FRAME)))
00232             break;
00233 
00234          write = (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) ||
00235              ast_bridged_channel(mixmonitor->chan));
00236 
00237          /* it is possible for ast_channel_spy_read_frame() to return a chain
00238             of frames if a queue flush was necessary, so process them
00239          */
00240          for (; f; f = next) {
00241             next = f->next;
00242             if (write)
00243                ast_writestream(fs, f);
00244             ast_frfree(f);
00245          }
00246       }
00247 
00248       ast_mutex_unlock(&spy.lock);
00249    }
00250    
00251    stopmon(mixmonitor->chan, &spy);
00252 
00253    if (option_verbose > 1)
00254       ast_verbose(VERBOSE_PREFIX_2 "End MixMonitor Recording %s\n", name);
00255 
00256    if (!ast_strlen_zero(post_process)) {
00257       if (option_verbose > 2)
00258          ast_verbose(VERBOSE_PREFIX_2 "Executing [%s]\n", post_process);
00259       ast_safe_system(post_process);
00260    }
00261 
00262 out2:
00263    ast_mutex_destroy(&spy.lock);
00264 
00265    if (fs)
00266       ast_closestream(fs);
00267 
00268 out:
00269    free(mixmonitor);
00270 
00271    STANDARD_DECREMENT_USECOUNT;
00272 
00273    return NULL;
00274 }
00275 
00276 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
00277               int readvol, int writevol, const char *post_process) 
00278 {
00279    pthread_attr_t attr;
00280    pthread_t thread;
00281    struct mixmonitor *mixmonitor;
00282    int len;
00283 
00284    len = sizeof(*mixmonitor) + strlen(filename) + 1;
00285    if (!ast_strlen_zero(post_process))
00286       len += strlen(post_process) + 1;
00287 
00288    if (!(mixmonitor = calloc(1, len))) {
00289       ast_log(LOG_ERROR, "Memory Error!\n");
00290       return;
00291    }
00292 
00293    mixmonitor->chan = chan;
00294    mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor);
00295    strcpy(mixmonitor->filename, filename);
00296    if (!ast_strlen_zero(post_process)) {
00297       mixmonitor->post_process = mixmonitor->filename + strlen(filename) + 1;
00298       strcpy(mixmonitor->post_process, post_process);
00299    }
00300    mixmonitor->readvol = readvol;
00301    mixmonitor->writevol = writevol;
00302    mixmonitor->flags = flags;
00303 
00304    pthread_attr_init(&attr);
00305    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
00306    ast_pthread_create(&thread, &attr, mixmonitor_thread, mixmonitor);
00307    pthread_attr_destroy(&attr);
00308 }
00309 
00310 static int mixmonitor_exec(struct ast_channel *chan, void *data)
00311 {
00312    int x, readvol = 0, writevol = 0;
00313    struct localuser *u;
00314    struct ast_flags flags = {0};
00315    char *parse;
00316    AST_DECLARE_APP_ARGS(args,
00317       AST_APP_ARG(filename);
00318       AST_APP_ARG(options);
00319       AST_APP_ARG(post_process);
00320    );
00321    
00322    if (ast_strlen_zero(data)) {
00323       ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00324       return -1;
00325    }
00326 
00327    LOCAL_USER_ADD(u);
00328 
00329    if (!(parse = ast_strdupa(data))) {
00330       ast_log(LOG_WARNING, "Memory Error!\n");
00331       LOCAL_USER_REMOVE(u);
00332       return -1;
00333    }
00334 
00335    AST_STANDARD_APP_ARGS(args, parse);
00336    
00337    if (ast_strlen_zero(args.filename)) {
00338       ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00339       LOCAL_USER_REMOVE(u);
00340       return -1;
00341    }
00342 
00343    if (args.options) {
00344       char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
00345 
00346       ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
00347 
00348       if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
00349          if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
00350             ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
00351          } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00352             ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
00353          } else {
00354             readvol = get_volfactor(x);
00355          }
00356       }
00357       
00358       if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
00359          if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
00360             ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
00361          } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00362             ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
00363          } else {
00364             writevol = get_volfactor(x);
00365          }
00366       }
00367       
00368       if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
00369          if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
00370             ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
00371          } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00372             ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
00373          } else {
00374             readvol = writevol = get_volfactor(x);
00375          }
00376       }
00377    }
00378 
00379    /* if not provided an absolute path, use the system-configured monitoring directory */
00380    if (args.filename[0] != '/') {
00381       char *build;
00382 
00383       build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
00384       sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
00385       args.filename = build;
00386    }
00387 
00388    pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
00389    launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
00390 
00391    LOCAL_USER_REMOVE(u);
00392 
00393    return 0;
00394 }
00395 
00396 static int mixmonitor_cli(int fd, int argc, char **argv) 
00397 {
00398    struct ast_channel *chan;
00399 
00400    if (argc < 3)
00401       return RESULT_SHOWUSAGE;
00402 
00403    if (!(chan = ast_get_channel_by_name_prefix_locked(argv[2], strlen(argv[2])))) {
00404       ast_cli(fd, "No channel matching '%s' found.\n", argv[2]);
00405       return RESULT_SUCCESS;
00406    }
00407 
00408    if (!strcasecmp(argv[1], "start"))
00409       mixmonitor_exec(chan, argv[3]);
00410    else if (!strcasecmp(argv[1], "stop"))
00411       ast_channel_spy_stop_by_type(chan, mixmonitor_spy_type);
00412 
00413    ast_mutex_unlock(&chan->lock);
00414 
00415    return RESULT_SUCCESS;
00416 }
00417 
00418 
00419 static struct ast_cli_entry cli_mixmonitor = {
00420    { "mixmonitor", NULL, NULL },
00421    mixmonitor_cli, 
00422    "Execute a MixMonitor command",
00423    "mixmonitor <start|stop> <chan_name> [<args>]\n"
00424 };
00425 
00426 
00427 int unload_module(void)
00428 {
00429    int res;
00430 
00431    res = ast_cli_unregister(&cli_mixmonitor);
00432    res |= ast_unregister_application(app);
00433    
00434    STANDARD_HANGUP_LOCALUSERS;
00435 
00436    return res;
00437 }
00438 
00439 int load_module(void)
00440 {
00441    int res;
00442 
00443    res = ast_cli_register(&cli_mixmonitor);
00444    res |= ast_register_application(app, mixmonitor_exec, synopsis, desc);
00445 
00446    return res;
00447 }
00448 
00449 char *description(void)
00450 {
00451    return (char *) tdesc;
00452 }
00453 
00454 int usecount(void)
00455 {
00456    int res;
00457 
00458    STANDARD_USECOUNT(res);
00459 
00460    return res;
00461 }
00462 
00463 char *key()
00464 {
00465    return ASTERISK_GPL_KEY;
00466 }

Generated on Sat Mar 24 23:25:59 2007 for Asterisk - the Open Source PBX by  doxygen 1.4.6