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