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