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>If you utilize this option inside a Local channel, you must make sure the Local
83 channel is not optimized away. To do this, be sure to call your Local channel with the
84 <literal>/n</literal> option. For example: Dial(Local/start@mycontext/n)</para></note>
87 <para>Adjust the <emphasis>heard</emphasis> volume by a factor of <replaceable>x</replaceable>
88 (range <literal>-4</literal> to <literal>4</literal>)</para>
89 <argument name="x" required="true" />
92 <para>Adjust the <emphasis>spoken</emphasis> volume by a factor
93 of <replaceable>x</replaceable> (range <literal>-4</literal> to <literal>4</literal>)</para>
94 <argument name="x" required="true" />
97 <para>Adjust both, <emphasis>heard and spoken</emphasis> volumes by a factor
98 of <replaceable>x</replaceable> (range <literal>-4</literal> to <literal>4</literal>)</para>
99 <argument name="x" required="true" />
102 <argument name="file" required="true" />
103 <para>Use the specified file to record the <emphasis>receive</emphasis> audio feed.
104 Like with the basic filename argument, if an absolute path isn't given, it will create
105 the file in the configured monitoring directory.</para>
108 <argument name="file" required="true" />
109 <para>Use the specified file to record the <emphasis>transmit</emphasis> audio feed.
110 Like with the basic filename argument, if an absolute path isn't given, it will create
111 the file in the configured monitoring directory.</para>
114 <argument name="chanvar" required="true" />
115 <para>Stores the MixMonitor's ID on this channel variable.</para>
118 <argument name="mailbox" required="true" />
119 <para>Create a copy of the recording as a voicemail in the indicated <emphasis>mailbox</emphasis>(es)
120 separated by commas eg. m(1111@default,2222@default,...). Folders can be optionally specified using
121 the syntax: mailbox@context/folder</para>
125 <parameter name="command">
126 <para>Will be executed when the recording is over.</para>
127 <para>Any strings matching <literal>^{X}</literal> will be unescaped to <variable>X</variable>.</para>
128 <para>All variables will be evaluated at the time MixMonitor is called.</para>
132 <para>Records the audio on the current channel to the specified file.</para>
133 <para>This application does not automatically answer and should be preceeded by
134 an application such as Answer or Progress().</para>
135 <note><para>MixMonitor runs as an audiohook.</para></note>
137 <variable name="MIXMONITOR_FILENAME">
138 <para>Will contain the filename used to record.</para>
143 <ref type="application">Monitor</ref>
144 <ref type="application">StopMixMonitor</ref>
145 <ref type="application">PauseMonitor</ref>
146 <ref type="application">UnpauseMonitor</ref>
147 <ref type="function">AUDIOHOOK_INHERIT</ref>
150 <application name="StopMixMonitor" language="en_US">
152 Stop recording a call through MixMonitor, and free the recording's file handle.
155 <parameter name="MixMonitorID" required="false">
156 <para>If a valid ID is provided, then this command will stop only that specific
161 <para>Stops the audio recording that was started with a call to <literal>MixMonitor()</literal>
162 on the current channel.</para>
165 <ref type="application">MixMonitor</ref>
168 <manager name="MixMonitorMute" language="en_US">
170 Mute / unMute a Mixmonitor recording.
173 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
174 <parameter name="Channel" required="true">
175 <para>Used to specify the channel to mute.</para>
177 <parameter name="Direction">
178 <para>Which part of the recording to mute: read, write or both (from channel, to channel or both channels).</para>
180 <parameter name="State">
181 <para>Turn mute on or off : 1 to turn on, 0 to turn off.</para>
185 <para>This action may be used to mute a MixMonitor recording.</para>
188 <manager name="MixMonitor" language="en_US">
190 Record a call and mix the audio during the recording. Use of StopMixMonitor is required
191 to guarantee the audio file is available for processing during dialplan execution.
194 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
195 <parameter name="Channel" required="true">
196 <para>Used to specify the channel to record.</para>
198 <parameter name="File">
199 <para>Is the name of the file created in the monitor spool directory.
200 Defaults to the same name as the channel (with slashes replaced with dashes).
201 This argument is optional if you specify to record unidirectional audio with
202 either the r(filename) or t(filename) options in the options field. If
203 neither MIXMONITOR_FILENAME or this parameter is set, the mixed stream won't
206 <parameter name="options">
207 <para>Options that apply to the MixMonitor in the same way as they
208 would apply if invoked from the MixMonitor application. For a list of
209 available options, see the documentation for the mixmonitor application. </para>
213 <para>This action records the audio on the current channel to the specified file.</para>
215 <variable name="MIXMONITOR_FILENAME">
216 <para>Will contain the filename used to record the mixed stream.</para>
221 <manager name="StopMixMonitor" language="en_US">
223 Stop recording a call through MixMonitor, and free the recording's file handle.
226 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
227 <parameter name="Channel" required="true">
228 <para>The name of the channel monitored.</para>
230 <parameter name="MixMonitorID" required="false">
231 <para>If a valid ID is provided, then this command will stop only that specific
236 <para>This action stops the audio recording that was started with the <literal>MixMonitor</literal>
237 action on the current channel.</para>
240 <function name="MIXMONITOR" language="en_US">
242 Retrieve data pertaining to specific instances of MixMonitor on a channel.
245 <parameter name="id" required="true">
246 <para>The unique ID of the MixMonitor instance. The unique ID can be retrieved through the channel
247 variable used as an argument to the <replaceable>i</replaceable> option to MixMonitor.</para>
249 <parameter name="key" required="true">
250 <para>The piece of data to retrieve from the MixMonitor.</para>
252 <enum name="filename" />
260 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
262 static const char * const app = "MixMonitor";
264 static const char * const stop_app = "StopMixMonitor";
266 static const char * const mixmonitor_spy_type = "MixMonitor";
270 * \brief This struct is a list item holds data needed to find a vm_recipient within voicemail
272 struct vm_recipient {
273 char mailbox[AST_MAX_CONTEXT];
274 char context[AST_MAX_EXTENSION];
276 AST_LIST_ENTRY(vm_recipient) list;
280 struct ast_audiohook audiohook;
281 struct ast_callid *callid;
284 char *filename_write;
288 struct ast_autochan *autochan;
289 struct mixmonitor_ds *mixmonitor_ds;
291 /* the below string fields describe data used for creating voicemails from the recording */
292 AST_DECLARE_STRING_FIELDS(
293 AST_STRING_FIELD(call_context);
294 AST_STRING_FIELD(call_macrocontext);
295 AST_STRING_FIELD(call_extension);
296 AST_STRING_FIELD(call_callerchan);
297 AST_STRING_FIELD(call_callerid);
301 /* FUTURE DEVELOPMENT NOTICE
302 * recipient_list will need locks if we make it editable after the monitor is started */
303 AST_LIST_HEAD_NOLOCK(, vm_recipient) recipient_list;
306 enum mixmonitor_flags {
307 MUXFLAG_APPEND = (1 << 1),
308 MUXFLAG_BRIDGED = (1 << 2),
309 MUXFLAG_VOLUME = (1 << 3),
310 MUXFLAG_READVOLUME = (1 << 4),
311 MUXFLAG_WRITEVOLUME = (1 << 5),
312 MUXFLAG_READ = (1 << 6),
313 MUXFLAG_WRITE = (1 << 7),
314 MUXFLAG_COMBINED = (1 << 8),
315 MUXFLAG_UID = (1 << 9),
316 MUXFLAG_VMRECIPIENTS = (1 << 10),
319 enum mixmonitor_args {
320 OPT_ARG_READVOLUME = 0,
326 OPT_ARG_VMRECIPIENTS,
327 OPT_ARG_ARRAY_SIZE, /* Always last element of the enum */
330 AST_APP_OPTIONS(mixmonitor_opts, {
331 AST_APP_OPTION('a', MUXFLAG_APPEND),
332 AST_APP_OPTION('b', MUXFLAG_BRIDGED),
333 AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
334 AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
335 AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
336 AST_APP_OPTION_ARG('r', MUXFLAG_READ, OPT_ARG_READNAME),
337 AST_APP_OPTION_ARG('t', MUXFLAG_WRITE, OPT_ARG_WRITENAME),
338 AST_APP_OPTION_ARG('i', MUXFLAG_UID, OPT_ARG_UID),
339 AST_APP_OPTION_ARG('m', MUXFLAG_VMRECIPIENTS, OPT_ARG_VMRECIPIENTS),
342 struct mixmonitor_ds {
343 unsigned int destruction_ok;
344 ast_cond_t destruction_condition;
347 /* The filestream is held in the datastore so it can be stopped
348 * immediately during stop_mixmonitor or channel destruction. */
351 struct ast_filestream *fs;
352 struct ast_filestream *fs_read;
353 struct ast_filestream *fs_write;
355 struct ast_audiohook *audiohook;
357 unsigned int samp_rate;
363 * \pre mixmonitor_ds must be locked before calling this function
365 static void mixmonitor_ds_close_fs(struct mixmonitor_ds *mixmonitor_ds)
367 unsigned char quitting = 0;
369 if (mixmonitor_ds->fs) {
371 ast_closestream(mixmonitor_ds->fs);
372 mixmonitor_ds->fs = NULL;
373 ast_verb(2, "MixMonitor close filestream (mixed)\n");
376 if (mixmonitor_ds->fs_read) {
378 ast_closestream(mixmonitor_ds->fs_read);
379 mixmonitor_ds->fs_read = NULL;
380 ast_verb(2, "MixMonitor close filestream (read)\n");
383 if (mixmonitor_ds->fs_write) {
385 ast_closestream(mixmonitor_ds->fs_write);
386 mixmonitor_ds->fs_write = NULL;
387 ast_verb(2, "MixMonitor close filestream (write)\n");
391 mixmonitor_ds->fs_quit = 1;
395 static void mixmonitor_ds_destroy(void *data)
397 struct mixmonitor_ds *mixmonitor_ds = data;
399 ast_mutex_lock(&mixmonitor_ds->lock);
400 mixmonitor_ds->audiohook = NULL;
401 mixmonitor_ds->destruction_ok = 1;
402 ast_free(mixmonitor_ds->filename);
403 ast_cond_signal(&mixmonitor_ds->destruction_condition);
404 ast_mutex_unlock(&mixmonitor_ds->lock);
407 static const struct ast_datastore_info mixmonitor_ds_info = {
408 .type = "mixmonitor",
409 .destroy = mixmonitor_ds_destroy,
412 static void destroy_monitor_audiohook(struct mixmonitor *mixmonitor)
414 if (mixmonitor->mixmonitor_ds) {
415 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
416 mixmonitor->mixmonitor_ds->audiohook = NULL;
417 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
419 /* kill the audiohook.*/
420 ast_audiohook_lock(&mixmonitor->audiohook);
421 ast_audiohook_detach(&mixmonitor->audiohook);
422 ast_audiohook_unlock(&mixmonitor->audiohook);
423 ast_audiohook_destroy(&mixmonitor->audiohook);
426 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
433 ast_audiohook_attach(chan, audiohook);
436 ast_channel_lock(chan);
437 if (ast_channel_is_bridged(chan)) {
438 ast_softhangup_nolock(chan, AST_SOFTHANGUP_UNBRIDGE);
440 ast_channel_unlock(chan);
448 * \brief adds recipients to a mixmonitor's recipient list
449 * \param mixmonitor mixmonitor being affected
450 * \param vm_recipients string containing the desired recipients to add
452 static void add_vm_recipients_from_string(struct mixmonitor *mixmonitor, const char *vm_recipients)
454 /* recipients are in a single string with a format format resembling "mailbox@context/INBOX,mailbox2@context2,mailbox3@context3/Work" */
455 char *cur_mailbox = ast_strdupa(vm_recipients);
459 int elements_processed = 0;
461 while (!ast_strlen_zero(cur_mailbox)) {
462 ast_debug(3, "attempting to add next element %d from %s\n", elements_processed, cur_mailbox);
463 if ((next = strchr(cur_mailbox, ',')) || (next = strchr(cur_mailbox, '&'))) {
467 if ((cur_folder = strchr(cur_mailbox, '/'))) {
468 *(cur_folder++) = '\0';
470 cur_folder = "INBOX";
473 if ((cur_context = strchr(cur_mailbox, '@'))) {
474 *(cur_context++) = '\0';
476 cur_context = "default";
479 if (!ast_strlen_zero(cur_mailbox) && !ast_strlen_zero(cur_context)) {
480 struct vm_recipient *recipient;
481 if (!(recipient = ast_malloc(sizeof(*recipient)))) {
482 ast_log(LOG_ERROR, "Failed to allocate recipient. Aborting function.\n");
485 ast_copy_string(recipient->context, cur_context, sizeof(recipient->context));
486 ast_copy_string(recipient->mailbox, cur_mailbox, sizeof(recipient->mailbox));
487 ast_copy_string(recipient->folder, cur_folder, sizeof(recipient->folder));
490 ast_verb(5, "Adding %s@%s to recipient list\n", recipient->mailbox, recipient->context);
491 AST_LIST_INSERT_HEAD(&mixmonitor->recipient_list, recipient, list);
493 ast_log(LOG_ERROR, "Failed to properly parse extension and/or context from element %d of recipient string: %s\n", elements_processed, vm_recipients);
497 elements_processed++;
501 static void clear_mixmonitor_recipient_list(struct mixmonitor *mixmonitor)
503 struct vm_recipient *current;
504 while ((current = AST_LIST_REMOVE_HEAD(&mixmonitor->recipient_list, list))) {
505 /* Clear list element data */
510 #define SAMPLES_PER_FRAME 160
512 static void mixmonitor_free(struct mixmonitor *mixmonitor)
515 if (mixmonitor->mixmonitor_ds) {
516 ast_mutex_destroy(&mixmonitor->mixmonitor_ds->lock);
517 ast_cond_destroy(&mixmonitor->mixmonitor_ds->destruction_condition);
518 ast_free(mixmonitor->mixmonitor_ds);
521 ast_free(mixmonitor->name);
522 ast_free(mixmonitor->post_process);
523 ast_free(mixmonitor->filename);
524 ast_free(mixmonitor->filename_write);
525 ast_free(mixmonitor->filename_read);
527 /* Free everything in the recipient list */
528 clear_mixmonitor_recipient_list(mixmonitor);
530 /* clean stringfields */
531 ast_string_field_free_memory(mixmonitor);
533 if (mixmonitor->callid) {
534 ast_callid_unref(mixmonitor->callid);
536 ast_free(mixmonitor);
542 * \brief Copies the mixmonitor to all voicemail recipients
543 * \param mixmonitor The mixmonitor that needs to forward its file to recipients
544 * \param ext Format of the file that was saved
546 static void copy_to_voicemail(struct mixmonitor *mixmonitor, const char *ext, const char *filename)
548 struct vm_recipient *recipient = NULL;
549 struct ast_vm_recording_data recording_data;
550 if (ast_string_field_init(&recording_data, 512)) {
551 ast_log(LOG_ERROR, "Failed to string_field_init, skipping copy_to_voicemail\n");
555 /* Copy strings to stringfields that will be used for all recipients */
556 ast_string_field_set(&recording_data, recording_file, filename);
557 ast_string_field_set(&recording_data, recording_ext, ext);
558 ast_string_field_set(&recording_data, call_context, mixmonitor->call_context);
559 ast_string_field_set(&recording_data, call_macrocontext, mixmonitor->call_macrocontext);
560 ast_string_field_set(&recording_data, call_extension, mixmonitor->call_extension);
561 ast_string_field_set(&recording_data, call_callerchan, mixmonitor->call_callerchan);
562 ast_string_field_set(&recording_data, call_callerid, mixmonitor->call_callerid);
563 /* and call_priority gets copied too */
564 recording_data.call_priority = mixmonitor->call_priority;
566 AST_LIST_TRAVERSE(&mixmonitor->recipient_list, recipient, list) {
567 /* context, mailbox, and folder need to be set per recipient */
568 ast_string_field_set(&recording_data, context, recipient->context);
569 ast_string_field_set(&recording_data, mailbox, recipient->mailbox);
570 ast_string_field_set(&recording_data, folder, recipient->folder);
572 ast_verb(4, "MixMonitor attempting to send voicemail copy to %s@%s\n", recording_data.mailbox,
573 recording_data.context);
574 ast_app_copy_recording_to_vm(&recording_data);
577 /* Free the string fields for recording_data before exiting the function. */
578 ast_string_field_free_memory(&recording_data);
581 static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename, struct ast_filestream **fs, unsigned int *oflags, int *errflag, char **ext)
583 /* Initialize the file if not already done so */
584 char *last_slash = NULL;
585 if (!ast_strlen_zero(filename)) {
586 if (!*fs && !*errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
587 *oflags = O_CREAT | O_WRONLY;
588 *oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
590 last_slash = strrchr(filename, '/');
592 if ((*ext = strrchr(filename, '.')) && (*ext > last_slash)) {
599 if (!(*fs = ast_writefile(filename, *ext, NULL, *oflags, 0, 0666))) {
600 ast_log(LOG_ERROR, "Cannot open %s.%s\n", filename, *ext);
603 struct ast_filestream *tmp = *fs;
604 mixmonitor->mixmonitor_ds->samp_rate = MAX(mixmonitor->mixmonitor_ds->samp_rate, ast_format_rate(&tmp->fmt->format));
610 static void *mixmonitor_thread(void *obj)
612 struct mixmonitor *mixmonitor = obj;
614 char *fs_read_ext = "";
615 char *fs_write_ext = "";
617 struct ast_filestream **fs = NULL;
618 struct ast_filestream **fs_read = NULL;
619 struct ast_filestream **fs_write = NULL;
623 struct ast_format format_slin;
625 /* Keep callid association before any log messages */
626 if (mixmonitor->callid) {
627 ast_callid_threadassoc_add(mixmonitor->callid);
630 ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name);
632 fs = &mixmonitor->mixmonitor_ds->fs;
633 fs_read = &mixmonitor->mixmonitor_ds->fs_read;
634 fs_write = &mixmonitor->mixmonitor_ds->fs_write;
636 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
637 mixmonitor_save_prep(mixmonitor, mixmonitor->filename, fs, &oflags, &errflag, &fs_ext);
638 mixmonitor_save_prep(mixmonitor, mixmonitor->filename_read, fs_read, &oflags, &errflag, &fs_read_ext);
639 mixmonitor_save_prep(mixmonitor, mixmonitor->filename_write, fs_write, &oflags, &errflag, &fs_write_ext);
641 ast_format_set(&format_slin, ast_format_slin_by_rate(mixmonitor->mixmonitor_ds->samp_rate), 0);
643 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
646 /* The audiohook must enter and exit the loop locked */
647 ast_audiohook_lock(&mixmonitor->audiohook);
648 while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING && !mixmonitor->mixmonitor_ds->fs_quit) {
649 struct ast_frame *fr = NULL;
650 struct ast_frame *fr_read = NULL;
651 struct ast_frame *fr_write = NULL;
653 if (!(fr = ast_audiohook_read_frame_all(&mixmonitor->audiohook, SAMPLES_PER_FRAME, &format_slin,
654 &fr_read, &fr_write))) {
655 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
657 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
663 /* audiohook lock is not required for the next block.
664 * Unlock it, but remember to lock it before looping or exiting */
665 ast_audiohook_unlock(&mixmonitor->audiohook);
667 if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED)
668 || (mixmonitor->autochan->chan
669 && ast_channel_is_bridged(mixmonitor->autochan->chan))) {
670 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
672 /* Write out the frame(s) */
673 if ((*fs_read) && (fr_read)) {
674 struct ast_frame *cur;
676 for (cur = fr_read; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
677 ast_writestream(*fs_read, cur);
681 if ((*fs_write) && (fr_write)) {
682 struct ast_frame *cur;
684 for (cur = fr_write; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
685 ast_writestream(*fs_write, cur);
690 struct ast_frame *cur;
692 for (cur = fr; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
693 ast_writestream(*fs, cur);
696 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
698 /* All done! free it. */
700 ast_frame_free(fr, 0);
703 ast_frame_free(fr_read, 0);
706 ast_frame_free(fr_write, 0);
713 ast_audiohook_lock(&mixmonitor->audiohook);
717 ast_test_suite_event_notify("MIXMONITOR_END", "Channel: %s\r\n"
719 ast_channel_name(mixmonitor->autochan->chan),
720 mixmonitor->filename);
722 ast_audiohook_unlock(&mixmonitor->audiohook);
724 ast_autochan_destroy(mixmonitor->autochan);
726 /* Datastore cleanup. close the filestream and wait for ds destruction */
727 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
728 mixmonitor_ds_close_fs(mixmonitor->mixmonitor_ds);
729 if (!mixmonitor->mixmonitor_ds->destruction_ok) {
730 ast_cond_wait(&mixmonitor->mixmonitor_ds->destruction_condition, &mixmonitor->mixmonitor_ds->lock);
732 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
734 /* kill the audiohook */
735 destroy_monitor_audiohook(mixmonitor);
737 if (mixmonitor->post_process) {
738 ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
739 ast_safe_system(mixmonitor->post_process);
742 ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
744 if (!AST_LIST_EMPTY(&mixmonitor->recipient_list)) {
745 if (ast_strlen_zero(fs_ext)) {
746 ast_log(LOG_ERROR, "No file extension set for Mixmonitor %s. Skipping copy to voicemail.\n",
749 ast_verb(3, "Copying recordings for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
750 copy_to_voicemail(mixmonitor, fs_ext, mixmonitor->filename);
752 if (!ast_strlen_zero(fs_read_ext)) {
753 ast_verb(3, "Copying read recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
754 copy_to_voicemail(mixmonitor, fs_read_ext, mixmonitor->filename_read);
756 if (!ast_strlen_zero(fs_write_ext)) {
757 ast_verb(3, "Copying write recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
758 copy_to_voicemail(mixmonitor, fs_write_ext, mixmonitor->filename_write);
761 ast_debug(3, "No recipients to forward monitor to, moving on.\n");
764 mixmonitor_free(mixmonitor);
766 ast_module_unref(ast_module_info->self);
770 static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan, char **datastore_id)
772 struct ast_datastore *datastore = NULL;
773 struct mixmonitor_ds *mixmonitor_ds;
775 if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
779 if (ast_asprintf(datastore_id, "%p", mixmonitor_ds) == -1) {
780 ast_log(LOG_ERROR, "Failed to allocate memory for MixMonitor ID.\n");
783 ast_mutex_init(&mixmonitor_ds->lock);
784 ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
786 if (!(datastore = ast_datastore_alloc(&mixmonitor_ds_info, *datastore_id))) {
787 ast_mutex_destroy(&mixmonitor_ds->lock);
788 ast_cond_destroy(&mixmonitor_ds->destruction_condition);
789 ast_free(mixmonitor_ds);
794 mixmonitor_ds->samp_rate = 8000;
795 mixmonitor_ds->audiohook = &mixmonitor->audiohook;
796 mixmonitor_ds->filename = ast_strdup(mixmonitor->filename);
797 datastore->data = mixmonitor_ds;
799 ast_channel_lock(chan);
800 ast_channel_datastore_add(chan, datastore);
801 ast_channel_unlock(chan);
803 mixmonitor->mixmonitor_ds = mixmonitor_ds;
807 static int launch_monitor_thread(struct ast_channel *chan, const char *filename,
808 unsigned int flags, int readvol, int writevol,
809 const char *post_process, const char *filename_write,
810 char *filename_read, const char *uid_channel_var,
811 const char *recipients)
814 struct mixmonitor *mixmonitor;
815 char postprocess2[1024] = "";
816 char *datastore_id = NULL;
819 /* If a post process system command is given attach it to the structure */
820 if (!ast_strlen_zero(post_process)) {
823 p1 = ast_strdupa(post_process);
824 for (p2 = p1; *p2; p2++) {
825 if (*p2 == '^' && *(p2+1) == '{') {
829 ast_channel_lock(chan);
830 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
831 ast_channel_unlock(chan);
834 /* Pre-allocate mixmonitor structure and spy */
835 if (!(mixmonitor = ast_calloc(1, sizeof(*mixmonitor)))) {
839 /* Now that the struct has been calloced, go ahead and initialize the string fields. */
840 if (ast_string_field_init(mixmonitor, 512)) {
841 mixmonitor_free(mixmonitor);
845 /* Setup the actual spy before creating our thread */
846 if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type, 0)) {
847 mixmonitor_free(mixmonitor);
851 /* Copy over flags and channel name */
852 mixmonitor->flags = flags;
853 if (!(mixmonitor->autochan = ast_autochan_setup(chan))) {
854 mixmonitor_free(mixmonitor);
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 (setup_mixmonitor_ds(mixmonitor, chan, &datastore_id)) {
871 ast_autochan_destroy(mixmonitor->autochan);
872 mixmonitor_free(mixmonitor);
873 ast_free(datastore_id);
877 if (!ast_strlen_zero(uid_channel_var)) {
879 pbx_builtin_setvar_helper(chan, uid_channel_var, datastore_id);
882 ast_free(datastore_id);
884 mixmonitor->name = ast_strdup(ast_channel_name(chan));
886 if (!ast_strlen_zero(postprocess2)) {
887 mixmonitor->post_process = ast_strdup(postprocess2);
890 if (!ast_strlen_zero(recipients)) {
892 struct ast_party_connected_line *connected;
894 ast_channel_lock(chan);
896 /* We use the connected line of the invoking channel for caller ID. */
898 connected = ast_channel_connected(chan);
899 ast_debug(3, "Connected Line CID = %d - %s : %d - %s\n", connected->id.name.valid,
900 connected->id.name.str, connected->id.number.valid,
901 connected->id.number.str);
902 ast_callerid_merge(callerid, sizeof(callerid),
903 S_COR(connected->id.name.valid, connected->id.name.str, NULL),
904 S_COR(connected->id.number.valid, connected->id.number.str, NULL),
907 ast_string_field_set(mixmonitor, call_context, ast_channel_context(chan));
908 ast_string_field_set(mixmonitor, call_macrocontext, ast_channel_macrocontext(chan));
909 ast_string_field_set(mixmonitor, call_extension, ast_channel_exten(chan));
910 ast_string_field_set(mixmonitor, call_callerchan, ast_channel_name(chan));
911 ast_string_field_set(mixmonitor, call_callerid, callerid);
912 mixmonitor->call_priority = ast_channel_priority(chan);
914 ast_channel_unlock(chan);
916 add_vm_recipients_from_string(mixmonitor, recipients);
919 ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
922 mixmonitor->audiohook.options.read_volume = readvol;
924 mixmonitor->audiohook.options.write_volume = writevol;
926 if (startmon(chan, &mixmonitor->audiohook)) {
927 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
928 mixmonitor_spy_type, ast_channel_name(chan));
929 ast_audiohook_destroy(&mixmonitor->audiohook);
930 mixmonitor_free(mixmonitor);
934 /* reference be released at mixmonitor destruction */
935 mixmonitor->callid = ast_read_threadstorage_callid();
937 return ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
940 /* a note on filename_parse: creates directory structure and assigns absolute path from relative paths for filenames */
941 /* requires immediate copying of string from return to retain data since otherwise it will immediately lose scope */
942 static char *filename_parse(char *filename, char *buffer, size_t len)
945 if (ast_strlen_zero(filename)) {
946 ast_log(LOG_WARNING, "No file name was provided for a file save option.\n");
947 } else if (filename[0] != '/') {
949 build = ast_alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(filename) + 3);
950 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, filename);
954 ast_copy_string(buffer, filename, len);
956 if ((slash = strrchr(filename, '/'))) {
959 ast_mkdir(filename, 0777);
964 static int mixmonitor_exec(struct ast_channel *chan, const char *data)
966 int x, readvol = 0, writevol = 0;
967 char *filename_read = NULL;
968 char *filename_write = NULL;
969 char filename_buffer[1024] = "";
970 char *uid_channel_var = NULL;
972 struct ast_flags flags = { 0 };
973 char *recipients = NULL;
975 AST_DECLARE_APP_ARGS(args,
976 AST_APP_ARG(filename);
977 AST_APP_ARG(options);
978 AST_APP_ARG(post_process);
981 if (ast_strlen_zero(data)) {
982 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename or ,t(filename) and/or r(filename)\n");
986 parse = ast_strdupa(data);
988 AST_STANDARD_APP_ARGS(args, parse);
991 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
993 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
995 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
996 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
997 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
998 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
999 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
1001 readvol = get_volfactor(x);
1005 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
1006 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
1007 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
1008 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
1009 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
1011 writevol = get_volfactor(x);
1015 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
1016 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
1017 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
1018 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
1019 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
1021 readvol = writevol = get_volfactor(x);
1025 if (ast_test_flag(&flags, MUXFLAG_VMRECIPIENTS)) {
1026 if (ast_strlen_zero(opts[OPT_ARG_VMRECIPIENTS])) {
1027 ast_log(LOG_WARNING, "No voicemail recipients were specified for the vm copy ('m') option.\n");
1029 recipients = ast_strdupa(opts[OPT_ARG_VMRECIPIENTS]);
1033 if (ast_test_flag(&flags, MUXFLAG_WRITE)) {
1034 filename_write = ast_strdupa(filename_parse(opts[OPT_ARG_WRITENAME], filename_buffer, sizeof(filename_buffer)));
1037 if (ast_test_flag(&flags, MUXFLAG_READ)) {
1038 filename_read = ast_strdupa(filename_parse(opts[OPT_ARG_READNAME], filename_buffer, sizeof(filename_buffer)));
1041 if (ast_test_flag(&flags, MUXFLAG_UID)) {
1042 uid_channel_var = opts[OPT_ARG_UID];
1045 /* If there are no file writing arguments/options for the mix monitor, send a warning message and return -1 */
1047 if (!ast_test_flag(&flags, MUXFLAG_WRITE) && !ast_test_flag(&flags, MUXFLAG_READ) && ast_strlen_zero(args.filename)) {
1048 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
1052 /* If filename exists, try to create directories for it */
1053 if (!(ast_strlen_zero(args.filename))) {
1054 args.filename = ast_strdupa(filename_parse(args.filename, filename_buffer, sizeof(filename_buffer)));
1057 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
1059 /* If launch_monitor_thread works, the module reference must not be released until it is finished. */
1060 ast_module_ref(ast_module_info->self);
1061 if (launch_monitor_thread(chan,
1071 ast_module_unref(ast_module_info->self);
1077 static int stop_mixmonitor_full(struct ast_channel *chan, const char *data)
1079 struct ast_datastore *datastore = NULL;
1081 struct mixmonitor_ds *mixmonitor_ds;
1083 AST_DECLARE_APP_ARGS(args,
1084 AST_APP_ARG(mixmonid);
1087 if (!ast_strlen_zero(data)) {
1088 parse = ast_strdupa(data);
1091 AST_STANDARD_APP_ARGS(args, parse);
1093 ast_channel_lock(chan);
1095 datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, args.mixmonid);
1097 ast_channel_unlock(chan);
1100 mixmonitor_ds = datastore->data;
1102 ast_mutex_lock(&mixmonitor_ds->lock);
1104 /* closing the filestream here guarantees the file is available to the dialplan
1105 * after calling StopMixMonitor */
1106 mixmonitor_ds_close_fs(mixmonitor_ds);
1108 /* The mixmonitor thread may be waiting on the audiohook trigger.
1109 * In order to exit from the mixmonitor loop before waiting on channel
1110 * destruction, poke the audiohook trigger. */
1111 if (mixmonitor_ds->audiohook) {
1112 if (mixmonitor_ds->audiohook->status != AST_AUDIOHOOK_STATUS_DONE) {
1113 ast_audiohook_update_status(mixmonitor_ds->audiohook, AST_AUDIOHOOK_STATUS_SHUTDOWN);
1115 ast_audiohook_lock(mixmonitor_ds->audiohook);
1116 ast_cond_signal(&mixmonitor_ds->audiohook->trigger);
1117 ast_audiohook_unlock(mixmonitor_ds->audiohook);
1118 mixmonitor_ds->audiohook = NULL;
1121 ast_mutex_unlock(&mixmonitor_ds->lock);
1123 /* Remove the datastore so the monitor thread can exit */
1124 if (!ast_channel_datastore_remove(chan, datastore)) {
1125 ast_datastore_free(datastore);
1127 ast_channel_unlock(chan);
1132 static int stop_mixmonitor_exec(struct ast_channel *chan, const char *data)
1134 stop_mixmonitor_full(chan, data);
1138 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1140 struct ast_channel *chan;
1141 struct ast_datastore *datastore = NULL;
1142 struct mixmonitor_ds *mixmonitor_ds = NULL;
1146 e->command = "mixmonitor {start|stop|list}";
1148 "Usage: mixmonitor start <chan_name> [args]\n"
1149 " The optional arguments are passed to the MixMonitor application.\n"
1150 " mixmonitor stop <chan_name> [args]\n"
1151 " The optional arguments are passed to the StopMixMonitor application.\n"
1152 " mixmonitor list <chan_name>\n";
1155 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
1159 return CLI_SHOWUSAGE;
1162 if (!(chan = ast_channel_get_by_name_prefix(a->argv[2], strlen(a->argv[2])))) {
1163 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
1164 /* Technically this is a failure, but we don't want 2 errors printing out */
1168 if (!strcasecmp(a->argv[1], "start")) {
1169 mixmonitor_exec(chan, (a->argc >= 4) ? a->argv[3] : "");
1170 } else if (!strcasecmp(a->argv[1], "stop")){
1171 stop_mixmonitor_exec(chan, (a->argc >= 4) ? a->argv[3] : "");
1172 } else if (!strcasecmp(a->argv[1], "list")) {
1173 ast_cli(a->fd, "MixMonitor ID\tFile\tReceive File\tTransmit File\n");
1174 ast_cli(a->fd, "=========================================================================\n");
1175 ast_channel_lock(chan);
1176 AST_LIST_TRAVERSE(ast_channel_datastores(chan), datastore, entry) {
1177 if (datastore->info == &mixmonitor_ds_info) {
1178 char *filename = "";
1179 char *filename_read = "";
1180 char *filename_write = "";
1182 mixmonitor_ds = datastore->data;
1183 if (mixmonitor_ds->fs) {
1184 filename = mixmonitor_ds->fs->filename;
1186 if (mixmonitor_ds->fs_read) {
1187 filename_read = mixmonitor_ds->fs_read->filename;
1189 if (mixmonitor_ds->fs_write) {
1190 filename_write = mixmonitor_ds->fs_write->filename;
1192 ast_cli(a->fd, "%p\t%s\t%s\t%s\n", mixmonitor_ds, filename, filename_read, filename_write);
1195 ast_channel_unlock(chan);
1197 chan = ast_channel_unref(chan);
1198 return CLI_SHOWUSAGE;
1201 chan = ast_channel_unref(chan);
1206 /*! \brief Mute / unmute a MixMonitor channel */
1207 static int manager_mute_mixmonitor(struct mansession *s, const struct message *m)
1209 struct ast_channel *c;
1210 const char *name = astman_get_header(m, "Channel");
1211 const char *id = astman_get_header(m, "ActionID");
1212 const char *state = astman_get_header(m, "State");
1213 const char *direction = astman_get_header(m,"Direction");
1215 enum ast_audiohook_flags flag;
1217 if (ast_strlen_zero(direction)) {
1218 astman_send_error(s, m, "No direction specified. Must be read, write or both");
1222 if (!strcasecmp(direction, "read")) {
1223 flag = AST_AUDIOHOOK_MUTE_READ;
1224 } else if (!strcasecmp(direction, "write")) {
1225 flag = AST_AUDIOHOOK_MUTE_WRITE;
1226 } else if (!strcasecmp(direction, "both")) {
1227 flag = AST_AUDIOHOOK_MUTE_READ | AST_AUDIOHOOK_MUTE_WRITE;
1229 astman_send_error(s, m, "Invalid direction specified. Must be read, write or both");
1233 if (ast_strlen_zero(name)) {
1234 astman_send_error(s, m, "No channel specified");
1238 if (ast_strlen_zero(state)) {
1239 astman_send_error(s, m, "No state specified");
1243 clearmute = ast_false(state);
1245 c = ast_channel_get_by_name(name);
1247 astman_send_error(s, m, "No such channel");
1251 if (ast_audiohook_set_mute(c, mixmonitor_spy_type, flag, clearmute)) {
1252 ast_channel_unref(c);
1253 astman_send_error(s, m, "Cannot set mute flag");
1257 astman_append(s, "Response: Success\r\n");
1259 if (!ast_strlen_zero(id)) {
1260 astman_append(s, "ActionID: %s\r\n", id);
1263 astman_append(s, "\r\n");
1265 ast_channel_unref(c);
1270 static int start_mixmonitor_callback(struct ast_channel *chan, const char *filename, const char *options)
1272 char args[PATH_MAX];
1274 if (ast_strlen_zero(options)) {
1275 snprintf(args, sizeof(args), "%s", filename);
1277 snprintf(args, sizeof(args), "%s,%s", filename, options);
1280 return mixmonitor_exec(chan, args);
1283 static int stop_mixmonitor_callback(struct ast_channel *chan, const char *mixmonitor_id)
1285 return stop_mixmonitor_full(chan, mixmonitor_id);
1288 static int manager_mixmonitor(struct mansession *s, const struct message *m)
1290 struct ast_channel *c;
1291 const char *name = astman_get_header(m, "Channel");
1292 const char *id = astman_get_header(m, "ActionID");
1293 const char *file = astman_get_header(m, "File");
1294 const char *options = astman_get_header(m, "Options");
1295 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
1296 struct ast_flags flags = { 0 };
1297 char *uid_channel_var = NULL;
1298 const char *mixmonitor_id = NULL;
1300 char args[PATH_MAX];
1302 if (ast_strlen_zero(name)) {
1303 astman_send_error(s, m, "No channel specified");
1307 c = ast_channel_get_by_name(name);
1309 astman_send_error(s, m, "No such channel");
1313 if (!ast_strlen_zero(options)) {
1314 ast_app_parse_options(mixmonitor_opts, &flags, opts, ast_strdupa(options));
1317 snprintf(args, sizeof(args), "%s,%s", file, options);
1319 res = mixmonitor_exec(c, args);
1321 if (ast_test_flag(&flags, MUXFLAG_UID)) {
1322 uid_channel_var = opts[OPT_ARG_UID];
1323 ast_channel_lock(c);
1324 mixmonitor_id = pbx_builtin_getvar_helper(c, uid_channel_var);
1325 mixmonitor_id = ast_strdupa(S_OR(mixmonitor_id, ""));
1326 ast_channel_unlock(c);
1330 ast_channel_unref(c);
1331 astman_send_error(s, m, "Could not start monitoring channel");
1335 astman_append(s, "Response: Success\r\n");
1337 if (!ast_strlen_zero(id)) {
1338 astman_append(s, "ActionID: %s\r\n", id);
1341 if (!ast_strlen_zero(mixmonitor_id)) {
1342 astman_append(s, "MixMonitorID: %s\r\n", mixmonitor_id);
1345 astman_append(s, "\r\n");
1347 ast_channel_unref(c);
1352 static int manager_stop_mixmonitor(struct mansession *s, const struct message *m)
1354 struct ast_channel *c;
1355 const char *name = astman_get_header(m, "Channel");
1356 const char *id = astman_get_header(m, "ActionID");
1357 const char *mixmonitor_id = astman_get_header(m, "MixMonitorID");
1360 if (ast_strlen_zero(name)) {
1361 astman_send_error(s, m, "No channel specified");
1365 c = ast_channel_get_by_name(name);
1367 astman_send_error(s, m, "No such channel");
1371 res = stop_mixmonitor_full(c, mixmonitor_id);
1373 ast_channel_unref(c);
1374 astman_send_error(s, m, "Could not stop monitoring channel");
1378 astman_append(s, "Response: Success\r\n");
1380 if (!ast_strlen_zero(id)) {
1381 astman_append(s, "ActionID: %s\r\n", id);
1384 astman_append(s, "\r\n");
1386 ast_channel_unref(c);
1391 static int func_mixmonitor_read(struct ast_channel *chan, const char *cmd, char *data,
1392 char *buf, size_t len)
1394 struct ast_datastore *datastore;
1395 struct mixmonitor_ds *ds_data;
1396 AST_DECLARE_APP_ARGS(args,
1401 AST_STANDARD_APP_ARGS(args, data);
1403 if (ast_strlen_zero(args.id) || ast_strlen_zero(args.key)) {
1404 ast_log(LOG_WARNING, "Not enough arguments provided to %s. "
1405 "An ID and key must be provided\n", cmd);
1409 ast_channel_lock(chan);
1410 datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, args.id);
1411 ast_channel_unlock(chan);
1414 ast_log(LOG_WARNING, "Could not find MixMonitor with ID %s\n", args.id);
1418 ds_data = datastore->data;
1420 if (!strcasecmp(args.key, "filename")) {
1421 ast_copy_string(buf, ds_data->filename, len);
1423 ast_log(LOG_WARNING, "Unrecognized %s option %s\n", cmd, args.key);
1429 static struct ast_custom_function mixmonitor_function = {
1430 .name = "MIXMONITOR",
1431 .read = func_mixmonitor_read,
1434 static struct ast_cli_entry cli_mixmonitor[] = {
1435 AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
1438 static int set_mixmonitor_methods(void)
1440 struct ast_mixmonitor_methods mixmonitor_methods = {
1441 .start = start_mixmonitor_callback,
1442 .stop = stop_mixmonitor_callback,
1445 return ast_set_mixmonitor_methods(&mixmonitor_methods);
1448 static int clear_mixmonitor_methods(void)
1450 return ast_clear_mixmonitor_methods();
1453 static int unload_module(void)
1457 ast_cli_unregister_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
1458 res = ast_unregister_application(stop_app);
1459 res |= ast_unregister_application(app);
1460 res |= ast_manager_unregister("MixMonitorMute");
1461 res |= ast_manager_unregister("MixMonitor");
1462 res |= ast_manager_unregister("StopMixMonitor");
1463 res |= ast_custom_function_unregister(&mixmonitor_function);
1464 res |= clear_mixmonitor_methods();
1469 static int load_module(void)
1473 ast_cli_register_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
1474 res = ast_register_application_xml(app, mixmonitor_exec);
1475 res |= ast_register_application_xml(stop_app, stop_mixmonitor_exec);
1476 res |= ast_manager_register_xml("MixMonitorMute", 0, manager_mute_mixmonitor);
1477 res |= ast_manager_register_xml("MixMonitor", 0, manager_mixmonitor);
1478 res |= ast_manager_register_xml("StopMixMonitor", 0, manager_stop_mixmonitor);
1479 res |= ast_custom_function_register(&mixmonitor_function);
1480 res |= set_mixmonitor_methods();
1485 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");