Use the same delimited character as the FILTER function in FIELDQTY and CUT.
[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/audiohook.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 or sounds played to each bridged\n"
72 "                party.\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}.\n"
79 "All variables will be evaluated at the time MixMonitor is called.\n"
80 "The variable MIXMONITOR_FILENAME will contain the filename used to record.\n"
81 "";
82
83 static const char *stop_app = "StopMixMonitor";
84 static const char *stop_synopsis = "Stop recording a call through MixMonitor";
85 static const char *stop_desc = ""
86 "  StopMixMonitor()\n\n"
87 "Stops the audio recording that was started with a call to MixMonitor()\n"
88 "on the current channel.\n"
89 "";
90
91 struct module_symbols *me;
92
93 static const char *mixmonitor_spy_type = "MixMonitor";
94
95 struct mixmonitor {
96         struct ast_audiohook audiohook;
97         char *filename;
98         char *post_process;
99         char *name;
100         unsigned int flags;
101 };
102
103 enum {
104         MUXFLAG_APPEND = (1 << 1),
105         MUXFLAG_BRIDGED = (1 << 2),
106         MUXFLAG_VOLUME = (1 << 3),
107         MUXFLAG_READVOLUME = (1 << 4),
108         MUXFLAG_WRITEVOLUME = (1 << 5),
109 } mixmonitor_flags;
110
111 enum {
112         OPT_ARG_READVOLUME = 0,
113         OPT_ARG_WRITEVOLUME,
114         OPT_ARG_VOLUME,
115         OPT_ARG_ARRAY_SIZE,
116 } mixmonitor_args;
117
118 AST_APP_OPTIONS(mixmonitor_opts, {
119         AST_APP_OPTION('a', MUXFLAG_APPEND),
120         AST_APP_OPTION('b', MUXFLAG_BRIDGED),
121         AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
122         AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
123         AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
124 });
125
126 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook) 
127 {
128         struct ast_channel *peer = NULL;
129         int res = 0;
130
131         if (!chan)
132                 return -1;
133
134         ast_audiohook_attach(chan, audiohook);
135
136         if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
137                 ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);  
138
139         return res;
140 }
141
142 #define SAMPLES_PER_FRAME 160
143
144 static void *mixmonitor_thread(void *obj) 
145 {
146         struct mixmonitor *mixmonitor = obj;
147         struct ast_filestream *fs = NULL;
148         unsigned int oflags;
149         char *ext;
150         int errflag = 0;
151
152         if (option_verbose > 1)
153                 ast_verbose(VERBOSE_PREFIX_2 "Begin MixMonitor Recording %s\n", mixmonitor->name);
154         
155         ast_audiohook_lock(&mixmonitor->audiohook);
156
157         while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
158                 struct ast_frame *fr = NULL;
159
160                 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
161
162                 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING)
163                         break;
164
165                 if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR)))
166                         continue;
167
168                 /* Initialize the file if not already done so */
169                 if (!fs && !errflag) {
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                         if (!(fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0644))) {
179                                 ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
180                                 errflag = 1;
181                         }
182                 }
183
184                 /* Write out frame */
185                 if (fs)
186                         ast_writestream(fs, fr);
187
188                 /* All done! free it. */
189                 ast_frame_free(fr, 0);
190
191         }
192
193         ast_audiohook_detach(&mixmonitor->audiohook);
194         ast_audiohook_unlock(&mixmonitor->audiohook);
195         ast_audiohook_destroy(&mixmonitor->audiohook);
196
197         if (option_verbose > 1)
198                 ast_verbose(VERBOSE_PREFIX_2 "End MixMonitor Recording %s\n", mixmonitor->name);
199
200         if (mixmonitor->post_process) {
201                 if (option_verbose > 2)
202                         ast_verbose(VERBOSE_PREFIX_2 "Executing [%s]\n", mixmonitor->post_process);
203                 ast_safe_system(mixmonitor->post_process);
204         }
205                 
206         if (fs)
207                 ast_closestream(fs);
208
209         ast_free(mixmonitor);
210
211
212         return NULL;
213 }
214
215 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
216                                   int readvol, int writevol, const char *post_process) 
217 {
218         pthread_t thread;
219         struct mixmonitor *mixmonitor;
220         char postprocess2[1024] = "";
221         size_t len;
222
223         len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
224
225         /* If a post process system command is given attach it to the structure */
226         if (!ast_strlen_zero(post_process)) {
227                 char *p1, *p2;
228
229                 p1 = ast_strdupa(post_process);
230                 for (p2 = p1; *p2 ; p2++) {
231                         if (*p2 == '^' && *(p2+1) == '{') {
232                                 *p2 = '$';
233                         }
234                 }
235
236                 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
237                 if (!ast_strlen_zero(postprocess2))
238                         len += strlen(postprocess2) + 1;
239         }
240
241         /* Pre-allocate mixmonitor structure and spy */
242         if (!(mixmonitor = ast_calloc(1, len))) {
243                 return;
244         }
245
246         /* Copy over flags and channel name */
247         mixmonitor->flags = flags;
248         mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
249         strcpy(mixmonitor->name, chan->name);
250         if (!ast_strlen_zero(postprocess2)) {
251                 mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
252                 strcpy(mixmonitor->post_process, postprocess2);
253         }
254
255         mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
256         strcpy(mixmonitor->filename, filename);
257
258         /* Setup the actual spy before creating our thread */
259         if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
260                 free(mixmonitor);
261                 return;
262         }
263
264         ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_WRITE);
265
266         if (startmon(chan, &mixmonitor->audiohook)) {
267                 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
268                         mixmonitor_spy_type, chan->name);
269                 ast_audiohook_destroy(&mixmonitor->audiohook);
270                 ast_free(mixmonitor);
271                 return;
272         }
273
274         ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
275
276 }
277
278 static int mixmonitor_exec(struct ast_channel *chan, void *data)
279 {
280         int x, readvol = 0, writevol = 0;
281         struct ast_flags flags = {0};
282         char *parse, *tmp, *slash;
283         AST_DECLARE_APP_ARGS(args,
284                 AST_APP_ARG(filename);
285                 AST_APP_ARG(options);
286                 AST_APP_ARG(post_process);
287         );
288         
289         if (ast_strlen_zero(data)) {
290                 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
291                 return -1;
292         }
293
294         parse = ast_strdupa(data);
295
296         AST_STANDARD_APP_ARGS(args, parse);
297         
298         if (ast_strlen_zero(args.filename)) {
299                 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
300                 return -1;
301         }
302
303         if (args.options) {
304                 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
305
306                 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
307
308                 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
309                         if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
310                                 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
311                         } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
312                                 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
313                         } else {
314                                 readvol = get_volfactor(x);
315                         }
316                 }
317                 
318                 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
319                         if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
320                                 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
321                         } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
322                                 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
323                         } else {
324                                 writevol = get_volfactor(x);
325                         }
326                 }
327                 
328                 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
329                         if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
330                                 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
331                         } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
332                                 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
333                         } else {
334                                 readvol = writevol = get_volfactor(x);
335                         }
336                 }
337         }
338
339         /* if not provided an absolute path, use the system-configured monitoring directory */
340         if (args.filename[0] != '/') {
341                 char *build;
342
343                 build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
344                 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
345                 args.filename = build;
346         }
347
348         tmp = ast_strdupa(args.filename);
349         if ((slash = strrchr(tmp, '/')))
350                 *slash = '\0';
351         ast_mkdir(tmp, 0777);
352
353         pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
354         launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
355
356         return 0;
357 }
358
359 static int stop_mixmonitor_exec(struct ast_channel *chan, void *data)
360 {
361         ast_audiohook_detach_source(chan, mixmonitor_spy_type);
362         return 0;
363 }
364
365 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
366 {
367         struct ast_channel *chan;
368
369         switch (cmd) {
370         case CLI_INIT:
371                 e->command = "mixmonitor [start|stop]";
372                 e->usage =
373                         "Usage: mixmonitor <start|stop> <chan_name> [args]\n"
374                         "       The optional arguments are passed to the MixMonitor\n"
375                         "       application when the 'start' command is used.\n";
376                 return NULL;
377         case CLI_GENERATE:
378                 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
379         }
380
381         if (a->argc < 3)
382                 return CLI_SHOWUSAGE;
383
384         if (!(chan = ast_get_channel_by_name_prefix_locked(a->argv[2], strlen(a->argv[2])))) {
385                 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
386                 /* Technically this is a failure, but we don't want 2 errors printing out */
387                 return CLI_SUCCESS;
388         }
389
390         if (!strcasecmp(a->argv[1], "start")) {
391                 mixmonitor_exec(chan, a->argv[3]);
392                 ast_channel_unlock(chan);
393         } else {
394                 ast_channel_unlock(chan);
395                 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
396         }
397
398         return CLI_SUCCESS;
399 }
400
401 static struct ast_cli_entry cli_mixmonitor[] = {
402         AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
403 };
404
405 static int unload_module(void)
406 {
407         int res;
408
409         ast_cli_unregister_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
410         res = ast_unregister_application(stop_app);
411         res |= ast_unregister_application(app);
412         
413         return res;
414 }
415
416 static int load_module(void)
417 {
418         int res;
419
420         ast_cli_register_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
421         res = ast_register_application(app, mixmonitor_exec, synopsis, desc);
422         res |= ast_register_application(stop_app, stop_mixmonitor_exec, stop_synopsis, stop_desc);
423
424         return res;
425 }
426
427 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");