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"
60 #include "asterisk/format_cache.h"
61 #include "asterisk/beep.h"
64 <application name="MixMonitor" language="en_US">
66 Record a call and mix the audio during the recording. Use of StopMixMonitor is required
67 to guarantee the audio file is available for processing during dialplan execution.
70 <parameter name="file" required="true" argsep=".">
71 <argument name="filename" required="true">
72 <para>If <replaceable>filename</replaceable> is an absolute path, uses that path, otherwise
73 creates the file in the configured monitoring directory from <filename>asterisk.conf.</filename></para>
75 <argument name="extension" required="true" />
77 <parameter name="options">
80 <para>Append to the file instead of overwriting it.</para>
83 <para>Only save audio to the file while the channel is bridged.</para>
84 <note><para>If you utilize this option inside a Local channel, you must make sure the Local
85 channel is not optimized away. To do this, be sure to call your Local channel with the
86 <literal>/n</literal> option. For example: Dial(Local/start@mycontext/n)</para></note>
89 <para>Play a periodic beep while this call is being recorded.</para>
90 <argument name="interval"><para>Interval, in seconds. Default is 15.</para></argument>
93 <para>Adjust the <emphasis>heard</emphasis> volume by a factor of <replaceable>x</replaceable>
94 (range <literal>-4</literal> to <literal>4</literal>)</para>
95 <argument name="x" required="true" />
98 <para>Adjust the <emphasis>spoken</emphasis> volume by a factor
99 of <replaceable>x</replaceable> (range <literal>-4</literal> to <literal>4</literal>)</para>
100 <argument name="x" required="true" />
103 <para>Adjust both, <emphasis>heard and spoken</emphasis> volumes by a factor
104 of <replaceable>x</replaceable> (range <literal>-4</literal> to <literal>4</literal>)</para>
105 <argument name="x" required="true" />
108 <argument name="file" required="true" />
109 <para>Use the specified file to record the <emphasis>receive</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="file" required="true" />
115 <para>Use the specified file to record the <emphasis>transmit</emphasis> audio feed.
116 Like with the basic filename argument, if an absolute path isn't given, it will create
117 the file in the configured monitoring directory.</para>
120 <argument name="chanvar" required="true" />
121 <para>Stores the MixMonitor's ID on this channel variable.</para>
124 <para>Play a beep on the channel that starts the recording.</para>
127 <para>Play a beep on the channel that stops the recording.</para>
130 <argument name="mailbox" required="true" />
131 <para>Create a copy of the recording as a voicemail in the indicated <emphasis>mailbox</emphasis>(es)
132 separated by commas eg. m(1111@default,2222@default,...). Folders can be optionally specified using
133 the syntax: mailbox@context/folder</para>
137 <parameter name="command">
138 <para>Will be executed when the recording is over.</para>
139 <para>Any strings matching <literal>^{X}</literal> will be unescaped to <variable>X</variable>.</para>
140 <para>All variables will be evaluated at the time MixMonitor is called.</para>
144 <para>Records the audio on the current channel to the specified file.</para>
145 <para>This application does not automatically answer and should be preceeded by
146 an application such as Answer or Progress().</para>
147 <note><para>MixMonitor runs as an audiohook.</para></note>
149 <variable name="MIXMONITOR_FILENAME">
150 <para>Will contain the filename used to record.</para>
155 <ref type="application">Monitor</ref>
156 <ref type="application">StopMixMonitor</ref>
157 <ref type="application">PauseMonitor</ref>
158 <ref type="application">UnpauseMonitor</ref>
159 <ref type="function">AUDIOHOOK_INHERIT</ref>
162 <application name="StopMixMonitor" language="en_US">
164 Stop recording a call through MixMonitor, and free the recording's file handle.
167 <parameter name="MixMonitorID" required="false">
168 <para>If a valid ID is provided, then this command will stop only that specific
173 <para>Stops the audio recording that was started with a call to <literal>MixMonitor()</literal>
174 on the current channel.</para>
177 <ref type="application">MixMonitor</ref>
180 <manager name="MixMonitorMute" language="en_US">
182 Mute / unMute a Mixmonitor recording.
185 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
186 <parameter name="Channel" required="true">
187 <para>Used to specify the channel to mute.</para>
189 <parameter name="Direction">
190 <para>Which part of the recording to mute: read, write or both (from channel, to channel or both channels).</para>
192 <parameter name="State">
193 <para>Turn mute on or off : 1 to turn on, 0 to turn off.</para>
197 <para>This action may be used to mute a MixMonitor recording.</para>
200 <manager name="MixMonitor" language="en_US">
202 Record a call and mix the audio during the recording. Use of StopMixMonitor is required
203 to guarantee the audio file is available for processing during dialplan execution.
206 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
207 <parameter name="Channel" required="true">
208 <para>Used to specify the channel to record.</para>
210 <parameter name="File">
211 <para>Is the name of the file created in the monitor spool directory.
212 Defaults to the same name as the channel (with slashes replaced with dashes).
213 This argument is optional if you specify to record unidirectional audio with
214 either the r(filename) or t(filename) options in the options field. If
215 neither MIXMONITOR_FILENAME or this parameter is set, the mixed stream won't
218 <parameter name="options">
219 <para>Options that apply to the MixMonitor in the same way as they
220 would apply if invoked from the MixMonitor application. For a list of
221 available options, see the documentation for the mixmonitor application. </para>
223 <parameter name="Command">
224 <para>Will be executed when the recording is over.
225 Any strings matching <literal>^{X}</literal> will be unescaped to <variable>X</variable>.
226 All variables will be evaluated at the time MixMonitor is called.</para>
230 <para>This action records the audio on the current channel to the specified file.</para>
232 <variable name="MIXMONITOR_FILENAME">
233 <para>Will contain the filename used to record the mixed stream.</para>
238 <manager name="StopMixMonitor" language="en_US">
240 Stop recording a call through MixMonitor, and free the recording's file handle.
243 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
244 <parameter name="Channel" required="true">
245 <para>The name of the channel monitored.</para>
247 <parameter name="MixMonitorID" required="false">
248 <para>If a valid ID is provided, then this command will stop only that specific
253 <para>This action stops the audio recording that was started with the <literal>MixMonitor</literal>
254 action on the current channel.</para>
257 <function name="MIXMONITOR" language="en_US">
259 Retrieve data pertaining to specific instances of MixMonitor on a channel.
262 <parameter name="id" required="true">
263 <para>The unique ID of the MixMonitor instance. The unique ID can be retrieved through the channel
264 variable used as an argument to the <replaceable>i</replaceable> option to MixMonitor.</para>
266 <parameter name="key" required="true">
267 <para>The piece of data to retrieve from the MixMonitor.</para>
269 <enum name="filename" />
277 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
279 static const char * const app = "MixMonitor";
281 static const char * const stop_app = "StopMixMonitor";
283 static const char * const mixmonitor_spy_type = "MixMonitor";
287 * \brief This struct is a list item holds data needed to find a vm_recipient within voicemail
289 struct vm_recipient {
290 char mailbox[AST_MAX_CONTEXT];
291 char context[AST_MAX_EXTENSION];
293 AST_LIST_ENTRY(vm_recipient) list;
297 struct ast_audiohook audiohook;
298 struct ast_callid *callid;
301 char *filename_write;
305 struct ast_autochan *autochan;
306 struct mixmonitor_ds *mixmonitor_ds;
308 /* the below string fields describe data used for creating voicemails from the recording */
309 AST_DECLARE_STRING_FIELDS(
310 AST_STRING_FIELD(call_context);
311 AST_STRING_FIELD(call_macrocontext);
312 AST_STRING_FIELD(call_extension);
313 AST_STRING_FIELD(call_callerchan);
314 AST_STRING_FIELD(call_callerid);
318 /* FUTURE DEVELOPMENT NOTICE
319 * recipient_list will need locks if we make it editable after the monitor is started */
320 AST_LIST_HEAD_NOLOCK(, vm_recipient) recipient_list;
323 enum mixmonitor_flags {
324 MUXFLAG_APPEND = (1 << 1),
325 MUXFLAG_BRIDGED = (1 << 2),
326 MUXFLAG_VOLUME = (1 << 3),
327 MUXFLAG_READVOLUME = (1 << 4),
328 MUXFLAG_WRITEVOLUME = (1 << 5),
329 MUXFLAG_READ = (1 << 6),
330 MUXFLAG_WRITE = (1 << 7),
331 MUXFLAG_COMBINED = (1 << 8),
332 MUXFLAG_UID = (1 << 9),
333 MUXFLAG_VMRECIPIENTS = (1 << 10),
334 MUXFLAG_BEEP = (1 << 11),
335 MUXFLAG_BEEP_START = (1 << 12),
336 MUXFLAG_BEEP_STOP = (1 << 13)
339 enum mixmonitor_args {
340 OPT_ARG_READVOLUME = 0,
346 OPT_ARG_VMRECIPIENTS,
347 OPT_ARG_BEEP_INTERVAL,
348 OPT_ARG_ARRAY_SIZE, /* Always last element of the enum */
351 AST_APP_OPTIONS(mixmonitor_opts, {
352 AST_APP_OPTION('a', MUXFLAG_APPEND),
353 AST_APP_OPTION('b', MUXFLAG_BRIDGED),
354 AST_APP_OPTION_ARG('B', MUXFLAG_BEEP, OPT_ARG_BEEP_INTERVAL),
355 AST_APP_OPTION('p', MUXFLAG_BEEP_START),
356 AST_APP_OPTION('P', MUXFLAG_BEEP_STOP),
357 AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
358 AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
359 AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
360 AST_APP_OPTION_ARG('r', MUXFLAG_READ, OPT_ARG_READNAME),
361 AST_APP_OPTION_ARG('t', MUXFLAG_WRITE, OPT_ARG_WRITENAME),
362 AST_APP_OPTION_ARG('i', MUXFLAG_UID, OPT_ARG_UID),
363 AST_APP_OPTION_ARG('m', MUXFLAG_VMRECIPIENTS, OPT_ARG_VMRECIPIENTS),
366 struct mixmonitor_ds {
367 unsigned int destruction_ok;
368 ast_cond_t destruction_condition;
371 /* The filestream is held in the datastore so it can be stopped
372 * immediately during stop_mixmonitor or channel destruction. */
375 struct ast_filestream *fs;
376 struct ast_filestream *fs_read;
377 struct ast_filestream *fs_write;
379 struct ast_audiohook *audiohook;
381 unsigned int samp_rate;
388 * \pre mixmonitor_ds must be locked before calling this function
390 static void mixmonitor_ds_close_fs(struct mixmonitor_ds *mixmonitor_ds)
392 unsigned char quitting = 0;
394 if (mixmonitor_ds->fs) {
396 ast_closestream(mixmonitor_ds->fs);
397 mixmonitor_ds->fs = NULL;
398 ast_verb(2, "MixMonitor close filestream (mixed)\n");
401 if (mixmonitor_ds->fs_read) {
403 ast_closestream(mixmonitor_ds->fs_read);
404 mixmonitor_ds->fs_read = NULL;
405 ast_verb(2, "MixMonitor close filestream (read)\n");
408 if (mixmonitor_ds->fs_write) {
410 ast_closestream(mixmonitor_ds->fs_write);
411 mixmonitor_ds->fs_write = NULL;
412 ast_verb(2, "MixMonitor close filestream (write)\n");
416 mixmonitor_ds->fs_quit = 1;
420 static void mixmonitor_ds_destroy(void *data)
422 struct mixmonitor_ds *mixmonitor_ds = data;
424 ast_mutex_lock(&mixmonitor_ds->lock);
425 mixmonitor_ds->audiohook = NULL;
426 mixmonitor_ds->destruction_ok = 1;
427 ast_free(mixmonitor_ds->filename);
428 ast_free(mixmonitor_ds->beep_id);
429 ast_cond_signal(&mixmonitor_ds->destruction_condition);
430 ast_mutex_unlock(&mixmonitor_ds->lock);
433 static const struct ast_datastore_info mixmonitor_ds_info = {
434 .type = "mixmonitor",
435 .destroy = mixmonitor_ds_destroy,
438 static void destroy_monitor_audiohook(struct mixmonitor *mixmonitor)
440 if (mixmonitor->mixmonitor_ds) {
441 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
442 mixmonitor->mixmonitor_ds->audiohook = NULL;
443 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
445 /* kill the audiohook.*/
446 ast_audiohook_lock(&mixmonitor->audiohook);
447 ast_audiohook_detach(&mixmonitor->audiohook);
448 ast_audiohook_unlock(&mixmonitor->audiohook);
449 ast_audiohook_destroy(&mixmonitor->audiohook);
452 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
459 ast_audiohook_attach(chan, audiohook);
462 ast_channel_lock(chan);
463 if (ast_channel_is_bridged(chan)) {
464 ast_softhangup_nolock(chan, AST_SOFTHANGUP_UNBRIDGE);
466 ast_channel_unlock(chan);
474 * \brief adds recipients to a mixmonitor's recipient list
475 * \param mixmonitor mixmonitor being affected
476 * \param vm_recipients string containing the desired recipients to add
478 static void add_vm_recipients_from_string(struct mixmonitor *mixmonitor, const char *vm_recipients)
480 /* recipients are in a single string with a format format resembling "mailbox@context/INBOX,mailbox2@context2,mailbox3@context3/Work" */
481 char *cur_mailbox = ast_strdupa(vm_recipients);
485 int elements_processed = 0;
487 while (!ast_strlen_zero(cur_mailbox)) {
488 ast_debug(3, "attempting to add next element %d from %s\n", elements_processed, cur_mailbox);
489 if ((next = strchr(cur_mailbox, ',')) || (next = strchr(cur_mailbox, '&'))) {
493 if ((cur_folder = strchr(cur_mailbox, '/'))) {
494 *(cur_folder++) = '\0';
496 cur_folder = "INBOX";
499 if ((cur_context = strchr(cur_mailbox, '@'))) {
500 *(cur_context++) = '\0';
502 cur_context = "default";
505 if (!ast_strlen_zero(cur_mailbox) && !ast_strlen_zero(cur_context)) {
506 struct vm_recipient *recipient;
507 if (!(recipient = ast_malloc(sizeof(*recipient)))) {
508 ast_log(LOG_ERROR, "Failed to allocate recipient. Aborting function.\n");
511 ast_copy_string(recipient->context, cur_context, sizeof(recipient->context));
512 ast_copy_string(recipient->mailbox, cur_mailbox, sizeof(recipient->mailbox));
513 ast_copy_string(recipient->folder, cur_folder, sizeof(recipient->folder));
516 ast_verb(4, "Adding %s@%s to recipient list\n", recipient->mailbox, recipient->context);
517 AST_LIST_INSERT_HEAD(&mixmonitor->recipient_list, recipient, list);
519 ast_log(LOG_ERROR, "Failed to properly parse extension and/or context from element %d of recipient string: %s\n", elements_processed, vm_recipients);
523 elements_processed++;
527 static void clear_mixmonitor_recipient_list(struct mixmonitor *mixmonitor)
529 struct vm_recipient *current;
530 while ((current = AST_LIST_REMOVE_HEAD(&mixmonitor->recipient_list, list))) {
531 /* Clear list element data */
536 #define SAMPLES_PER_FRAME 160
538 static void mixmonitor_free(struct mixmonitor *mixmonitor)
541 if (mixmonitor->mixmonitor_ds) {
542 ast_mutex_destroy(&mixmonitor->mixmonitor_ds->lock);
543 ast_cond_destroy(&mixmonitor->mixmonitor_ds->destruction_condition);
544 ast_free(mixmonitor->mixmonitor_ds);
547 ast_free(mixmonitor->name);
548 ast_free(mixmonitor->post_process);
549 ast_free(mixmonitor->filename);
550 ast_free(mixmonitor->filename_write);
551 ast_free(mixmonitor->filename_read);
553 /* Free everything in the recipient list */
554 clear_mixmonitor_recipient_list(mixmonitor);
556 /* clean stringfields */
557 ast_string_field_free_memory(mixmonitor);
559 if (mixmonitor->callid) {
560 ast_callid_unref(mixmonitor->callid);
562 ast_free(mixmonitor);
568 * \brief Copies the mixmonitor to all voicemail recipients
569 * \param mixmonitor The mixmonitor that needs to forward its file to recipients
570 * \param ext Format of the file that was saved
572 static void copy_to_voicemail(struct mixmonitor *mixmonitor, const char *ext, const char *filename)
574 struct vm_recipient *recipient = NULL;
575 struct ast_vm_recording_data recording_data;
576 if (ast_string_field_init(&recording_data, 512)) {
577 ast_log(LOG_ERROR, "Failed to string_field_init, skipping copy_to_voicemail\n");
581 /* Copy strings to stringfields that will be used for all recipients */
582 ast_string_field_set(&recording_data, recording_file, filename);
583 ast_string_field_set(&recording_data, recording_ext, ext);
584 ast_string_field_set(&recording_data, call_context, mixmonitor->call_context);
585 ast_string_field_set(&recording_data, call_macrocontext, mixmonitor->call_macrocontext);
586 ast_string_field_set(&recording_data, call_extension, mixmonitor->call_extension);
587 ast_string_field_set(&recording_data, call_callerchan, mixmonitor->call_callerchan);
588 ast_string_field_set(&recording_data, call_callerid, mixmonitor->call_callerid);
589 /* and call_priority gets copied too */
590 recording_data.call_priority = mixmonitor->call_priority;
592 AST_LIST_TRAVERSE(&mixmonitor->recipient_list, recipient, list) {
593 /* context, mailbox, and folder need to be set per recipient */
594 ast_string_field_set(&recording_data, context, recipient->context);
595 ast_string_field_set(&recording_data, mailbox, recipient->mailbox);
596 ast_string_field_set(&recording_data, folder, recipient->folder);
598 ast_verb(4, "MixMonitor attempting to send voicemail copy to %s@%s\n", recording_data.mailbox,
599 recording_data.context);
600 ast_app_copy_recording_to_vm(&recording_data);
603 /* Free the string fields for recording_data before exiting the function. */
604 ast_string_field_free_memory(&recording_data);
607 static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename, struct ast_filestream **fs, unsigned int *oflags, int *errflag, char **ext)
609 /* Initialize the file if not already done so */
610 char *last_slash = NULL;
611 if (!ast_strlen_zero(filename)) {
612 if (!*fs && !*errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
613 *oflags = O_CREAT | O_WRONLY;
614 *oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
616 last_slash = strrchr(filename, '/');
618 if ((*ext = strrchr(filename, '.')) && (*ext > last_slash)) {
625 if (!(*fs = ast_writefile(filename, *ext, NULL, *oflags, 0, 0666))) {
626 ast_log(LOG_ERROR, "Cannot open %s.%s\n", filename, *ext);
629 struct ast_filestream *tmp = *fs;
630 mixmonitor->mixmonitor_ds->samp_rate = MAX(mixmonitor->mixmonitor_ds->samp_rate, ast_format_get_sample_rate(tmp->fmt->format));
636 static void *mixmonitor_thread(void *obj)
638 struct mixmonitor *mixmonitor = obj;
640 char *fs_read_ext = "";
641 char *fs_write_ext = "";
643 struct ast_filestream **fs = NULL;
644 struct ast_filestream **fs_read = NULL;
645 struct ast_filestream **fs_write = NULL;
649 struct ast_format *format_slin;
651 /* Keep callid association before any log messages */
652 if (mixmonitor->callid) {
653 ast_callid_threadassoc_add(mixmonitor->callid);
656 ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name);
658 fs = &mixmonitor->mixmonitor_ds->fs;
659 fs_read = &mixmonitor->mixmonitor_ds->fs_read;
660 fs_write = &mixmonitor->mixmonitor_ds->fs_write;
662 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
663 mixmonitor_save_prep(mixmonitor, mixmonitor->filename, fs, &oflags, &errflag, &fs_ext);
664 mixmonitor_save_prep(mixmonitor, mixmonitor->filename_read, fs_read, &oflags, &errflag, &fs_read_ext);
665 mixmonitor_save_prep(mixmonitor, mixmonitor->filename_write, fs_write, &oflags, &errflag, &fs_write_ext);
667 format_slin = ast_format_cache_get_slin_by_rate(mixmonitor->mixmonitor_ds->samp_rate);
669 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
671 /* The audiohook must enter and exit the loop locked */
672 ast_audiohook_lock(&mixmonitor->audiohook);
673 while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING && !mixmonitor->mixmonitor_ds->fs_quit) {
674 struct ast_frame *fr = NULL;
675 struct ast_frame *fr_read = NULL;
676 struct ast_frame *fr_write = NULL;
678 if (!(fr = ast_audiohook_read_frame_all(&mixmonitor->audiohook, SAMPLES_PER_FRAME, format_slin,
679 &fr_read, &fr_write))) {
680 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
682 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
688 /* audiohook lock is not required for the next block.
689 * Unlock it, but remember to lock it before looping or exiting */
690 ast_audiohook_unlock(&mixmonitor->audiohook);
692 if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED)
693 || (mixmonitor->autochan->chan
694 && ast_channel_is_bridged(mixmonitor->autochan->chan))) {
695 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
697 /* Write out the frame(s) */
698 if ((*fs_read) && (fr_read)) {
699 struct ast_frame *cur;
701 for (cur = fr_read; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
702 ast_writestream(*fs_read, cur);
706 if ((*fs_write) && (fr_write)) {
707 struct ast_frame *cur;
709 for (cur = fr_write; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
710 ast_writestream(*fs_write, cur);
715 struct ast_frame *cur;
717 for (cur = fr; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
718 ast_writestream(*fs, cur);
721 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
723 /* All done! free it. */
725 ast_frame_free(fr, 0);
728 ast_frame_free(fr_read, 0);
731 ast_frame_free(fr_write, 0);
738 ast_audiohook_lock(&mixmonitor->audiohook);
742 ast_test_suite_event_notify("MIXMONITOR_END", "Channel: %s\r\n"
744 ast_channel_name(mixmonitor->autochan->chan),
745 mixmonitor->filename);
746 ast_channel_lock(mixmonitor->autochan->chan);
747 if (ast_test_flag(mixmonitor, MUXFLAG_BEEP_STOP)) {
748 ast_stream_and_wait(mixmonitor->autochan->chan, "beep", "");
750 ast_channel_unlock(mixmonitor->autochan->chan);
752 ast_audiohook_unlock(&mixmonitor->audiohook);
754 ast_autochan_destroy(mixmonitor->autochan);
756 /* Datastore cleanup. close the filestream and wait for ds destruction */
757 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
758 mixmonitor_ds_close_fs(mixmonitor->mixmonitor_ds);
759 if (!mixmonitor->mixmonitor_ds->destruction_ok) {
760 ast_cond_wait(&mixmonitor->mixmonitor_ds->destruction_condition, &mixmonitor->mixmonitor_ds->lock);
762 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
764 /* kill the audiohook */
765 destroy_monitor_audiohook(mixmonitor);
767 if (mixmonitor->post_process) {
768 ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
769 ast_safe_system(mixmonitor->post_process);
772 ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
774 if (!AST_LIST_EMPTY(&mixmonitor->recipient_list)) {
775 if (ast_strlen_zero(fs_ext)) {
776 ast_log(LOG_ERROR, "No file extension set for Mixmonitor %s. Skipping copy to voicemail.\n",
779 ast_verb(3, "Copying recordings for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
780 copy_to_voicemail(mixmonitor, fs_ext, mixmonitor->filename);
782 if (!ast_strlen_zero(fs_read_ext)) {
783 ast_verb(3, "Copying read recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
784 copy_to_voicemail(mixmonitor, fs_read_ext, mixmonitor->filename_read);
786 if (!ast_strlen_zero(fs_write_ext)) {
787 ast_verb(3, "Copying write recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
788 copy_to_voicemail(mixmonitor, fs_write_ext, mixmonitor->filename_write);
791 ast_debug(3, "No recipients to forward monitor to, moving on.\n");
794 mixmonitor_free(mixmonitor);
796 ast_module_unref(ast_module_info->self);
800 static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan, char **datastore_id, const char *beep_id)
802 struct ast_datastore *datastore = NULL;
803 struct mixmonitor_ds *mixmonitor_ds;
805 if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
809 if (ast_asprintf(datastore_id, "%p", mixmonitor_ds) == -1) {
810 ast_log(LOG_ERROR, "Failed to allocate memory for MixMonitor ID.\n");
813 ast_mutex_init(&mixmonitor_ds->lock);
814 ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
816 if (!(datastore = ast_datastore_alloc(&mixmonitor_ds_info, *datastore_id))) {
817 ast_mutex_destroy(&mixmonitor_ds->lock);
818 ast_cond_destroy(&mixmonitor_ds->destruction_condition);
819 ast_free(mixmonitor_ds);
823 ast_channel_lock(mixmonitor->autochan->chan);
824 if (ast_test_flag(mixmonitor, MUXFLAG_BEEP_START)) {
825 ast_stream_and_wait(mixmonitor->autochan->chan, "beep", "");
827 ast_channel_unlock(mixmonitor->autochan->chan);
829 mixmonitor_ds->samp_rate = 8000;
830 mixmonitor_ds->audiohook = &mixmonitor->audiohook;
831 mixmonitor_ds->filename = ast_strdup(mixmonitor->filename);
832 if (!ast_strlen_zero(beep_id)) {
833 mixmonitor_ds->beep_id = ast_strdup(beep_id);
835 datastore->data = mixmonitor_ds;
837 ast_channel_lock(chan);
838 ast_channel_datastore_add(chan, datastore);
839 ast_channel_unlock(chan);
841 mixmonitor->mixmonitor_ds = mixmonitor_ds;
845 static int launch_monitor_thread(struct ast_channel *chan, const char *filename,
846 unsigned int flags, int readvol, int writevol,
847 const char *post_process, const char *filename_write,
848 char *filename_read, const char *uid_channel_var,
849 const char *recipients, const char *beep_id)
852 struct mixmonitor *mixmonitor;
853 char postprocess2[1024] = "";
854 char *datastore_id = NULL;
857 /* If a post process system command is given attach it to the structure */
858 if (!ast_strlen_zero(post_process)) {
861 p1 = ast_strdupa(post_process);
862 for (p2 = p1; *p2; p2++) {
863 if (*p2 == '^' && *(p2+1) == '{') {
867 ast_channel_lock(chan);
868 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
869 ast_channel_unlock(chan);
872 /* Pre-allocate mixmonitor structure and spy */
873 if (!(mixmonitor = ast_calloc(1, sizeof(*mixmonitor)))) {
877 /* Now that the struct has been calloced, go ahead and initialize the string fields. */
878 if (ast_string_field_init(mixmonitor, 512)) {
879 mixmonitor_free(mixmonitor);
883 /* Setup the actual spy before creating our thread */
884 if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type, 0)) {
885 mixmonitor_free(mixmonitor);
889 /* Copy over flags and channel name */
890 mixmonitor->flags = flags;
891 if (!(mixmonitor->autochan = ast_autochan_setup(chan))) {
892 mixmonitor_free(mixmonitor);
896 if (!ast_strlen_zero(filename)) {
897 mixmonitor->filename = ast_strdup(filename);
900 if (!ast_strlen_zero(filename_write)) {
901 mixmonitor->filename_write = ast_strdup(filename_write);
904 if (!ast_strlen_zero(filename_read)) {
905 mixmonitor->filename_read = ast_strdup(filename_read);
908 if (setup_mixmonitor_ds(mixmonitor, chan, &datastore_id, beep_id)) {
909 ast_autochan_destroy(mixmonitor->autochan);
910 mixmonitor_free(mixmonitor);
911 ast_free(datastore_id);
915 if (!ast_strlen_zero(uid_channel_var)) {
917 pbx_builtin_setvar_helper(chan, uid_channel_var, datastore_id);
920 ast_free(datastore_id);
922 mixmonitor->name = ast_strdup(ast_channel_name(chan));
924 if (!ast_strlen_zero(postprocess2)) {
925 mixmonitor->post_process = ast_strdup(postprocess2);
928 if (!ast_strlen_zero(recipients)) {
930 struct ast_party_connected_line *connected;
932 ast_channel_lock(chan);
934 /* We use the connected line of the invoking channel for caller ID. */
936 connected = ast_channel_connected(chan);
937 ast_debug(3, "Connected Line CID = %d - %s : %d - %s\n", connected->id.name.valid,
938 connected->id.name.str, connected->id.number.valid,
939 connected->id.number.str);
940 ast_callerid_merge(callerid, sizeof(callerid),
941 S_COR(connected->id.name.valid, connected->id.name.str, NULL),
942 S_COR(connected->id.number.valid, connected->id.number.str, NULL),
945 ast_string_field_set(mixmonitor, call_context, ast_channel_context(chan));
946 ast_string_field_set(mixmonitor, call_macrocontext, ast_channel_macrocontext(chan));
947 ast_string_field_set(mixmonitor, call_extension, ast_channel_exten(chan));
948 ast_string_field_set(mixmonitor, call_callerchan, ast_channel_name(chan));
949 ast_string_field_set(mixmonitor, call_callerid, callerid);
950 mixmonitor->call_priority = ast_channel_priority(chan);
952 ast_channel_unlock(chan);
954 add_vm_recipients_from_string(mixmonitor, recipients);
957 ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
960 mixmonitor->audiohook.options.read_volume = readvol;
962 mixmonitor->audiohook.options.write_volume = writevol;
964 if (startmon(chan, &mixmonitor->audiohook)) {
965 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
966 mixmonitor_spy_type, ast_channel_name(chan));
967 ast_audiohook_destroy(&mixmonitor->audiohook);
968 mixmonitor_free(mixmonitor);
972 /* reference be released at mixmonitor destruction */
973 mixmonitor->callid = ast_read_threadstorage_callid();
975 return ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
978 /* a note on filename_parse: creates directory structure and assigns absolute path from relative paths for filenames */
979 /* requires immediate copying of string from return to retain data since otherwise it will immediately lose scope */
980 static char *filename_parse(char *filename, char *buffer, size_t len)
983 if (ast_strlen_zero(filename)) {
984 ast_log(LOG_WARNING, "No file name was provided for a file save option.\n");
985 } else if (filename[0] != '/') {
987 build = ast_alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(filename) + 3);
988 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, filename);
992 ast_copy_string(buffer, filename, len);
994 if ((slash = strrchr(filename, '/'))) {
997 ast_mkdir(filename, 0777);
1002 static int mixmonitor_exec(struct ast_channel *chan, const char *data)
1004 int x, readvol = 0, writevol = 0;
1005 char *filename_read = NULL;
1006 char *filename_write = NULL;
1007 char filename_buffer[1024] = "";
1008 char *uid_channel_var = NULL;
1009 char beep_id[64] = "";
1011 struct ast_flags flags = { 0 };
1012 char *recipients = NULL;
1014 AST_DECLARE_APP_ARGS(args,
1015 AST_APP_ARG(filename);
1016 AST_APP_ARG(options);
1017 AST_APP_ARG(post_process);
1020 if (ast_strlen_zero(data)) {
1021 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename or ,t(filename) and/or r(filename)\n");
1025 parse = ast_strdupa(data);
1027 AST_STANDARD_APP_ARGS(args, parse);
1030 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
1032 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
1034 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
1035 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
1036 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
1037 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
1038 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
1040 readvol = get_volfactor(x);
1044 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
1045 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
1046 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
1047 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
1048 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
1050 writevol = get_volfactor(x);
1054 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
1055 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
1056 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
1057 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
1058 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
1060 readvol = writevol = get_volfactor(x);
1064 if (ast_test_flag(&flags, MUXFLAG_VMRECIPIENTS)) {
1065 if (ast_strlen_zero(opts[OPT_ARG_VMRECIPIENTS])) {
1066 ast_log(LOG_WARNING, "No voicemail recipients were specified for the vm copy ('m') option.\n");
1068 recipients = ast_strdupa(opts[OPT_ARG_VMRECIPIENTS]);
1072 if (ast_test_flag(&flags, MUXFLAG_WRITE)) {
1073 filename_write = ast_strdupa(filename_parse(opts[OPT_ARG_WRITENAME], filename_buffer, sizeof(filename_buffer)));
1076 if (ast_test_flag(&flags, MUXFLAG_READ)) {
1077 filename_read = ast_strdupa(filename_parse(opts[OPT_ARG_READNAME], filename_buffer, sizeof(filename_buffer)));
1080 if (ast_test_flag(&flags, MUXFLAG_UID)) {
1081 uid_channel_var = opts[OPT_ARG_UID];
1084 if (ast_test_flag(&flags, MUXFLAG_BEEP)) {
1085 const char *interval_str = S_OR(opts[OPT_ARG_BEEP_INTERVAL], "15");
1086 unsigned int interval = 15;
1088 if (sscanf(interval_str, "%30u", &interval) != 1) {
1089 ast_log(LOG_WARNING, "Invalid interval '%s' for periodic beep. Using default of %u\n",
1090 interval_str, interval);
1093 if (ast_beep_start(chan, interval, beep_id, sizeof(beep_id))) {
1094 ast_log(LOG_WARNING, "Unable to enable periodic beep, please ensure func_periodic_hook is loaded.\n");
1099 /* If there are no file writing arguments/options for the mix monitor, send a warning message and return -1 */
1101 if (!ast_test_flag(&flags, MUXFLAG_WRITE) && !ast_test_flag(&flags, MUXFLAG_READ) && ast_strlen_zero(args.filename)) {
1102 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
1106 /* If filename exists, try to create directories for it */
1107 if (!(ast_strlen_zero(args.filename))) {
1108 args.filename = ast_strdupa(filename_parse(args.filename, filename_buffer, sizeof(filename_buffer)));
1111 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
1113 /* If launch_monitor_thread works, the module reference must not be released until it is finished. */
1114 ast_module_ref(ast_module_info->self);
1115 if (launch_monitor_thread(chan,
1126 ast_module_unref(ast_module_info->self);
1132 static int stop_mixmonitor_full(struct ast_channel *chan, const char *data)
1134 struct ast_datastore *datastore = NULL;
1136 struct mixmonitor_ds *mixmonitor_ds;
1137 const char *beep_id = NULL;
1139 AST_DECLARE_APP_ARGS(args,
1140 AST_APP_ARG(mixmonid);
1143 if (!ast_strlen_zero(data)) {
1144 parse = ast_strdupa(data);
1147 AST_STANDARD_APP_ARGS(args, parse);
1149 ast_channel_lock(chan);
1151 datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, args.mixmonid);
1153 ast_channel_unlock(chan);
1156 mixmonitor_ds = datastore->data;
1158 ast_mutex_lock(&mixmonitor_ds->lock);
1160 /* closing the filestream here guarantees the file is available to the dialplan
1161 * after calling StopMixMonitor */
1162 mixmonitor_ds_close_fs(mixmonitor_ds);
1164 /* The mixmonitor thread may be waiting on the audiohook trigger.
1165 * In order to exit from the mixmonitor loop before waiting on channel
1166 * destruction, poke the audiohook trigger. */
1167 if (mixmonitor_ds->audiohook) {
1168 if (mixmonitor_ds->audiohook->status != AST_AUDIOHOOK_STATUS_DONE) {
1169 ast_audiohook_update_status(mixmonitor_ds->audiohook, AST_AUDIOHOOK_STATUS_SHUTDOWN);
1171 ast_audiohook_lock(mixmonitor_ds->audiohook);
1172 ast_cond_signal(&mixmonitor_ds->audiohook->trigger);
1173 ast_audiohook_unlock(mixmonitor_ds->audiohook);
1174 mixmonitor_ds->audiohook = NULL;
1177 if (!ast_strlen_zero(mixmonitor_ds->beep_id)) {
1178 beep_id = ast_strdupa(mixmonitor_ds->beep_id);
1181 ast_mutex_unlock(&mixmonitor_ds->lock);
1183 /* Remove the datastore so the monitor thread can exit */
1184 if (!ast_channel_datastore_remove(chan, datastore)) {
1185 ast_datastore_free(datastore);
1188 ast_channel_unlock(chan);
1190 if (!ast_strlen_zero(beep_id)) {
1191 ast_beep_stop(chan, beep_id);
1197 static int stop_mixmonitor_exec(struct ast_channel *chan, const char *data)
1199 stop_mixmonitor_full(chan, data);
1203 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1205 struct ast_channel *chan;
1206 struct ast_datastore *datastore = NULL;
1207 struct mixmonitor_ds *mixmonitor_ds = NULL;
1211 e->command = "mixmonitor {start|stop|list}";
1213 "Usage: mixmonitor start <chan_name> [args]\n"
1214 " The optional arguments are passed to the MixMonitor application.\n"
1215 " mixmonitor stop <chan_name> [args]\n"
1216 " The optional arguments are passed to the StopMixMonitor application.\n"
1217 " mixmonitor list <chan_name>\n";
1220 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
1224 return CLI_SHOWUSAGE;
1227 if (!(chan = ast_channel_get_by_name_prefix(a->argv[2], strlen(a->argv[2])))) {
1228 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
1229 /* Technically this is a failure, but we don't want 2 errors printing out */
1233 if (!strcasecmp(a->argv[1], "start")) {
1234 mixmonitor_exec(chan, (a->argc >= 4) ? a->argv[3] : "");
1235 } else if (!strcasecmp(a->argv[1], "stop")){
1236 stop_mixmonitor_exec(chan, (a->argc >= 4) ? a->argv[3] : "");
1237 } else if (!strcasecmp(a->argv[1], "list")) {
1238 ast_cli(a->fd, "MixMonitor ID\tFile\tReceive File\tTransmit File\n");
1239 ast_cli(a->fd, "=========================================================================\n");
1240 ast_channel_lock(chan);
1241 AST_LIST_TRAVERSE(ast_channel_datastores(chan), datastore, entry) {
1242 if (datastore->info == &mixmonitor_ds_info) {
1243 char *filename = "";
1244 char *filename_read = "";
1245 char *filename_write = "";
1247 mixmonitor_ds = datastore->data;
1248 if (mixmonitor_ds->fs) {
1249 filename = mixmonitor_ds->fs->filename;
1251 if (mixmonitor_ds->fs_read) {
1252 filename_read = mixmonitor_ds->fs_read->filename;
1254 if (mixmonitor_ds->fs_write) {
1255 filename_write = mixmonitor_ds->fs_write->filename;
1257 ast_cli(a->fd, "%p\t%s\t%s\t%s\n", mixmonitor_ds, filename, filename_read, filename_write);
1260 ast_channel_unlock(chan);
1262 chan = ast_channel_unref(chan);
1263 return CLI_SHOWUSAGE;
1266 chan = ast_channel_unref(chan);
1271 /*! \brief Mute / unmute a MixMonitor channel */
1272 static int manager_mute_mixmonitor(struct mansession *s, const struct message *m)
1274 struct ast_channel *c;
1275 const char *name = astman_get_header(m, "Channel");
1276 const char *id = astman_get_header(m, "ActionID");
1277 const char *state = astman_get_header(m, "State");
1278 const char *direction = astman_get_header(m,"Direction");
1280 enum ast_audiohook_flags flag;
1282 if (ast_strlen_zero(direction)) {
1283 astman_send_error(s, m, "No direction specified. Must be read, write or both");
1287 if (!strcasecmp(direction, "read")) {
1288 flag = AST_AUDIOHOOK_MUTE_READ;
1289 } else if (!strcasecmp(direction, "write")) {
1290 flag = AST_AUDIOHOOK_MUTE_WRITE;
1291 } else if (!strcasecmp(direction, "both")) {
1292 flag = AST_AUDIOHOOK_MUTE_READ | AST_AUDIOHOOK_MUTE_WRITE;
1294 astman_send_error(s, m, "Invalid direction specified. Must be read, write or both");
1298 if (ast_strlen_zero(name)) {
1299 astman_send_error(s, m, "No channel specified");
1303 if (ast_strlen_zero(state)) {
1304 astman_send_error(s, m, "No state specified");
1308 clearmute = ast_false(state);
1310 c = ast_channel_get_by_name(name);
1312 astman_send_error(s, m, "No such channel");
1316 if (ast_audiohook_set_mute(c, mixmonitor_spy_type, flag, clearmute)) {
1317 ast_channel_unref(c);
1318 astman_send_error(s, m, "Cannot set mute flag");
1322 astman_append(s, "Response: Success\r\n");
1324 if (!ast_strlen_zero(id)) {
1325 astman_append(s, "ActionID: %s\r\n", id);
1328 astman_append(s, "\r\n");
1330 ast_channel_unref(c);
1335 static int start_mixmonitor_callback(struct ast_channel *chan, const char *filename, const char *options)
1337 char args[PATH_MAX];
1339 if (ast_strlen_zero(options)) {
1340 snprintf(args, sizeof(args), "%s", filename);
1342 snprintf(args, sizeof(args), "%s,%s", filename, options);
1345 return mixmonitor_exec(chan, args);
1348 static int stop_mixmonitor_callback(struct ast_channel *chan, const char *mixmonitor_id)
1350 return stop_mixmonitor_full(chan, mixmonitor_id);
1353 static int manager_mixmonitor(struct mansession *s, const struct message *m)
1355 struct ast_channel *c;
1356 const char *name = astman_get_header(m, "Channel");
1357 const char *id = astman_get_header(m, "ActionID");
1358 const char *file = astman_get_header(m, "File");
1359 const char *options = astman_get_header(m, "Options");
1360 const char *command = astman_get_header(m, "Command");
1361 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
1362 struct ast_flags flags = { 0 };
1363 char *uid_channel_var = NULL;
1364 const char *mixmonitor_id = NULL;
1366 char args[PATH_MAX];
1368 if (ast_strlen_zero(name)) {
1369 astman_send_error(s, m, "No channel specified");
1373 c = ast_channel_get_by_name(name);
1375 astman_send_error(s, m, "No such channel");
1379 if (!ast_strlen_zero(options)) {
1380 ast_app_parse_options(mixmonitor_opts, &flags, opts, ast_strdupa(options));
1383 snprintf(args, sizeof(args), "%s,%s,%s", file, options, command);
1385 res = mixmonitor_exec(c, args);
1387 if (ast_test_flag(&flags, MUXFLAG_UID)) {
1388 uid_channel_var = opts[OPT_ARG_UID];
1389 ast_channel_lock(c);
1390 mixmonitor_id = pbx_builtin_getvar_helper(c, uid_channel_var);
1391 mixmonitor_id = ast_strdupa(S_OR(mixmonitor_id, ""));
1392 ast_channel_unlock(c);
1396 ast_channel_unref(c);
1397 astman_send_error(s, m, "Could not start monitoring channel");
1401 astman_append(s, "Response: Success\r\n");
1403 if (!ast_strlen_zero(id)) {
1404 astman_append(s, "ActionID: %s\r\n", id);
1407 if (!ast_strlen_zero(mixmonitor_id)) {
1408 astman_append(s, "MixMonitorID: %s\r\n", mixmonitor_id);
1411 astman_append(s, "\r\n");
1413 ast_channel_unref(c);
1418 static int manager_stop_mixmonitor(struct mansession *s, const struct message *m)
1420 struct ast_channel *c;
1421 const char *name = astman_get_header(m, "Channel");
1422 const char *id = astman_get_header(m, "ActionID");
1423 const char *mixmonitor_id = astman_get_header(m, "MixMonitorID");
1426 if (ast_strlen_zero(name)) {
1427 astman_send_error(s, m, "No channel specified");
1431 c = ast_channel_get_by_name(name);
1433 astman_send_error(s, m, "No such channel");
1437 res = stop_mixmonitor_full(c, mixmonitor_id);
1439 ast_channel_unref(c);
1440 astman_send_error(s, m, "Could not stop monitoring channel");
1444 astman_append(s, "Response: Success\r\n");
1446 if (!ast_strlen_zero(id)) {
1447 astman_append(s, "ActionID: %s\r\n", id);
1450 astman_append(s, "\r\n");
1452 ast_channel_unref(c);
1457 static int func_mixmonitor_read(struct ast_channel *chan, const char *cmd, char *data,
1458 char *buf, size_t len)
1460 struct ast_datastore *datastore;
1461 struct mixmonitor_ds *ds_data;
1462 AST_DECLARE_APP_ARGS(args,
1467 AST_STANDARD_APP_ARGS(args, data);
1469 if (ast_strlen_zero(args.id) || ast_strlen_zero(args.key)) {
1470 ast_log(LOG_WARNING, "Not enough arguments provided to %s. "
1471 "An ID and key must be provided\n", cmd);
1475 ast_channel_lock(chan);
1476 datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, args.id);
1477 ast_channel_unlock(chan);
1480 ast_log(LOG_WARNING, "Could not find MixMonitor with ID %s\n", args.id);
1484 ds_data = datastore->data;
1486 if (!strcasecmp(args.key, "filename")) {
1487 ast_copy_string(buf, ds_data->filename, len);
1489 ast_log(LOG_WARNING, "Unrecognized %s option %s\n", cmd, args.key);
1495 static struct ast_custom_function mixmonitor_function = {
1496 .name = "MIXMONITOR",
1497 .read = func_mixmonitor_read,
1500 static struct ast_cli_entry cli_mixmonitor[] = {
1501 AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
1504 static int set_mixmonitor_methods(void)
1506 struct ast_mixmonitor_methods mixmonitor_methods = {
1507 .start = start_mixmonitor_callback,
1508 .stop = stop_mixmonitor_callback,
1511 return ast_set_mixmonitor_methods(&mixmonitor_methods);
1514 static int clear_mixmonitor_methods(void)
1516 return ast_clear_mixmonitor_methods();
1519 static int unload_module(void)
1523 ast_cli_unregister_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
1524 res = ast_unregister_application(stop_app);
1525 res |= ast_unregister_application(app);
1526 res |= ast_manager_unregister("MixMonitorMute");
1527 res |= ast_manager_unregister("MixMonitor");
1528 res |= ast_manager_unregister("StopMixMonitor");
1529 res |= ast_custom_function_unregister(&mixmonitor_function);
1530 res |= clear_mixmonitor_methods();
1535 static int load_module(void)
1539 ast_cli_register_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
1540 res = ast_register_application_xml(app, mixmonitor_exec);
1541 res |= ast_register_application_xml(stop_app, stop_mixmonitor_exec);
1542 res |= ast_manager_register_xml("MixMonitorMute", EVENT_FLAG_SYSTEM | EVENT_FLAG_CALL, manager_mute_mixmonitor);
1543 res |= ast_manager_register_xml("MixMonitor", EVENT_FLAG_SYSTEM, manager_mixmonitor);
1544 res |= ast_manager_register_xml("StopMixMonitor", EVENT_FLAG_SYSTEM | EVENT_FLAG_CALL, manager_stop_mixmonitor);
1545 res |= ast_custom_function_register(&mixmonitor_function);
1546 res |= set_mixmonitor_methods();
1551 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");