00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040 #include "asterisk.h"
00041
00042 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 375484 $")
00043
00044 #include "asterisk/paths.h"
00045 #include "asterisk/file.h"
00046 #include "asterisk/audiohook.h"
00047 #include "asterisk/pbx.h"
00048 #include "asterisk/module.h"
00049 #include "asterisk/cli.h"
00050 #include "asterisk/app.h"
00051 #include "asterisk/channel.h"
00052 #include "asterisk/autochan.h"
00053 #include "asterisk/manager.h"
00054 #include "asterisk/test.h"
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135
00136
00137
00138
00139
00140
00141
00142
00143
00144
00145
00146
00147
00148
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158
00159
00160
00161
00162
00163
00164 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
00165
00166 static const char * const app = "MixMonitor";
00167
00168 static const char * const stop_app = "StopMixMonitor";
00169
00170 static const char * const mixmonitor_spy_type = "MixMonitor";
00171
00172 struct mixmonitor {
00173 struct ast_audiohook audiohook;
00174 char *filename;
00175 char *post_process;
00176 char *name;
00177 unsigned int flags;
00178 struct ast_autochan *autochan;
00179 struct mixmonitor_ds *mixmonitor_ds;
00180 };
00181
00182 enum mixmonitor_flags {
00183 MUXFLAG_APPEND = (1 << 1),
00184 MUXFLAG_BRIDGED = (1 << 2),
00185 MUXFLAG_VOLUME = (1 << 3),
00186 MUXFLAG_READVOLUME = (1 << 4),
00187 MUXFLAG_WRITEVOLUME = (1 << 5),
00188 };
00189
00190 enum mixmonitor_args {
00191 OPT_ARG_READVOLUME = 0,
00192 OPT_ARG_WRITEVOLUME,
00193 OPT_ARG_VOLUME,
00194 OPT_ARG_ARRAY_SIZE,
00195 };
00196
00197 AST_APP_OPTIONS(mixmonitor_opts, {
00198 AST_APP_OPTION('a', MUXFLAG_APPEND),
00199 AST_APP_OPTION('b', MUXFLAG_BRIDGED),
00200 AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
00201 AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
00202 AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
00203 });
00204
00205 struct mixmonitor_ds {
00206 unsigned int destruction_ok;
00207 ast_cond_t destruction_condition;
00208 ast_mutex_t lock;
00209
00210
00211
00212 int fs_quit;
00213 struct ast_filestream *fs;
00214 struct ast_audiohook *audiohook;
00215 };
00216
00217
00218
00219
00220
00221 static void mixmonitor_ds_close_fs(struct mixmonitor_ds *mixmonitor_ds)
00222 {
00223 if (mixmonitor_ds->fs) {
00224 ast_closestream(mixmonitor_ds->fs);
00225 mixmonitor_ds->fs = NULL;
00226 mixmonitor_ds->fs_quit = 1;
00227 ast_verb(2, "MixMonitor close filestream\n");
00228 }
00229 }
00230
00231 static void mixmonitor_ds_destroy(void *data)
00232 {
00233 struct mixmonitor_ds *mixmonitor_ds = data;
00234
00235 ast_mutex_lock(&mixmonitor_ds->lock);
00236 mixmonitor_ds->audiohook = NULL;
00237 mixmonitor_ds->destruction_ok = 1;
00238 ast_cond_signal(&mixmonitor_ds->destruction_condition);
00239 ast_mutex_unlock(&mixmonitor_ds->lock);
00240 }
00241
00242 static const struct ast_datastore_info mixmonitor_ds_info = {
00243 .type = "mixmonitor",
00244 .destroy = mixmonitor_ds_destroy,
00245 };
00246
00247 static void destroy_monitor_audiohook(struct mixmonitor *mixmonitor)
00248 {
00249 if (mixmonitor->mixmonitor_ds) {
00250 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
00251 mixmonitor->mixmonitor_ds->audiohook = NULL;
00252 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
00253 }
00254
00255 ast_audiohook_lock(&mixmonitor->audiohook);
00256 ast_audiohook_detach(&mixmonitor->audiohook);
00257 ast_audiohook_unlock(&mixmonitor->audiohook);
00258 ast_audiohook_destroy(&mixmonitor->audiohook);
00259 }
00260
00261 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
00262 {
00263 struct ast_channel *peer = NULL;
00264 int res = 0;
00265
00266 if (!chan)
00267 return -1;
00268
00269 ast_audiohook_attach(chan, audiohook);
00270
00271 if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
00272 ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
00273
00274 return res;
00275 }
00276
00277 #define SAMPLES_PER_FRAME 160
00278
00279 static void mixmonitor_free(struct mixmonitor *mixmonitor)
00280 {
00281 if (mixmonitor) {
00282 if (mixmonitor->mixmonitor_ds) {
00283 ast_mutex_destroy(&mixmonitor->mixmonitor_ds->lock);
00284 ast_cond_destroy(&mixmonitor->mixmonitor_ds->destruction_condition);
00285 ast_free(mixmonitor->mixmonitor_ds);
00286 }
00287 ast_free(mixmonitor);
00288 }
00289 }
00290 static void *mixmonitor_thread(void *obj)
00291 {
00292 struct mixmonitor *mixmonitor = obj;
00293 struct ast_filestream **fs = NULL;
00294 unsigned int oflags;
00295 char *ext;
00296 char *last_slash;
00297 int errflag = 0;
00298
00299 ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name);
00300
00301 fs = &mixmonitor->mixmonitor_ds->fs;
00302
00303
00304 ast_audiohook_lock(&mixmonitor->audiohook);
00305 while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING && !mixmonitor->mixmonitor_ds->fs_quit) {
00306 struct ast_frame *fr = NULL;
00307
00308 if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR))) {
00309 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
00310
00311 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
00312 break;
00313 }
00314 continue;
00315 }
00316
00317
00318
00319 ast_audiohook_unlock(&mixmonitor->audiohook);
00320
00321 if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || (mixmonitor->autochan->chan && ast_bridged_channel(mixmonitor->autochan->chan))) {
00322 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
00323
00324 if (!*fs && !errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
00325 oflags = O_CREAT | O_WRONLY;
00326 oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
00327
00328 last_slash = strrchr(mixmonitor->filename, '/');
00329 if ((ext = strrchr(mixmonitor->filename, '.')) && (ext > last_slash))
00330 *(ext++) = '\0';
00331 else
00332 ext = "raw";
00333
00334 if (!(*fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0666))) {
00335 ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
00336 errflag = 1;
00337 }
00338 }
00339
00340
00341 if (*fs) {
00342 struct ast_frame *cur;
00343
00344 for (cur = fr; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
00345 ast_writestream(*fs, cur);
00346 }
00347 }
00348 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
00349 }
00350
00351 ast_frame_free(fr, 0);
00352
00353 ast_audiohook_lock(&mixmonitor->audiohook);
00354 }
00355
00356
00357 ast_test_suite_event_notify("MIXMONITOR_END", "Channel: %s\r\n"
00358 "File: %s\r\n",
00359 mixmonitor->autochan->chan->name,
00360 mixmonitor->filename);
00361
00362 ast_audiohook_unlock(&mixmonitor->audiohook);
00363
00364 ast_autochan_destroy(mixmonitor->autochan);
00365
00366
00367 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
00368 mixmonitor_ds_close_fs(mixmonitor->mixmonitor_ds);
00369 if (!mixmonitor->mixmonitor_ds->destruction_ok) {
00370 ast_cond_wait(&mixmonitor->mixmonitor_ds->destruction_condition, &mixmonitor->mixmonitor_ds->lock);
00371 }
00372 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
00373
00374
00375 destroy_monitor_audiohook(mixmonitor);
00376
00377 if (mixmonitor->post_process) {
00378 ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
00379 ast_safe_system(mixmonitor->post_process);
00380 }
00381
00382 ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
00383 mixmonitor_free(mixmonitor);
00384 return NULL;
00385 }
00386
00387 static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan)
00388 {
00389 struct ast_datastore *datastore = NULL;
00390 struct mixmonitor_ds *mixmonitor_ds;
00391
00392 if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
00393 return -1;
00394 }
00395
00396 ast_mutex_init(&mixmonitor_ds->lock);
00397 ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
00398
00399 if (!(datastore = ast_datastore_alloc(&mixmonitor_ds_info, NULL))) {
00400 ast_mutex_destroy(&mixmonitor_ds->lock);
00401 ast_cond_destroy(&mixmonitor_ds->destruction_condition);
00402 ast_free(mixmonitor_ds);
00403 return -1;
00404 }
00405
00406 mixmonitor_ds->audiohook = &mixmonitor->audiohook;
00407 datastore->data = mixmonitor_ds;
00408
00409 ast_channel_lock(chan);
00410 ast_channel_datastore_add(chan, datastore);
00411 ast_channel_unlock(chan);
00412
00413 mixmonitor->mixmonitor_ds = mixmonitor_ds;
00414 return 0;
00415 }
00416
00417 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
00418 int readvol, int writevol, const char *post_process)
00419 {
00420 pthread_t thread;
00421 struct mixmonitor *mixmonitor;
00422 char postprocess2[1024] = "";
00423 size_t len;
00424
00425 len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
00426
00427 postprocess2[0] = 0;
00428
00429 if (!ast_strlen_zero(post_process)) {
00430 char *p1, *p2;
00431
00432 p1 = ast_strdupa(post_process);
00433 for (p2 = p1; *p2 ; p2++) {
00434 if (*p2 == '^' && *(p2+1) == '{') {
00435 *p2 = '$';
00436 }
00437 }
00438 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
00439 if (!ast_strlen_zero(postprocess2))
00440 len += strlen(postprocess2) + 1;
00441 }
00442
00443
00444 if (!(mixmonitor = ast_calloc(1, len))) {
00445 return;
00446 }
00447
00448
00449 if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
00450 mixmonitor_free(mixmonitor);
00451 return;
00452 }
00453
00454
00455 mixmonitor->flags = flags;
00456 if (!(mixmonitor->autochan = ast_autochan_setup(chan))) {
00457 mixmonitor_free(mixmonitor);
00458 return;
00459 }
00460
00461 if (setup_mixmonitor_ds(mixmonitor, chan)) {
00462 ast_autochan_destroy(mixmonitor->autochan);
00463 mixmonitor_free(mixmonitor);
00464 return;
00465 }
00466 mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
00467 strcpy(mixmonitor->name, chan->name);
00468 if (!ast_strlen_zero(postprocess2)) {
00469 mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
00470 strcpy(mixmonitor->post_process, postprocess2);
00471 }
00472
00473 mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
00474 strcpy(mixmonitor->filename, filename);
00475
00476 ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
00477
00478 if (readvol)
00479 mixmonitor->audiohook.options.read_volume = readvol;
00480 if (writevol)
00481 mixmonitor->audiohook.options.write_volume = writevol;
00482
00483 if (startmon(chan, &mixmonitor->audiohook)) {
00484 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
00485 mixmonitor_spy_type, chan->name);
00486 ast_audiohook_destroy(&mixmonitor->audiohook);
00487 mixmonitor_free(mixmonitor);
00488 return;
00489 }
00490
00491 ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
00492 }
00493
00494 static int mixmonitor_exec(struct ast_channel *chan, const char *data)
00495 {
00496 int x, readvol = 0, writevol = 0;
00497 struct ast_flags flags = {0};
00498 char *parse, *tmp, *slash;
00499 AST_DECLARE_APP_ARGS(args,
00500 AST_APP_ARG(filename);
00501 AST_APP_ARG(options);
00502 AST_APP_ARG(post_process);
00503 );
00504
00505 if (ast_strlen_zero(data)) {
00506 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00507 return -1;
00508 }
00509
00510 parse = ast_strdupa(data);
00511
00512 AST_STANDARD_APP_ARGS(args, parse);
00513
00514 if (ast_strlen_zero(args.filename)) {
00515 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00516 return -1;
00517 }
00518
00519 if (args.options) {
00520 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
00521
00522 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
00523
00524 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
00525 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
00526 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
00527 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
00528 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
00529 } else {
00530 readvol = get_volfactor(x);
00531 }
00532 }
00533
00534 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
00535 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
00536 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
00537 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
00538 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
00539 } else {
00540 writevol = get_volfactor(x);
00541 }
00542 }
00543
00544 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
00545 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
00546 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
00547 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
00548 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
00549 } else {
00550 readvol = writevol = get_volfactor(x);
00551 }
00552 }
00553 }
00554
00555
00556 if (args.filename[0] != '/') {
00557 char *build;
00558
00559 build = ast_alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
00560 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
00561 args.filename = build;
00562 }
00563
00564 tmp = ast_strdupa(args.filename);
00565 if ((slash = strrchr(tmp, '/')))
00566 *slash = '\0';
00567 ast_mkdir(tmp, 0777);
00568
00569 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
00570 launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
00571
00572 return 0;
00573 }
00574
00575 static int stop_mixmonitor_exec(struct ast_channel *chan, const char *data)
00576 {
00577 struct ast_datastore *datastore = NULL;
00578
00579 ast_channel_lock(chan);
00580 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00581 if ((datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, NULL))) {
00582 struct mixmonitor_ds *mixmonitor_ds = datastore->data;
00583
00584 ast_mutex_lock(&mixmonitor_ds->lock);
00585
00586
00587
00588 mixmonitor_ds_close_fs(mixmonitor_ds);
00589
00590
00591
00592
00593 if (mixmonitor_ds->audiohook) {
00594 ast_audiohook_lock(mixmonitor_ds->audiohook);
00595 ast_cond_signal(&mixmonitor_ds->audiohook->trigger);
00596 ast_audiohook_unlock(mixmonitor_ds->audiohook);
00597 mixmonitor_ds->audiohook = NULL;
00598 }
00599
00600 ast_mutex_unlock(&mixmonitor_ds->lock);
00601
00602
00603 if (!ast_channel_datastore_remove(chan, datastore)) {
00604 ast_datastore_free(datastore);
00605 }
00606 }
00607 ast_channel_unlock(chan);
00608
00609 return 0;
00610 }
00611
00612 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00613 {
00614 struct ast_channel *chan;
00615
00616 switch (cmd) {
00617 case CLI_INIT:
00618 e->command = "mixmonitor {start|stop}";
00619 e->usage =
00620 "Usage: mixmonitor <start|stop> <chan_name> [args]\n"
00621 " The optional arguments are passed to the MixMonitor\n"
00622 " application when the 'start' command is used.\n";
00623 return NULL;
00624 case CLI_GENERATE:
00625 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
00626 }
00627
00628 if (a->argc < 3)
00629 return CLI_SHOWUSAGE;
00630
00631 if (!(chan = ast_channel_get_by_name_prefix(a->argv[2], strlen(a->argv[2])))) {
00632 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
00633
00634 return CLI_SUCCESS;
00635 }
00636
00637 ast_channel_lock(chan);
00638
00639 if (!strcasecmp(a->argv[1], "start")) {
00640 mixmonitor_exec(chan, a->argv[3]);
00641 ast_channel_unlock(chan);
00642 } else {
00643 ast_channel_unlock(chan);
00644 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00645 }
00646
00647 chan = ast_channel_unref(chan);
00648
00649 return CLI_SUCCESS;
00650 }
00651
00652
00653 static int manager_mute_mixmonitor(struct mansession *s, const struct message *m)
00654 {
00655 struct ast_channel *c = NULL;
00656
00657 const char *name = astman_get_header(m, "Channel");
00658 const char *id = astman_get_header(m, "ActionID");
00659 const char *state = astman_get_header(m, "State");
00660 const char *direction = astman_get_header(m,"Direction");
00661
00662 int clearmute = 1;
00663
00664 enum ast_audiohook_flags flag;
00665
00666 if (ast_strlen_zero(direction)) {
00667 astman_send_error(s, m, "No direction specified. Must be read, write or both");
00668 return AMI_SUCCESS;
00669 }
00670
00671 if (!strcasecmp(direction, "read")) {
00672 flag = AST_AUDIOHOOK_MUTE_READ;
00673 } else if (!strcasecmp(direction, "write")) {
00674 flag = AST_AUDIOHOOK_MUTE_WRITE;
00675 } else if (!strcasecmp(direction, "both")) {
00676 flag = AST_AUDIOHOOK_MUTE_READ | AST_AUDIOHOOK_MUTE_WRITE;
00677 } else {
00678 astman_send_error(s, m, "Invalid direction specified. Must be read, write or both");
00679 return AMI_SUCCESS;
00680 }
00681
00682 if (ast_strlen_zero(name)) {
00683 astman_send_error(s, m, "No channel specified");
00684 return AMI_SUCCESS;
00685 }
00686
00687 if (ast_strlen_zero(state)) {
00688 astman_send_error(s, m, "No state specified");
00689 return AMI_SUCCESS;
00690 }
00691
00692 clearmute = ast_false(state);
00693 c = ast_channel_get_by_name(name);
00694
00695 if (!c) {
00696 astman_send_error(s, m, "No such channel");
00697 return AMI_SUCCESS;
00698 }
00699
00700 if (ast_audiohook_set_mute(c, mixmonitor_spy_type, flag, clearmute)) {
00701 c = ast_channel_unref(c);
00702 astman_send_error(s, m, "Cannot set mute flag");
00703 return AMI_SUCCESS;
00704 }
00705
00706 astman_append(s, "Response: Success\r\n");
00707
00708 if (!ast_strlen_zero(id)) {
00709 astman_append(s, "ActionID: %s\r\n", id);
00710 }
00711
00712 astman_append(s, "\r\n");
00713
00714 c = ast_channel_unref(c);
00715
00716 return AMI_SUCCESS;
00717 }
00718
00719 static struct ast_cli_entry cli_mixmonitor[] = {
00720 AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
00721 };
00722
00723 static int unload_module(void)
00724 {
00725 int res;
00726
00727 ast_cli_unregister_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
00728 res = ast_unregister_application(stop_app);
00729 res |= ast_unregister_application(app);
00730 res |= ast_manager_unregister("MixMonitorMute");
00731
00732 return res;
00733 }
00734
00735 static int load_module(void)
00736 {
00737 int res;
00738
00739 ast_cli_register_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
00740 res = ast_register_application_xml(app, mixmonitor_exec);
00741 res |= ast_register_application_xml(stop_app, stop_mixmonitor_exec);
00742 res |= ast_manager_register_xml("MixMonitorMute", 0, manager_mute_mixmonitor);
00743
00744 return res;
00745 }
00746
00747 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");