3c4ef97bc9882612750caa674fffbe2b85c94065
[asterisk/asterisk.git] / apps / app_mixmonitor.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2005, Anthony Minessale II
5  * Copyright (C) 2005 - 2006, Digium, Inc.
6  *
7  * Mark Spencer <markster@digium.com>
8  * Kevin P. Fleming <kpfleming@digium.com>
9  *
10  * Based on app_muxmon.c provided by
11  * Anthony Minessale II <anthmct@yahoo.com>
12  *
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.
18  *
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.
22  */
23
24 /*! \file
25  *
26  * \brief MixMonitor() - Record a call and mix the audio during the recording
27  * \ingroup applications
28  *
29  * \author Mark Spencer <markster@digium.com>
30  * \author Kevin P. Fleming <kpfleming@digium.com>
31  *
32  * \note Based on app_muxmon.c provided by
33  * Anthony Minessale II <anthmct@yahoo.com>
34  */
35
36 #include "asterisk.h"
37
38 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
39
40 #include <stdlib.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <unistd.h>
44
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"
57
58 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
59
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 "";
82
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 "";
90
91 struct module_symbols *me;
92
93 static const char *mixmonitor_spy_type = "MixMonitor";
94
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;
102 };
103
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;
111
112 enum {
113         OPT_ARG_READVOLUME = 0,
114         OPT_ARG_WRITEVOLUME,
115         OPT_ARG_VOLUME,
116         OPT_ARG_ARRAY_SIZE,
117 } mixmonitor_args;
118
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),
125 });
126
127 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook) 
128 {
129         struct ast_channel *peer = NULL;
130         int res = 0;
131
132         if (!chan)
133                 return -1;
134
135         ast_audiohook_attach(chan, audiohook);
136
137         if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
138                 ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);  
139
140         return res;
141 }
142
143 #define SAMPLES_PER_FRAME 160
144
145 static void *mixmonitor_thread(void *obj) 
146 {
147         struct mixmonitor *mixmonitor = obj;
148         struct ast_filestream *fs = NULL;
149         unsigned int oflags;
150         char *ext;
151         int errflag = 0;
152
153         if (option_verbose > 1)
154                 ast_verbose(VERBOSE_PREFIX_2 "Begin MixMonitor Recording %s\n", mixmonitor->name);
155         
156         ast_audiohook_lock(&mixmonitor->audiohook);
157
158         while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
159                 struct ast_frame *fr = NULL;
160
161                 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
162
163                 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING)
164                         break;
165
166                 if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR)))
167                         continue;
168
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;
174                                 
175                                 if ((ext = strrchr(mixmonitor->filename, '.')))
176                                         *(ext++) = '\0';
177                                 else
178                                         ext = "raw";
179                                 
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;
183                                 }
184                         }
185                         
186                         /* Write out frame */
187                         if (fs)
188                                 ast_writestream(fs, fr);
189                 }
190
191                 /* All done! free it. */
192                 ast_frame_free(fr, 0);
193
194         }
195
196         ast_audiohook_detach(&mixmonitor->audiohook);
197         ast_audiohook_unlock(&mixmonitor->audiohook);
198         ast_audiohook_destroy(&mixmonitor->audiohook);
199
200         if (option_verbose > 1)
201                 ast_verbose(VERBOSE_PREFIX_2 "End MixMonitor Recording %s\n", mixmonitor->name);
202
203         if (mixmonitor->post_process) {
204                 if (option_verbose > 2)
205                         ast_verbose(VERBOSE_PREFIX_2 "Executing [%s]\n", mixmonitor->post_process);
206                 ast_safe_system(mixmonitor->post_process);
207         }
208                 
209         if (fs)
210                 ast_closestream(fs);
211
212         ast_free(mixmonitor);
213
214
215         return NULL;
216 }
217
218 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
219                                   int readvol, int writevol, const char *post_process) 
220 {
221         pthread_t thread;
222         struct mixmonitor *mixmonitor;
223         char postprocess2[1024];
224         size_t len;
225
226         len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
227
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;
231
232                 p1 = ast_strdupa(post_process);
233                 for (p2 = p1; *p2 ; p2++) {
234                         if (*p2 == '^' && *(p2+1) == '{') {
235                                 *p2 = '$';
236                         }
237                 }
238                 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
239                 if (!ast_strlen_zero(postprocess2))
240                         len += strlen(postprocess2) + 1;
241         }
242
243         /* Pre-allocate mixmonitor structure and spy */
244         if (!(mixmonitor = ast_calloc(1, len))) {
245                 return;
246         }
247
248         /* Copy over flags and channel name */
249         mixmonitor->flags = flags;
250         mixmonitor->chan = chan;
251         mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
252         strcpy(mixmonitor->name, chan->name);
253         if (!ast_strlen_zero(postprocess2)) {
254                 mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
255                 strcpy(mixmonitor->post_process, postprocess2);
256         }
257
258         mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
259         strcpy(mixmonitor->filename, filename);
260
261         /* Setup the actual spy before creating our thread */
262         if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
263                 free(mixmonitor);
264                 return;
265         }
266
267         ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_WRITE);
268
269         if (readvol)
270                 mixmonitor->audiohook.options.read_volume = readvol;
271         if (writevol)
272                 mixmonitor->audiohook.options.write_volume = writevol;
273
274         if (startmon(chan, &mixmonitor->audiohook)) {
275                 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
276                         mixmonitor_spy_type, chan->name);
277                 ast_audiohook_destroy(&mixmonitor->audiohook);
278                 ast_free(mixmonitor);
279                 return;
280         }
281
282         ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
283
284 }
285
286 static int mixmonitor_exec(struct ast_channel *chan, void *data)
287 {
288         int x, readvol = 0, writevol = 0;
289         struct ast_flags flags = {0};
290         char *parse, *tmp, *slash;
291         AST_DECLARE_APP_ARGS(args,
292                 AST_APP_ARG(filename);
293                 AST_APP_ARG(options);
294                 AST_APP_ARG(post_process);
295         );
296         
297         if (ast_strlen_zero(data)) {
298                 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
299                 return -1;
300         }
301
302         parse = ast_strdupa(data);
303
304         AST_STANDARD_APP_ARGS(args, parse);
305         
306         if (ast_strlen_zero(args.filename)) {
307                 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
308                 return -1;
309         }
310
311         if (args.options) {
312                 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
313
314                 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
315
316                 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
317                         if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
318                                 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
319                         } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
320                                 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
321                         } else {
322                                 readvol = get_volfactor(x);
323                         }
324                 }
325                 
326                 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
327                         if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
328                                 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
329                         } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
330                                 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
331                         } else {
332                                 writevol = get_volfactor(x);
333                         }
334                 }
335                 
336                 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
337                         if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
338                                 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
339                         } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
340                                 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
341                         } else {
342                                 readvol = writevol = get_volfactor(x);
343                         }
344                 }
345         }
346
347         /* if not provided an absolute path, use the system-configured monitoring directory */
348         if (args.filename[0] != '/') {
349                 char *build;
350
351                 build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
352                 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
353                 args.filename = build;
354         }
355
356         tmp = ast_strdupa(args.filename);
357         if ((slash = strrchr(tmp, '/')))
358                 *slash = '\0';
359         ast_mkdir(tmp, 0777);
360
361         pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
362         launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
363
364         return 0;
365 }
366
367 static int stop_mixmonitor_exec(struct ast_channel *chan, void *data)
368 {
369         ast_audiohook_detach_source(chan, mixmonitor_spy_type);
370         return 0;
371 }
372
373 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
374 {
375         struct ast_channel *chan;
376
377         switch (cmd) {
378         case CLI_INIT:
379                 e->command = "mixmonitor [start|stop]";
380                 e->usage =
381                         "Usage: mixmonitor <start|stop> <chan_name> [args]\n"
382                         "       The optional arguments are passed to the MixMonitor\n"
383                         "       application when the 'start' command is used.\n";
384                 return NULL;
385         case CLI_GENERATE:
386                 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
387         }
388
389         if (a->argc < 3)
390                 return CLI_SHOWUSAGE;
391
392         if (!(chan = ast_get_channel_by_name_prefix_locked(a->argv[2], strlen(a->argv[2])))) {
393                 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
394                 /* Technically this is a failure, but we don't want 2 errors printing out */
395                 return CLI_SUCCESS;
396         }
397
398         if (!strcasecmp(a->argv[1], "start")) {
399                 mixmonitor_exec(chan, a->argv[3]);
400                 ast_channel_unlock(chan);
401         } else {
402                 ast_channel_unlock(chan);
403                 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
404         }
405
406         return CLI_SUCCESS;
407 }
408
409 static struct ast_cli_entry cli_mixmonitor[] = {
410         AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
411 };
412
413 static int unload_module(void)
414 {
415         int res;
416
417         ast_cli_unregister_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
418         res = ast_unregister_application(stop_app);
419         res |= ast_unregister_application(app);
420         
421         return res;
422 }
423
424 static int load_module(void)
425 {
426         int res;
427
428         ast_cli_register_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
429         res = ast_register_application(app, mixmonitor_exec, synopsis, desc);
430         res |= ast_register_application(stop_app, stop_mixmonitor_exec, stop_synopsis, stop_desc);
431
432         return res;
433 }
434
435 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");