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