2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2005, Anthony Minessale II
5 * Copyright (C) 2005 - 2006, Digium, Inc.
7 * Mark Spencer <markster@digium.com>
8 * Kevin P. Fleming <kpfleming@digium.com>
10 * Based on app_muxmon.c provided by
11 * Anthony Minessale II <anthmct@yahoo.com>
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.
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.
26 * \brief MixMonitor() - Record a call and mix the audio during the recording
27 * \ingroup applications
29 * \author Mark Spencer <markster@digium.com>
30 * \author Kevin P. Fleming <kpfleming@digium.com>
32 * \note Based on app_muxmon.c provided by
33 * Anthony Minessale II <anthmct@yahoo.com>
37 <support_level>core</support_level>
42 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
44 #include "asterisk/paths.h" /* use ast_config_AST_MONITOR_DIR */
45 #include "asterisk/stringfields.h"
46 #include "asterisk/file.h"
47 #include "asterisk/audiohook.h"
48 #include "asterisk/pbx.h"
49 #include "asterisk/module.h"
50 #include "asterisk/cli.h"
51 #include "asterisk/app.h"
52 #include "asterisk/channel.h"
53 #include "asterisk/autochan.h"
54 #include "asterisk/manager.h"
55 #include "asterisk/callerid.h"
56 #include "asterisk/mod_format.h"
57 #include "asterisk/linkedlists.h"
58 #include "asterisk/test.h"
59 #include "asterisk/mixmonitor.h"
62 <application name="MixMonitor" language="en_US">
64 Record a call and mix the audio during the recording. Use of StopMixMonitor is required
65 to guarantee the audio file is available for processing during dialplan execution.
68 <parameter name="file" required="true" argsep=".">
69 <argument name="filename" required="true">
70 <para>If <replaceable>filename</replaceable> is an absolute path, uses that path, otherwise
71 creates the file in the configured monitoring directory from <filename>asterisk.conf.</filename></para>
73 <argument name="extension" required="true" />
75 <parameter name="options">
78 <para>Append to the file instead of overwriting it.</para>
81 <para>Only save audio to the file while the channel is bridged.</para>
82 <note><para>Does not include conferences or sounds played to each bridged party</para></note>
83 <note><para>If you utilize this option inside a Local channel, you must make sure the Local
84 channel is not optimized away. To do this, be sure to call your Local channel with the
85 <literal>/n</literal> option. For example: Dial(Local/start@mycontext/n)</para></note>
88 <para>Adjust the <emphasis>heard</emphasis> volume by a factor of <replaceable>x</replaceable>
89 (range <literal>-4</literal> to <literal>4</literal>)</para>
90 <argument name="x" required="true" />
93 <para>Adjust the <emphasis>spoken</emphasis> volume by a factor
94 of <replaceable>x</replaceable> (range <literal>-4</literal> to <literal>4</literal>)</para>
95 <argument name="x" required="true" />
98 <para>Adjust both, <emphasis>heard and spoken</emphasis> volumes by a factor
99 of <replaceable>x</replaceable> (range <literal>-4</literal> to <literal>4</literal>)</para>
100 <argument name="x" required="true" />
103 <argument name="file" required="true" />
104 <para>Use the specified file to record the <emphasis>receive</emphasis> audio feed.
105 Like with the basic filename argument, if an absolute path isn't given, it will create
106 the file in the configured monitoring directory.</para>
110 <argument name="file" required="true" />
111 <para>Use the specified file to record the <emphasis>transmit</emphasis> audio feed.
112 Like with the basic filename argument, if an absolute path isn't given, it will create
113 the file in the configured monitoring directory.</para>
116 <argument name="chanvar" required="true" />
117 <para>Stores the MixMonitor's ID on this channel variable.</para>
120 <argument name="mailbox" required="true" />
121 <para>Create a copy of the recording as a voicemail in the indicated <emphasis>mailbox</emphasis>(es)
122 separated by commas eg. m(1111@default,2222@default,...). Folders can be optionally specified using
123 the syntax: mailbox@context/folder</para>
127 <parameter name="command">
128 <para>Will be executed when the recording is over.</para>
129 <para>Any strings matching <literal>^{X}</literal> will be unescaped to <variable>X</variable>.</para>
130 <para>All variables will be evaluated at the time MixMonitor is called.</para>
134 <para>Records the audio on the current channel to the specified file.</para>
135 <para>This application does not automatically answer and should be preceeded by
136 an application such as Answer or Progress().</para>
137 <note><para>MixMonitor runs as an audiohook. In order to keep it running through
138 a transfer, AUDIOHOOK_INHERIT must be set for the channel which ran mixmonitor.
139 For more information, including dialplan configuration set for using
140 AUDIOHOOK_INHERIT with MixMonitor, see the function documentation for
141 AUDIOHOOK_INHERIT.</para></note>
143 <variable name="MIXMONITOR_FILENAME">
144 <para>Will contain the filename used to record.</para>
149 <ref type="application">Monitor</ref>
150 <ref type="application">StopMixMonitor</ref>
151 <ref type="application">PauseMonitor</ref>
152 <ref type="application">UnpauseMonitor</ref>
153 <ref type="function">AUDIOHOOK_INHERIT</ref>
156 <application name="StopMixMonitor" language="en_US">
158 Stop recording a call through MixMonitor, and free the recording's file handle.
161 <parameter name="MixMonitorID" required="false">
162 <para>If a valid ID is provided, then this command will stop only that specific
167 <para>Stops the audio recording that was started with a call to <literal>MixMonitor()</literal>
168 on the current channel.</para>
171 <ref type="application">MixMonitor</ref>
174 <manager name="MixMonitorMute" language="en_US">
176 Mute / unMute a Mixmonitor recording.
179 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
180 <parameter name="Channel" required="true">
181 <para>Used to specify the channel to mute.</para>
183 <parameter name="Direction">
184 <para>Which part of the recording to mute: read, write or both (from channel, to channel or both channels).</para>
186 <parameter name="State">
187 <para>Turn mute on or off : 1 to turn on, 0 to turn off.</para>
191 <para>This action may be used to mute a MixMonitor recording.</para>
194 <manager name="MixMonitor" language="en_US">
196 Record a call and mix the audio during the recording. Use of StopMixMonitor is required
197 to guarantee the audio file is available for processing during dialplan execution.
200 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
201 <parameter name="Channel" required="true">
202 <para>Used to specify the channel to record.</para>
204 <parameter name="File">
205 <para>Is the name of the file created in the monitor spool directory.
206 Defaults to the same name as the channel (with slashes replaced with dashes).
207 This argument is optional if you specify to record unidirectional audio with
208 either the r(filename) or t(filename) options in the options field. If
209 neither MIXMONITOR_FILENAME or this parameter is set, the mixed stream won't
212 <parameter name="options">
213 <para>Options that apply to the MixMonitor in the same way as they
214 would apply if invoked from the MixMonitor application. For a list of
215 available options, see the documentation for the mixmonitor application. </para>
219 <para>This action records the audio on the current channel to the specified file.</para>
221 <variable name="MIXMONITOR_FILENAME">
222 <para>Will contain the filename used to record the mixed stream.</para>
227 <manager name="StopMixMonitor" language="en_US">
229 Stop recording a call through MixMonitor, and free the recording's file handle.
232 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
233 <parameter name="Channel" required="true">
234 <para>The name of the channel monitored.</para>
236 <parameter name="MixMonitorID" required="false">
237 <para>If a valid ID is provided, then this command will stop only that specific
242 <para>This action stops the audio recording that was started with the <literal>MixMonitor</literal>
243 action on the current channel.</para>
249 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
251 static const char * const app = "MixMonitor";
253 static const char * const stop_app = "StopMixMonitor";
255 static const char * const mixmonitor_spy_type = "MixMonitor";
259 * \brief This struct is a list item holds data needed to find a vm_recipient within voicemail
261 struct vm_recipient {
262 char mailbox[AST_MAX_CONTEXT];
263 char context[AST_MAX_EXTENSION];
265 AST_LIST_ENTRY(vm_recipient) list;
269 struct ast_audiohook audiohook;
270 struct ast_callid *callid;
273 char *filename_write;
277 struct ast_autochan *autochan;
278 struct mixmonitor_ds *mixmonitor_ds;
280 /* the below string fields describe data used for creating voicemails from the recording */
281 AST_DECLARE_STRING_FIELDS(
282 AST_STRING_FIELD(call_context);
283 AST_STRING_FIELD(call_macrocontext);
284 AST_STRING_FIELD(call_extension);
285 AST_STRING_FIELD(call_callerchan);
286 AST_STRING_FIELD(call_callerid);
290 /* FUTURE DEVELOPMENT NOTICE
291 * recipient_list will need locks if we make it editable after the monitor is started */
292 AST_LIST_HEAD_NOLOCK(, vm_recipient) recipient_list;
295 enum mixmonitor_flags {
296 MUXFLAG_APPEND = (1 << 1),
297 MUXFLAG_BRIDGED = (1 << 2),
298 MUXFLAG_VOLUME = (1 << 3),
299 MUXFLAG_READVOLUME = (1 << 4),
300 MUXFLAG_WRITEVOLUME = (1 << 5),
301 MUXFLAG_READ = (1 << 6),
302 MUXFLAG_WRITE = (1 << 7),
303 MUXFLAG_COMBINED = (1 << 8),
304 MUXFLAG_UID = (1 << 9),
305 MUXFLAG_VMRECIPIENTS = (1 << 10),
308 enum mixmonitor_args {
309 OPT_ARG_READVOLUME = 0,
315 OPT_ARG_VMRECIPIENTS,
316 OPT_ARG_ARRAY_SIZE, /* Always last element of the enum */
319 AST_APP_OPTIONS(mixmonitor_opts, {
320 AST_APP_OPTION('a', MUXFLAG_APPEND),
321 AST_APP_OPTION('b', MUXFLAG_BRIDGED),
322 AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
323 AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
324 AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
325 AST_APP_OPTION_ARG('r', MUXFLAG_READ, OPT_ARG_READNAME),
326 AST_APP_OPTION_ARG('t', MUXFLAG_WRITE, OPT_ARG_WRITENAME),
327 AST_APP_OPTION_ARG('i', MUXFLAG_UID, OPT_ARG_UID),
328 AST_APP_OPTION_ARG('m', MUXFLAG_VMRECIPIENTS, OPT_ARG_VMRECIPIENTS),
331 struct mixmonitor_ds {
332 unsigned int destruction_ok;
333 ast_cond_t destruction_condition;
336 /* The filestream is held in the datastore so it can be stopped
337 * immediately during stop_mixmonitor or channel destruction. */
340 struct ast_filestream *fs;
341 struct ast_filestream *fs_read;
342 struct ast_filestream *fs_write;
344 struct ast_audiohook *audiohook;
346 unsigned int samp_rate;
351 * \pre mixmonitor_ds must be locked before calling this function
353 static void mixmonitor_ds_close_fs(struct mixmonitor_ds *mixmonitor_ds)
355 unsigned char quitting = 0;
357 if (mixmonitor_ds->fs) {
359 ast_closestream(mixmonitor_ds->fs);
360 mixmonitor_ds->fs = NULL;
361 ast_verb(2, "MixMonitor close filestream (mixed)\n");
364 if (mixmonitor_ds->fs_read) {
366 ast_closestream(mixmonitor_ds->fs_read);
367 mixmonitor_ds->fs_read = NULL;
368 ast_verb(2, "MixMonitor close filestream (read)\n");
371 if (mixmonitor_ds->fs_write) {
373 ast_closestream(mixmonitor_ds->fs_write);
374 mixmonitor_ds->fs_write = NULL;
375 ast_verb(2, "MixMonitor close filestream (write)\n");
379 mixmonitor_ds->fs_quit = 1;
383 static void mixmonitor_ds_destroy(void *data)
385 struct mixmonitor_ds *mixmonitor_ds = data;
387 ast_mutex_lock(&mixmonitor_ds->lock);
388 mixmonitor_ds->audiohook = NULL;
389 mixmonitor_ds->destruction_ok = 1;
390 ast_cond_signal(&mixmonitor_ds->destruction_condition);
391 ast_mutex_unlock(&mixmonitor_ds->lock);
394 static const struct ast_datastore_info mixmonitor_ds_info = {
395 .type = "mixmonitor",
396 .destroy = mixmonitor_ds_destroy,
399 static void destroy_monitor_audiohook(struct mixmonitor *mixmonitor)
401 if (mixmonitor->mixmonitor_ds) {
402 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
403 mixmonitor->mixmonitor_ds->audiohook = NULL;
404 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
406 /* kill the audiohook.*/
407 ast_audiohook_lock(&mixmonitor->audiohook);
408 ast_audiohook_detach(&mixmonitor->audiohook);
409 ast_audiohook_unlock(&mixmonitor->audiohook);
410 ast_audiohook_destroy(&mixmonitor->audiohook);
413 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
420 ast_audiohook_attach(chan, audiohook);
423 ast_channel_lock(chan);
424 if (ast_channel_is_bridged(chan)) {
425 ast_softhangup_nolock(chan, AST_SOFTHANGUP_UNBRIDGE);
427 ast_channel_unlock(chan);
435 * \brief adds recipients to a mixmonitor's recipient list
436 * \param mixmonitor mixmonitor being affected
437 * \param vm_recipients string containing the desired recipients to add
439 static void add_vm_recipients_from_string(struct mixmonitor *mixmonitor, const char *vm_recipients)
441 /* recipients are in a single string with a format format resembling "mailbox@context/INBOX,mailbox2@context2,mailbox3@context3/Work" */
442 char *cur_mailbox = ast_strdupa(vm_recipients);
446 int elements_processed = 0;
448 while (!ast_strlen_zero(cur_mailbox)) {
449 ast_debug(3, "attempting to add next element %d from %s\n", elements_processed, cur_mailbox);
450 if ((next = strchr(cur_mailbox, ',')) || (next = strchr(cur_mailbox, '&'))) {
454 if ((cur_folder = strchr(cur_mailbox, '/'))) {
455 *(cur_folder++) = '\0';
457 cur_folder = "INBOX";
460 if ((cur_context = strchr(cur_mailbox, '@'))) {
461 *(cur_context++) = '\0';
463 cur_context = "default";
466 if (!ast_strlen_zero(cur_mailbox) && !ast_strlen_zero(cur_context)) {
467 struct vm_recipient *recipient;
468 if (!(recipient = ast_malloc(sizeof(*recipient)))) {
469 ast_log(LOG_ERROR, "Failed to allocate recipient. Aborting function.\n");
472 ast_copy_string(recipient->context, cur_context, sizeof(recipient->context));
473 ast_copy_string(recipient->mailbox, cur_mailbox, sizeof(recipient->mailbox));
474 ast_copy_string(recipient->folder, cur_folder, sizeof(recipient->folder));
477 ast_verb(5, "Adding %s@%s to recipient list\n", recipient->mailbox, recipient->context);
478 AST_LIST_INSERT_HEAD(&mixmonitor->recipient_list, recipient, list);
480 ast_log(LOG_ERROR, "Failed to properly parse extension and/or context from element %d of recipient string: %s\n", elements_processed, vm_recipients);
484 elements_processed++;
488 static void clear_mixmonitor_recipient_list(struct mixmonitor *mixmonitor)
490 struct vm_recipient *current;
491 while ((current = AST_LIST_REMOVE_HEAD(&mixmonitor->recipient_list, list))) {
492 /* Clear list element data */
497 #define SAMPLES_PER_FRAME 160
499 static void mixmonitor_free(struct mixmonitor *mixmonitor)
502 if (mixmonitor->mixmonitor_ds) {
503 ast_mutex_destroy(&mixmonitor->mixmonitor_ds->lock);
504 ast_cond_destroy(&mixmonitor->mixmonitor_ds->destruction_condition);
505 ast_free(mixmonitor->filename_write);
506 ast_free(mixmonitor->filename_read);
507 ast_free(mixmonitor->mixmonitor_ds);
508 ast_free(mixmonitor->name);
509 ast_free(mixmonitor->post_process);
512 /* Free everything in the recipient list */
513 clear_mixmonitor_recipient_list(mixmonitor);
515 /* clean stringfields */
516 ast_string_field_free_memory(mixmonitor);
518 if (mixmonitor->callid) {
519 ast_callid_unref(mixmonitor->callid);
521 ast_free(mixmonitor);
527 * \brief Copies the mixmonitor to all voicemail recipients
528 * \param mixmonitor The mixmonitor that needs to forward its file to recipients
529 * \param ext Format of the file that was saved
531 static void copy_to_voicemail(struct mixmonitor *mixmonitor, const char *ext, const char *filename)
533 struct vm_recipient *recipient = NULL;
534 struct ast_vm_recording_data recording_data;
535 if (ast_string_field_init(&recording_data, 512)) {
536 ast_log(LOG_ERROR, "Failed to string_field_init, skipping copy_to_voicemail\n");
540 /* Copy strings to stringfields that will be used for all recipients */
541 ast_string_field_set(&recording_data, recording_file, filename);
542 ast_string_field_set(&recording_data, recording_ext, ext);
543 ast_string_field_set(&recording_data, call_context, mixmonitor->call_context);
544 ast_string_field_set(&recording_data, call_macrocontext, mixmonitor->call_macrocontext);
545 ast_string_field_set(&recording_data, call_extension, mixmonitor->call_extension);
546 ast_string_field_set(&recording_data, call_callerchan, mixmonitor->call_callerchan);
547 ast_string_field_set(&recording_data, call_callerid, mixmonitor->call_callerid);
548 /* and call_priority gets copied too */
549 recording_data.call_priority = mixmonitor->call_priority;
551 AST_LIST_TRAVERSE(&mixmonitor->recipient_list, recipient, list) {
552 /* context, mailbox, and folder need to be set per recipient */
553 ast_string_field_set(&recording_data, context, recipient->context);
554 ast_string_field_set(&recording_data, mailbox, recipient->mailbox);
555 ast_string_field_set(&recording_data, folder, recipient->folder);
557 ast_verb(4, "MixMonitor attempting to send voicemail copy to %s@%s\n", recording_data.mailbox,
558 recording_data.context);
559 ast_app_copy_recording_to_vm(&recording_data);
562 /* Free the string fields for recording_data before exiting the function. */
563 ast_string_field_free_memory(&recording_data);
566 static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename, struct ast_filestream **fs, unsigned int *oflags, int *errflag, char **ext)
568 /* Initialize the file if not already done so */
569 char *last_slash = NULL;
570 if (!ast_strlen_zero(filename)) {
571 if (!*fs && !*errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
572 *oflags = O_CREAT | O_WRONLY;
573 *oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
575 last_slash = strrchr(filename, '/');
577 if ((*ext = strrchr(filename, '.')) && (*ext > last_slash)) {
584 if (!(*fs = ast_writefile(filename, *ext, NULL, *oflags, 0, 0666))) {
585 ast_log(LOG_ERROR, "Cannot open %s.%s\n", filename, *ext);
588 struct ast_filestream *tmp = *fs;
589 mixmonitor->mixmonitor_ds->samp_rate = MAX(mixmonitor->mixmonitor_ds->samp_rate, ast_format_rate(&tmp->fmt->format));
595 static void *mixmonitor_thread(void *obj)
597 struct mixmonitor *mixmonitor = obj;
599 char *fs_read_ext = "";
600 char *fs_write_ext = "";
602 struct ast_filestream **fs = NULL;
603 struct ast_filestream **fs_read = NULL;
604 struct ast_filestream **fs_write = NULL;
608 struct ast_format format_slin;
610 /* Keep callid association before any log messages */
611 if (mixmonitor->callid) {
612 ast_callid_threadassoc_add(mixmonitor->callid);
615 ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name);
617 fs = &mixmonitor->mixmonitor_ds->fs;
618 fs_read = &mixmonitor->mixmonitor_ds->fs_read;
619 fs_write = &mixmonitor->mixmonitor_ds->fs_write;
621 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
622 mixmonitor_save_prep(mixmonitor, mixmonitor->filename, fs, &oflags, &errflag, &fs_ext);
623 mixmonitor_save_prep(mixmonitor, mixmonitor->filename_read, fs_read, &oflags, &errflag, &fs_read_ext);
624 mixmonitor_save_prep(mixmonitor, mixmonitor->filename_write, fs_write, &oflags, &errflag, &fs_write_ext);
626 ast_format_set(&format_slin, ast_format_slin_by_rate(mixmonitor->mixmonitor_ds->samp_rate), 0);
628 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
631 /* The audiohook must enter and exit the loop locked */
632 ast_audiohook_lock(&mixmonitor->audiohook);
633 while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING && !mixmonitor->mixmonitor_ds->fs_quit) {
634 struct ast_frame *fr = NULL;
635 struct ast_frame *fr_read = NULL;
636 struct ast_frame *fr_write = NULL;
638 if (!(fr = ast_audiohook_read_frame_all(&mixmonitor->audiohook, SAMPLES_PER_FRAME, &format_slin,
639 &fr_read, &fr_write))) {
640 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
642 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
648 /* audiohook lock is not required for the next block.
649 * Unlock it, but remember to lock it before looping or exiting */
650 ast_audiohook_unlock(&mixmonitor->audiohook);
652 if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || (mixmonitor->autochan->chan && ast_bridged_channel(mixmonitor->autochan->chan))) {
653 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
655 /* Write out the frame(s) */
656 if ((*fs_read) && (fr_read)) {
657 struct ast_frame *cur;
659 for (cur = fr_read; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
660 ast_writestream(*fs_read, cur);
664 if ((*fs_write) && (fr_write)) {
665 struct ast_frame *cur;
667 for (cur = fr_write; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
668 ast_writestream(*fs_write, cur);
673 struct ast_frame *cur;
675 for (cur = fr; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
676 ast_writestream(*fs, cur);
679 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
681 /* All done! free it. */
683 ast_frame_free(fr, 0);
686 ast_frame_free(fr_read, 0);
689 ast_frame_free(fr_write, 0);
696 ast_audiohook_lock(&mixmonitor->audiohook);
700 ast_test_suite_event_notify("MIXMONITOR_END", "Channel: %s\r\n"
702 ast_channel_name(mixmonitor->autochan->chan),
703 mixmonitor->filename);
705 ast_audiohook_unlock(&mixmonitor->audiohook);
707 ast_autochan_destroy(mixmonitor->autochan);
709 /* Datastore cleanup. close the filestream and wait for ds destruction */
710 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
711 mixmonitor_ds_close_fs(mixmonitor->mixmonitor_ds);
712 if (!mixmonitor->mixmonitor_ds->destruction_ok) {
713 ast_cond_wait(&mixmonitor->mixmonitor_ds->destruction_condition, &mixmonitor->mixmonitor_ds->lock);
715 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
717 /* kill the audiohook */
718 destroy_monitor_audiohook(mixmonitor);
720 if (mixmonitor->post_process) {
721 ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
722 ast_safe_system(mixmonitor->post_process);
725 ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
727 if (!AST_LIST_EMPTY(&mixmonitor->recipient_list)) {
728 if (ast_strlen_zero(fs_ext)) {
729 ast_log(LOG_ERROR, "No file extension set for Mixmonitor %s. Skipping copy to voicemail.\n",
732 ast_verb(3, "Copying recordings for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
733 copy_to_voicemail(mixmonitor, fs_ext, mixmonitor->filename);
735 if (!ast_strlen_zero(fs_read_ext)) {
736 ast_verb(3, "Copying read recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
737 copy_to_voicemail(mixmonitor, fs_read_ext, mixmonitor->filename_read);
739 if (!ast_strlen_zero(fs_write_ext)) {
740 ast_verb(3, "Copying write recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
741 copy_to_voicemail(mixmonitor, fs_write_ext, mixmonitor->filename_write);
744 ast_debug(3, "No recipients to forward monitor to, moving on.\n");
747 mixmonitor_free(mixmonitor);
749 ast_module_unref(ast_module_info->self);
753 static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan, char **datastore_id)
755 struct ast_datastore *datastore = NULL;
756 struct mixmonitor_ds *mixmonitor_ds;
758 if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
762 if (ast_asprintf(datastore_id, "%p", mixmonitor_ds) == -1) {
763 ast_log(LOG_ERROR, "Failed to allocate memory for MixMonitor ID.\n");
766 ast_mutex_init(&mixmonitor_ds->lock);
767 ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
769 if (!(datastore = ast_datastore_alloc(&mixmonitor_ds_info, *datastore_id))) {
770 ast_mutex_destroy(&mixmonitor_ds->lock);
771 ast_cond_destroy(&mixmonitor_ds->destruction_condition);
772 ast_free(mixmonitor_ds);
777 mixmonitor_ds->samp_rate = 8000;
778 mixmonitor_ds->audiohook = &mixmonitor->audiohook;
779 datastore->data = mixmonitor_ds;
781 ast_channel_lock(chan);
782 ast_channel_datastore_add(chan, datastore);
783 ast_channel_unlock(chan);
785 mixmonitor->mixmonitor_ds = mixmonitor_ds;
789 static int launch_monitor_thread(struct ast_channel *chan, const char *filename,
790 unsigned int flags, int readvol, int writevol,
791 const char *post_process, const char *filename_write,
792 char *filename_read, const char *uid_channel_var,
793 const char *recipients)
796 struct mixmonitor *mixmonitor;
797 char postprocess2[1024] = "";
798 char *datastore_id = NULL;
801 /* If a post process system command is given attach it to the structure */
802 if (!ast_strlen_zero(post_process)) {
805 p1 = ast_strdupa(post_process);
806 for (p2 = p1; *p2; p2++) {
807 if (*p2 == '^' && *(p2+1) == '{') {
811 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
814 /* Pre-allocate mixmonitor structure and spy */
815 if (!(mixmonitor = ast_calloc(1, sizeof(*mixmonitor)))) {
819 /* Now that the struct has been calloced, go ahead and initialize the string fields. */
820 if (ast_string_field_init(mixmonitor, 512)) {
821 mixmonitor_free(mixmonitor);
825 /* Setup the actual spy before creating our thread */
826 if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type, 0)) {
827 mixmonitor_free(mixmonitor);
831 /* Copy over flags and channel name */
832 mixmonitor->flags = flags;
833 if (!(mixmonitor->autochan = ast_autochan_setup(chan))) {
834 mixmonitor_free(mixmonitor);
838 if (setup_mixmonitor_ds(mixmonitor, chan, &datastore_id)) {
839 ast_autochan_destroy(mixmonitor->autochan);
840 mixmonitor_free(mixmonitor);
841 ast_free(datastore_id);
845 if (!ast_strlen_zero(uid_channel_var)) {
847 pbx_builtin_setvar_helper(chan, uid_channel_var, datastore_id);
850 ast_free(datastore_id);
852 mixmonitor->name = ast_strdup(ast_channel_name(chan));
854 if (!ast_strlen_zero(postprocess2)) {
855 mixmonitor->post_process = ast_strdup(postprocess2);
858 if (!ast_strlen_zero(filename)) {
859 mixmonitor->filename = ast_strdup(filename);
862 if (!ast_strlen_zero(filename_write)) {
863 mixmonitor->filename_write = ast_strdup(filename_write);
866 if (!ast_strlen_zero(filename_read)) {
867 mixmonitor->filename_read = ast_strdup(filename_read);
870 if (!ast_strlen_zero(recipients)) {
872 struct ast_party_connected_line *connected;
874 ast_channel_lock(chan);
876 /* We use the connected line of the invoking channel for caller ID. */
878 connected = ast_channel_connected(chan);
879 ast_debug(3, "Connected Line CID = %d - %s : %d - %s\n", connected->id.name.valid,
880 connected->id.name.str, connected->id.number.valid,
881 connected->id.number.str);
882 ast_callerid_merge(callerid, sizeof(callerid),
883 S_COR(connected->id.name.valid, connected->id.name.str, NULL),
884 S_COR(connected->id.number.valid, connected->id.number.str, NULL),
887 ast_string_field_set(mixmonitor, call_context, ast_channel_context(chan));
888 ast_string_field_set(mixmonitor, call_macrocontext, ast_channel_macrocontext(chan));
889 ast_string_field_set(mixmonitor, call_extension, ast_channel_exten(chan));
890 ast_string_field_set(mixmonitor, call_callerchan, ast_channel_name(chan));
891 ast_string_field_set(mixmonitor, call_callerid, callerid);
892 mixmonitor->call_priority = ast_channel_priority(chan);
894 ast_channel_unlock(chan);
896 add_vm_recipients_from_string(mixmonitor, recipients);
899 ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
902 mixmonitor->audiohook.options.read_volume = readvol;
904 mixmonitor->audiohook.options.write_volume = writevol;
906 if (startmon(chan, &mixmonitor->audiohook)) {
907 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
908 mixmonitor_spy_type, ast_channel_name(chan));
909 ast_audiohook_destroy(&mixmonitor->audiohook);
910 mixmonitor_free(mixmonitor);
914 /* reference be released at mixmonitor destruction */
915 mixmonitor->callid = ast_read_threadstorage_callid();
917 return ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
920 /* a note on filename_parse: creates directory structure and assigns absolute path from relative paths for filenames */
921 /* requires immediate copying of string from return to retain data since otherwise it will immediately lose scope */
922 static char *filename_parse(char *filename, char *buffer, size_t len)
925 if (ast_strlen_zero(filename)) {
926 ast_log(LOG_WARNING, "No file name was provided for a file save option.\n");
927 } else if (filename[0] != '/') {
929 build = ast_alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(filename) + 3);
930 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, filename);
934 ast_copy_string(buffer, filename, len);
936 if ((slash = strrchr(filename, '/'))) {
939 ast_mkdir(filename, 0777);
944 static int mixmonitor_exec(struct ast_channel *chan, const char *data)
946 int x, readvol = 0, writevol = 0;
947 char *filename_read = NULL;
948 char *filename_write = NULL;
949 char filename_buffer[1024] = "";
950 char *uid_channel_var = NULL;
952 struct ast_flags flags = { 0 };
953 char *recipients = NULL;
955 AST_DECLARE_APP_ARGS(args,
956 AST_APP_ARG(filename);
957 AST_APP_ARG(options);
958 AST_APP_ARG(post_process);
961 if (ast_strlen_zero(data)) {
962 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename or ,t(filename) and/or r(filename)\n");
966 parse = ast_strdupa(data);
968 AST_STANDARD_APP_ARGS(args, parse);
971 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
973 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
975 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
976 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
977 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
978 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
979 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
981 readvol = get_volfactor(x);
985 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
986 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
987 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
988 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
989 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
991 writevol = get_volfactor(x);
995 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
996 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
997 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
998 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
999 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
1001 readvol = writevol = get_volfactor(x);
1005 if (ast_test_flag(&flags, MUXFLAG_VMRECIPIENTS)) {
1006 if (ast_strlen_zero(opts[OPT_ARG_VMRECIPIENTS])) {
1007 ast_log(LOG_WARNING, "No voicemail recipients were specified for the vm copy ('m') option.\n");
1009 recipients = ast_strdupa(opts[OPT_ARG_VMRECIPIENTS]);
1013 if (ast_test_flag(&flags, MUXFLAG_WRITE)) {
1014 filename_write = ast_strdupa(filename_parse(opts[OPT_ARG_WRITENAME], filename_buffer, sizeof(filename_buffer)));
1017 if (ast_test_flag(&flags, MUXFLAG_READ)) {
1018 filename_read = ast_strdupa(filename_parse(opts[OPT_ARG_READNAME], filename_buffer, sizeof(filename_buffer)));
1021 if (ast_test_flag(&flags, MUXFLAG_UID)) {
1022 uid_channel_var = opts[OPT_ARG_UID];
1025 /* If there are no file writing arguments/options for the mix monitor, send a warning message and return -1 */
1027 if (!ast_test_flag(&flags, MUXFLAG_WRITE) && !ast_test_flag(&flags, MUXFLAG_READ) && ast_strlen_zero(args.filename)) {
1028 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
1032 /* If filename exists, try to create directories for it */
1033 if (!(ast_strlen_zero(args.filename))) {
1034 args.filename = ast_strdupa(filename_parse(args.filename, filename_buffer, sizeof(filename_buffer)));
1037 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
1039 /* If launch_monitor_thread works, the module reference must not be released until it is finished. */
1040 ast_module_ref(ast_module_info->self);
1041 if (launch_monitor_thread(chan,
1051 ast_module_unref(ast_module_info->self);
1057 static int stop_mixmonitor_full(struct ast_channel *chan, const char *data)
1059 struct ast_datastore *datastore = NULL;
1061 struct mixmonitor_ds *mixmonitor_ds;
1063 AST_DECLARE_APP_ARGS(args,
1064 AST_APP_ARG(mixmonid);
1067 if (!ast_strlen_zero(data)) {
1068 parse = ast_strdupa(data);
1071 AST_STANDARD_APP_ARGS(args, parse);
1073 ast_channel_lock(chan);
1075 if (!(datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, args.mixmonid))) {
1076 ast_channel_unlock(chan);
1079 mixmonitor_ds = datastore->data;
1081 ast_mutex_lock(&mixmonitor_ds->lock);
1083 /* closing the filestream here guarantees the file is avaliable to the dialplan
1084 * after calling StopMixMonitor */
1085 mixmonitor_ds_close_fs(mixmonitor_ds);
1087 /* The mixmonitor thread may be waiting on the audiohook trigger.
1088 * In order to exit from the mixmonitor loop before waiting on channel
1089 * destruction, poke the audiohook trigger. */
1090 if (mixmonitor_ds->audiohook) {
1091 if (mixmonitor_ds->audiohook->status != AST_AUDIOHOOK_STATUS_DONE) {
1092 ast_audiohook_update_status(mixmonitor_ds->audiohook, AST_AUDIOHOOK_STATUS_SHUTDOWN);
1094 ast_audiohook_lock(mixmonitor_ds->audiohook);
1095 ast_cond_signal(&mixmonitor_ds->audiohook->trigger);
1096 ast_audiohook_unlock(mixmonitor_ds->audiohook);
1097 mixmonitor_ds->audiohook = NULL;
1100 ast_mutex_unlock(&mixmonitor_ds->lock);
1102 /* Remove the datastore so the monitor thread can exit */
1103 if (!ast_channel_datastore_remove(chan, datastore)) {
1104 ast_datastore_free(datastore);
1106 ast_channel_unlock(chan);
1111 static int stop_mixmonitor_exec(struct ast_channel *chan, const char *data)
1113 stop_mixmonitor_full(chan, data);
1117 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1119 struct ast_channel *chan;
1120 struct ast_datastore *datastore = NULL;
1121 struct mixmonitor_ds *mixmonitor_ds = NULL;
1125 e->command = "mixmonitor {start|stop|list}";
1127 "Usage: mixmonitor <start|stop|list> <chan_name> [args]\n"
1128 " The optional arguments are passed to the MixMonitor\n"
1129 " application when the 'start' command is used.\n";
1132 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
1136 return CLI_SHOWUSAGE;
1139 if (!(chan = ast_channel_get_by_name_prefix(a->argv[2], strlen(a->argv[2])))) {
1140 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
1141 /* Technically this is a failure, but we don't want 2 errors printing out */
1145 ast_channel_lock(chan);
1147 if (!strcasecmp(a->argv[1], "start")) {
1148 mixmonitor_exec(chan, (a->argc >= 4) ? a->argv[3] : "");
1149 ast_channel_unlock(chan);
1150 } else if (!strcasecmp(a->argv[1], "stop")){
1151 ast_channel_unlock(chan);
1152 stop_mixmonitor_exec(chan, (a->argc >= 4) ? a->argv[3] : "");
1153 } else if (!strcasecmp(a->argv[1], "list")) {
1154 ast_cli(a->fd, "MixMonitor ID\tFile\tReceive File\tTransmit File\n");
1155 ast_cli(a->fd, "=========================================================================\n");
1156 AST_LIST_TRAVERSE(ast_channel_datastores(chan), datastore, entry) {
1157 if (datastore->info == &mixmonitor_ds_info) {
1158 char *filename = "";
1159 char *filename_read = "";
1160 char *filename_write = "";
1161 mixmonitor_ds = datastore->data;
1162 if (mixmonitor_ds->fs)
1163 filename = ast_strdupa(mixmonitor_ds->fs->filename);
1164 if (mixmonitor_ds->fs_read)
1165 filename_read = ast_strdupa(mixmonitor_ds->fs_read->filename);
1166 if (mixmonitor_ds->fs_write)
1167 filename_write = ast_strdupa(mixmonitor_ds->fs_write->filename);
1168 ast_cli(a->fd, "%p\t%s\t%s\t%s\n", mixmonitor_ds, filename, filename_read, filename_write);
1171 ast_channel_unlock(chan);
1173 ast_channel_unlock(chan);
1174 chan = ast_channel_unref(chan);
1175 return CLI_SHOWUSAGE;
1178 chan = ast_channel_unref(chan);
1183 /*! \brief Mute / unmute a MixMonitor channel */
1184 static int manager_mute_mixmonitor(struct mansession *s, const struct message *m)
1186 struct ast_channel *c = NULL;
1188 const char *name = astman_get_header(m, "Channel");
1189 const char *id = astman_get_header(m, "ActionID");
1190 const char *state = astman_get_header(m, "State");
1191 const char *direction = astman_get_header(m,"Direction");
1195 enum ast_audiohook_flags flag;
1197 if (ast_strlen_zero(direction)) {
1198 astman_send_error(s, m, "No direction specified. Must be read, write or both");
1202 if (!strcasecmp(direction, "read")) {
1203 flag = AST_AUDIOHOOK_MUTE_READ;
1204 } else if (!strcasecmp(direction, "write")) {
1205 flag = AST_AUDIOHOOK_MUTE_WRITE;
1206 } else if (!strcasecmp(direction, "both")) {
1207 flag = AST_AUDIOHOOK_MUTE_READ | AST_AUDIOHOOK_MUTE_WRITE;
1209 astman_send_error(s, m, "Invalid direction specified. Must be read, write or both");
1213 if (ast_strlen_zero(name)) {
1214 astman_send_error(s, m, "No channel specified");
1218 if (ast_strlen_zero(state)) {
1219 astman_send_error(s, m, "No state specified");
1223 clearmute = ast_false(state);
1224 c = ast_channel_get_by_name(name);
1227 astman_send_error(s, m, "No such channel");
1231 if (ast_audiohook_set_mute(c, mixmonitor_spy_type, flag, clearmute)) {
1232 c = ast_channel_unref(c);
1233 astman_send_error(s, m, "Cannot set mute flag");
1237 astman_append(s, "Response: Success\r\n");
1239 if (!ast_strlen_zero(id)) {
1240 astman_append(s, "ActionID: %s\r\n", id);
1243 astman_append(s, "\r\n");
1245 c = ast_channel_unref(c);
1250 static int start_mixmonitor_callback(struct ast_channel *chan, const char *filename, const char *options)
1252 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
1253 struct ast_flags flags = { 0 };
1254 char args[PATH_MAX] = "";
1257 if (!ast_strlen_zero(options)) {
1258 ast_app_parse_options(mixmonitor_opts, &flags, opts, ast_strdupa(options));
1261 snprintf(args, sizeof(args), "%s,%s", filename, options);
1263 ast_channel_lock(chan);
1264 res = mixmonitor_exec(chan, args);
1265 ast_channel_unlock(chan);
1270 static int stop_mixmonitor_callback(struct ast_channel *chan, const char *mixmonitor_id)
1272 return stop_mixmonitor_full(chan, mixmonitor_id);
1275 static int manager_mixmonitor(struct mansession *s, const struct message *m)
1277 struct ast_channel *c = NULL;
1279 const char *name = astman_get_header(m, "Channel");
1280 const char *id = astman_get_header(m, "ActionID");
1281 const char *file = astman_get_header(m, "File");
1282 const char *options = astman_get_header(m, "Options");
1283 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
1284 struct ast_flags flags = { 0 };
1285 char *uid_channel_var = NULL;
1286 const char *mixmonitor_id = NULL;
1289 char args[PATH_MAX] = "";
1290 if (ast_strlen_zero(name)) {
1291 astman_send_error(s, m, "No channel specified");
1295 c = ast_channel_get_by_name(name);
1298 astman_send_error(s, m, "No such channel");
1302 if (!ast_strlen_zero(options)) {
1303 ast_app_parse_options(mixmonitor_opts, &flags, opts, ast_strdupa(options));
1306 snprintf(args, sizeof(args), "%s,%s", file, options);
1308 ast_channel_lock(c);
1309 res = mixmonitor_exec(c, args);
1311 if (ast_test_flag(&flags, MUXFLAG_UID)) {
1312 uid_channel_var = opts[OPT_ARG_UID];
1313 mixmonitor_id = pbx_builtin_getvar_helper(c, uid_channel_var);
1315 ast_channel_unlock(c);
1318 c = ast_channel_unref(c);
1319 astman_send_error(s, m, "Could not start monitoring channel");
1323 astman_append(s, "Response: Success\r\n");
1325 if (!ast_strlen_zero(id)) {
1326 astman_append(s, "ActionID: %s\r\n", id);
1329 if (!ast_strlen_zero(mixmonitor_id)) {
1330 astman_append(s, "MixMonitorID: %s\r\n", mixmonitor_id);
1333 astman_append(s, "\r\n");
1335 c = ast_channel_unref(c);
1340 static int manager_stop_mixmonitor(struct mansession *s, const struct message *m)
1342 struct ast_channel *c = NULL;
1344 const char *name = astman_get_header(m, "Channel");
1345 const char *id = astman_get_header(m, "ActionID");
1346 const char *mixmonitor_id = astman_get_header(m, "MixMonitorID");
1349 if (ast_strlen_zero(name)) {
1350 astman_send_error(s, m, "No channel specified");
1354 c = ast_channel_get_by_name(name);
1357 astman_send_error(s, m, "No such channel");
1361 res = stop_mixmonitor_full(c, mixmonitor_id);
1364 astman_send_error(s, m, "Could not stop monitoring channel");
1368 astman_append(s, "Response: Success\r\n");
1370 if (!ast_strlen_zero(id)) {
1371 astman_append(s, "ActionID: %s\r\n", id);
1374 astman_append(s, "\r\n");
1376 c = ast_channel_unref(c);
1381 static struct ast_cli_entry cli_mixmonitor[] = {
1382 AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
1385 static int set_mixmonitor_methods(void)
1387 struct ast_mixmonitor_methods mixmonitor_methods = {
1388 .start = start_mixmonitor_callback,
1389 .stop = stop_mixmonitor_callback,
1392 return ast_set_mixmonitor_methods(&mixmonitor_methods);
1395 static int clear_mixmonitor_methods(void)
1397 return ast_clear_mixmonitor_methods();
1400 static int unload_module(void)
1404 ast_cli_unregister_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
1405 res = ast_unregister_application(stop_app);
1406 res |= ast_unregister_application(app);
1407 res |= ast_manager_unregister("MixMonitorMute");
1408 res |= ast_manager_unregister("MixMonitor");
1409 res |= ast_manager_unregister("StopMixMonitor");
1410 res |= clear_mixmonitor_methods();
1415 static int load_module(void)
1419 ast_cli_register_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
1420 res = ast_register_application_xml(app, mixmonitor_exec);
1421 res |= ast_register_application_xml(stop_app, stop_mixmonitor_exec);
1422 res |= ast_manager_register_xml("MixMonitorMute", 0, manager_mute_mixmonitor);
1423 res |= ast_manager_register_xml("MixMonitor", 0, manager_mixmonitor);
1424 res |= ast_manager_register_xml("StopMixMonitor", 0, manager_stop_mixmonitor);
1425 res |= set_mixmonitor_methods();
1430 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");