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: 230511 $")
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 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
00050
00051 static const char *app = "MixMonitor";
00052 static const char *synopsis = "Record a call and mix the audio during the recording";
00053 static const char *desc = ""
00054 " MixMonitor(<file>.<ext>[,<options>[,<command>]]):\n"
00055 "Records the audio on the current channel to the specified file.\n"
00056 "If the filename is an absolute path, uses that path, otherwise\n"
00057 "creates the file in the configured monitoring directory from\n"
00058 "asterisk.conf. Use of StopMixMonitor is required to guarantee\n"
00059 "the audio file is available for processing during dialplan execution.\n\n"
00060 "Valid options:\n"
00061 " a - Append to the file instead of overwriting it.\n"
00062 " b - Only save audio to the file while the channel is bridged.\n"
00063 " Note: Does not include conferences or sounds played to each bridged\n"
00064 " party.\n"
00065 " Note: If you utilize this option inside a Local channel, you must\n"
00066 " make sure the Local channel is not optimized away. To do this,\n"
00067 " be sure to call your Local channel with the '/n' option.\n"
00068 " For example: Dial(Local/start@mycontext/n)\n"
00069 " v(<x>) - Adjust the heard volume by a factor of <x> (range -4 to 4)\n"
00070 " V(<x>) - Adjust the spoken volume by a factor of <x> (range -4 to 4)\n"
00071 " W(<x>) - Adjust the both heard and spoken volumes by a factor of <x>\n"
00072 " (range -4 to 4)\n\n"
00073 "<command> will be executed when the recording is over\n"
00074 "Any strings matching ^{X} will be unescaped to ${X}.\n"
00075 "All variables will be evaluated at the time MixMonitor is called.\n"
00076 "The variable MIXMONITOR_FILENAME will contain the filename used to record.\n"
00077 "";
00078
00079 static const char *stop_app = "StopMixMonitor";
00080 static const char *stop_synopsis = "Stop recording a call through MixMonitor";
00081 static const char *stop_desc = ""
00082 " StopMixMonitor():\n"
00083 "Stop recording a call through MixMonitor, and free the recording's file handle.\n"
00084 "";
00085
00086 struct module_symbols *me;
00087
00088 static const char *mixmonitor_spy_type = "MixMonitor";
00089
00090 struct mixmonitor {
00091 struct ast_audiohook audiohook;
00092 char *filename;
00093 char *post_process;
00094 char *name;
00095 unsigned int flags;
00096 struct mixmonitor_ds *mixmonitor_ds;
00097 };
00098
00099 enum {
00100 MUXFLAG_APPEND = (1 << 1),
00101 MUXFLAG_BRIDGED = (1 << 2),
00102 MUXFLAG_VOLUME = (1 << 3),
00103 MUXFLAG_READVOLUME = (1 << 4),
00104 MUXFLAG_WRITEVOLUME = (1 << 5),
00105 } mixmonitor_flags;
00106
00107 enum {
00108 OPT_ARG_READVOLUME = 0,
00109 OPT_ARG_WRITEVOLUME,
00110 OPT_ARG_VOLUME,
00111 OPT_ARG_ARRAY_SIZE,
00112 } mixmonitor_args;
00113
00114 AST_APP_OPTIONS(mixmonitor_opts, {
00115 AST_APP_OPTION('a', MUXFLAG_APPEND),
00116 AST_APP_OPTION('b', MUXFLAG_BRIDGED),
00117 AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
00118 AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
00119 AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
00120 });
00121
00122
00123
00124
00125
00126 struct mixmonitor_ds {
00127 struct ast_channel *chan;
00128
00129
00130
00131
00132
00133
00134
00135 unsigned int destruction_ok;
00136 ast_cond_t destruction_condition;
00137 ast_mutex_t lock;
00138
00139
00140
00141 int fs_quit;
00142 struct ast_filestream *fs;
00143 struct ast_audiohook *audiohook;
00144 };
00145
00146
00147
00148
00149
00150 static void mixmonitor_ds_close_fs(struct mixmonitor_ds *mixmonitor_ds)
00151 {
00152 if (mixmonitor_ds->fs) {
00153 ast_closestream(mixmonitor_ds->fs);
00154 mixmonitor_ds->fs = NULL;
00155 mixmonitor_ds->fs_quit = 1;
00156 ast_verb(2, "MixMonitor close filestream\n");
00157 }
00158 }
00159
00160 static void mixmonitor_ds_destroy(void *data)
00161 {
00162 struct mixmonitor_ds *mixmonitor_ds = data;
00163
00164 ast_mutex_lock(&mixmonitor_ds->lock);
00165 mixmonitor_ds->chan = NULL;
00166 mixmonitor_ds->audiohook = NULL;
00167 mixmonitor_ds->destruction_ok = 1;
00168 ast_cond_signal(&mixmonitor_ds->destruction_condition);
00169 ast_mutex_unlock(&mixmonitor_ds->lock);
00170 }
00171
00172 static void mixmonitor_ds_chan_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
00173 {
00174 struct mixmonitor_ds *mixmonitor_ds = data;
00175
00176 ast_mutex_lock(&mixmonitor_ds->lock);
00177 mixmonitor_ds->chan = new_chan;
00178 ast_mutex_unlock(&mixmonitor_ds->lock);
00179 }
00180
00181 static struct ast_datastore_info mixmonitor_ds_info = {
00182 .type = "mixmonitor",
00183 .destroy = mixmonitor_ds_destroy,
00184 .chan_fixup = mixmonitor_ds_chan_fixup,
00185 };
00186
00187 static void destroy_monitor_audiohook(struct mixmonitor *mixmonitor)
00188 {
00189 if (mixmonitor->mixmonitor_ds) {
00190 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
00191 mixmonitor->mixmonitor_ds->audiohook = NULL;
00192 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
00193 }
00194
00195 ast_audiohook_lock(&mixmonitor->audiohook);
00196 ast_audiohook_detach(&mixmonitor->audiohook);
00197 ast_audiohook_unlock(&mixmonitor->audiohook);
00198 ast_audiohook_destroy(&mixmonitor->audiohook);
00199 }
00200
00201 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
00202 {
00203 struct ast_channel *peer = NULL;
00204 int res = 0;
00205
00206 if (!chan)
00207 return -1;
00208
00209 ast_audiohook_attach(chan, audiohook);
00210
00211 if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
00212 ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
00213
00214 return res;
00215 }
00216
00217 #define SAMPLES_PER_FRAME 160
00218
00219 static void mixmonitor_free(struct mixmonitor *mixmonitor)
00220 {
00221 if (mixmonitor) {
00222 if (mixmonitor->mixmonitor_ds) {
00223 ast_mutex_destroy(&mixmonitor->mixmonitor_ds->lock);
00224 ast_cond_destroy(&mixmonitor->mixmonitor_ds->destruction_condition);
00225 ast_free(mixmonitor->mixmonitor_ds);
00226 }
00227 ast_free(mixmonitor);
00228 }
00229 }
00230
00231 static void *mixmonitor_thread(void *obj)
00232 {
00233 struct mixmonitor *mixmonitor = obj;
00234 struct ast_filestream **fs = NULL;
00235 unsigned int oflags;
00236 char *ext;
00237 int errflag = 0;
00238
00239 ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name);
00240
00241 fs = &mixmonitor->mixmonitor_ds->fs;
00242
00243
00244 ast_audiohook_lock(&mixmonitor->audiohook);
00245 while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING && !mixmonitor->mixmonitor_ds->fs_quit) {
00246 struct ast_frame *fr = NULL;
00247
00248 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
00249
00250 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING)
00251 break;
00252
00253 if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR)))
00254 continue;
00255
00256
00257
00258 ast_audiohook_unlock(&mixmonitor->audiohook);
00259
00260 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
00261 if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || (mixmonitor->mixmonitor_ds->chan && ast_bridged_channel(mixmonitor->mixmonitor_ds->chan))) {
00262
00263 if (!*fs && !errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
00264 oflags = O_CREAT | O_WRONLY;
00265 oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
00266
00267 if ((ext = strrchr(mixmonitor->filename, '.')))
00268 *(ext++) = '\0';
00269 else
00270 ext = "raw";
00271
00272 if (!(*fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0666))) {
00273 ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
00274 errflag = 1;
00275 }
00276 }
00277
00278
00279 if (*fs) {
00280 struct ast_frame *cur;
00281
00282 for (cur = fr; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
00283 ast_writestream(*fs, cur);
00284 }
00285 }
00286 }
00287 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
00288
00289
00290 ast_frame_free(fr, 0);
00291 ast_audiohook_lock(&mixmonitor->audiohook);
00292 }
00293
00294 ast_audiohook_unlock(&mixmonitor->audiohook);
00295
00296
00297 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
00298 mixmonitor_ds_close_fs(mixmonitor->mixmonitor_ds);
00299 if (!mixmonitor->mixmonitor_ds->destruction_ok) {
00300 ast_cond_wait(&mixmonitor->mixmonitor_ds->destruction_condition, &mixmonitor->mixmonitor_ds->lock);
00301 }
00302 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
00303
00304
00305 destroy_monitor_audiohook(mixmonitor);
00306
00307 if (mixmonitor->post_process) {
00308 ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
00309 ast_safe_system(mixmonitor->post_process);
00310 }
00311
00312 ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
00313 mixmonitor_free(mixmonitor);
00314 return NULL;
00315 }
00316
00317 static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan)
00318 {
00319 struct ast_datastore *datastore = NULL;
00320 struct mixmonitor_ds *mixmonitor_ds;
00321
00322 if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
00323 return -1;
00324 }
00325
00326 ast_mutex_init(&mixmonitor_ds->lock);
00327 ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
00328
00329 if (!(datastore = ast_datastore_alloc(&mixmonitor_ds_info, NULL))) {
00330 ast_mutex_destroy(&mixmonitor_ds->lock);
00331 ast_cond_destroy(&mixmonitor_ds->destruction_condition);
00332 ast_free(mixmonitor_ds);
00333 return -1;
00334 }
00335
00336
00337 mixmonitor_ds->chan = chan;
00338 mixmonitor_ds->audiohook = &mixmonitor->audiohook;
00339 datastore->data = mixmonitor_ds;
00340
00341 ast_channel_lock(chan);
00342 ast_channel_datastore_add(chan, datastore);
00343 ast_channel_unlock(chan);
00344
00345 mixmonitor->mixmonitor_ds = mixmonitor_ds;
00346 return 0;
00347 }
00348
00349 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
00350 int readvol, int writevol, const char *post_process)
00351 {
00352 pthread_t thread;
00353 struct mixmonitor *mixmonitor;
00354 char postprocess2[1024] = "";
00355 size_t len;
00356
00357 len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
00358
00359 postprocess2[0] = 0;
00360
00361 if (!ast_strlen_zero(post_process)) {
00362 char *p1, *p2;
00363
00364 p1 = ast_strdupa(post_process);
00365 for (p2 = p1; *p2 ; p2++) {
00366 if (*p2 == '^' && *(p2+1) == '{') {
00367 *p2 = '$';
00368 }
00369 }
00370 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
00371 if (!ast_strlen_zero(postprocess2))
00372 len += strlen(postprocess2) + 1;
00373 }
00374
00375
00376 if (!(mixmonitor = ast_calloc(1, len))) {
00377 return;
00378 }
00379
00380
00381 if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
00382 mixmonitor_free(mixmonitor);
00383 return;
00384 }
00385
00386
00387 mixmonitor->flags = flags;
00388 if (setup_mixmonitor_ds(mixmonitor, chan)) {
00389 mixmonitor_free(mixmonitor);
00390 return;
00391 }
00392 mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
00393 strcpy(mixmonitor->name, chan->name);
00394 if (!ast_strlen_zero(postprocess2)) {
00395 mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
00396 strcpy(mixmonitor->post_process, postprocess2);
00397 }
00398
00399 mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
00400 strcpy(mixmonitor->filename, filename);
00401
00402 ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
00403
00404 if (readvol)
00405 mixmonitor->audiohook.options.read_volume = readvol;
00406 if (writevol)
00407 mixmonitor->audiohook.options.write_volume = writevol;
00408
00409 if (startmon(chan, &mixmonitor->audiohook)) {
00410 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
00411 mixmonitor_spy_type, chan->name);
00412 ast_audiohook_destroy(&mixmonitor->audiohook);
00413 mixmonitor_free(mixmonitor);
00414 return;
00415 }
00416
00417 ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
00418 }
00419
00420 static int mixmonitor_exec(struct ast_channel *chan, void *data)
00421 {
00422 int x, readvol = 0, writevol = 0;
00423 struct ast_flags flags = {0};
00424 char *parse, *tmp, *slash;
00425 AST_DECLARE_APP_ARGS(args,
00426 AST_APP_ARG(filename);
00427 AST_APP_ARG(options);
00428 AST_APP_ARG(post_process);
00429 );
00430
00431 if (ast_strlen_zero(data)) {
00432 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00433 return -1;
00434 }
00435
00436 parse = ast_strdupa(data);
00437
00438 AST_STANDARD_APP_ARGS(args, parse);
00439
00440 if (ast_strlen_zero(args.filename)) {
00441 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00442 return -1;
00443 }
00444
00445 if (args.options) {
00446 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
00447
00448 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
00449
00450 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
00451 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
00452 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
00453 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
00454 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
00455 } else {
00456 readvol = get_volfactor(x);
00457 }
00458 }
00459
00460 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
00461 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
00462 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
00463 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
00464 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
00465 } else {
00466 writevol = get_volfactor(x);
00467 }
00468 }
00469
00470 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
00471 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
00472 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
00473 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
00474 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
00475 } else {
00476 readvol = writevol = get_volfactor(x);
00477 }
00478 }
00479 }
00480
00481
00482 if (args.filename[0] != '/') {
00483 char *build;
00484
00485 build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
00486 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
00487 args.filename = build;
00488 }
00489
00490 tmp = ast_strdupa(args.filename);
00491 if ((slash = strrchr(tmp, '/')))
00492 *slash = '\0';
00493 ast_mkdir(tmp, 0777);
00494
00495 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
00496 launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
00497
00498 return 0;
00499 }
00500
00501 static int stop_mixmonitor_exec(struct ast_channel *chan, void *data)
00502 {
00503 struct ast_datastore *datastore = NULL;
00504
00505 ast_channel_lock(chan);
00506 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00507 if ((datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, NULL))) {
00508 struct mixmonitor_ds *mixmonitor_ds = datastore->data;
00509
00510 ast_mutex_lock(&mixmonitor_ds->lock);
00511
00512
00513
00514 mixmonitor_ds_close_fs(mixmonitor_ds);
00515
00516
00517
00518
00519 if (mixmonitor_ds->audiohook) {
00520 ast_audiohook_lock(mixmonitor_ds->audiohook);
00521 ast_cond_signal(&mixmonitor_ds->audiohook->trigger);
00522 ast_audiohook_unlock(mixmonitor_ds->audiohook);
00523 mixmonitor_ds->audiohook = NULL;
00524 }
00525
00526 ast_mutex_unlock(&mixmonitor_ds->lock);
00527
00528
00529 if (!ast_channel_datastore_remove(chan, datastore)) {
00530 ast_datastore_free(datastore);
00531 }
00532 }
00533 ast_channel_unlock(chan);
00534
00535 return 0;
00536 }
00537
00538 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00539 {
00540 struct ast_channel *chan;
00541
00542 switch (cmd) {
00543 case CLI_INIT:
00544 e->command = "mixmonitor [start|stop]";
00545 e->usage =
00546 "Usage: mixmonitor <start|stop> <chan_name> [args]\n"
00547 " The optional arguments are passed to the MixMonitor\n"
00548 " application when the 'start' command is used.\n";
00549 return NULL;
00550 case CLI_GENERATE:
00551 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
00552 }
00553
00554 if (a->argc < 3)
00555 return CLI_SHOWUSAGE;
00556
00557 if (!(chan = ast_get_channel_by_name_prefix_locked(a->argv[2], strlen(a->argv[2])))) {
00558 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
00559
00560 return CLI_SUCCESS;
00561 }
00562
00563 if (!strcasecmp(a->argv[1], "start")) {
00564 mixmonitor_exec(chan, a->argv[3]);
00565 ast_channel_unlock(chan);
00566 } else {
00567 ast_channel_unlock(chan);
00568 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00569 }
00570
00571 return CLI_SUCCESS;
00572 }
00573
00574 static struct ast_cli_entry cli_mixmonitor[] = {
00575 AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
00576 };
00577
00578 static int unload_module(void)
00579 {
00580 int res;
00581
00582 ast_cli_unregister_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
00583 res = ast_unregister_application(stop_app);
00584 res |= ast_unregister_application(app);
00585
00586 return res;
00587 }
00588
00589 static int load_module(void)
00590 {
00591 int res;
00592
00593 ast_cli_register_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
00594 res = ast_register_application(app, mixmonitor_exec, synopsis, desc);
00595 res |= ast_register_application(stop_app, stop_mixmonitor_exec, stop_synopsis, stop_desc);
00596
00597 return res;
00598 }
00599
00600 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");