ddf8f5248c59b41fcfe34aa4cc23e2cbf749fb79
[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, 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  * \brief MixMonitor() - Record a call and mix the audio during the recording
26  * \ingroup applications
27  */
28
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <string.h>
32
33 #include "asterisk.h"
34
35 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
36
37 #include "asterisk/file.h"
38 #include "asterisk/logger.h"
39 #include "asterisk/channel.h"
40 #include "asterisk/pbx.h"
41 #include "asterisk/module.h"
42 #include "asterisk/lock.h"
43 #include "asterisk/cli.h"
44 #include "asterisk/options.h"
45 #include "asterisk/app.h"
46 #include "asterisk/linkedlists.h"
47
48 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
49
50 static const char *tdesc = "Mixed Audio Monitoring Application";
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\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.\n"
63 " v(<x>) - Adjust the heard volume by a factor of <x> (range -4 to 4)\n"        
64 " V(<x>) - Adjust the spoken volume by a factor of <x> (range -4 to 4)\n"       
65 " W(<x>) - Adjust the both heard and spoken volumes by a factor of <x>\n"
66 "         (range -4 to 4)\n\n"  
67 "<command> will be executed when the recording is over\n"
68 "Any strings matching ^{X} will be unescaped to ${X} and \n"
69 "all variables will be evaluated at that time.\n"
70 "The variable MIXMONITOR_FILENAME will contain the filename used to record.\n"
71 "";
72
73 STANDARD_LOCAL_USER;
74
75 LOCAL_USER_DECL;
76
77 static const char *mixmonitor_spy_type = "MixMonitor";
78
79 struct mixmonitor {
80         struct ast_channel *chan;
81         char *filename;
82         char *post_process;
83         unsigned int flags;
84         int readvol;
85         int writevol;
86 };
87
88 enum {
89         MUXFLAG_APPEND = (1 << 1),
90         MUXFLAG_BRIDGED = (1 << 2),
91         MUXFLAG_VOLUME = (1 << 3),
92         MUXFLAG_READVOLUME = (1 << 4),
93         MUXFLAG_WRITEVOLUME = (1 << 5),
94 } mixmonitor_flags;
95
96 enum {
97         OPT_ARG_READVOLUME = 0,
98         OPT_ARG_WRITEVOLUME,
99         OPT_ARG_VOLUME,
100         OPT_ARG_ARRAY_SIZE,
101 } mixmonitor_args;
102
103 AST_APP_OPTIONS(mixmonitor_opts, {
104         AST_APP_OPTION('a', MUXFLAG_APPEND),
105         AST_APP_OPTION('b', MUXFLAG_BRIDGED),
106         AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
107         AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
108         AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
109 });
110
111 static void stopmon(struct ast_channel *chan, struct ast_channel_spy *spy) 
112 {
113         /* If our status has changed, then the channel we're spying on is gone....
114            DON'T TOUCH IT!!!  RUN AWAY!!! */
115         if (spy->status != CHANSPY_RUNNING)
116                 return;
117
118         if (!chan)
119                 return;
120
121         ast_mutex_lock(&chan->lock);
122         ast_channel_spy_remove(chan, spy);
123         ast_mutex_unlock(&chan->lock);
124 }
125
126 static int startmon(struct ast_channel *chan, struct ast_channel_spy *spy) 
127 {
128         struct ast_channel *peer;
129         int res;
130
131         if (!chan)
132                 return -1;
133
134         ast_mutex_lock(&chan->lock);
135         res = ast_channel_spy_add(chan, spy);
136         ast_mutex_unlock(&chan->lock);
137                 
138         if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
139                 ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);  
140
141         return res;
142 }
143
144 #define SAMPLES_PER_FRAME 160
145
146 static void *mixmonitor_thread(void *obj) 
147 {
148         struct mixmonitor *mixmonitor = obj;
149         struct ast_channel_spy spy;
150         struct ast_filestream *fs = NULL;
151         char *ext, *name;
152         unsigned int oflags;
153         struct ast_frame *f;
154         char post_process[1024] = "";
155         
156         STANDARD_INCREMENT_USECOUNT;
157
158         name = ast_strdupa(mixmonitor->chan->name);
159
160         oflags = O_CREAT|O_WRONLY;
161         oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
162                 
163         if ((ext = strrchr(mixmonitor->filename, '.'))) {
164                 *(ext++) = '\0';
165         } else {
166                 ext = "raw";
167         }
168
169         fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0644);
170         if (!fs) {
171                 ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
172                 goto out;
173         }
174
175         if (ast_test_flag(mixmonitor, MUXFLAG_APPEND))
176                 ast_seekstream(fs, 0, SEEK_END);
177         
178         memset(&spy, 0, sizeof(spy));
179         ast_set_flag(&spy, CHANSPY_FORMAT_AUDIO);
180         ast_set_flag(&spy, CHANSPY_MIXAUDIO);
181         spy.type = mixmonitor_spy_type;
182         spy.status = CHANSPY_RUNNING;
183         spy.read_queue.format = AST_FORMAT_SLINEAR;
184         spy.write_queue.format = AST_FORMAT_SLINEAR;
185         if (mixmonitor->readvol) {
186                 ast_set_flag(&spy, CHANSPY_READ_VOLADJUST);
187                 spy.read_vol_adjustment = mixmonitor->readvol;
188         }
189         if (mixmonitor->writevol) {
190                 ast_set_flag(&spy, CHANSPY_WRITE_VOLADJUST);
191                 spy.write_vol_adjustment = mixmonitor->writevol;
192         }
193         ast_mutex_init(&spy.lock);
194
195         if (startmon(mixmonitor->chan, &spy)) {
196                 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
197                         spy.type, mixmonitor->chan->name);
198                 goto out2;
199         }
200
201         if (option_verbose > 1)
202                 ast_verbose(VERBOSE_PREFIX_2 "Begin MixMonitor Recording %s\n", name);
203         
204         while (1) {
205                 struct ast_frame *next;
206                 int write;
207
208                 ast_mutex_lock(&spy.lock);
209
210                 ast_channel_spy_trigger_wait(&spy);
211                 
212                 if (ast_check_hangup(mixmonitor->chan) || spy.status != CHANSPY_RUNNING) {
213                         ast_mutex_unlock(&spy.lock);
214                         break;
215                 }
216                 
217                 while (1) {
218                         if (!(f = ast_channel_spy_read_frame(&spy, SAMPLES_PER_FRAME)))
219                                 break;
220
221                         write = (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) ||
222                                  ast_bridged_channel(mixmonitor->chan));
223
224                         /* it is possible for ast_channel_spy_read_frame() to return a chain
225                            of frames if a queue flush was necessary, so process them
226                         */
227                         for (; f; f = next) {
228                                 next = f->next;
229                                 if (write)
230                                         ast_writestream(fs, f);
231                                 ast_frfree(f);
232                         }
233                 }
234
235                 ast_mutex_unlock(&spy.lock);
236         }
237         
238         if (mixmonitor->post_process) {
239                 char *p;
240
241                 for (p = mixmonitor->post_process; *p ; p++) {
242                         if (*p == '^' && *(p+1) == '{') {
243                                 *p = '$';
244                         }
245                 }
246                 pbx_substitute_variables_helper(mixmonitor->chan, mixmonitor->post_process, post_process, sizeof(post_process) - 1);
247         }
248
249         stopmon(mixmonitor->chan, &spy);
250
251         if (option_verbose > 1)
252                 ast_verbose(VERBOSE_PREFIX_2 "End MixMonitor Recording %s\n", name);
253
254         if (!ast_strlen_zero(post_process)) {
255                 if (option_verbose > 2)
256                         ast_verbose(VERBOSE_PREFIX_2 "Executing [%s]\n", post_process);
257                 ast_safe_system(post_process);
258         }
259
260 out2:
261         ast_mutex_destroy(&spy.lock);
262
263         if (fs)
264                 ast_closestream(fs);
265
266 out:
267         free(mixmonitor);
268
269         STANDARD_DECREMENT_USECOUNT;
270
271         return NULL;
272 }
273
274 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
275                                   int readvol, int writevol, const char *post_process) 
276 {
277         pthread_attr_t attr;
278         pthread_t thread;
279         struct mixmonitor *mixmonitor;
280         int len;
281
282         len = sizeof(*mixmonitor) + strlen(filename) + 1;
283         if (!ast_strlen_zero(post_process))
284                 len += strlen(post_process) + 1;
285
286         if (!(mixmonitor = calloc(1, len))) {
287                 ast_log(LOG_ERROR, "Memory Error!\n");
288                 return;
289         }
290
291         mixmonitor->chan = chan;
292         mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor);
293         strcpy(mixmonitor->filename, filename);
294         if (!ast_strlen_zero(post_process)) {
295                 mixmonitor->post_process = mixmonitor->filename + strlen(filename) + 1;
296                 strcpy(mixmonitor->post_process, post_process);
297         }
298         mixmonitor->readvol = readvol;
299         mixmonitor->writevol = writevol;
300         mixmonitor->flags = flags;
301
302         pthread_attr_init(&attr);
303         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
304         ast_pthread_create(&thread, &attr, mixmonitor_thread, mixmonitor);
305         pthread_attr_destroy(&attr);
306 }
307
308 static int mixmonitor_exec(struct ast_channel *chan, void *data)
309 {
310         int x, readvol = 0, writevol = 0;
311         struct localuser *u;
312         struct ast_flags flags = {0};
313         char *parse;
314         AST_DECLARE_APP_ARGS(args,
315                 AST_APP_ARG(filename);
316                 AST_APP_ARG(options);
317                 AST_APP_ARG(post_process);
318         );
319         
320         if (ast_strlen_zero(data)) {
321                 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
322                 return -1;
323         }
324
325         LOCAL_USER_ADD(u);
326
327         if (!(parse = ast_strdupa(data))) {
328                 ast_log(LOG_WARNING, "Memory Error!\n");
329                 LOCAL_USER_REMOVE(u);
330                 return -1;
331         }
332
333         AST_STANDARD_APP_ARGS(args, parse);
334         
335         if (ast_strlen_zero(args.filename)) {
336                 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
337                 LOCAL_USER_REMOVE(u);
338                 return -1;
339         }
340
341         if (args.options) {
342                 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
343
344                 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
345
346                 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
347                         if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
348                                 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
349                         } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
350                                 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
351                         } else {
352                                 readvol = get_volfactor(x);
353                         }
354                 }
355                 
356                 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
357                         if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
358                                 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
359                         } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
360                                 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
361                         } else {
362                                 writevol = get_volfactor(x);
363                         }
364                 }
365                 
366                 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
367                         if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
368                                 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
369                         } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
370                                 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
371                         } else {
372                                 readvol = writevol = get_volfactor(x);
373                         }
374                 }
375         }
376
377         /* if not provided an absolute path, use the system-configured monitoring directory */
378         if (args.filename[0] != '/') {
379                 char *build;
380
381                 build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
382                 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
383                 args.filename = build;
384         }
385
386         pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
387         launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
388
389         LOCAL_USER_REMOVE(u);
390
391         return 0;
392 }
393
394 static int mixmonitor_cli(int fd, int argc, char **argv) 
395 {
396         struct ast_channel *chan;
397
398         if (argc < 3)
399                 return RESULT_SHOWUSAGE;
400
401         if (!(chan = ast_get_channel_by_name_prefix_locked(argv[2], strlen(argv[2])))) {
402                 ast_cli(fd, "No channel matching '%s' found.\n", argv[2]);
403                 return RESULT_SUCCESS;
404         }
405
406         if (!strcasecmp(argv[1], "start"))
407                 mixmonitor_exec(chan, argv[3]);
408         else if (!strcasecmp(argv[1], "stop"))
409                 ast_channel_spy_stop_by_type(chan, mixmonitor_spy_type);
410
411         ast_mutex_unlock(&chan->lock);
412
413         return RESULT_SUCCESS;
414 }
415
416
417 static struct ast_cli_entry cli_mixmonitor = {
418         { "mixmonitor", NULL, NULL },
419         mixmonitor_cli, 
420         "Execute a MixMonitor command",
421         "mixmonitor <start|stop> <chan_name> [<args>]\n"
422 };
423
424
425 int unload_module(void)
426 {
427         int res;
428
429         res = ast_cli_unregister(&cli_mixmonitor);
430         res |= ast_unregister_application(app);
431         
432         STANDARD_HANGUP_LOCALUSERS;
433
434         return res;
435 }
436
437 int load_module(void)
438 {
439         int res;
440
441         res = ast_cli_register(&cli_mixmonitor);
442         res |= ast_register_application(app, mixmonitor_exec, synopsis, desc);
443
444         return res;
445 }
446
447 char *description(void)
448 {
449         return (char *) tdesc;
450 }
451
452 int usecount(void)
453 {
454         int res;
455
456         STANDARD_USECOUNT(res);
457
458         return res;
459 }
460
461 char *key()
462 {
463         return ASTERISK_GPL_KEY;
464 }