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$")
40 #include "asterisk/paths.h" /* use ast_config_AST_MONITOR_DIR */
41 #include "asterisk/file.h"
42 #include "asterisk/audiohook.h"
43 #include "asterisk/pbx.h"
44 #include "asterisk/module.h"
45 #include "asterisk/cli.h"
46 #include "asterisk/app.h"
47 #include "asterisk/channel.h"
48 #include "asterisk/autochan.h"
51 <application name="MixMonitor" language="en_US">
53 Record a call and mix the audio during the recording.
56 <parameter name="file" required="true" argsep=".">
57 <argument name="filename" required="true">
58 <para>If <replaceable>filename</replaceable> is an absolute path, uses that path, otherwise
59 creates the file in the configured monitoring directory from <filename>asterisk.conf.</filename></para>
61 <argument name="extension" required="true" />
63 <parameter name="options">
66 <para>Append to the file instead of overwriting it.</para>
69 <para>Only save audio to the file while the channel is bridged.</para>
70 <note><para>Does not include conferences or sounds played to each bridged party</para></note>
71 <note><para>If you utilize this option inside a Local channel, you must make sure the Local
72 channel is not optimized away. To do this, be sure to call your Local channel with the
73 <literal>/n</literal> option. For example: Dial(Local/start@mycontext/n)</para></note>
76 <para>Adjust the <emphasis>heard</emphasis> volume by a factor of <replaceable>x</replaceable>
77 (range <literal>-4</literal> to <literal>4</literal>)</para>
78 <argument name="x" required="true" />
81 <para>Adjust the <emphasis>spoken</emphasis> volume by a factor
82 of <replaceable>x</replaceable> (range <literal>-4</literal> to <literal>4</literal>)</para>
83 <argument name="x" required="true" />
86 <para>Adjust both, <emphasis>heard and spoken</emphasis> volumes by a factor
87 of <replaceable>x</replaceable> (range <literal>-4</literal> to <literal>4</literal>)</para>
88 <argument name="x" required="true" />
92 <parameter name="command">
93 <para>Will be executed when the recording is over.</para>
94 <para>Any strings matching <literal>^{X}</literal> will be unescaped to <variable>X</variable>.</para>
95 <para>All variables will be evaluated at the time MixMonitor is called.</para>
99 <para>Records the audio on the current channel to the specified file.</para>
101 <variable name="MIXMONITOR_FILENAME">
102 <para>Will contain the filename used to record.</para>
107 <ref type="application">Monitor</ref>
108 <ref type="application">StopMixMonitor</ref>
109 <ref type="application">PauseMonitor</ref>
110 <ref type="application">UnpauseMonitor</ref>
113 <application name="StopMixMonitor" language="en_US">
115 Stop recording a call through MixMonitor.
119 <para>Stops the audio recording that was started with a call to <literal>MixMonitor()</literal>
120 on the current channel.</para>
123 <ref type="application">MixMonitor</ref>
129 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
131 static const char * const app = "MixMonitor";
133 static const char * const stop_app = "StopMixMonitor";
135 static const char * const mixmonitor_spy_type = "MixMonitor";
138 struct ast_audiohook audiohook;
143 struct ast_autochan *autochan;
146 enum mixmonitor_flags {
147 MUXFLAG_APPEND = (1 << 1),
148 MUXFLAG_BRIDGED = (1 << 2),
149 MUXFLAG_VOLUME = (1 << 3),
150 MUXFLAG_READVOLUME = (1 << 4),
151 MUXFLAG_WRITEVOLUME = (1 << 5),
154 enum mixmonitor_args {
155 OPT_ARG_READVOLUME = 0,
161 AST_APP_OPTIONS(mixmonitor_opts, {
162 AST_APP_OPTION('a', MUXFLAG_APPEND),
163 AST_APP_OPTION('b', MUXFLAG_BRIDGED),
164 AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
165 AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
166 AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
169 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
171 struct ast_channel *peer = NULL;
177 ast_audiohook_attach(chan, audiohook);
179 if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
180 ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
185 #define SAMPLES_PER_FRAME 160
187 static void *mixmonitor_thread(void *obj)
189 struct mixmonitor *mixmonitor = obj;
190 struct ast_filestream *fs = NULL;
195 ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name);
197 ast_audiohook_lock(&mixmonitor->audiohook);
199 while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
200 struct ast_frame *fr = NULL;
202 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
204 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING)
207 if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR)))
210 if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || (mixmonitor->autochan->chan && ast_bridged_channel(mixmonitor->autochan->chan))) {
211 /* Initialize the file if not already done so */
212 if (!fs && !errflag) {
213 oflags = O_CREAT | O_WRONLY;
214 oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
216 if ((ext = strrchr(mixmonitor->filename, '.')))
221 if (!(fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0666))) {
222 ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
227 /* Write out frame */
229 ast_writestream(fs, fr);
231 /* All done! free it. */
232 ast_frame_free(fr, 0);
236 ast_audiohook_detach(&mixmonitor->audiohook);
237 ast_audiohook_unlock(&mixmonitor->audiohook);
238 ast_audiohook_destroy(&mixmonitor->audiohook);
240 ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
245 if (mixmonitor->post_process) {
246 ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
247 ast_safe_system(mixmonitor->post_process);
250 ast_autochan_destroy(mixmonitor->autochan);
251 ast_free(mixmonitor);
256 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
257 int readvol, int writevol, const char *post_process)
260 struct mixmonitor *mixmonitor;
261 char postprocess2[1024] = "";
264 len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
267 /* If a post process system command is given attach it to the structure */
268 if (!ast_strlen_zero(post_process)) {
271 p1 = ast_strdupa(post_process);
272 for (p2 = p1; *p2 ; p2++) {
273 if (*p2 == '^' && *(p2+1) == '{') {
277 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
278 if (!ast_strlen_zero(postprocess2))
279 len += strlen(postprocess2) + 1;
282 /* Pre-allocate mixmonitor structure and spy */
283 if (!(mixmonitor = ast_calloc(1, len))) {
287 /* Copy over flags and channel name */
288 mixmonitor->flags = flags;
289 if (!(mixmonitor->autochan = ast_autochan_setup(chan))) {
292 mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
293 strcpy(mixmonitor->name, chan->name);
294 if (!ast_strlen_zero(postprocess2)) {
295 mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
296 strcpy(mixmonitor->post_process, postprocess2);
299 mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
300 strcpy(mixmonitor->filename, filename);
302 /* Setup the actual spy before creating our thread */
303 if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
304 ast_free(mixmonitor);
308 ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
311 mixmonitor->audiohook.options.read_volume = readvol;
313 mixmonitor->audiohook.options.write_volume = writevol;
315 if (startmon(chan, &mixmonitor->audiohook)) {
316 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
317 mixmonitor_spy_type, chan->name);
318 ast_audiohook_destroy(&mixmonitor->audiohook);
319 ast_free(mixmonitor);
323 ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
326 static int mixmonitor_exec(struct ast_channel *chan, const char *data)
328 int x, readvol = 0, writevol = 0;
329 struct ast_flags flags = {0};
330 char *parse, *tmp, *slash;
331 AST_DECLARE_APP_ARGS(args,
332 AST_APP_ARG(filename);
333 AST_APP_ARG(options);
334 AST_APP_ARG(post_process);
337 if (ast_strlen_zero(data)) {
338 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
342 parse = ast_strdupa(data);
344 AST_STANDARD_APP_ARGS(args, parse);
346 if (ast_strlen_zero(args.filename)) {
347 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
352 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
354 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
356 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
357 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
358 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
359 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
360 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
362 readvol = get_volfactor(x);
366 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
367 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
368 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
369 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
370 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
372 writevol = get_volfactor(x);
376 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
377 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
378 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
379 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
380 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
382 readvol = writevol = get_volfactor(x);
387 /* if not provided an absolute path, use the system-configured monitoring directory */
388 if (args.filename[0] != '/') {
391 build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
392 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
393 args.filename = build;
396 tmp = ast_strdupa(args.filename);
397 if ((slash = strrchr(tmp, '/')))
399 ast_mkdir(tmp, 0777);
401 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
402 launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
407 static int stop_mixmonitor_exec(struct ast_channel *chan, const char *data)
409 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
413 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
415 struct ast_channel *chan;
419 e->command = "mixmonitor {start|stop} {<chan_name>} [args]";
421 "Usage: mixmonitor <start|stop> <chan_name> [args]\n"
422 " The optional arguments are passed to the MixMonitor\n"
423 " application when the 'start' command is used.\n";
426 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
430 return CLI_SHOWUSAGE;
432 if (!(chan = ast_channel_get_by_name_prefix(a->argv[2], strlen(a->argv[2])))) {
433 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
434 /* Technically this is a failure, but we don't want 2 errors printing out */
438 ast_channel_lock(chan);
440 if (!strcasecmp(a->argv[1], "start")) {
441 mixmonitor_exec(chan, a->argv[3]);
442 ast_channel_unlock(chan);
444 ast_channel_unlock(chan);
445 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
448 chan = ast_channel_unref(chan);
453 static struct ast_cli_entry cli_mixmonitor[] = {
454 AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
457 static int unload_module(void)
461 ast_cli_unregister_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
462 res = ast_unregister_application(stop_app);
463 res |= ast_unregister_application(app);
468 static int load_module(void)
472 ast_cli_register_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
473 res = ast_register_application_xml(app, mixmonitor_exec);
474 res |= ast_register_application_xml(stop_app, stop_mixmonitor_exec);
479 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");