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: 257739 $")
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 if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR))) {
00249 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
00250
00251 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
00252 break;
00253 }
00254 continue;
00255 }
00256
00257
00258
00259 ast_audiohook_unlock(&mixmonitor->audiohook);
00260
00261 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
00262 if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || (mixmonitor->mixmonitor_ds->chan && ast_bridged_channel(mixmonitor->mixmonitor_ds->chan))) {
00263
00264 if (!*fs && !errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
00265 oflags = O_CREAT | O_WRONLY;
00266 oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
00267
00268 if ((ext = strrchr(mixmonitor->filename, '.')))
00269 *(ext++) = '\0';
00270 else
00271 ext = "raw";
00272
00273 if (!(*fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0666))) {
00274 ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
00275 errflag = 1;
00276 }
00277 }
00278
00279
00280 if (*fs) {
00281 struct ast_frame *cur;
00282
00283 for (cur = fr; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
00284 ast_writestream(*fs, cur);
00285 }
00286 }
00287 }
00288 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
00289
00290
00291 ast_frame_free(fr, 0);
00292 ast_audiohook_lock(&mixmonitor->audiohook);
00293 }
00294
00295 ast_audiohook_unlock(&mixmonitor->audiohook);
00296
00297
00298 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
00299 mixmonitor_ds_close_fs(mixmonitor->mixmonitor_ds);
00300 if (!mixmonitor->mixmonitor_ds->destruction_ok) {
00301 ast_cond_wait(&mixmonitor->mixmonitor_ds->destruction_condition, &mixmonitor->mixmonitor_ds->lock);
00302 }
00303 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
00304
00305
00306 destroy_monitor_audiohook(mixmonitor);
00307
00308 if (mixmonitor->post_process) {
00309 ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
00310 ast_safe_system(mixmonitor->post_process);
00311 }
00312
00313 ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
00314 mixmonitor_free(mixmonitor);
00315 return NULL;
00316 }
00317
00318 static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan)
00319 {
00320 struct ast_datastore *datastore = NULL;
00321 struct mixmonitor_ds *mixmonitor_ds;
00322
00323 if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
00324 return -1;
00325 }
00326
00327 ast_mutex_init(&mixmonitor_ds->lock);
00328 ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
00329
00330 if (!(datastore = ast_datastore_alloc(&mixmonitor_ds_info, NULL))) {
00331 ast_mutex_destroy(&mixmonitor_ds->lock);
00332 ast_cond_destroy(&mixmonitor_ds->destruction_condition);
00333 ast_free(mixmonitor_ds);
00334 return -1;
00335 }
00336
00337
00338 mixmonitor_ds->chan = chan;
00339 mixmonitor_ds->audiohook = &mixmonitor->audiohook;
00340 datastore->data = mixmonitor_ds;
00341
00342 ast_channel_lock(chan);
00343 ast_channel_datastore_add(chan, datastore);
00344 ast_channel_unlock(chan);
00345
00346 mixmonitor->mixmonitor_ds = mixmonitor_ds;
00347 return 0;
00348 }
00349
00350 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
00351 int readvol, int writevol, const char *post_process)
00352 {
00353 pthread_t thread;
00354 struct mixmonitor *mixmonitor;
00355 char postprocess2[1024] = "";
00356 size_t len;
00357
00358 len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
00359
00360 postprocess2[0] = 0;
00361
00362 if (!ast_strlen_zero(post_process)) {
00363 char *p1, *p2;
00364
00365 p1 = ast_strdupa(post_process);
00366 for (p2 = p1; *p2 ; p2++) {
00367 if (*p2 == '^' && *(p2+1) == '{') {
00368 *p2 = '$';
00369 }
00370 }
00371 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
00372 if (!ast_strlen_zero(postprocess2))
00373 len += strlen(postprocess2) + 1;
00374 }
00375
00376
00377 if (!(mixmonitor = ast_calloc(1, len))) {
00378 return;
00379 }
00380
00381
00382 if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
00383 mixmonitor_free(mixmonitor);
00384 return;
00385 }
00386
00387
00388 mixmonitor->flags = flags;
00389 if (setup_mixmonitor_ds(mixmonitor, chan)) {
00390 mixmonitor_free(mixmonitor);
00391 return;
00392 }
00393 mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
00394 strcpy(mixmonitor->name, chan->name);
00395 if (!ast_strlen_zero(postprocess2)) {
00396 mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
00397 strcpy(mixmonitor->post_process, postprocess2);
00398 }
00399
00400 mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
00401 strcpy(mixmonitor->filename, filename);
00402
00403 ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
00404
00405 if (readvol)
00406 mixmonitor->audiohook.options.read_volume = readvol;
00407 if (writevol)
00408 mixmonitor->audiohook.options.write_volume = writevol;
00409
00410 if (startmon(chan, &mixmonitor->audiohook)) {
00411 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
00412 mixmonitor_spy_type, chan->name);
00413 ast_audiohook_destroy(&mixmonitor->audiohook);
00414 mixmonitor_free(mixmonitor);
00415 return;
00416 }
00417
00418 ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
00419 }
00420
00421 static int mixmonitor_exec(struct ast_channel *chan, void *data)
00422 {
00423 int x, readvol = 0, writevol = 0;
00424 struct ast_flags flags = {0};
00425 char *parse, *tmp, *slash;
00426 AST_DECLARE_APP_ARGS(args,
00427 AST_APP_ARG(filename);
00428 AST_APP_ARG(options);
00429 AST_APP_ARG(post_process);
00430 );
00431
00432 if (ast_strlen_zero(data)) {
00433 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00434 return -1;
00435 }
00436
00437 parse = ast_strdupa(data);
00438
00439 AST_STANDARD_APP_ARGS(args, parse);
00440
00441 if (ast_strlen_zero(args.filename)) {
00442 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00443 return -1;
00444 }
00445
00446 if (args.options) {
00447 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
00448
00449 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
00450
00451 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
00452 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
00453 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
00454 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
00455 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
00456 } else {
00457 readvol = get_volfactor(x);
00458 }
00459 }
00460
00461 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
00462 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
00463 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
00464 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
00465 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
00466 } else {
00467 writevol = get_volfactor(x);
00468 }
00469 }
00470
00471 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
00472 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
00473 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
00474 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
00475 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
00476 } else {
00477 readvol = writevol = get_volfactor(x);
00478 }
00479 }
00480 }
00481
00482
00483 if (args.filename[0] != '/') {
00484 char *build;
00485
00486 build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
00487 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
00488 args.filename = build;
00489 }
00490
00491 tmp = ast_strdupa(args.filename);
00492 if ((slash = strrchr(tmp, '/')))
00493 *slash = '\0';
00494 ast_mkdir(tmp, 0777);
00495
00496 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
00497 launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
00498
00499 return 0;
00500 }
00501
00502 static int stop_mixmonitor_exec(struct ast_channel *chan, void *data)
00503 {
00504 struct ast_datastore *datastore = NULL;
00505
00506 ast_channel_lock(chan);
00507 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00508 if ((datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, NULL))) {
00509 struct mixmonitor_ds *mixmonitor_ds = datastore->data;
00510
00511 ast_mutex_lock(&mixmonitor_ds->lock);
00512
00513
00514
00515 mixmonitor_ds_close_fs(mixmonitor_ds);
00516
00517
00518
00519
00520 if (mixmonitor_ds->audiohook) {
00521 ast_audiohook_lock(mixmonitor_ds->audiohook);
00522 ast_cond_signal(&mixmonitor_ds->audiohook->trigger);
00523 ast_audiohook_unlock(mixmonitor_ds->audiohook);
00524 mixmonitor_ds->audiohook = NULL;
00525 }
00526
00527 ast_mutex_unlock(&mixmonitor_ds->lock);
00528
00529
00530 if (!ast_channel_datastore_remove(chan, datastore)) {
00531 ast_datastore_free(datastore);
00532 }
00533 }
00534 ast_channel_unlock(chan);
00535
00536 return 0;
00537 }
00538
00539 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00540 {
00541 struct ast_channel *chan;
00542
00543 switch (cmd) {
00544 case CLI_INIT:
00545 e->command = "mixmonitor [start|stop]";
00546 e->usage =
00547 "Usage: mixmonitor <start|stop> <chan_name> [args]\n"
00548 " The optional arguments are passed to the MixMonitor\n"
00549 " application when the 'start' command is used.\n";
00550 return NULL;
00551 case CLI_GENERATE:
00552 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
00553 }
00554
00555 if (a->argc < 3)
00556 return CLI_SHOWUSAGE;
00557
00558 if (!(chan = ast_get_channel_by_name_prefix_locked(a->argv[2], strlen(a->argv[2])))) {
00559 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
00560
00561 return CLI_SUCCESS;
00562 }
00563
00564 if (!strcasecmp(a->argv[1], "start")) {
00565 mixmonitor_exec(chan, a->argv[3]);
00566 ast_channel_unlock(chan);
00567 } else {
00568 ast_channel_unlock(chan);
00569 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00570 }
00571
00572 return CLI_SUCCESS;
00573 }
00574
00575 static struct ast_cli_entry cli_mixmonitor[] = {
00576 AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
00577 };
00578
00579 static int unload_module(void)
00580 {
00581 int res;
00582
00583 ast_cli_unregister_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
00584 res = ast_unregister_application(stop_app);
00585 res |= ast_unregister_application(app);
00586
00587 return res;
00588 }
00589
00590 static int load_module(void)
00591 {
00592 int res;
00593
00594 ast_cli_register_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
00595 res = ast_register_application(app, mixmonitor_exec, synopsis, desc);
00596 res |= ast_register_application(stop_app, stop_mixmonitor_exec, stop_synopsis, stop_desc);
00597
00598 return res;
00599 }
00600
00601 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");