diff options
Diffstat (limited to 'trunk/apps/app_mixmonitor.c')
-rw-r--r-- | trunk/apps/app_mixmonitor.c | 424 |
1 files changed, 424 insertions, 0 deletions
diff --git a/trunk/apps/app_mixmonitor.c b/trunk/apps/app_mixmonitor.c new file mode 100644 index 000000000..012af01c5 --- /dev/null +++ b/trunk/apps/app_mixmonitor.c @@ -0,0 +1,424 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2005, Anthony Minessale II + * Copyright (C) 2005 - 2006, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * Kevin P. Fleming <kpfleming@digium.com> + * + * Based on app_muxmon.c provided by + * Anthony Minessale II <anthmct@yahoo.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief MixMonitor() - Record a call and mix the audio during the recording + * \ingroup applications + * + * \author Mark Spencer <markster@digium.com> + * \author Kevin P. Fleming <kpfleming@digium.com> + * + * \note Based on app_muxmon.c provided by + * Anthony Minessale II <anthmct@yahoo.com> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/paths.h" /* use ast_config_AST_MONITOR_DIR */ +#include "asterisk/file.h" +#include "asterisk/audiohook.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/cli.h" +#include "asterisk/app.h" +#include "asterisk/channel.h" + +#define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0 + +static const char *app = "MixMonitor"; +static const char *synopsis = "Record a call and mix the audio during the recording"; +static const char *desc = "" +" MixMonitor(<file>.<ext>[,<options>[,<command>]]):\n" +"Records the audio on the current channel to the specified file.\n" +"If the filename is an absolute path, uses that path, otherwise\n" +"creates the file in the configured monitoring directory from\n" +"asterisk.conf.\n\n" +"Valid options:\n" +" a - Append to the file instead of overwriting it.\n" +" b - Only save audio to the file while the channel is bridged.\n" +" Note: Does not include conferences or sounds played to each bridged\n" +" party.\n" +" v(<x>) - Adjust the heard volume by a factor of <x> (range -4 to 4)\n" +" V(<x>) - Adjust the spoken volume by a factor of <x> (range -4 to 4)\n" +" W(<x>) - Adjust the both heard and spoken volumes by a factor of <x>\n" +" (range -4 to 4)\n\n" +"<command> will be executed when the recording is over\n" +"Any strings matching ^{X} will be unescaped to ${X}.\n" +"All variables will be evaluated at the time MixMonitor is called.\n" +"The variable MIXMONITOR_FILENAME will contain the filename used to record.\n" +""; + +static const char *stop_app = "StopMixMonitor"; +static const char *stop_synopsis = "Stop recording a call through MixMonitor"; +static const char *stop_desc = "" +" StopMixMonitor():\n" +"Stops the audio recording that was started with a call to MixMonitor()\n" +"on the current channel.\n" +""; + +struct module_symbols *me; + +static const char *mixmonitor_spy_type = "MixMonitor"; + +struct mixmonitor { + struct ast_audiohook audiohook; + char *filename; + char *post_process; + char *name; + unsigned int flags; + struct ast_channel *chan; +}; + +enum { + MUXFLAG_APPEND = (1 << 1), + MUXFLAG_BRIDGED = (1 << 2), + MUXFLAG_VOLUME = (1 << 3), + MUXFLAG_READVOLUME = (1 << 4), + MUXFLAG_WRITEVOLUME = (1 << 5), +} mixmonitor_flags; + +enum { + OPT_ARG_READVOLUME = 0, + OPT_ARG_WRITEVOLUME, + OPT_ARG_VOLUME, + OPT_ARG_ARRAY_SIZE, +} mixmonitor_args; + +AST_APP_OPTIONS(mixmonitor_opts, { + AST_APP_OPTION('a', MUXFLAG_APPEND), + AST_APP_OPTION('b', MUXFLAG_BRIDGED), + AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME), + AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME), + AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME), +}); + +static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook) +{ + struct ast_channel *peer = NULL; + int res = 0; + + if (!chan) + return -1; + + ast_audiohook_attach(chan, audiohook); + + if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan))) + ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE); + + return res; +} + +#define SAMPLES_PER_FRAME 160 + +static void *mixmonitor_thread(void *obj) +{ + struct mixmonitor *mixmonitor = obj; + struct ast_filestream *fs = NULL; + unsigned int oflags; + char *ext; + int errflag = 0; + + ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name); + + ast_audiohook_lock(&mixmonitor->audiohook); + + while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) { + struct ast_frame *fr = NULL; + + ast_audiohook_trigger_wait(&mixmonitor->audiohook); + + if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) + break; + + if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR))) + continue; + + if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || ast_bridged_channel(mixmonitor->chan)) { + /* Initialize the file if not already done so */ + if (!fs && !errflag) { + oflags = O_CREAT | O_WRONLY; + oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC; + + if ((ext = strrchr(mixmonitor->filename, '.'))) + *(ext++) = '\0'; + else + ext = "raw"; + + if (!(fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0644))) { + ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext); + errflag = 1; + } + } + + /* Write out frame */ + if (fs) + ast_writestream(fs, fr); + } + + /* All done! free it. */ + ast_frame_free(fr, 0); + + } + + ast_audiohook_detach(&mixmonitor->audiohook); + ast_audiohook_unlock(&mixmonitor->audiohook); + ast_audiohook_destroy(&mixmonitor->audiohook); + + ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name); + + if (fs) + ast_closestream(fs); + + if (mixmonitor->post_process) { + ast_verb(2, "Executing [%s]\n", mixmonitor->post_process); + ast_safe_system(mixmonitor->post_process); + } + + ast_free(mixmonitor); + + + return NULL; +} + +static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags, + int readvol, int writevol, const char *post_process) +{ + pthread_t thread; + struct mixmonitor *mixmonitor; + char postprocess2[1024] = ""; + size_t len; + + len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2; + + postprocess2[0] = 0; + /* If a post process system command is given attach it to the structure */ + if (!ast_strlen_zero(post_process)) { + char *p1, *p2; + + p1 = ast_strdupa(post_process); + for (p2 = p1; *p2 ; p2++) { + if (*p2 == '^' && *(p2+1) == '{') { + *p2 = '$'; + } + } + pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1); + if (!ast_strlen_zero(postprocess2)) + len += strlen(postprocess2) + 1; + } + + /* Pre-allocate mixmonitor structure and spy */ + if (!(mixmonitor = ast_calloc(1, len))) { + return; + } + + /* Copy over flags and channel name */ + mixmonitor->flags = flags; + mixmonitor->chan = chan; + mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor); + strcpy(mixmonitor->name, chan->name); + if (!ast_strlen_zero(postprocess2)) { + mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2; + strcpy(mixmonitor->post_process, postprocess2); + } + + mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1; + strcpy(mixmonitor->filename, filename); + + /* Setup the actual spy before creating our thread */ + if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) { + ast_free(mixmonitor); + return; + } + + ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_WRITE); + + if (readvol) + mixmonitor->audiohook.options.read_volume = readvol; + if (writevol) + mixmonitor->audiohook.options.write_volume = writevol; + + if (startmon(chan, &mixmonitor->audiohook)) { + ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n", + mixmonitor_spy_type, chan->name); + ast_audiohook_destroy(&mixmonitor->audiohook); + ast_free(mixmonitor); + return; + } + + ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor); + +} + +static int mixmonitor_exec(struct ast_channel *chan, void *data) +{ + int x, readvol = 0, writevol = 0; + struct ast_flags flags = {0}; + char *parse, *tmp, *slash; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(filename); + AST_APP_ARG(options); + AST_APP_ARG(post_process); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n"); + return -1; + } + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.filename)) { + ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n"); + return -1; + } + + if (args.options) { + char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, }; + + ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options); + + if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) { + if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) { + ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n"); + } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) { + ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]); + } else { + readvol = get_volfactor(x); + } + } + + if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) { + if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) { + ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n"); + } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) { + ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]); + } else { + writevol = get_volfactor(x); + } + } + + if (ast_test_flag(&flags, MUXFLAG_VOLUME)) { + if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) { + ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n"); + } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) { + ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]); + } else { + readvol = writevol = get_volfactor(x); + } + } + } + + /* if not provided an absolute path, use the system-configured monitoring directory */ + if (args.filename[0] != '/') { + char *build; + + build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3); + sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename); + args.filename = build; + } + + tmp = ast_strdupa(args.filename); + if ((slash = strrchr(tmp, '/'))) + *slash = '\0'; + ast_mkdir(tmp, 0777); + + pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename); + launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process); + + return 0; +} + +static int stop_mixmonitor_exec(struct ast_channel *chan, void *data) +{ + ast_audiohook_detach_source(chan, mixmonitor_spy_type); + return 0; +} + +static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ast_channel *chan; + + switch (cmd) { + case CLI_INIT: + e->command = "mixmonitor [start|stop]"; + e->usage = + "Usage: mixmonitor <start|stop> <chan_name> [args]\n" + " The optional arguments are passed to the MixMonitor\n" + " application when the 'start' command is used.\n"; + return NULL; + case CLI_GENERATE: + return ast_complete_channels(a->line, a->word, a->pos, a->n, 2); + } + + if (a->argc < 3) + return CLI_SHOWUSAGE; + + if (!(chan = ast_get_channel_by_name_prefix_locked(a->argv[2], strlen(a->argv[2])))) { + ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]); + /* Technically this is a failure, but we don't want 2 errors printing out */ + return CLI_SUCCESS; + } + + if (!strcasecmp(a->argv[1], "start")) { + mixmonitor_exec(chan, a->argv[3]); + ast_channel_unlock(chan); + } else { + ast_channel_unlock(chan); + ast_audiohook_detach_source(chan, mixmonitor_spy_type); + } + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_mixmonitor[] = { + AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command") +}; + +static int unload_module(void) +{ + int res; + + ast_cli_unregister_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry)); + res = ast_unregister_application(stop_app); + res |= ast_unregister_application(app); + + return res; +} + +static int load_module(void) +{ + int res; + + ast_cli_register_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry)); + res = ast_register_application(app, mixmonitor_exec, synopsis, desc); + res |= ast_register_application(stop_app, stop_mixmonitor_exec, stop_synopsis, stop_desc); + + return res; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application"); |