13c25d8c7823cb072b9b93c7c0093ef02a3b9943
[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
239                 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
240                 if (!ast_strlen_zero(postprocess2))
241                         len += strlen(postprocess2) + 1;
242         }
243
244         /* Pre-allocate mixmonitor structure and spy */
245         if (!(mixmonitor = ast_calloc(1, len))) {
246                 return;
247         }
248
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);
257         }
258
259         mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
260         strcpy(mixmonitor->filename, filename);
261
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;
266         }
267
268         ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_WRITE);
269
270         if (readvol)
271                 mixmonitor->audiohook.options.read_volume = readvol;
272         if (writevol)
273                 mixmonitor->audiohook.options.write_volume = writevol;
274
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                 ast_audiohook_destroy(&mixmonitor->audiohook);
279                 ast_free(mixmonitor);
280                 return;
281         }
282
283         ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
284
285 }
286
287 static int mixmonitor_exec(struct ast_channel *chan, void *data)
288 {
289         int x, readvol = 0, writevol = 0;
290         struct ast_flags flags = {0};
291         char *parse, *tmp, *slash;
292         AST_DECLARE_APP_ARGS(args,
293                 AST_APP_ARG(filename);
294                 AST_APP_ARG(options);
295                 AST_APP_ARG(post_process);
296         );
297         
298         if (ast_strlen_zero(data)) {
299                 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
300                 return -1;
301         }
302
303         parse = ast_strdupa(data);
304
305         AST_STANDARD_APP_ARGS(args, parse);
306         
307         if (ast_strlen_zero(args.filename)) {
308                 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
309                 return -1;
310         }
311
312         if (args.options) {
313                 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
314
315                 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
316
317                 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
318                         if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
319                                 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
320                         } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
321                                 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
322                         } else {
323                                 readvol = get_volfactor(x);
324                         }
325                 }
326                 
327                 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
328                         if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
329                                 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
330                         } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
331                                 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
332                         } else {
333                                 writevol = get_volfactor(x);
334                         }
335                 }
336                 
337                 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
338                         if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
339                                 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
340                         } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
341                                 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
342                         } else {
343                                 readvol = writevol = get_volfactor(x);
344                         }
345                 }
346         }
347
348         /* if not provided an absolute path, use the system-configured monitoring directory */
349         if (args.filename[0] != '/') {
350                 char *build;
351
352                 build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
353                 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
354                 args.filename = build;
355         }
356
357         tmp = ast_strdupa(args.filename);
358         if ((slash = strrchr(tmp, '/')))
359                 *slash = '\0';
360         ast_mkdir(tmp, 0777);
361
362         pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
363         launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
364
365         return 0;
366 }
367
368 static int stop_mixmonitor_exec(struct ast_channel *chan, void *data)
369 {
370         ast_audiohook_detach_source(chan, mixmonitor_spy_type);
371         return 0;
372 }
373
374 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
375 {
376         struct ast_channel *chan;
377
378         switch (cmd) {
379         case CLI_INIT:
380                 e->command = "mixmonitor [start|stop]";
381                 e->usage =
382                         "Usage: mixmonitor <start|stop> <chan_name> [args]\n"
383                         "       The optional arguments are passed to the MixMonitor\n"
384                         "       application when the 'start' command is used.\n";
385                 return NULL;
386         case CLI_GENERATE:
387                 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
388         }
389
390         if (a->argc < 3)
391                 return CLI_SHOWUSAGE;
392
393         if (!(chan = ast_get_channel_by_name_prefix_locked(a->argv[2], strlen(a->argv[2])))) {
394                 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
395                 /* Technically this is a failure, but we don't want 2 errors printing out */
396                 return CLI_SUCCESS;
397         }
398
399         if (!strcasecmp(a->argv[1], "start")) {
400                 mixmonitor_exec(chan, a->argv[3]);
401                 ast_channel_unlock(chan);
402         } else {
403                 ast_channel_unlock(chan);
404                 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
405         }
406
407         return CLI_SUCCESS;
408 }
409
410 static struct ast_cli_entry cli_mixmonitor[] = {
411         AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
412 };
413
414 static int unload_module(void)
415 {
416         int res;
417
418         ast_cli_unregister_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
419         res = ast_unregister_application(stop_app);
420         res |= ast_unregister_application(app);
421         
422         return res;
423 }
424
425 static int load_module(void)
426 {
427         int res;
428
429         ast_cli_register_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
430         res = ast_register_application(app, mixmonitor_exec, synopsis, desc);
431         res |= ast_register_application(stop_app, stop_mixmonitor_exec, stop_synopsis, stop_desc);
432
433         return res;
434 }
435
436 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");