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