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: 201449 $")
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 };
00144
00145 static void mixmonitor_ds_close_fs(struct mixmonitor_ds *mixmonitor_ds)
00146 {
00147 ast_mutex_lock(&mixmonitor_ds->lock);
00148 if (mixmonitor_ds->fs) {
00149 ast_closestream(mixmonitor_ds->fs);
00150 mixmonitor_ds->fs = NULL;
00151 mixmonitor_ds->fs_quit = 1;
00152 ast_verb(2, "MixMonitor close filestream\n");
00153 }
00154 ast_mutex_unlock(&mixmonitor_ds->lock);
00155 }
00156
00157 static void mixmonitor_ds_destroy(void *data)
00158 {
00159 struct mixmonitor_ds *mixmonitor_ds = data;
00160
00161 ast_mutex_lock(&mixmonitor_ds->lock);
00162 mixmonitor_ds->chan = NULL;
00163 mixmonitor_ds->destruction_ok = 1;
00164 ast_cond_signal(&mixmonitor_ds->destruction_condition);
00165 ast_mutex_unlock(&mixmonitor_ds->lock);
00166 }
00167
00168 static void mixmonitor_ds_chan_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
00169 {
00170 struct mixmonitor_ds *mixmonitor_ds = data;
00171
00172 ast_mutex_lock(&mixmonitor_ds->lock);
00173 mixmonitor_ds->chan = new_chan;
00174 ast_mutex_unlock(&mixmonitor_ds->lock);
00175 }
00176
00177 static struct ast_datastore_info mixmonitor_ds_info = {
00178 .type = "mixmonitor",
00179 .destroy = mixmonitor_ds_destroy,
00180 .chan_fixup = mixmonitor_ds_chan_fixup,
00181 };
00182
00183 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
00184 {
00185 struct ast_channel *peer = NULL;
00186 int res = 0;
00187
00188 if (!chan)
00189 return -1;
00190
00191 ast_audiohook_attach(chan, audiohook);
00192
00193 if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
00194 ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
00195
00196 return res;
00197 }
00198
00199 #define SAMPLES_PER_FRAME 160
00200
00201 static void mixmonitor_free(struct mixmonitor *mixmonitor)
00202 {
00203 if (mixmonitor) {
00204 if (mixmonitor->mixmonitor_ds) {
00205 ast_mutex_destroy(&mixmonitor->mixmonitor_ds->lock);
00206 ast_cond_destroy(&mixmonitor->mixmonitor_ds->destruction_condition);
00207 ast_free(mixmonitor->mixmonitor_ds);
00208 }
00209 ast_free(mixmonitor);
00210 }
00211 }
00212
00213 static void *mixmonitor_thread(void *obj)
00214 {
00215 struct mixmonitor *mixmonitor = obj;
00216 struct ast_filestream **fs = NULL;
00217 unsigned int oflags;
00218 char *ext;
00219 int errflag = 0;
00220
00221 ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name);
00222
00223 ast_audiohook_lock(&mixmonitor->audiohook);
00224
00225 fs = &mixmonitor->mixmonitor_ds->fs;
00226
00227 while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING && !mixmonitor->mixmonitor_ds->fs_quit) {
00228 struct ast_frame *fr = NULL;
00229
00230 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
00231
00232 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING)
00233 break;
00234
00235 if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR)))
00236 continue;
00237
00238 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
00239 if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || (mixmonitor->mixmonitor_ds->chan && ast_bridged_channel(mixmonitor->mixmonitor_ds->chan))) {
00240
00241 if (!*fs && !errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
00242 oflags = O_CREAT | O_WRONLY;
00243 oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
00244
00245 if ((ext = strrchr(mixmonitor->filename, '.')))
00246 *(ext++) = '\0';
00247 else
00248 ext = "raw";
00249
00250 if (!(*fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0644))) {
00251 ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
00252 errflag = 1;
00253 }
00254 }
00255
00256
00257 if (*fs) {
00258 struct ast_frame *cur;
00259
00260 for (cur = fr; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
00261 ast_writestream(*fs, cur);
00262 }
00263 }
00264 }
00265 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
00266
00267
00268 ast_frame_free(fr, 0);
00269
00270 }
00271
00272 ast_audiohook_detach(&mixmonitor->audiohook);
00273 ast_audiohook_unlock(&mixmonitor->audiohook);
00274 ast_audiohook_destroy(&mixmonitor->audiohook);
00275
00276 mixmonitor_ds_close_fs(mixmonitor->mixmonitor_ds);
00277
00278 if (mixmonitor->post_process) {
00279 ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
00280 ast_safe_system(mixmonitor->post_process);
00281 }
00282
00283 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
00284 if (!mixmonitor->mixmonitor_ds->destruction_ok) {
00285 ast_cond_wait(&mixmonitor->mixmonitor_ds->destruction_condition, &mixmonitor->mixmonitor_ds->lock);
00286 }
00287 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
00288
00289 ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
00290 mixmonitor_free(mixmonitor);
00291 return NULL;
00292 }
00293
00294 static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan)
00295 {
00296 struct ast_datastore *datastore = NULL;
00297 struct mixmonitor_ds *mixmonitor_ds;
00298
00299 if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
00300 return -1;
00301 }
00302
00303 ast_mutex_init(&mixmonitor_ds->lock);
00304 ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
00305
00306 if (!(datastore = ast_channel_datastore_alloc(&mixmonitor_ds_info, NULL))) {
00307 ast_mutex_destroy(&mixmonitor_ds->lock);
00308 ast_cond_destroy(&mixmonitor_ds->destruction_condition);
00309 ast_free(mixmonitor_ds);
00310 return -1;
00311 }
00312
00313
00314 mixmonitor_ds->chan = chan;
00315 datastore->data = mixmonitor_ds;
00316
00317 ast_channel_lock(chan);
00318 ast_channel_datastore_add(chan, datastore);
00319 ast_channel_unlock(chan);
00320
00321 mixmonitor->mixmonitor_ds = mixmonitor_ds;
00322 return 0;
00323 }
00324
00325 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
00326 int readvol, int writevol, const char *post_process)
00327 {
00328 pthread_t thread;
00329 struct mixmonitor *mixmonitor;
00330 char postprocess2[1024] = "";
00331 size_t len;
00332
00333 len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
00334
00335 postprocess2[0] = 0;
00336
00337 if (!ast_strlen_zero(post_process)) {
00338 char *p1, *p2;
00339
00340 p1 = ast_strdupa(post_process);
00341 for (p2 = p1; *p2 ; p2++) {
00342 if (*p2 == '^' && *(p2+1) == '{') {
00343 *p2 = '$';
00344 }
00345 }
00346 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
00347 if (!ast_strlen_zero(postprocess2))
00348 len += strlen(postprocess2) + 1;
00349 }
00350
00351
00352 if (!(mixmonitor = ast_calloc(1, len))) {
00353 return;
00354 }
00355
00356
00357 mixmonitor->flags = flags;
00358 if (setup_mixmonitor_ds(mixmonitor, chan)) {
00359 mixmonitor_free(mixmonitor);
00360 return;
00361 }
00362 mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
00363 strcpy(mixmonitor->name, chan->name);
00364 if (!ast_strlen_zero(postprocess2)) {
00365 mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
00366 strcpy(mixmonitor->post_process, postprocess2);
00367 }
00368
00369 mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
00370 strcpy(mixmonitor->filename, filename);
00371
00372
00373 if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
00374 ast_free(mixmonitor);
00375 return;
00376 }
00377
00378 ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
00379
00380 if (readvol)
00381 mixmonitor->audiohook.options.read_volume = readvol;
00382 if (writevol)
00383 mixmonitor->audiohook.options.write_volume = writevol;
00384
00385 if (startmon(chan, &mixmonitor->audiohook)) {
00386 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
00387 mixmonitor_spy_type, chan->name);
00388 ast_audiohook_destroy(&mixmonitor->audiohook);
00389 ast_free(mixmonitor);
00390 return;
00391 }
00392
00393 ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
00394 }
00395
00396 static int mixmonitor_exec(struct ast_channel *chan, void *data)
00397 {
00398 int x, readvol = 0, writevol = 0;
00399 struct ast_flags flags = {0};
00400 char *parse, *tmp, *slash;
00401 AST_DECLARE_APP_ARGS(args,
00402 AST_APP_ARG(filename);
00403 AST_APP_ARG(options);
00404 AST_APP_ARG(post_process);
00405 );
00406
00407 if (ast_strlen_zero(data)) {
00408 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00409 return -1;
00410 }
00411
00412 parse = ast_strdupa(data);
00413
00414 AST_STANDARD_APP_ARGS(args, parse);
00415
00416 if (ast_strlen_zero(args.filename)) {
00417 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00418 return -1;
00419 }
00420
00421 if (args.options) {
00422 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
00423
00424 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
00425
00426 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
00427 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
00428 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
00429 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00430 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
00431 } else {
00432 readvol = get_volfactor(x);
00433 }
00434 }
00435
00436 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
00437 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
00438 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
00439 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00440 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
00441 } else {
00442 writevol = get_volfactor(x);
00443 }
00444 }
00445
00446 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
00447 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
00448 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
00449 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00450 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
00451 } else {
00452 readvol = writevol = get_volfactor(x);
00453 }
00454 }
00455 }
00456
00457
00458 if (args.filename[0] != '/') {
00459 char *build;
00460
00461 build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
00462 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
00463 args.filename = build;
00464 }
00465
00466 tmp = ast_strdupa(args.filename);
00467 if ((slash = strrchr(tmp, '/')))
00468 *slash = '\0';
00469 ast_mkdir(tmp, 0777);
00470
00471 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
00472 launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
00473
00474 return 0;
00475 }
00476
00477 static int stop_mixmonitor_exec(struct ast_channel *chan, void *data)
00478 {
00479 struct ast_datastore *datastore = NULL;
00480
00481
00482
00483 if ((datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, NULL))) {
00484 mixmonitor_ds_close_fs(datastore->data);
00485 }
00486
00487 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00488 return 0;
00489 }
00490
00491 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00492 {
00493 struct ast_channel *chan;
00494
00495 switch (cmd) {
00496 case CLI_INIT:
00497 e->command = "mixmonitor [start|stop]";
00498 e->usage =
00499 "Usage: mixmonitor <start|stop> <chan_name> [args]\n"
00500 " The optional arguments are passed to the MixMonitor\n"
00501 " application when the 'start' command is used.\n";
00502 return NULL;
00503 case CLI_GENERATE:
00504 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
00505 }
00506
00507 if (a->argc < 3)
00508 return CLI_SHOWUSAGE;
00509
00510 if (!(chan = ast_get_channel_by_name_prefix_locked(a->argv[2], strlen(a->argv[2])))) {
00511 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
00512
00513 return CLI_SUCCESS;
00514 }
00515
00516 if (!strcasecmp(a->argv[1], "start")) {
00517 mixmonitor_exec(chan, a->argv[3]);
00518 ast_channel_unlock(chan);
00519 } else {
00520 ast_channel_unlock(chan);
00521 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00522 }
00523
00524 return CLI_SUCCESS;
00525 }
00526
00527 static struct ast_cli_entry cli_mixmonitor[] = {
00528 AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
00529 };
00530
00531 static int unload_module(void)
00532 {
00533 int res;
00534
00535 ast_cli_unregister_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
00536 res = ast_unregister_application(stop_app);
00537 res |= ast_unregister_application(app);
00538
00539 return res;
00540 }
00541
00542 static int load_module(void)
00543 {
00544 int res;
00545
00546 ast_cli_register_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
00547 res = ast_register_application(app, mixmonitor_exec, synopsis, desc);
00548 res |= ast_register_application(stop_app, stop_mixmonitor_exec, stop_synopsis, stop_desc);
00549
00550 return res;
00551 }
00552
00553 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");