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