Fri Aug 24 02:22:10 2007

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

Generated on Fri Aug 24 02:22:10 2007 for Asterisk - the Open Source PBX by  doxygen 1.5.1