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/file.h"
46 #include "asterisk/audiohook.h"
47 #include "asterisk/pbx.h"
48 #include "asterisk/module.h"
49 #include "asterisk/cli.h"
50 #include "asterisk/app.h"
51 #include "asterisk/channel.h"
52 #include "asterisk/autochan.h"
53 #include "asterisk/manager.h"
54 #include "asterisk/mod_format.h"
55 #include "asterisk/linkedlists.h"
58 <application name="MixMonitor" language="en_US">
60 Record a call and mix the audio during the recording. Use of StopMixMonitor is required
61 to guarantee the audio file is available for processing during dialplan execution.
64 <parameter name="file" required="true" argsep=".">
65 <argument name="filename" required="true">
66 <para>If <replaceable>filename</replaceable> is an absolute path, uses that path, otherwise
67 creates the file in the configured monitoring directory from <filename>asterisk.conf.</filename></para>
69 <argument name="extension" required="true" />
71 <parameter name="options">
74 <para>Append to the file instead of overwriting it.</para>
77 <para>Only save audio to the file while the channel is bridged.</para>
78 <note><para>Does not include conferences or sounds played to each bridged party</para></note>
79 <note><para>If you utilize this option inside a Local channel, you must make sure the Local
80 channel is not optimized away. To do this, be sure to call your Local channel with the
81 <literal>/n</literal> option. For example: Dial(Local/start@mycontext/n)</para></note>
84 <para>Adjust the <emphasis>heard</emphasis> volume by a factor of <replaceable>x</replaceable>
85 (range <literal>-4</literal> to <literal>4</literal>)</para>
86 <argument name="x" required="true" />
89 <para>Adjust the <emphasis>spoken</emphasis> volume by a factor
90 of <replaceable>x</replaceable> (range <literal>-4</literal> to <literal>4</literal>)</para>
91 <argument name="x" required="true" />
94 <para>Adjust both, <emphasis>heard and spoken</emphasis> volumes by a factor
95 of <replaceable>x</replaceable> (range <literal>-4</literal> to <literal>4</literal>)</para>
96 <argument name="x" required="true" />
99 <argument name="file" required="true" />
100 <para>Use the specified file to record the <emphasis>receive</emphasis> audio feed.
101 Like with the basic filename argument, if an absolute path isn't given, it will create
102 the file in the configured monitoring directory.</para>
106 <argument name="file" required="true" />
107 <para>Use the specified file to record the <emphasis>transmit</emphasis> audio feed.
108 Like with the basic filename argument, if an absolute path isn't given, it will create
109 the file in the configured monitoring directory.</para>
112 <argument name="chanvar" required="true" />
113 <para>Stores the MixMonitor's ID on this channel variable.</para>
117 <parameter name="command">
118 <para>Will be executed when the recording is over.</para>
119 <para>Any strings matching <literal>^{X}</literal> will be unescaped to <variable>X</variable>.</para>
120 <para>All variables will be evaluated at the time MixMonitor is called.</para>
124 <para>Records the audio on the current channel to the specified file.</para>
125 <para>This application does not automatically answer and should be preceeded by
126 an application such as Answer or Progress().</para>
128 <variable name="MIXMONITOR_FILENAME">
129 <para>Will contain the filename used to record.</para>
134 <ref type="application">Monitor</ref>
135 <ref type="application">StopMixMonitor</ref>
136 <ref type="application">PauseMonitor</ref>
137 <ref type="application">UnpauseMonitor</ref>
140 <application name="StopMixMonitor" language="en_US">
142 Stop recording a call through MixMonitor, and free the recording's file handle.
145 <parameter name="MixMonitorID" required="false">
146 <para>If a valid ID is provided, then this command will stop only that specific
151 <para>Stops the audio recording that was started with a call to <literal>MixMonitor()</literal>
152 on the current channel.</para>
155 <ref type="application">MixMonitor</ref>
158 <manager name="MixMonitorMute" language="en_US">
160 Mute / unMute a Mixmonitor recording.
163 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
164 <parameter name="Channel" required="true">
165 <para>Used to specify the channel to mute.</para>
167 <parameter name="Direction">
168 <para>Which part of the recording to mute: read, write or both (from channel, to channel or both channels).</para>
170 <parameter name="State">
171 <para>Turn mute on or off : 1 to turn on, 0 to turn off.</para>
175 <para>This action may be used to mute a MixMonitor recording.</para>
178 <manager name="MixMonitor" language="en_US">
180 Record a call and mix the audio during the recording. Use of StopMixMonitor is required
181 to guarantee the audio file is available for processing during dialplan execution.
184 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
185 <parameter name="Channel" required="true">
186 <para>Used to specify the channel to record.</para>
188 <parameter name="File">
189 <para>Is the name of the file created in the monitor spool directory.
190 Defaults to the same name as the channel (with slashes replaced with dashes).
191 This argument is optional if you specify to record unidirectional audio with
192 either the r(filename) or t(filename) options in the options field. If
193 neither MIXMONITOR_FILENAME or this parameter is set, the mixed stream won't
196 <parameter name="options">
197 <para>Options that apply to the MixMonitor in the same way as they
198 would apply if invoked from the MixMonitor application. For a list of
199 available options, see the documentation for the mixmonitor application. </para>
203 <para>This action records the audio on the current channel to the specified file.</para>
205 <variable name="MIXMONITOR_FILENAME">
206 <para>Will contain the filename used to record the mixed stream.</para>
211 <manager name="StopMixMonitor" language="en_US">
213 Stop recording a call through MixMonitor, and free the recording's file handle.
216 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
217 <parameter name="Channel" required="true">
218 <para>The name of the channel monitored.</para>
220 <parameter name="MixMonitorID" required="false">
221 <para>If a valid ID is provided, then this command will stop only that specific
226 <para>This action stops the audio recording that was started with the <literal>MixMonitor</literal>
227 action on the current channel.</para>
233 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
235 static const char * const app = "MixMonitor";
237 static const char * const stop_app = "StopMixMonitor";
239 static const char * const mixmonitor_spy_type = "MixMonitor";
242 struct ast_audiohook audiohook;
245 char *filename_write;
249 struct ast_autochan *autochan;
250 struct mixmonitor_ds *mixmonitor_ds;
253 enum mixmonitor_flags {
254 MUXFLAG_APPEND = (1 << 1),
255 MUXFLAG_BRIDGED = (1 << 2),
256 MUXFLAG_VOLUME = (1 << 3),
257 MUXFLAG_READVOLUME = (1 << 4),
258 MUXFLAG_WRITEVOLUME = (1 << 5),
259 MUXFLAG_READ = (1 << 6),
260 MUXFLAG_WRITE = (1 << 7),
261 MUXFLAG_COMBINED = (1 << 8),
262 MUXFLAG_UID = (1 << 9),
265 enum mixmonitor_args {
266 OPT_ARG_READVOLUME = 0,
272 OPT_ARG_ARRAY_SIZE, /* Always last element of the enum */
275 AST_APP_OPTIONS(mixmonitor_opts, {
276 AST_APP_OPTION('a', MUXFLAG_APPEND),
277 AST_APP_OPTION('b', MUXFLAG_BRIDGED),
278 AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
279 AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
280 AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
281 AST_APP_OPTION_ARG('r', MUXFLAG_READ, OPT_ARG_READNAME),
282 AST_APP_OPTION_ARG('t', MUXFLAG_WRITE, OPT_ARG_WRITENAME),
283 AST_APP_OPTION_ARG('i', MUXFLAG_UID, OPT_ARG_UID),
286 struct mixmonitor_ds {
287 unsigned int destruction_ok;
288 ast_cond_t destruction_condition;
291 /* The filestream is held in the datastore so it can be stopped
292 * immediately during stop_mixmonitor or channel destruction. */
295 struct ast_filestream *fs;
296 struct ast_filestream *fs_read;
297 struct ast_filestream *fs_write;
299 struct ast_audiohook *audiohook;
301 unsigned int samp_rate;
306 * \pre mixmonitor_ds must be locked before calling this function
308 static void mixmonitor_ds_close_fs(struct mixmonitor_ds *mixmonitor_ds)
310 unsigned char quitting = 0;
312 if (mixmonitor_ds->fs) {
314 ast_closestream(mixmonitor_ds->fs);
315 mixmonitor_ds->fs = NULL;
316 ast_verb(2, "MixMonitor close filestream (mixed)\n");
319 if (mixmonitor_ds->fs_read) {
321 ast_closestream(mixmonitor_ds->fs_read);
322 mixmonitor_ds->fs_read = NULL;
323 ast_verb(2, "MixMonitor close filestream (read)\n");
326 if (mixmonitor_ds->fs_write) {
328 ast_closestream(mixmonitor_ds->fs_write);
329 mixmonitor_ds->fs_write = NULL;
330 ast_verb(2, "MixMonitor close filestream (write)\n");
334 mixmonitor_ds->fs_quit = 1;
338 static void mixmonitor_ds_destroy(void *data)
340 struct mixmonitor_ds *mixmonitor_ds = data;
342 ast_mutex_lock(&mixmonitor_ds->lock);
343 mixmonitor_ds->audiohook = NULL;
344 mixmonitor_ds->destruction_ok = 1;
345 ast_cond_signal(&mixmonitor_ds->destruction_condition);
346 ast_mutex_unlock(&mixmonitor_ds->lock);
349 static struct ast_datastore_info mixmonitor_ds_info = {
350 .type = "mixmonitor",
351 .destroy = mixmonitor_ds_destroy,
354 static void destroy_monitor_audiohook(struct mixmonitor *mixmonitor)
356 if (mixmonitor->mixmonitor_ds) {
357 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
358 mixmonitor->mixmonitor_ds->audiohook = NULL;
359 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
361 /* kill the audiohook.*/
362 ast_audiohook_lock(&mixmonitor->audiohook);
363 ast_audiohook_detach(&mixmonitor->audiohook);
364 ast_audiohook_unlock(&mixmonitor->audiohook);
365 ast_audiohook_destroy(&mixmonitor->audiohook);
368 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
370 struct ast_channel *peer = NULL;
376 ast_audiohook_attach(chan, audiohook);
378 if (!res && ast_test_flag(ast_channel_flags(chan), AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
379 ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
384 #define SAMPLES_PER_FRAME 160
386 static void mixmonitor_free(struct mixmonitor *mixmonitor)
389 if (mixmonitor->mixmonitor_ds) {
390 ast_mutex_destroy(&mixmonitor->mixmonitor_ds->lock);
391 ast_cond_destroy(&mixmonitor->mixmonitor_ds->destruction_condition);
392 ast_free(mixmonitor->filename_write);
393 ast_free(mixmonitor->filename_read);
394 ast_free(mixmonitor->mixmonitor_ds);
395 ast_free(mixmonitor->name);
396 ast_free(mixmonitor->post_process);
398 ast_free(mixmonitor);
402 static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename, struct ast_filestream **fs, unsigned int *oflags, int *errflag)
404 /* Initialize the file if not already done so */
406 char *last_slash = NULL;
407 if (!ast_strlen_zero(filename)) {
408 if (!*fs && !*errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
409 *oflags = O_CREAT | O_WRONLY;
410 *oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
412 last_slash = strrchr(filename, '/');
414 if ((ext = strrchr(filename, '.')) && (ext > last_slash)) {
420 if (!(*fs = ast_writefile(filename, ext, NULL, *oflags, 0, 0666))) {
421 ast_log(LOG_ERROR, "Cannot open %s.%s\n", filename, ext);
424 struct ast_filestream *tmp = *fs;
425 mixmonitor->mixmonitor_ds->samp_rate = MAX(mixmonitor->mixmonitor_ds->samp_rate, ast_format_rate(&tmp->fmt->format));
431 static void *mixmonitor_thread(void *obj)
433 struct mixmonitor *mixmonitor = obj;
435 struct ast_filestream **fs = NULL;
436 struct ast_filestream **fs_read = NULL;
437 struct ast_filestream **fs_write = NULL;
441 struct ast_format format_slin;
443 ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name);
445 fs = &mixmonitor->mixmonitor_ds->fs;
446 fs_read = &mixmonitor->mixmonitor_ds->fs_read;
447 fs_write = &mixmonitor->mixmonitor_ds->fs_write;
449 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
450 mixmonitor_save_prep(mixmonitor, mixmonitor->filename, fs, &oflags, &errflag);
451 mixmonitor_save_prep(mixmonitor, mixmonitor->filename_read, fs_read, &oflags, &errflag);
452 mixmonitor_save_prep(mixmonitor, mixmonitor->filename_write, fs_write, &oflags, &errflag);
454 ast_format_set(&format_slin, ast_format_slin_by_rate(mixmonitor->mixmonitor_ds->samp_rate), 0);
456 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
459 /* The audiohook must enter and exit the loop locked */
460 ast_audiohook_lock(&mixmonitor->audiohook);
461 while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING && !mixmonitor->mixmonitor_ds->fs_quit) {
462 struct ast_frame *fr = NULL;
463 struct ast_frame *fr_read = NULL;
464 struct ast_frame *fr_write = NULL;
466 if (!(fr = ast_audiohook_read_frame_all(&mixmonitor->audiohook, SAMPLES_PER_FRAME, &format_slin,
467 &fr_read, &fr_write))) {
468 ast_audiohook_trigger_wait(&mixmonitor->audiohook);
470 if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
476 /* audiohook lock is not required for the next block.
477 * Unlock it, but remember to lock it before looping or exiting */
478 ast_audiohook_unlock(&mixmonitor->audiohook);
480 if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || (mixmonitor->autochan->chan && ast_bridged_channel(mixmonitor->autochan->chan))) {
481 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
483 /* Write out the frame(s) */
484 if ((*fs_read) && (fr_read)) {
485 struct ast_frame *cur;
487 for (cur = fr_read; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
488 ast_writestream(*fs_read, cur);
492 if ((*fs_write) && (fr_write)) {
493 struct ast_frame *cur;
495 for (cur = fr_write; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
496 ast_writestream(*fs_write, cur);
501 struct ast_frame *cur;
503 for (cur = fr; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
504 ast_writestream(*fs, cur);
507 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
509 /* All done! free it. */
511 ast_frame_free(fr, 0);
514 ast_frame_free(fr_read, 0);
517 ast_frame_free(fr_write, 0);
524 ast_audiohook_lock(&mixmonitor->audiohook);
526 ast_audiohook_unlock(&mixmonitor->audiohook);
528 ast_autochan_destroy(mixmonitor->autochan);
530 /* Datastore cleanup. close the filestream and wait for ds destruction */
531 ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
532 mixmonitor_ds_close_fs(mixmonitor->mixmonitor_ds);
533 if (!mixmonitor->mixmonitor_ds->destruction_ok) {
534 ast_cond_wait(&mixmonitor->mixmonitor_ds->destruction_condition, &mixmonitor->mixmonitor_ds->lock);
536 ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
538 /* kill the audiohook */
539 destroy_monitor_audiohook(mixmonitor);
541 if (mixmonitor->post_process) {
542 ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
543 ast_safe_system(mixmonitor->post_process);
546 ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
547 mixmonitor_free(mixmonitor);
551 static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan, char **datastore_id)
553 struct ast_datastore *datastore = NULL;
554 struct mixmonitor_ds *mixmonitor_ds;
556 if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
560 if (ast_asprintf(datastore_id, "%p", mixmonitor_ds) == -1) {
561 ast_log(LOG_ERROR, "Failed to allocate memory for MixMonitor ID.\n");
564 ast_mutex_init(&mixmonitor_ds->lock);
565 ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
567 if (!(datastore = ast_datastore_alloc(&mixmonitor_ds_info, *datastore_id))) {
568 ast_mutex_destroy(&mixmonitor_ds->lock);
569 ast_cond_destroy(&mixmonitor_ds->destruction_condition);
570 ast_free(mixmonitor_ds);
575 mixmonitor_ds->samp_rate = 8000;
576 mixmonitor_ds->audiohook = &mixmonitor->audiohook;
577 datastore->data = mixmonitor_ds;
579 ast_channel_lock(chan);
580 ast_channel_datastore_add(chan, datastore);
581 ast_channel_unlock(chan);
583 mixmonitor->mixmonitor_ds = mixmonitor_ds;
587 static void launch_monitor_thread(struct ast_channel *chan, const char *filename,
588 unsigned int flags, int readvol, int writevol,
589 const char *post_process, const char *filename_write,
590 char *filename_read, const char *uid_channel_var)
593 struct mixmonitor *mixmonitor;
594 char postprocess2[1024] = "";
595 char *datastore_id = NULL;
598 /* If a post process system command is given attach it to the structure */
599 if (!ast_strlen_zero(post_process)) {
602 p1 = ast_strdupa(post_process);
603 for (p2 = p1; *p2; p2++) {
604 if (*p2 == '^' && *(p2+1) == '{') {
608 pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
611 /* Pre-allocate mixmonitor structure and spy */
612 if (!(mixmonitor = ast_calloc(1, sizeof(*mixmonitor)))) {
616 /* Setup the actual spy before creating our thread */
617 if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type, 0)) {
618 mixmonitor_free(mixmonitor);
622 /* Copy over flags and channel name */
623 mixmonitor->flags = flags;
624 if (!(mixmonitor->autochan = ast_autochan_setup(chan))) {
625 mixmonitor_free(mixmonitor);
629 if (setup_mixmonitor_ds(mixmonitor, chan, &datastore_id)) {
630 ast_autochan_destroy(mixmonitor->autochan);
631 mixmonitor_free(mixmonitor);
632 ast_free(datastore_id);
636 if (!ast_strlen_zero(uid_channel_var)) {
638 pbx_builtin_setvar_helper(chan, uid_channel_var, datastore_id);
641 ast_free(datastore_id);
644 mixmonitor->name = ast_strdup(ast_channel_name(chan));
646 if (!ast_strlen_zero(postprocess2)) {
647 mixmonitor->post_process = ast_strdup(postprocess2);
650 if (!ast_strlen_zero(filename)) {
651 mixmonitor->filename = ast_strdup(filename);
654 if (!ast_strlen_zero(filename_write)) {
655 mixmonitor->filename_write = ast_strdup(filename_write);
658 if (!ast_strlen_zero(filename_read)) {
659 mixmonitor->filename_read = ast_strdup(filename_read);
662 ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
665 mixmonitor->audiohook.options.read_volume = readvol;
667 mixmonitor->audiohook.options.write_volume = writevol;
669 if (startmon(chan, &mixmonitor->audiohook)) {
670 ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
671 mixmonitor_spy_type, ast_channel_name(chan));
672 ast_audiohook_destroy(&mixmonitor->audiohook);
673 mixmonitor_free(mixmonitor);
677 ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
680 /* a note on filename_parse: creates directory structure and assigns absolute path from relative paths for filenames */
681 /* requires immediate copying of string from return to retain data since otherwise it will immediately lose scope */
682 static char *filename_parse(char *filename, char *buffer, size_t len)
685 if (ast_strlen_zero(filename)) {
686 ast_log(LOG_WARNING, "No file name was provided for a file save option.\n");
687 } else if (filename[0] != '/') {
689 build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(filename) + 3);
690 sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, filename);
694 ast_copy_string(buffer, filename, len);
696 if ((slash = strrchr(filename, '/'))) {
699 ast_mkdir(filename, 0777);
704 static int mixmonitor_exec(struct ast_channel *chan, const char *data)
706 int x, readvol = 0, writevol = 0;
707 char *filename_read = NULL;
708 char *filename_write = NULL;
709 char filename_buffer[1024] = "";
710 char *uid_channel_var = NULL;
712 struct ast_flags flags = { 0 };
714 AST_DECLARE_APP_ARGS(args,
715 AST_APP_ARG(filename);
716 AST_APP_ARG(options);
717 AST_APP_ARG(post_process);
720 if (ast_strlen_zero(data)) {
721 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename or ,t(filename) and/or r(filename)\n");
725 parse = ast_strdupa(data);
727 AST_STANDARD_APP_ARGS(args, parse);
730 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
732 ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
734 if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
735 if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
736 ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
737 } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
738 ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
740 readvol = get_volfactor(x);
744 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
745 if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
746 ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
747 } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
748 ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
750 writevol = get_volfactor(x);
754 if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
755 if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
756 ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
757 } else if ((sscanf(opts[OPT_ARG_VOLUME], "%2d", &x) != 1) || (x < -4) || (x > 4)) {
758 ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
760 readvol = writevol = get_volfactor(x);
764 if (ast_test_flag(&flags, MUXFLAG_WRITE)) {
765 filename_write = ast_strdupa(filename_parse(opts[OPT_ARG_WRITENAME], filename_buffer, sizeof(filename_buffer)));
768 if (ast_test_flag(&flags, MUXFLAG_READ)) {
769 filename_read = ast_strdupa(filename_parse(opts[OPT_ARG_READNAME], filename_buffer, sizeof(filename_buffer)));
772 if (ast_test_flag(&flags, MUXFLAG_UID)) {
773 uid_channel_var = opts[OPT_ARG_UID];
776 /* If there are no file writing arguments/options for the mix monitor, send a warning message and return -1 */
778 if (!ast_test_flag(&flags, MUXFLAG_WRITE) && !ast_test_flag(&flags, MUXFLAG_READ) && ast_strlen_zero(args.filename)) {
779 ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
783 /* If filename exists, try to create directories for it */
784 if (!(ast_strlen_zero(args.filename))) {
785 args.filename = ast_strdupa(filename_parse(args.filename, filename_buffer, sizeof(filename_buffer)));
788 pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
789 launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process, filename_write, filename_read, uid_channel_var);
794 static int stop_mixmonitor_exec(struct ast_channel *chan, const char *data)
796 struct ast_datastore *datastore = NULL;
798 struct mixmonitor_ds *mixmonitor_ds;
800 AST_DECLARE_APP_ARGS(args,
801 AST_APP_ARG(mixmonid);
804 if (!ast_strlen_zero(data)) {
805 parse = ast_strdupa(data);
808 AST_STANDARD_APP_ARGS(args, parse);
810 ast_channel_lock(chan);
812 if (!(datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info, args.mixmonid))) {
813 ast_channel_unlock(chan);
816 mixmonitor_ds = datastore->data;
818 ast_mutex_lock(&mixmonitor_ds->lock);
820 /* closing the filestream here guarantees the file is avaliable to the dialplan
821 * after calling StopMixMonitor */
822 mixmonitor_ds_close_fs(mixmonitor_ds);
824 /* The mixmonitor thread may be waiting on the audiohook trigger.
825 * In order to exit from the mixmonitor loop before waiting on channel
826 * destruction, poke the audiohook trigger. */
827 if (mixmonitor_ds->audiohook) {
828 if (mixmonitor_ds->audiohook->status != AST_AUDIOHOOK_STATUS_DONE) {
829 ast_audiohook_update_status(mixmonitor_ds->audiohook, AST_AUDIOHOOK_STATUS_SHUTDOWN);
831 ast_audiohook_lock(mixmonitor_ds->audiohook);
832 ast_cond_signal(&mixmonitor_ds->audiohook->trigger);
833 ast_audiohook_unlock(mixmonitor_ds->audiohook);
834 mixmonitor_ds->audiohook = NULL;
837 ast_mutex_unlock(&mixmonitor_ds->lock);
839 /* Remove the datastore so the monitor thread can exit */
840 if (!ast_channel_datastore_remove(chan, datastore)) {
841 ast_datastore_free(datastore);
843 ast_channel_unlock(chan);
848 static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
850 struct ast_channel *chan;
851 struct ast_datastore *datastore = NULL;
852 struct mixmonitor_ds *mixmonitor_ds = NULL;
856 e->command = "mixmonitor {start|stop|list}";
858 "Usage: mixmonitor <start|stop|list> <chan_name> [args]\n"
859 " The optional arguments are passed to the MixMonitor\n"
860 " application when the 'start' command is used.\n";
863 return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
867 return CLI_SHOWUSAGE;
870 if (!(chan = ast_channel_get_by_name_prefix(a->argv[2], strlen(a->argv[2])))) {
871 ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
872 /* Technically this is a failure, but we don't want 2 errors printing out */
876 ast_channel_lock(chan);
878 if (!strcasecmp(a->argv[1], "start")) {
879 mixmonitor_exec(chan, (a->argc >= 4) ? a->argv[3] : "");
880 ast_channel_unlock(chan);
881 } else if (!strcasecmp(a->argv[1], "stop")){
882 ast_channel_unlock(chan);
883 stop_mixmonitor_exec(chan, (a->argc >= 4) ? a->argv[3] : "");
884 } else if (!strcasecmp(a->argv[1], "list")) {
885 ast_cli(a->fd, "MixMonitor ID\tFile\tReceive File\tTransmit File\n");
886 ast_cli(a->fd, "=========================================================================\n");
887 AST_LIST_TRAVERSE(ast_channel_datastores(chan), datastore, entry) {
888 if (datastore->info == &mixmonitor_ds_info) {
890 char *filename_read = "";
891 char *filename_write = "";
892 mixmonitor_ds = datastore->data;
893 if (mixmonitor_ds->fs)
894 filename = ast_strdupa(mixmonitor_ds->fs->filename);
895 if (mixmonitor_ds->fs_read)
896 filename_read = ast_strdupa(mixmonitor_ds->fs_read->filename);
897 if (mixmonitor_ds->fs_write)
898 filename_write = ast_strdupa(mixmonitor_ds->fs_write->filename);
899 ast_cli(a->fd, "%p\t%s\t%s\t%s\n", mixmonitor_ds, filename, filename_read, filename_write);
902 ast_channel_unlock(chan);
904 ast_channel_unlock(chan);
905 chan = ast_channel_unref(chan);
906 return CLI_SHOWUSAGE;
909 chan = ast_channel_unref(chan);
914 /*! \brief Mute / unmute a MixMonitor channel */
915 static int manager_mute_mixmonitor(struct mansession *s, const struct message *m)
917 struct ast_channel *c = NULL;
919 const char *name = astman_get_header(m, "Channel");
920 const char *id = astman_get_header(m, "ActionID");
921 const char *state = astman_get_header(m, "State");
922 const char *direction = astman_get_header(m,"Direction");
926 enum ast_audiohook_flags flag;
928 if (ast_strlen_zero(direction)) {
929 astman_send_error(s, m, "No direction specified. Must be read, write or both");
933 if (!strcasecmp(direction, "read")) {
934 flag = AST_AUDIOHOOK_MUTE_READ;
935 } else if (!strcasecmp(direction, "write")) {
936 flag = AST_AUDIOHOOK_MUTE_WRITE;
937 } else if (!strcasecmp(direction, "both")) {
938 flag = AST_AUDIOHOOK_MUTE_READ | AST_AUDIOHOOK_MUTE_WRITE;
940 astman_send_error(s, m, "Invalid direction specified. Must be read, write or both");
944 if (ast_strlen_zero(name)) {
945 astman_send_error(s, m, "No channel specified");
949 if (ast_strlen_zero(state)) {
950 astman_send_error(s, m, "No state specified");
954 clearmute = ast_false(state);
955 c = ast_channel_get_by_name(name);
958 astman_send_error(s, m, "No such channel");
962 if (ast_audiohook_set_mute(c, mixmonitor_spy_type, flag, clearmute)) {
963 c = ast_channel_unref(c);
964 astman_send_error(s, m, "Cannot set mute flag");
968 astman_append(s, "Response: Success\r\n");
970 if (!ast_strlen_zero(id)) {
971 astman_append(s, "ActionID: %s\r\n", id);
974 astman_append(s, "\r\n");
976 c = ast_channel_unref(c);
981 static int manager_mixmonitor(struct mansession *s, const struct message *m)
983 struct ast_channel *c = NULL;
985 const char *name = astman_get_header(m, "Channel");
986 const char *id = astman_get_header(m, "ActionID");
987 const char *file = astman_get_header(m, "File");
988 const char *options = astman_get_header(m, "Options");
989 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
990 struct ast_flags flags = { 0 };
991 char *uid_channel_var = NULL;
992 const char *mixmonitor_id = NULL;
995 char args[PATH_MAX] = "";
996 if (ast_strlen_zero(name)) {
997 astman_send_error(s, m, "No channel specified");
1001 c = ast_channel_get_by_name(name);
1004 astman_send_error(s, m, "No such channel");
1008 if (!ast_strlen_zero(options)) {
1009 ast_app_parse_options(mixmonitor_opts, &flags, opts, ast_strdupa(options));
1012 snprintf(args, sizeof(args), "%s,%s", file, options);
1014 ast_channel_lock(c);
1015 res = mixmonitor_exec(c, args);
1017 if (ast_test_flag(&flags, MUXFLAG_UID)) {
1018 uid_channel_var = opts[OPT_ARG_UID];
1019 mixmonitor_id = pbx_builtin_getvar_helper(c, uid_channel_var);
1021 ast_channel_unlock(c);
1024 astman_send_error(s, m, "Could not start monitoring channel");
1028 astman_append(s, "Response: Success\r\n");
1030 if (!ast_strlen_zero(id)) {
1031 astman_append(s, "ActionID: %s\r\n", id);
1034 if (!ast_strlen_zero(mixmonitor_id)) {
1035 astman_append(s, "MixMonitorID: %s\r\n", mixmonitor_id);
1038 astman_append(s, "\r\n");
1040 c = ast_channel_unref(c);
1045 static int manager_stop_mixmonitor(struct mansession *s, const struct message *m)
1047 struct ast_channel *c = NULL;
1049 const char *name = astman_get_header(m, "Channel");
1050 const char *id = astman_get_header(m, "ActionID");
1051 const char *mixmonitor_id = astman_get_header(m, "MixMonitorID");
1054 if (ast_strlen_zero(name)) {
1055 astman_send_error(s, m, "No channel specified");
1059 c = ast_channel_get_by_name(name);
1062 astman_send_error(s, m, "No such channel");
1066 res = stop_mixmonitor_exec(c, mixmonitor_id);
1069 astman_send_error(s, m, "Could not stop monitoring channel");
1073 astman_append(s, "Response: Success\r\n");
1075 if (!ast_strlen_zero(id)) {
1076 astman_append(s, "ActionID: %s\r\n", id);
1079 astman_append(s, "\r\n");
1081 c = ast_channel_unref(c);
1086 static struct ast_cli_entry cli_mixmonitor[] = {
1087 AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
1090 static int unload_module(void)
1094 ast_cli_unregister_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
1095 res = ast_unregister_application(stop_app);
1096 res |= ast_unregister_application(app);
1097 res |= ast_manager_unregister("MixMonitorMute");
1098 res |= ast_manager_unregister("MixMonitor");
1099 res |= ast_manager_unregister("StopMixMonitor");
1104 static int load_module(void)
1108 ast_cli_register_multiple(cli_mixmonitor, ARRAY_LEN(cli_mixmonitor));
1109 res = ast_register_application_xml(app, mixmonitor_exec);
1110 res |= ast_register_application_xml(stop_app, stop_mixmonitor_exec);
1111 res |= ast_manager_register_xml("MixMonitorMute", 0, manager_mute_mixmonitor);
1112 res |= ast_manager_register_xml("MixMonitor", 0, manager_mixmonitor);
1113 res |= ast_manager_register_xml("StopMixMonitor", 0, manager_stop_mixmonitor);
1118 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");