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