Optionally display the value of several variables within the Status command.
[asterisk-bristuff.git] / apps / app_mixmonitor.c
blob066103b3bee5018c2737cacec76cbb6cd3f5a586
1 /*
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.
24 /*! \file
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>
36 #include "asterisk.h"
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"
58 "asterisk.conf.\n\n"
59 "Valid options:\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"
63 " party.\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"
72 "";
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"
80 "";
82 struct module_symbols *me;
84 static const char *mixmonitor_spy_type = "MixMonitor";
86 struct mixmonitor {
87 struct ast_audiohook audiohook;
88 char *filename;
89 char *post_process;
90 char *name;
91 unsigned int flags;
92 struct ast_channel *chan;
95 enum {
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),
101 } mixmonitor_flags;
103 enum {
104 OPT_ARG_READVOLUME = 0,
105 OPT_ARG_WRITEVOLUME,
106 OPT_ARG_VOLUME,
107 OPT_ARG_ARRAY_SIZE,
108 } mixmonitor_args;
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;
121 int res = 0;
123 if (!chan)
124 return -1;
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);
131 return res;
134 #define SAMPLES_PER_FRAME 160
136 static void *mixmonitor_thread(void *obj)
138 struct mixmonitor *mixmonitor = obj;
139 struct ast_filestream *fs = NULL;
140 unsigned int oflags;
141 char *ext;
142 int errflag = 0;
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)
154 break;
156 if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR)))
157 continue;
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, '.')))
166 *(ext++) = '\0';
167 else
168 ext = "raw";
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);
172 errflag = 1;
176 /* Write out frame */
177 if (fs)
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);
192 if (fs)
193 ast_closestream(fs);
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);
203 return NULL;
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)
209 pthread_t thread;
210 struct mixmonitor *mixmonitor;
211 char postprocess2[1024] = "";
212 size_t len;
214 len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
216 postprocess2[0] = 0;
217 /* If a post process system command is given attach it to the structure */
218 if (!ast_strlen_zero(post_process)) {
219 char *p1, *p2;
221 p1 = ast_strdupa(post_process);
222 for (p2 = p1; *p2 ; p2++) {
223 if (*p2 == '^' && *(p2+1) == '{') {
224 *p2 = '$';
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))) {
234 return;
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);
253 return;
256 ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
258 if (readvol)
259 mixmonitor->audiohook.options.read_volume = readvol;
260 if (writevol)
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);
268 return;
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");
288 return -1;
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");
297 return -1;
300 if (args.options) {
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]);
310 } else {
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]);
320 } else {
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]);
330 } else {
331 readvol = writevol = get_volfactor(x);
336 /* if not provided an absolute path, use the system-configured monitoring directory */
337 if (args.filename[0] != '/') {
338 char *build;
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, '/')))
347 *slash = '\0';
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);
353 return 0;
356 static int stop_mixmonitor_exec(struct ast_channel *chan, void *data)
358 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
359 return 0;
362 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
364 struct ast_channel *chan;
366 switch (cmd) {
367 case CLI_INIT:
368 e->command = "mixmonitor [start|stop]";
369 e->usage =
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";
373 return NULL;
374 case CLI_GENERATE:
375 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
378 if (a->argc < 3)
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 */
384 return CLI_SUCCESS;
387 if (!strcasecmp(a->argv[1], "start")) {
388 mixmonitor_exec(chan, a->argv[3]);
389 ast_channel_unlock(chan);
390 } else {
391 ast_channel_unlock(chan);
392 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
395 return CLI_SUCCESS;
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)
404 int res;
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);
410 return res;
413 static int load_module(void)
415 int res;
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);
421 return res;
424 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");