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 <argument name="mailbox" required="true" />
125 <para>Create a copy of the recording as a voicemail in the indicated <emphasis>mailbox</emphasis>(es)
126 separated by commas eg. m(1111@default,2222@default,...). Folders can be optionally specified using
127 the syntax: mailbox@context/folder</para>
131 <parameter name="command">
132 <para>Will be executed when the recording is over.</para>
133 <para>Any strings matching <literal>^{X}</literal> will be unescaped to <variable>X</variable>.</para>
134 <para>All variables will be evaluated at the time MixMonitor is called.</para>
138 <para>Records the audio on the current channel to the specified file.</para>
139 <para>This application does not automatically answer and should be preceeded by
140 an application such as Answer or Progress().</para>
141 <note><para>MixMonitor runs as an audiohook.</para></note>
143 <variable name="MIXMONITOR_FILENAME">
144 <para>Will contain the filename used to record.</para>
149 <ref type="application">Monitor</ref>
150 <ref type="application">StopMixMonitor</ref>
151 <ref type="application">PauseMonitor</ref>
152 <ref type="application">UnpauseMonitor</ref>
153 <ref type="function">AUDIOHOOK_INHERIT</ref>
156 <application name="StopMixMonitor" language="en_US">
158 Stop recording a call through MixMonitor, and free the recording's file handle.
161 <parameter name="MixMonitorID" required="false">
162 <para>If a valid ID is provided, then this command will stop only that specific
167 <para>Stops the audio recording that was started with a call to <literal>MixMonitor()</literal>
168 on the current channel.</para>
171 <ref type="application">MixMonitor</ref>
174 <manager name="MixMonitorMute" language="en_US">
176 Mute / unMute a Mixmonitor recording.
179 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
180 <parameter name="Channel" required="true">
181 <para>Used to specify the channel to mute.</para>
183 <parameter name="Direction">
184 <para>Which part of the recording to mute: read, write or both (from channel, to channel or both channels).</para>
186 <parameter name="State">
187 <para>Turn mute on or off : 1 to turn on, 0 to turn off.</para>
191 <para>This action may be used to mute a MixMonitor recording.</para>
194 <manager name="MixMonitor" language="en_US">
196 Record a call and mix the audio during the recording. Use of StopMixMonitor is required
197 to guarantee the audio file is available for processing during dialplan execution.
200 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
201 <parameter name="Channel" required="true">
202 <para>Used to specify the channel to record.</para>
204 <parameter name="File">
205 <para>Is the name of the file created in the monitor spool directory.
206 Defaults to the same name as the channel (with slashes replaced with dashes).
207 This argument is optional if you specify to record unidirectional audio with
208 either the r(filename) or t(filename) options in the options field. If
209 neither MIXMONITOR_FILENAME or this parameter is set, the mixed stream won't
212 <parameter name="options">
213 <para>Options that apply to the MixMonitor in the same way as they
214 would apply if invoked from the MixMonitor application. For a list of
215 available options, see the documentation for the mixmonitor application. </para>
217 <parameter name="Command">
218 <para>Will be executed when the recording is over.
219 Any strings matching <literal>^{X}</literal> will be unescaped to <variable>X</variable>.
220 All variables will be evaluated at the time MixMonitor is called.</para>
224 <para>This action records the audio on the current channel to the specified file.</para>
226 <variable name="MIXMONITOR_FILENAME">
227 <para>Will contain the filename used to record the mixed stream.</para>
232 <manager name="StopMixMonitor" language="en_US">
234 Stop recording a call through MixMonitor, and free the recording's file handle.
237 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
238 <parameter name="Channel" required="true">
239 <para>The name of the channel monitored.</para>
241 <parameter name="MixMonitorID" required="false">
242 <para>If a valid ID is provided, then this command will stop only that specific
247 <para>This action stops the audio recording that was started with the <literal>MixMonitor</literal>
248 action on the current channel.</para>
251 <function name="MIXMONITOR" language="en_US">
253 Retrieve data pertaining to specific instances of MixMonitor on a channel.
256 <parameter name="id" required="true">
257 <para>The unique ID of the MixMonitor instance. The unique ID can be retrieved through the channel
258 variable used as an argument to the <replaceable>i</replaceable> option to MixMonitor.</para>
260 <parameter name="key" required="true">
261 <para>The piece of data to retrieve from the MixMonitor.</para>
263 <enum name="filename" />
271 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
273 static const char * const app = "MixMonitor";
275 static const char * const stop_app = "StopMixMonitor";
277 static const char * const mixmonitor_spy_type = "MixMonitor";
281 * \brief This struct is a list item holds data needed to find a vm_recipient within voicemail
283 struct vm_recipient {
284 char mailbox[AST_MAX_CONTEXT];
285 char context[AST_MAX_EXTENSION];
287 AST_LIST_ENTRY(vm_recipient) list;
291 struct ast_audiohook audiohook;
292 struct ast_callid *callid;
295 char *filename_write;
299 struct ast_autochan *autochan;
300 struct mixmonitor_ds *mixmonitor_ds;
302 /* the below string fields describe data used for creating voicemails from the recording */
303 AST_DECLARE_STRING_FIELDS(
304 AST_STRING_FIELD(call_context);
305 AST_STRING_FIELD(call_macrocontext);
306 AST_STRING_FIELD(call_extension);
307 AST_STRING_FIELD(call_callerchan);
308 AST_STRING_FIELD(call_callerid);
312 /* FUTURE DEVELOPMENT NOTICE
313 * recipient_list will need locks if we make it editable after the monitor is started */
314 AST_LIST_HEAD_NOLOCK(, vm_recipient) recipient_list;
317 enum mixmonitor_flags {
318 MUXFLAG_APPEND = (1 << 1),
319 MUXFLAG_BRIDGED = (1 << 2),
320 MUXFLAG_VOLUME = (1 << 3),
321 MUXFLAG_READVOLUME = (1 << 4),
322 MUXFLAG_WRITEVOLUME = (1 << 5),
323 MUXFLAG_READ = (1 << 6),
324 MUXFLAG_WRITE = (1 << 7),
325 MUXFLAG_COMBINED = (1 << 8),
326 MUXFLAG_UID = (1 << 9),
327 MUXFLAG_VMRECIPIENTS = (1 << 10),
328 MUXFLAG_BEEP = (1 << 11),
331 enum mixmonitor_args {
332 OPT_ARG_READVOLUME = 0,
338 OPT_ARG_VMRECIPIENTS,
339 OPT_ARG_BEEP_INTERVAL,
340 OPT_ARG_ARRAY_SIZE, /* Always last element of the enum */
343 AST_APP_OPTIONS(mixmonitor_opts, {
344 AST_APP_OPTION('a', MUXFLAG_APPEND),
345 AST_APP_OPTION('b', MUXFLAG_BRIDGED),
346 AST_APP_OPTION_ARG('B', MUXFLAG_BEEP, OPT_ARG_BEEP_INTERVAL),
347 AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
348 AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
349 AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
350 AST_APP_OPTION_ARG('r', MUXFLAG_READ, OPT_ARG_READNAME),
351 AST_APP_OPTION_ARG('t', MUXFLAG_WRITE, OPT_ARG_WRITENAME),
352 AST_APP_OPTION_ARG('i', MUXFLAG_UID, OPT_ARG_UID),
353 AST_APP_OPTION_ARG('m', MUXFLAG_VMRECIPIENTS, OPT_ARG_VMRECIPIENTS),
356 struct mixmonitor_ds {
357 unsigned int destruction_ok;
358 ast_cond_t destruction_condition;
361 /* The filestream is held in the datastore so it can be stopped
362 * immediately during stop_mixmonitor or channel destruction. */
365 struct ast_filestream *fs;
366 struct ast_filestream *fs_read;
367 struct ast_filestream *fs_write;
369 struct ast_audiohook *audiohook;
371 unsigned int samp_rate;
378 * \pre mixmonitor_ds must be locked before calling this function
380 static void mixmonitor_ds_close_fs(struct mixmonitor_ds *mixmonitor_ds)
382 unsigned char quitting = 0;
384 if (mixmonitor_ds->fs) {
386 ast_closestream(mixmonitor_ds->fs);
387 mixmonitor_ds->fs = NULL;
388 ast_verb(2, "MixMonitor close filestream (mixed)\n");
391 if (mixmonitor_ds->fs_read) {
393 ast_closestream(mixmonitor_ds->fs_read);
394 mixmonitor_ds->fs_read = NULL;
395 ast_verb(2, "MixMonitor close filestream (read)\n");
398 if (mixmonitor_ds->fs_write) {
400 ast_closestream(mixmonitor_ds->fs_write);
401 mixmonitor_ds->fs_write = NULL;
402 ast_verb(2, "MixMonitor close filestream (write)\n");
406 mixmonitor_ds->fs_quit = 1;
410 static void mixmonitor_ds_destroy(void *data)
412 struct mixmonitor_ds *mixmonitor_ds = data;
414 ast_mutex_lock(&mixmonitor_ds->lock);
415 mixmonitor_ds->audiohook = NULL;
416 mixmonitor_ds->destruction_ok = 1;
417 ast_free(mixmonitor_ds->filename);
418 ast_free(mixmonitor_ds->beep_id);
419 ast_cond_signal(&mixmonitor_ds->destruction_condition);
420 ast_mutex_unlock(&mixmonitor_ds->lock);
423 static const struct ast_datastore_info mixmonitor_ds_info = {
424 .type = "mixmonitor",
425 .destroy = mixmonitor_ds_destroy,
428 static void destroy_monitor_audiohook(struct mixmonitor *mixmonitor)
430 if (mixmonitor->mixmonitor_ds) {
431 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
432 mixmonitor->mixmonitor_ds->audiohook = NULL;
433 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
435 /* kill the audiohook.*/
436 ast_audiohook_lock(&mixmonitor->audiohook);
437 ast_audiohook_detach(&mixmonitor->audiohook);
438 ast_audiohook_unlock(&mixmonitor->audiohook);
439 ast_audiohook_destroy(&mixmonitor->audiohook);
442 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
449 ast_audiohook_attach(chan, audiohook);
452 ast_channel_lock(chan);
453 if (ast_channel_is_bridged(chan)) {
454 ast_softhangup_nolock(chan, AST_SOFTHANGUP_UNBRIDGE);
456 ast_channel_unlock(chan);
464 * \brief adds recipients to a mixmonitor's recipient list
465 * \param mixmonitor mixmonitor being affected
466 * \param vm_recipients string containing the desired recipients to add
468 static void add_vm_recipients_from_string(struct mixmonitor *mixmonitor, const char *vm_recipients)
470 /* recipients are in a single string with a format format resembling "mailbox@context/INBOX,mailbox2@context2,mailbox3@context3/Work" */
471 char *cur_mailbox = ast_strdupa(vm_recipients);
475 int elements_processed = 0;
477 while (!ast_strlen_zero(cur_mailbox)) {
478 ast_debug(3, "attempting to add next element %d from %s\n", elements_processed, cur_mailbox);
479 if ((next = strchr(cur_mailbox, ',')) || (next = strchr(cur_mailbox, '&'))) {
483 if ((cur_folder = strchr(cur_mailbox, '/'))) {
484 *(cur_folder++) = '\0';
486 cur_folder = "INBOX";
489 if ((cur_context = strchr(cur_mailbox, '@'))) {
490 *(cur_context++) = '\0';
492 cur_context = "default";
495 if (!ast_strlen_zero(cur_mailbox) && !ast_strlen_zero(cur_context)) {
496 struct vm_recipient *recipient;
497 if (!(recipient = ast_malloc(sizeof(*recipient)))) {
498 ast_log(LOG_ERROR, "Failed to allocate recipient. Aborting function.\n");
501 ast_copy_string(recipient->context, cur_context, sizeof(recipient->context));
502 ast_copy_string(recipient->mailbox, cur_mailbox, sizeof(recipient->mailbox));
503 ast_copy_string(recipient->folder, cur_folder, sizeof(recipient->folder));
506 ast_verb(4, "Adding %s@%s to recipient list\n", recipient->mailbox, recipient->context);
507 AST_LIST_INSERT_HEAD(&mixmonitor->recipient_list, recipient, list);
509 ast_log(LOG_ERROR, "Failed to properly parse extension and/or context from element %d of recipient string: %s\n", elements_processed, vm_recipients);
513 elements_processed++;
517 static void clear_mixmonitor_recipient_list(struct mixmonitor *mixmonitor)
519 struct vm_recipient *current;
520 while ((current = AST_LIST_REMOVE_HEAD(&mixmonitor->recipient_list, list))) {
521 /* Clear list element data */
526 #define SAMPLES_PER_FRAME 160
528 static void mixmonitor_free(struct mixmonitor *mixmonitor)
531 if (mixmonitor->mixmonitor_ds) {
532 ast_mutex_destroy(&mixmonitor->mixmonitor_ds->lock);
533 ast_cond_destroy(&mixmonitor->mixmonitor_ds->destruction_condition);
534 ast_free(mixmonitor->mixmonitor_ds);
537 ast_free(mixmonitor->name);
538 ast_free(mixmonitor->post_process);
539 ast_free(mixmonitor->filename);
540 ast_free(mixmonitor->filename_write);
541 ast_free(mixmonitor->filename_read);
543 /* Free everything in the recipient list */
544 clear_mixmonitor_recipient_list(mixmonitor);
546 /* clean stringfields */
547 ast_string_field_free_memory(mixmonitor);
549 if (mixmonitor->callid) {
550 ast_callid_unref(mixmonitor->callid);
552 ast_free(mixmonitor);
558 * \brief Copies the mixmonitor to all voicemail recipients
559 * \param mixmonitor The mixmonitor that needs to forward its file to recipients
560 * \param ext Format of the file that was saved
562 static void copy_to_voicemail(struct mixmonitor *mixmonitor, const char *ext, const char *filename)
564 struct vm_recipient *recipient = NULL;
565 struct ast_vm_recording_data recording_data;
566 if (ast_string_field_init(&recording_data, 512)) {
567 ast_log(LOG_ERROR, "Failed to string_field_init, skipping copy_to_voicemail\n");
571 /* Copy strings to stringfields that will be used for all recipients */
572 ast_string_field_set(&recording_data, recording_file, filename);
573 ast_string_field_set(&recording_data, recording_ext, ext);
574 ast_string_field_set(&recording_data, call_context, mixmonitor->call_context);
575 ast_string_field_set(&recording_data, call_macrocontext, mixmonitor->call_macrocontext);
576 ast_string_field_set(&recording_data, call_extension, mixmonitor->call_extension);
577 ast_string_field_set(&recording_data, call_callerchan, mixmonitor->call_callerchan);
578 ast_string_field_set(&recording_data, call_callerid, mixmonitor->call_callerid);
579 /* and call_priority gets copied too */
580 recording_data.call_priority = mixmonitor->call_priority;
582 AST_LIST_TRAVERSE(&mixmonitor->recipient_list, recipient, list) {
583 /* context, mailbox, and folder need to be set per recipient */
584 ast_string_field_set(&recording_data, context, recipient->context);
585 ast_string_field_set(&recording_data, mailbox, recipient->mailbox);
586 ast_string_field_set(&recording_data, folder, recipient->folder);
588 ast_verb(4, "MixMonitor attempting to send voicemail copy to %s@%s\n", recording_data.mailbox,
589 recording_data.context);
590 ast_app_copy_recording_to_vm(&recording_data);
593 /* Free the string fields for recording_data before exiting the function. */
594 ast_string_field_free_memory(&recording_data);
597 static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename, struct ast_filestream **fs, unsigned int *oflags, int *errflag, char **ext)
599 /* Initialize the file if not already done so */
600 char *last_slash = NULL;
601 if (!ast_strlen_zero(filename)) {
602 if (!*fs && !*errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
603 *oflags = O_CREAT | O_WRONLY;
604 *oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
606 last_slash = strrchr(filename, '/');
608 if ((*ext = strrchr(filename, '.')) && (*ext > last_slash)) {
615 if (!(*fs = ast_writefile(filename, *ext, NULL, *oflags, 0, 0666))) {
616 ast_log(LOG_ERROR, "Cannot open %s.%s\n", filename, *ext);
619 struct ast_filestream *tmp = *fs;
620 mixmonitor->mixmonitor_ds->samp_rate = MAX(mixmonitor->mixmonitor_ds->samp_rate, ast_format_get_sample_rate(tmp->fmt->format));
626 static void *mixmonitor_thread(void *obj)
628 struct mixmonitor *mixmonitor = obj;
630 char *fs_read_ext = "";
631 char *fs_write_ext = "";
633 struct ast_filestream **fs = NULL;
634 struct ast_filestream **fs_read = NULL;
635 struct ast_filestream **fs_write = NULL;
639 struct ast_format *format_slin;
641 /* Keep callid association before any log messages */
642 if (mixmonitor->callid) {
643 ast_callid_threadassoc_add(mixmonitor->callid);
646 ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name);
648 fs = &mixmonitor->mixmonitor_ds->fs;
649 fs_read = &mixmonitor->mixmonitor_ds->fs_read;
650 fs_write = &mixmonitor->mixmonitor_ds->fs_write;
652 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
653 mixmonitor_save_prep(mixmonitor, mixmonitor->filename, fs, &oflags, &errflag, &fs_ext);
654 mixmonitor_save_prep(mixmonitor, mixmonitor->filename_read, fs_read, &oflags, &errflag, &fs_read_ext);
655 mixmonitor_save_prep(mixmonitor, mixmonitor->filename_write, fs_write, &oflags, &errflag, &fs_write_ext);
657 format_slin = ast_format_cache_get_slin_by_rate(mixmonitor->mixmonitor_ds->samp_rate);
659 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
661 /* The audiohook must enter and exit the loop locked */
662 ast_audiohook_lock(&mixmonitor->audiohook);
663 while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING && !mixmonitor->mixmonitor_ds->fs_quit) {
664 struct ast_frame *fr = NULL;
665 struct ast_frame *fr_read = NULL;
666 struct ast_frame *fr_write = NULL;
668 if (!(fr = ast_audiohook_read_frame_all(&mixmonitor->audiohook, SAMPLES_PER_FRAME, format_slin,
669 &fr_read, &fr_write))) {
670 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
672 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
678 /* audiohook lock is not required for the next block.
679 * Unlock it, but remember to lock it before looping or exiting */
680 ast_audiohook_unlock(&mixmonitor->audiohook);
682 if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED)
683 || (mixmonitor->autochan->chan
684 && ast_channel_is_bridged(mixmonitor->autochan->chan))) {
685 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
687 /* Write out the frame(s) */
688 if ((*fs_read) && (fr_read)) {
689 struct ast_frame *cur;
691 for (cur = fr_read; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
692 ast_writestream(*fs_read, cur);
696 if ((*fs_write) && (fr_write)) {
697 struct ast_frame *cur;
699 for (cur = fr_write; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
700 ast_writestream(*fs_write, cur);
705 struct ast_frame *cur;
707 for (cur = fr; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
708 ast_writestream(*fs, cur);
711 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
713 /* All done! free it. */
715 ast_frame_free(fr, 0);
718 ast_frame_free(fr_read, 0);
721 ast_frame_free(fr_write, 0);
728 ast_audiohook_lock(&mixmonitor->audiohook);
732 ast_test_suite_event_notify("MIXMONITOR_END", "Channel: %s\r\n"
734 ast_channel_name(mixmonitor->autochan->chan),
735 mixmonitor->filename);
737 ast_audiohook_unlock(&mixmonitor->audiohook);
739 ast_autochan_destroy(mixmonitor->autochan);
741 /* Datastore cleanup. close the filestream and wait for ds destruction */
742 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
743 mixmonitor_ds_close_fs(mixmonitor->mixmonitor_ds);
744 if (!mixmonitor->mixmonitor_ds->destruction_ok) {
745 ast_cond_wait(&mixmonitor->mixmonitor_ds->destruction_condition, &mixmonitor->mixmonitor_ds->lock);
747 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
749 /* kill the audiohook */
750 destroy_monitor_audiohook(mixmonitor);
752 if (mixmonitor->post_process) {
753 ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
754 ast_safe_system(mixmonitor->post_process);
757 ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
759 if (!AST_LIST_EMPTY(&mixmonitor->recipient_list)) {
760 if (ast_strlen_zero(fs_ext)) {
761 ast_log(LOG_ERROR, "No file extension set for Mixmonitor %s. Skipping copy to voicemail.\n",
764 ast_verb(3, "Copying recordings for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
765 copy_to_voicemail(mixmonitor, fs_ext, mixmonitor->filename);
767 if (!ast_strlen_zero(fs_read_ext)) {
768 ast_verb(3, "Copying read recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
769 copy_to_voicemail(mixmonitor, fs_read_ext, mixmonitor->filename_read);
771 if (!ast_strlen_zero(fs_write_ext)) {
772 ast_verb(3, "Copying write recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
773 copy_to_voicemail(mixmonitor, fs_write_ext, mixmonitor->filename_write);
776 ast_debug(3, "No recipients to forward monitor to, moving on.\n");
779 mixmonitor_free(mixmonitor);
781 ast_module_unref(ast_module_info->self);
785 static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan, char **datastore_id, const char *beep_id)
787 struct ast_datastore *datastore = NULL;
788 struct mixmonitor_ds *mixmonitor_ds;
790 if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
794 if (ast_asprintf(datastore_id, "%p", mixmonitor_ds) == -1) {
795 ast_log(LOG_ERROR, "Failed to allocate memory for MixMonitor ID.\n");
798 ast_mutex_init(&mixmonitor_ds->lock);
799 ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
801 if (!(datastore = ast_datastore_alloc(&mixmonitor_ds_info, *datastore_id))) {
802 ast_mutex_destroy(&mixmonitor_ds->lock);
803 ast_cond_destroy(&mixmonitor_ds->destruction_condition);
804 ast_free(mixmonitor_ds);
809 mixmonitor_ds->samp_rate = 8000;
810 mixmonitor_ds->audiohook = &mixmonitor->audiohook;
811 mixmonitor_ds->filename = ast_strdup(mixmonitor->filename);
812 if (!ast_strlen_zero(beep_id)) {
813 mixmonitor_ds->beep_id = ast_strdup(beep_id);
815 datastore->data = mixmonitor_ds;
817 ast_channel_lock(chan);
818 ast_channel_datastore_add(chan, datastore);
819 ast_channel_unlock(chan);
821 mixmonitor->mixmonitor_ds = mixmonitor_ds;
825 static int launch_monitor_thread(struct ast_channel *chan, const char *filename,
826 unsigned int flags, int readvol, int writevol,
827 const char *post_process, const char *filename_write,
828 char *filename_read, const char *uid_channel_var,
829 const char *recipients, const char *beep_id)
832 struct mixmonitor *mixmonitor;
833 char postprocess2[1024] = "";
834 char *datastore_id = NULL;
837 /* If a post process system command is given attach it to the structure */
838 if (!ast_strlen_zero(post_process)) {
841 p1 = ast_strdupa(post_process);
842 for (p2 = p1; *p2; p2++) {
843 if (*p2 == '^' && *(p2+1) == '{') {
847 ast_channel_lock(chan);
848 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
849 ast_channel_unlock(chan);
852 /* Pre-allocate mixmonitor structure and spy */
853 if (!(mixmonitor = ast_calloc(1, sizeof(*mixmonitor)))) {
857 /* Now that the struct has been calloced, go ahead and initialize the string fields. */
858 if (ast_string_field_init(mixmonitor, 512)) {
859 mixmonitor_free(mixmonitor);
863 /* Setup the actual spy before creating our thread */
864 if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type, 0)) {
865 mixmonitor_free(mixmonitor);
869 /* Copy over flags and channel name */
870 mixmonitor->flags = flags;
871 if (!(mixmonitor->autochan = ast_autochan_setup(chan))) {
872 mixmonitor_free(mixmonitor);
876 if (!ast_strlen_zero(filename)) {
877 mixmonitor->filename = ast_strdup(filename);
880 if (!ast_strlen_zero(filename_write)) {
881 mixmonitor->filename_write = ast_strdup(filename_write);
884 if (!ast_strlen_zero(filename_read)) {
885 mixmonitor->filename_read = ast_strdup(filename_read);
888 if (setup_mixmonitor_ds(mixmonitor, chan, &datastore_id, beep_id)) {
889 ast_autochan_destroy(mixmonitor->autochan);
890 mixmonitor_free(mixmonitor);
891 ast_free(datastore_id);
895 if (!ast_strlen_zero(uid_channel_var)) {
897 pbx_builtin_setvar_helper(chan, uid_channel_var, datastore_id);
900 ast_free(datastore_id);
902 mixmonitor->name = ast_strdup(ast_channel_name(chan));
904 if (!ast_strlen_zero(postprocess2)) {
905 mixmonitor->post_process = ast_strdup(postprocess2);
908 if (!ast_strlen_zero(recipients)) {
910 struct ast_party_connected_line *connected;
912 ast_channel_lock(chan);
914 /* We use the connected line of the invoking channel for caller ID. */
916 connected = ast_channel_connected(chan);
917 ast_debug(3, "Connected Line CID = %d - %s : %d - %s\n", connected->id.name.valid,
918 connected->id.name.str, connected->id.number.valid,
919 connected->id.number.str);
920 ast_callerid_merge(callerid, sizeof(callerid),
921 S_COR(connected->id.name.valid, connected->id.name.str, NULL),
922 S_COR(connected->id.number.valid, connected->id.number.str, NULL),
925 ast_string_field_set(mixmonitor, call_context, ast_channel_context(chan));
926 ast_string_field_set(mixmonitor, call_macrocontext, ast_channel_macrocontext(chan));
927 ast_string_field_set(mixmonitor, call_extension, ast_channel_exten(chan));
928 ast_string_field_set(mixmonitor, call_callerchan, ast_channel_name(chan));
929 ast_string_field_set(mixmonitor, call_callerid, callerid);
930 mixmonitor->call_priority = ast_channel_priority(chan);
932 ast_channel_unlock(chan);
934 add_vm_recipients_from_string(mixmonitor, recipients);
937 ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
940 mixmonitor->audiohook.options.read_volume = readvol;
942 mixmonitor->audiohook.options.write_volume = writevol;
944 if (startmon(chan, &mixmonitor->audiohook)) {
945 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
946 mixmonitor_spy_type, ast_channel_name(chan));
947 ast_audiohook_destroy(&mixmonitor->audiohook);
948 mixmonitor_free(mixmonitor);
952 /* reference be released at mixmonitor destruction */
953 mixmonitor->callid = ast_read_threadstorage_callid();
955 return ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
958 /* a note on filename_parse: creates directory structure and assigns absolute path from relative paths for filenames */
959 /* requires immediate copying of string from return to retain data since otherwise it will immediately lose scope */
960 static char *filename_parse(char *filename, char *buffer, size_t len)
963 if (ast_strlen_zero(filename)) {
964 ast_log(LOG_WARNING, "No file name was provided for a file save option.\n");
965 } else if (filename[0] != '/') {
967 build = ast_alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(filename) + 3);
968 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, filename);
972 ast_copy_string(buffer, filename, len);
974 if ((slash = strrchr(filename, '/'))) {
977 ast_mkdir(filename, 0777);
982 static int mixmonitor_exec(struct ast_channel *chan, const char *data)
984 int x, readvol = 0, writevol = 0;
985 char *filename_read = NULL;
986 char *filename_write = NULL;
987 char filename_buffer[1024] = "";
988 char *uid_channel_var = NULL;
989 char beep_id[64] = "";
991 struct ast_flags flags = { 0 };
992 char *recipients = NULL;
994 AST_DECLARE_APP_ARGS(args,
995 AST_APP_ARG(filename);
996 AST_APP_ARG(options);
997 AST_APP_ARG(post_process);
1000 if (ast_strlen_zero(data)) {
1001 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename or ,t(filename) and/or r(filename)\n");
1005 parse = ast_strdupa(data);
1007 AST_STANDARD_APP_ARGS(args, parse);
1010 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
1012 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
1014 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
1015 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
1016 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
1017 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
1018 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
1020 readvol = get_volfactor(x);
1024 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
1025 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
1026 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
1027 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
1028 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
1030 writevol = get_volfactor(x);
1034 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
1035 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
1036 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
1037 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
1038 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
1040 readvol = writevol = get_volfactor(x);
1044 if (ast_test_flag(&flags, MUXFLAG_VMRECIPIENTS)) {
1045 if (ast_strlen_zero(opts[OPT_ARG_VMRECIPIENTS])) {
1046 ast_log(LOG_WARNING, "No voicemail recipients were specified for the vm copy ('m') option.\n");
1048 recipients = ast_strdupa(opts[OPT_ARG_VMRECIPIENTS]);
1052 if (ast_test_flag(&flags, MUXFLAG_WRITE)) {
1053 filename_write = ast_strdupa(filename_parse(opts[OPT_ARG_WRITENAME], filename_buffer, sizeof(filename_buffer)));
1056 if (ast_test_flag(&flags, MUXFLAG_READ)) {
1057 filename_read = ast_strdupa(filename_parse(opts[OPT_ARG_READNAME], filename_buffer, sizeof(filename_buffer)));
1060 if (ast_test_flag(&flags, MUXFLAG_UID)) {
1061 uid_channel_var = opts[OPT_ARG_UID];
1064 if (ast_test_flag(&flags, MUXFLAG_BEEP)) {
1065 const char *interval_str = S_OR(opts[OPT_ARG_BEEP_INTERVAL], "15");
1066 unsigned int interval = 15;
1068 if (sscanf(interval_str, "%30u", &interval) != 1) {
1069 ast_log(LOG_WARNING, "Invalid interval '%s' for periodic beep. Using default of %u\n",
1070 interval_str, interval);
1073 if (ast_beep_start(chan, interval, beep_id, sizeof(beep_id))) {
1074 ast_log(LOG_WARNING, "Unable to enable periodic beep, please ensure func_periodic_hook is loaded.\n");
1079 /* If there are no file writing arguments/options for the mix monitor, send a warning message and return -1 */
1081 if (!ast_test_flag(&flags, MUXFLAG_WRITE) && !ast_test_flag(&flags, MUXFLAG_READ) && ast_strlen_zero(args.filename)) {
1082 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
1086 /* If filename exists, try to create directories for it */
1087 if (!(ast_strlen_zero(args.filename))) {
1088 args.filename = ast_strdupa(filename_parse(args.filename, filename_buffer, sizeof(filename_buffer)));
1091 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
1093 /* If launch_monitor_thread works, the module reference must not be released until it is finished. */
1094 ast_module_ref(ast_module_info->self);
1095 if (launch_monitor_thread(chan,
1106 ast_module_unref(ast_module_info->self);
1112 static int stop_mixmonitor_full(struct ast_channel *chan, const char *data)
1114 struct ast_datastore *datastore = NULL;
1116 struct mixmonitor_ds *mixmonitor_ds;
1117 const char *beep_id = NULL;
1119 AST_DECLARE_APP_ARGS(args,
1120 AST_APP_ARG(mixmonid);
1123 if (!ast_strlen_zero(data)) {
1124 parse = ast_strdupa(data);
1127 AST_STANDARD_APP_ARGS(args, parse);
1129 ast_channel_lock(chan);
1131 datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, args.mixmonid);
1133 ast_channel_unlock(chan);
1136 mixmonitor_ds = datastore->data;
1138 ast_mutex_lock(&mixmonitor_ds->lock);
1140 /* closing the filestream here guarantees the file is available to the dialplan
1141 * after calling StopMixMonitor */
1142 mixmonitor_ds_close_fs(mixmonitor_ds);
1144 /* The mixmonitor thread may be waiting on the audiohook trigger.
1145 * In order to exit from the mixmonitor loop before waiting on channel
1146 * destruction, poke the audiohook trigger. */
1147 if (mixmonitor_ds->audiohook) {
1148 if (mixmonitor_ds->audiohook->status != AST_AUDIOHOOK_STATUS_DONE) {
1149 ast_audiohook_update_status(mixmonitor_ds->audiohook, AST_AUDIOHOOK_STATUS_SHUTDOWN);
1151 ast_audiohook_lock(mixmonitor_ds->audiohook);
1152 ast_cond_signal(&mixmonitor_ds->audiohook->trigger);
1153 ast_audiohook_unlock(mixmonitor_ds->audiohook);
1154 mixmonitor_ds->audiohook = NULL;
1157 if (!ast_strlen_zero(mixmonitor_ds->beep_id)) {
1158 beep_id = ast_strdupa(mixmonitor_ds->beep_id);
1161 ast_mutex_unlock(&mixmonitor_ds->lock);
1163 /* Remove the datastore so the monitor thread can exit */
1164 if (!ast_channel_datastore_remove(chan, datastore)) {
1165 ast_datastore_free(datastore);
1167 ast_channel_unlock(chan);
1169 if (!ast_strlen_zero(beep_id)) {
1170 ast_beep_stop(chan, beep_id);
1176 static int stop_mixmonitor_exec(struct ast_channel *chan, const char *data)
1178 stop_mixmonitor_full(chan, data);
1182 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1184 struct ast_channel *chan;
1185 struct ast_datastore *datastore = NULL;
1186 struct mixmonitor_ds *mixmonitor_ds = NULL;
1190 e->command = "mixmonitor {start|stop|list}";
1192 "Usage: mixmonitor start <chan_name> [args]\n"
1193 " The optional arguments are passed to the MixMonitor application.\n"
1194 " mixmonitor stop <chan_name> [args]\n"
1195 " The optional arguments are passed to the StopMixMonitor application.\n"
1196 " mixmonitor list <chan_name>\n";
1199 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
1203 return CLI_SHOWUSAGE;
1206 if (!(chan = ast_channel_get_by_name_prefix(a->argv[2], strlen(a->argv[2])))) {
1207 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
1208 /* Technically this is a failure, but we don't want 2 errors printing out */
1212 if (!strcasecmp(a->argv[1], "start")) {
1213 mixmonitor_exec(chan, (a->argc >= 4) ? a->argv[3] : "");
1214 } else if (!strcasecmp(a->argv[1], "stop")){
1215 stop_mixmonitor_exec(chan, (a->argc >= 4) ? a->argv[3] : "");
1216 } else if (!strcasecmp(a->argv[1], "list")) {
1217 ast_cli(a->fd, "MixMonitor ID\tFile\tReceive File\tTransmit File\n");
1218 ast_cli(a->fd, "=========================================================================\n");
1219 ast_channel_lock(chan);
1220 AST_LIST_TRAVERSE(ast_channel_datastores(chan), datastore, entry) {
1221 if (datastore->info == &mixmonitor_ds_info) {
1222 char *filename = "";
1223 char *filename_read = "";
1224 char *filename_write = "";
1226 mixmonitor_ds = datastore->data;
1227 if (mixmonitor_ds->fs) {
1228 filename = mixmonitor_ds->fs->filename;
1230 if (mixmonitor_ds->fs_read) {
1231 filename_read = mixmonitor_ds->fs_read->filename;
1233 if (mixmonitor_ds->fs_write) {
1234 filename_write = mixmonitor_ds->fs_write->filename;
1236 ast_cli(a->fd, "%p\t%s\t%s\t%s\n", mixmonitor_ds, filename, filename_read, filename_write);
1239 ast_channel_unlock(chan);
1241 chan = ast_channel_unref(chan);
1242 return CLI_SHOWUSAGE;
1245 chan = ast_channel_unref(chan);
1250 /*! \brief Mute / unmute a MixMonitor channel */
1251 static int manager_mute_mixmonitor(struct mansession *s, const struct message *m)
1253 struct ast_channel *c;
1254 const char *name = astman_get_header(m, "Channel");
1255 const char *id = astman_get_header(m, "ActionID");
1256 const char *state = astman_get_header(m, "State");
1257 const char *direction = astman_get_header(m,"Direction");
1259 enum ast_audiohook_flags flag;
1261 if (ast_strlen_zero(direction)) {
1262 astman_send_error(s, m, "No direction specified. Must be read, write or both");
1266 if (!strcasecmp(direction, "read")) {
1267 flag = AST_AUDIOHOOK_MUTE_READ;
1268 } else if (!strcasecmp(direction, "write")) {
1269 flag = AST_AUDIOHOOK_MUTE_WRITE;
1270 } else if (!strcasecmp(direction, "both")) {
1271 flag = AST_AUDIOHOOK_MUTE_READ | AST_AUDIOHOOK_MUTE_WRITE;
1273 astman_send_error(s, m, "Invalid direction specified. Must be read, write or both");
1277 if (ast_strlen_zero(name)) {
1278 astman_send_error(s, m, "No channel specified");
1282 if (ast_strlen_zero(state)) {
1283 astman_send_error(s, m, "No state specified");
1287 clearmute = ast_false(state);
1289 c = ast_channel_get_by_name(name);
1291 astman_send_error(s, m, "No such channel");
1295 if (ast_audiohook_set_mute(c, mixmonitor_spy_type, flag, clearmute)) {
1296 ast_channel_unref(c);
1297 astman_send_error(s, m, "Cannot set mute flag");
1301 astman_append(s, "Response: Success\r\n");
1303 if (!ast_strlen_zero(id)) {
1304 astman_append(s, "ActionID: %s\r\n", id);
1307 astman_append(s, "\r\n");
1309 ast_channel_unref(c);
1314 static int start_mixmonitor_callback(struct ast_channel *chan, const char *filename, const char *options)
1316 char args[PATH_MAX];
1318 if (ast_strlen_zero(options)) {
1319 snprintf(args, sizeof(args), "%s", filename);
1321 snprintf(args, sizeof(args), "%s,%s", filename, options);
1324 return mixmonitor_exec(chan, args);
1327 static int stop_mixmonitor_callback(struct ast_channel *chan, const char *mixmonitor_id)
1329 return stop_mixmonitor_full(chan, mixmonitor_id);
1332 static int manager_mixmonitor(struct mansession *s, const struct message *m)
1334 struct ast_channel *c;
1335 const char *name = astman_get_header(m, "Channel");
1336 const char *id = astman_get_header(m, "ActionID");
1337 const char *file = astman_get_header(m, "File");
1338 const char *options = astman_get_header(m, "Options");
1339 const char *command = astman_get_header(m, "Command");
1340 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
1341 struct ast_flags flags = { 0 };
1342 char *uid_channel_var = NULL;
1343 const char *mixmonitor_id = NULL;
1345 char args[PATH_MAX];
1347 if (ast_strlen_zero(name)) {
1348 astman_send_error(s, m, "No channel specified");
1352 c = ast_channel_get_by_name(name);
1354 astman_send_error(s, m, "No such channel");
1358 if (!ast_strlen_zero(options)) {
1359 ast_app_parse_options(mixmonitor_opts, &flags, opts, ast_strdupa(options));
1362 snprintf(args, sizeof(args), "%s,%s,%s", file, options, command);
1364 res = mixmonitor_exec(c, args);
1366 if (ast_test_flag(&flags, MUXFLAG_UID)) {
1367 uid_channel_var = opts[OPT_ARG_UID];
1368 ast_channel_lock(c);
1369 mixmonitor_id = pbx_builtin_getvar_helper(c, uid_channel_var);
1370 mixmonitor_id = ast_strdupa(S_OR(mixmonitor_id, ""));
1371 ast_channel_unlock(c);
1375 ast_channel_unref(c);
1376 astman_send_error(s, m, "Could not start monitoring channel");
1380 astman_append(s, "Response: Success\r\n");
1382 if (!ast_strlen_zero(id)) {
1383 astman_append(s, "ActionID: %s\r\n", id);
1386 if (!ast_strlen_zero(mixmonitor_id)) {
1387 astman_append(s, "MixMonitorID: %s\r\n", mixmonitor_id);
1390 astman_append(s, "\r\n");
1392 ast_channel_unref(c);
1397 static int manager_stop_mixmonitor(struct mansession *s, const struct message *m)
1399 struct ast_channel *c;
1400 const char *name = astman_get_header(m, "Channel");
1401 const char *id = astman_get_header(m, "ActionID");
1402 const char *mixmonitor_id = astman_get_header(m, "MixMonitorID");
1405 if (ast_strlen_zero(name)) {
1406 astman_send_error(s, m, "No channel specified");
1410 c = ast_channel_get_by_name(name);
1412 astman_send_error(s, m, "No such channel");
1416 res = stop_mixmonitor_full(c, mixmonitor_id);
1418 ast_channel_unref(c);
1419 astman_send_error(s, m, "Could not stop monitoring channel");
1423 astman_append(s, "Response: Success\r\n");
1425 if (!ast_strlen_zero(id)) {
1426 astman_append(s, "ActionID: %s\r\n", id);
1429 astman_append(s, "\r\n");
1431 ast_channel_unref(c);
1436 static int func_mixmonitor_read(struct ast_channel *chan, const char *cmd, char *data,
1437 char *buf, size_t len)
1439 struct ast_datastore *datastore;
1440 struct mixmonitor_ds *ds_data;
1441 AST_DECLARE_APP_ARGS(args,
1446 AST_STANDARD_APP_ARGS(args, data);
1448 if (ast_strlen_zero(args.id) || ast_strlen_zero(args.key)) {
1449 ast_log(LOG_WARNING, "Not enough arguments provided to %s. "
1450 "An ID and key must be provided\n", cmd);
1454 ast_channel_lock(chan);
1455 datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, args.id);
1456 ast_channel_unlock(chan);
1459 ast_log(LOG_WARNING, "Could not find MixMonitor with ID %s\n", args.id);
1463 ds_data = datastore->data;
1465 if (!strcasecmp(args.key, "filename")) {
1466 ast_copy_string(buf, ds_data->filename, len);
1468 ast_log(LOG_WARNING, "Unrecognized %s option %s\n", cmd, args.key);
1474 static struct ast_custom_function mixmonitor_function = {
1475 .name = "MIXMONITOR",
1476 .read = func_mixmonitor_read,
1479 static struct ast_cli_entry cli_mixmonitor[] = {
1480 AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
1483 static int set_mixmonitor_methods(void)
1485 struct ast_mixmonitor_methods mixmonitor_methods = {
1486 .start = start_mixmonitor_callback,
1487 .stop = stop_mixmonitor_callback,
1490 return ast_set_mixmonitor_methods(&mixmonitor_methods);
1493 static int clear_mixmonitor_methods(void)
1495 return ast_clear_mixmonitor_methods();
1498 static int unload_module(void)
1502 ast_cli_unregister_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
1503 res = ast_unregister_application(stop_app);
1504 res |= ast_unregister_application(app);
1505 res |= ast_manager_unregister("MixMonitorMute");
1506 res |= ast_manager_unregister("MixMonitor");
1507 res |= ast_manager_unregister("StopMixMonitor");
1508 res |= ast_custom_function_unregister(&mixmonitor_function);
1509 res |= clear_mixmonitor_methods();
1514 static int load_module(void)
1518 ast_cli_register_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
1519 res = ast_register_application_xml(app, mixmonitor_exec);
1520 res |= ast_register_application_xml(stop_app, stop_mixmonitor_exec);
1521 res |= ast_manager_register_xml("MixMonitorMute", EVENT_FLAG_SYSTEM | EVENT_FLAG_CALL, manager_mute_mixmonitor);
1522 res |= ast_manager_register_xml("MixMonitor", EVENT_FLAG_SYSTEM, manager_mixmonitor);
1523 res |= ast_manager_register_xml("StopMixMonitor", EVENT_FLAG_SYSTEM | EVENT_FLAG_CALL, manager_stop_mixmonitor);
1524 res |= ast_custom_function_register(&mixmonitor_function);
1525 res |= set_mixmonitor_methods();
1530 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");