Fri Sep 29 11:12:23 2006

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: 42054 $")
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_spy spy;
00083    struct ast_filestream *fs;
00084    char *post_process;
00085    char *name;
00086    unsigned int flags;
00087 };
00088 
00089 enum {
00090    MUXFLAG_APPEND = (1 << 1),
00091    MUXFLAG_BRIDGED = (1 << 2),
00092    MUXFLAG_VOLUME = (1 << 3),
00093    MUXFLAG_READVOLUME = (1 << 4),
00094    MUXFLAG_WRITEVOLUME = (1 << 5),
00095 } mixmonitor_flags;
00096 
00097 enum {
00098    OPT_ARG_READVOLUME = 0,
00099    OPT_ARG_WRITEVOLUME,
00100    OPT_ARG_VOLUME,
00101    OPT_ARG_ARRAY_SIZE,
00102 } mixmonitor_args;
00103 
00104 AST_APP_OPTIONS(mixmonitor_opts, {
00105    AST_APP_OPTION('a', MUXFLAG_APPEND),
00106    AST_APP_OPTION('b', MUXFLAG_BRIDGED),
00107    AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
00108    AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
00109    AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
00110 });
00111 
00112 static int startmon(struct ast_channel *chan, struct ast_channel_spy *spy) 
00113 {
00114    struct ast_channel *peer;
00115    int res;
00116 
00117    if (!chan)
00118       return -1;
00119 
00120    ast_mutex_lock(&chan->lock);
00121    res = ast_channel_spy_add(chan, spy);
00122    ast_mutex_unlock(&chan->lock);
00123 
00124    if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
00125       ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);  
00126 
00127    return res;
00128 }
00129 
00130 #define SAMPLES_PER_FRAME 160
00131 
00132 static void *mixmonitor_thread(void *obj) 
00133 {
00134    struct mixmonitor *mixmonitor = obj;
00135    struct ast_frame *f = NULL;
00136    
00137    STANDARD_INCREMENT_USECOUNT;
00138    
00139    if (option_verbose > 1)
00140       ast_verbose(VERBOSE_PREFIX_2 "Begin MixMonitor Recording %s\n", mixmonitor->name);
00141    
00142    ast_mutex_lock(&mixmonitor->spy.lock);
00143 
00144    while (mixmonitor->spy.chan) {
00145       struct ast_frame *next;
00146       int write;
00147 
00148       ast_channel_spy_trigger_wait(&mixmonitor->spy);
00149       
00150       if (!mixmonitor->spy.chan || mixmonitor->spy.status != CHANSPY_RUNNING)
00151          break;
00152       
00153       while (1) {
00154          if (!(f = ast_channel_spy_read_frame(&mixmonitor->spy, SAMPLES_PER_FRAME)))
00155             break;
00156 
00157          write = (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) ||
00158              ast_bridged_channel(mixmonitor->spy.chan));
00159 
00160          /* it is possible for ast_channel_spy_read_frame() to return a chain
00161             of frames if a queue flush was necessary, so process them
00162          */
00163          for (; f; f = next) {
00164             next = f->next;
00165             if (write)
00166                ast_writestream(mixmonitor->fs, f);
00167             ast_frfree(f);
00168          }
00169       }
00170    }
00171 
00172    ast_mutex_unlock(&mixmonitor->spy.lock);
00173    
00174    ast_channel_spy_free(&mixmonitor->spy);
00175 
00176    if (option_verbose > 1)
00177       ast_verbose(VERBOSE_PREFIX_2 "End MixMonitor Recording %s\n", mixmonitor->name);
00178 
00179    if (mixmonitor->post_process) {
00180       if (option_verbose > 2)
00181          ast_verbose(VERBOSE_PREFIX_2 "Executing [%s]\n", mixmonitor->post_process);
00182       ast_safe_system(mixmonitor->post_process);
00183    }
00184       
00185    ast_closestream(mixmonitor->fs);
00186 
00187    free(mixmonitor);
00188 
00189    STANDARD_DECREMENT_USECOUNT;
00190 
00191    return NULL;
00192 }
00193 
00194 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
00195               int readvol, int writevol, const char *post_process) 
00196 {
00197    pthread_attr_t attr;
00198    pthread_t thread;
00199    struct mixmonitor *mixmonitor;
00200    char *file_name, *ext;
00201    char postprocess2[1024] = "";
00202    unsigned int oflags;
00203    size_t len;
00204 
00205    len = sizeof(*mixmonitor) + strlen(chan->name) + 1;
00206 
00207    /* If a post process system command is given attach it to the structure */
00208    if (!ast_strlen_zero(post_process)) {
00209       char *p1, *p2;
00210 
00211       p1 = ast_strdupa(post_process);
00212       for (p2 = p1; *p2 ; p2++) {
00213          if (*p2 == '^' && *(p2+1) == '{') {
00214             *p2 = '$';
00215          }
00216       }
00217 
00218       pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
00219       if (!ast_strlen_zero(postprocess2))
00220          len += strlen(postprocess2) + 1;
00221    }
00222 
00223    /* Pre-allocate mixmonitor structure and spy */
00224    if (!(mixmonitor = calloc(1, len))) {
00225       ast_log(LOG_ERROR, "Memory Error!\n");
00226       return;
00227    }
00228 
00229    /* Copy over flags and channel name */
00230    mixmonitor->flags = flags;
00231    mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
00232    strcpy(mixmonitor->name, chan->name);
00233    if (!ast_strlen_zero(postprocess2)) {
00234       mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + 1;
00235       strcpy(mixmonitor->post_process, postprocess2);
00236    }
00237 
00238    /* Determine creation flags and filename plus extension for filestream */
00239    oflags = O_CREAT | O_WRONLY;
00240    oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
00241    file_name = ast_strdupa(filename);
00242    if ((ext = strrchr(file_name, '.'))) {
00243       *(ext++) = '\0';
00244    } else {
00245       ext = "raw";
00246    }
00247 
00248    /* Move onto actually creating the filestream */
00249    mixmonitor->fs = ast_writefile(file_name, ext, NULL, oflags, 0, 0644);
00250    if (!mixmonitor->fs) {
00251       ast_log(LOG_ERROR, "Cannot open %s.%s\n", file_name, ext);
00252       free(mixmonitor);
00253       return;
00254    }
00255 
00256    /* Setup the actual spy before creating our thread */
00257    ast_set_flag(&mixmonitor->spy, CHANSPY_FORMAT_AUDIO);
00258    ast_set_flag(&mixmonitor->spy, CHANSPY_MIXAUDIO);
00259    mixmonitor->spy.type = mixmonitor_spy_type;
00260    mixmonitor->spy.status = CHANSPY_RUNNING;
00261    mixmonitor->spy.read_queue.format = AST_FORMAT_SLINEAR;
00262    mixmonitor->spy.write_queue.format = AST_FORMAT_SLINEAR;
00263    if (readvol) {
00264       ast_set_flag(&mixmonitor->spy, CHANSPY_READ_VOLADJUST);
00265       mixmonitor->spy.read_vol_adjustment = readvol;
00266    }
00267    if (writevol) {
00268       ast_set_flag(&mixmonitor->spy, CHANSPY_WRITE_VOLADJUST);
00269       mixmonitor->spy.write_vol_adjustment = writevol;
00270    }
00271    ast_mutex_init(&mixmonitor->spy.lock);
00272 
00273    if (startmon(chan, &mixmonitor->spy)) {
00274       ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
00275          mixmonitor->spy.type, chan->name);
00276       /* Since we couldn't add ourselves - bail out! */
00277       ast_mutex_destroy(&mixmonitor->spy.lock);
00278       ast_closestream(mixmonitor->fs);
00279       free(mixmonitor);
00280       return;
00281    }
00282 
00283    pthread_attr_init(&attr);
00284    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
00285    ast_pthread_create(&thread, &attr, mixmonitor_thread, mixmonitor);
00286    pthread_attr_destroy(&attr);
00287 
00288 }
00289 
00290 static int mixmonitor_exec(struct ast_channel *chan, void *data)
00291 {
00292    int x, readvol = 0, writevol = 0;
00293    struct localuser *u;
00294    struct ast_flags flags = {0};
00295    char *parse;
00296    AST_DECLARE_APP_ARGS(args,
00297       AST_APP_ARG(filename);
00298       AST_APP_ARG(options);
00299       AST_APP_ARG(post_process);
00300    );
00301    
00302    if (ast_strlen_zero(data)) {
00303       ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00304       return -1;
00305    }
00306 
00307    LOCAL_USER_ADD(u);
00308 
00309    if (!(parse = ast_strdupa(data))) {
00310       ast_log(LOG_WARNING, "Memory Error!\n");
00311       LOCAL_USER_REMOVE(u);
00312       return -1;
00313    }
00314 
00315    AST_STANDARD_APP_ARGS(args, parse);
00316    
00317    if (ast_strlen_zero(args.filename)) {
00318       ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00319       LOCAL_USER_REMOVE(u);
00320       return -1;
00321    }
00322 
00323    if (args.options) {
00324       char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
00325 
00326       ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
00327 
00328       if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
00329          if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
00330             ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
00331          } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00332             ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
00333          } else {
00334             readvol = get_volfactor(x);
00335          }
00336       }
00337       
00338       if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
00339          if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
00340             ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
00341          } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00342             ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
00343          } else {
00344             writevol = get_volfactor(x);
00345          }
00346       }
00347       
00348       if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
00349          if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
00350             ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
00351          } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00352             ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
00353          } else {
00354             readvol = writevol = get_volfactor(x);
00355          }
00356       }
00357    }
00358 
00359    /* if not provided an absolute path, use the system-configured monitoring directory */
00360    if (args.filename[0] != '/') {
00361       char *build;
00362 
00363       build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
00364       sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
00365       args.filename = build;
00366    }
00367 
00368    pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
00369    launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
00370 
00371    LOCAL_USER_REMOVE(u);
00372 
00373    return 0;
00374 }
00375 
00376 static int mixmonitor_cli(int fd, int argc, char **argv) 
00377 {
00378    struct ast_channel *chan;
00379 
00380    if (argc < 3)
00381       return RESULT_SHOWUSAGE;
00382 
00383    if (!(chan = ast_get_channel_by_name_prefix_locked(argv[2], strlen(argv[2])))) {
00384       ast_cli(fd, "No channel matching '%s' found.\n", argv[2]);
00385       return RESULT_SUCCESS;
00386    }
00387 
00388    if (!strcasecmp(argv[1], "start"))
00389       mixmonitor_exec(chan, argv[3]);
00390    else if (!strcasecmp(argv[1], "stop"))
00391       ast_channel_spy_stop_by_type(chan, mixmonitor_spy_type);
00392 
00393    ast_mutex_unlock(&chan->lock);
00394 
00395    return RESULT_SUCCESS;
00396 }
00397 
00398 
00399 static struct ast_cli_entry cli_mixmonitor = {
00400    { "mixmonitor", NULL, NULL },
00401    mixmonitor_cli, 
00402    "Execute a MixMonitor command",
00403    "mixmonitor <start|stop> <chan_name> [<args>]\n"
00404 };
00405 
00406 
00407 int unload_module(void)
00408 {
00409    int res;
00410 
00411    res = ast_cli_unregister(&cli_mixmonitor);
00412    res |= ast_unregister_application(app);
00413    
00414    STANDARD_HANGUP_LOCALUSERS;
00415 
00416    return res;
00417 }
00418 
00419 int load_module(void)
00420 {
00421    int res;
00422 
00423    res = ast_cli_register(&cli_mixmonitor);
00424    res |= ast_register_application(app, mixmonitor_exec, synopsis, desc);
00425 
00426    return res;
00427 }
00428 
00429 char *description(void)
00430 {
00431    return (char *) tdesc;
00432 }
00433 
00434 int usecount(void)
00435 {
00436    int res;
00437 
00438    STANDARD_USECOUNT(res);
00439 
00440    return res;
00441 }
00442 
00443 char *key()
00444 {
00445    return ASTERISK_GPL_KEY;
00446 }

Generated on Fri Sep 29 11:12:23 2006 for Asterisk - the Open Source PBX by  doxygen 1.4.7