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