Sun Aug 6 15:02:24 2006

Asterisk developer's documentation


Main Page | Modules | Alphabetical List | Data Structures | Directories | File List | Data Fields | Globals | Related Pages

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: 33841 $")
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 void stopmon(struct ast_channel_spy *spy) 
00113 {
00114    struct ast_channel *chan = spy->chan;
00115 
00116    /* If our status has changed to DONE, then the channel we're spying on is gone....
00117       DON'T TOUCH IT!!!  RUN AWAY!!! */
00118    if (spy->status == CHANSPY_DONE)
00119       return;
00120   
00121    if (!chan)
00122       return;
00123 
00124    ast_mutex_lock(&chan->lock);
00125    ast_channel_spy_remove(chan, spy);
00126    ast_mutex_unlock(&chan->lock);
00127 }
00128 
00129 static int startmon(struct ast_channel *chan, struct ast_channel_spy *spy) 
00130 {
00131    struct ast_channel *peer;
00132    int res;
00133 
00134    if (!chan)
00135       return -1;
00136 
00137    ast_mutex_lock(&chan->lock);
00138    res = ast_channel_spy_add(chan, spy);
00139    ast_mutex_unlock(&chan->lock);
00140 
00141    if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
00142       ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);  
00143 
00144    return res;
00145 }
00146 
00147 #define SAMPLES_PER_FRAME 160
00148 
00149 static void *mixmonitor_thread(void *obj) 
00150 {
00151    struct mixmonitor *mixmonitor = obj;
00152    struct ast_frame *f = NULL;
00153    
00154    STANDARD_INCREMENT_USECOUNT;
00155    
00156    if (option_verbose > 1)
00157       ast_verbose(VERBOSE_PREFIX_2 "Begin MixMonitor Recording %s\n", mixmonitor->name);
00158    
00159    ast_mutex_lock(&mixmonitor->spy.lock);
00160 
00161    while (mixmonitor->spy.chan) {
00162       struct ast_frame *next;
00163       int write;
00164 
00165       ast_channel_spy_trigger_wait(&mixmonitor->spy);
00166       
00167       if (!mixmonitor->spy.chan || mixmonitor->spy.status != CHANSPY_RUNNING) {
00168          break;
00169       }
00170       
00171       while (1) {
00172          if (!(f = ast_channel_spy_read_frame(&mixmonitor->spy, SAMPLES_PER_FRAME)))
00173             break;
00174 
00175          write = (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) ||
00176              ast_bridged_channel(mixmonitor->spy.chan));
00177 
00178          /* it is possible for ast_channel_spy_read_frame() to return a chain
00179             of frames if a queue flush was necessary, so process them
00180          */
00181          for (; f; f = next) {
00182             next = f->next;
00183             if (write)
00184                ast_writestream(mixmonitor->fs, f);
00185             ast_frfree(f);
00186          }
00187       }
00188    }
00189 
00190    ast_mutex_unlock(&mixmonitor->spy.lock);
00191    
00192    stopmon(&mixmonitor->spy);
00193 
00194    if (option_verbose > 1)
00195       ast_verbose(VERBOSE_PREFIX_2 "End MixMonitor Recording %s\n", mixmonitor->name);
00196 
00197    if (mixmonitor->post_process) {
00198       if (option_verbose > 2)
00199          ast_verbose(VERBOSE_PREFIX_2 "Executing [%s]\n", mixmonitor->post_process);
00200       ast_safe_system(mixmonitor->post_process);
00201    }
00202 
00203    ast_mutex_destroy(&mixmonitor->spy.lock);
00204       
00205    ast_closestream(mixmonitor->fs);
00206 
00207    free(mixmonitor);
00208 
00209    STANDARD_DECREMENT_USECOUNT;
00210 
00211    return NULL;
00212 }
00213 
00214 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
00215               int readvol, int writevol, const char *post_process) 
00216 {
00217    pthread_attr_t attr;
00218    pthread_t thread;
00219    struct mixmonitor *mixmonitor;
00220    char *file_name, *ext;
00221    char postprocess2[1024] = "";
00222    unsigned int oflags;
00223    size_t len;
00224 
00225    len = sizeof(*mixmonitor) + strlen(chan->name) + 1;
00226 
00227    /* If a post process system command is given attach it to the structure */
00228    if (!ast_strlen_zero(post_process)) {
00229       char *p1, *p2;
00230 
00231       p1 = ast_strdupa(post_process);
00232       for (p2 = p1; *p2 ; p2++) {
00233          if (*p2 == '^' && *(p2+1) == '{') {
00234             *p2 = '$';
00235          }
00236       }
00237 
00238       pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
00239       if (!ast_strlen_zero(postprocess2))
00240          len += strlen(postprocess2) + 1;
00241    }
00242 
00243    /* Pre-allocate mixmonitor structure and spy */
00244    if (!(mixmonitor = calloc(1, len))) {
00245       ast_log(LOG_ERROR, "Memory Error!\n");
00246       return;
00247    }
00248 
00249    /* Copy over flags and channel name */
00250    mixmonitor->flags = flags;
00251    mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
00252    strcpy(mixmonitor->name, chan->name);
00253    if (!ast_strlen_zero(postprocess2)) {
00254       mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + 1;
00255       strcpy(mixmonitor->post_process, postprocess2);
00256    }
00257 
00258    /* Determine creation flags and filename plus extension for filestream */
00259    oflags = O_CREAT | O_WRONLY;
00260    oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
00261    file_name = ast_strdupa(filename);
00262    if ((ext = strrchr(file_name, '.'))) {
00263       *(ext++) = '\0';
00264    } else {
00265       ext = "raw";
00266    }
00267 
00268    /* Move onto actually creating the filestream */
00269    mixmonitor->fs = ast_writefile(file_name, ext, NULL, oflags, 0, 0644);
00270    if (!mixmonitor->fs) {
00271       ast_log(LOG_ERROR, "Cannot open %s.%s\n", file_name, ext);
00272       free(mixmonitor);
00273       return;
00274    }
00275 
00276    /* Setup the actual spy before creating our thread */
00277    ast_set_flag(&mixmonitor->spy, CHANSPY_FORMAT_AUDIO);
00278    ast_set_flag(&mixmonitor->spy, CHANSPY_MIXAUDIO);
00279    mixmonitor->spy.type = mixmonitor_spy_type;
00280    mixmonitor->spy.status = CHANSPY_RUNNING;
00281    mixmonitor->spy.read_queue.format = AST_FORMAT_SLINEAR;
00282    mixmonitor->spy.write_queue.format = AST_FORMAT_SLINEAR;
00283    if (readvol) {
00284       ast_set_flag(&mixmonitor->spy, CHANSPY_READ_VOLADJUST);
00285       mixmonitor->spy.read_vol_adjustment = readvol;
00286    }
00287    if (writevol) {
00288       ast_set_flag(&mixmonitor->spy, CHANSPY_WRITE_VOLADJUST);
00289       mixmonitor->spy.write_vol_adjustment = writevol;
00290    }
00291    ast_mutex_init(&mixmonitor->spy.lock);
00292 
00293    if (startmon(chan, &mixmonitor->spy)) {
00294       ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
00295          mixmonitor->spy.type, chan->name);
00296       /* Since we couldn't add ourselves - bail out! */
00297       ast_mutex_destroy(&mixmonitor->spy.lock);
00298       ast_closestream(mixmonitor->fs);
00299       free(mixmonitor);
00300       return;
00301    }
00302 
00303    pthread_attr_init(&attr);
00304    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
00305    ast_pthread_create(&thread, &attr, mixmonitor_thread, mixmonitor);
00306    pthread_attr_destroy(&attr);
00307 
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 Sun Aug 6 15:02:24 2006 for Asterisk - the Open Source PBX by  doxygen 1.4.2