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