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