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