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

Generated on Thu Dec 17 23:51:00 2009 for Asterisk - the Open Source PBX by  doxygen 1.4.7