Fri Jun 19 12:09:26 2009

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 - 2006, 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  *
00026  * \brief MixMonitor() - Record a call and mix the audio during the recording
00027  * \ingroup applications
00028  *
00029  * \author Mark Spencer <markster@digium.com>
00030  * \author Kevin P. Fleming <kpfleming@digium.com>
00031  *
00032  * \note Based on app_muxmon.c provided by
00033  * Anthony Minessale II <anthmct@yahoo.com>
00034  */
00035 
00036 #include "asterisk.h"
00037 
00038 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 173595 $")
00039 
00040 #include "asterisk/paths.h"   /* use ast_config_AST_MONITOR_DIR */
00041 #include "asterisk/file.h"
00042 #include "asterisk/audiohook.h"
00043 #include "asterisk/pbx.h"
00044 #include "asterisk/module.h"
00045 #include "asterisk/cli.h"
00046 #include "asterisk/app.h"
00047 #include "asterisk/channel.h"
00048 
00049 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
00050 
00051 static const char *app = "MixMonitor";
00052 static const char *synopsis = "Record a call and mix the audio during the recording";
00053 static const char *desc = ""
00054 "  MixMonitor(<file>.<ext>[,<options>[,<command>]]):\n"
00055 "Records the audio on the current channel to the specified file.\n"
00056 "If the filename is an absolute path, uses that path, otherwise\n"
00057 "creates the file in the configured monitoring directory from\n"
00058 "asterisk.conf.\n\n"
00059 "Valid options:\n"
00060 " a      - Append to the file instead of overwriting it.\n"
00061 " b      - Only save audio to the file while the channel is bridged.\n"
00062 "          Note: Does not include conferences or sounds played to each bridged\n"
00063 "                party.\n"
00064 " v(<x>) - Adjust the heard volume by a factor of <x> (range -4 to 4)\n"   
00065 " V(<x>) - Adjust the spoken volume by a factor of <x> (range -4 to 4)\n"  
00066 " W(<x>) - Adjust the both heard and spoken volumes by a factor of <x>\n"
00067 "         (range -4 to 4)\n\n"   
00068 "<command> will be executed when the recording is over\n"
00069 "Any strings matching ^{X} will be unescaped to ${X}.\n"
00070 "All variables will be evaluated at the time MixMonitor is called.\n"
00071 "The variable MIXMONITOR_FILENAME will contain the filename used to record.\n"
00072 "";
00073 
00074 static const char *stop_app = "StopMixMonitor";
00075 static const char *stop_synopsis = "Stop recording a call through MixMonitor";
00076 static const char *stop_desc = ""
00077 "  StopMixMonitor():\n"
00078 "Stops the audio recording that was started with a call to MixMonitor()\n"
00079 "on the current channel.\n"
00080 "";
00081 
00082 struct module_symbols *me;
00083 
00084 static const char *mixmonitor_spy_type = "MixMonitor";
00085 
00086 struct mixmonitor {
00087    struct ast_audiohook audiohook;
00088    char *filename;
00089    char *post_process;
00090    char *name;
00091    unsigned int flags;
00092    struct mixmonitor_ds *mixmonitor_ds;
00093 };
00094 
00095 enum {
00096    MUXFLAG_APPEND = (1 << 1),
00097    MUXFLAG_BRIDGED = (1 << 2),
00098    MUXFLAG_VOLUME = (1 << 3),
00099    MUXFLAG_READVOLUME = (1 << 4),
00100    MUXFLAG_WRITEVOLUME = (1 << 5),
00101 } mixmonitor_flags;
00102 
00103 enum {
00104    OPT_ARG_READVOLUME = 0,
00105    OPT_ARG_WRITEVOLUME,
00106    OPT_ARG_VOLUME,
00107    OPT_ARG_ARRAY_SIZE,
00108 } mixmonitor_args;
00109 
00110 AST_APP_OPTIONS(mixmonitor_opts, {
00111    AST_APP_OPTION('a', MUXFLAG_APPEND),
00112    AST_APP_OPTION('b', MUXFLAG_BRIDGED),
00113    AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
00114    AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
00115    AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
00116 });
00117 
00118 /* This structure is used as a means of making sure that our pointer to
00119  * the channel we are monitoring remains valid. This is very similar to 
00120  * what is used in app_chanspy.c.
00121  */
00122 struct mixmonitor_ds {
00123    struct ast_channel *chan;
00124    /* These condition variables are used to be sure that the channel
00125     * hangup code completes before the mixmonitor thread attempts to
00126     * free this structure. The combination of a bookean flag and a
00127     * ast_cond_t ensure that no matter what order the threads run in,
00128     * we are guaranteed to never have the waiting thread block forever
00129     * in the case that the signaling thread runs first.
00130     */
00131    unsigned int destruction_ok;
00132    ast_cond_t destruction_condition;
00133    ast_mutex_t lock;
00134 };
00135 
00136 static void mixmonitor_ds_destroy(void *data)
00137 {
00138    struct mixmonitor_ds *mixmonitor_ds = data;
00139 
00140    ast_mutex_lock(&mixmonitor_ds->lock);
00141    mixmonitor_ds->chan = NULL;
00142    mixmonitor_ds->destruction_ok = 1;
00143    ast_cond_signal(&mixmonitor_ds->destruction_condition);
00144    ast_mutex_unlock(&mixmonitor_ds->lock);
00145 }
00146 
00147 static void mixmonitor_ds_chan_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
00148 {
00149    struct mixmonitor_ds *mixmonitor_ds = data;
00150 
00151    ast_mutex_lock(&mixmonitor_ds->lock);
00152    mixmonitor_ds->chan = new_chan;
00153    ast_mutex_unlock(&mixmonitor_ds->lock);
00154 }
00155 
00156 static struct ast_datastore_info mixmonitor_ds_info = {
00157    .type = "mixmonitor",
00158    .destroy = mixmonitor_ds_destroy,
00159    .chan_fixup = mixmonitor_ds_chan_fixup,
00160 };
00161 
00162 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook) 
00163 {
00164    struct ast_channel *peer = NULL;
00165    int res = 0;
00166 
00167    if (!chan)
00168       return -1;
00169 
00170    ast_audiohook_attach(chan, audiohook);
00171 
00172    if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
00173       ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);  
00174 
00175    return res;
00176 }
00177 
00178 #define SAMPLES_PER_FRAME 160
00179 
00180 static void *mixmonitor_thread(void *obj) 
00181 {
00182    struct mixmonitor *mixmonitor = obj;
00183    struct ast_filestream *fs = NULL;
00184    unsigned int oflags;
00185    char *ext;
00186    int errflag = 0;
00187 
00188    ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name);
00189    
00190    ast_audiohook_lock(&mixmonitor->audiohook);
00191 
00192    while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
00193       struct ast_frame *fr = NULL;
00194 
00195       ast_audiohook_trigger_wait(&mixmonitor->audiohook);
00196 
00197       if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING)
00198          break;
00199 
00200       if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR)))
00201          continue;
00202 
00203       ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
00204       if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || (mixmonitor->mixmonitor_ds->chan && ast_bridged_channel(mixmonitor->mixmonitor_ds->chan))) {
00205          ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
00206          /* Initialize the file if not already done so */
00207          if (!fs && !errflag) {
00208             oflags = O_CREAT | O_WRONLY;
00209             oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
00210             
00211             if ((ext = strrchr(mixmonitor->filename, '.')))
00212                *(ext++) = '\0';
00213             else
00214                ext = "raw";
00215             
00216             if (!(fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0666))) {
00217                ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
00218                errflag = 1;
00219             }
00220          }
00221          
00222          /* Write out frame */
00223          if (fs)
00224             ast_writestream(fs, fr);
00225       } else {
00226          ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
00227       }
00228 
00229       /* All done! free it. */
00230       ast_frame_free(fr, 0);
00231 
00232    }
00233 
00234    ast_audiohook_detach(&mixmonitor->audiohook);
00235    ast_audiohook_unlock(&mixmonitor->audiohook);
00236    ast_audiohook_destroy(&mixmonitor->audiohook);
00237 
00238    ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
00239 
00240    if (fs)
00241       ast_closestream(fs);
00242 
00243    if (mixmonitor->post_process) {
00244       ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
00245       ast_safe_system(mixmonitor->post_process);
00246    }
00247 
00248    ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
00249    if (!mixmonitor->mixmonitor_ds->destruction_ok) {
00250       ast_cond_wait(&mixmonitor->mixmonitor_ds->destruction_condition, &mixmonitor->mixmonitor_ds->lock);
00251    }
00252    ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
00253    ast_mutex_destroy(&mixmonitor->mixmonitor_ds->lock);
00254    ast_cond_destroy(&mixmonitor->mixmonitor_ds->destruction_condition);
00255    ast_free(mixmonitor->mixmonitor_ds);
00256    ast_free(mixmonitor);
00257 
00258    return NULL;
00259 }
00260 
00261 static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan)
00262 {
00263    struct ast_datastore *datastore = NULL;
00264    struct mixmonitor_ds *mixmonitor_ds;
00265 
00266    if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
00267       return -1;
00268    }
00269    
00270    ast_mutex_init(&mixmonitor_ds->lock);
00271    ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
00272 
00273    if (!(datastore = ast_datastore_alloc(&mixmonitor_ds_info, NULL))) {
00274       ast_free(mixmonitor_ds);
00275       return -1;
00276    }
00277 
00278    /* No need to lock mixmonitor_ds since this is still operating in the channel's thread */
00279    mixmonitor_ds->chan = chan;
00280    datastore->data = mixmonitor_ds;
00281 
00282    ast_channel_lock(chan);
00283    ast_channel_datastore_add(chan, datastore);
00284    ast_channel_unlock(chan);
00285 
00286    mixmonitor->mixmonitor_ds = mixmonitor_ds;
00287    return 0;
00288 }
00289 
00290 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
00291               int readvol, int writevol, const char *post_process) 
00292 {
00293    pthread_t thread;
00294    struct mixmonitor *mixmonitor;
00295    char postprocess2[1024] = "";
00296    size_t len;
00297 
00298    len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
00299 
00300    postprocess2[0] = 0;
00301    /* If a post process system command is given attach it to the structure */
00302    if (!ast_strlen_zero(post_process)) {
00303       char *p1, *p2;
00304 
00305       p1 = ast_strdupa(post_process);
00306       for (p2 = p1; *p2 ; p2++) {
00307          if (*p2 == '^' && *(p2+1) == '{') {
00308             *p2 = '$';
00309          }
00310       }
00311       pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
00312       if (!ast_strlen_zero(postprocess2))
00313          len += strlen(postprocess2) + 1;
00314    }
00315 
00316    /* Pre-allocate mixmonitor structure and spy */
00317    if (!(mixmonitor = ast_calloc(1, len))) {
00318       return;
00319    }
00320 
00321    /* Copy over flags and channel name */
00322    mixmonitor->flags = flags;
00323    if (setup_mixmonitor_ds(mixmonitor, chan)) {
00324       return;
00325    }
00326    mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
00327    strcpy(mixmonitor->name, chan->name);
00328    if (!ast_strlen_zero(postprocess2)) {
00329       mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
00330       strcpy(mixmonitor->post_process, postprocess2);
00331    }
00332 
00333    mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
00334    strcpy(mixmonitor->filename, filename);
00335 
00336    /* Setup the actual spy before creating our thread */
00337    if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
00338       ast_free(mixmonitor);
00339       return;
00340    }
00341 
00342    ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
00343 
00344    if (readvol)
00345       mixmonitor->audiohook.options.read_volume = readvol;
00346    if (writevol)
00347       mixmonitor->audiohook.options.write_volume = writevol;
00348 
00349    if (startmon(chan, &mixmonitor->audiohook)) {
00350       ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
00351          mixmonitor_spy_type, chan->name);
00352       ast_audiohook_destroy(&mixmonitor->audiohook);
00353       ast_free(mixmonitor);
00354       return;
00355    }
00356 
00357    ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
00358 }
00359 
00360 static int mixmonitor_exec(struct ast_channel *chan, void *data)
00361 {
00362    int x, readvol = 0, writevol = 0;
00363    struct ast_flags flags = {0};
00364    char *parse, *tmp, *slash;
00365    AST_DECLARE_APP_ARGS(args,
00366       AST_APP_ARG(filename);
00367       AST_APP_ARG(options);
00368       AST_APP_ARG(post_process);
00369    );
00370    
00371    if (ast_strlen_zero(data)) {
00372       ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00373       return -1;
00374    }
00375 
00376    parse = ast_strdupa(data);
00377 
00378    AST_STANDARD_APP_ARGS(args, parse);
00379    
00380    if (ast_strlen_zero(args.filename)) {
00381       ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00382       return -1;
00383    }
00384 
00385    if (args.options) {
00386       char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
00387 
00388       ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
00389 
00390       if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
00391          if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
00392             ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
00393          } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00394             ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
00395          } else {
00396             readvol = get_volfactor(x);
00397          }
00398       }
00399       
00400       if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
00401          if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
00402             ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
00403          } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00404             ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
00405          } else {
00406             writevol = get_volfactor(x);
00407          }
00408       }
00409       
00410       if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
00411          if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
00412             ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
00413          } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00414             ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
00415          } else {
00416             readvol = writevol = get_volfactor(x);
00417          }
00418       }
00419    }
00420 
00421    /* if not provided an absolute path, use the system-configured monitoring directory */
00422    if (args.filename[0] != '/') {
00423       char *build;
00424 
00425       build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
00426       sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
00427       args.filename = build;
00428    }
00429 
00430    tmp = ast_strdupa(args.filename);
00431    if ((slash = strrchr(tmp, '/')))
00432       *slash = '\0';
00433    ast_mkdir(tmp, 0777);
00434 
00435    pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
00436    launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
00437 
00438    return 0;
00439 }
00440 
00441 static int stop_mixmonitor_exec(struct ast_channel *chan, void *data)
00442 {
00443    ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00444    return 0;
00445 }
00446 
00447 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00448 {
00449    struct ast_channel *chan;
00450 
00451    switch (cmd) {
00452    case CLI_INIT:
00453       e->command = "mixmonitor [start|stop]";
00454       e->usage =
00455          "Usage: mixmonitor <start|stop> <chan_name> [args]\n"
00456          "       The optional arguments are passed to the MixMonitor\n"
00457          "       application when the 'start' command is used.\n";
00458       return NULL;
00459    case CLI_GENERATE:
00460       return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
00461    }
00462 
00463    if (a->argc < 3)
00464       return CLI_SHOWUSAGE;
00465 
00466    if (!(chan = ast_get_channel_by_name_prefix_locked(a->argv[2], strlen(a->argv[2])))) {
00467       ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
00468       /* Technically this is a failure, but we don't want 2 errors printing out */
00469       return CLI_SUCCESS;
00470    }
00471 
00472    if (!strcasecmp(a->argv[1], "start")) {
00473       mixmonitor_exec(chan, a->argv[3]);
00474       ast_channel_unlock(chan);
00475    } else {
00476       ast_channel_unlock(chan);
00477       ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00478    }
00479 
00480    return CLI_SUCCESS;
00481 }
00482 
00483 static struct ast_cli_entry cli_mixmonitor[] = {
00484    AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
00485 };
00486 
00487 static int unload_module(void)
00488 {
00489    int res;
00490 
00491    ast_cli_unregister_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
00492    res = ast_unregister_application(stop_app);
00493    res |= ast_unregister_application(app);
00494    
00495    return res;
00496 }
00497 
00498 static int load_module(void)
00499 {
00500    int res;
00501 
00502    ast_cli_register_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
00503    res = ast_register_application(app, mixmonitor_exec, synopsis, desc);
00504    res |= ast_register_application(stop_app, stop_mixmonitor_exec, stop_synopsis, stop_desc);
00505 
00506    return res;
00507 }
00508 
00509 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");

Generated on Fri Jun 19 12:09:26 2009 for Asterisk - the Open Source PBX by  doxygen 1.4.7