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