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: 108083 $")
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 ast_channel *chan;
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 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
00128 {
00129 struct ast_channel *peer;
00130 int res;
00131
00132 if (!chan)
00133 return -1;
00134
00135 res = ast_audiohook_attach(chan, audiohook);
00136
00137 if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
00138 ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
00139
00140 return res;
00141 }
00142
00143 #define SAMPLES_PER_FRAME 160
00144
00145 static void *mixmonitor_thread(void *obj)
00146 {
00147 struct mixmonitor *mixmonitor = obj;
00148 struct ast_filestream *fs = NULL;
00149 unsigned int oflags;
00150 char *ext;
00151 int errflag = 0;
00152
00153 if (option_verbose > 1)
00154 ast_verbose(VERBOSE_PREFIX_2 "Begin MixMonitor Recording %s\n", mixmonitor->name);
00155
00156 ast_audiohook_lock(&mixmonitor->audiohook);
00157
00158 while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
00159 struct ast_frame *fr = NULL;
00160
00161 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
00162
00163 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING)
00164 break;
00165
00166 if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR)))
00167 continue;
00168
00169 if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || ast_bridged_channel(mixmonitor->chan)) {
00170
00171 if (!fs && !errflag) {
00172 oflags = O_CREAT | O_WRONLY;
00173 oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
00174
00175 if ((ext = strrchr(mixmonitor->filename, '.')))
00176 *(ext++) = '\0';
00177 else
00178 ext = "raw";
00179
00180 if (!(fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0644))) {
00181 ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
00182 errflag = 1;
00183 }
00184 }
00185
00186
00187 if (fs)
00188 ast_writestream(fs, fr);
00189 }
00190
00191
00192 ast_frame_free(fr, 0);
00193 }
00194
00195 ast_audiohook_detach(&mixmonitor->audiohook);
00196 ast_audiohook_unlock(&mixmonitor->audiohook);
00197 ast_audiohook_destroy(&mixmonitor->audiohook);
00198
00199 if (option_verbose > 1)
00200 ast_verbose(VERBOSE_PREFIX_2 "End MixMonitor Recording %s\n", mixmonitor->name);
00201
00202 if (fs)
00203 ast_closestream(fs);
00204
00205 if (mixmonitor->post_process) {
00206 if (option_verbose > 2)
00207 ast_verbose(VERBOSE_PREFIX_2 "Executing [%s]\n", mixmonitor->post_process);
00208 ast_safe_system(mixmonitor->post_process);
00209 }
00210
00211 free(mixmonitor);
00212
00213
00214 return NULL;
00215 }
00216
00217 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
00218 int readvol, int writevol, const char *post_process)
00219 {
00220 pthread_attr_t attr;
00221 pthread_t thread;
00222 struct mixmonitor *mixmonitor;
00223 char postprocess2[1024] = "";
00224 size_t len;
00225
00226 len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
00227
00228
00229 if (!ast_strlen_zero(post_process)) {
00230 char *p1, *p2;
00231
00232 p1 = ast_strdupa(post_process);
00233 for (p2 = p1; *p2 ; p2++) {
00234 if (*p2 == '^' && *(p2+1) == '{') {
00235 *p2 = '$';
00236 }
00237 }
00238
00239 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
00240 if (!ast_strlen_zero(postprocess2))
00241 len += strlen(postprocess2) + 1;
00242 }
00243
00244
00245 if (!(mixmonitor = calloc(1, len))) {
00246 return;
00247 }
00248
00249
00250 mixmonitor->flags = flags;
00251 mixmonitor->chan = chan;
00252 mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
00253 strcpy(mixmonitor->name, chan->name);
00254 if (!ast_strlen_zero(postprocess2)) {
00255 mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
00256 strcpy(mixmonitor->post_process, postprocess2);
00257 }
00258
00259 mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
00260 strcpy(mixmonitor->filename, filename);
00261
00262
00263 if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
00264 free(mixmonitor);
00265 return;
00266 }
00267
00268 ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
00269
00270 if (readvol)
00271 mixmonitor->audiohook.options.read_volume = readvol;
00272 if (writevol)
00273 mixmonitor->audiohook.options.write_volume = writevol;
00274
00275 if (startmon(chan, &mixmonitor->audiohook)) {
00276 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
00277 mixmonitor_spy_type, chan->name);
00278
00279 ast_audiohook_destroy(&mixmonitor->audiohook);
00280 free(mixmonitor);
00281 return;
00282 }
00283
00284 pthread_attr_init(&attr);
00285 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
00286 ast_pthread_create_background(&thread, &attr, mixmonitor_thread, mixmonitor);
00287 pthread_attr_destroy(&attr);
00288
00289 }
00290
00291 static int mixmonitor_exec(struct ast_channel *chan, void *data)
00292 {
00293 int x, readvol = 0, writevol = 0;
00294 struct ast_module_user *u;
00295 struct ast_flags flags = {0};
00296 char *parse;
00297 AST_DECLARE_APP_ARGS(args,
00298 AST_APP_ARG(filename);
00299 AST_APP_ARG(options);
00300 AST_APP_ARG(post_process);
00301 );
00302
00303 if (ast_strlen_zero(data)) {
00304 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00305 return -1;
00306 }
00307
00308 u = ast_module_user_add(chan);
00309
00310 parse = ast_strdupa(data);
00311
00312 AST_STANDARD_APP_ARGS(args, parse);
00313
00314 if (ast_strlen_zero(args.filename)) {
00315 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
00316 ast_module_user_remove(u);
00317 return -1;
00318 }
00319
00320 if (args.options) {
00321 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
00322
00323 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
00324
00325 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
00326 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
00327 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
00328 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00329 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
00330 } else {
00331 readvol = get_volfactor(x);
00332 }
00333 }
00334
00335 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
00336 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
00337 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
00338 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00339 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
00340 } else {
00341 writevol = get_volfactor(x);
00342 }
00343 }
00344
00345 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
00346 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
00347 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
00348 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
00349 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
00350 } else {
00351 readvol = writevol = get_volfactor(x);
00352 }
00353 }
00354 }
00355
00356
00357 if (args.filename[0] != '/') {
00358 char *build;
00359
00360 build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
00361 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
00362 args.filename = build;
00363 }
00364
00365 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
00366 launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
00367
00368 ast_module_user_remove(u);
00369
00370 return 0;
00371 }
00372
00373 static int stop_mixmonitor_exec(struct ast_channel *chan, void *data)
00374 {
00375 struct ast_module_user *u;
00376
00377 u = ast_module_user_add(chan);
00378
00379 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00380
00381 ast_module_user_remove(u);
00382
00383 return 0;
00384 }
00385
00386 static int mixmonitor_cli(int fd, int argc, char **argv)
00387 {
00388 struct ast_channel *chan;
00389
00390 if (argc < 3)
00391 return RESULT_SHOWUSAGE;
00392
00393 if (!(chan = ast_get_channel_by_name_prefix_locked(argv[2], strlen(argv[2])))) {
00394 ast_cli(fd, "No channel matching '%s' found.\n", argv[2]);
00395 return RESULT_SUCCESS;
00396 }
00397
00398 if (!strcasecmp(argv[1], "start"))
00399 mixmonitor_exec(chan, argv[3]);
00400 else if (!strcasecmp(argv[1], "stop"))
00401 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
00402
00403 ast_channel_unlock(chan);
00404
00405 return RESULT_SUCCESS;
00406 }
00407
00408 static char *complete_mixmonitor_cli(const char *line, const char *word, int pos, int state)
00409 {
00410 return ast_complete_channels(line, word, pos, state, 2);
00411 }
00412
00413 static struct ast_cli_entry cli_mixmonitor[] = {
00414 { { "mixmonitor", NULL, NULL },
00415 mixmonitor_cli, "Execute a MixMonitor command.",
00416 "mixmonitor <start|stop> <chan_name> [args]\n\n"
00417 "The optional arguments are passed to the\n"
00418 "MixMonitor application when the 'start' command is used.\n",
00419 complete_mixmonitor_cli },
00420 };
00421
00422 static int unload_module(void)
00423 {
00424 int res;
00425
00426 ast_cli_unregister_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
00427 res = ast_unregister_application(stop_app);
00428 res |= ast_unregister_application(app);
00429
00430 ast_module_user_hangup_all();
00431
00432 return res;
00433 }
00434
00435 static int load_module(void)
00436 {
00437 int res;
00438
00439 ast_cli_register_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
00440 res = ast_register_application(app, mixmonitor_exec, synopsis, desc);
00441 res |= ast_register_application(stop_app, stop_mixmonitor_exec, stop_synopsis, stop_desc);
00442
00443 return res;
00444 }
00445
00446 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");