2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2005, Anthony Minessale II
5 * Copyright (C) 2005 - 2006, Digium, Inc.
7 * Mark Spencer <markster@digium.com>
8 * Kevin P. Fleming <kpfleming@digium.com>
10 * Based on app_muxmon.c provided by
11 * Anthony Minessale II <anthmct@yahoo.com>
13 * See http://www.asterisk.org for more information about
14 * the Asterisk project. Please do not directly contact
15 * any of the maintainers of this project for assistance;
16 * the project provides a web site, mailing lists and IRC
17 * channels for your use.
19 * This program is free software, distributed under the terms of
20 * the GNU General Public License Version 2. See the LICENSE file
21 * at the top of the source tree.
26 * \brief MixMonitor() - Record a call and mix the audio during the recording
27 * \ingroup applications
29 * \author Mark Spencer <markster@digium.com>
30 * \author Kevin P. Fleming <kpfleming@digium.com>
32 * \note Based on app_muxmon.c provided by
33 * Anthony Minessale II <anthmct@yahoo.com>
38 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
45 #include "asterisk/file.h"
46 #include "asterisk/logger.h"
47 #include "asterisk/channel.h"
48 #include "asterisk/chanspy.h"
49 #include "asterisk/pbx.h"
50 #include "asterisk/module.h"
51 #include "asterisk/lock.h"
52 #include "asterisk/cli.h"
53 #include "asterisk/options.h"
54 #include "asterisk/app.h"
55 #include "asterisk/linkedlists.h"
56 #include "asterisk/utils.h"
58 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
60 static const char *app = "MixMonitor";
61 static const char *synopsis = "Record a call and mix the audio during the recording";
62 static const char *desc = ""
63 " MixMonitor(<file>.<ext>[|<options>[|<command>]])\n\n"
64 "Records the audio on the current channel to the specified file.\n"
65 "If the filename is an absolute path, uses that path, otherwise\n"
66 "creates the file in the configured monitoring directory from\n"
69 " a - Append to the file instead of overwriting it.\n"
70 " b - Only save audio to the file while the channel is bridged.\n"
71 " Note: Does not include conferences or sounds played to each bridged\n"
73 " v(<x>) - Adjust the heard volume by a factor of <x> (range -4 to 4)\n"
74 " V(<x>) - Adjust the spoken volume by a factor of <x> (range -4 to 4)\n"
75 " W(<x>) - Adjust the both heard and spoken volumes by a factor of <x>\n"
76 " (range -4 to 4)\n\n"
77 "<command> will be executed when the recording is over\n"
78 "Any strings matching ^{X} will be unescaped to ${X}.\n"
79 "All variables will be evaluated at the time MixMonitor is called.\n"
80 "The variable MIXMONITOR_FILENAME will contain the filename used to record.\n"
83 static const char *stop_app = "StopMixMonitor";
84 static const char *stop_synopsis = "Stop recording a call through MixMonitor";
85 static const char *stop_desc = ""
86 " StopMixMonitor()\n\n"
87 "Stops the audio recording that was started with a call to MixMonitor()\n"
88 "on the current channel.\n"
91 struct module_symbols *me;
93 static const char *mixmonitor_spy_type = "MixMonitor";
96 struct ast_channel_spy spy;
104 MUXFLAG_APPEND = (1 << 1),
105 MUXFLAG_BRIDGED = (1 << 2),
106 MUXFLAG_VOLUME = (1 << 3),
107 MUXFLAG_READVOLUME = (1 << 4),
108 MUXFLAG_WRITEVOLUME = (1 << 5),
112 OPT_ARG_READVOLUME = 0,
118 AST_APP_OPTIONS(mixmonitor_opts, {
119 AST_APP_OPTION('a', MUXFLAG_APPEND),
120 AST_APP_OPTION('b', MUXFLAG_BRIDGED),
121 AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
122 AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
123 AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
126 static int startmon(struct ast_channel *chan, struct ast_channel_spy *spy)
128 struct ast_channel *peer;
134 ast_channel_lock(chan);
135 res = ast_channel_spy_add(chan, spy);
136 ast_channel_unlock(chan);
138 if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
139 ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
144 #define SAMPLES_PER_FRAME 160
146 static void *mixmonitor_thread(void *obj)
148 struct mixmonitor *mixmonitor = obj;
149 struct ast_frame *f = NULL;
150 struct ast_filestream *fs = NULL;
155 if (option_verbose > 1)
156 ast_verbose(VERBOSE_PREFIX_2 "Begin MixMonitor Recording %s\n", mixmonitor->name);
158 ast_mutex_lock(&mixmonitor->spy.lock);
160 while (mixmonitor->spy.chan) {
161 struct ast_frame *next;
164 ast_channel_spy_trigger_wait(&mixmonitor->spy);
166 if (!mixmonitor->spy.chan || mixmonitor->spy.status != CHANSPY_RUNNING)
170 if (!(f = ast_channel_spy_read_frame(&mixmonitor->spy, SAMPLES_PER_FRAME)))
173 write = (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) ||
174 ast_bridged_channel(mixmonitor->spy.chan));
176 /* it is possible for ast_channel_spy_read_frame() to return a chain
177 of frames if a queue flush was necessary, so process them
179 for (; f; f = next) {
180 next = AST_LIST_NEXT(f, frame_list);
181 if (write && errflag == 0) {
183 /* Determine creation flags and filename plus extension for filestream */
184 oflags = O_CREAT | O_WRONLY;
185 oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
187 if ((ext = strrchr(mixmonitor->filename, '.')))
192 /* Move onto actually creating the filestream */
193 if (!(fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0644))) {
194 ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
200 ast_writestream(fs, f);
202 ast_frame_free(f, 0);
207 ast_mutex_unlock(&mixmonitor->spy.lock);
209 ast_channel_spy_free(&mixmonitor->spy);
211 if (option_verbose > 1)
212 ast_verbose(VERBOSE_PREFIX_2 "End MixMonitor Recording %s\n", mixmonitor->name);
214 if (mixmonitor->post_process) {
215 if (option_verbose > 2)
216 ast_verbose(VERBOSE_PREFIX_2 "Executing [%s]\n", mixmonitor->post_process);
217 ast_safe_system(mixmonitor->post_process);
223 ast_free(mixmonitor);
229 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, uint64_t flags,
230 int readvol, int writevol, const char *post_process)
233 struct mixmonitor *mixmonitor;
234 char postprocess2[1024] = "";
237 len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
239 /* If a post process system command is given attach it to the structure */
240 if (!ast_strlen_zero(post_process)) {
243 p1 = ast_strdupa(post_process);
244 for (p2 = p1; *p2 ; p2++) {
245 if (*p2 == '^' && *(p2+1) == '{') {
250 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
251 if (!ast_strlen_zero(postprocess2))
252 len += strlen(postprocess2) + 1;
255 /* Pre-allocate mixmonitor structure and spy */
256 if (!(mixmonitor = ast_calloc(1, len))) {
260 /* Copy over flags and channel name */
261 mixmonitor->flags = flags;
262 mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
263 strcpy(mixmonitor->name, chan->name);
264 if (!ast_strlen_zero(postprocess2)) {
265 mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
266 strcpy(mixmonitor->post_process, postprocess2);
269 mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
270 strcpy(mixmonitor->filename, filename);
272 /* Setup the actual spy before creating our thread */
273 ast_set_flag(&mixmonitor->spy, CHANSPY_FORMAT_AUDIO);
274 ast_set_flag(&mixmonitor->spy, CHANSPY_MIXAUDIO);
275 mixmonitor->spy.type = mixmonitor_spy_type;
276 mixmonitor->spy.status = CHANSPY_RUNNING;
277 mixmonitor->spy.read_queue.format = AST_FORMAT_SLINEAR;
278 mixmonitor->spy.write_queue.format = AST_FORMAT_SLINEAR;
280 ast_set_flag(&mixmonitor->spy, CHANSPY_READ_VOLADJUST);
281 mixmonitor->spy.read_vol_adjustment = readvol;
284 ast_set_flag(&mixmonitor->spy, CHANSPY_WRITE_VOLADJUST);
285 mixmonitor->spy.write_vol_adjustment = writevol;
287 ast_mutex_init(&mixmonitor->spy.lock);
289 if (startmon(chan, &mixmonitor->spy)) {
290 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
291 mixmonitor->spy.type, chan->name);
292 /* Since we couldn't add ourselves - bail out! */
293 ast_mutex_destroy(&mixmonitor->spy.lock);
294 ast_free(mixmonitor);
298 ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
302 static int mixmonitor_exec(struct ast_channel *chan, void *data)
304 int x, readvol = 0, writevol = 0;
305 struct ast_flags flags = {0};
306 char *parse, *tmp, *slash;
307 AST_DECLARE_APP_ARGS(args,
308 AST_APP_ARG(filename);
309 AST_APP_ARG(options);
310 AST_APP_ARG(post_process);
313 if (ast_strlen_zero(data)) {
314 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
318 parse = ast_strdupa(data);
320 AST_STANDARD_APP_ARGS(args, parse);
322 if (ast_strlen_zero(args.filename)) {
323 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
328 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
330 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
332 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
333 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
334 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
335 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
336 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
338 readvol = get_volfactor(x);
342 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
343 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
344 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
345 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
346 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
348 writevol = get_volfactor(x);
352 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
353 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
354 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
355 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
356 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
358 readvol = writevol = get_volfactor(x);
363 /* if not provided an absolute path, use the system-configured monitoring directory */
364 if (args.filename[0] != '/') {
367 build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
368 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
369 args.filename = build;
372 tmp = ast_strdupa(args.filename);
373 if ((slash = strrchr(tmp, '/')))
375 ast_mkdir(tmp, 0777);
377 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
378 launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
383 static int stop_mixmonitor_exec(struct ast_channel *chan, void *data)
385 ast_channel_lock(chan);
386 ast_channel_spy_stop_by_type(chan, mixmonitor_spy_type);
387 ast_channel_unlock(chan);
391 static int mixmonitor_cli(int fd, int argc, char **argv)
393 struct ast_channel *chan;
396 return RESULT_SHOWUSAGE;
398 if (!(chan = ast_get_channel_by_name_prefix_locked(argv[2], strlen(argv[2])))) {
399 ast_cli(fd, "No channel matching '%s' found.\n", argv[2]);
400 return RESULT_SUCCESS;
403 if (!strcasecmp(argv[1], "start"))
404 mixmonitor_exec(chan, argv[3]);
405 else if (!strcasecmp(argv[1], "stop"))
406 ast_channel_spy_stop_by_type(chan, mixmonitor_spy_type);
408 ast_channel_unlock(chan);
410 return RESULT_SUCCESS;
413 static char *complete_mixmonitor_cli(const char *line, const char *word, int pos, int state)
415 char *options[] = {"start", "stop", NULL};
418 return ast_cli_complete (word, options, state);
420 return ast_complete_channels(line, word, pos, state, 2);
423 static struct ast_cli_entry cli_mixmonitor[] = {
424 { { "mixmonitor", NULL, NULL },
425 mixmonitor_cli, "Execute a MixMonitor command.",
426 "mixmonitor <start|stop> <chan_name> [args]\n\n"
427 "The optional arguments are passed to the\n"
428 "MixMonitor application when the 'start' command is used.\n",
429 complete_mixmonitor_cli },
432 static int unload_module(void)
436 ast_cli_unregister_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
437 res = ast_unregister_application(stop_app);
438 res |= ast_unregister_application(app);
443 static int load_module(void)
447 ast_cli_register_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
448 res = ast_register_application(app, mixmonitor_exec, synopsis, desc);
449 res |= ast_register_application(stop_app, stop_mixmonitor_exec, stop_synopsis, stop_desc);
454 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");