(closes issue #12846)
[asterisk-bristuff.git] / apps / app_mixmonitor.c
blob3cf79b1fc2850cbf707423815ec80433083fcc22
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 <stdlib.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <unistd.h>
45 #include "asterisk/file.h"
46 #include "asterisk/logger.h"
47 #include "asterisk/channel.h"
48 #include "asterisk/audiohook.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"
67 "asterisk.conf.\n\n"
68 "Valid options:\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"
72 " party.\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"
81 "";
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"
89 "";
91 struct module_symbols *me;
93 static const char *mixmonitor_spy_type = "MixMonitor";
95 struct mixmonitor {
96 struct ast_audiohook audiohook;
97 char *filename;
98 char *post_process;
99 char *name;
100 unsigned int flags;
101 struct ast_channel *chan;
104 enum {
105 MUXFLAG_APPEND = (1 << 1),
106 MUXFLAG_BRIDGED = (1 << 2),
107 MUXFLAG_VOLUME = (1 << 3),
108 MUXFLAG_READVOLUME = (1 << 4),
109 MUXFLAG_WRITEVOLUME = (1 << 5),
110 } mixmonitor_flags;
112 enum {
113 OPT_ARG_READVOLUME = 0,
114 OPT_ARG_WRITEVOLUME,
115 OPT_ARG_VOLUME,
116 OPT_ARG_ARRAY_SIZE,
117 } mixmonitor_args;
119 AST_APP_OPTIONS(mixmonitor_opts, {
120 AST_APP_OPTION('a', MUXFLAG_APPEND),
121 AST_APP_OPTION('b', MUXFLAG_BRIDGED),
122 AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
123 AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
124 AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
127 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
129 struct ast_channel *peer;
130 int res;
132 if (!chan)
133 return -1;
135 res = ast_audiohook_attach(chan, audiohook);
137 if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
138 ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
140 return res;
143 #define SAMPLES_PER_FRAME 160
145 static void *mixmonitor_thread(void *obj)
147 struct mixmonitor *mixmonitor = obj;
148 struct ast_filestream *fs = NULL;
149 unsigned int oflags;
150 char *ext;
151 int errflag = 0;
153 if (option_verbose > 1)
154 ast_verbose(VERBOSE_PREFIX_2 "Begin MixMonitor Recording %s\n", mixmonitor->name);
156 ast_audiohook_lock(&mixmonitor->audiohook);
158 while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
159 struct ast_frame *fr = NULL;
161 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
163 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING)
164 break;
166 if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR)))
167 continue;
169 if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || ast_bridged_channel(mixmonitor->chan)) {
170 /* Initialize the file if not already done so */
171 if (!fs && !errflag) {
172 oflags = O_CREAT | O_WRONLY;
173 oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
175 if ((ext = strrchr(mixmonitor->filename, '.')))
176 *(ext++) = '\0';
177 else
178 ext = "raw";
180 if (!(fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0644))) {
181 ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
182 errflag = 1;
186 /* Write out the frame */
187 if (fs)
188 ast_writestream(fs, fr);
191 /* All done! free it. */
192 ast_frame_free(fr, 0);
195 ast_audiohook_detach(&mixmonitor->audiohook);
196 ast_audiohook_unlock(&mixmonitor->audiohook);
197 ast_audiohook_destroy(&mixmonitor->audiohook);
199 if (option_verbose > 1)
200 ast_verbose(VERBOSE_PREFIX_2 "End MixMonitor Recording %s\n", mixmonitor->name);
202 if (fs)
203 ast_closestream(fs);
205 if (mixmonitor->post_process) {
206 if (option_verbose > 2)
207 ast_verbose(VERBOSE_PREFIX_2 "Executing [%s]\n", mixmonitor->post_process);
208 ast_safe_system(mixmonitor->post_process);
211 free(mixmonitor);
214 return NULL;
217 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
218 int readvol, int writevol, const char *post_process)
220 pthread_attr_t attr;
221 pthread_t thread;
222 struct mixmonitor *mixmonitor;
223 char postprocess2[1024] = "";
224 size_t len;
226 len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
228 /* If a post process system command is given attach it to the structure */
229 if (!ast_strlen_zero(post_process)) {
230 char *p1, *p2;
232 p1 = ast_strdupa(post_process);
233 for (p2 = p1; *p2 ; p2++) {
234 if (*p2 == '^' && *(p2+1) == '{') {
235 *p2 = '$';
239 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
240 if (!ast_strlen_zero(postprocess2))
241 len += strlen(postprocess2) + 1;
244 /* Pre-allocate mixmonitor structure and spy */
245 if (!(mixmonitor = calloc(1, len))) {
246 return;
249 /* Copy over flags and channel name */
250 mixmonitor->flags = flags;
251 mixmonitor->chan = chan;
252 mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
253 strcpy(mixmonitor->name, chan->name);
254 if (!ast_strlen_zero(postprocess2)) {
255 mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
256 strcpy(mixmonitor->post_process, postprocess2);
259 mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
260 strcpy(mixmonitor->filename, filename);
262 /* Setup the actual spy before creating our thread */
263 if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
264 free(mixmonitor);
265 return;
268 ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
270 if (readvol)
271 mixmonitor->audiohook.options.read_volume = readvol;
272 if (writevol)
273 mixmonitor->audiohook.options.write_volume = writevol;
275 if (startmon(chan, &mixmonitor->audiohook)) {
276 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
277 mixmonitor_spy_type, chan->name);
278 /* Since we couldn't add ourselves - bail out! */
279 ast_audiohook_destroy(&mixmonitor->audiohook);
280 free(mixmonitor);
281 return;
284 pthread_attr_init(&attr);
285 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
286 ast_pthread_create_background(&thread, &attr, mixmonitor_thread, mixmonitor);
287 pthread_attr_destroy(&attr);
291 static int mixmonitor_exec(struct ast_channel *chan, void *data)
293 int x, readvol = 0, writevol = 0;
294 struct ast_module_user *u;
295 struct ast_flags flags = {0};
296 char *parse;
297 AST_DECLARE_APP_ARGS(args,
298 AST_APP_ARG(filename);
299 AST_APP_ARG(options);
300 AST_APP_ARG(post_process);
303 if (ast_strlen_zero(data)) {
304 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
305 return -1;
308 u = ast_module_user_add(chan);
310 parse = ast_strdupa(data);
312 AST_STANDARD_APP_ARGS(args, parse);
314 if (ast_strlen_zero(args.filename)) {
315 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
316 ast_module_user_remove(u);
317 return -1;
320 if (args.options) {
321 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
323 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
325 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
326 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
327 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
328 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
329 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
330 } else {
331 readvol = get_volfactor(x);
335 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
336 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
337 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
338 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
339 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
340 } else {
341 writevol = get_volfactor(x);
345 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
346 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
347 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
348 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
349 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
350 } else {
351 readvol = writevol = get_volfactor(x);
356 /* if not provided an absolute path, use the system-configured monitoring directory */
357 if (args.filename[0] != '/') {
358 char *build;
360 build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
361 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
362 args.filename = build;
365 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
366 launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
368 ast_module_user_remove(u);
370 return 0;
373 static int stop_mixmonitor_exec(struct ast_channel *chan, void *data)
375 struct ast_module_user *u;
377 u = ast_module_user_add(chan);
379 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
381 ast_module_user_remove(u);
383 return 0;
386 static int mixmonitor_cli(int fd, int argc, char **argv)
388 struct ast_channel *chan;
390 if (argc < 3)
391 return RESULT_SHOWUSAGE;
393 if (!(chan = ast_get_channel_by_name_prefix_locked(argv[2], strlen(argv[2])))) {
394 ast_cli(fd, "No channel matching '%s' found.\n", argv[2]);
395 return RESULT_SUCCESS;
398 if (!strcasecmp(argv[1], "start"))
399 mixmonitor_exec(chan, argv[3]);
400 else if (!strcasecmp(argv[1], "stop"))
401 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
403 ast_channel_unlock(chan);
405 return RESULT_SUCCESS;
408 static char *complete_mixmonitor_cli(const char *line, const char *word, int pos, int state)
410 return ast_complete_channels(line, word, pos, state, 2);
413 static struct ast_cli_entry cli_mixmonitor[] = {
414 { { "mixmonitor", NULL, NULL },
415 mixmonitor_cli, "Execute a MixMonitor command.",
416 "mixmonitor <start|stop> <chan_name> [args]\n\n"
417 "The optional arguments are passed to the\n"
418 "MixMonitor application when the 'start' command is used.\n",
419 complete_mixmonitor_cli },
422 static int unload_module(void)
424 int res;
426 ast_cli_unregister_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
427 res = ast_unregister_application(stop_app);
428 res |= ast_unregister_application(app);
430 ast_module_user_hangup_all();
432 return res;
435 static int load_module(void)
437 int res;
439 ast_cli_register_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
440 res = ast_register_application(app, mixmonitor_exec, synopsis, desc);
441 res |= ast_register_application(stop_app, stop_mixmonitor_exec, stop_synopsis, stop_desc);
443 return res;
446 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");