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

Generated on Thu May 14 14:48:44 2009 for Asterisk - the Open Source PBX by  doxygen 1.4.7