Resolve a crash in SLATrunk when the specified trunk doesn't exist.
[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         struct ast_audiohook *audiohook;
181 };
182
183 /*!
184  * \internal
185  * \pre mixmonitor_ds must be locked before calling this function
186  */
187 static void mixmonitor_ds_close_fs(struct mixmonitor_ds *mixmonitor_ds)
188 {
189         if (mixmonitor_ds->fs) {
190                 ast_closestream(mixmonitor_ds->fs);
191                 mixmonitor_ds->fs = NULL;
192                 mixmonitor_ds->fs_quit = 1;
193                 ast_verb(2, "MixMonitor close filestream\n");
194         }
195 }
196
197 static void mixmonitor_ds_destroy(void *data)
198 {
199         struct mixmonitor_ds *mixmonitor_ds = data;
200
201         ast_mutex_lock(&mixmonitor_ds->lock);
202         mixmonitor_ds->audiohook = NULL;
203         mixmonitor_ds->destruction_ok = 1;
204         ast_cond_signal(&mixmonitor_ds->destruction_condition);
205         ast_mutex_unlock(&mixmonitor_ds->lock);
206 }
207
208 static struct ast_datastore_info mixmonitor_ds_info = {
209         .type = "mixmonitor",
210         .destroy = mixmonitor_ds_destroy,
211 };
212
213 static void destroy_monitor_audiohook(struct mixmonitor *mixmonitor)
214 {
215         if (mixmonitor->mixmonitor_ds) {
216                 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
217                 mixmonitor->mixmonitor_ds->audiohook = NULL;
218                 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
219         }
220         /* kill the audiohook.*/
221         ast_audiohook_lock(&mixmonitor->audiohook);
222         ast_audiohook_detach(&mixmonitor->audiohook);
223         ast_audiohook_unlock(&mixmonitor->audiohook);
224         ast_audiohook_destroy(&mixmonitor->audiohook);
225 }
226
227 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook) 
228 {
229         struct ast_channel *peer = NULL;
230         int res = 0;
231
232         if (!chan)
233                 return -1;
234
235         ast_audiohook_attach(chan, audiohook);
236
237         if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
238                 ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);  
239
240         return res;
241 }
242
243 #define SAMPLES_PER_FRAME 160
244
245 static void mixmonitor_free(struct mixmonitor *mixmonitor)
246 {
247         if (mixmonitor) {
248                 if (mixmonitor->mixmonitor_ds) {
249                         ast_mutex_destroy(&mixmonitor->mixmonitor_ds->lock);
250                         ast_cond_destroy(&mixmonitor->mixmonitor_ds->destruction_condition);
251                         ast_free(mixmonitor->mixmonitor_ds);
252                 }
253                 ast_free(mixmonitor);
254         }
255 }
256 static void *mixmonitor_thread(void *obj) 
257 {
258         struct mixmonitor *mixmonitor = obj;
259         struct ast_filestream **fs = NULL;
260         unsigned int oflags;
261         char *ext;
262         int errflag = 0;
263
264         ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name);
265
266         fs = &mixmonitor->mixmonitor_ds->fs;
267
268         /* The audiohook must enter and exit the loop locked */
269         ast_audiohook_lock(&mixmonitor->audiohook);
270         while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING && !mixmonitor->mixmonitor_ds->fs_quit) {
271                 struct ast_frame *fr = NULL;
272
273                 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
274
275                 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING)
276                         break;
277
278                 if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR)))
279                         continue;
280
281                 /* audiohook lock is not required for the next block.
282                  * Unlock it, but remember to lock it before looping or exiting */
283                 ast_audiohook_unlock(&mixmonitor->audiohook);
284
285                 if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || (mixmonitor->autochan->chan && ast_bridged_channel(mixmonitor->autochan->chan))) {
286                         ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
287                         /* Initialize the file if not already done so */
288                         if (!*fs && !errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
289                                 oflags = O_CREAT | O_WRONLY;
290                                 oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
291
292                                 if ((ext = strrchr(mixmonitor->filename, '.')))
293                                         *(ext++) = '\0';
294                                 else
295                                         ext = "raw";
296
297                                 if (!(*fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0666))) {
298                                         ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
299                                         errflag = 1;
300                                 }
301                         }
302
303                         /* Write out the frame(s) */
304                         if (*fs) {
305                                 struct ast_frame *cur;
306
307                                 for (cur = fr; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
308                                         ast_writestream(*fs, cur);
309                                 }
310                         }
311                         ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
312                 }
313                 /* All done! free it. */
314                 ast_frame_free(fr, 0);
315
316                 ast_audiohook_lock(&mixmonitor->audiohook);
317         }
318         ast_audiohook_unlock(&mixmonitor->audiohook);
319
320         ast_autochan_destroy(mixmonitor->autochan);
321
322         /* Datastore cleanup.  close the filestream and wait for ds destruction */
323         ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
324         mixmonitor_ds_close_fs(mixmonitor->mixmonitor_ds);
325         if (!mixmonitor->mixmonitor_ds->destruction_ok) {
326                 ast_cond_wait(&mixmonitor->mixmonitor_ds->destruction_condition, &mixmonitor->mixmonitor_ds->lock);
327         }
328         ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
329
330         /* kill the audiohook */
331         destroy_monitor_audiohook(mixmonitor);
332
333         if (mixmonitor->post_process) {
334                 ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
335                 ast_safe_system(mixmonitor->post_process);
336         }
337
338         ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
339         mixmonitor_free(mixmonitor);
340         return NULL;
341 }
342
343 static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan)
344 {
345         struct ast_datastore *datastore = NULL;
346         struct mixmonitor_ds *mixmonitor_ds;
347
348         if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
349                 return -1;
350         }
351
352         ast_mutex_init(&mixmonitor_ds->lock);
353         ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
354
355         if (!(datastore = ast_datastore_alloc(&mixmonitor_ds_info, NULL))) {
356                 ast_mutex_destroy(&mixmonitor_ds->lock);
357                 ast_cond_destroy(&mixmonitor_ds->destruction_condition);
358                 ast_free(mixmonitor_ds);
359                 return -1;
360         }
361
362         mixmonitor_ds->audiohook = &mixmonitor->audiohook;
363         datastore->data = mixmonitor_ds;
364
365         ast_channel_lock(chan);
366         ast_channel_datastore_add(chan, datastore);
367         ast_channel_unlock(chan);
368
369         mixmonitor->mixmonitor_ds = mixmonitor_ds;
370         return 0;
371 }
372
373 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
374                                   int readvol, int writevol, const char *post_process) 
375 {
376         pthread_t thread;
377         struct mixmonitor *mixmonitor;
378         char postprocess2[1024] = "";
379         size_t len;
380
381         len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
382
383         postprocess2[0] = 0;
384         /* If a post process system command is given attach it to the structure */
385         if (!ast_strlen_zero(post_process)) {
386                 char *p1, *p2;
387
388                 p1 = ast_strdupa(post_process);
389                 for (p2 = p1; *p2 ; p2++) {
390                         if (*p2 == '^' && *(p2+1) == '{') {
391                                 *p2 = '$';
392                         }
393                 }
394                 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
395                 if (!ast_strlen_zero(postprocess2))
396                         len += strlen(postprocess2) + 1;
397         }
398
399         /* Pre-allocate mixmonitor structure and spy */
400         if (!(mixmonitor = ast_calloc(1, len))) {
401                 return;
402         }
403
404         /* Setup the actual spy before creating our thread */
405         if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
406                 mixmonitor_free(mixmonitor);
407                 return;
408         }
409
410         /* Copy over flags and channel name */
411         mixmonitor->flags = flags;
412         if (!(mixmonitor->autochan = ast_autochan_setup(chan))) {
413                 mixmonitor_free(mixmonitor);
414                 return;
415         }
416
417         if (setup_mixmonitor_ds(mixmonitor, chan)) {
418                 ast_autochan_destroy(mixmonitor->autochan);
419                 mixmonitor_free(mixmonitor);
420                 return;
421         }
422         mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
423         strcpy(mixmonitor->name, chan->name);
424         if (!ast_strlen_zero(postprocess2)) {
425                 mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
426                 strcpy(mixmonitor->post_process, postprocess2);
427         }
428
429         mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
430         strcpy(mixmonitor->filename, filename);
431
432         ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
433
434         if (readvol)
435                 mixmonitor->audiohook.options.read_volume = readvol;
436         if (writevol)
437                 mixmonitor->audiohook.options.write_volume = writevol;
438
439         if (startmon(chan, &mixmonitor->audiohook)) {
440                 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
441                         mixmonitor_spy_type, chan->name);
442                 ast_audiohook_destroy(&mixmonitor->audiohook);
443                 mixmonitor_free(mixmonitor);
444                 return;
445         }
446
447         ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
448 }
449
450 static int mixmonitor_exec(struct ast_channel *chan, const char *data)
451 {
452         int x, readvol = 0, writevol = 0;
453         struct ast_flags flags = {0};
454         char *parse, *tmp, *slash;
455         AST_DECLARE_APP_ARGS(args,
456                 AST_APP_ARG(filename);
457                 AST_APP_ARG(options);
458                 AST_APP_ARG(post_process);
459         );
460         
461         if (ast_strlen_zero(data)) {
462                 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
463                 return -1;
464         }
465
466         parse = ast_strdupa(data);
467
468         AST_STANDARD_APP_ARGS(args, parse);
469         
470         if (ast_strlen_zero(args.filename)) {
471                 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
472                 return -1;
473         }
474
475         if (args.options) {
476                 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
477
478                 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
479
480                 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
481                         if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
482                                 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
483                         } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
484                                 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
485                         } else {
486                                 readvol = get_volfactor(x);
487                         }
488                 }
489                 
490                 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
491                         if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
492                                 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
493                         } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
494                                 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
495                         } else {
496                                 writevol = get_volfactor(x);
497                         }
498                 }
499                 
500                 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
501                         if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
502                                 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
503                         } else if ((sscanf(opts[OPT_ARG_VOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
504                                 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
505                         } else {
506                                 readvol = writevol = get_volfactor(x);
507                         }
508                 }
509         }
510
511         /* if not provided an absolute path, use the system-configured monitoring directory */
512         if (args.filename[0] != '/') {
513                 char *build;
514
515                 build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
516                 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
517                 args.filename = build;
518         }
519
520         tmp = ast_strdupa(args.filename);
521         if ((slash = strrchr(tmp, '/')))
522                 *slash = '\0';
523         ast_mkdir(tmp, 0777);
524
525         pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
526         launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
527
528         return 0;
529 }
530
531 static int stop_mixmonitor_exec(struct ast_channel *chan, const char *data)
532 {
533         struct ast_datastore *datastore = NULL;
534
535         ast_channel_lock(chan);
536         ast_audiohook_detach_source(chan, mixmonitor_spy_type);
537         if ((datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, NULL))) {
538                 struct mixmonitor_ds *mixmonitor_ds = datastore->data;
539
540                 ast_mutex_lock(&mixmonitor_ds->lock);
541
542                 /* closing the filestream here guarantees the file is avaliable to the dialplan
543                  * after calling StopMixMonitor */
544                 mixmonitor_ds_close_fs(mixmonitor_ds);
545
546                 /* The mixmonitor thread may be waiting on the audiohook trigger.
547                  * In order to exit from the mixmonitor loop before waiting on channel
548                  * destruction, poke the audiohook trigger. */
549                 if (mixmonitor_ds->audiohook) {
550                         ast_audiohook_lock(mixmonitor_ds->audiohook);
551                         ast_cond_signal(&mixmonitor_ds->audiohook->trigger);
552                         ast_audiohook_unlock(mixmonitor_ds->audiohook);
553                         mixmonitor_ds->audiohook = NULL;
554                 }
555
556                 ast_mutex_unlock(&mixmonitor_ds->lock);
557
558                 /* Remove the datastore so the monitor thread can exit */
559                 if (!ast_channel_datastore_remove(chan, datastore)) {
560                         ast_datastore_free(datastore);
561                 }
562         }
563         ast_channel_unlock(chan);
564
565         return 0;
566 }
567
568 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
569 {
570         struct ast_channel *chan;
571
572         switch (cmd) {
573         case CLI_INIT:
574                 e->command = "mixmonitor {start|stop}";
575                 e->usage =
576                         "Usage: mixmonitor <start|stop> <chan_name> [args]\n"
577                         "       The optional arguments are passed to the MixMonitor\n"
578                         "       application when the 'start' command is used.\n";
579                 return NULL;
580         case CLI_GENERATE:
581                 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
582         }
583
584         if (a->argc < 3)
585                 return CLI_SHOWUSAGE;
586
587         if (!(chan = ast_channel_get_by_name_prefix(a->argv[2], strlen(a->argv[2])))) {
588                 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
589                 /* Technically this is a failure, but we don't want 2 errors printing out */
590                 return CLI_SUCCESS;
591         }
592
593         ast_channel_lock(chan);
594
595         if (!strcasecmp(a->argv[1], "start")) {
596                 mixmonitor_exec(chan, a->argv[3]);
597                 ast_channel_unlock(chan);
598         } else {
599                 ast_channel_unlock(chan);
600                 ast_audiohook_detach_source(chan, mixmonitor_spy_type);
601         }
602
603         chan = ast_channel_unref(chan);
604
605         return CLI_SUCCESS;
606 }
607
608 static struct ast_cli_entry cli_mixmonitor[] = {
609         AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
610 };
611
612 static int unload_module(void)
613 {
614         int res;
615
616         ast_cli_unregister_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
617         res = ast_unregister_application(stop_app);
618         res |= ast_unregister_application(app);
619         
620         return res;
621 }
622
623 static int load_module(void)
624 {
625         int res;
626
627         ast_cli_register_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
628         res = ast_register_application_xml(app, mixmonitor_exec);
629         res |= ast_register_application_xml(stop_app, stop_mixmonitor_exec);
630
631         return res;
632 }
633
634 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");