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

Generated on Wed Oct 28 11:50:53 2009 for Asterisk - the Open Source PBX by  doxygen 1.5.6