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: 309857 $")
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
00049
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 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
00130
00131 static const char *app = "MixMonitor";
00132
00133 static const char *stop_app = "StopMixMonitor";
00134
00135 struct module_symbols *me;
00136
00137 static const char *mixmonitor_spy_type = "MixMonitor";
00138
00139 struct mixmonitor {
00140 struct ast_audiohook audiohook;
00141 char *filename;
00142 char *post_process;
00143 char *name;
00144 unsigned int flags;
00145 struct mixmonitor_ds *mixmonitor_ds;
00146 };
00147
00148 enum {
00149 MUXFLAG_APPEND = (1 << 1),
00150 MUXFLAG_BRIDGED = (1 << 2),
00151 MUXFLAG_VOLUME = (1 << 3),
00152 MUXFLAG_READVOLUME = (1 << 4),
00153 MUXFLAG_WRITEVOLUME = (1 << 5),
00154 } mixmonitor_flags;
00155
00156 enum {
00157 OPT_ARG_READVOLUME = 0,
00158 OPT_ARG_WRITEVOLUME,
00159 OPT_ARG_VOLUME,
00160 OPT_ARG_ARRAY_SIZE,
00161 } mixmonitor_args;
00162
00163 AST_APP_OPTIONS(mixmonitor_opts, {
00164 AST_APP_OPTION('a', MUXFLAG_APPEND),
00165 AST_APP_OPTION('b', MUXFLAG_BRIDGED),
00166 AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
00167 AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
00168 AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
00169 });
00170
00171
00172
00173
00174
00175 struct mixmonitor_ds {
00176 struct ast_channel *chan;
00177
00178
00179
00180
00181
00182
00183
00184 unsigned int destruction_ok;
00185 ast_cond_t destruction_condition;
00186 ast_mutex_t lock;
00187
00188
00189
00190 int fs_quit;
00191 struct ast_filestream *fs;
00192 struct ast_audiohook *audiohook;
00193 };
00194
00195
00196
00197
00198
00199 static void mixmonitor_ds_close_fs(struct mixmonitor_ds *mixmonitor_ds)
00200 {
00201 if (mixmonitor_ds->fs) {
00202 ast_closestream(mixmonitor_ds->fs);
00203 mixmonitor_ds->fs = NULL;
00204 mixmonitor_ds->fs_quit = 1;
00205 ast_verb(2, "MixMonitor close filestream\n");
00206 }
00207 }
00208
00209 static void mixmonitor_ds_destroy(void *data)
00210 {
00211 struct mixmonitor_ds *mixmonitor_ds = data;
00212
00213 ast_mutex_lock(&mixmonitor_ds->lock);
00214 mixmonitor_ds->chan = NULL;
00215 mixmonitor_ds->audiohook = NULL;
00216 mixmonitor_ds->destruction_ok = 1;
00217 ast_cond_signal(&mixmonitor_ds->destruction_condition);
00218 ast_mutex_unlock(&mixmonitor_ds->lock);
00219 }
00220
00221 static void mixmonitor_ds_chan_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
00222 {
00223 struct mixmonitor_ds *mixmonitor_ds = data;
00224
00225 ast_mutex_lock(&mixmonitor_ds->lock);
00226 mixmonitor_ds->chan = new_chan;
00227 ast_mutex_unlock(&mixmonitor_ds->lock);
00228 }
00229
00230 static struct ast_datastore_info mixmonitor_ds_info = {
00231 .type = "mixmonitor",
00232 .destroy = mixmonitor_ds_destroy,
00233 .chan_fixup = mixmonitor_ds_chan_fixup,
00234 };
00235
00236 static void destroy_monitor_audiohook(struct mixmonitor *mixmonitor)
00237 {
00238 if (mixmonitor->mixmonitor_ds) {
00239 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
00240 mixmonitor->mixmonitor_ds->audiohook = NULL;
00241 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
00242 }
00243
00244 ast_audiohook_lock(&mixmonitor->audiohook);
00245 ast_audiohook_detach(&mixmonitor->audiohook);
00246 ast_audiohook_unlock(&mixmonitor->audiohook);
00247 ast_audiohook_destroy(&mixmonitor->audiohook);
00248 }
00249
00250 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
00251 {
00252 struct ast_channel *peer = NULL;
00253 int res = 0;
00254
00255 if (!chan)
00256 return -1;
00257
00258 ast_audiohook_attach(chan, audiohook);
00259
00260 if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
00261 ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
00262
00263 return res;
00264 }
00265
00266 #define SAMPLES_PER_FRAME 160
00267
00268 static void mixmonitor_free(struct mixmonitor *mixmonitor)
00269 {
00270 if (mixmonitor) {
00271 if (mixmonitor->mixmonitor_ds) {
00272 ast_mutex_destroy(&mixmonitor->mixmonitor_ds->lock);
00273 ast_cond_destroy(&mixmonitor->mixmonitor_ds->destruction_condition);
00274 ast_free(mixmonitor->mixmonitor_ds);
00275 }
00276 ast_free(mixmonitor);
00277 }
00278 }
00279
00280 static void *mixmonitor_thread(void *obj)
00281 {
00282 struct mixmonitor *mixmonitor = obj;
00283 struct ast_filestream **fs = NULL;
00284 unsigned int oflags;
00285 char *ext;
00286 char *last_slash;
00287 int errflag = 0;
00288
00289 ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name);
00290
00291 fs = &mixmonitor->mixmonitor_ds->fs;
00292
00293
00294 ast_audiohook_lock(&mixmonitor->audiohook);
00295 while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING && !mixmonitor->mixmonitor_ds->fs_quit) {
00296 struct ast_frame *fr = NULL;
00297
00298 if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR))) {
00299 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
00300
00301 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
00302 break;
00303 }
00304 continue;
00305 }
00306
00307
00308
00309 ast_audiohook_unlock(&mixmonitor->audiohook);
00310
00311 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
00312 if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || (mixmonitor->mixmonitor_ds->chan && ast_bridged_channel(mixmonitor->mixmonitor_ds->chan))) {
00313
00314 if (!*fs && !errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
00315 oflags = O_CREAT | O_WRONLY;
00316 oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
00317
00318 last_slash = strrchr(mixmonitor->filename, '/');
00319 if ((ext = strrchr(mixmonitor->filename, '.')) && (ext > last_slash))
00320 *(ext++) = '\0';
00321 else
00322 ext = "raw";
00323
00324 if (!(*fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0666))) {
00325 ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
00326 errflag = 1;
00327 }
00328 }
00329
00330
00331 if (*fs) {
00332 struct ast_frame *cur;
00333
00334 for (cur = fr; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
00335 ast_writestream(*fs, cur);
00336 }
00337 }
00338 }
00339 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
00340
00341
00342 ast_frame_free(fr, 0);
00343 ast_audiohook_lock(&mixmonitor->audiohook);
00344 }
00345
00346 ast_audiohook_unlock(&mixmonitor->audiohook);
00347
00348
00349 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
00350 mixmonitor_ds_close_fs(mixmonitor->mixmonitor_ds);
00351 if (!mixmonitor->mixmonitor_ds->destruction_ok) {
00352 ast_cond_wait(&mixmonitor->mixmonitor_ds->destruction_condition, &mixmonitor->mixmonitor_ds->lock);
00353 }
00354 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
00355
00356
00357 destroy_monitor_audiohook(mixmonitor);
00358
00359 if (mixmonitor->post_process) {
00360 ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
00361 ast_safe_system(mixmonitor->post_process);
00362 }
00363
00364 ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
00365 mixmonitor_free(mixmonitor);
00366 return NULL;
00367 }
00368
00369 static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan)
00370 {
00371 struct ast_datastore *datastore = NULL;
00372 struct mixmonitor_ds *mixmonitor_ds;
00373
00374 if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
00375 return -1;
00376 }
00377
00378 ast_mutex_init(&mixmonitor_ds->lock);
00379 ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
00380
00381 if (!(datastore = ast_datastore_alloc(&mixmonitor_ds_info, NULL))) {
00382 ast_mutex_destroy(&mixmonitor_ds->lock);
00383 ast_cond_destroy(&mixmonitor_ds->destruction_condition);
00384 ast_free(mixmonitor_ds);
00385 return -1;
00386 }
00387
00388
00389 mixmonitor_ds->chan = chan;
00390 mixmonitor_ds->audiohook = &mixmonitor->audiohook;
00391 datastore->data = mixmonitor_ds;
00392
00393 ast_channel_lock(chan);
00394 ast_channel_datastore_add(chan, datastore);
00395 ast_channel_unlock(chan);
00396
00397 mixmonitor->mixmonitor_ds = mixmonitor_ds;
00398 return 0;
00399 }
00400
00401 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
00402 int readvol, int writevol, const char *post_process)
00403 {
00404 pthread_t thread;
00405 struct mixmonitor *mixmonitor;
00406 char postprocess2[1024] = "";
00407 size_t len;
00408
00409 len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
00410
00411 postprocess2[0] = 0;
00412
00413 if (!ast_strlen_zero(post_process)) {
00414 char *p1, *p2;
00415
00416 p1 = ast_strdupa(post_process);
00417 for (p2 = p1; *p2 ; p2++) {
00418 if (*p2 == '^' && *(p2+1) == '{') {
00419 *p2 = '$';
00420 }
00421 }
00422 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
00423 if (!ast_strlen_zero(postprocess2))
00424 len += strlen(postprocess2) + 1;
00425 }
00426
00427
00428 if (!(mixmonitor = ast_calloc(1, len))) {
00429 return;
00430 }
00431
00432
00433 if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
00434 mixmonitor_free(mixmonitor);
00435 return;
00436 }
00437
00438
00439 mixmonitor->flags = flags;
00440 if (setup_mixmonitor_ds(mixmonitor, chan)) {
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, void *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, void *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_get_channel_by_name_prefix_locked(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 if (!strcasecmp(a->argv[1], "start")) {
00616 mixmonitor_exec(chan, a->argv[3]);
00617 ast_channel_unlock(chan);
00618 } else {
00619 ast_channel_unlock(chan);
00620 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00621 }
00622
00623 return CLI_SUCCESS;
00624 }
00625
00626 static struct ast_cli_entry cli_mixmonitor[] = {
00627 AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
00628 };
00629
00630 static int unload_module(void)
00631 {
00632 int res;
00633
00634 ast_cli_unregister_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
00635 res = ast_unregister_application(stop_app);
00636 res |= ast_unregister_application(app);
00637
00638 return res;
00639 }
00640
00641 static int load_module(void)
00642 {
00643 int res;
00644
00645 ast_cli_register_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
00646 res = ast_register_application_xml(app, mixmonitor_exec);
00647 res |= ast_register_application_xml(stop_app, stop_mixmonitor_exec);
00648
00649 return res;
00650 }
00651
00652 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");