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)
458 return ast_audiohook_attach(chan, audiohook);
463 * \brief adds recipients to a mixmonitor's recipient list
464 * \param mixmonitor mixmonitor being affected
465 * \param vm_recipients string containing the desired recipients to add
467 static void add_vm_recipients_from_string(struct mixmonitor *mixmonitor, const char *vm_recipients)
469 /* recipients are in a single string with a format format resembling "mailbox@context/INBOX,mailbox2@context2,mailbox3@context3/Work" */
470 char *cur_mailbox = ast_strdupa(vm_recipients);
474 int elements_processed = 0;
476 while (!ast_strlen_zero(cur_mailbox)) {
477 ast_debug(3, "attempting to add next element %d from %s\n", elements_processed, cur_mailbox);
478 if ((next = strchr(cur_mailbox, ',')) || (next = strchr(cur_mailbox, '&'))) {
482 if ((cur_folder = strchr(cur_mailbox, '/'))) {
483 *(cur_folder++) = '\0';
485 cur_folder = "INBOX";
488 if ((cur_context = strchr(cur_mailbox, '@'))) {
489 *(cur_context++) = '\0';
491 cur_context = "default";
494 if (!ast_strlen_zero(cur_mailbox) && !ast_strlen_zero(cur_context)) {
495 struct vm_recipient *recipient;
496 if (!(recipient = ast_malloc(sizeof(*recipient)))) {
497 ast_log(LOG_ERROR, "Failed to allocate recipient. Aborting function.\n");
500 ast_copy_string(recipient->context, cur_context, sizeof(recipient->context));
501 ast_copy_string(recipient->mailbox, cur_mailbox, sizeof(recipient->mailbox));
502 ast_copy_string(recipient->folder, cur_folder, sizeof(recipient->folder));
505 ast_verb(4, "Adding %s@%s to recipient list\n", recipient->mailbox, recipient->context);
506 AST_LIST_INSERT_HEAD(&mixmonitor->recipient_list, recipient, list);
508 ast_log(LOG_ERROR, "Failed to properly parse extension and/or context from element %d of recipient string: %s\n", elements_processed, vm_recipients);
512 elements_processed++;
516 static void clear_mixmonitor_recipient_list(struct mixmonitor *mixmonitor)
518 struct vm_recipient *current;
519 while ((current = AST_LIST_REMOVE_HEAD(&mixmonitor->recipient_list, list))) {
520 /* Clear list element data */
525 #define SAMPLES_PER_FRAME 160
527 static void mixmonitor_free(struct mixmonitor *mixmonitor)
530 if (mixmonitor->mixmonitor_ds) {
531 ast_mutex_destroy(&mixmonitor->mixmonitor_ds->lock);
532 ast_cond_destroy(&mixmonitor->mixmonitor_ds->destruction_condition);
533 ast_free(mixmonitor->mixmonitor_ds);
536 ast_free(mixmonitor->name);
537 ast_free(mixmonitor->post_process);
538 ast_free(mixmonitor->filename);
539 ast_free(mixmonitor->filename_write);
540 ast_free(mixmonitor->filename_read);
542 /* Free everything in the recipient list */
543 clear_mixmonitor_recipient_list(mixmonitor);
545 /* clean stringfields */
546 ast_string_field_free_memory(mixmonitor);
548 if (mixmonitor->callid) {
549 ast_callid_unref(mixmonitor->callid);
551 ast_free(mixmonitor);
557 * \brief Copies the mixmonitor to all voicemail recipients
558 * \param mixmonitor The mixmonitor that needs to forward its file to recipients
559 * \param ext Format of the file that was saved
561 static void copy_to_voicemail(struct mixmonitor *mixmonitor, const char *ext, const char *filename)
563 struct vm_recipient *recipient = NULL;
564 struct ast_vm_recording_data recording_data;
565 if (ast_string_field_init(&recording_data, 512)) {
566 ast_log(LOG_ERROR, "Failed to string_field_init, skipping copy_to_voicemail\n");
570 /* Copy strings to stringfields that will be used for all recipients */
571 ast_string_field_set(&recording_data, recording_file, filename);
572 ast_string_field_set(&recording_data, recording_ext, ext);
573 ast_string_field_set(&recording_data, call_context, mixmonitor->call_context);
574 ast_string_field_set(&recording_data, call_macrocontext, mixmonitor->call_macrocontext);
575 ast_string_field_set(&recording_data, call_extension, mixmonitor->call_extension);
576 ast_string_field_set(&recording_data, call_callerchan, mixmonitor->call_callerchan);
577 ast_string_field_set(&recording_data, call_callerid, mixmonitor->call_callerid);
578 /* and call_priority gets copied too */
579 recording_data.call_priority = mixmonitor->call_priority;
581 AST_LIST_TRAVERSE(&mixmonitor->recipient_list, recipient, list) {
582 /* context, mailbox, and folder need to be set per recipient */
583 ast_string_field_set(&recording_data, context, recipient->context);
584 ast_string_field_set(&recording_data, mailbox, recipient->mailbox);
585 ast_string_field_set(&recording_data, folder, recipient->folder);
587 ast_verb(4, "MixMonitor attempting to send voicemail copy to %s@%s\n", recording_data.mailbox,
588 recording_data.context);
589 ast_app_copy_recording_to_vm(&recording_data);
592 /* Free the string fields for recording_data before exiting the function. */
593 ast_string_field_free_memory(&recording_data);
596 static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename, struct ast_filestream **fs, unsigned int *oflags, int *errflag, char **ext)
598 /* Initialize the file if not already done so */
599 char *last_slash = NULL;
600 if (!ast_strlen_zero(filename)) {
601 if (!*fs && !*errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
602 *oflags = O_CREAT | O_WRONLY;
603 *oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
605 last_slash = strrchr(filename, '/');
607 if ((*ext = strrchr(filename, '.')) && (*ext > last_slash)) {
614 if (!(*fs = ast_writefile(filename, *ext, NULL, *oflags, 0, 0666))) {
615 ast_log(LOG_ERROR, "Cannot open %s.%s\n", filename, *ext);
618 struct ast_filestream *tmp = *fs;
619 mixmonitor->mixmonitor_ds->samp_rate = MAX(mixmonitor->mixmonitor_ds->samp_rate, ast_format_get_sample_rate(tmp->fmt->format));
625 static void *mixmonitor_thread(void *obj)
627 struct mixmonitor *mixmonitor = obj;
629 char *fs_read_ext = "";
630 char *fs_write_ext = "";
632 struct ast_filestream **fs = NULL;
633 struct ast_filestream **fs_read = NULL;
634 struct ast_filestream **fs_write = NULL;
638 struct ast_format *format_slin;
640 /* Keep callid association before any log messages */
641 if (mixmonitor->callid) {
642 ast_callid_threadassoc_add(mixmonitor->callid);
645 ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name);
647 fs = &mixmonitor->mixmonitor_ds->fs;
648 fs_read = &mixmonitor->mixmonitor_ds->fs_read;
649 fs_write = &mixmonitor->mixmonitor_ds->fs_write;
651 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
652 mixmonitor_save_prep(mixmonitor, mixmonitor->filename, fs, &oflags, &errflag, &fs_ext);
653 mixmonitor_save_prep(mixmonitor, mixmonitor->filename_read, fs_read, &oflags, &errflag, &fs_read_ext);
654 mixmonitor_save_prep(mixmonitor, mixmonitor->filename_write, fs_write, &oflags, &errflag, &fs_write_ext);
656 format_slin = ast_format_cache_get_slin_by_rate(mixmonitor->mixmonitor_ds->samp_rate);
658 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
660 /* The audiohook must enter and exit the loop locked */
661 ast_audiohook_lock(&mixmonitor->audiohook);
662 while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING && !mixmonitor->mixmonitor_ds->fs_quit) {
663 struct ast_frame *fr = NULL;
664 struct ast_frame *fr_read = NULL;
665 struct ast_frame *fr_write = NULL;
667 if (!(fr = ast_audiohook_read_frame_all(&mixmonitor->audiohook, SAMPLES_PER_FRAME, format_slin,
668 &fr_read, &fr_write))) {
669 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
671 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
677 /* audiohook lock is not required for the next block.
678 * Unlock it, but remember to lock it before looping or exiting */
679 ast_audiohook_unlock(&mixmonitor->audiohook);
681 if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED)
682 || (mixmonitor->autochan->chan
683 && ast_channel_is_bridged(mixmonitor->autochan->chan))) {
684 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
686 /* Write out the frame(s) */
687 if ((*fs_read) && (fr_read)) {
688 struct ast_frame *cur;
690 for (cur = fr_read; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
691 ast_writestream(*fs_read, cur);
695 if ((*fs_write) && (fr_write)) {
696 struct ast_frame *cur;
698 for (cur = fr_write; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
699 ast_writestream(*fs_write, cur);
704 struct ast_frame *cur;
706 for (cur = fr; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
707 ast_writestream(*fs, cur);
710 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
712 /* All done! free it. */
714 ast_frame_free(fr, 0);
717 ast_frame_free(fr_read, 0);
720 ast_frame_free(fr_write, 0);
727 ast_audiohook_lock(&mixmonitor->audiohook);
731 ast_test_suite_event_notify("MIXMONITOR_END", "Channel: %s\r\n"
733 ast_channel_name(mixmonitor->autochan->chan),
734 mixmonitor->filename);
736 ast_audiohook_unlock(&mixmonitor->audiohook);
738 ast_channel_lock(mixmonitor->autochan->chan);
739 if (ast_test_flag(mixmonitor, MUXFLAG_BEEP_STOP)) {
740 ast_stream_and_wait(mixmonitor->autochan->chan, "beep", "");
742 ast_channel_unlock(mixmonitor->autochan->chan);
744 ast_autochan_destroy(mixmonitor->autochan);
746 /* Datastore cleanup. close the filestream and wait for ds destruction */
747 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
748 mixmonitor_ds_close_fs(mixmonitor->mixmonitor_ds);
749 if (!mixmonitor->mixmonitor_ds->destruction_ok) {
750 ast_cond_wait(&mixmonitor->mixmonitor_ds->destruction_condition, &mixmonitor->mixmonitor_ds->lock);
752 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
754 /* kill the audiohook */
755 destroy_monitor_audiohook(mixmonitor);
757 if (mixmonitor->post_process) {
758 ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
759 ast_safe_system(mixmonitor->post_process);
762 ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
764 if (!AST_LIST_EMPTY(&mixmonitor->recipient_list)) {
765 if (ast_strlen_zero(fs_ext)) {
766 ast_log(LOG_ERROR, "No file extension set for Mixmonitor %s. Skipping copy to voicemail.\n",
769 ast_verb(3, "Copying recordings for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
770 copy_to_voicemail(mixmonitor, fs_ext, mixmonitor->filename);
772 if (!ast_strlen_zero(fs_read_ext)) {
773 ast_verb(3, "Copying read recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
774 copy_to_voicemail(mixmonitor, fs_read_ext, mixmonitor->filename_read);
776 if (!ast_strlen_zero(fs_write_ext)) {
777 ast_verb(3, "Copying write recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
778 copy_to_voicemail(mixmonitor, fs_write_ext, mixmonitor->filename_write);
781 ast_debug(3, "No recipients to forward monitor to, moving on.\n");
784 mixmonitor_free(mixmonitor);
786 ast_module_unref(ast_module_info->self);
790 static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan, char **datastore_id, const char *beep_id)
792 struct ast_datastore *datastore = NULL;
793 struct mixmonitor_ds *mixmonitor_ds;
795 if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
799 if (ast_asprintf(datastore_id, "%p", mixmonitor_ds) == -1) {
800 ast_log(LOG_ERROR, "Failed to allocate memory for MixMonitor ID.\n");
803 ast_mutex_init(&mixmonitor_ds->lock);
804 ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
806 if (!(datastore = ast_datastore_alloc(&mixmonitor_ds_info, *datastore_id))) {
807 ast_mutex_destroy(&mixmonitor_ds->lock);
808 ast_cond_destroy(&mixmonitor_ds->destruction_condition);
809 ast_free(mixmonitor_ds);
813 ast_channel_lock(mixmonitor->autochan->chan);
814 if (ast_test_flag(mixmonitor, MUXFLAG_BEEP_START)) {
815 ast_stream_and_wait(mixmonitor->autochan->chan, "beep", "");
817 ast_channel_unlock(mixmonitor->autochan->chan);
819 mixmonitor_ds->samp_rate = 8000;
820 mixmonitor_ds->audiohook = &mixmonitor->audiohook;
821 mixmonitor_ds->filename = ast_strdup(mixmonitor->filename);
822 if (!ast_strlen_zero(beep_id)) {
823 mixmonitor_ds->beep_id = ast_strdup(beep_id);
825 datastore->data = mixmonitor_ds;
827 ast_channel_lock(chan);
828 ast_channel_datastore_add(chan, datastore);
829 ast_channel_unlock(chan);
831 mixmonitor->mixmonitor_ds = mixmonitor_ds;
835 static int launch_monitor_thread(struct ast_channel *chan, const char *filename,
836 unsigned int flags, int readvol, int writevol,
837 const char *post_process, const char *filename_write,
838 char *filename_read, const char *uid_channel_var,
839 const char *recipients, const char *beep_id)
842 struct mixmonitor *mixmonitor;
843 char postprocess2[1024] = "";
844 char *datastore_id = NULL;
847 /* If a post process system command is given attach it to the structure */
848 if (!ast_strlen_zero(post_process)) {
851 p1 = ast_strdupa(post_process);
852 for (p2 = p1; *p2; p2++) {
853 if (*p2 == '^' && *(p2+1) == '{') {
857 ast_channel_lock(chan);
858 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
859 ast_channel_unlock(chan);
862 /* Pre-allocate mixmonitor structure and spy */
863 if (!(mixmonitor = ast_calloc(1, sizeof(*mixmonitor)))) {
867 /* Now that the struct has been calloced, go ahead and initialize the string fields. */
868 if (ast_string_field_init(mixmonitor, 512)) {
869 mixmonitor_free(mixmonitor);
873 /* Setup the actual spy before creating our thread */
874 if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type, 0)) {
875 mixmonitor_free(mixmonitor);
879 /* Copy over flags and channel name */
880 mixmonitor->flags = flags;
881 if (!(mixmonitor->autochan = ast_autochan_setup(chan))) {
882 mixmonitor_free(mixmonitor);
886 if (!ast_strlen_zero(filename)) {
887 mixmonitor->filename = ast_strdup(filename);
890 if (!ast_strlen_zero(filename_write)) {
891 mixmonitor->filename_write = ast_strdup(filename_write);
894 if (!ast_strlen_zero(filename_read)) {
895 mixmonitor->filename_read = ast_strdup(filename_read);
898 if (setup_mixmonitor_ds(mixmonitor, chan, &datastore_id, beep_id)) {
899 ast_autochan_destroy(mixmonitor->autochan);
900 mixmonitor_free(mixmonitor);
901 ast_free(datastore_id);
905 if (!ast_strlen_zero(uid_channel_var)) {
907 pbx_builtin_setvar_helper(chan, uid_channel_var, datastore_id);
910 ast_free(datastore_id);
912 mixmonitor->name = ast_strdup(ast_channel_name(chan));
914 if (!ast_strlen_zero(postprocess2)) {
915 mixmonitor->post_process = ast_strdup(postprocess2);
918 if (!ast_strlen_zero(recipients)) {
920 struct ast_party_connected_line *connected;
922 ast_channel_lock(chan);
924 /* We use the connected line of the invoking channel for caller ID. */
926 connected = ast_channel_connected(chan);
927 ast_debug(3, "Connected Line CID = %d - %s : %d - %s\n", connected->id.name.valid,
928 connected->id.name.str, connected->id.number.valid,
929 connected->id.number.str);
930 ast_callerid_merge(callerid, sizeof(callerid),
931 S_COR(connected->id.name.valid, connected->id.name.str, NULL),
932 S_COR(connected->id.number.valid, connected->id.number.str, NULL),
935 ast_string_field_set(mixmonitor, call_context, ast_channel_context(chan));
936 ast_string_field_set(mixmonitor, call_macrocontext, ast_channel_macrocontext(chan));
937 ast_string_field_set(mixmonitor, call_extension, ast_channel_exten(chan));
938 ast_string_field_set(mixmonitor, call_callerchan, ast_channel_name(chan));
939 ast_string_field_set(mixmonitor, call_callerid, callerid);
940 mixmonitor->call_priority = ast_channel_priority(chan);
942 ast_channel_unlock(chan);
944 add_vm_recipients_from_string(mixmonitor, recipients);
947 ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
950 mixmonitor->audiohook.options.read_volume = readvol;
952 mixmonitor->audiohook.options.write_volume = writevol;
954 if (startmon(chan, &mixmonitor->audiohook)) {
955 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
956 mixmonitor_spy_type, ast_channel_name(chan));
957 ast_audiohook_destroy(&mixmonitor->audiohook);
958 mixmonitor_free(mixmonitor);
962 /* reference be released at mixmonitor destruction */
963 mixmonitor->callid = ast_read_threadstorage_callid();
965 return ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
968 /* a note on filename_parse: creates directory structure and assigns absolute path from relative paths for filenames */
969 /* requires immediate copying of string from return to retain data since otherwise it will immediately lose scope */
970 static char *filename_parse(char *filename, char *buffer, size_t len)
973 if (ast_strlen_zero(filename)) {
974 ast_log(LOG_WARNING, "No file name was provided for a file save option.\n");
975 } else if (filename[0] != '/') {
977 build = ast_alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(filename) + 3);
978 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, filename);
982 ast_copy_string(buffer, filename, len);
984 if ((slash = strrchr(filename, '/'))) {
987 ast_mkdir(filename, 0777);
992 static int mixmonitor_exec(struct ast_channel *chan, const char *data)
994 int x, readvol = 0, writevol = 0;
995 char *filename_read = NULL;
996 char *filename_write = NULL;
997 char filename_buffer[1024] = "";
998 char *uid_channel_var = NULL;
999 char beep_id[64] = "";
1001 struct ast_flags flags = { 0 };
1002 char *recipients = NULL;
1004 AST_DECLARE_APP_ARGS(args,
1005 AST_APP_ARG(filename);
1006 AST_APP_ARG(options);
1007 AST_APP_ARG(post_process);
1010 if (ast_strlen_zero(data)) {
1011 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename or ,t(filename) and/or r(filename)\n");
1015 parse = ast_strdupa(data);
1017 AST_STANDARD_APP_ARGS(args, parse);
1020 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
1022 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
1024 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
1025 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
1026 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
1027 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
1028 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
1030 readvol = get_volfactor(x);
1034 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
1035 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
1036 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
1037 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
1038 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
1040 writevol = get_volfactor(x);
1044 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
1045 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
1046 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
1047 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
1048 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
1050 readvol = writevol = get_volfactor(x);
1054 if (ast_test_flag(&flags, MUXFLAG_VMRECIPIENTS)) {
1055 if (ast_strlen_zero(opts[OPT_ARG_VMRECIPIENTS])) {
1056 ast_log(LOG_WARNING, "No voicemail recipients were specified for the vm copy ('m') option.\n");
1058 recipients = ast_strdupa(opts[OPT_ARG_VMRECIPIENTS]);
1062 if (ast_test_flag(&flags, MUXFLAG_WRITE)) {
1063 filename_write = ast_strdupa(filename_parse(opts[OPT_ARG_WRITENAME], filename_buffer, sizeof(filename_buffer)));
1066 if (ast_test_flag(&flags, MUXFLAG_READ)) {
1067 filename_read = ast_strdupa(filename_parse(opts[OPT_ARG_READNAME], filename_buffer, sizeof(filename_buffer)));
1070 if (ast_test_flag(&flags, MUXFLAG_UID)) {
1071 uid_channel_var = opts[OPT_ARG_UID];
1074 if (ast_test_flag(&flags, MUXFLAG_BEEP)) {
1075 const char *interval_str = S_OR(opts[OPT_ARG_BEEP_INTERVAL], "15");
1076 unsigned int interval = 15;
1078 if (sscanf(interval_str, "%30u", &interval) != 1) {
1079 ast_log(LOG_WARNING, "Invalid interval '%s' for periodic beep. Using default of %u\n",
1080 interval_str, interval);
1083 if (ast_beep_start(chan, interval, beep_id, sizeof(beep_id))) {
1084 ast_log(LOG_WARNING, "Unable to enable periodic beep, please ensure func_periodic_hook is loaded.\n");
1089 /* If there are no file writing arguments/options for the mix monitor, send a warning message and return -1 */
1091 if (!ast_test_flag(&flags, MUXFLAG_WRITE) && !ast_test_flag(&flags, MUXFLAG_READ) && ast_strlen_zero(args.filename)) {
1092 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
1096 /* If filename exists, try to create directories for it */
1097 if (!(ast_strlen_zero(args.filename))) {
1098 args.filename = ast_strdupa(filename_parse(args.filename, filename_buffer, sizeof(filename_buffer)));
1101 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
1103 /* If launch_monitor_thread works, the module reference must not be released until it is finished. */
1104 ast_module_ref(ast_module_info->self);
1105 if (launch_monitor_thread(chan,
1116 ast_module_unref(ast_module_info->self);
1122 static int stop_mixmonitor_full(struct ast_channel *chan, const char *data)
1124 struct ast_datastore *datastore = NULL;
1126 struct mixmonitor_ds *mixmonitor_ds;
1127 const char *beep_id = NULL;
1129 AST_DECLARE_APP_ARGS(args,
1130 AST_APP_ARG(mixmonid);
1133 if (!ast_strlen_zero(data)) {
1134 parse = ast_strdupa(data);
1137 AST_STANDARD_APP_ARGS(args, parse);
1139 ast_channel_lock(chan);
1141 datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info,
1142 S_OR(args.mixmonid, NULL));
1144 ast_channel_unlock(chan);
1147 mixmonitor_ds = datastore->data;
1149 ast_mutex_lock(&mixmonitor_ds->lock);
1151 /* closing the filestream here guarantees the file is available to the dialplan
1152 * after calling StopMixMonitor */
1153 mixmonitor_ds_close_fs(mixmonitor_ds);
1155 /* The mixmonitor thread may be waiting on the audiohook trigger.
1156 * In order to exit from the mixmonitor loop before waiting on channel
1157 * destruction, poke the audiohook trigger. */
1158 if (mixmonitor_ds->audiohook) {
1159 if (mixmonitor_ds->audiohook->status != AST_AUDIOHOOK_STATUS_DONE) {
1160 ast_audiohook_update_status(mixmonitor_ds->audiohook, AST_AUDIOHOOK_STATUS_SHUTDOWN);
1162 ast_audiohook_lock(mixmonitor_ds->audiohook);
1163 ast_cond_signal(&mixmonitor_ds->audiohook->trigger);
1164 ast_audiohook_unlock(mixmonitor_ds->audiohook);
1165 mixmonitor_ds->audiohook = NULL;
1168 if (!ast_strlen_zero(mixmonitor_ds->beep_id)) {
1169 beep_id = ast_strdupa(mixmonitor_ds->beep_id);
1172 ast_mutex_unlock(&mixmonitor_ds->lock);
1174 /* Remove the datastore so the monitor thread can exit */
1175 if (!ast_channel_datastore_remove(chan, datastore)) {
1176 ast_datastore_free(datastore);
1179 ast_channel_unlock(chan);
1181 if (!ast_strlen_zero(beep_id)) {
1182 ast_beep_stop(chan, beep_id);
1188 static int stop_mixmonitor_exec(struct ast_channel *chan, const char *data)
1190 stop_mixmonitor_full(chan, data);
1194 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1196 struct ast_channel *chan;
1197 struct ast_datastore *datastore = NULL;
1198 struct mixmonitor_ds *mixmonitor_ds = NULL;
1202 e->command = "mixmonitor {start|stop|list}";
1204 "Usage: mixmonitor start <chan_name> [args]\n"
1205 " The optional arguments are passed to the MixMonitor application.\n"
1206 " mixmonitor stop <chan_name> [args]\n"
1207 " The optional arguments are passed to the StopMixMonitor application.\n"
1208 " mixmonitor list <chan_name>\n";
1211 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
1215 return CLI_SHOWUSAGE;
1218 if (!(chan = ast_channel_get_by_name_prefix(a->argv[2], strlen(a->argv[2])))) {
1219 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
1220 /* Technically this is a failure, but we don't want 2 errors printing out */
1224 if (!strcasecmp(a->argv[1], "start")) {
1225 mixmonitor_exec(chan, (a->argc >= 4) ? a->argv[3] : "");
1226 } else if (!strcasecmp(a->argv[1], "stop")){
1227 stop_mixmonitor_exec(chan, (a->argc >= 4) ? a->argv[3] : "");
1228 } else if (!strcasecmp(a->argv[1], "list")) {
1229 ast_cli(a->fd, "MixMonitor ID\tFile\tReceive File\tTransmit File\n");
1230 ast_cli(a->fd, "=========================================================================\n");
1231 ast_channel_lock(chan);
1232 AST_LIST_TRAVERSE(ast_channel_datastores(chan), datastore, entry) {
1233 if (datastore->info == &mixmonitor_ds_info) {
1234 char *filename = "";
1235 char *filename_read = "";
1236 char *filename_write = "";
1238 mixmonitor_ds = datastore->data;
1239 if (mixmonitor_ds->fs) {
1240 filename = mixmonitor_ds->fs->filename;
1242 if (mixmonitor_ds->fs_read) {
1243 filename_read = mixmonitor_ds->fs_read->filename;
1245 if (mixmonitor_ds->fs_write) {
1246 filename_write = mixmonitor_ds->fs_write->filename;
1248 ast_cli(a->fd, "%p\t%s\t%s\t%s\n", mixmonitor_ds, filename, filename_read, filename_write);
1251 ast_channel_unlock(chan);
1253 chan = ast_channel_unref(chan);
1254 return CLI_SHOWUSAGE;
1257 chan = ast_channel_unref(chan);
1262 /*! \brief Mute / unmute a MixMonitor channel */
1263 static int manager_mute_mixmonitor(struct mansession *s, const struct message *m)
1265 struct ast_channel *c;
1266 const char *name = astman_get_header(m, "Channel");
1267 const char *id = astman_get_header(m, "ActionID");
1268 const char *state = astman_get_header(m, "State");
1269 const char *direction = astman_get_header(m,"Direction");
1271 enum ast_audiohook_flags flag;
1273 if (ast_strlen_zero(direction)) {
1274 astman_send_error(s, m, "No direction specified. Must be read, write or both");
1278 if (!strcasecmp(direction, "read")) {
1279 flag = AST_AUDIOHOOK_MUTE_READ;
1280 } else if (!strcasecmp(direction, "write")) {
1281 flag = AST_AUDIOHOOK_MUTE_WRITE;
1282 } else if (!strcasecmp(direction, "both")) {
1283 flag = AST_AUDIOHOOK_MUTE_READ | AST_AUDIOHOOK_MUTE_WRITE;
1285 astman_send_error(s, m, "Invalid direction specified. Must be read, write or both");
1289 if (ast_strlen_zero(name)) {
1290 astman_send_error(s, m, "No channel specified");
1294 if (ast_strlen_zero(state)) {
1295 astman_send_error(s, m, "No state specified");
1299 clearmute = ast_false(state);
1301 c = ast_channel_get_by_name(name);
1303 astman_send_error(s, m, "No such channel");
1307 if (ast_audiohook_set_mute(c, mixmonitor_spy_type, flag, clearmute)) {
1308 ast_channel_unref(c);
1309 astman_send_error(s, m, "Cannot set mute flag");
1313 astman_append(s, "Response: Success\r\n");
1315 if (!ast_strlen_zero(id)) {
1316 astman_append(s, "ActionID: %s\r\n", id);
1319 astman_append(s, "\r\n");
1321 ast_channel_unref(c);
1326 static int start_mixmonitor_callback(struct ast_channel *chan, const char *filename, const char *options)
1328 char args[PATH_MAX];
1330 if (ast_strlen_zero(options)) {
1331 snprintf(args, sizeof(args), "%s", filename);
1333 snprintf(args, sizeof(args), "%s,%s", filename, options);
1336 return mixmonitor_exec(chan, args);
1339 static int stop_mixmonitor_callback(struct ast_channel *chan, const char *mixmonitor_id)
1341 return stop_mixmonitor_full(chan, mixmonitor_id);
1344 static int manager_mixmonitor(struct mansession *s, const struct message *m)
1346 struct ast_channel *c;
1347 const char *name = astman_get_header(m, "Channel");
1348 const char *id = astman_get_header(m, "ActionID");
1349 const char *file = astman_get_header(m, "File");
1350 const char *options = astman_get_header(m, "Options");
1351 const char *command = astman_get_header(m, "Command");
1352 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
1353 struct ast_flags flags = { 0 };
1354 char *uid_channel_var = NULL;
1355 const char *mixmonitor_id = NULL;
1357 char args[PATH_MAX];
1359 if (ast_strlen_zero(name)) {
1360 astman_send_error(s, m, "No channel specified");
1364 c = ast_channel_get_by_name(name);
1366 astman_send_error(s, m, "No such channel");
1370 if (!ast_strlen_zero(options)) {
1371 ast_app_parse_options(mixmonitor_opts, &flags, opts, ast_strdupa(options));
1374 snprintf(args, sizeof(args), "%s,%s,%s", file, options, command);
1376 res = mixmonitor_exec(c, args);
1378 if (ast_test_flag(&flags, MUXFLAG_UID)) {
1379 uid_channel_var = opts[OPT_ARG_UID];
1380 ast_channel_lock(c);
1381 mixmonitor_id = pbx_builtin_getvar_helper(c, uid_channel_var);
1382 mixmonitor_id = ast_strdupa(S_OR(mixmonitor_id, ""));
1383 ast_channel_unlock(c);
1387 ast_channel_unref(c);
1388 astman_send_error(s, m, "Could not start monitoring channel");
1392 astman_append(s, "Response: Success\r\n");
1394 if (!ast_strlen_zero(id)) {
1395 astman_append(s, "ActionID: %s\r\n", id);
1398 if (!ast_strlen_zero(mixmonitor_id)) {
1399 astman_append(s, "MixMonitorID: %s\r\n", mixmonitor_id);
1402 astman_append(s, "\r\n");
1404 ast_channel_unref(c);
1409 static int manager_stop_mixmonitor(struct mansession *s, const struct message *m)
1411 struct ast_channel *c;
1412 const char *name = astman_get_header(m, "Channel");
1413 const char *id = astman_get_header(m, "ActionID");
1414 const char *mixmonitor_id = astman_get_header(m, "MixMonitorID");
1417 if (ast_strlen_zero(name)) {
1418 astman_send_error(s, m, "No channel specified");
1422 c = ast_channel_get_by_name(name);
1424 astman_send_error(s, m, "No such channel");
1428 res = stop_mixmonitor_full(c, mixmonitor_id);
1430 ast_channel_unref(c);
1431 astman_send_error(s, m, "Could not stop monitoring channel");
1435 astman_append(s, "Response: Success\r\n");
1437 if (!ast_strlen_zero(id)) {
1438 astman_append(s, "ActionID: %s\r\n", id);
1441 astman_append(s, "\r\n");
1443 ast_channel_unref(c);
1448 static int func_mixmonitor_read(struct ast_channel *chan, const char *cmd, char *data,
1449 char *buf, size_t len)
1451 struct ast_datastore *datastore;
1452 struct mixmonitor_ds *ds_data;
1453 AST_DECLARE_APP_ARGS(args,
1458 AST_STANDARD_APP_ARGS(args, data);
1460 if (ast_strlen_zero(args.id) || ast_strlen_zero(args.key)) {
1461 ast_log(LOG_WARNING, "Not enough arguments provided to %s. "
1462 "An ID and key must be provided\n", cmd);
1466 ast_channel_lock(chan);
1467 datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, args.id);
1468 ast_channel_unlock(chan);
1471 ast_log(LOG_WARNING, "Could not find MixMonitor with ID %s\n", args.id);
1475 ds_data = datastore->data;
1477 if (!strcasecmp(args.key, "filename")) {
1478 ast_copy_string(buf, ds_data->filename, len);
1480 ast_log(LOG_WARNING, "Unrecognized %s option %s\n", cmd, args.key);
1486 static struct ast_custom_function mixmonitor_function = {
1487 .name = "MIXMONITOR",
1488 .read = func_mixmonitor_read,
1491 static struct ast_cli_entry cli_mixmonitor[] = {
1492 AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
1495 static int set_mixmonitor_methods(void)
1497 struct ast_mixmonitor_methods mixmonitor_methods = {
1498 .start = start_mixmonitor_callback,
1499 .stop = stop_mixmonitor_callback,
1502 return ast_set_mixmonitor_methods(&mixmonitor_methods);
1505 static int clear_mixmonitor_methods(void)
1507 return ast_clear_mixmonitor_methods();
1510 static int unload_module(void)
1514 ast_cli_unregister_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
1515 res = ast_unregister_application(stop_app);
1516 res |= ast_unregister_application(app);
1517 res |= ast_manager_unregister("MixMonitorMute");
1518 res |= ast_manager_unregister("MixMonitor");
1519 res |= ast_manager_unregister("StopMixMonitor");
1520 res |= ast_custom_function_unregister(&mixmonitor_function);
1521 res |= clear_mixmonitor_methods();
1526 static int load_module(void)
1530 ast_cli_register_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
1531 res = ast_register_application_xml(app, mixmonitor_exec);
1532 res |= ast_register_application_xml(stop_app, stop_mixmonitor_exec);
1533 res |= ast_manager_register_xml("MixMonitorMute", EVENT_FLAG_SYSTEM | EVENT_FLAG_CALL, manager_mute_mixmonitor);
1534 res |= ast_manager_register_xml("MixMonitor", EVENT_FLAG_SYSTEM, manager_mixmonitor);
1535 res |= ast_manager_register_xml("StopMixMonitor", EVENT_FLAG_SYSTEM | EVENT_FLAG_CALL, manager_stop_mixmonitor);
1536 res |= ast_custom_function_register(&mixmonitor_function);
1537 res |= set_mixmonitor_methods();
1542 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");