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_channel_set_unbridged_nolock(chan, 1);
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);
747 ast_audiohook_unlock(&mixmonitor->audiohook);
749 ast_channel_lock(mixmonitor->autochan->chan);
750 if (ast_test_flag(mixmonitor, MUXFLAG_BEEP_STOP)) {
751 ast_stream_and_wait(mixmonitor->autochan->chan, "beep", "");
753 ast_channel_unlock(mixmonitor->autochan->chan);
755 ast_autochan_destroy(mixmonitor->autochan);
757 /* Datastore cleanup. close the filestream and wait for ds destruction */
758 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
759 mixmonitor_ds_close_fs(mixmonitor->mixmonitor_ds);
760 if (!mixmonitor->mixmonitor_ds->destruction_ok) {
761 ast_cond_wait(&mixmonitor->mixmonitor_ds->destruction_condition, &mixmonitor->mixmonitor_ds->lock);
763 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
765 /* kill the audiohook */
766 destroy_monitor_audiohook(mixmonitor);
768 if (mixmonitor->post_process) {
769 ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
770 ast_safe_system(mixmonitor->post_process);
773 ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
775 if (!AST_LIST_EMPTY(&mixmonitor->recipient_list)) {
776 if (ast_strlen_zero(fs_ext)) {
777 ast_log(LOG_ERROR, "No file extension set for Mixmonitor %s. Skipping copy to voicemail.\n",
780 ast_verb(3, "Copying recordings for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
781 copy_to_voicemail(mixmonitor, fs_ext, mixmonitor->filename);
783 if (!ast_strlen_zero(fs_read_ext)) {
784 ast_verb(3, "Copying read recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
785 copy_to_voicemail(mixmonitor, fs_read_ext, mixmonitor->filename_read);
787 if (!ast_strlen_zero(fs_write_ext)) {
788 ast_verb(3, "Copying write recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
789 copy_to_voicemail(mixmonitor, fs_write_ext, mixmonitor->filename_write);
792 ast_debug(3, "No recipients to forward monitor to, moving on.\n");
795 mixmonitor_free(mixmonitor);
797 ast_module_unref(ast_module_info->self);
801 static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan, char **datastore_id, const char *beep_id)
803 struct ast_datastore *datastore = NULL;
804 struct mixmonitor_ds *mixmonitor_ds;
806 if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
810 if (ast_asprintf(datastore_id, "%p", mixmonitor_ds) == -1) {
811 ast_log(LOG_ERROR, "Failed to allocate memory for MixMonitor ID.\n");
814 ast_mutex_init(&mixmonitor_ds->lock);
815 ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
817 if (!(datastore = ast_datastore_alloc(&mixmonitor_ds_info, *datastore_id))) {
818 ast_mutex_destroy(&mixmonitor_ds->lock);
819 ast_cond_destroy(&mixmonitor_ds->destruction_condition);
820 ast_free(mixmonitor_ds);
824 ast_channel_lock(mixmonitor->autochan->chan);
825 if (ast_test_flag(mixmonitor, MUXFLAG_BEEP_START)) {
826 ast_stream_and_wait(mixmonitor->autochan->chan, "beep", "");
828 ast_channel_unlock(mixmonitor->autochan->chan);
830 mixmonitor_ds->samp_rate = 8000;
831 mixmonitor_ds->audiohook = &mixmonitor->audiohook;
832 mixmonitor_ds->filename = ast_strdup(mixmonitor->filename);
833 if (!ast_strlen_zero(beep_id)) {
834 mixmonitor_ds->beep_id = ast_strdup(beep_id);
836 datastore->data = mixmonitor_ds;
838 ast_channel_lock(chan);
839 ast_channel_datastore_add(chan, datastore);
840 ast_channel_unlock(chan);
842 mixmonitor->mixmonitor_ds = mixmonitor_ds;
846 static int launch_monitor_thread(struct ast_channel *chan, const char *filename,
847 unsigned int flags, int readvol, int writevol,
848 const char *post_process, const char *filename_write,
849 char *filename_read, const char *uid_channel_var,
850 const char *recipients, const char *beep_id)
853 struct mixmonitor *mixmonitor;
854 char postprocess2[1024] = "";
855 char *datastore_id = NULL;
858 /* If a post process system command is given attach it to the structure */
859 if (!ast_strlen_zero(post_process)) {
862 p1 = ast_strdupa(post_process);
863 for (p2 = p1; *p2; p2++) {
864 if (*p2 == '^' && *(p2+1) == '{') {
868 ast_channel_lock(chan);
869 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
870 ast_channel_unlock(chan);
873 /* Pre-allocate mixmonitor structure and spy */
874 if (!(mixmonitor = ast_calloc(1, sizeof(*mixmonitor)))) {
878 /* Now that the struct has been calloced, go ahead and initialize the string fields. */
879 if (ast_string_field_init(mixmonitor, 512)) {
880 mixmonitor_free(mixmonitor);
884 /* Setup the actual spy before creating our thread */
885 if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type, 0)) {
886 mixmonitor_free(mixmonitor);
890 /* Copy over flags and channel name */
891 mixmonitor->flags = flags;
892 if (!(mixmonitor->autochan = ast_autochan_setup(chan))) {
893 mixmonitor_free(mixmonitor);
897 if (!ast_strlen_zero(filename)) {
898 mixmonitor->filename = ast_strdup(filename);
901 if (!ast_strlen_zero(filename_write)) {
902 mixmonitor->filename_write = ast_strdup(filename_write);
905 if (!ast_strlen_zero(filename_read)) {
906 mixmonitor->filename_read = ast_strdup(filename_read);
909 if (setup_mixmonitor_ds(mixmonitor, chan, &datastore_id, beep_id)) {
910 ast_autochan_destroy(mixmonitor->autochan);
911 mixmonitor_free(mixmonitor);
912 ast_free(datastore_id);
916 if (!ast_strlen_zero(uid_channel_var)) {
918 pbx_builtin_setvar_helper(chan, uid_channel_var, datastore_id);
921 ast_free(datastore_id);
923 mixmonitor->name = ast_strdup(ast_channel_name(chan));
925 if (!ast_strlen_zero(postprocess2)) {
926 mixmonitor->post_process = ast_strdup(postprocess2);
929 if (!ast_strlen_zero(recipients)) {
931 struct ast_party_connected_line *connected;
933 ast_channel_lock(chan);
935 /* We use the connected line of the invoking channel for caller ID. */
937 connected = ast_channel_connected(chan);
938 ast_debug(3, "Connected Line CID = %d - %s : %d - %s\n", connected->id.name.valid,
939 connected->id.name.str, connected->id.number.valid,
940 connected->id.number.str);
941 ast_callerid_merge(callerid, sizeof(callerid),
942 S_COR(connected->id.name.valid, connected->id.name.str, NULL),
943 S_COR(connected->id.number.valid, connected->id.number.str, NULL),
946 ast_string_field_set(mixmonitor, call_context, ast_channel_context(chan));
947 ast_string_field_set(mixmonitor, call_macrocontext, ast_channel_macrocontext(chan));
948 ast_string_field_set(mixmonitor, call_extension, ast_channel_exten(chan));
949 ast_string_field_set(mixmonitor, call_callerchan, ast_channel_name(chan));
950 ast_string_field_set(mixmonitor, call_callerid, callerid);
951 mixmonitor->call_priority = ast_channel_priority(chan);
953 ast_channel_unlock(chan);
955 add_vm_recipients_from_string(mixmonitor, recipients);
958 ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
961 mixmonitor->audiohook.options.read_volume = readvol;
963 mixmonitor->audiohook.options.write_volume = writevol;
965 if (startmon(chan, &mixmonitor->audiohook)) {
966 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
967 mixmonitor_spy_type, ast_channel_name(chan));
968 ast_audiohook_destroy(&mixmonitor->audiohook);
969 mixmonitor_free(mixmonitor);
973 /* reference be released at mixmonitor destruction */
974 mixmonitor->callid = ast_read_threadstorage_callid();
976 return ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
979 /* a note on filename_parse: creates directory structure and assigns absolute path from relative paths for filenames */
980 /* requires immediate copying of string from return to retain data since otherwise it will immediately lose scope */
981 static char *filename_parse(char *filename, char *buffer, size_t len)
984 if (ast_strlen_zero(filename)) {
985 ast_log(LOG_WARNING, "No file name was provided for a file save option.\n");
986 } else if (filename[0] != '/') {
988 build = ast_alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(filename) + 3);
989 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, filename);
993 ast_copy_string(buffer, filename, len);
995 if ((slash = strrchr(filename, '/'))) {
998 ast_mkdir(filename, 0777);
1003 static int mixmonitor_exec(struct ast_channel *chan, const char *data)
1005 int x, readvol = 0, writevol = 0;
1006 char *filename_read = NULL;
1007 char *filename_write = NULL;
1008 char filename_buffer[1024] = "";
1009 char *uid_channel_var = NULL;
1010 char beep_id[64] = "";
1012 struct ast_flags flags = { 0 };
1013 char *recipients = NULL;
1015 AST_DECLARE_APP_ARGS(args,
1016 AST_APP_ARG(filename);
1017 AST_APP_ARG(options);
1018 AST_APP_ARG(post_process);
1021 if (ast_strlen_zero(data)) {
1022 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename or ,t(filename) and/or r(filename)\n");
1026 parse = ast_strdupa(data);
1028 AST_STANDARD_APP_ARGS(args, parse);
1031 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
1033 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
1035 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
1036 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
1037 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
1038 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
1039 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
1041 readvol = get_volfactor(x);
1045 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
1046 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
1047 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
1048 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
1049 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
1051 writevol = get_volfactor(x);
1055 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
1056 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
1057 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
1058 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
1059 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
1061 readvol = writevol = get_volfactor(x);
1065 if (ast_test_flag(&flags, MUXFLAG_VMRECIPIENTS)) {
1066 if (ast_strlen_zero(opts[OPT_ARG_VMRECIPIENTS])) {
1067 ast_log(LOG_WARNING, "No voicemail recipients were specified for the vm copy ('m') option.\n");
1069 recipients = ast_strdupa(opts[OPT_ARG_VMRECIPIENTS]);
1073 if (ast_test_flag(&flags, MUXFLAG_WRITE)) {
1074 filename_write = ast_strdupa(filename_parse(opts[OPT_ARG_WRITENAME], filename_buffer, sizeof(filename_buffer)));
1077 if (ast_test_flag(&flags, MUXFLAG_READ)) {
1078 filename_read = ast_strdupa(filename_parse(opts[OPT_ARG_READNAME], filename_buffer, sizeof(filename_buffer)));
1081 if (ast_test_flag(&flags, MUXFLAG_UID)) {
1082 uid_channel_var = opts[OPT_ARG_UID];
1085 if (ast_test_flag(&flags, MUXFLAG_BEEP)) {
1086 const char *interval_str = S_OR(opts[OPT_ARG_BEEP_INTERVAL], "15");
1087 unsigned int interval = 15;
1089 if (sscanf(interval_str, "%30u", &interval) != 1) {
1090 ast_log(LOG_WARNING, "Invalid interval '%s' for periodic beep. Using default of %u\n",
1091 interval_str, interval);
1094 if (ast_beep_start(chan, interval, beep_id, sizeof(beep_id))) {
1095 ast_log(LOG_WARNING, "Unable to enable periodic beep, please ensure func_periodic_hook is loaded.\n");
1100 /* If there are no file writing arguments/options for the mix monitor, send a warning message and return -1 */
1102 if (!ast_test_flag(&flags, MUXFLAG_WRITE) && !ast_test_flag(&flags, MUXFLAG_READ) && ast_strlen_zero(args.filename)) {
1103 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
1107 /* If filename exists, try to create directories for it */
1108 if (!(ast_strlen_zero(args.filename))) {
1109 args.filename = ast_strdupa(filename_parse(args.filename, filename_buffer, sizeof(filename_buffer)));
1112 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
1114 /* If launch_monitor_thread works, the module reference must not be released until it is finished. */
1115 ast_module_ref(ast_module_info->self);
1116 if (launch_monitor_thread(chan,
1127 ast_module_unref(ast_module_info->self);
1133 static int stop_mixmonitor_full(struct ast_channel *chan, const char *data)
1135 struct ast_datastore *datastore = NULL;
1137 struct mixmonitor_ds *mixmonitor_ds;
1138 const char *beep_id = NULL;
1140 AST_DECLARE_APP_ARGS(args,
1141 AST_APP_ARG(mixmonid);
1144 if (!ast_strlen_zero(data)) {
1145 parse = ast_strdupa(data);
1148 AST_STANDARD_APP_ARGS(args, parse);
1150 ast_channel_lock(chan);
1152 datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, args.mixmonid);
1154 ast_channel_unlock(chan);
1157 mixmonitor_ds = datastore->data;
1159 ast_mutex_lock(&mixmonitor_ds->lock);
1161 /* closing the filestream here guarantees the file is available to the dialplan
1162 * after calling StopMixMonitor */
1163 mixmonitor_ds_close_fs(mixmonitor_ds);
1165 /* The mixmonitor thread may be waiting on the audiohook trigger.
1166 * In order to exit from the mixmonitor loop before waiting on channel
1167 * destruction, poke the audiohook trigger. */
1168 if (mixmonitor_ds->audiohook) {
1169 if (mixmonitor_ds->audiohook->status != AST_AUDIOHOOK_STATUS_DONE) {
1170 ast_audiohook_update_status(mixmonitor_ds->audiohook, AST_AUDIOHOOK_STATUS_SHUTDOWN);
1172 ast_audiohook_lock(mixmonitor_ds->audiohook);
1173 ast_cond_signal(&mixmonitor_ds->audiohook->trigger);
1174 ast_audiohook_unlock(mixmonitor_ds->audiohook);
1175 mixmonitor_ds->audiohook = NULL;
1178 if (!ast_strlen_zero(mixmonitor_ds->beep_id)) {
1179 beep_id = ast_strdupa(mixmonitor_ds->beep_id);
1182 ast_mutex_unlock(&mixmonitor_ds->lock);
1184 /* Remove the datastore so the monitor thread can exit */
1185 if (!ast_channel_datastore_remove(chan, datastore)) {
1186 ast_datastore_free(datastore);
1189 ast_channel_unlock(chan);
1191 if (!ast_strlen_zero(beep_id)) {
1192 ast_beep_stop(chan, beep_id);
1198 static int stop_mixmonitor_exec(struct ast_channel *chan, const char *data)
1200 stop_mixmonitor_full(chan, data);
1204 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1206 struct ast_channel *chan;
1207 struct ast_datastore *datastore = NULL;
1208 struct mixmonitor_ds *mixmonitor_ds = NULL;
1212 e->command = "mixmonitor {start|stop|list}";
1214 "Usage: mixmonitor start <chan_name> [args]\n"
1215 " The optional arguments are passed to the MixMonitor application.\n"
1216 " mixmonitor stop <chan_name> [args]\n"
1217 " The optional arguments are passed to the StopMixMonitor application.\n"
1218 " mixmonitor list <chan_name>\n";
1221 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
1225 return CLI_SHOWUSAGE;
1228 if (!(chan = ast_channel_get_by_name_prefix(a->argv[2], strlen(a->argv[2])))) {
1229 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
1230 /* Technically this is a failure, but we don't want 2 errors printing out */
1234 if (!strcasecmp(a->argv[1], "start")) {
1235 mixmonitor_exec(chan, (a->argc >= 4) ? a->argv[3] : "");
1236 } else if (!strcasecmp(a->argv[1], "stop")){
1237 stop_mixmonitor_exec(chan, (a->argc >= 4) ? a->argv[3] : "");
1238 } else if (!strcasecmp(a->argv[1], "list")) {
1239 ast_cli(a->fd, "MixMonitor ID\tFile\tReceive File\tTransmit File\n");
1240 ast_cli(a->fd, "=========================================================================\n");
1241 ast_channel_lock(chan);
1242 AST_LIST_TRAVERSE(ast_channel_datastores(chan), datastore, entry) {
1243 if (datastore->info == &mixmonitor_ds_info) {
1244 char *filename = "";
1245 char *filename_read = "";
1246 char *filename_write = "";
1248 mixmonitor_ds = datastore->data;
1249 if (mixmonitor_ds->fs) {
1250 filename = mixmonitor_ds->fs->filename;
1252 if (mixmonitor_ds->fs_read) {
1253 filename_read = mixmonitor_ds->fs_read->filename;
1255 if (mixmonitor_ds->fs_write) {
1256 filename_write = mixmonitor_ds->fs_write->filename;
1258 ast_cli(a->fd, "%p\t%s\t%s\t%s\n", mixmonitor_ds, filename, filename_read, filename_write);
1261 ast_channel_unlock(chan);
1263 chan = ast_channel_unref(chan);
1264 return CLI_SHOWUSAGE;
1267 chan = ast_channel_unref(chan);
1272 /*! \brief Mute / unmute a MixMonitor channel */
1273 static int manager_mute_mixmonitor(struct mansession *s, const struct message *m)
1275 struct ast_channel *c;
1276 const char *name = astman_get_header(m, "Channel");
1277 const char *id = astman_get_header(m, "ActionID");
1278 const char *state = astman_get_header(m, "State");
1279 const char *direction = astman_get_header(m,"Direction");
1281 enum ast_audiohook_flags flag;
1283 if (ast_strlen_zero(direction)) {
1284 astman_send_error(s, m, "No direction specified. Must be read, write or both");
1288 if (!strcasecmp(direction, "read")) {
1289 flag = AST_AUDIOHOOK_MUTE_READ;
1290 } else if (!strcasecmp(direction, "write")) {
1291 flag = AST_AUDIOHOOK_MUTE_WRITE;
1292 } else if (!strcasecmp(direction, "both")) {
1293 flag = AST_AUDIOHOOK_MUTE_READ | AST_AUDIOHOOK_MUTE_WRITE;
1295 astman_send_error(s, m, "Invalid direction specified. Must be read, write or both");
1299 if (ast_strlen_zero(name)) {
1300 astman_send_error(s, m, "No channel specified");
1304 if (ast_strlen_zero(state)) {
1305 astman_send_error(s, m, "No state specified");
1309 clearmute = ast_false(state);
1311 c = ast_channel_get_by_name(name);
1313 astman_send_error(s, m, "No such channel");
1317 if (ast_audiohook_set_mute(c, mixmonitor_spy_type, flag, clearmute)) {
1318 ast_channel_unref(c);
1319 astman_send_error(s, m, "Cannot set mute flag");
1323 astman_append(s, "Response: Success\r\n");
1325 if (!ast_strlen_zero(id)) {
1326 astman_append(s, "ActionID: %s\r\n", id);
1329 astman_append(s, "\r\n");
1331 ast_channel_unref(c);
1336 static int start_mixmonitor_callback(struct ast_channel *chan, const char *filename, const char *options)
1338 char args[PATH_MAX];
1340 if (ast_strlen_zero(options)) {
1341 snprintf(args, sizeof(args), "%s", filename);
1343 snprintf(args, sizeof(args), "%s,%s", filename, options);
1346 return mixmonitor_exec(chan, args);
1349 static int stop_mixmonitor_callback(struct ast_channel *chan, const char *mixmonitor_id)
1351 return stop_mixmonitor_full(chan, mixmonitor_id);
1354 static int manager_mixmonitor(struct mansession *s, const struct message *m)
1356 struct ast_channel *c;
1357 const char *name = astman_get_header(m, "Channel");
1358 const char *id = astman_get_header(m, "ActionID");
1359 const char *file = astman_get_header(m, "File");
1360 const char *options = astman_get_header(m, "Options");
1361 const char *command = astman_get_header(m, "Command");
1362 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
1363 struct ast_flags flags = { 0 };
1364 char *uid_channel_var = NULL;
1365 const char *mixmonitor_id = NULL;
1367 char args[PATH_MAX];
1369 if (ast_strlen_zero(name)) {
1370 astman_send_error(s, m, "No channel specified");
1374 c = ast_channel_get_by_name(name);
1376 astman_send_error(s, m, "No such channel");
1380 if (!ast_strlen_zero(options)) {
1381 ast_app_parse_options(mixmonitor_opts, &flags, opts, ast_strdupa(options));
1384 snprintf(args, sizeof(args), "%s,%s,%s", file, options, command);
1386 res = mixmonitor_exec(c, args);
1388 if (ast_test_flag(&flags, MUXFLAG_UID)) {
1389 uid_channel_var = opts[OPT_ARG_UID];
1390 ast_channel_lock(c);
1391 mixmonitor_id = pbx_builtin_getvar_helper(c, uid_channel_var);
1392 mixmonitor_id = ast_strdupa(S_OR(mixmonitor_id, ""));
1393 ast_channel_unlock(c);
1397 ast_channel_unref(c);
1398 astman_send_error(s, m, "Could not start monitoring channel");
1402 astman_append(s, "Response: Success\r\n");
1404 if (!ast_strlen_zero(id)) {
1405 astman_append(s, "ActionID: %s\r\n", id);
1408 if (!ast_strlen_zero(mixmonitor_id)) {
1409 astman_append(s, "MixMonitorID: %s\r\n", mixmonitor_id);
1412 astman_append(s, "\r\n");
1414 ast_channel_unref(c);
1419 static int manager_stop_mixmonitor(struct mansession *s, const struct message *m)
1421 struct ast_channel *c;
1422 const char *name = astman_get_header(m, "Channel");
1423 const char *id = astman_get_header(m, "ActionID");
1424 const char *mixmonitor_id = astman_get_header(m, "MixMonitorID");
1427 if (ast_strlen_zero(name)) {
1428 astman_send_error(s, m, "No channel specified");
1432 c = ast_channel_get_by_name(name);
1434 astman_send_error(s, m, "No such channel");
1438 res = stop_mixmonitor_full(c, mixmonitor_id);
1440 ast_channel_unref(c);
1441 astman_send_error(s, m, "Could not stop monitoring channel");
1445 astman_append(s, "Response: Success\r\n");
1447 if (!ast_strlen_zero(id)) {
1448 astman_append(s, "ActionID: %s\r\n", id);
1451 astman_append(s, "\r\n");
1453 ast_channel_unref(c);
1458 static int func_mixmonitor_read(struct ast_channel *chan, const char *cmd, char *data,
1459 char *buf, size_t len)
1461 struct ast_datastore *datastore;
1462 struct mixmonitor_ds *ds_data;
1463 AST_DECLARE_APP_ARGS(args,
1468 AST_STANDARD_APP_ARGS(args, data);
1470 if (ast_strlen_zero(args.id) || ast_strlen_zero(args.key)) {
1471 ast_log(LOG_WARNING, "Not enough arguments provided to %s. "
1472 "An ID and key must be provided\n", cmd);
1476 ast_channel_lock(chan);
1477 datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, args.id);
1478 ast_channel_unlock(chan);
1481 ast_log(LOG_WARNING, "Could not find MixMonitor with ID %s\n", args.id);
1485 ds_data = datastore->data;
1487 if (!strcasecmp(args.key, "filename")) {
1488 ast_copy_string(buf, ds_data->filename, len);
1490 ast_log(LOG_WARNING, "Unrecognized %s option %s\n", cmd, args.key);
1496 static struct ast_custom_function mixmonitor_function = {
1497 .name = "MIXMONITOR",
1498 .read = func_mixmonitor_read,
1501 static struct ast_cli_entry cli_mixmonitor[] = {
1502 AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
1505 static int set_mixmonitor_methods(void)
1507 struct ast_mixmonitor_methods mixmonitor_methods = {
1508 .start = start_mixmonitor_callback,
1509 .stop = stop_mixmonitor_callback,
1512 return ast_set_mixmonitor_methods(&mixmonitor_methods);
1515 static int clear_mixmonitor_methods(void)
1517 return ast_clear_mixmonitor_methods();
1520 static int unload_module(void)
1524 ast_cli_unregister_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
1525 res = ast_unregister_application(stop_app);
1526 res |= ast_unregister_application(app);
1527 res |= ast_manager_unregister("MixMonitorMute");
1528 res |= ast_manager_unregister("MixMonitor");
1529 res |= ast_manager_unregister("StopMixMonitor");
1530 res |= ast_custom_function_unregister(&mixmonitor_function);
1531 res |= clear_mixmonitor_methods();
1536 static int load_module(void)
1540 ast_cli_register_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
1541 res = ast_register_application_xml(app, mixmonitor_exec);
1542 res |= ast_register_application_xml(stop_app, stop_mixmonitor_exec);
1543 res |= ast_manager_register_xml("MixMonitorMute", EVENT_FLAG_SYSTEM | EVENT_FLAG_CALL, manager_mute_mixmonitor);
1544 res |= ast_manager_register_xml("MixMonitor", EVENT_FLAG_SYSTEM, manager_mixmonitor);
1545 res |= ast_manager_register_xml("StopMixMonitor", EVENT_FLAG_SYSTEM | EVENT_FLAG_CALL, manager_stop_mixmonitor);
1546 res |= ast_custom_function_register(&mixmonitor_function);
1547 res |= set_mixmonitor_methods();
1552 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");