Thu Oct 8 21:55:53 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: 108083 $")
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 ast_channel *chan;
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 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook) 
00128 {
00129    struct ast_channel *peer;
00130    int res;
00131 
00132    if (!chan)
00133       return -1;
00134 
00135    res = ast_audiohook_attach(chan, audiohook);
00136 
00137    if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
00138       ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);  
00139 
00140    return res;
00141 }
00142 
00143 #define SAMPLES_PER_FRAME 160
00144 
00145 static void *mixmonitor_thread(void *obj) 
00146 {
00147    struct mixmonitor *mixmonitor = obj;
00148    struct ast_filestream *fs = NULL;
00149    unsigned int oflags;
00150    char *ext;
00151    int errflag = 0;
00152 
00153    if (option_verbose > 1)
00154       ast_verbose(VERBOSE_PREFIX_2 "Begin MixMonitor Recording %s\n", mixmonitor->name);
00155    
00156    ast_audiohook_lock(&mixmonitor->audiohook);
00157 
00158    while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
00159       struct ast_frame *fr = NULL;
00160       
00161       ast_audiohook_trigger_wait(&mixmonitor->audiohook);
00162       
00163       if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING)
00164          break;
00165       
00166       if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR)))
00167          continue;
00168       
00169       if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || ast_bridged_channel(mixmonitor->chan)) {
00170          /* Initialize the file if not already done so */
00171          if (!fs && !errflag) {
00172             oflags = O_CREAT | O_WRONLY;
00173             oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
00174             
00175             if ((ext = strrchr(mixmonitor->filename, '.')))
00176                *(ext++) = '\0';
00177             else
00178                ext = "raw";
00179             
00180             if (!(fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0644))) {
00181                ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
00182                errflag = 1;
00183             }
00184          }
00185 
00186          /* Write out the frame */
00187          if (fs)
00188             ast_writestream(fs, fr);
00189       }
00190 
00191       /* All done! free it. */
00192       ast_frame_free(fr, 0);
00193    }
00194 
00195    ast_audiohook_detach(&mixmonitor->audiohook);
00196    ast_audiohook_unlock(&mixmonitor->audiohook);
00197    ast_audiohook_destroy(&mixmonitor->audiohook);
00198    
00199    if (option_verbose > 1)
00200       ast_verbose(VERBOSE_PREFIX_2 "End MixMonitor Recording %s\n", mixmonitor->name);
00201 
00202    if (fs)
00203       ast_closestream(fs);
00204 
00205    if (mixmonitor->post_process) {
00206       if (option_verbose > 2)
00207          ast_verbose(VERBOSE_PREFIX_2 "Executing [%s]\n", mixmonitor->post_process);
00208       ast_safe_system(mixmonitor->post_process);
00209    }
00210 
00211    free(mixmonitor);
00212 
00213 
00214    return NULL;
00215 }
00216 
00217 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
00218               int readvol, int writevol, const char *post_process) 
00219 {
00220    pthread_attr_t attr;
00221    pthread_t thread;
00222    struct mixmonitor *mixmonitor;
00223    char postprocess2[1024] = "";
00224    size_t len;
00225 
00226    len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
00227 
00228    /* If a post process system command is given attach it to the structure */
00229    if (!ast_strlen_zero(post_process)) {
00230       char *p1, *p2;
00231 
00232       p1 = ast_strdupa(post_process);
00233       for (p2 = p1; *p2 ; p2++) {
00234          if (*p2 == '^' && *(p2+1) == '{') {
00235             *p2 = '$';
00236          }
00237       }
00238 
00239       pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
00240       if (!ast_strlen_zero(postprocess2))
00241          len += strlen(postprocess2) + 1;
00242    }
00243 
00244    /* Pre-allocate mixmonitor structure and spy */
00245    if (!(mixmonitor = calloc(1, len))) {
00246       return;
00247    }
00248 
00249    /* Copy over flags and channel name */
00250    mixmonitor->flags = flags;
00251    mixmonitor->chan = chan;
00252    mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
00253    strcpy(mixmonitor->name, chan->name);
00254    if (!ast_strlen_zero(postprocess2)) {
00255       mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
00256       strcpy(mixmonitor->post_process, postprocess2);
00257    }
00258 
00259    mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
00260    strcpy(mixmonitor->filename, filename);
00261 
00262    /* Setup the actual spy before creating our thread */
00263    if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
00264       free(mixmonitor);
00265       return;
00266    }
00267    
00268    ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
00269    
00270    if (readvol)
00271       mixmonitor->audiohook.options.read_volume = readvol;
00272    if (writevol)
00273       mixmonitor->audiohook.options.write_volume = writevol;
00274 
00275    if (startmon(chan, &mixmonitor->audiohook)) {
00276       ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
00277          mixmonitor_spy_type, chan->name);
00278       /* Since we couldn't add ourselves - bail out! */
00279       ast_audiohook_destroy(&mixmonitor->audiohook);
00280       free(mixmonitor);
00281       return;
00282    }
00283 
00284    pthread_attr_init(&attr);
00285    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
00286    ast_pthread_create_background(&thread, &attr, mixmonitor_thread, mixmonitor);
00287    pthread_attr_destroy(&attr);
00288 
00289 }
00290 
00291 static int mixmonitor_exec(struct ast_channel *chan, void *data)
00292 {
00293    int x, readvol = 0, writevol = 0;
00294    struct ast_module_user *u;
00295    struct ast_flags flags = {0};
00296    char *parse;
00297    AST_DECLARE_APP_ARGS(args,
00298       AST_APP_ARG(filename);
00299       AST_APP_ARG(options);
00300       AST_APP_ARG(post_process);
00301    );
00302    
00303    if (ast_strlen_zero(data)) {
00304       ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00305       return -1;
00306    }
00307 
00308    u = ast_module_user_add(chan);
00309 
00310    parse = ast_strdupa(data);
00311 
00312    AST_STANDARD_APP_ARGS(args, parse);
00313    
00314    if (ast_strlen_zero(args.filename)) {
00315       ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00316       ast_module_user_remove(u);
00317       return -1;
00318    }
00319 
00320    if (args.options) {
00321       char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
00322 
00323       ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
00324 
00325       if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
00326          if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
00327             ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
00328          } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00329             ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
00330          } else {
00331             readvol = get_volfactor(x);
00332          }
00333       }
00334       
00335       if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
00336          if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
00337             ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
00338          } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00339             ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
00340          } else {
00341             writevol = get_volfactor(x);
00342          }
00343       }
00344       
00345       if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
00346          if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
00347             ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
00348          } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00349             ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
00350          } else {
00351             readvol = writevol = get_volfactor(x);
00352          }
00353       }
00354    }
00355 
00356    /* if not provided an absolute path, use the system-configured monitoring directory */
00357    if (args.filename[0] != '/') {
00358       char *build;
00359 
00360       build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
00361       sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
00362       args.filename = build;
00363    }
00364 
00365    pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
00366    launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
00367 
00368    ast_module_user_remove(u);
00369 
00370    return 0;
00371 }
00372 
00373 static int stop_mixmonitor_exec(struct ast_channel *chan, void *data)
00374 {
00375    struct ast_module_user *u;
00376 
00377    u = ast_module_user_add(chan);
00378 
00379    ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00380 
00381    ast_module_user_remove(u);
00382 
00383    return 0;
00384 }
00385 
00386 static int mixmonitor_cli(int fd, int argc, char **argv) 
00387 {
00388    struct ast_channel *chan;
00389 
00390    if (argc < 3)
00391       return RESULT_SHOWUSAGE;
00392 
00393    if (!(chan = ast_get_channel_by_name_prefix_locked(argv[2], strlen(argv[2])))) {
00394       ast_cli(fd, "No channel matching '%s' found.\n", argv[2]);
00395       return RESULT_SUCCESS;
00396    }
00397 
00398    if (!strcasecmp(argv[1], "start"))
00399       mixmonitor_exec(chan, argv[3]);
00400    else if (!strcasecmp(argv[1], "stop"))
00401       ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00402 
00403    ast_channel_unlock(chan);
00404 
00405    return RESULT_SUCCESS;
00406 }
00407 
00408 static char *complete_mixmonitor_cli(const char *line, const char *word, int pos, int state)
00409 {
00410    return ast_complete_channels(line, word, pos, state, 2);
00411 }
00412 
00413 static struct ast_cli_entry cli_mixmonitor[] = {
00414    { { "mixmonitor", NULL, NULL },
00415    mixmonitor_cli, "Execute a MixMonitor command.",
00416    "mixmonitor <start|stop> <chan_name> [args]\n\n"
00417    "The optional arguments are passed to the\n"
00418    "MixMonitor application when the 'start' command is used.\n",
00419    complete_mixmonitor_cli },
00420 };
00421 
00422 static int unload_module(void)
00423 {
00424    int res;
00425 
00426    ast_cli_unregister_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
00427    res = ast_unregister_application(stop_app);
00428    res |= ast_unregister_application(app);
00429    
00430    ast_module_user_hangup_all();
00431 
00432    return res;
00433 }
00434 
00435 static int load_module(void)
00436 {
00437    int res;
00438 
00439    ast_cli_register_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
00440    res = ast_register_application(app, mixmonitor_exec, synopsis, desc);
00441    res |= ast_register_application(stop_app, stop_mixmonitor_exec, stop_synopsis, stop_desc);
00442 
00443    return res;
00444 }
00445 
00446 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");

Generated on Thu Oct 8 21:55:53 2009 for Asterisk - the Open Source PBX by  doxygen 1.5.6