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>
38 ASTERISK_FILE_VERSION(__FILE__
, "$Revision$")
45 #include "asterisk/file.h"
46 #include "asterisk/logger.h"
47 #include "asterisk/channel.h"
48 #include "asterisk/chanspy.h"
49 #include "asterisk/pbx.h"
50 #include "asterisk/module.h"
51 #include "asterisk/lock.h"
52 #include "asterisk/cli.h"
53 #include "asterisk/options.h"
54 #include "asterisk/app.h"
55 #include "asterisk/linkedlists.h"
56 #include "asterisk/utils.h"
58 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
60 static const char *app
= "MixMonitor";
61 static const char *synopsis
= "Record a call and mix the audio during the recording";
62 static const char *desc
= ""
63 " MixMonitor(<file>.<ext>[|<options>[|<command>]])\n\n"
64 "Records the audio on the current channel to the specified file.\n"
65 "If the filename is an absolute path, uses that path, otherwise\n"
66 "creates the file in the configured monitoring directory from\n"
69 " a - Append to the file instead of overwriting it.\n"
70 " b - Only save audio to the file while the channel is bridged.\n"
71 " Note: does not include conferences.\n"
72 " v(<x>) - Adjust the heard volume by a factor of <x> (range -4 to 4)\n"
73 " V(<x>) - Adjust the spoken volume by a factor of <x> (range -4 to 4)\n"
74 " W(<x>) - Adjust the both heard and spoken volumes by a factor of <x>\n"
75 " (range -4 to 4)\n\n"
76 "<command> will be executed when the recording is over\n"
77 "Any strings matching ^{X} will be unescaped to ${X} and \n"
78 "all variables will be evaluated at that time.\n"
79 "The variable MIXMONITOR_FILENAME will contain the filename used to record.\n"
82 static const char *stop_app
= "StopMixMonitor";
83 static const char *stop_synopsis
= "Stop recording a call through MixMonitor";
84 static const char *stop_desc
= ""
85 " StopMixMonitor()\n\n"
86 "Stops the audio recording that was started with a call to MixMonitor()\n"
87 "on the current channel.\n"
90 struct module_symbols
*me
;
92 static const char *mixmonitor_spy_type
= "MixMonitor";
95 struct ast_channel_spy spy
;
96 struct ast_filestream
*fs
;
103 MUXFLAG_APPEND
= (1 << 1),
104 MUXFLAG_BRIDGED
= (1 << 2),
105 MUXFLAG_VOLUME
= (1 << 3),
106 MUXFLAG_READVOLUME
= (1 << 4),
107 MUXFLAG_WRITEVOLUME
= (1 << 5),
111 OPT_ARG_READVOLUME
= 0,
117 AST_APP_OPTIONS(mixmonitor_opts
, {
118 AST_APP_OPTION('a', MUXFLAG_APPEND
),
119 AST_APP_OPTION('b', MUXFLAG_BRIDGED
),
120 AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME
, OPT_ARG_READVOLUME
),
121 AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME
, OPT_ARG_WRITEVOLUME
),
122 AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME
, OPT_ARG_VOLUME
),
125 static int startmon(struct ast_channel
*chan
, struct ast_channel_spy
*spy
)
127 struct ast_channel
*peer
;
133 ast_channel_lock(chan
);
134 res
= ast_channel_spy_add(chan
, spy
);
135 ast_channel_unlock(chan
);
137 if (!res
&& ast_test_flag(chan
, AST_FLAG_NBRIDGE
) && (peer
= ast_bridged_channel(chan
)))
138 ast_softhangup(peer
, AST_SOFTHANGUP_UNBRIDGE
);
143 #define SAMPLES_PER_FRAME 160
145 static void *mixmonitor_thread(void *obj
)
147 struct mixmonitor
*mixmonitor
= obj
;
148 struct ast_frame
*f
= NULL
;
151 if (option_verbose
> 1)
152 ast_verbose(VERBOSE_PREFIX_2
"Begin MixMonitor Recording %s\n", mixmonitor
->name
);
154 ast_mutex_lock(&mixmonitor
->spy
.lock
);
156 while (mixmonitor
->spy
.chan
) {
157 struct ast_frame
*next
;
160 ast_channel_spy_trigger_wait(&mixmonitor
->spy
);
162 if (!mixmonitor
->spy
.chan
|| mixmonitor
->spy
.status
!= CHANSPY_RUNNING
)
166 if (!(f
= ast_channel_spy_read_frame(&mixmonitor
->spy
, SAMPLES_PER_FRAME
)))
169 write
= (!ast_test_flag(mixmonitor
, MUXFLAG_BRIDGED
) ||
170 ast_bridged_channel(mixmonitor
->spy
.chan
));
172 /* it is possible for ast_channel_spy_read_frame() to return a chain
173 of frames if a queue flush was necessary, so process them
175 for (; f
; f
= next
) {
176 next
= AST_LIST_NEXT(f
, frame_list
);
178 ast_writestream(mixmonitor
->fs
, f
);
179 ast_frame_free(f
, 0);
184 ast_mutex_unlock(&mixmonitor
->spy
.lock
);
186 ast_channel_spy_free(&mixmonitor
->spy
);
188 if (option_verbose
> 1)
189 ast_verbose(VERBOSE_PREFIX_2
"End MixMonitor Recording %s\n", mixmonitor
->name
);
191 if (mixmonitor
->post_process
) {
192 if (option_verbose
> 2)
193 ast_verbose(VERBOSE_PREFIX_2
"Executing [%s]\n", mixmonitor
->post_process
);
194 ast_safe_system(mixmonitor
->post_process
);
197 ast_closestream(mixmonitor
->fs
);
205 static void launch_monitor_thread(struct ast_channel
*chan
, const char *filename
, unsigned int flags
,
206 int readvol
, int writevol
, const char *post_process
)
210 struct mixmonitor
*mixmonitor
;
211 char *file_name
, *ext
;
212 char postprocess2
[1024] = "";
216 len
= sizeof(*mixmonitor
) + strlen(chan
->name
) + 1;
218 /* If a post process system command is given attach it to the structure */
219 if (!ast_strlen_zero(post_process
)) {
222 p1
= ast_strdupa(post_process
);
223 for (p2
= p1
; *p2
; p2
++) {
224 if (*p2
== '^' && *(p2
+1) == '{') {
229 pbx_substitute_variables_helper(chan
, p1
, postprocess2
, sizeof(postprocess2
) - 1);
230 if (!ast_strlen_zero(postprocess2
))
231 len
+= strlen(postprocess2
) + 1;
234 /* Pre-allocate mixmonitor structure and spy */
235 if (!(mixmonitor
= calloc(1, len
))) {
239 /* Copy over flags and channel name */
240 mixmonitor
->flags
= flags
;
241 mixmonitor
->name
= (char *) mixmonitor
+ sizeof(*mixmonitor
);
242 strcpy(mixmonitor
->name
, chan
->name
);
243 if (!ast_strlen_zero(postprocess2
)) {
244 mixmonitor
->post_process
= mixmonitor
->name
+ strlen(mixmonitor
->name
) + 1;
245 strcpy(mixmonitor
->post_process
, postprocess2
);
248 /* Determine creation flags and filename plus extension for filestream */
249 oflags
= O_CREAT
| O_WRONLY
;
250 oflags
|= ast_test_flag(mixmonitor
, MUXFLAG_APPEND
) ? O_APPEND
: O_TRUNC
;
251 file_name
= ast_strdupa(filename
);
252 if ((ext
= strrchr(file_name
, '.'))) {
258 /* Move onto actually creating the filestream */
259 mixmonitor
->fs
= ast_writefile(file_name
, ext
, NULL
, oflags
, 0, 0644);
260 if (!mixmonitor
->fs
) {
261 ast_log(LOG_ERROR
, "Cannot open %s.%s\n", file_name
, ext
);
266 /* Setup the actual spy before creating our thread */
267 ast_set_flag(&mixmonitor
->spy
, CHANSPY_FORMAT_AUDIO
);
268 ast_set_flag(&mixmonitor
->spy
, CHANSPY_MIXAUDIO
);
269 mixmonitor
->spy
.type
= mixmonitor_spy_type
;
270 mixmonitor
->spy
.status
= CHANSPY_RUNNING
;
271 mixmonitor
->spy
.read_queue
.format
= AST_FORMAT_SLINEAR
;
272 mixmonitor
->spy
.write_queue
.format
= AST_FORMAT_SLINEAR
;
274 ast_set_flag(&mixmonitor
->spy
, CHANSPY_READ_VOLADJUST
);
275 mixmonitor
->spy
.read_vol_adjustment
= readvol
;
278 ast_set_flag(&mixmonitor
->spy
, CHANSPY_WRITE_VOLADJUST
);
279 mixmonitor
->spy
.write_vol_adjustment
= writevol
;
281 ast_mutex_init(&mixmonitor
->spy
.lock
);
283 if (startmon(chan
, &mixmonitor
->spy
)) {
284 ast_log(LOG_WARNING
, "Unable to add '%s' spy to channel '%s'\n",
285 mixmonitor
->spy
.type
, chan
->name
);
286 /* Since we couldn't add ourselves - bail out! */
287 ast_mutex_destroy(&mixmonitor
->spy
.lock
);
288 ast_closestream(mixmonitor
->fs
);
293 pthread_attr_init(&attr
);
294 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
295 ast_pthread_create_background(&thread
, &attr
, mixmonitor_thread
, mixmonitor
);
296 pthread_attr_destroy(&attr
);
300 static int mixmonitor_exec(struct ast_channel
*chan
, void *data
)
302 int x
, readvol
= 0, writevol
= 0;
303 struct ast_module_user
*u
;
304 struct ast_flags flags
= {0};
306 AST_DECLARE_APP_ARGS(args
,
307 AST_APP_ARG(filename
);
308 AST_APP_ARG(options
);
309 AST_APP_ARG(post_process
);
312 if (ast_strlen_zero(data
)) {
313 ast_log(LOG_WARNING
, "MixMonitor requires an argument (filename)\n");
317 u
= ast_module_user_add(chan
);
319 parse
= ast_strdupa(data
);
321 AST_STANDARD_APP_ARGS(args
, parse
);
323 if (ast_strlen_zero(args
.filename
)) {
324 ast_log(LOG_WARNING
, "MixMonitor requires an argument (filename)\n");
325 ast_module_user_remove(u
);
330 char *opts
[OPT_ARG_ARRAY_SIZE
] = { NULL
, };
332 ast_app_parse_options(mixmonitor_opts
, &flags
, opts
, args
.options
);
334 if (ast_test_flag(&flags
, MUXFLAG_READVOLUME
)) {
335 if (ast_strlen_zero(opts
[OPT_ARG_READVOLUME
])) {
336 ast_log(LOG_WARNING
, "No volume level was provided for the heard volume ('v') option.\n");
337 } else if ((sscanf(opts
[OPT_ARG_READVOLUME
], "%d", &x
) != 1) || (x
< -4) || (x
> 4)) {
338 ast_log(LOG_NOTICE
, "Heard volume must be a number between -4 and 4, not '%s'\n", opts
[OPT_ARG_READVOLUME
]);
340 readvol
= get_volfactor(x
);
344 if (ast_test_flag(&flags
, MUXFLAG_WRITEVOLUME
)) {
345 if (ast_strlen_zero(opts
[OPT_ARG_WRITEVOLUME
])) {
346 ast_log(LOG_WARNING
, "No volume level was provided for the spoken volume ('V') option.\n");
347 } else if ((sscanf(opts
[OPT_ARG_WRITEVOLUME
], "%d", &x
) != 1) || (x
< -4) || (x
> 4)) {
348 ast_log(LOG_NOTICE
, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts
[OPT_ARG_WRITEVOLUME
]);
350 writevol
= get_volfactor(x
);
354 if (ast_test_flag(&flags
, MUXFLAG_VOLUME
)) {
355 if (ast_strlen_zero(opts
[OPT_ARG_VOLUME
])) {
356 ast_log(LOG_WARNING
, "No volume level was provided for the combined volume ('W') option.\n");
357 } else if ((sscanf(opts
[OPT_ARG_VOLUME
], "%d", &x
) != 1) || (x
< -4) || (x
> 4)) {
358 ast_log(LOG_NOTICE
, "Combined volume must be a number between -4 and 4, not '%s'\n", opts
[OPT_ARG_VOLUME
]);
360 readvol
= writevol
= get_volfactor(x
);
365 /* if not provided an absolute path, use the system-configured monitoring directory */
366 if (args
.filename
[0] != '/') {
369 build
= alloca(strlen(ast_config_AST_MONITOR_DIR
) + strlen(args
.filename
) + 3);
370 sprintf(build
, "%s/%s", ast_config_AST_MONITOR_DIR
, args
.filename
);
371 args
.filename
= build
;
374 pbx_builtin_setvar_helper(chan
, "MIXMONITOR_FILENAME", args
.filename
);
375 launch_monitor_thread(chan
, args
.filename
, flags
.flags
, readvol
, writevol
, args
.post_process
);
377 ast_module_user_remove(u
);
382 static int stop_mixmonitor_exec(struct ast_channel
*chan
, void *data
)
384 struct ast_module_user
*u
;
386 u
= ast_module_user_add(chan
);
388 ast_channel_lock(chan
);
389 ast_channel_spy_stop_by_type(chan
, mixmonitor_spy_type
);
390 ast_channel_unlock(chan
);
392 ast_module_user_remove(u
);
397 static int mixmonitor_cli(int fd
, int argc
, char **argv
)
399 struct ast_channel
*chan
;
402 return RESULT_SHOWUSAGE
;
404 if (!(chan
= ast_get_channel_by_name_prefix_locked(argv
[2], strlen(argv
[2])))) {
405 ast_cli(fd
, "No channel matching '%s' found.\n", argv
[2]);
406 return RESULT_SUCCESS
;
409 if (!strcasecmp(argv
[1], "start"))
410 mixmonitor_exec(chan
, argv
[3]);
411 else if (!strcasecmp(argv
[1], "stop"))
412 ast_channel_spy_stop_by_type(chan
, mixmonitor_spy_type
);
414 ast_channel_unlock(chan
);
416 return RESULT_SUCCESS
;
419 static char *complete_mixmonitor_cli(const char *line
, const char *word
, int pos
, int state
)
421 return ast_complete_channels(line
, word
, pos
, state
, 2);
424 static struct ast_cli_entry cli_mixmonitor
[] = {
425 { { "mixmonitor", NULL
, NULL
},
426 mixmonitor_cli
, "Execute a MixMonitor command.",
427 "mixmonitor <start|stop> <chan_name> [args]\n\n"
428 "The optional arguments are passed to the\n"
429 "MixMonitor application when the 'start' command is used.\n",
430 complete_mixmonitor_cli
},
433 static int unload_module(void)
437 ast_cli_unregister_multiple(cli_mixmonitor
, sizeof(cli_mixmonitor
) / sizeof(struct ast_cli_entry
));
438 res
= ast_unregister_application(stop_app
);
439 res
|= ast_unregister_application(app
);
441 ast_module_user_hangup_all();
446 static int load_module(void)
450 ast_cli_register_multiple(cli_mixmonitor
, sizeof(cli_mixmonitor
) / sizeof(struct ast_cli_entry
));
451 res
= ast_register_application(app
, mixmonitor_exec
, synopsis
, desc
);
452 res
|= ast_register_application(stop_app
, stop_mixmonitor_exec
, stop_synopsis
, stop_desc
);
457 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY
, "Mixed Audio Monitoring Application");