Merged revisions 173592 via svnmerge from
[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 /*** DOCUMENTATION
50         <application name="MixMonitor" language="en_US">
51                 <synopsis>
52                         Record a call and mix the audio during the recording.
53                 </synopsis>
54                 <syntax>
55                         <parameter name="file" required="true" argsep=".">
56                                 <argument name="filename" required="true">
57                                         <para>If <replaceable>filename</replaceable> is an absolute path, uses that path, otherwise
58                                         creates the file in the configured monitoring directory from <filename>asterisk.conf.</filename></para>
59                                 </argument>
60                                 <argument name="extension" required="true" />
61                         </parameter>
62                         <parameter name="options">
63                                 <optionlist>
64                                         <option name="a">
65                                                 <para>Append to the file instead of overwriting it.</para>
66                                         </option>
67                                         <option name="b">
68                                                 <para>Only save audio to the file while the channel is bridged.</para>
69                                                 <note><para>Does not include conferences or sounds played to each bridged party</para></note>
70                                         </option>
71                                         <option name="v">
72                                                 <para>Adjust the <emphasis>heard</emphasis> volume by a factor of <replaceable>x</replaceable>
73                                                 (range <literal>-4</literal> to <literal>4</literal>)</para>
74                                                 <argument name="x" required="true" />
75                                         </option>
76                                         <option name="V">
77                                                 <para>Adjust the <emphasis>spoken</emphasis> volume by a factor
78                                                 of <replaceable>x</replaceable> (range <literal>-4</literal> to <literal>4</literal>)</para>
79                                                 <argument name="x" required="true" />
80                                         </option>
81                                         <option name="W">
82                                                 <para>Adjust both, <emphasis>heard and spoken</emphasis> volumes by a factor
83                                                 of <replaceable>x</replaceable> (range <literal>-4</literal> to <literal>4</literal>)</para>
84                                                 <argument name="x" required="true" />
85                                         </option>
86                                 </optionlist>
87                         </parameter>
88                         <parameter name="command">
89                                 <para>Will be executed when the recording is over.</para>
90                                 <para>Any strings matching <literal>^{X}</literal> will be unescaped to <variable>X</variable>.</para>
91                                 <para>All variables will be evaluated at the time MixMonitor is called.</para>
92                         </parameter>
93                 </syntax>
94                 <description>
95                         <para>Records the audio on the current channel to the specified file.</para>
96                         <variablelist>
97                                 <variable name="MIXMONITOR_FILENAME">
98                                         <para>Will contain the filename used to record.</para>
99                                 </variable>
100                         </variablelist> 
101                 </description>
102                 <see-also>
103                         <ref type="application">Monitor</ref>
104                         <ref type="application">StopMixMonitor</ref>
105                         <ref type="application">PauseMonitor</ref>
106                         <ref type="application">UnpauseMonitor</ref>
107                 </see-also>
108         </application>
109         <application name="StopMixMonitor" language="en_US">
110                 <synopsis>
111                         Stop recording a call through MixMonitor.
112                 </synopsis>
113                 <syntax />
114                 <description>
115                         <para>Stops the audio recording that was started with a call to <literal>MixMonitor()</literal>
116                         on the current channel.</para>
117                 </description>
118                 <see-also>
119                         <ref type="application">MixMonitor</ref>
120                 </see-also>
121         </application>
122                 
123  ***/
124
125 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
126
127 static const char *app = "MixMonitor";
128
129 static const char *stop_app = "StopMixMonitor";
130
131 struct module_symbols *me;
132
133 static const char *mixmonitor_spy_type = "MixMonitor";
134
135 struct mixmonitor {
136         struct ast_audiohook audiohook;
137         char *filename;
138         char *post_process;
139         char *name;
140         unsigned int flags;
141         struct mixmonitor_ds *mixmonitor_ds;
142 };
143
144 enum {
145         MUXFLAG_APPEND = (1 << 1),
146         MUXFLAG_BRIDGED = (1 << 2),
147         MUXFLAG_VOLUME = (1 << 3),
148         MUXFLAG_READVOLUME = (1 << 4),
149         MUXFLAG_WRITEVOLUME = (1 << 5),
150 } mixmonitor_flags;
151
152 enum {
153         OPT_ARG_READVOLUME = 0,
154         OPT_ARG_WRITEVOLUME,
155         OPT_ARG_VOLUME,
156         OPT_ARG_ARRAY_SIZE,
157 } mixmonitor_args;
158
159 AST_APP_OPTIONS(mixmonitor_opts, {
160         AST_APP_OPTION('a', MUXFLAG_APPEND),
161         AST_APP_OPTION('b', MUXFLAG_BRIDGED),
162         AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
163         AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
164         AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
165 });
166
167 /* This structure is used as a means of making sure that our pointer to
168  * the channel we are monitoring remains valid. This is very similar to 
169  * what is used in app_chanspy.c.
170  */
171 struct mixmonitor_ds {
172         struct ast_channel *chan;
173         /* These condition variables are used to be sure that the channel
174          * hangup code completes before the mixmonitor thread attempts to
175          * free this structure. The combination of a bookean flag and a
176          * ast_cond_t ensure that no matter what order the threads run in,
177          * we are guaranteed to never have the waiting thread block forever
178          * in the case that the signaling thread runs first.
179          */
180         unsigned int destruction_ok;
181         ast_cond_t destruction_condition;
182         ast_mutex_t lock;
183 };
184
185 static void mixmonitor_ds_destroy(void *data)
186 {
187         struct mixmonitor_ds *mixmonitor_ds = data;
188
189         ast_mutex_lock(&mixmonitor_ds->lock);
190         mixmonitor_ds->chan = NULL;
191         mixmonitor_ds->destruction_ok = 1;
192         ast_cond_signal(&mixmonitor_ds->destruction_condition);
193         ast_mutex_unlock(&mixmonitor_ds->lock);
194 }
195
196 static void mixmonitor_ds_chan_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
197 {
198         struct mixmonitor_ds *mixmonitor_ds = data;
199
200         ast_mutex_lock(&mixmonitor_ds->lock);
201         mixmonitor_ds->chan = new_chan;
202         ast_mutex_unlock(&mixmonitor_ds->lock);
203 }
204
205 static struct ast_datastore_info mixmonitor_ds_info = {
206         .type = "mixmonitor",
207         .destroy = mixmonitor_ds_destroy,
208         .chan_fixup = mixmonitor_ds_chan_fixup,
209 };
210
211 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook) 
212 {
213         struct ast_channel *peer = NULL;
214         int res = 0;
215
216         if (!chan)
217                 return -1;
218
219         ast_audiohook_attach(chan, audiohook);
220
221         if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
222                 ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);  
223
224         return res;
225 }
226
227 #define SAMPLES_PER_FRAME 160
228
229 static void *mixmonitor_thread(void *obj) 
230 {
231         struct mixmonitor *mixmonitor = obj;
232         struct ast_filestream *fs = NULL;
233         unsigned int oflags;
234         char *ext;
235         int errflag = 0;
236
237         ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name);
238         
239         ast_audiohook_lock(&mixmonitor->audiohook);
240
241         while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
242                 struct ast_frame *fr = NULL;
243
244                 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
245
246                 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING)
247                         break;
248
249                 if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR)))
250                         continue;
251
252                 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
253                 if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || (mixmonitor->mixmonitor_ds->chan && ast_bridged_channel(mixmonitor->mixmonitor_ds->chan))) {
254                         ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
255                         /* Initialize the file if not already done so */
256                         if (!fs && !errflag) {
257                                 oflags = O_CREAT | O_WRONLY;
258                                 oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
259                                 
260                                 if ((ext = strrchr(mixmonitor->filename, '.')))
261                                         *(ext++) = '\0';
262                                 else
263                                         ext = "raw";
264                                 
265                                 if (!(fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0666))) {
266                                         ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
267                                         errflag = 1;
268                                 }
269                         }
270                         
271                         /* Write out frame */
272                         if (fs)
273                                 ast_writestream(fs, fr);
274                 } else {
275                         ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
276                 }
277
278                 /* All done! free it. */
279                 ast_frame_free(fr, 0);
280
281         }
282
283         ast_audiohook_detach(&mixmonitor->audiohook);
284         ast_audiohook_unlock(&mixmonitor->audiohook);
285         ast_audiohook_destroy(&mixmonitor->audiohook);
286
287         ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
288
289         if (fs)
290                 ast_closestream(fs);
291
292         if (mixmonitor->post_process) {
293                 ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
294                 ast_safe_system(mixmonitor->post_process);
295         }
296
297         ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
298         if (!mixmonitor->mixmonitor_ds->destruction_ok) {
299                 ast_cond_wait(&mixmonitor->mixmonitor_ds->destruction_condition, &mixmonitor->mixmonitor_ds->lock);
300         }
301         ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
302         ast_mutex_destroy(&mixmonitor->mixmonitor_ds->lock);
303         ast_cond_destroy(&mixmonitor->mixmonitor_ds->destruction_condition);
304         ast_free(mixmonitor->mixmonitor_ds);
305         ast_free(mixmonitor);
306
307         return NULL;
308 }
309
310 static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan)
311 {
312         struct ast_datastore *datastore = NULL;
313         struct mixmonitor_ds *mixmonitor_ds;
314
315         if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
316                 return -1;
317         }
318         
319         ast_mutex_init(&mixmonitor_ds->lock);
320         ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
321
322         if (!(datastore = ast_datastore_alloc(&mixmonitor_ds_info, NULL))) {
323                 ast_free(mixmonitor_ds);
324                 return -1;
325         }
326
327         /* No need to lock mixmonitor_ds since this is still operating in the channel's thread */
328         mixmonitor_ds->chan = chan;
329         datastore->data = mixmonitor_ds;
330
331         ast_channel_lock(chan);
332         ast_channel_datastore_add(chan, datastore);
333         ast_channel_unlock(chan);
334
335         mixmonitor->mixmonitor_ds = mixmonitor_ds;
336         return 0;
337 }
338
339 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
340                                   int readvol, int writevol, const char *post_process) 
341 {
342         pthread_t thread;
343         struct mixmonitor *mixmonitor;
344         char postprocess2[1024] = "";
345         size_t len;
346
347         len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
348
349         postprocess2[0] = 0;
350         /* If a post process system command is given attach it to the structure */
351         if (!ast_strlen_zero(post_process)) {
352                 char *p1, *p2;
353
354                 p1 = ast_strdupa(post_process);
355                 for (p2 = p1; *p2 ; p2++) {
356                         if (*p2 == '^' && *(p2+1) == '{') {
357                                 *p2 = '$';
358                         }
359                 }
360                 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
361                 if (!ast_strlen_zero(postprocess2))
362                         len += strlen(postprocess2) + 1;
363         }
364
365         /* Pre-allocate mixmonitor structure and spy */
366         if (!(mixmonitor = ast_calloc(1, len))) {
367                 return;
368         }
369
370         /* Copy over flags and channel name */
371         mixmonitor->flags = flags;
372         if (setup_mixmonitor_ds(mixmonitor, chan)) {
373                 return;
374         }
375         mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
376         strcpy(mixmonitor->name, chan->name);
377         if (!ast_strlen_zero(postprocess2)) {
378                 mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
379                 strcpy(mixmonitor->post_process, postprocess2);
380         }
381
382         mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
383         strcpy(mixmonitor->filename, filename);
384
385         /* Setup the actual spy before creating our thread */
386         if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
387                 ast_free(mixmonitor);
388                 return;
389         }
390
391         ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
392
393         if (readvol)
394                 mixmonitor->audiohook.options.read_volume = readvol;
395         if (writevol)
396                 mixmonitor->audiohook.options.write_volume = writevol;
397
398         if (startmon(chan, &mixmonitor->audiohook)) {
399                 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
400                         mixmonitor_spy_type, chan->name);
401                 ast_audiohook_destroy(&mixmonitor->audiohook);
402                 ast_free(mixmonitor);
403                 return;
404         }
405
406         ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
407 }
408
409 static int mixmonitor_exec(struct ast_channel *chan, void *data)
410 {
411         int x, readvol = 0, writevol = 0;
412         struct ast_flags flags = {0};
413         char *parse, *tmp, *slash;
414         AST_DECLARE_APP_ARGS(args,
415                 AST_APP_ARG(filename);
416                 AST_APP_ARG(options);
417                 AST_APP_ARG(post_process);
418         );
419         
420         if (ast_strlen_zero(data)) {
421                 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
422                 return -1;
423         }
424
425         parse = ast_strdupa(data);
426
427         AST_STANDARD_APP_ARGS(args, parse);
428         
429         if (ast_strlen_zero(args.filename)) {
430                 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
431                 return -1;
432         }
433
434         if (args.options) {
435                 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
436
437                 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
438
439                 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
440                         if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
441                                 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
442                         } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
443                                 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
444                         } else {
445                                 readvol = get_volfactor(x);
446                         }
447                 }
448                 
449                 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
450                         if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
451                                 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
452                         } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
453                                 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
454                         } else {
455                                 writevol = get_volfactor(x);
456                         }
457                 }
458                 
459                 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
460                         if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
461                                 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
462                         } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
463                                 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
464                         } else {
465                                 readvol = writevol = get_volfactor(x);
466                         }
467                 }
468         }
469
470         /* if not provided an absolute path, use the system-configured monitoring directory */
471         if (args.filename[0] != '/') {
472                 char *build;
473
474                 build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
475                 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
476                 args.filename = build;
477         }
478
479         tmp = ast_strdupa(args.filename);
480         if ((slash = strrchr(tmp, '/')))
481                 *slash = '\0';
482         ast_mkdir(tmp, 0777);
483
484         pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
485         launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
486
487         return 0;
488 }
489
490 static int stop_mixmonitor_exec(struct ast_channel *chan, void *data)
491 {
492         ast_audiohook_detach_source(chan, mixmonitor_spy_type);
493         return 0;
494 }
495
496 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
497 {
498         struct ast_channel *chan;
499
500         switch (cmd) {
501         case CLI_INIT:
502                 e->command = "mixmonitor {start|stop} {<chan_name>} [args]";
503                 e->usage =
504                         "Usage: mixmonitor <start|stop> <chan_name> [args]\n"
505                         "       The optional arguments are passed to the MixMonitor\n"
506                         "       application when the 'start' command is used.\n";
507                 return NULL;
508         case CLI_GENERATE:
509                 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
510         }
511
512         if (a->argc < 3)
513                 return CLI_SHOWUSAGE;
514
515         if (!(chan = ast_get_channel_by_name_prefix_locked(a->argv[2], strlen(a->argv[2])))) {
516                 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
517                 /* Technically this is a failure, but we don't want 2 errors printing out */
518                 return CLI_SUCCESS;
519         }
520
521         if (!strcasecmp(a->argv[1], "start")) {
522                 mixmonitor_exec(chan, a->argv[3]);
523                 ast_channel_unlock(chan);
524         } else {
525                 ast_channel_unlock(chan);
526                 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
527         }
528
529         return CLI_SUCCESS;
530 }
531
532 static struct ast_cli_entry cli_mixmonitor[] = {
533         AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
534 };
535
536 static int unload_module(void)
537 {
538         int res;
539
540         ast_cli_unregister_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
541         res = ast_unregister_application(stop_app);
542         res |= ast_unregister_application(app);
543         
544         return res;
545 }
546
547 static int load_module(void)
548 {
549         int res;
550
551         ast_cli_register_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
552         res = ast_register_application_xml(app, mixmonitor_exec);
553         res |= ast_register_application_xml(stop_app, stop_mixmonitor_exec);
554
555         return res;
556 }
557
558 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");