diff --git a/CHANGES b/CHANGES index 8c12974d24..04f7d807c2 100644 --- a/CHANGES +++ b/CHANGES @@ -283,7 +283,7 @@ Features will now apply the feature to the calling party while use of a lowercase letter will apply that feature to the called party. - * Add support for automixmonitor to the BRIDGE_FEATURES channel variable. + * Add support for automixmon to the BRIDGE_FEATURES channel variable. * Parking has been pulled from core and placed into a separate module called res_parking. See Parking changes below for more details. @@ -292,6 +292,14 @@ Features and FEATUREMAP() functions inherited to child channels by setting FEATURE(inherit)=yes. + * automixmon now supports additional channel variables from automon including: + TOUCH_MIXMONITOR_PREFIX, TOUCH_MIXMONITOR_MESSAGE_START, + and TOUCH_MIXMONITOR_MESSAGE_STOP + + * A new general features.conf option 'recordingfailsound' has been added which + allowssetting a failure sound for a user tries to invoke a recording feature + such as automon or automixmon and it fails. + Logging ------------------- * When performing queue pause/unpause on an interface without specifying an diff --git a/apps/app_mixmonitor.c b/apps/app_mixmonitor.c index ea05ec32cf..a0df5d601b 100644 --- a/apps/app_mixmonitor.c +++ b/apps/app_mixmonitor.c @@ -56,6 +56,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/mod_format.h" #include "asterisk/linkedlists.h" #include "asterisk/test.h" +#include "asterisk/mixmonitor.h" /*** DOCUMENTATION @@ -1246,6 +1247,31 @@ static int manager_mute_mixmonitor(struct mansession *s, const struct message *m return AMI_SUCCESS; } +static int start_mixmonitor_callback(struct ast_channel *chan, const char *filename, const char *options) +{ + char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, }; + struct ast_flags flags = { 0 }; + char args[PATH_MAX] = ""; + int res; + + if (!ast_strlen_zero(options)) { + ast_app_parse_options(mixmonitor_opts, &flags, opts, ast_strdupa(options)); + } + + snprintf(args, sizeof(args), "%s,%s", filename, options); + + ast_channel_lock(chan); + res = mixmonitor_exec(chan, args); + ast_channel_unlock(chan); + + return res; +} + +static int stop_mixmonitor_callback(struct ast_channel *chan, const char *mixmonitor_id) +{ + return stop_mixmonitor_full(chan, mixmonitor_id); +} + static int manager_mixmonitor(struct mansession *s, const struct message *m) { struct ast_channel *c = NULL; @@ -1356,6 +1382,21 @@ static struct ast_cli_entry cli_mixmonitor[] = { AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command") }; +static int set_mixmonitor_methods(void) +{ + struct ast_mixmonitor_methods mixmonitor_methods = { + .start = start_mixmonitor_callback, + .stop = stop_mixmonitor_callback, + }; + + return ast_set_mixmonitor_methods(&mixmonitor_methods); +} + +static int clear_mixmonitor_methods(void) +{ + return ast_clear_mixmonitor_methods(); +} + static int unload_module(void) { int res; @@ -1366,6 +1407,7 @@ static int unload_module(void) res |= ast_manager_unregister("MixMonitorMute"); res |= ast_manager_unregister("MixMonitor"); res |= ast_manager_unregister("StopMixMonitor"); + res |= clear_mixmonitor_methods(); return res; } @@ -1380,6 +1422,7 @@ static int load_module(void) res |= ast_manager_register_xml("MixMonitorMute", 0, manager_mute_mixmonitor); res |= ast_manager_register_xml("MixMonitor", 0, manager_mixmonitor); res |= ast_manager_register_xml("StopMixMonitor", 0, manager_stop_mixmonitor); + res |= set_mixmonitor_methods(); return res; } diff --git a/bridges/bridge_builtin_features.c b/bridges/bridge_builtin_features.c index 2d8a68a1a6..91f709bc49 100644 --- a/bridges/bridge_builtin_features.c +++ b/bridges/bridge_builtin_features.c @@ -50,6 +50,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/pbx.h" #include "asterisk/parking.h" #include "asterisk/features_config.h" +#include "asterisk/monitor.h" +#include "asterisk/mixmonitor.h" +#include "asterisk/audiohook.h" /*! * \brief Helper function that presents dialtone and grabs extension @@ -463,6 +466,304 @@ static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridg return 0; } +static void stop_automonitor(struct ast_bridge_channel *bridge_channel, struct ast_channel *peer_chan, struct ast_features_general_config *features_cfg) +{ + const char *stop_message; + + ast_channel_lock(bridge_channel->chan); + stop_message = pbx_builtin_getvar_helper(bridge_channel->chan, "TOUCH_MONITOR_MESSAGE_STOP"); + stop_message = ast_strdupa(S_OR(stop_message, "")); + ast_channel_unlock(bridge_channel->chan); + + ast_verb(3, "AutoMonitor used to stop recording call.\n"); + + ast_channel_lock(peer_chan); + if (ast_channel_monitor(peer_chan)) { + if (ast_channel_monitor(peer_chan)->stop(peer_chan, 1)) { + ast_verb(3, "Cannot stop AutoMonitor for %s\n", ast_channel_name(bridge_channel->chan)); + if (features_cfg && !(ast_strlen_zero(features_cfg->recordingfailsound))) { + ast_bridge_channel_queue_playfile(bridge_channel, NULL, features_cfg->recordingfailsound, NULL); + } + ast_channel_unlock(peer_chan); + return; + } + } else { + /* Something else removed the Monitor before we got to it. */ + ast_channel_unlock(peer_chan); + return; + } + + ast_channel_unlock(peer_chan); + + if (features_cfg && !(ast_strlen_zero(features_cfg->courtesytone))) { + ast_bridge_channel_queue_playfile(bridge_channel, NULL, features_cfg->courtesytone, NULL); + ast_bridge_channel_write_playfile(bridge_channel, NULL, features_cfg->courtesytone, NULL); + } + + if (!ast_strlen_zero(stop_message)) { + ast_bridge_channel_queue_playfile(bridge_channel, NULL, stop_message, NULL); + ast_bridge_channel_write_playfile(bridge_channel, NULL, stop_message, NULL); + } +} + +enum set_touch_variables_res { + SET_TOUCH_SUCCESS = 0, + SET_TOUCH_UNSET, + SET_TOUCH_ALLOC_FAILURE, +}; + +static int set_touch_variables(struct ast_channel *chan, int is_mixmonitor, char **touch_format, char **touch_monitor, char **touch_monitor_prefix) +{ + enum set_touch_variables_res res = SET_TOUCH_UNSET; + const char *c_touch_format, *c_touch_monitor, *c_touch_monitor_prefix; + + SCOPED_CHANNELLOCK(lock, chan); + + c_touch_format = pbx_builtin_getvar_helper(chan, is_mixmonitor ? "TOUCH_MIXMONITOR_FORMAT" : "TOUCH_MONITOR_FORMAT"); + + if (!ast_strlen_zero(c_touch_format)) { + if (!(*touch_format = ast_strdup(c_touch_format))) { + return SET_TOUCH_ALLOC_FAILURE; + } + res = SET_TOUCH_SUCCESS; + } + + c_touch_monitor = pbx_builtin_getvar_helper(chan, is_mixmonitor ? "TOUCH_MIXMONITOR" : "TOUCH_MONITOR"); + + if (!ast_strlen_zero(c_touch_monitor)) { + if (!(*touch_monitor = ast_strdup(c_touch_monitor))) { + return SET_TOUCH_ALLOC_FAILURE; + } + res = SET_TOUCH_SUCCESS; + } + + c_touch_monitor_prefix = pbx_builtin_getvar_helper(chan, is_mixmonitor ? "TOUCH_MIXMONITOR_PREFIX" : "TOUCH_MONITOR_PREFIX"); + + if (!ast_strlen_zero(c_touch_monitor_prefix)) { + if (!(*touch_monitor_prefix = ast_strdup(c_touch_monitor_prefix))) { + return SET_TOUCH_ALLOC_FAILURE; + } + res = SET_TOUCH_SUCCESS; + } + + return res; +} + +static int feature_automonitor(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt) +{ + char *caller_chan_id = NULL, *peer_chan_id = NULL, *touch_filename = NULL; + size_t len; + const char *automon_message; + int x; + enum set_touch_variables_res set_touch_res; + + RAII_VAR(char *, touch_format, NULL, ast_free); + RAII_VAR(char *, touch_monitor, NULL, ast_free); + RAII_VAR(char *, touch_monitor_prefix, NULL, ast_free); + + RAII_VAR(struct ast_channel *, peer_chan, NULL, ast_channel_cleanup); + RAII_VAR(struct ast_features_general_config *, features_cfg, NULL, ao2_cleanup); + + features_cfg = ast_get_chan_features_general_config(bridge_channel->chan); + peer_chan = ast_bridge_peer(bridge, bridge_channel->chan); + + if (!peer_chan) { + ast_verb(3, "Cannot start AutoMonitor for %s - can not determine peer in bridge.\n", ast_channel_name(bridge_channel->chan)); + if (features_cfg && !(ast_strlen_zero(features_cfg->recordingfailsound))) { + ast_bridge_channel_queue_playfile(bridge_channel, NULL, features_cfg->recordingfailsound, NULL); + } + return 0; + } + + if (ast_channel_monitor(peer_chan)) { + stop_automonitor(bridge_channel, peer_chan, features_cfg); + return 0; + } + + if ((set_touch_res = set_touch_variables(bridge_channel->chan, 0, &touch_format, &touch_monitor, &touch_monitor_prefix))) { + if (set_touch_res == SET_TOUCH_ALLOC_FAILURE) { + return 0; + } + if (set_touch_variables(peer_chan, 0, &touch_format, &touch_monitor, &touch_monitor_prefix) == SET_TOUCH_ALLOC_FAILURE) { + return 0; + } + } + + if (!ast_strlen_zero(touch_monitor)) { + len = strlen(touch_monitor) + 50; + touch_filename = ast_alloca(len); + snprintf(touch_filename, len, "%s-%ld-%s", S_OR(touch_monitor_prefix, "auto"), (long)time(NULL), touch_monitor); + } else { + caller_chan_id = ast_strdupa(S_COR(ast_channel_caller(bridge_channel->chan)->id.number.valid, + ast_channel_caller(bridge_channel->chan)->id.number.str, ast_channel_name(bridge_channel->chan))); + peer_chan_id = ast_strdupa(S_COR(ast_channel_caller(peer_chan)->id.number.valid, + ast_channel_caller(peer_chan)->id.number.str, ast_channel_name(peer_chan))); + len = strlen(caller_chan_id) + strlen(peer_chan_id) + 50; + touch_filename = ast_alloca(len); + snprintf(touch_filename, len, "%s-%ld-%s-%s", S_OR(touch_monitor_prefix, "auto"), (long)time(NULL), caller_chan_id, peer_chan_id); + } + + for ( x = 0; x < strlen(touch_filename); x++) { + if (touch_filename[x] == '/') { + touch_filename[x] = '-'; + } + } + + ast_verb(3, "AutoMonitor used to record call. Filename: %s\n", touch_filename); + + if (ast_monitor_start(peer_chan, touch_format, touch_filename, 1, X_REC_IN | X_REC_OUT)) { + ast_verb(3, "automon feature was tried by '%s' but monitor failed to start.\n", ast_channel_name(bridge_channel->chan)); + return 0; + } + + ast_channel_lock(bridge_channel->chan); + if ((automon_message = pbx_builtin_getvar_helper(bridge_channel->chan, "TOUCH_MONITOR_MESSAGE_START"))) { + automon_message = ast_strdupa(automon_message); + } + ast_channel_unlock(bridge_channel->chan); + + if ((features_cfg = ast_get_chan_features_general_config(bridge_channel->chan)) && !(ast_strlen_zero(features_cfg->courtesytone))) { + ast_bridge_channel_queue_playfile(bridge_channel, NULL, features_cfg->courtesytone, NULL); + ast_bridge_channel_write_playfile(bridge_channel, NULL, features_cfg->courtesytone, NULL); + } + + if (!ast_strlen_zero(automon_message)) { + ast_bridge_channel_queue_playfile(bridge_channel, NULL, automon_message, NULL); + ast_bridge_channel_write_playfile(bridge_channel, NULL, automon_message, NULL); + } + + pbx_builtin_setvar_helper(peer_chan, "TOUCH_MONITOR_OUTPUT", touch_filename); + + return 0; +} + +static void stop_automixmonitor(struct ast_bridge_channel *bridge_channel, struct ast_channel *peer_chan, struct ast_features_general_config *features_cfg) +{ + const char *stop_message; + + ast_channel_lock(bridge_channel->chan); + stop_message = pbx_builtin_getvar_helper(bridge_channel->chan, "TOUCH_MIXMONITOR_MESSAGE_STOP"); + stop_message = ast_strdupa(S_OR(stop_message, "")); + ast_channel_unlock(bridge_channel->chan); + + ast_verb(3, "AutoMixMonitor used to stop recording call.\n"); + + if (ast_stop_mixmonitor(peer_chan, NULL)) { + ast_verb(3, "Failed to stop Mixmonitor for %s.\n", ast_channel_name(bridge_channel->chan)); + if (features_cfg && !(ast_strlen_zero(features_cfg->recordingfailsound))) { + ast_bridge_channel_queue_playfile(bridge_channel, NULL, features_cfg->recordingfailsound, NULL); + } + return; + } + + if (features_cfg && !(ast_strlen_zero(features_cfg->courtesytone))) { + ast_bridge_channel_queue_playfile(bridge_channel, NULL, features_cfg->courtesytone, NULL); + ast_bridge_channel_write_playfile(bridge_channel, NULL, features_cfg->courtesytone, NULL); + } + + if (!ast_strlen_zero(stop_message)) { + ast_bridge_channel_queue_playfile(bridge_channel, NULL, stop_message, NULL); + ast_bridge_channel_write_playfile(bridge_channel, NULL, stop_message, NULL); + } + +} + +static int feature_automixmonitor(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt) +{ + char *caller_chan_id = NULL, *peer_chan_id = NULL, *touch_filename = NULL; + size_t len; + const char *automon_message; + static char *mixmonitor_spy_type = "MixMonitor"; + int count, x; + enum set_touch_variables_res set_touch_res; + + RAII_VAR(char *, touch_format, NULL, ast_free); + RAII_VAR(char *, touch_monitor, NULL, ast_free); + RAII_VAR(char *, touch_monitor_prefix, NULL, ast_free); + + RAII_VAR(struct ast_channel *, peer_chan, NULL, ast_channel_cleanup); + RAII_VAR(struct ast_features_general_config *, features_cfg, NULL, ao2_cleanup); + + features_cfg = ast_get_chan_features_general_config(bridge_channel->chan); + + peer_chan = ast_bridge_peer(bridge, bridge_channel->chan); + + if (!peer_chan) { + ast_verb(3, "Cannot start AutoMixMonitor for %s - can not determine peer in bridge.\n", ast_channel_name(bridge_channel->chan)); + if (features_cfg && !(ast_strlen_zero(features_cfg->recordingfailsound))) { + ast_bridge_channel_queue_playfile(bridge_channel, NULL, features_cfg->recordingfailsound, NULL); + } + return 0; + } + + count = ast_channel_audiohook_count_by_source(peer_chan, mixmonitor_spy_type, AST_AUDIOHOOK_TYPE_SPY); + if (count > 0) { + stop_automixmonitor(bridge_channel, peer_chan, features_cfg); + return 0; + } + + if ((set_touch_res = set_touch_variables(bridge_channel->chan, 1, &touch_format, &touch_monitor, &touch_monitor_prefix))) { + if (set_touch_res == SET_TOUCH_ALLOC_FAILURE) { + return 0; + } + if (set_touch_variables(peer_chan, 1, &touch_format, &touch_monitor, &touch_monitor_prefix) == SET_TOUCH_ALLOC_FAILURE) { + return 0; + } + } + + if (!ast_strlen_zero(touch_monitor)) { + len = strlen(touch_monitor) + 50; + touch_filename = ast_alloca(len); + snprintf(touch_filename, len, "%s-%ld-%s.%s", S_OR(touch_monitor_prefix, "auto"), (long)time(NULL), touch_monitor, S_OR(touch_format, "wav")); + } else { + caller_chan_id = ast_strdupa(S_COR(ast_channel_caller(bridge_channel->chan)->id.number.valid, + ast_channel_caller(bridge_channel->chan)->id.number.str, ast_channel_name(bridge_channel->chan))); + peer_chan_id = ast_strdupa(S_COR(ast_channel_caller(peer_chan)->id.number.valid, + ast_channel_caller(peer_chan)->id.number.str, ast_channel_name(peer_chan))); + len = strlen(caller_chan_id) + strlen(peer_chan_id) + 50; + touch_filename = ast_alloca(len); + snprintf(touch_filename, len, "%s-%ld-%s-%s.%s", S_OR(touch_monitor_prefix, "auto"), (long)time(NULL), caller_chan_id, peer_chan_id, S_OR(touch_format, "wav")); + } + + for ( x = 0; x < strlen(touch_filename); x++) { + if (touch_filename[x] == '/') { + touch_filename[x] = '-'; + } + } + + ast_verb(3, "AutoMixMonitor used to record call. Filename: %s\n", touch_filename); + + if (ast_start_mixmonitor(peer_chan, touch_filename, "b")) { + ast_verb(3, "automixmon feature was tried by '%s' but mixmonitor failed to start.\n", ast_channel_name(bridge_channel->chan)); + + if (features_cfg && !ast_strlen_zero(features_cfg->recordingfailsound)) { + ast_bridge_channel_queue_playfile(bridge_channel, NULL, features_cfg->recordingfailsound, NULL); + } + + return 0; + } + + ast_channel_lock(bridge_channel->chan); + if ((automon_message = pbx_builtin_getvar_helper(bridge_channel->chan, "TOUCH_MIXMONITOR_MESSAGE_START"))) { + automon_message = ast_strdupa(automon_message); + } + ast_channel_unlock(bridge_channel->chan); + + if ((features_cfg = ast_get_chan_features_general_config(bridge_channel->chan)) && !(ast_strlen_zero(features_cfg->courtesytone))) { + ast_bridge_channel_queue_playfile(bridge_channel, NULL, features_cfg->courtesytone, NULL); + ast_bridge_channel_write_playfile(bridge_channel, NULL, features_cfg->courtesytone, NULL); + } + + if (!ast_strlen_zero(automon_message)) { + ast_bridge_channel_queue_playfile(bridge_channel, NULL, automon_message, NULL); + ast_bridge_channel_write_playfile(bridge_channel, NULL, automon_message, NULL); + } + + pbx_builtin_setvar_helper(peer_chan, "TOUCH_MIXMONITOR_OUTPUT", touch_filename); + + return 0; +} + /*! \brief Internal built in feature for hangup */ static int feature_hangup(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt) { @@ -485,6 +786,8 @@ static int load_module(void) ast_bridge_features_register(AST_BRIDGE_BUILTIN_BLINDTRANSFER, feature_blind_transfer, NULL); ast_bridge_features_register(AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, feature_attended_transfer, NULL); ast_bridge_features_register(AST_BRIDGE_BUILTIN_HANGUP, feature_hangup, NULL); + ast_bridge_features_register(AST_BRIDGE_BUILTIN_AUTOMON, feature_automonitor, NULL); + ast_bridge_features_register(AST_BRIDGE_BUILTIN_AUTOMIXMON, feature_automixmonitor, NULL); /* Bump up our reference count so we can't be unloaded */ ast_module_ref(ast_module_info->self); diff --git a/configs/features.conf.sample b/configs/features.conf.sample index 12fb3151c9..fbaa9a0cc0 100644 --- a/configs/features.conf.sample +++ b/configs/features.conf.sample @@ -105,6 +105,8 @@ context => parkedcalls ; Which context parked calls are in (default par ;pickupfailsound = beeperr ; to indicate that the pickup failed (default: no sound) ;featuredigittimeout = 1000 ; Max time (ms) between digits for ; feature activation (default is 1000 ms) +;recordingfailsound = beeperr ; indicates that a one-touch monitor or one-touch mixmonitor feature failed + ; to be applied to the call. (default: no sound) ;atxfernoanswertimeout = 15 ; Timeout for answer on attended transfer default is 15 seconds. ;atxferdropcall = no ; If someone does an attended transfer, then hangs up before the transferred ; caller is connected, then by default, the system will try to call back the diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 1b36c14ae3..fae43d423b 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -2596,6 +2596,17 @@ struct ast_group_info { */ #define ast_channel_unref(c) ({ ao2_ref(c, -1); (struct ast_channel *) (NULL); }) +/*! + * \brief Cleanup a channel reference + * + * \param c the channel (NULL tolerant) + * + * \retval NULL always + * + * \since 12.0.0 + */ +#define ast_channel_cleanup(c) ({ ao2_cleanup(c); (struct ast_channel *) (NULL); }) + /*! Channel Iterating @{ */ /*! diff --git a/include/asterisk/features_config.h b/include/asterisk/features_config.h index a80fa7968d..7be019cd29 100644 --- a/include/asterisk/features_config.h +++ b/include/asterisk/features_config.h @@ -30,6 +30,8 @@ struct ast_features_general_config { AST_DECLARE_STRING_FIELDS( /*! Sound played when automon or automixmon features are used */ AST_STRING_FIELD(courtesytone); + /*! Sound played when automon or automixmon features fail when used */ + AST_STRING_FIELD(recordingfailsound); ); /*! Milliseconds allowed between digit presses when entering feature code */ unsigned int featuredigittimeout; diff --git a/include/asterisk/mixmonitor.h b/include/asterisk/mixmonitor.h new file mode 100644 index 0000000000..892cd2a780 --- /dev/null +++ b/include/asterisk/mixmonitor.h @@ -0,0 +1,105 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Jonathan Rose + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief loadable MixMonitor functionality + * + * \author Jonathan Rose + */ + +/*! + * \brief Start a mixmonitor on a channel. + * \since 12.0.0 + * + * \param chan Which channel to put the MixMonitor on + * \param filename What the name of the file should be + * \param options What options should be used for the mixmonitor + * + * \retval 0 on success + * \retval non-zero on failure + */ +typedef int (*ast_mixmonitor_start_fn)(struct ast_channel *chan, const char *filename, const char *options); + +/*! + * \brief Stop a mixmonitor on a channel. + * \since 12.0.0 + * + * \param chan Which channel to stop a MixMonitor on + * \param mixmon_id Stop the MixMonitor with this mixmonid if it is on the channel (may be NULL) + * + * \retval 0 on success + * \retval non-zero on failure + */ +typedef int (*ast_mixmonitor_stop_fn)(struct ast_channel *chan, const char *mixmon_id); + +/*! + * \brief MixMonitor virtual methods table definition + * \since 12.0.0 + */ +struct ast_mixmonitor_methods { + ast_mixmonitor_start_fn start; + ast_mixmonitor_stop_fn stop; +}; + +/*! + * \brief Setup MixMonitor virtual methods table. Use this to provide the MixMonitor functionality from a loadable module. + * \since 12.0.0 + * + * \param vmethod_table pointer to vmethod table providing mixmonitor functions + * + * \retval 0 if successful + * \retval non-zero on failure + */ +int ast_set_mixmonitor_methods(struct ast_mixmonitor_methods *vmethod_table); + +/*! + * \brief Clear the MixMonitor virtual methods table. Use this to cleanup function pointers provided by a module that set. + * \since 12.0.0 + * + * \retval 0 if successful + * \retval non-zero on failure (occurs when methods aren't loaded) + */ +int ast_clear_mixmonitor_methods(void); + +/*! + * \brief Start a mixmonitor on a channel with the given parameters + * \since 12.0.0 + * + * \param chan Which channel to apply the MixMonitor to + * \param filename filename to use for the recording + * \param options Optional arguments to be interpreted by the MixMonitor start function + * + * \retval 0 if successful + * \retval non-zero on failure + * + * \note This function will always fail is nothing has set the mixmonitor methods + */ +int ast_start_mixmonitor(struct ast_channel *chan, const char *filename, const char *options); + +/*! + * \brief Stop a mixmonitor on a channel with the given parameters + * \since 12.0.0 + * + * \param chan Which channel to stop a MixMonitor on (may be NULL if mixmon_id is provided) + * \param mixmon_id Which mixmon_id should be stopped (may be NULL if chan is provided) + * + * \retval 0 if successful + * \retval non-zero on failure + */ +int ast_stop_mixmonitor(struct ast_channel *chan, const char *mixmon_id); diff --git a/main/features_config.c b/main/features_config.c index 9fe72a2252..66fdbfae80 100644 --- a/main/features_config.c +++ b/main/features_config.c @@ -39,6 +39,9 @@ Sound to play when automon or automixmon is activated + + Sound to play when automon or automixmon is attempted but fails to start + Milliseconds allowed between digit presses when dialing a transfer destination @@ -292,6 +295,7 @@ + @@ -338,6 +342,7 @@ /*! Default general options */ #define DEFAULT_FEATURE_DIGIT_TIMEOUT 1000 #define DEFAULT_COURTESY_TONE "" +#define DEFAULT_RECORDING_FAIL_SOUND "" /*! Default xfer options */ #define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000 @@ -787,6 +792,8 @@ static int general_set(struct ast_features_general_config *general, const char * res = ast_parse_arg(value, PARSE_INT32, &general->featuredigittimeout); } else if (!strcasecmp(name, "courtesytone")) { ast_string_field_set(general, courtesytone, value); + } else if (!strcasecmp(name, "recordingfailsound")) { + ast_string_field_set(general, recordingfailsound, value); } else { /* Unrecognized option */ res = -1; @@ -804,6 +811,8 @@ static int general_get(struct ast_features_general_config *general, const char * snprintf(buf, len, "%u", general->featuredigittimeout); } else if (!strcasecmp(field, "courtesytone")) { ast_copy_string(buf, general->courtesytone, len); + } else if (!strcasecmp(field, "recordingfailsound")) { + ast_copy_string(buf, general->recordingfailsound, len); } else { /* Unrecognized option */ res = -1; @@ -1595,8 +1604,10 @@ static int load_config(void) aco_option_register_custom(&cfg_info, "featuredigittimeout", ACO_EXACT, global_options, __stringify(DEFAULT_FEATURE_DIGIT_TIMEOUT), general_handler, 0); + aco_option_register_custom(&cfg_info, "recordingfailsound", ACO_EXACT, global_options, + DEFAULT_RECORDING_FAIL_SOUND, general_handler, 0); aco_option_register_custom(&cfg_info, "courtesytone", ACO_EXACT, global_options, - __stringify(DEFAULT_COURTESY_TONE), general_handler, 0); + DEFAULT_COURTESY_TONE, general_handler, 0); aco_option_register_custom(&cfg_info, "transferdigittimeout", ACO_EXACT, global_options, __stringify(DEFAULT_TRANSFER_DIGIT_TIMEOUT), xfer_handler, 0) diff --git a/main/mixmonitor.c b/main/mixmonitor.c new file mode 100644 index 0000000000..6de72794ab --- /dev/null +++ b/main/mixmonitor.c @@ -0,0 +1,98 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Jonathan Rose + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief loadable MixMonitor functionality + * + * \author Jonathan Rose + */ + +/*** MODULEINFO + core + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 390830 $") + +#include "asterisk/lock.h" +#include "asterisk/logger.h" +#include "asterisk/mixmonitor.h" +#include "asterisk/utils.h" +#include "asterisk/channel.h" + +AST_RWLOCK_DEFINE_STATIC(mixmonitor_lock); + +static struct ast_mixmonitor_methods mixmonitor_methods; +static int table_loaded = 0; + +int ast_set_mixmonitor_methods(struct ast_mixmonitor_methods *method_table) +{ + SCOPED_WRLOCK(lock, &mixmonitor_lock); + + if (table_loaded) { + /* If mixmonitor methods have already been provided, reject the new set */ + ast_log(LOG_ERROR, "Tried to set mixmonitor methods, but something else has already provided them.\n"); + return -1; + } + + mixmonitor_methods = *method_table; + + table_loaded = 1; + return 0; +} + +int ast_clear_mixmonitor_methods(void) +{ + SCOPED_WRLOCK(lock, &mixmonitor_lock); + + if (!table_loaded) { + ast_log(LOG_ERROR, "Tried to clear mixmonitor methods, but none are currently loaded.\n"); + return -1; + } + + memset(&mixmonitor_methods, 0, sizeof(mixmonitor_methods)); + + table_loaded = 0; + return 0; +} + +int ast_start_mixmonitor(struct ast_channel *chan, const char *filename, const char *options) +{ + SCOPED_RDLOCK(lock, &mixmonitor_lock); + + if (!mixmonitor_methods.start) { + ast_log(LOG_ERROR, "No loaded module currently provides MixMonitor starting functionality.\n"); + return -1; + } + + return mixmonitor_methods.start(chan, filename, options); +} + +int ast_stop_mixmonitor(struct ast_channel *chan, const char *mixmon_id) +{ + SCOPED_RDLOCK(lock, &mixmonitor_lock); + + if (!mixmonitor_methods.stop) { + ast_log(LOG_ERROR, "No loaded module currently provides MixMonitor stopping functionality.\n"); + return -1; + } + + return mixmonitor_methods.stop(chan, mixmon_id); +}