create the IAX2 processing threads as background threads so they will use smaller...
[asterisk-bristuff.git] / apps / app_mixmonitor.c
blob1171fc89e17adaee41abac3acc75bd2181aaf229
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/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"
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.\n"
72 " v(<x>) - Adjust the heard volume by a factor of <x> (range -4 to 4)\n"
73 " V(<x>) - Adjust the spoken volume by a factor of <x> (range -4 to 4)\n"
74 " W(<x>) - Adjust the both heard and spoken volumes by a factor of <x>\n"
75 " (range -4 to 4)\n\n"
76 "<command> will be executed when the recording is over\n"
77 "Any strings matching ^{X} will be unescaped to ${X} and \n"
78 "all variables will be evaluated at that time.\n"
79 "The variable MIXMONITOR_FILENAME will contain the filename used to record.\n"
80 "";
82 static const char *stop_app = "StopMixMonitor";
83 static const char *stop_synopsis = "Stop recording a call through MixMonitor";
84 static const char *stop_desc = ""
85 " StopMixMonitor()\n\n"
86 "Stops the audio recording that was started with a call to MixMonitor()\n"
87 "on the current channel.\n"
88 "";
90 struct module_symbols *me;
92 static const char *mixmonitor_spy_type = "MixMonitor";
94 struct mixmonitor {
95 struct ast_channel_spy spy;
96 struct ast_filestream *fs;
97 char *post_process;
98 char *name;
99 unsigned int flags;
102 enum {
103 MUXFLAG_APPEND = (1 << 1),
104 MUXFLAG_BRIDGED = (1 << 2),
105 MUXFLAG_VOLUME = (1 << 3),
106 MUXFLAG_READVOLUME = (1 << 4),
107 MUXFLAG_WRITEVOLUME = (1 << 5),
108 } mixmonitor_flags;
110 enum {
111 OPT_ARG_READVOLUME = 0,
112 OPT_ARG_WRITEVOLUME,
113 OPT_ARG_VOLUME,
114 OPT_ARG_ARRAY_SIZE,
115 } mixmonitor_args;
117 AST_APP_OPTIONS(mixmonitor_opts, {
118 AST_APP_OPTION('a', MUXFLAG_APPEND),
119 AST_APP_OPTION('b', MUXFLAG_BRIDGED),
120 AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
121 AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
122 AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
125 static int startmon(struct ast_channel *chan, struct ast_channel_spy *spy)
127 struct ast_channel *peer;
128 int res;
130 if (!chan)
131 return -1;
133 ast_channel_lock(chan);
134 res = ast_channel_spy_add(chan, spy);
135 ast_channel_unlock(chan);
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_frame *f = NULL;
151 if (option_verbose > 1)
152 ast_verbose(VERBOSE_PREFIX_2 "Begin MixMonitor Recording %s\n", mixmonitor->name);
154 ast_mutex_lock(&mixmonitor->spy.lock);
156 while (mixmonitor->spy.chan) {
157 struct ast_frame *next;
158 int write;
160 ast_channel_spy_trigger_wait(&mixmonitor->spy);
162 if (!mixmonitor->spy.chan || mixmonitor->spy.status != CHANSPY_RUNNING)
163 break;
165 while (1) {
166 if (!(f = ast_channel_spy_read_frame(&mixmonitor->spy, SAMPLES_PER_FRAME)))
167 break;
169 write = (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) ||
170 ast_bridged_channel(mixmonitor->spy.chan));
172 /* it is possible for ast_channel_spy_read_frame() to return a chain
173 of frames if a queue flush was necessary, so process them
175 for (; f; f = next) {
176 next = AST_LIST_NEXT(f, frame_list);
177 if (write)
178 ast_writestream(mixmonitor->fs, f);
179 ast_frame_free(f, 0);
184 ast_mutex_unlock(&mixmonitor->spy.lock);
186 ast_channel_spy_free(&mixmonitor->spy);
188 if (option_verbose > 1)
189 ast_verbose(VERBOSE_PREFIX_2 "End MixMonitor Recording %s\n", mixmonitor->name);
191 if (mixmonitor->post_process) {
192 if (option_verbose > 2)
193 ast_verbose(VERBOSE_PREFIX_2 "Executing [%s]\n", mixmonitor->post_process);
194 ast_safe_system(mixmonitor->post_process);
197 ast_closestream(mixmonitor->fs);
199 free(mixmonitor);
202 return NULL;
205 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
206 int readvol, int writevol, const char *post_process)
208 pthread_attr_t attr;
209 pthread_t thread;
210 struct mixmonitor *mixmonitor;
211 char *file_name, *ext;
212 char postprocess2[1024] = "";
213 unsigned int oflags;
214 size_t len;
216 len = sizeof(*mixmonitor) + strlen(chan->name) + 1;
218 /* If a post process system command is given attach it to the structure */
219 if (!ast_strlen_zero(post_process)) {
220 char *p1, *p2;
222 p1 = ast_strdupa(post_process);
223 for (p2 = p1; *p2 ; p2++) {
224 if (*p2 == '^' && *(p2+1) == '{') {
225 *p2 = '$';
229 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
230 if (!ast_strlen_zero(postprocess2))
231 len += strlen(postprocess2) + 1;
234 /* Pre-allocate mixmonitor structure and spy */
235 if (!(mixmonitor = calloc(1, len))) {
236 return;
239 /* Copy over flags and channel name */
240 mixmonitor->flags = flags;
241 mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
242 strcpy(mixmonitor->name, chan->name);
243 if (!ast_strlen_zero(postprocess2)) {
244 mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + 1;
245 strcpy(mixmonitor->post_process, postprocess2);
248 /* Determine creation flags and filename plus extension for filestream */
249 oflags = O_CREAT | O_WRONLY;
250 oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
251 file_name = ast_strdupa(filename);
252 if ((ext = strrchr(file_name, '.'))) {
253 *(ext++) = '\0';
254 } else {
255 ext = "raw";
258 /* Move onto actually creating the filestream */
259 mixmonitor->fs = ast_writefile(file_name, ext, NULL, oflags, 0, 0644);
260 if (!mixmonitor->fs) {
261 ast_log(LOG_ERROR, "Cannot open %s.%s\n", file_name, ext);
262 free(mixmonitor);
263 return;
266 /* Setup the actual spy before creating our thread */
267 ast_set_flag(&mixmonitor->spy, CHANSPY_FORMAT_AUDIO);
268 ast_set_flag(&mixmonitor->spy, CHANSPY_MIXAUDIO);
269 mixmonitor->spy.type = mixmonitor_spy_type;
270 mixmonitor->spy.status = CHANSPY_RUNNING;
271 mixmonitor->spy.read_queue.format = AST_FORMAT_SLINEAR;
272 mixmonitor->spy.write_queue.format = AST_FORMAT_SLINEAR;
273 if (readvol) {
274 ast_set_flag(&mixmonitor->spy, CHANSPY_READ_VOLADJUST);
275 mixmonitor->spy.read_vol_adjustment = readvol;
277 if (writevol) {
278 ast_set_flag(&mixmonitor->spy, CHANSPY_WRITE_VOLADJUST);
279 mixmonitor->spy.write_vol_adjustment = writevol;
281 ast_mutex_init(&mixmonitor->spy.lock);
283 if (startmon(chan, &mixmonitor->spy)) {
284 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
285 mixmonitor->spy.type, chan->name);
286 /* Since we couldn't add ourselves - bail out! */
287 ast_mutex_destroy(&mixmonitor->spy.lock);
288 ast_closestream(mixmonitor->fs);
289 free(mixmonitor);
290 return;
293 pthread_attr_init(&attr);
294 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
295 ast_pthread_create_background(&thread, &attr, mixmonitor_thread, mixmonitor);
296 pthread_attr_destroy(&attr);
300 static int mixmonitor_exec(struct ast_channel *chan, void *data)
302 int x, readvol = 0, writevol = 0;
303 struct ast_module_user *u;
304 struct ast_flags flags = {0};
305 char *parse;
306 AST_DECLARE_APP_ARGS(args,
307 AST_APP_ARG(filename);
308 AST_APP_ARG(options);
309 AST_APP_ARG(post_process);
312 if (ast_strlen_zero(data)) {
313 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
314 return -1;
317 u = ast_module_user_add(chan);
319 parse = ast_strdupa(data);
321 AST_STANDARD_APP_ARGS(args, parse);
323 if (ast_strlen_zero(args.filename)) {
324 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
325 ast_module_user_remove(u);
326 return -1;
329 if (args.options) {
330 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
332 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
334 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
335 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
336 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
337 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
338 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
339 } else {
340 readvol = get_volfactor(x);
344 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
345 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
346 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
347 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
348 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
349 } else {
350 writevol = get_volfactor(x);
354 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
355 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
356 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
357 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
358 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
359 } else {
360 readvol = writevol = get_volfactor(x);
365 /* if not provided an absolute path, use the system-configured monitoring directory */
366 if (args.filename[0] != '/') {
367 char *build;
369 build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
370 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
371 args.filename = build;
374 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
375 launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
377 ast_module_user_remove(u);
379 return 0;
382 static int stop_mixmonitor_exec(struct ast_channel *chan, void *data)
384 struct ast_module_user *u;
386 u = ast_module_user_add(chan);
388 ast_channel_lock(chan);
389 ast_channel_spy_stop_by_type(chan, mixmonitor_spy_type);
390 ast_channel_unlock(chan);
392 ast_module_user_remove(u);
394 return 0;
397 static int mixmonitor_cli(int fd, int argc, char **argv)
399 struct ast_channel *chan;
401 if (argc < 3)
402 return RESULT_SHOWUSAGE;
404 if (!(chan = ast_get_channel_by_name_prefix_locked(argv[2], strlen(argv[2])))) {
405 ast_cli(fd, "No channel matching '%s' found.\n", argv[2]);
406 return RESULT_SUCCESS;
409 if (!strcasecmp(argv[1], "start"))
410 mixmonitor_exec(chan, argv[3]);
411 else if (!strcasecmp(argv[1], "stop"))
412 ast_channel_spy_stop_by_type(chan, mixmonitor_spy_type);
414 ast_channel_unlock(chan);
416 return RESULT_SUCCESS;
419 static char *complete_mixmonitor_cli(const char *line, const char *word, int pos, int state)
421 return ast_complete_channels(line, word, pos, state, 2);
424 static struct ast_cli_entry cli_mixmonitor[] = {
425 { { "mixmonitor", NULL, NULL },
426 mixmonitor_cli, "Execute a MixMonitor command.",
427 "mixmonitor <start|stop> <chan_name> [args]\n\n"
428 "The optional arguments are passed to the\n"
429 "MixMonitor application when the 'start' command is used.\n",
430 complete_mixmonitor_cli },
433 static int unload_module(void)
435 int res;
437 ast_cli_unregister_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
438 res = ast_unregister_application(stop_app);
439 res |= ast_unregister_application(app);
441 ast_module_user_hangup_all();
443 return res;
446 static int load_module(void)
448 int res;
450 ast_cli_register_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
451 res = ast_register_application(app, mixmonitor_exec, synopsis, desc);
452 res |= ast_register_application(stop_app, stop_mixmonitor_exec, stop_synopsis, stop_desc);
454 return res;
457 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");