2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief PBX channel monitoring
23 * \author Mark Spencer <markster@digium.com>
28 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34 #include <sys/types.h>
38 #include "asterisk/lock.h"
39 #include "asterisk/channel.h"
40 #include "asterisk/logger.h"
41 #include "asterisk/file.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/module.h"
44 #include "asterisk/manager.h"
45 #include "asterisk/cli.h"
46 #include "asterisk/monitor.h"
47 #include "asterisk/app.h"
48 #include "asterisk/utils.h"
49 #include "asterisk/config.h"
50 #include "asterisk/options.h"
52 AST_MUTEX_DEFINE_STATIC(monitorlock);
54 #define LOCK_IF_NEEDED(lock, needed) do { \
56 ast_channel_lock(lock); \
59 #define UNLOCK_IF_NEEDED(lock, needed) do { \
61 ast_channel_unlock(lock); \
64 static unsigned long seq = 0;
66 static char *monitor_synopsis = "Monitor a channel";
68 static char *monitor_descrip = "Monitor([file_format[:urlbase]|[fname_base]|[options]]):\n"
69 "Used to start monitoring a channel. The channel's input and output\n"
70 "voice packets are logged to files until the channel hangs up or\n"
71 "monitoring is stopped by the StopMonitor application.\n"
72 " file_format optional, if not set, defaults to \"wav\"\n"
73 " fname_base if set, changes the filename used to the one specified.\n"
75 " m - when the recording ends mix the two leg files into one and\n"
76 " delete the two leg files. If the variable MONITOR_EXEC is set, the\n"
77 " application referenced in it will be executed instead of\n"
78 " soxmix and the raw leg files will NOT be deleted automatically.\n"
79 " soxmix or MONITOR_EXEC is handed 3 arguments, the two leg files\n"
80 " and a target mixed file name which is the same as the leg file names\n"
81 " only without the in/out designator.\n"
82 " If MONITOR_EXEC_ARGS is set, the contents will be passed on as\n"
83 " additional arguements to MONITOR_EXEC\n"
84 " Both MONITOR_EXEC and the Mix flag can be set from the\n"
85 " administrator interface\n"
87 " b - Don't begin recording unless a call is bridged to another channel\n"
88 "\nReturns -1 if monitor files can't be opened or if the channel is already\n"
89 "monitored, otherwise 0.\n"
92 static char *stopmonitor_synopsis = "Stop monitoring a channel";
94 static char *stopmonitor_descrip = "StopMonitor\n"
95 "Stops monitoring a channel. Has no effect if the channel is not monitored\n";
97 static char *changemonitor_synopsis = "Change monitoring filename of a channel";
99 static char *changemonitor_descrip = "ChangeMonitor(filename_base)\n"
100 "Changes monitoring filename of a channel. Has no effect if the channel is not monitored\n"
101 "The argument is the new filename base to use for monitoring this channel.\n";
103 static char *pausemonitor_synopsis = "Pause monitoring of a channel";
105 static char *pausemonitor_descrip = "PauseMonitor\n"
106 "Pauses monitoring of a channel until it is re-enabled by a call to UnpauseMonitor.\n";
108 static char *unpausemonitor_synopsis = "Unpause monitoring of a channel";
110 static char *unpausemonitor_descrip = "UnpauseMonitor\n"
111 "Unpauses monitoring of a channel on which monitoring had\n"
112 "previously been paused with PauseMonitor.\n";
114 static int ast_monitor_set_state(struct ast_channel *chan, int state)
116 LOCK_IF_NEEDED(chan, 1);
117 if (!chan->monitor) {
118 UNLOCK_IF_NEEDED(chan, 1);
121 chan->monitor->state = state;
122 UNLOCK_IF_NEEDED(chan, 1);
126 /* Start monitoring a channel */
127 int ast_monitor_start( struct ast_channel *chan, const char *format_spec,
128 const char *fname_base, int need_lock)
133 LOCK_IF_NEEDED(chan, need_lock);
135 if (!(chan->monitor)) {
136 struct ast_channel_monitor *monitor;
137 char *channel_name, *p;
139 /* Create monitoring directory if needed */
140 if (mkdir(ast_config_AST_MONITOR_DIR, 0770) < 0) {
141 if (errno != EEXIST) {
142 ast_log(LOG_WARNING, "Unable to create audio monitor directory: %s\n",
147 if (!(monitor = ast_calloc(1, sizeof(*monitor)))) {
148 UNLOCK_IF_NEEDED(chan, need_lock);
152 /* Determine file names */
153 if (!ast_strlen_zero(fname_base)) {
154 int directory = strchr(fname_base, '/') ? 1 : 0;
155 /* try creating the directory just in case it doesn't exist */
157 char *name = strdup(fname_base);
158 snprintf(tmp, sizeof(tmp), "mkdir -p \"%s\"",dirname(name));
160 ast_safe_system(tmp);
162 snprintf(monitor->read_filename, FILENAME_MAX, "%s/%s-in",
163 directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
164 snprintf(monitor->write_filename, FILENAME_MAX, "%s/%s-out",
165 directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
166 ast_copy_string(monitor->filename_base, fname_base, sizeof(monitor->filename_base));
168 ast_mutex_lock(&monitorlock);
169 snprintf(monitor->read_filename, FILENAME_MAX, "%s/audio-in-%ld",
170 ast_config_AST_MONITOR_DIR, seq);
171 snprintf(monitor->write_filename, FILENAME_MAX, "%s/audio-out-%ld",
172 ast_config_AST_MONITOR_DIR, seq);
174 ast_mutex_unlock(&monitorlock);
176 channel_name = ast_strdupa(chan->name);
177 while ((p = strchr(channel_name, '/'))) {
180 snprintf(monitor->filename_base, FILENAME_MAX, "%s/%d-%s",
181 ast_config_AST_MONITOR_DIR, (int)time(NULL), channel_name);
182 monitor->filename_changed = 1;
185 monitor->stop = ast_monitor_stop;
187 /* Determine file format */
188 if (!ast_strlen_zero(format_spec)) {
189 monitor->format = strdup(format_spec);
191 monitor->format = strdup("wav");
195 if (ast_fileexists(monitor->read_filename, NULL, NULL) > 0) {
196 ast_filedelete(monitor->read_filename, NULL);
198 if (!(monitor->read_stream = ast_writefile(monitor->read_filename,
199 monitor->format, NULL,
200 O_CREAT|O_TRUNC|O_WRONLY, 0, AST_FILE_MODE))) {
201 ast_log(LOG_WARNING, "Could not create file %s\n",
202 monitor->read_filename);
204 UNLOCK_IF_NEEDED(chan, need_lock);
207 if (ast_fileexists(monitor->write_filename, NULL, NULL) > 0) {
208 ast_filedelete(monitor->write_filename, NULL);
210 if (!(monitor->write_stream = ast_writefile(monitor->write_filename,
211 monitor->format, NULL,
212 O_CREAT|O_TRUNC|O_WRONLY, 0, AST_FILE_MODE))) {
213 ast_log(LOG_WARNING, "Could not create file %s\n",
214 monitor->write_filename);
215 ast_closestream(monitor->read_stream);
217 UNLOCK_IF_NEEDED(chan, need_lock);
220 chan->monitor = monitor;
221 ast_monitor_set_state(chan, AST_MONITOR_RUNNING);
222 /* so we know this call has been monitored in case we need to bill for it or something */
223 pbx_builtin_setvar_helper(chan, "__MONITORED","true");
226 ast_log(LOG_DEBUG,"Cannot start monitoring %s, already monitored\n",
231 UNLOCK_IF_NEEDED(chan, need_lock);
237 * The file format extensions that Asterisk uses are not all the same as that
238 * which soxmix expects. This function ensures that the format used as the
239 * extension on the filename is something soxmix will understand.
241 static const char *get_soxmix_format(const char *format)
243 const char *res = format;
245 if (!strcasecmp(format,"ulaw"))
247 if (!strcasecmp(format,"alaw"))
253 /* Stop monitoring a channel */
254 int ast_monitor_stop(struct ast_channel *chan, int need_lock)
258 LOCK_IF_NEEDED(chan, need_lock);
261 char filename[ FILENAME_MAX ];
263 if (chan->monitor->read_stream) {
264 ast_closestream(chan->monitor->read_stream);
266 if (chan->monitor->write_stream) {
267 ast_closestream(chan->monitor->write_stream);
270 if (chan->monitor->filename_changed && !ast_strlen_zero(chan->monitor->filename_base)) {
271 if (ast_fileexists(chan->monitor->read_filename,NULL,NULL) > 0) {
272 snprintf(filename, FILENAME_MAX, "%s-in", chan->monitor->filename_base);
273 if (ast_fileexists(filename, NULL, NULL) > 0) {
274 ast_filedelete(filename, NULL);
276 ast_filerename(chan->monitor->read_filename, filename, chan->monitor->format);
278 ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->read_filename);
281 if (ast_fileexists(chan->monitor->write_filename,NULL,NULL) > 0) {
282 snprintf(filename, FILENAME_MAX, "%s-out", chan->monitor->filename_base);
283 if (ast_fileexists(filename, NULL, NULL) > 0) {
284 ast_filedelete(filename, NULL);
286 ast_filerename(chan->monitor->write_filename, filename, chan->monitor->format);
288 ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->write_filename);
292 if (chan->monitor->joinfiles && !ast_strlen_zero(chan->monitor->filename_base)) {
295 const char *format = !strcasecmp(chan->monitor->format,"wav49") ? "WAV" : chan->monitor->format;
296 char *name = chan->monitor->filename_base;
297 int directory = strchr(name, '/') ? 1 : 0;
298 char *dir = directory ? "" : ast_config_AST_MONITOR_DIR;
299 const char *execute, *execute_args;
301 /* Set the execute application */
302 execute = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC");
303 if (ast_strlen_zero(execute)) {
304 execute = "nice -n 19 soxmix";
305 format = get_soxmix_format(format);
308 execute_args = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC_ARGS");
309 if (ast_strlen_zero(execute_args)) {
313 snprintf(tmp, sizeof(tmp), "%s \"%s/%s-in.%s\" \"%s/%s-out.%s\" \"%s/%s.%s\" %s &", execute, dir, name, format, dir, name, format, dir, name, format,execute_args);
315 snprintf(tmp2,sizeof(tmp2), "( %s& rm -f \"%s/%s-\"* ) &",tmp, dir ,name); /* remove legs when done mixing */
316 ast_copy_string(tmp, tmp2, sizeof(tmp));
319 ast_log(LOG_DEBUG,"monitor executing %s\n",tmp);
320 if (ast_safe_system(tmp) == -1)
321 ast_log(LOG_WARNING, "Execute of %s failed.\n",tmp);
324 free(chan->monitor->format);
326 chan->monitor = NULL;
329 UNLOCK_IF_NEEDED(chan, need_lock);
335 /* Pause monitoring of a channel */
336 int ast_monitor_pause(struct ast_channel *chan)
338 return ast_monitor_set_state(chan, AST_MONITOR_PAUSED);
341 /* Unpause monitoring of a channel */
342 int ast_monitor_unpause(struct ast_channel *chan)
344 return ast_monitor_set_state(chan, AST_MONITOR_RUNNING);
347 static int pause_monitor_exec(struct ast_channel *chan, void *data)
349 return ast_monitor_pause(chan);
352 static int unpause_monitor_exec(struct ast_channel *chan, void *data)
354 return ast_monitor_unpause(chan);
357 /* Change monitoring filename of a channel */
358 int ast_monitor_change_fname(struct ast_channel *chan, const char *fname_base, int need_lock)
361 if (ast_strlen_zero(fname_base)) {
362 ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to null\n", chan->name);
366 LOCK_IF_NEEDED(chan, need_lock);
369 int directory = strchr(fname_base, '/') ? 1 : 0;
370 /* try creating the directory just in case it doesn't exist */
372 char *name = strdup(fname_base);
373 snprintf(tmp, sizeof(tmp), "mkdir -p %s",dirname(name));
375 ast_safe_system(tmp);
378 snprintf(chan->monitor->filename_base, FILENAME_MAX, "%s/%s", directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
380 ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to %s, monitoring not started\n", chan->name, fname_base);
383 UNLOCK_IF_NEEDED(chan, need_lock);
388 static int start_monitor_exec(struct ast_channel *chan, void *data)
392 char *fname_base = NULL;
393 char *options = NULL;
395 char *urlprefix = NULL;
398 int waitforbridge = 0;
401 /* Parse arguments. */
402 if (!ast_strlen_zero((char*)data)) {
403 arg = ast_strdupa((char*)data);
405 fname_base = strchr(arg, '|');
409 if ((options = strchr(fname_base, '|'))) {
412 if (strchr(options, 'm'))
414 if (strchr(options, 'b'))
418 arg = strchr(format,':');
425 snprintf(tmp,sizeof(tmp) - 1,"%s/%s.%s",urlprefix,fname_base,
426 ((strcmp(format,"gsm")) ? "wav" : "gsm"));
427 if (!chan->cdr && !(chan->cdr = ast_cdr_alloc()))
429 ast_cdr_setuserfield(chan, tmp);
432 /* We must remove the "b" option if listed. In principle none of
433 the following could give NULL results, but we check just to
434 be pedantic. Reconstructing with checks for 'm' option does not
435 work if we end up adding more options than 'm' in the future. */
436 delay = ast_strdupa(data);
437 options = strrchr(delay, '|');
439 arg = strchr(options, 'b');
442 pbx_builtin_setvar_helper(chan,"AUTO_MONITOR",delay);
448 res = ast_monitor_start(chan, format, fname_base, 1);
450 res = ast_monitor_change_fname(chan, fname_base, 1);
451 ast_monitor_setjoinfiles(chan, joinfiles);
456 static int stop_monitor_exec(struct ast_channel *chan, void *data)
458 return ast_monitor_stop(chan, 1);
461 static int change_monitor_exec(struct ast_channel *chan, void *data)
463 return ast_monitor_change_fname(chan, (const char*)data, 1);
466 static char start_monitor_action_help[] =
467 "Description: The 'Monitor' action may be used to record the audio on a\n"
468 " specified channel. The following parameters may be used to control\n"
470 " Channel - Required. Used to specify the channel to record.\n"
471 " File - Optional. Is the name of the file created in the\n"
472 " monitor spool directory. Defaults to the same name\n"
473 " as the channel (with slashes replaced with dashes).\n"
474 " Format - Optional. Is the audio recording format. Defaults\n"
476 " Mix - Optional. Boolean parameter as to whether to mix\n"
477 " the input and output channels together after the\n"
478 " recording is finished.\n";
480 static int start_monitor_action(struct mansession *s, const struct message *m)
482 struct ast_channel *c = NULL;
483 const char *name = astman_get_header(m, "Channel");
484 const char *fname = astman_get_header(m, "File");
485 const char *format = astman_get_header(m, "Format");
486 const char *mix = astman_get_header(m, "Mix");
489 if (ast_strlen_zero(name)) {
490 astman_send_error(s, m, "No channel specified");
493 c = ast_get_channel_by_name_locked(name);
495 astman_send_error(s, m, "No such channel");
499 if (ast_strlen_zero(fname)) {
500 /* No filename base specified, default to channel name as per CLI */
501 if (!(fname = ast_strdup(c->name))) {
502 astman_send_error(s, m, "Could not start monitoring channel");
503 ast_channel_unlock(c);
506 /* Channels have the format technology/channel_name - have to replace that / */
507 if ((d = strchr(fname, '/')))
511 if (ast_monitor_start(c, format, fname, 1)) {
512 if (ast_monitor_change_fname(c, fname, 1)) {
513 astman_send_error(s, m, "Could not start monitoring channel");
514 ast_channel_unlock(c);
520 ast_monitor_setjoinfiles(c, 1);
523 ast_channel_unlock(c);
524 astman_send_ack(s, m, "Started monitoring channel");
528 static char stop_monitor_action_help[] =
529 "Description: The 'StopMonitor' action may be used to end a previously\n"
530 " started 'Monitor' action. The only parameter is 'Channel', the name\n"
531 " of the channel monitored.\n";
533 static int stop_monitor_action(struct mansession *s, const struct message *m)
535 struct ast_channel *c = NULL;
536 const char *name = astman_get_header(m, "Channel");
538 if (ast_strlen_zero(name)) {
539 astman_send_error(s, m, "No channel specified");
542 c = ast_get_channel_by_name_locked(name);
544 astman_send_error(s, m, "No such channel");
547 res = ast_monitor_stop(c, 1);
548 ast_channel_unlock(c);
550 astman_send_error(s, m, "Could not stop monitoring channel");
553 astman_send_ack(s, m, "Stopped monitoring channel");
557 static char change_monitor_action_help[] =
558 "Description: The 'ChangeMonitor' action may be used to change the file\n"
559 " started by a previous 'Monitor' action. The following parameters may\n"
560 " be used to control this:\n"
561 " Channel - Required. Used to specify the channel to record.\n"
562 " File - Required. Is the new name of the file created in the\n"
563 " monitor spool directory.\n";
565 static int change_monitor_action(struct mansession *s, const struct message *m)
567 struct ast_channel *c = NULL;
568 const char *name = astman_get_header(m, "Channel");
569 const char *fname = astman_get_header(m, "File");
570 if (ast_strlen_zero(name)) {
571 astman_send_error(s, m, "No channel specified");
574 if (ast_strlen_zero(fname)) {
575 astman_send_error(s, m, "No filename specified");
578 c = ast_get_channel_by_name_locked(name);
580 astman_send_error(s, m, "No such channel");
583 if (ast_monitor_change_fname(c, fname, 1)) {
584 astman_send_error(s, m, "Could not change monitored filename of channel");
585 ast_channel_unlock(c);
588 ast_channel_unlock(c);
589 astman_send_ack(s, m, "Changed monitor filename");
593 void ast_monitor_setjoinfiles(struct ast_channel *chan, int turnon)
596 chan->monitor->joinfiles = turnon;
599 enum MONITOR_PAUSING_ACTION
601 MONITOR_ACTION_PAUSE,
602 MONITOR_ACTION_UNPAUSE
605 static int do_pause_or_unpause(struct mansession *s, const struct message *m, int action)
607 struct ast_channel *c = NULL;
608 const char *name = astman_get_header(m, "Channel");
610 if (ast_strlen_zero(name)) {
611 astman_send_error(s, m, "No channel specified");
615 c = ast_get_channel_by_name_locked(name);
617 astman_send_error(s, m, "No such channel");
621 if (action == MONITOR_ACTION_PAUSE)
622 ast_monitor_pause(c);
624 ast_monitor_unpause(c);
626 ast_channel_unlock(c);
627 astman_send_ack(s, m, "Paused monitoring of the channel");
631 static char pause_monitor_action_help[] =
632 "Description: The 'PauseMonitor' action may be used to temporarily stop the\n"
633 " recording of a channel. The following parameters may\n"
634 " be used to control this:\n"
635 " Channel - Required. Used to specify the channel to record.\n";
637 static int pause_monitor_action(struct mansession *s, const struct message *m)
639 return do_pause_or_unpause(s, m, MONITOR_ACTION_PAUSE);
642 static char unpause_monitor_action_help[] =
643 "Description: The 'UnpauseMonitor' action may be used to re-enable recording\n"
644 " of a channel after calling PauseMonitor. The following parameters may\n"
645 " be used to control this:\n"
646 " Channel - Required. Used to specify the channel to record.\n";
648 static int unpause_monitor_action(struct mansession *s, const struct message *m)
650 return do_pause_or_unpause(s, m, MONITOR_ACTION_UNPAUSE);
654 static int load_module(void)
656 ast_register_application("Monitor", start_monitor_exec, monitor_synopsis, monitor_descrip);
657 ast_register_application("StopMonitor", stop_monitor_exec, stopmonitor_synopsis, stopmonitor_descrip);
658 ast_register_application("ChangeMonitor", change_monitor_exec, changemonitor_synopsis, changemonitor_descrip);
659 ast_register_application("PauseMonitor", pause_monitor_exec, pausemonitor_synopsis, pausemonitor_descrip);
660 ast_register_application("UnpauseMonitor", unpause_monitor_exec, unpausemonitor_synopsis, unpausemonitor_descrip);
661 ast_manager_register2("Monitor", EVENT_FLAG_CALL, start_monitor_action, monitor_synopsis, start_monitor_action_help);
662 ast_manager_register2("StopMonitor", EVENT_FLAG_CALL, stop_monitor_action, stopmonitor_synopsis, stop_monitor_action_help);
663 ast_manager_register2("ChangeMonitor", EVENT_FLAG_CALL, change_monitor_action, changemonitor_synopsis, change_monitor_action_help);
664 ast_manager_register2("PauseMonitor", EVENT_FLAG_CALL, pause_monitor_action, pausemonitor_synopsis, pause_monitor_action_help);
665 ast_manager_register2("UnpauseMonitor", EVENT_FLAG_CALL, unpause_monitor_action, unpausemonitor_synopsis, unpause_monitor_action_help);
670 static int unload_module(void)
672 ast_unregister_application("Monitor");
673 ast_unregister_application("StopMonitor");
674 ast_unregister_application("ChangeMonitor");
675 ast_unregister_application("PauseMonitor");
676 ast_unregister_application("UnpauseMonitor");
677 ast_manager_unregister("Monitor");
678 ast_manager_unregister("StopMonitor");
679 ast_manager_unregister("ChangeMonitor");
680 ast_manager_unregister("PauseMonitor");
681 ast_manager_unregister("UnpauseMonitor");
686 /* usecount semantics need to be defined */
687 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Call Monitoring Resource",
689 .unload = unload_module,