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: 404044 $")
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
00385 ast_module_unref(ast_module_info->self);
00386 return NULL;
00387 }
00388
00389 static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan)
00390 {
00391 struct ast_datastore *datastore = NULL;
00392 struct mixmonitor_ds *mixmonitor_ds;
00393
00394 if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
00395 return -1;
00396 }
00397
00398 ast_mutex_init(&mixmonitor_ds->lock);
00399 ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
00400
00401 if (!(datastore = ast_datastore_alloc(&mixmonitor_ds_info, NULL))) {
00402 ast_mutex_destroy(&mixmonitor_ds->lock);
00403 ast_cond_destroy(&mixmonitor_ds->destruction_condition);
00404 ast_free(mixmonitor_ds);
00405 return -1;
00406 }
00407
00408 mixmonitor_ds->audiohook = &mixmonitor->audiohook;
00409 datastore->data = mixmonitor_ds;
00410
00411 ast_channel_lock(chan);
00412 ast_channel_datastore_add(chan, datastore);
00413 ast_channel_unlock(chan);
00414
00415 mixmonitor->mixmonitor_ds = mixmonitor_ds;
00416 return 0;
00417 }
00418
00419 static int launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
00420 int readvol, int writevol, const char *post_process)
00421 {
00422 pthread_t thread;
00423 struct mixmonitor *mixmonitor;
00424 char postprocess2[1024] = "";
00425 size_t len;
00426
00427 len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
00428
00429 postprocess2[0] = 0;
00430
00431 if (!ast_strlen_zero(post_process)) {
00432 char *p1, *p2;
00433
00434 p1 = ast_strdupa(post_process);
00435 for (p2 = p1; *p2 ; p2++) {
00436 if (*p2 == '^' && *(p2+1) == '{') {
00437 *p2 = '$';
00438 }
00439 }
00440 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
00441 if (!ast_strlen_zero(postprocess2))
00442 len += strlen(postprocess2) + 1;
00443 }
00444
00445
00446 if (!(mixmonitor = ast_calloc(1, len))) {
00447 return -1;
00448 }
00449
00450
00451 if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
00452 mixmonitor_free(mixmonitor);
00453 return -1;
00454 }
00455
00456
00457 mixmonitor->flags = flags;
00458 if (!(mixmonitor->autochan = ast_autochan_setup(chan))) {
00459 mixmonitor_free(mixmonitor);
00460 return -1;
00461 }
00462
00463 if (setup_mixmonitor_ds(mixmonitor, chan)) {
00464 ast_autochan_destroy(mixmonitor->autochan);
00465 mixmonitor_free(mixmonitor);
00466 return -1;
00467 }
00468 mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
00469 strcpy(mixmonitor->name, chan->name);
00470 if (!ast_strlen_zero(postprocess2)) {
00471 mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
00472 strcpy(mixmonitor->post_process, postprocess2);
00473 }
00474
00475 mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
00476 strcpy(mixmonitor->filename, filename);
00477
00478 ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
00479
00480 if (readvol)
00481 mixmonitor->audiohook.options.read_volume = readvol;
00482 if (writevol)
00483 mixmonitor->audiohook.options.write_volume = writevol;
00484
00485 if (startmon(chan, &mixmonitor->audiohook)) {
00486 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
00487 mixmonitor_spy_type, chan->name);
00488 ast_audiohook_destroy(&mixmonitor->audiohook);
00489 mixmonitor_free(mixmonitor);
00490 return -1;
00491 }
00492
00493 return ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
00494 }
00495
00496 static int mixmonitor_exec(struct ast_channel *chan, const char *data)
00497 {
00498 int x, readvol = 0, writevol = 0;
00499 struct ast_flags flags = {0};
00500 char *parse, *tmp, *slash;
00501 AST_DECLARE_APP_ARGS(args,
00502 AST_APP_ARG(filename);
00503 AST_APP_ARG(options);
00504 AST_APP_ARG(post_process);
00505 );
00506
00507 if (ast_strlen_zero(data)) {
00508 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00509 return -1;
00510 }
00511
00512 parse = ast_strdupa(data);
00513
00514 AST_STANDARD_APP_ARGS(args, parse);
00515
00516 if (ast_strlen_zero(args.filename)) {
00517 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00518 return -1;
00519 }
00520
00521 if (args.options) {
00522 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
00523
00524 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
00525
00526 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
00527 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
00528 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
00529 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
00530 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
00531 } else {
00532 readvol = get_volfactor(x);
00533 }
00534 }
00535
00536 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
00537 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
00538 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
00539 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
00540 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
00541 } else {
00542 writevol = get_volfactor(x);
00543 }
00544 }
00545
00546 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
00547 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
00548 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
00549 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
00550 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
00551 } else {
00552 readvol = writevol = get_volfactor(x);
00553 }
00554 }
00555 }
00556
00557
00558 if (args.filename[0] != '/') {
00559 char *build;
00560
00561 build = ast_alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
00562 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
00563 args.filename = build;
00564 }
00565
00566 tmp = ast_strdupa(args.filename);
00567 if ((slash = strrchr(tmp, '/')))
00568 *slash = '\0';
00569 ast_mkdir(tmp, 0777);
00570
00571 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
00572
00573
00574 ast_module_ref(ast_module_info->self);
00575 if (launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process)) {
00576 ast_module_unref(ast_module_info->self);
00577 }
00578
00579 return 0;
00580 }
00581
00582 static int stop_mixmonitor_exec(struct ast_channel *chan, const char *data)
00583 {
00584 struct ast_datastore *datastore = NULL;
00585
00586 ast_channel_lock(chan);
00587 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00588 if ((datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, NULL))) {
00589 struct mixmonitor_ds *mixmonitor_ds = datastore->data;
00590
00591 ast_mutex_lock(&mixmonitor_ds->lock);
00592
00593
00594
00595 mixmonitor_ds_close_fs(mixmonitor_ds);
00596
00597
00598
00599
00600 if (mixmonitor_ds->audiohook) {
00601 ast_audiohook_lock(mixmonitor_ds->audiohook);
00602 ast_cond_signal(&mixmonitor_ds->audiohook->trigger);
00603 ast_audiohook_unlock(mixmonitor_ds->audiohook);
00604 mixmonitor_ds->audiohook = NULL;
00605 }
00606
00607 ast_mutex_unlock(&mixmonitor_ds->lock);
00608
00609
00610 if (!ast_channel_datastore_remove(chan, datastore)) {
00611 ast_datastore_free(datastore);
00612 }
00613 }
00614 ast_channel_unlock(chan);
00615
00616 return 0;
00617 }
00618
00619 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00620 {
00621 struct ast_channel *chan;
00622
00623 switch (cmd) {
00624 case CLI_INIT:
00625 e->command = "mixmonitor {start|stop}";
00626 e->usage =
00627 "Usage: mixmonitor <start|stop> <chan_name> [args]\n"
00628 " The optional arguments are passed to the MixMonitor\n"
00629 " application when the 'start' command is used.\n";
00630 return NULL;
00631 case CLI_GENERATE:
00632 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
00633 }
00634
00635 if (a->argc < 3)
00636 return CLI_SHOWUSAGE;
00637
00638 if (!(chan = ast_channel_get_by_name_prefix(a->argv[2], strlen(a->argv[2])))) {
00639 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
00640
00641 return CLI_SUCCESS;
00642 }
00643
00644 ast_channel_lock(chan);
00645
00646 if (!strcasecmp(a->argv[1], "start")) {
00647 mixmonitor_exec(chan, a->argv[3]);
00648 ast_channel_unlock(chan);
00649 } else {
00650 ast_channel_unlock(chan);
00651 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00652 }
00653
00654 chan = ast_channel_unref(chan);
00655
00656 return CLI_SUCCESS;
00657 }
00658
00659
00660 static int manager_mute_mixmonitor(struct mansession *s, const struct message *m)
00661 {
00662 struct ast_channel *c = NULL;
00663
00664 const char *name = astman_get_header(m, "Channel");
00665 const char *id = astman_get_header(m, "ActionID");
00666 const char *state = astman_get_header(m, "State");
00667 const char *direction = astman_get_header(m,"Direction");
00668
00669 int clearmute = 1;
00670
00671 enum ast_audiohook_flags flag;
00672
00673 if (ast_strlen_zero(direction)) {
00674 astman_send_error(s, m, "No direction specified. Must be read, write or both");
00675 return AMI_SUCCESS;
00676 }
00677
00678 if (!strcasecmp(direction, "read")) {
00679 flag = AST_AUDIOHOOK_MUTE_READ;
00680 } else if (!strcasecmp(direction, "write")) {
00681 flag = AST_AUDIOHOOK_MUTE_WRITE;
00682 } else if (!strcasecmp(direction, "both")) {
00683 flag = AST_AUDIOHOOK_MUTE_READ | AST_AUDIOHOOK_MUTE_WRITE;
00684 } else {
00685 astman_send_error(s, m, "Invalid direction specified. Must be read, write or both");
00686 return AMI_SUCCESS;
00687 }
00688
00689 if (ast_strlen_zero(name)) {
00690 astman_send_error(s, m, "No channel specified");
00691 return AMI_SUCCESS;
00692 }
00693
00694 if (ast_strlen_zero(state)) {
00695 astman_send_error(s, m, "No state specified");
00696 return AMI_SUCCESS;
00697 }
00698
00699 clearmute = ast_false(state);
00700 c = ast_channel_get_by_name(name);
00701
00702 if (!c) {
00703 astman_send_error(s, m, "No such channel");
00704 return AMI_SUCCESS;
00705 }
00706
00707 if (ast_audiohook_set_mute(c, mixmonitor_spy_type, flag, clearmute)) {
00708 c = ast_channel_unref(c);
00709 astman_send_error(s, m, "Cannot set mute flag");
00710 return AMI_SUCCESS;
00711 }
00712
00713 astman_append(s, "Response: Success\r\n");
00714
00715 if (!ast_strlen_zero(id)) {
00716 astman_append(s, "ActionID: %s\r\n", id);
00717 }
00718
00719 astman_append(s, "\r\n");
00720
00721 c = ast_channel_unref(c);
00722
00723 return AMI_SUCCESS;
00724 }
00725
00726 static struct ast_cli_entry cli_mixmonitor[] = {
00727 AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
00728 };
00729
00730 static int unload_module(void)
00731 {
00732 int res;
00733
00734 ast_cli_unregister_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
00735 res = ast_unregister_application(stop_app);
00736 res |= ast_unregister_application(app);
00737 res |= ast_manager_unregister("MixMonitorMute");
00738
00739 return res;
00740 }
00741
00742 static int load_module(void)
00743 {
00744 int res;
00745
00746 ast_cli_register_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
00747 res = ast_register_application_xml(app, mixmonitor_exec);
00748 res |= ast_register_application_xml(stop_app, stop_mixmonitor_exec);
00749 res |= ast_manager_register_xml("MixMonitorMute", 0, manager_mute_mixmonitor);
00750
00751 return res;
00752 }
00753
00754 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");