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"
49 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
51 static const char *app
= "MixMonitor";
52 static const char *synopsis
= "Record a call and mix the audio during the recording";
53 static const char *desc
= ""
54 " MixMonitor(<file>.<ext>[,<options>[,<command>]]):\n"
55 "Records the audio on the current channel to the specified file.\n"
56 "If the filename is an absolute path, uses that path, otherwise\n"
57 "creates the file in the configured monitoring directory from\n"
60 " a - Append to the file instead of overwriting it.\n"
61 " b - Only save audio to the file while the channel is bridged.\n"
62 " Note: Does not include conferences or sounds played to each bridged\n"
64 " v(<x>) - Adjust the heard volume by a factor of <x> (range -4 to 4)\n"
65 " V(<x>) - Adjust the spoken volume by a factor of <x> (range -4 to 4)\n"
66 " W(<x>) - Adjust the both heard and spoken volumes by a factor of <x>\n"
67 " (range -4 to 4)\n\n"
68 "<command> will be executed when the recording is over\n"
69 "Any strings matching ^{X} will be unescaped to ${X}.\n"
70 "All variables will be evaluated at the time MixMonitor is called.\n"
71 "The variable MIXMONITOR_FILENAME will contain the filename used to record.\n"
74 static const char *stop_app
= "StopMixMonitor";
75 static const char *stop_synopsis
= "Stop recording a call through MixMonitor";
76 static const char *stop_desc
= ""
77 " StopMixMonitor():\n"
78 "Stops the audio recording that was started with a call to MixMonitor()\n"
79 "on the current channel.\n"
82 struct module_symbols
*me
;
84 static const char *mixmonitor_spy_type
= "MixMonitor";
87 struct ast_audiohook audiohook
;
92 struct ast_channel
*chan
;
96 MUXFLAG_APPEND
= (1 << 1),
97 MUXFLAG_BRIDGED
= (1 << 2),
98 MUXFLAG_VOLUME
= (1 << 3),
99 MUXFLAG_READVOLUME
= (1 << 4),
100 MUXFLAG_WRITEVOLUME
= (1 << 5),
104 OPT_ARG_READVOLUME
= 0,
110 AST_APP_OPTIONS(mixmonitor_opts
, {
111 AST_APP_OPTION('a', MUXFLAG_APPEND
),
112 AST_APP_OPTION('b', MUXFLAG_BRIDGED
),
113 AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME
, OPT_ARG_READVOLUME
),
114 AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME
, OPT_ARG_WRITEVOLUME
),
115 AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME
, OPT_ARG_VOLUME
),
118 static int startmon(struct ast_channel
*chan
, struct ast_audiohook
*audiohook
)
120 struct ast_channel
*peer
= NULL
;
126 ast_audiohook_attach(chan
, audiohook
);
128 if (!res
&& ast_test_flag(chan
, AST_FLAG_NBRIDGE
) && (peer
= ast_bridged_channel(chan
)))
129 ast_softhangup(peer
, AST_SOFTHANGUP_UNBRIDGE
);
134 #define SAMPLES_PER_FRAME 160
136 static void *mixmonitor_thread(void *obj
)
138 struct mixmonitor
*mixmonitor
= obj
;
139 struct ast_filestream
*fs
= NULL
;
144 ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor
->name
);
146 ast_audiohook_lock(&mixmonitor
->audiohook
);
148 while (mixmonitor
->audiohook
.status
== AST_AUDIOHOOK_STATUS_RUNNING
) {
149 struct ast_frame
*fr
= NULL
;
151 ast_audiohook_trigger_wait(&mixmonitor
->audiohook
);
153 if (mixmonitor
->audiohook
.status
!= AST_AUDIOHOOK_STATUS_RUNNING
)
156 if (!(fr
= ast_audiohook_read_frame(&mixmonitor
->audiohook
, SAMPLES_PER_FRAME
, AST_AUDIOHOOK_DIRECTION_BOTH
, AST_FORMAT_SLINEAR
)))
159 if (!ast_test_flag(mixmonitor
, MUXFLAG_BRIDGED
) || ast_bridged_channel(mixmonitor
->chan
)) {
160 /* Initialize the file if not already done so */
161 if (!fs
&& !errflag
) {
162 oflags
= O_CREAT
| O_WRONLY
;
163 oflags
|= ast_test_flag(mixmonitor
, MUXFLAG_APPEND
) ? O_APPEND
: O_TRUNC
;
165 if ((ext
= strrchr(mixmonitor
->filename
, '.')))
170 if (!(fs
= ast_writefile(mixmonitor
->filename
, ext
, NULL
, oflags
, 0, 0644))) {
171 ast_log(LOG_ERROR
, "Cannot open %s.%s\n", mixmonitor
->filename
, ext
);
176 /* Write out frame */
178 ast_writestream(fs
, fr
);
181 /* All done! free it. */
182 ast_frame_free(fr
, 0);
186 ast_audiohook_detach(&mixmonitor
->audiohook
);
187 ast_audiohook_unlock(&mixmonitor
->audiohook
);
188 ast_audiohook_destroy(&mixmonitor
->audiohook
);
190 ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor
->name
);
195 if (mixmonitor
->post_process
) {
196 ast_verb(2, "Executing [%s]\n", mixmonitor
->post_process
);
197 ast_safe_system(mixmonitor
->post_process
);
200 ast_free(mixmonitor
);
206 static void launch_monitor_thread(struct ast_channel
*chan
, const char *filename
, unsigned int flags
,
207 int readvol
, int writevol
, const char *post_process
)
210 struct mixmonitor
*mixmonitor
;
211 char postprocess2
[1024] = "";
214 len
= sizeof(*mixmonitor
) + strlen(chan
->name
) + strlen(filename
) + 2;
217 /* If a post process system command is given attach it to the structure */
218 if (!ast_strlen_zero(post_process
)) {
221 p1
= ast_strdupa(post_process
);
222 for (p2
= p1
; *p2
; p2
++) {
223 if (*p2
== '^' && *(p2
+1) == '{') {
227 pbx_substitute_variables_helper(chan
, p1
, postprocess2
, sizeof(postprocess2
) - 1);
228 if (!ast_strlen_zero(postprocess2
))
229 len
+= strlen(postprocess2
) + 1;
232 /* Pre-allocate mixmonitor structure and spy */
233 if (!(mixmonitor
= ast_calloc(1, len
))) {
237 /* Copy over flags and channel name */
238 mixmonitor
->flags
= flags
;
239 mixmonitor
->chan
= chan
;
240 mixmonitor
->name
= (char *) mixmonitor
+ sizeof(*mixmonitor
);
241 strcpy(mixmonitor
->name
, chan
->name
);
242 if (!ast_strlen_zero(postprocess2
)) {
243 mixmonitor
->post_process
= mixmonitor
->name
+ strlen(mixmonitor
->name
) + strlen(filename
) + 2;
244 strcpy(mixmonitor
->post_process
, postprocess2
);
247 mixmonitor
->filename
= (char *) mixmonitor
+ sizeof(*mixmonitor
) + strlen(chan
->name
) + 1;
248 strcpy(mixmonitor
->filename
, filename
);
250 /* Setup the actual spy before creating our thread */
251 if (ast_audiohook_init(&mixmonitor
->audiohook
, AST_AUDIOHOOK_TYPE_SPY
, mixmonitor_spy_type
)) {
252 ast_free(mixmonitor
);
256 ast_set_flag(&mixmonitor
->audiohook
, AST_AUDIOHOOK_TRIGGER_SYNC
);
259 mixmonitor
->audiohook
.options
.read_volume
= readvol
;
261 mixmonitor
->audiohook
.options
.write_volume
= writevol
;
263 if (startmon(chan
, &mixmonitor
->audiohook
)) {
264 ast_log(LOG_WARNING
, "Unable to add '%s' spy to channel '%s'\n",
265 mixmonitor_spy_type
, chan
->name
);
266 ast_audiohook_destroy(&mixmonitor
->audiohook
);
267 ast_free(mixmonitor
);
271 ast_pthread_create_detached_background(&thread
, NULL
, mixmonitor_thread
, mixmonitor
);
275 static int mixmonitor_exec(struct ast_channel
*chan
, void *data
)
277 int x
, readvol
= 0, writevol
= 0;
278 struct ast_flags flags
= {0};
279 char *parse
, *tmp
, *slash
;
280 AST_DECLARE_APP_ARGS(args
,
281 AST_APP_ARG(filename
);
282 AST_APP_ARG(options
);
283 AST_APP_ARG(post_process
);
286 if (ast_strlen_zero(data
)) {
287 ast_log(LOG_WARNING
, "MixMonitor requires an argument (filename)\n");
291 parse
= ast_strdupa(data
);
293 AST_STANDARD_APP_ARGS(args
, parse
);
295 if (ast_strlen_zero(args
.filename
)) {
296 ast_log(LOG_WARNING
, "MixMonitor requires an argument (filename)\n");
301 char *opts
[OPT_ARG_ARRAY_SIZE
] = { NULL
, };
303 ast_app_parse_options(mixmonitor_opts
, &flags
, opts
, args
.options
);
305 if (ast_test_flag(&flags
, MUXFLAG_READVOLUME
)) {
306 if (ast_strlen_zero(opts
[OPT_ARG_READVOLUME
])) {
307 ast_log(LOG_WARNING
, "No volume level was provided for the heard volume ('v') option.\n");
308 } else if ((sscanf(opts
[OPT_ARG_READVOLUME
], "%d", &x
) != 1) || (x
< -4) || (x
> 4)) {
309 ast_log(LOG_NOTICE
, "Heard volume must be a number between -4 and 4, not '%s'\n", opts
[OPT_ARG_READVOLUME
]);
311 readvol
= get_volfactor(x
);
315 if (ast_test_flag(&flags
, MUXFLAG_WRITEVOLUME
)) {
316 if (ast_strlen_zero(opts
[OPT_ARG_WRITEVOLUME
])) {
317 ast_log(LOG_WARNING
, "No volume level was provided for the spoken volume ('V') option.\n");
318 } else if ((sscanf(opts
[OPT_ARG_WRITEVOLUME
], "%d", &x
) != 1) || (x
< -4) || (x
> 4)) {
319 ast_log(LOG_NOTICE
, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts
[OPT_ARG_WRITEVOLUME
]);
321 writevol
= get_volfactor(x
);
325 if (ast_test_flag(&flags
, MUXFLAG_VOLUME
)) {
326 if (ast_strlen_zero(opts
[OPT_ARG_VOLUME
])) {
327 ast_log(LOG_WARNING
, "No volume level was provided for the combined volume ('W') option.\n");
328 } else if ((sscanf(opts
[OPT_ARG_VOLUME
], "%d", &x
) != 1) || (x
< -4) || (x
> 4)) {
329 ast_log(LOG_NOTICE
, "Combined volume must be a number between -4 and 4, not '%s'\n", opts
[OPT_ARG_VOLUME
]);
331 readvol
= writevol
= get_volfactor(x
);
336 /* if not provided an absolute path, use the system-configured monitoring directory */
337 if (args
.filename
[0] != '/') {
340 build
= alloca(strlen(ast_config_AST_MONITOR_DIR
) + strlen(args
.filename
) + 3);
341 sprintf(build
, "%s/%s", ast_config_AST_MONITOR_DIR
, args
.filename
);
342 args
.filename
= build
;
345 tmp
= ast_strdupa(args
.filename
);
346 if ((slash
= strrchr(tmp
, '/')))
348 ast_mkdir(tmp
, 0777);
350 pbx_builtin_setvar_helper(chan
, "MIXMONITOR_FILENAME", args
.filename
);
351 launch_monitor_thread(chan
, args
.filename
, flags
.flags
, readvol
, writevol
, args
.post_process
);
356 static int stop_mixmonitor_exec(struct ast_channel
*chan
, void *data
)
358 ast_audiohook_detach_source(chan
, mixmonitor_spy_type
);
362 static char *handle_cli_mixmonitor(struct ast_cli_entry
*e
, int cmd
, struct ast_cli_args
*a
)
364 struct ast_channel
*chan
;
368 e
->command
= "mixmonitor [start|stop]";
370 "Usage: mixmonitor <start|stop> <chan_name> [args]\n"
371 " The optional arguments are passed to the MixMonitor\n"
372 " application when the 'start' command is used.\n";
375 return ast_complete_channels(a
->line
, a
->word
, a
->pos
, a
->n
, 2);
379 return CLI_SHOWUSAGE
;
381 if (!(chan
= ast_get_channel_by_name_prefix_locked(a
->argv
[2], strlen(a
->argv
[2])))) {
382 ast_cli(a
->fd
, "No channel matching '%s' found.\n", a
->argv
[2]);
383 /* Technically this is a failure, but we don't want 2 errors printing out */
387 if (!strcasecmp(a
->argv
[1], "start")) {
388 mixmonitor_exec(chan
, a
->argv
[3]);
389 ast_channel_unlock(chan
);
391 ast_channel_unlock(chan
);
392 ast_audiohook_detach_source(chan
, mixmonitor_spy_type
);
398 static struct ast_cli_entry cli_mixmonitor
[] = {
399 AST_CLI_DEFINE(handle_cli_mixmonitor
, "Execute a MixMonitor command")
402 static int unload_module(void)
406 ast_cli_unregister_multiple(cli_mixmonitor
, sizeof(cli_mixmonitor
) / sizeof(struct ast_cli_entry
));
407 res
= ast_unregister_application(stop_app
);
408 res
|= ast_unregister_application(app
);
413 static int load_module(void)
417 ast_cli_register_multiple(cli_mixmonitor
, sizeof(cli_mixmonitor
) / sizeof(struct ast_cli_entry
));
418 res
= ast_register_application(app
, mixmonitor_exec
, synopsis
, desc
);
419 res
|= ast_register_application(stop_app
, stop_mixmonitor_exec
, stop_synopsis
, stop_desc
);
424 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY
, "Mixed Audio Monitoring Application");