diff options
author | Richard Mudgett <rmudgett@digium.com> | 2013-05-21 18:00:22 +0000 |
---|---|---|
committer | Richard Mudgett <rmudgett@digium.com> | 2013-05-21 18:00:22 +0000 |
commit | 3d63833bd6c869b7efa383e8dea14be1a6eff998 (patch) | |
tree | 34957dd051b8f67c7cc58a510e24ee3873a61ad4 /apps | |
parent | e1e1cc2deefb92f8b43825f1f34e619354737842 (diff) |
Merge in the bridge_construction branch to make the system use the Bridging API.
Breaks many things until they can be reworked. A partial list:
chan_agent
chan_dahdi, chan_misdn, chan_iax2 native bridging
app_queue
COLP updates
DTMF attended transfers
Protocol attended transfers
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@389378 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'apps')
-rw-r--r-- | apps/app_bridgewait.c | 245 | ||||
-rw-r--r-- | apps/app_channelredirect.c | 4 | ||||
-rw-r--r-- | apps/app_chanspy.c | 9 | ||||
-rw-r--r-- | apps/app_confbridge.c | 501 | ||||
-rw-r--r-- | apps/app_dial.c | 78 | ||||
-rw-r--r-- | apps/app_dumpchan.c | 12 | ||||
-rw-r--r-- | apps/app_followme.c | 2 | ||||
-rw-r--r-- | apps/app_mixmonitor.c | 10 | ||||
-rw-r--r-- | apps/app_parkandannounce.c | 249 | ||||
-rw-r--r-- | apps/app_queue.c | 71 | ||||
-rw-r--r-- | apps/confbridge/conf_chan_announce.c | 207 | ||||
-rw-r--r-- | apps/confbridge/conf_chan_record.c | 94 | ||||
-rw-r--r-- | apps/confbridge/conf_config_parser.c | 4 | ||||
-rw-r--r-- | apps/confbridge/confbridge_manager.c | 339 | ||||
-rw-r--r-- | apps/confbridge/include/confbridge.h | 63 |
15 files changed, 1248 insertions, 640 deletions
diff --git a/apps/app_bridgewait.c b/apps/app_bridgewait.c new file mode 100644 index 000000000..ca12f0d30 --- /dev/null +++ b/apps/app_bridgewait.c @@ -0,0 +1,245 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * + * Author: Jonathan Rose <jrose@digium.com> + * + * 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 Application to place the channel into a holding Bridge + * + * \author Jonathan Rose <jrose@digium.com> + * + * \ingroup applications + */ + +/*** MODULEINFO + <depend>bridge_holding</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/features.h" +#include "asterisk/say.h" +#include "asterisk/lock.h" +#include "asterisk/utils.h" +#include "asterisk/app.h" +#include "asterisk/bridging.h" +#include "asterisk/musiconhold.h" + +/*** DOCUMENTATION + <application name="BridgeWait" language="en_US"> + <synopsis> + Put a call into the holding bridge. + </synopsis> + <syntax> + <parameter name="options"> + <optionlist> + <option name="A"> + <para>The channel will join the holding bridge as an + announcer</para> + </option> + <option name="m"> + <argument name="class" required="false" /> + <para>Play music on hold to the entering channel while it is + on hold. If the <emphasis>class</emphasis> is included, then + that class of music on hold will take priority over the + channel default.</para> + </option> + <option name="r"> + <para>Play a ringing tone to the entering channel while it is + on hold.</para> + </option> + <option name="S"> + <argument name="duration" required="true" /> + <para>Automatically end the hold and return to the PBX after + <emphasis>duration</emphasis> seconds.</para> + </option> + </optionlist> + </parameter> + </syntax> + <description> + <para>This application places the incoming channel into a holding bridge. + The channel will then wait in the holding bridge until some + event occurs which removes it from the holding bridge.</para> + </description> + </application> + ***/ +/* BUGBUG Add bridge name/id parameter to specify which holding bridge to join (required) */ +/* BUGBUG Add h(moh-class) option to put channel on hold using AST_CONTROL_HOLD/AST_CONTROL_UNHOLD while in bridge */ +/* BUGBUG Add s option to send silence media frames to channel while in bridge (uses a silence generator) */ +/* BUGBUG Add n option to send no media to channel while in bridge (Channel should not be answered yet) */ +/* BUGBUG The channel may or may not be answered with the r option. */ +/* BUGBUG You should not place an announcer into a holding bridge with unanswered channels. */ +/* BUGBUG Not supplying any option flags will assume the m option with the default music class. */ + +static char *app = "BridgeWait"; +static struct ast_bridge *holding_bridge; + +AST_MUTEX_DEFINE_STATIC(bridgewait_lock); + +enum bridgewait_flags { + MUXFLAG_PLAYMOH = (1 << 0), + MUXFLAG_RINGING = (1 << 1), + MUXFLAG_TIMEOUT = (1 << 2), + MUXFLAG_ANNOUNCER = (1 << 3), +}; + +enum bridgewait_args { + OPT_ARG_MOHCLASS, + OPT_ARG_TIMEOUT, + OPT_ARG_ARRAY_SIZE, /* Always the last element of the enum */ +}; + +AST_APP_OPTIONS(bridgewait_opts, { + AST_APP_OPTION('A', MUXFLAG_ANNOUNCER), + AST_APP_OPTION('r', MUXFLAG_RINGING), + AST_APP_OPTION_ARG('m', MUXFLAG_PLAYMOH, OPT_ARG_MOHCLASS), + AST_APP_OPTION_ARG('S', MUXFLAG_TIMEOUT, OPT_ARG_TIMEOUT), +}); + +static int apply_option_timeout(struct ast_bridge_features *features, char *duration_arg) +{ + struct ast_bridge_features_limits hold_limits; + + if (ast_strlen_zero(duration_arg)) { + ast_log(LOG_ERROR, "No duration value provided for the timeout ('S') option.\n"); + return -1; + } + + if (ast_bridge_features_limits_construct(&hold_limits)) { + ast_log(LOG_ERROR, "Could not construct duration limits. Bridge canceled.\n"); + return -1; + } + + if (sscanf(duration_arg, "%u", &(hold_limits.duration)) != 1 || hold_limits.duration == 0) { + ast_log(LOG_ERROR, "Duration value provided for the timeout ('S') option must be greater than 0\n"); + ast_bridge_features_limits_destroy(&hold_limits); + return -1; + } + + /* Limits struct holds time as milliseconds, so muliply 1000x */ + hold_limits.duration *= 1000; + ast_bridge_features_set_limits(features, &hold_limits, 1 /* remove_on_pull */); + ast_bridge_features_limits_destroy(&hold_limits); + + return 0; +} + +static void apply_option_moh(struct ast_channel *chan, char *class_arg) +{ + ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "musiconhold"); + ast_channel_set_bridge_role_option(chan, "holding_participant", "moh_class", class_arg); +} + +static void apply_option_ringing(struct ast_channel *chan) +{ + ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "ringing"); +} + +static int process_options(struct ast_channel *chan, struct ast_flags *flags, char **opts, struct ast_bridge_features *features) +{ + if (ast_test_flag(flags, MUXFLAG_TIMEOUT)) { + if (apply_option_timeout(features, opts[OPT_ARG_TIMEOUT])) { + return -1; + } + } + + if (ast_test_flag(flags, MUXFLAG_ANNOUNCER)) { + /* Announcer specific stuff */ + ast_channel_add_bridge_role(chan, "announcer"); + } else { + /* Non Announcer specific stuff */ + ast_channel_add_bridge_role(chan, "holding_participant"); + + if (ast_test_flag(flags, MUXFLAG_PLAYMOH)) { + apply_option_moh(chan, opts[OPT_ARG_MOHCLASS]); + } else if (ast_test_flag(flags, MUXFLAG_RINGING)) { + apply_option_ringing(chan); + } + } + + return 0; +} + +static int bridgewait_exec(struct ast_channel *chan, const char *data) +{ + struct ast_bridge_features chan_features; + struct ast_flags flags = { 0 }; + char *parse; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(options); + AST_APP_ARG(other); /* Any remaining unused arguments */ + ); + + ast_mutex_lock(&bridgewait_lock); + if (!holding_bridge) { + holding_bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_HOLDING, + AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM + | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_TRANSFER_PROHIBITED); + } + ast_mutex_unlock(&bridgewait_lock); + if (!holding_bridge) { + ast_log(LOG_ERROR, "Could not create holding bridge for '%s'.\n", ast_channel_name(chan)); + return -1; + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_bridge_features_init(&chan_features)) { + ast_bridge_features_cleanup(&chan_features); + return -1; + } + + if (args.options) { + char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, }; + ast_app_parse_options(bridgewait_opts, &flags, opts, args.options); + if (process_options(chan, &flags, opts, &chan_features)) { + ast_bridge_features_cleanup(&chan_features); + return -1; + } + } + + ast_bridge_join(holding_bridge, chan, NULL, &chan_features, NULL, 0); + + ast_bridge_features_cleanup(&chan_features); + return ast_check_hangup_locked(chan) ? -1 : 0; +} + +static int unload_module(void) +{ + ao2_cleanup(holding_bridge); + holding_bridge = NULL; + + return ast_unregister_application(app); +} + +static int load_module(void) +{ + return ast_register_application_xml(app, bridgewait_exec); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Place the channel into a holding bridge application"); diff --git a/apps/app_channelredirect.c b/apps/app_channelredirect.c index 8c98ed7b7..f636e0248 100644 --- a/apps/app_channelredirect.c +++ b/apps/app_channelredirect.c @@ -96,10 +96,6 @@ static int asyncgoto_exec(struct ast_channel *chan, const char *data) return 0; } - if (ast_channel_pbx(chan2)) { - ast_set_flag(ast_channel_flags(chan2), AST_FLAG_BRIDGE_HANGUP_DONT); /* don't let the after-bridge code run the h-exten */ - } - res = ast_async_parseable_goto(chan2, args.label); chan2 = ast_channel_unref(chan2); diff --git a/apps/app_chanspy.c b/apps/app_chanspy.c index c5adb78ad..8e4590781 100644 --- a/apps/app_chanspy.c +++ b/apps/app_chanspy.c @@ -482,15 +482,18 @@ static struct ast_generator spygen = { static int start_spying(struct ast_autochan *autochan, const char *spychan_name, struct ast_audiohook *audiohook) { int res = 0; - struct ast_channel *peer = NULL; ast_log(LOG_NOTICE, "Attaching %s to %s\n", spychan_name, ast_channel_name(autochan->chan)); ast_set_flag(audiohook, AST_AUDIOHOOK_TRIGGER_SYNC | AST_AUDIOHOOK_SMALL_QUEUE); res = ast_audiohook_attach(autochan->chan, audiohook); - if (!res && ast_test_flag(ast_channel_flags(autochan->chan), AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(autochan->chan))) { - ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE); + if (!res) { + ast_channel_lock(autochan->chan); + if (ast_channel_is_bridged(autochan->chan)) { + ast_softhangup_nolock(autochan->chan, AST_SOFTHANGUP_UNBRIDGE); + } + ast_channel_unlock(autochan->chan); } return res; } diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c index d4cce4b46..5107bcd5c 100644 --- a/apps/app_confbridge.c +++ b/apps/app_confbridge.c @@ -67,6 +67,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/paths.h" #include "asterisk/manager.h" #include "asterisk/test.h" +#include "asterisk/stasis.h" +#include "asterisk/stasis_bridging.h" +#include "asterisk/json.h" /*** DOCUMENTATION <application name="ConfBridge" language="en_US"> @@ -303,7 +306,7 @@ enum { }; /*! \brief Container to hold all conference bridges in progress */ -static struct ao2_container *conference_bridges; +struct ao2_container *conference_bridges; static void leave_conference(struct confbridge_user *user); static int play_sound_number(struct confbridge_conference *conference, int say_number); @@ -412,221 +415,77 @@ const char *conf_get_sound(enum conf_sounds sound, struct bridge_profile_sounds return ""; } -static void send_conf_start_event(const char *conf_name) -{ - /*** DOCUMENTATION - <managerEventInstance> - <synopsis>Raised when a conference starts.</synopsis> - <syntax> - <parameter name="Conference"> - <para>The name of the Confbridge conference.</para> - </parameter> - </syntax> - <see-also> - <ref type="managerEvent">ConfbridgeEnd</ref> - <ref type="application">ConfBridge</ref> - </see-also> - </managerEventInstance> - ***/ - manager_event(EVENT_FLAG_CALL, "ConfbridgeStart", "Conference: %s\r\n", conf_name); -} - -static void send_conf_end_event(const char *conf_name) -{ - /*** DOCUMENTATION - <managerEventInstance> - <synopsis>Raised when a conference ends.</synopsis> - <syntax> - <xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" /> - </syntax> - <see-also> - <ref type="managerEvent">ConfbridgeStart</ref> - </see-also> - </managerEventInstance> - ***/ - manager_event(EVENT_FLAG_CALL, "ConfbridgeEnd", "Conference: %s\r\n", conf_name); -} - -static void send_join_event(struct ast_channel *chan, const char *conf_name) -{ - /*** DOCUMENTATION - <managerEventInstance> - <synopsis>Raised when a channel joins a Confbridge conference.</synopsis> - <syntax> - <xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" /> - </syntax> - <see-also> - <ref type="managerEvent">ConfbridgeLeave</ref> - <ref type="application">ConfBridge</ref> - </see-also> - </managerEventInstance> - ***/ - ast_manager_event(chan, EVENT_FLAG_CALL, "ConfbridgeJoin", - "Channel: %s\r\n" - "Uniqueid: %s\r\n" - "Conference: %s\r\n" - "CallerIDnum: %s\r\n" - "CallerIDname: %s\r\n", - ast_channel_name(chan), - ast_channel_uniqueid(chan), - conf_name, - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, "<unknown>"), - S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, "<unknown>") - ); +static void send_conf_stasis(struct confbridge_conference *conference, struct ast_channel *chan, const char *type, struct ast_json *extras, int channel_topic) +{ + RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); + RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref); + + json_object = ast_json_pack("{s: s, s: s}", + "type", type, + "conference", conference->name); + + if (!json_object) { + return; + } + + if (extras) { + ast_json_object_update(json_object, extras); + } + + msg = ast_bridge_blob_create(confbridge_message_type(), + conference->bridge, + chan, + json_object); + if (!msg) { + return; + } + + if (channel_topic) { + stasis_publish(ast_channel_topic(chan), msg); + } else { + stasis_publish(ast_bridge_topic(conference->bridge), msg); + } + } -static void send_leave_event(struct ast_channel *chan, const char *conf_name) -{ - /*** DOCUMENTATION - <managerEventInstance> - <synopsis>Raised when a channel leaves a Confbridge conference.</synopsis> - <syntax> - <xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" /> - </syntax> - <see-also> - <ref type="managerEvent">ConfbridgeJoin</ref> - </see-also> - </managerEventInstance> - ***/ - ast_manager_event(chan, EVENT_FLAG_CALL, "ConfbridgeLeave", - "Channel: %s\r\n" - "Uniqueid: %s\r\n" - "Conference: %s\r\n" - "CallerIDnum: %s\r\n" - "CallerIDname: %s\r\n", - ast_channel_name(chan), - ast_channel_uniqueid(chan), - conf_name, - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, "<unknown>"), - S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, "<unknown>") - ); +static void send_conf_start_event(struct confbridge_conference *conference) +{ + send_conf_stasis(conference, NULL, "confbridge_start", NULL, 0); } -static void send_start_record_event(const char *conf_name) -{ - /*** DOCUMENTATION - <managerEventInstance> - <synopsis>Raised when a conference recording starts.</synopsis> - <syntax> - <xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" /> - </syntax> - <see-also> - <ref type="managerEvent">ConfbridgeStopRecord</ref> - <ref type="application">ConfBridge</ref> - </see-also> - </managerEventInstance> - ***/ - - manager_event(EVENT_FLAG_CALL, "ConfbridgeStartRecord", "Conference: %s\r\n", conf_name); -} - -static void send_stop_record_event(const char *conf_name) -{ - /*** DOCUMENTATION - <managerEventInstance> - <synopsis>Raised when a conference recording stops.</synopsis> - <syntax> - <xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" /> - </syntax> - <see-also> - <ref type="managerEvent">ConfbridgeStartRecord</ref> - </see-also> - </managerEventInstance> - ***/ - manager_event(EVENT_FLAG_CALL, "ConfbridgeStopRecord", "Conference: %s\r\n", conf_name); -} - -static void send_mute_event(struct ast_channel *chan, const char *conf_name) -{ - /*** DOCUMENTATION - <managerEventInstance> - <synopsis>Raised when a Confbridge participant mutes.</synopsis> - <syntax> - <xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" /> - </syntax> - <see-also> - <ref type="managerEvent">ConfbridgeUnmute</ref> - <ref type="application">ConfBridge</ref> - </see-also> - </managerEventInstance> - ***/ - ast_manager_event(chan, EVENT_FLAG_CALL, "ConfbridgeMute", - "Channel: %s\r\n" - "Uniqueid: %s\r\n" - "Conference: %s\r\n" - "CallerIDnum: %s\r\n" - "CallerIDname: %s\r\n", - ast_channel_name(chan), - ast_channel_uniqueid(chan), - conf_name, - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, "<unknown>"), - S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, "<unknown>") - ); +static void send_conf_end_event(struct confbridge_conference *conference) +{ + send_conf_stasis(conference, NULL, "confbridge_end", NULL, 0); } -static void send_unmute_event(struct ast_channel *chan, const char *conf_name) -{ - /*** DOCUMENTATION - <managerEventInstance> - <synopsis>Raised when a Confbridge participant unmutes.</synopsis> - <syntax> - <xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" /> - </syntax> - <see-also> - <ref type="managerEvent">ConfbridgeMute</ref> - </see-also> - </managerEventInstance> - ***/ - ast_manager_event(chan, EVENT_FLAG_CALL, "ConfbridgeUnmute", - "Channel: %s\r\n" - "Uniqueid: %s\r\n" - "Conference: %s\r\n" - "CallerIDnum: %s\r\n" - "CallerIDname: %s\r\n", - ast_channel_name(chan), - ast_channel_uniqueid(chan), - conf_name, - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, "<unknown>"), - S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, "<unknown>") - ); +static void send_join_event(struct ast_channel *chan, struct confbridge_conference *conference) +{ + send_conf_stasis(conference, chan, "confbridge_join", NULL, 0); } +static void send_leave_event(struct ast_channel *chan, struct confbridge_conference *conference) +{ + send_conf_stasis(conference, chan, "confbridge_leave", NULL, 0); +} -static struct ast_frame *rec_read(struct ast_channel *ast) +static void send_start_record_event(struct confbridge_conference *conference) { - return &ast_null_frame; + send_conf_stasis(conference, NULL, "confbridge_record", NULL, 0); } -static int rec_write(struct ast_channel *ast, struct ast_frame *f) + +static void send_stop_record_event(struct confbridge_conference *conference) { - return 0; + send_conf_stasis(conference, NULL, "confbridge_stop_record", NULL, 0); } -static struct ast_channel *rec_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause); -static struct ast_channel_tech record_tech = { - .type = "ConfBridgeRec", - .description = "Conference Bridge Recording Channel", - .requester = rec_request, - .read = rec_read, - .write = rec_write, -}; -static struct ast_channel *rec_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause) -{ - struct ast_channel *tmp; - struct ast_format fmt; - const char *conf_name = data; - if (!(tmp = ast_channel_alloc(1, AST_STATE_UP, 0, 0, "", "", "", NULL, 0, - "ConfBridgeRecorder/conf-%s-uid-%d", - conf_name, - (int) ast_random()))) { - return NULL; - } - ast_format_set(&fmt, AST_FORMAT_SLINEAR, 0); - ast_channel_tech_set(tmp, &record_tech); - ast_format_cap_add_all(ast_channel_nativeformats(tmp)); - ast_format_copy(ast_channel_writeformat(tmp), &fmt); - ast_format_copy(ast_channel_rawwriteformat(tmp), &fmt); - ast_format_copy(ast_channel_readformat(tmp), &fmt); - ast_format_copy(ast_channel_rawreadformat(tmp), &fmt); - return tmp; + +static void send_mute_event(struct ast_channel *chan, struct confbridge_conference *conference) +{ + send_conf_stasis(conference, chan, "confbridge_mute", NULL, 1); +} + +static void send_unmute_event(struct ast_channel *chan, struct confbridge_conference *conference) +{ + send_conf_stasis(conference, chan, "confbridge_unmute", NULL, 1); } static void set_rec_filename(struct confbridge_conference *conference, struct ast_str **filename, int is_new) @@ -682,6 +541,7 @@ static void *record_thread(void *obj) struct ast_channel *chan; struct ast_str *filename = ast_str_alloca(PATH_MAX); struct ast_str *orig_rec_file = NULL; + struct ast_bridge_features features; ast_mutex_lock(&conference->record_lock); if (!mixmonapp) { @@ -691,20 +551,29 @@ static void *record_thread(void *obj) ao2_ref(conference, -1); return NULL; } + if (ast_bridge_features_init(&features)) { + ast_bridge_features_cleanup(&features); + conference->record_thread = AST_PTHREADT_NULL; + ast_mutex_unlock(&conference->record_lock); + ao2_ref(conference, -1); + return NULL; + } + ast_set_flag(&features.feature_flags, AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE); /* XXX If we get an EXIT right here, START will essentially be a no-op */ while (conference->record_state != CONF_RECORD_EXIT) { set_rec_filename(conference, &filename, - is_new_rec_file(conference->b_profile.rec_file, &orig_rec_file)); + is_new_rec_file(conference->b_profile.rec_file, &orig_rec_file)); chan = ast_channel_ref(conference->record_chan); ast_answer(chan); pbx_exec(chan, mixmonapp, ast_str_buffer(filename)); - ast_bridge_join(conference->bridge, chan, NULL, NULL, NULL); + ast_bridge_join(conference->bridge, chan, NULL, &features, NULL, 0); ast_hangup(chan); /* This will eat this thread's reference to the channel as well */ /* STOP has been called. Wait for either a START or an EXIT */ ast_cond_wait(&conference->record_cond, &conference->record_lock); } + ast_bridge_features_cleanup(&features); ast_free(orig_rec_file); ast_mutex_unlock(&conference->record_lock); ao2_ref(conference, -1); @@ -739,7 +608,7 @@ static int conf_stop_record(struct confbridge_conference *conference) ast_queue_frame(chan, &ast_null_frame); chan = ast_channel_unref(chan); ast_test_suite_event_notify("CONF_STOP_RECORD", "Message: stopped conference recording channel\r\nConference: %s", conference->b_profile.name); - send_stop_record_event(conference->name); + send_stop_record_event(conference); return 0; } @@ -783,8 +652,7 @@ static int conf_stop_record_thread(struct confbridge_conference *conference) static int conf_start_record(struct confbridge_conference *conference) { struct ast_format_cap *cap; - struct ast_format tmpfmt; - int cause; + struct ast_format format; if (conference->record_state != CONF_RECORD_STOP) { return -1; @@ -795,25 +663,26 @@ static int conf_start_record(struct confbridge_conference *conference) return -1; } - if (!(cap = ast_format_cap_alloc_nolock())) { + cap = ast_format_cap_alloc_nolock(); + if (!cap) { return -1; } - ast_format_cap_add(cap, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0)); + ast_format_cap_add(cap, ast_format_set(&format, AST_FORMAT_SLINEAR, 0)); - if (!(conference->record_chan = ast_request("ConfBridgeRec", cap, NULL, conference->name, &cause))) { - cap = ast_format_cap_destroy(cap); + conference->record_chan = ast_request("CBRec", cap, NULL, + conference->name, NULL); + cap = ast_format_cap_destroy(cap); + if (!conference->record_chan) { return -1; } - cap = ast_format_cap_destroy(cap); - conference->record_state = CONF_RECORD_START; ast_mutex_lock(&conference->record_lock); ast_cond_signal(&conference->record_cond); ast_mutex_unlock(&conference->record_lock); ast_test_suite_event_notify("CONF_START_RECORD", "Message: started conference recording channel\r\nConference: %s", conference->b_profile.name); - send_start_record_event(conference->name); + send_start_record_event(conference); return 0; } @@ -1017,10 +886,7 @@ static void destroy_conference_bridge(void *obj) ast_debug(1, "Destroying conference bridge '%s'\n", conference->name); if (conference->playback_chan) { - struct ast_channel *underlying_channel = ast_channel_tech(conference->playback_chan)->bridged_channel(conference->playback_chan, NULL); - if (underlying_channel) { - ast_hangup(underlying_channel); - } + conf_announce_channel_depart(conference->playback_chan); ast_hangup(conference->playback_chan); conference->playback_chan = NULL; } @@ -1252,7 +1118,7 @@ void conf_ended(struct confbridge_conference *conference) { /* Called with a reference to conference */ ao2_unlink(conference_bridges, conference); - send_conf_end_event(conference->name); + send_conf_end_event(conference); conf_stop_record_thread(conference); } @@ -1314,7 +1180,9 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen conf_bridge_profile_copy(&conference->b_profile, &user->b_profile); /* Create an actual bridge that will do the audio mixing */ - if (!(conference->bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_MULTIMIX, 0))) { + conference->bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_MULTIMIX, + AST_BRIDGE_FLAG_MASQUERADE_ONLY | AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY); + if (!conference->bridge) { ao2_ref(conference, -1); conference = NULL; ao2_unlock(conference_bridges); @@ -1351,7 +1219,7 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen ao2_unlock(conference); } - send_conf_start_event(conference->name); + send_conf_start_event(conference); ast_debug(1, "Created conference '%s' and linked to container.\n", conference_name); } @@ -1452,74 +1320,59 @@ static void leave_conference(struct confbridge_user *user) /*! * \internal - * \brief allocates playback chan on a channel + * \brief Allocate playback channel for a conference. * \pre expects conference to be locked before calling this function */ static int alloc_playback_chan(struct confbridge_conference *conference) { - int cause; struct ast_format_cap *cap; - struct ast_format tmpfmt; + struct ast_format format; - if (conference->playback_chan) { - return 0; - } - if (!(cap = ast_format_cap_alloc_nolock())) { - return -1; - } - ast_format_cap_add(cap, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0)); - if (!(conference->playback_chan = ast_request("Bridge", cap, NULL, "", &cause))) { - cap = ast_format_cap_destroy(cap); + cap = ast_format_cap_alloc_nolock(); + if (!cap) { return -1; } + ast_format_cap_add(cap, ast_format_set(&format, AST_FORMAT_SLINEAR, 0)); + conference->playback_chan = ast_request("CBAnn", cap, NULL, + conference->name, NULL); cap = ast_format_cap_destroy(cap); - - ast_channel_internal_bridge_set(conference->playback_chan, conference->bridge); - - if (ast_call(conference->playback_chan, "", 0)) { - ast_hangup(conference->playback_chan); - conference->playback_chan = NULL; + if (!conference->playback_chan) { return -1; } - ast_debug(1, "Created a playback channel to conference bridge '%s'\n", conference->name); + ast_debug(1, "Created announcer channel '%s' to conference bridge '%s'\n", + ast_channel_name(conference->playback_chan), conference->name); return 0; } static int play_sound_helper(struct confbridge_conference *conference, const char *filename, int say_number) { - struct ast_channel *underlying_channel; - /* Do not waste resources trying to play files that do not exist */ if (!ast_strlen_zero(filename) && !sound_file_exists(filename)) { return 0; } ast_mutex_lock(&conference->playback_lock); - if (!(conference->playback_chan)) { - if (alloc_playback_chan(conference)) { - ast_mutex_unlock(&conference->playback_lock); - return -1; - } - underlying_channel = ast_channel_tech(conference->playback_chan)->bridged_channel(conference->playback_chan, NULL); - } else { - /* Channel was already available so we just need to add it back into the bridge */ - underlying_channel = ast_channel_tech(conference->playback_chan)->bridged_channel(conference->playback_chan, NULL); - if (ast_bridge_impart(conference->bridge, underlying_channel, NULL, NULL, 0)) { - ast_mutex_unlock(&conference->playback_lock); - return -1; - } + if (!conference->playback_chan && alloc_playback_chan(conference)) { + ast_mutex_unlock(&conference->playback_lock); + return -1; + } + if (conf_announce_channel_push(conference->playback_chan)) { + ast_mutex_unlock(&conference->playback_lock); + return -1; } /* The channel is all under our control, in goes the prompt */ if (!ast_strlen_zero(filename)) { ast_stream_and_wait(conference->playback_chan, filename, ""); } else if (say_number >= 0) { - ast_say_number(conference->playback_chan, say_number, "", ast_channel_language(conference->playback_chan), NULL); + ast_say_number(conference->playback_chan, say_number, "", + ast_channel_language(conference->playback_chan), NULL); } - ast_debug(1, "Departing underlying channel '%s' from bridge '%p'\n", ast_channel_name(underlying_channel), conference->bridge); - ast_bridge_depart(conference->bridge, underlying_channel); + ast_debug(1, "Departing announcer channel '%s' from conference bridge '%s'\n", + ast_channel_name(conference->playback_chan), conference->name); + conf_announce_channel_depart(conference->playback_chan); ast_mutex_unlock(&conference->playback_lock); @@ -1550,43 +1403,25 @@ static void conf_handle_talker_destructor(void *pvt_data) ast_free(pvt_data); } -static void conf_handle_talker_cb(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *pvt_data) +static void conf_handle_talker_cb(struct ast_bridge_channel *bridge_channel, void *pvt_data, int talking) { - char *conf_name = pvt_data; - int talking; + const char *conf_name = pvt_data; + struct confbridge_conference *conference = ao2_find(conference_bridges, conf_name, OBJ_KEY); + struct ast_json *talking_extras; - switch (bridge_channel->state) { - case AST_BRIDGE_CHANNEL_STATE_START_TALKING: - talking = 1; - break; - case AST_BRIDGE_CHANNEL_STATE_STOP_TALKING: - talking = 0; - break; - default: - return; /* uhh this shouldn't happen, but bail if it does. */ - } - - /* notify AMI someone is has either started or stopped talking */ - /*** DOCUMENTATION - <managerEventInstance> - <synopsis>Raised when a conference participant has started or stopped talking.</synopsis> - <syntax> - <xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" /> - <parameter name="TalkingStatus"> - <enumlist> - <enum name="on"/> - <enum name="off"/> - </enumlist> - </parameter> - </syntax> - </managerEventInstance> - ***/ - ast_manager_event(bridge_channel->chan, EVENT_FLAG_CALL, "ConfbridgeTalking", - "Channel: %s\r\n" - "Uniqueid: %s\r\n" - "Conference: %s\r\n" - "TalkingStatus: %s\r\n", - ast_channel_name(bridge_channel->chan), ast_channel_uniqueid(bridge_channel->chan), conf_name, talking ? "on" : "off"); + if (!conference) { + return; + } + + talking_extras = ast_json_pack("{s: s}", + "talking_status", talking ? "on" : "off"); + + if (!talking_extras) { + return; + } + + send_conf_stasis(conference, bridge_channel->chan, "confbridge_talking", talking_extras, 0); + ast_json_unref(talking_extras); } static int conf_get_pin(struct ast_channel *chan, struct confbridge_user *user) @@ -1681,12 +1516,16 @@ static int confbridge_exec(struct ast_channel *chan, const char *data) AST_APP_ARG(u_profile_name); AST_APP_ARG(menu_name); ); - ast_bridge_features_init(&user.features); if (ast_channel_state(chan) != AST_STATE_UP) { ast_answer(chan); } + if (ast_bridge_features_init(&user.features)) { + res = -1; + goto confbridge_cleanup; + } + if (ast_strlen_zero(data)) { ast_log(LOG_WARNING, "%s requires an argument (conference name[,options])\n", app); res = -1; /* invalid PIN */ @@ -1832,13 +1671,14 @@ static int confbridge_exec(struct ast_channel *chan, const char *data) conf_moh_unsuspend(&user); /* Join our conference bridge for real */ - send_join_event(user.chan, conference->name); + send_join_event(user.chan, conference); ast_bridge_join(conference->bridge, chan, NULL, &user.features, - &user.tech_args); - send_leave_event(user.chan, conference->name); + &user.tech_args, + 0); + send_leave_event(user.chan, conference); /* if we're shutting down, don't attempt to do further processing */ if (ast_shutting_down()) { @@ -1905,9 +1745,9 @@ static int action_toggle_mute(struct confbridge_conference *conference, user->features.mute = (!user->features.mute ? 1 : 0); ast_test_suite_event_notify("CONF_MUTE", "Message: participant %s %s\r\nConference: %s\r\nChannel: %s", ast_channel_name(chan), user->features.mute ? "muted" : "unmuted", user->b_profile.name, ast_channel_name(chan)); if (user->features.mute) { - send_mute_event(chan, conference->name); - } else { - send_unmute_event(chan, conference->name); + send_mute_event(chan, conference); + } else { + send_unmute_event(chan, conference); } } return ast_stream_and_wait(chan, (user->features.mute ? @@ -3207,6 +3047,46 @@ void conf_remove_user_waiting(struct confbridge_conference *conference, struct c conference->waitingusers--; } +/*! + * \internal + * \brief Unregister a ConfBridge channel technology. + * \since 12.0.0 + * + * \param tech What to unregister. + * + * \return Nothing + */ +static void unregister_channel_tech(struct ast_channel_tech *tech) +{ + ast_channel_unregister(tech); + tech->capabilities = ast_format_cap_destroy(tech->capabilities); +} + +/*! + * \internal + * \brief Register a ConfBridge channel technology. + * \since 12.0.0 + * + * \param tech What to register. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int register_channel_tech(struct ast_channel_tech *tech) +{ + tech->capabilities = ast_format_cap_alloc(); + if (!tech->capabilities) { + return -1; + } + ast_format_cap_add_all(tech->capabilities); + if (ast_channel_register(tech)) { + ast_log(LOG_ERROR, "Unable to register channel technology %s(%s).\n", + tech->type, tech->description); + return -1; + } + return 0; +} + /*! \brief Called when module is being unloaded */ static int unload_module(void) { @@ -3228,14 +3108,17 @@ static int unload_module(void) ast_manager_unregister("ConfbridgeStopRecord"); ast_manager_unregister("ConfbridgeSetSingleVideoSrc"); + /* Unsubscribe from stasis confbridge message type and clean it up. */ + manager_confbridge_shutdown(); + /* Get rid of the conference bridges container. Since we only allow dynamic ones none will be active. */ ao2_cleanup(conference_bridges); conference_bridges = NULL; conf_destroy_config(); - ast_channel_unregister(&record_tech); - record_tech.capabilities = ast_format_cap_destroy(record_tech.capabilities); + unregister_channel_tech(conf_announce_get_tech()); + unregister_channel_tech(conf_record_get_tech()); return 0; } @@ -3259,13 +3142,8 @@ static int load_module(void) return AST_MODULE_LOAD_DECLINE; } - if (!(record_tech.capabilities = ast_format_cap_alloc())) { - unload_module(); - return AST_MODULE_LOAD_FAILURE; - } - ast_format_cap_add_all(record_tech.capabilities); - if (ast_channel_register(&record_tech)) { - ast_log(LOG_ERROR, "Unable to register ConfBridge recorder.\n"); + if (register_channel_tech(conf_record_get_tech()) + || register_channel_tech(conf_announce_get_tech())) { unload_module(); return AST_MODULE_LOAD_FAILURE; } @@ -3278,6 +3156,9 @@ static int load_module(void) return AST_MODULE_LOAD_FAILURE; } + /* Setup manager stasis subscriptions */ + res |= manager_confbridge_init(); + res |= ast_register_application_xml(app, confbridge_exec); res |= ast_custom_function_register(&confbridge_function); diff --git a/apps/app_dial.c b/apps/app_dial.c index d3d37216a..35c9ad8bf 100644 --- a/apps/app_dial.c +++ b/apps/app_dial.c @@ -26,7 +26,6 @@ */ /*** MODULEINFO - <depend>chan_local</depend> <support_level>core</support_level> ***/ @@ -67,6 +66,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/ccss.h" #include "asterisk/indications.h" #include "asterisk/framehook.h" +#include "asterisk/bridging.h" #include "asterisk/stasis_channels.h" /*** DOCUMENTATION @@ -2037,6 +2037,40 @@ static int dial_handle_playtones(struct ast_channel *chan, const char *data) return res; } +/*! + * \internal + * \brief Setup the after bridge goto location on the peer. + * \since 12.0.0 + * + * \param chan Calling channel for bridge. + * \param peer Peer channel for bridge. + * \param opts Dialing option flags. + * \param opt_args Dialing option argument strings. + * + * \return Nothing + */ +static void setup_peer_after_bridge_goto(struct ast_channel *chan, struct ast_channel *peer, struct ast_flags64 *opts, char *opt_args[]) +{ + const char *context; + const char *extension; + int priority; + + if (ast_test_flag64(opts, OPT_PEER_H)) { + ast_channel_lock(chan); + context = ast_strdupa(ast_channel_context(chan)); + ast_channel_unlock(chan); + ast_after_bridge_set_h(peer, context); + } else if (ast_test_flag64(opts, OPT_CALLEE_GO_ON)) { + ast_channel_lock(chan); + context = ast_strdupa(ast_channel_context(chan)); + extension = ast_strdupa(ast_channel_exten(chan)); + priority = ast_channel_priority(chan); + ast_channel_unlock(chan); + ast_after_bridge_set_go_on(peer, context, extension, priority, + opt_args[OPT_ARG_CALLEE_GO_ON]); + } +} + static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast_flags64 *peerflags, int *continue_exec) { int res = -1; /* default: error */ @@ -2974,6 +3008,14 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast } if (res) { /* some error */ + if (!ast_check_hangup(chan) && ast_check_hangup(peer)) { + ast_channel_hangupcause_set(chan, ast_channel_hangupcause(peer)); + } + setup_peer_after_bridge_goto(chan, peer, &opts, opt_args); + if (ast_after_bridge_goto_setup(peer) + || ast_pbx_start(peer)) { + ast_autoservice_chan_hangup_peer(chan, peer); + } res = -1; } else { if (ast_test_flag64(peerflags, OPT_CALLEE_TRANSFER)) @@ -2996,8 +3038,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMIXMON); if (ast_test_flag64(peerflags, OPT_CALLER_MIXMONITOR)) ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMIXMON); - if (ast_test_flag64(peerflags, OPT_GO_ON)) - ast_set_flag(&(config.features_caller), AST_FEATURE_NO_H_EXTEN); config.end_bridge_callback = end_bridge_callback; config.end_bridge_callback_data = chan; @@ -3029,38 +3069,10 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast ast_channel_setoption(chan, AST_OPTION_OPRMODE, &oprmode, sizeof(oprmode), 0); } +/* BUGBUG bridge needs to set hangup cause on chan when peer breaks the bridge. */ + setup_peer_after_bridge_goto(chan, peer, &opts, opt_args); res = ast_bridge_call(chan, peer, &config); } - - ast_channel_context_set(peer, ast_channel_context(chan)); - - if (ast_test_flag64(&opts, OPT_PEER_H) - && ast_exists_extension(peer, ast_channel_context(peer), "h", 1, - S_COR(ast_channel_caller(peer)->id.number.valid, ast_channel_caller(peer)->id.number.str, NULL))) { - ast_autoservice_start(chan); - ast_pbx_h_exten_run(peer, ast_channel_context(peer)); - ast_autoservice_stop(chan); - } - if (!ast_check_hangup(peer)) { - if (ast_test_flag64(&opts, OPT_CALLEE_GO_ON)) { - int goto_res; - - if (!ast_strlen_zero(opt_args[OPT_ARG_CALLEE_GO_ON])) { - ast_replace_subargument_delimiter(opt_args[OPT_ARG_CALLEE_GO_ON]); - goto_res = ast_parseable_goto(peer, opt_args[OPT_ARG_CALLEE_GO_ON]); - } else { /* F() */ - goto_res = ast_goto_if_exists(peer, ast_channel_context(chan), - ast_channel_exten(chan), ast_channel_priority(chan) + 1); - } - if (!goto_res && !ast_pbx_start(peer)) { - /* The peer is now running its own PBX. */ - goto out; - } - } - } else if (!ast_check_hangup(chan)) { - ast_channel_hangupcause_set(chan, ast_channel_hangupcause(peer)); - } - ast_autoservice_chan_hangup_peer(chan, peer); } out: if (moh) { diff --git a/apps/app_dumpchan.c b/apps/app_dumpchan.c index 4a80a3d13..722f15541 100644 --- a/apps/app_dumpchan.c +++ b/apps/app_dumpchan.c @@ -41,6 +41,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/channel.h" #include "asterisk/app.h" #include "asterisk/translate.h" +#include "asterisk/bridging.h" /*** DOCUMENTATION <application name="DumpChan" language="en_US"> @@ -77,6 +78,7 @@ static int serialize_showchan(struct ast_channel *c, char *buf, size_t size) char pgrp[256]; struct ast_str *write_transpath = ast_str_alloca(256); struct ast_str *read_transpath = ast_str_alloca(256); + struct ast_bridge *bridge; now = ast_tvnow(); memset(buf, 0, size); @@ -90,6 +92,9 @@ static int serialize_showchan(struct ast_channel *c, char *buf, size_t size) sec = elapsed_seconds % 60; } + ast_channel_lock(c); + bridge = ast_channel_get_bridge(c); + ast_channel_unlock(c); snprintf(buf,size, "Name= %s\n" "Type= %s\n" @@ -117,8 +122,7 @@ static int serialize_showchan(struct ast_channel *c, char *buf, size_t size) "Framesout= %d %s\n" "TimetoHangup= %ld\n" "ElapsedTime= %dh%dm%ds\n" - "DirectBridge= %s\n" - "IndirectBridge= %s\n" + "BridgeID= %s\n" "Context= %s\n" "Extension= %s\n" "Priority= %d\n" @@ -158,8 +162,7 @@ static int serialize_showchan(struct ast_channel *c, char *buf, size_t size) hour, min, sec, - ast_channel_internal_bridged_channel(c) ? ast_channel_name(ast_channel_internal_bridged_channel(c)) : "<none>", - ast_bridged_channel(c) ? ast_channel_name(ast_bridged_channel(c)) : "<none>", + bridge ? bridge->uniqueid : "(Not bridged)", ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), @@ -169,6 +172,7 @@ static int serialize_showchan(struct ast_channel *c, char *buf, size_t size) ast_channel_data(c) ? S_OR(ast_channel_data(c), "(Empty)") : "(None)", (ast_test_flag(ast_channel_flags(c), AST_FLAG_BLOCKING) ? ast_channel_blockproc(c) : "(Not Blocking)")); + ao2_cleanup(bridge); return 0; } diff --git a/apps/app_followme.c b/apps/app_followme.c index 83f583bb3..43f196708 100644 --- a/apps/app_followme.c +++ b/apps/app_followme.c @@ -36,7 +36,6 @@ */ /*** MODULEINFO - <depend>chan_local</depend> <support_level>core</support_level> ***/ @@ -1520,7 +1519,6 @@ static int app_exec(struct ast_channel *chan, const char *data) } res = ast_bridge_call(caller, outbound, &config); - ast_autoservice_chan_hangup_peer(caller, outbound); } outrun: diff --git a/apps/app_mixmonitor.c b/apps/app_mixmonitor.c index 6e7976ec9..e8d4903f0 100644 --- a/apps/app_mixmonitor.c +++ b/apps/app_mixmonitor.c @@ -411,7 +411,6 @@ static void destroy_monitor_audiohook(struct mixmonitor *mixmonitor) static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook) { - struct ast_channel *peer = NULL; int res = 0; if (!chan) @@ -419,8 +418,13 @@ static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook) ast_audiohook_attach(chan, audiohook); - if (!res && ast_test_flag(ast_channel_flags(chan), AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan))) - ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE); + if (!res) { + ast_channel_lock(chan); + if (ast_channel_is_bridged(chan)) { + ast_softhangup_nolock(chan, AST_SOFTHANGUP_UNBRIDGE); + } + ast_channel_unlock(chan); + } return res; } diff --git a/apps/app_parkandannounce.c b/apps/app_parkandannounce.c deleted file mode 100644 index 6d6ccae26..000000000 --- a/apps/app_parkandannounce.c +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Asterisk -- An open source telephony toolkit. - * - * Copyright (C) 1999 - 2006, Digium, Inc. - * - * Mark Spencer <markster@digium.com> - * - * Author: Ben Miller <bgmiller@dccinc.com> - * With TONS of help from Mark! - * - * 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 ParkAndAnnounce application for Asterisk - * - * \author Ben Miller <bgmiller@dccinc.com> - * \arg With TONS of help from Mark! - * - * \ingroup applications - */ - -/*** MODULEINFO - <support_level>core</support_level> - ***/ - -#include "asterisk.h" - -ASTERISK_FILE_VERSION(__FILE__, "$Revision$") - -#include "asterisk/file.h" -#include "asterisk/channel.h" -#include "asterisk/pbx.h" -#include "asterisk/module.h" -#include "asterisk/features.h" -#include "asterisk/say.h" -#include "asterisk/lock.h" -#include "asterisk/utils.h" -#include "asterisk/app.h" - -/*** DOCUMENTATION - <application name="ParkAndAnnounce" language="en_US"> - <synopsis> - Park and Announce. - </synopsis> - <syntax> - <parameter name="announce_template" required="true" argsep=":"> - <argument name="announce" required="true"> - <para>Colon-separated list of files to announce. The word - <literal>PARKED</literal> will be replaced by a say_digits of the extension in which - the call is parked.</para> - </argument> - <argument name="announce1" multiple="true" /> - </parameter> - <parameter name="timeout" required="true"> - <para>Time in seconds before the call returns into the return - context.</para> - </parameter> - <parameter name="dial" required="true"> - <para>The app_dial style resource to call to make the - announcement. Console/dsp calls the console.</para> - </parameter> - <parameter name="return_context"> - <para>The goto-style label to jump the call back into after - timeout. Default <literal>priority+1</literal>.</para> - </parameter> - </syntax> - <description> - <para>Park a call into the parkinglot and announce the call to another channel.</para> - <para>The variable <variable>PARKEDAT</variable> will contain the parking extension - into which the call was placed. Use with the Local channel to allow the dialplan to make - use of this information.</para> - </description> - <see-also> - <ref type="application">Park</ref> - <ref type="application">ParkedCall</ref> - </see-also> - </application> - ***/ - -static char *app = "ParkAndAnnounce"; - -static int parkandannounce_exec(struct ast_channel *chan, const char *data) -{ - int res = -1; - int lot, timeout = 0, dres; - char *dialtech, *tmp[100], buf[13]; - int looptemp, i; - char *s; - struct ast_party_id caller_id; - - struct ast_channel *dchan; - struct outgoing_helper oh = { 0, }; - int outstate; - struct ast_format tmpfmt; - struct ast_format_cap *cap_slin = ast_format_cap_alloc_nolock(); - - AST_DECLARE_APP_ARGS(args, - AST_APP_ARG(template); - AST_APP_ARG(timeout); - AST_APP_ARG(dial); - AST_APP_ARG(return_context); - ); - if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "ParkAndAnnounce requires arguments: (announce_template,timeout,dial,[return_context])\n"); - res = -1; - goto parkcleanup; - } - if (!cap_slin) { - res = -1; - goto parkcleanup; - } - ast_format_cap_add(cap_slin, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0)); - - s = ast_strdupa(data); - AST_STANDARD_APP_ARGS(args, s); - - if (args.timeout) - timeout = atoi(args.timeout) * 1000; - - if (ast_strlen_zero(args.dial)) { - ast_log(LOG_WARNING, "PARK: A dial resource must be specified i.e: Console/dsp or DAHDI/g1/5551212\n"); - res = -1; - goto parkcleanup; - } - - dialtech = strsep(&args.dial, "/"); - ast_verb(3, "Dial Tech,String: (%s,%s)\n", dialtech, args.dial); - - if (!ast_strlen_zero(args.return_context)) { - ast_clear_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP); - ast_parseable_goto(chan, args.return_context); - } else { - ast_channel_priority_set(chan, ast_channel_priority(chan) + 1); - } - - ast_verb(3, "Return Context: (%s,%s,%d) ID: %s\n", ast_channel_context(chan), ast_channel_exten(chan), - ast_channel_priority(chan), - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, "")); - if (!ast_exists_extension(chan, ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan), - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { - ast_verb(3, "Warning: Return Context Invalid, call will return to default|s\n"); - } - - /* Save the CallerID because the masquerade turns chan into a ZOMBIE. */ - ast_party_id_init(&caller_id); - ast_channel_lock(chan); - ast_party_id_copy(&caller_id, &ast_channel_caller(chan)->id); - ast_channel_unlock(chan); - - /* we are using masq_park here to protect * from touching the channel once we park it. If the channel comes out of timeout - before we are done announcing and the channel is messed with, Kablooeee. So we use Masq to prevent this. */ - - res = ast_masq_park_call(chan, NULL, timeout, &lot); - if (res) { - /* Parking failed. */ - ast_party_id_free(&caller_id); - res = -1; - goto parkcleanup; - } - - ast_verb(3, "Call parked in space: %d, timeout: %d, return-context: %s\n", - lot, timeout, args.return_context ? args.return_context : ""); - - /* Now place the call to the extension */ - - snprintf(buf, sizeof(buf), "%d", lot); - oh.parent_channel = chan; - oh.vars = ast_variable_new("_PARKEDAT", buf, ""); - dchan = __ast_request_and_dial(dialtech, cap_slin, chan, args.dial, 30000, - &outstate, - S_COR(caller_id.number.valid, caller_id.number.str, NULL), - S_COR(caller_id.name.valid, caller_id.name.str, NULL), - &oh); - ast_variables_destroy(oh.vars); - ast_party_id_free(&caller_id); - if (dchan) { - if (ast_channel_state(dchan) == AST_STATE_UP) { - ast_verb(4, "Channel %s was answered.\n", ast_channel_name(dchan)); - } else { - ast_verb(4, "Channel %s was never answered.\n", ast_channel_name(dchan)); - ast_log(LOG_WARNING, "PARK: Channel %s was never answered for the announce.\n", ast_channel_name(dchan)); - ast_hangup(dchan); - res = -1; - goto parkcleanup; - } - } else { - ast_log(LOG_WARNING, "PARK: Unable to allocate announce channel.\n"); - res = -1; - goto parkcleanup; - } - - ast_stopstream(dchan); - - /* now we have the call placed and are ready to play stuff to it */ - - ast_verb(4, "Announce Template:%s\n", args.template); - - for (looptemp = 0; looptemp < ARRAY_LEN(tmp); looptemp++) { - if ((tmp[looptemp] = strsep(&args.template, ":")) != NULL) - continue; - else - break; - } - - for (i = 0; i < looptemp; i++) { - ast_verb(4, "Announce:%s\n", tmp[i]); - if (!strcmp(tmp[i], "PARKED")) { - ast_say_digits(dchan, lot, "", ast_channel_language(dchan)); - } else { - dres = ast_streamfile(dchan, tmp[i], ast_channel_language(dchan)); - if (!dres) { - dres = ast_waitstream(dchan, ""); - } else { - ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", tmp[i], ast_channel_name(dchan)); - } - } - } - - ast_stopstream(dchan); - ast_hangup(dchan); - -parkcleanup: - cap_slin = ast_format_cap_destroy(cap_slin); - - return res; -} - -static int unload_module(void) -{ - return ast_unregister_application(app); -} - -static int load_module(void) -{ - /* return ast_register_application(app, park_exec); */ - return ast_register_application_xml(app, parkandannounce_exec); -} - -AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Call Parking and Announce Application"); diff --git a/apps/app_queue.c b/apps/app_queue.c index c63cd071e..8a96b64b2 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -106,6 +106,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/cel.h" #include "asterisk/data.h" #include "asterisk/term.h" +#include "asterisk/bridging.h" /* Define, to debug reference counts on queues, without debugging reference counts on queue members */ /* #define REF_DEBUG_ONLY_QUEUES */ @@ -4912,6 +4913,7 @@ enum agent_complete_reason { TRANSFER }; +#if 0 // BUGBUG /*! \brief Send out AMI message with member call completion status information */ static void send_agent_complete(const struct queue_ent *qe, const char *queuename, const struct ast_channel *peer, const struct member *member, time_t callstart, @@ -4975,6 +4977,7 @@ static void send_agent_complete(const struct queue_ent *qe, const char *queuenam (long)(callstart - qe->start), (long)(time(NULL) - callstart), reason, qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, vars_len) : ""); } +#endif // BUGBUG struct queue_transfer_ds { struct queue_ent *qe; @@ -5029,6 +5032,7 @@ static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struc } } +#if 0 // BUGBUG /*! \brief mechanism to tell if a queue caller was atxferred by a queue member. * * When a caller is atxferred, then the queue_transfer_info datastore @@ -5041,6 +5045,7 @@ static int attended_transfer_occurred(struct ast_channel *chan) { return ast_channel_datastore_find(chan, &queue_transfer_info, NULL) ? 0 : 1; } +#endif // BUGBUG /*! \brief create a datastore for storing relevant info to log attended transfers in the queue_log */ @@ -5100,6 +5105,35 @@ static void end_bridge_callback(void *data) /*! * \internal + * \brief Setup the after bridge goto location on the peer. + * \since 12.0.0 + * + * \param chan Calling channel for bridge. + * \param peer Peer channel for bridge. + * \param opts Dialing option flags. + * \param opt_args Dialing option argument strings. + * + * \return Nothing + */ +static void setup_peer_after_bridge_goto(struct ast_channel *chan, struct ast_channel *peer, struct ast_flags *opts, char *opt_args[]) +{ + const char *context; + const char *extension; + int priority; + + if (ast_test_flag(opts, OPT_CALLEE_GO_ON)) { + ast_channel_lock(chan); + context = ast_strdupa(ast_channel_context(chan)); + extension = ast_strdupa(ast_channel_exten(chan)); + priority = ast_channel_priority(chan); + ast_channel_unlock(chan); + ast_after_bridge_set_go_on(peer, context, extension, priority, + opt_args[OPT_ARG_CALLEE_GO_ON]); + } +} + +/*! + * \internal * \brief A large function which calls members, updates statistics, and bridges the caller and a member * * Here is the process of this function @@ -5128,7 +5162,7 @@ static void end_bridge_callback(void *data) * \param[in] gosub the gosub passed as the seventh parameter to the Queue() application * \param[in] ringing 1 if the 'r' option is set, otherwise 0 */ -static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char **opt_args, char *announceoverride, const char *url, int *tries, int *noption, const char *agi, const char *macro, const char *gosub, int ringing) +static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_args, char *announceoverride, const char *url, int *tries, int *noption, const char *agi, const char *macro, const char *gosub, int ringing) { struct member *cur; struct callattempt *outgoing = NULL; /* the list of calls we are building */ @@ -5200,9 +5234,6 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char * if (ast_test_flag(&opts, OPT_CALLER_AUTOMON)) { ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_AUTOMON); } - if (ast_test_flag(&opts, OPT_GO_ON)) { - ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_NO_H_EXTEN); - } if (ast_test_flag(&opts, OPT_DATA_QUALITY)) { nondataquality = 0; } @@ -5244,7 +5275,7 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char * } /* if the calling channel has AST_CAUSE_ANSWERED_ELSEWHERE set, make sure this is inherited. - (this is mainly to support chan_local) + (this is mainly to support unreal/local channels) */ if (ast_channel_hangupcause(qe->chan) == AST_CAUSE_ANSWERED_ELSEWHERE) { qe->cancel_answered_elsewhere = 1; @@ -5437,11 +5468,6 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char * } } } else { /* peer is valid */ - /* These variables are used with the F option without arguments (callee jumps to next priority after queue) */ - char *caller_context; - char *caller_extension; - int caller_priority; - /* Ah ha! Someone answered within the desired timeframe. Of course after this we will always return with -1 so that it is hung up properly after the conversation. */ @@ -5595,11 +5621,8 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char * set_queue_variables(qe->parent, qe->chan); set_queue_variables(qe->parent, peer); + setup_peer_after_bridge_goto(qe->chan, peer, &opts, opt_args); ast_channel_lock(qe->chan); - /* Copy next destination data for 'F' option (no args) */ - caller_context = ast_strdupa(ast_channel_context(qe->chan)); - caller_extension = ast_strdupa(ast_channel_exten(qe->chan)); - caller_priority = ast_channel_priority(qe->chan); if ((monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME"))) { monitorfilename = ast_strdupa(monitorfilename); } @@ -5883,6 +5906,8 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char * transfer_ds = setup_transfer_datastore(qe, member, callstart, callcompletedinsl); bridge = ast_bridge_call(qe->chan, peer, &bridge_config); +/* BUGBUG need to do this queue logging a different way because we cannot reference peer anymore. Likely needs to be made a subscriber of stasis transfer events. */ +#if 0 // BUGBUG /* If the queue member did an attended transfer, then the TRANSFER already was logged in the queue_log * when the masquerade occurred. These other "ending" queue_log messages are unnecessary, except for * the AgentComplete manager event @@ -5917,28 +5942,12 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char * /* We already logged the TRANSFER on the queue_log, but we still need to send the AgentComplete event */ send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), TRANSFER); } +#endif // BUGBUG if (transfer_ds) { ast_datastore_free(transfer_ds); } - if (!ast_check_hangup(peer) && ast_test_flag(&opts, OPT_CALLEE_GO_ON)) { - int goto_res; - - if (!ast_strlen_zero(opt_args[OPT_ARG_CALLEE_GO_ON])) { - ast_replace_subargument_delimiter(opt_args[OPT_ARG_CALLEE_GO_ON]); - goto_res = ast_parseable_goto(peer, opt_args[OPT_ARG_CALLEE_GO_ON]); - } else { /* F() */ - goto_res = ast_goto_if_exists(peer, caller_context, caller_extension, - caller_priority + 1); - } - if (goto_res || ast_pbx_start(peer)) { - ast_autoservice_chan_hangup_peer(qe->chan, peer); - } - } else { - ast_autoservice_chan_hangup_peer(qe->chan, peer); - } - res = bridge ? bridge : 1; ao2_ref(member, -1); } diff --git a/apps/confbridge/conf_chan_announce.c b/apps/confbridge/conf_chan_announce.c new file mode 100644 index 000000000..46e074b20 --- /dev/null +++ b/apps/confbridge/conf_chan_announce.c @@ -0,0 +1,207 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013 Digium, Inc. + * + * Richard Mudgett <rmudgett@digium.com> + * + * 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 ConfBridge announcer channel driver + * + * \author Richard Mudgett <rmudgett@digium.com> + * + * See Also: + * \arg \ref AstCREDITS + */ + + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/channel.h" +#include "asterisk/bridging.h" +#include "asterisk/core_unreal.h" +#include "include/confbridge.h" + +/* ------------------------------------------------------------------- */ + +/*! ConfBridge announcer channel private. */ +struct announce_pvt { + /*! Unreal channel driver base class values. */ + struct ast_unreal_pvt base; + /*! Conference bridge associated with this announcer. */ + struct ast_bridge *bridge; +}; + +static int announce_call(struct ast_channel *chan, const char *addr, int timeout) +{ + /* Make sure anyone calling ast_call() for this channel driver is going to fail. */ + return -1; +} + +static int announce_hangup(struct ast_channel *ast) +{ + struct announce_pvt *p = ast_channel_tech_pvt(ast); + int res; + + if (!p) { + return -1; + } + + /* give the pvt a ref to fulfill calling requirements. */ + ao2_ref(p, +1); + res = ast_unreal_hangup(&p->base, ast); + ao2_ref(p, -1); + + return res; +} + +static void announce_pvt_destructor(void *vdoomed) +{ + struct announce_pvt *doomed = vdoomed; + + ao2_cleanup(doomed->bridge); + doomed->bridge = NULL; +} + +static struct ast_channel *announce_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause) +{ + struct ast_channel *chan; + const char *conf_name = data; + RAII_VAR(struct confbridge_conference *, conference, NULL, ao2_cleanup); + RAII_VAR(struct announce_pvt *, pvt, NULL, ao2_cleanup); + + conference = ao2_find(conference_bridges, conf_name, OBJ_KEY); + if (!conference) { + return NULL; + } + ast_assert(conference->bridge != NULL); + + /* Allocate a new private structure and then Asterisk channels */ + pvt = (struct announce_pvt *) ast_unreal_alloc(sizeof(*pvt), announce_pvt_destructor, + cap); + if (!pvt) { + return NULL; + } + ast_set_flag(&pvt->base, AST_UNREAL_NO_OPTIMIZATION); + ast_copy_string(pvt->base.name, conf_name, sizeof(pvt->base.name)); + pvt->bridge = conference->bridge; + ao2_ref(pvt->bridge, +1); + + chan = ast_unreal_new_channels(&pvt->base, conf_announce_get_tech(), + AST_STATE_UP, AST_STATE_UP, NULL, NULL, requestor, NULL); + if (chan) { + ast_answer(pvt->base.owner); + ast_answer(pvt->base.chan); + if (ast_channel_add_bridge_role(pvt->base.chan, "announcer")) { + ast_hangup(chan); + chan = NULL; + } + } + + return chan; +} + +static struct ast_channel_tech announce_tech = { + .type = "CBAnn", + .description = "Conference Bridge Announcing Channel", + .requester = announce_request, + .call = announce_call, + .hangup = announce_hangup, + + .send_digit_begin = ast_unreal_digit_begin, + .send_digit_end = ast_unreal_digit_end, + .read = ast_unreal_read, + .write = ast_unreal_write, + .write_video = ast_unreal_write, + .exception = ast_unreal_read, + .indicate = ast_unreal_indicate, + .fixup = ast_unreal_fixup, + .send_html = ast_unreal_sendhtml, + .send_text = ast_unreal_sendtext, + .queryoption = ast_unreal_queryoption, + .setoption = ast_unreal_setoption, +}; + +struct ast_channel_tech *conf_announce_get_tech(void) +{ + return &announce_tech; +} + +void conf_announce_channel_depart(struct ast_channel *chan) +{ + struct announce_pvt *p = ast_channel_tech_pvt(chan); + + if (!p) { + return; + } + + ao2_ref(p, +1); + ao2_lock(p); + if (!ast_test_flag(&p->base, AST_UNREAL_CARETAKER_THREAD)) { + ao2_unlock(p); + ao2_ref(p, -1); + return; + } + ast_clear_flag(&p->base, AST_UNREAL_CARETAKER_THREAD); + chan = p->base.chan; + if (chan) { + ast_channel_ref(chan); + } + ao2_unlock(p); + ao2_ref(p, -1); + if (chan) { + ast_bridge_depart(chan); + ast_channel_unref(chan); + } +} + +int conf_announce_channel_push(struct ast_channel *ast) +{ + struct ast_bridge_features *features; + RAII_VAR(struct announce_pvt *, p, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, chan, NULL, ast_channel_unref); + + { + SCOPED_CHANNELLOCK(lock, ast); + + p = ast_channel_tech_pvt(ast); + if (!p) { + return -1; + } + ao2_ref(p, +1); + chan = p->base.chan; + if (!chan) { + return -1; + } + ast_channel_ref(chan); + } + + features = ast_bridge_features_new(); + if (!features) { + return -1; + } + ast_set_flag(&features->feature_flags, AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE); + + /* Impart the output channel into the bridge */ + if (ast_bridge_impart(p->bridge, chan, NULL, features, 0)) { + return -1; + } + ao2_lock(p); + ast_set_flag(&p->base, AST_UNREAL_CARETAKER_THREAD); + ao2_unlock(p); + return 0; +} diff --git a/apps/confbridge/conf_chan_record.c b/apps/confbridge/conf_chan_record.c new file mode 100644 index 000000000..18f971f35 --- /dev/null +++ b/apps/confbridge/conf_chan_record.c @@ -0,0 +1,94 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013 Digium, Inc. + * + * Richard Mudgett <rmudgett@digium.com> + * + * 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 ConfBridge recorder channel driver + * + * \author Richard Mudgett <rmudgett@digium.com> + * + * See Also: + * \arg \ref AstCREDITS + */ + + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/channel.h" +#include "asterisk/bridging.h" +#include "include/confbridge.h" + +/* ------------------------------------------------------------------- */ + +static int rec_call(struct ast_channel *chan, const char *addr, int timeout) +{ + /* Make sure anyone calling ast_call() for this channel driver is going to fail. */ + return -1; +} + +static struct ast_frame *rec_read(struct ast_channel *ast) +{ + return &ast_null_frame; +} + +static int rec_write(struct ast_channel *ast, struct ast_frame *f) +{ + return 0; +} + +static struct ast_channel *rec_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause) +{ + struct ast_channel *chan; + struct ast_format format; + const char *conf_name = data; + + chan = ast_channel_alloc(1, AST_STATE_UP, NULL, NULL, NULL, NULL, NULL, NULL, 0, + "CBRec/conf-%s-uid-%d", + conf_name, (int) ast_random()); + if (!chan) { + return NULL; + } + if (ast_channel_add_bridge_role(chan, "recorder")) { + ast_channel_release(chan); + return NULL; + } + ast_format_set(&format, AST_FORMAT_SLINEAR, 0); + ast_channel_tech_set(chan, conf_record_get_tech()); + ast_format_cap_add_all(ast_channel_nativeformats(chan)); + ast_format_copy(ast_channel_writeformat(chan), &format); + ast_format_copy(ast_channel_rawwriteformat(chan), &format); + ast_format_copy(ast_channel_readformat(chan), &format); + ast_format_copy(ast_channel_rawreadformat(chan), &format); + return chan; +} + +static struct ast_channel_tech record_tech = { + .type = "CBRec", + .description = "Conference Bridge Recording Channel", + .requester = rec_request, + .call = rec_call, + .read = rec_read, + .write = rec_write, +}; + +struct ast_channel_tech *conf_record_get_tech(void) +{ + return &record_tech; +} diff --git a/apps/confbridge/conf_config_parser.c b/apps/confbridge/conf_config_parser.c index 1bca2d5c2..6cec25522 100644 --- a/apps/confbridge/conf_config_parser.c +++ b/apps/confbridge/conf_config_parser.c @@ -1924,6 +1924,7 @@ int conf_load_config(int reload) /* This option should only be used with the CONFBRIDGE dialplan function */ aco_option_register_custom(&cfg_info, "template", ACO_EXACT, user_types, NULL, user_template_handler, 0); +/* BUGBUG need a user supplied bridge merge_priority to merge ConfBridges (default = 1, range 1-INT_MAX) */ /* Bridge options */ aco_option_register(&cfg_info, "type", ACO_EXACT, bridge_types, NULL, OPT_NOOP_T, 0, 0); aco_option_register(&cfg_info, "jitterbuffer", ACO_EXACT, bridge_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct bridge_profile, flags), USER_OPT_JITTERBUFFER); @@ -2156,7 +2157,8 @@ int conf_set_menu_to_user(const char *menu_name, struct confbridge_user *user) ao2_ref(menu, +1); pvt->menu = menu; - ast_bridge_features_hook(&user->features, pvt->menu_entry.dtmf, menu_hook_callback, pvt, menu_hook_destroy); + ast_bridge_dtmf_hook(&user->features, pvt->menu_entry.dtmf, menu_hook_callback, + pvt, menu_hook_destroy, 0); } ao2_unlock(menu); diff --git a/apps/confbridge/confbridge_manager.c b/apps/confbridge/confbridge_manager.c new file mode 100644 index 000000000..56fedb98e --- /dev/null +++ b/apps/confbridge/confbridge_manager.c @@ -0,0 +1,339 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Jonathan Rose <jrose@digium.com> + * + * 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 Confbridge manager events for stasis messages + * + * \author Jonathan Rose <jrose@digium.com> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/channel.h" +#include "asterisk/bridging.h" +#include "asterisk/stasis.h" +#include "asterisk/stasis_channels.h" +#include "asterisk/stasis_bridging.h" +#include "asterisk/manager.h" +#include "asterisk/stasis_message_router.h" +#include "include/confbridge.h" + +/*** DOCUMENTATION + <managerEvent language="en_US" name="ConfbridgeStart"> + <managerEventInstance class="EVENT_FLAG_CALL"> + <synopsis>Raised when a conference starts.</synopsis> + <syntax> + <parameter name="Conference"> + <para>The name of the Confbridge conference.</para> + </parameter> + <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" /> + </syntax> + <see-also> + <ref type="managerEvent">ConfbridgeEnd</ref> + <ref type="application">ConfBridge</ref> + </see-also> + </managerEventInstance> + </managerEvent> + <managerEvent language="en_US" name="ConfbridgeEnd"> + <managerEventInstance class="EVENT_FLAG_CALL"> + <synopsis>Raised when a conference ends.</synopsis> + <syntax> + <parameter name="Conference"> + <para>The name of the Confbridge conference.</para> + </parameter> + <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" /> + </syntax> + <see-also> + <ref type="managerEvent">ConfbridgeStart</ref> + <ref type="application">ConfBridge</ref> + </see-also> + </managerEventInstance> + </managerEvent> + <managerEvent language="en_US" name="ConfbridgeJoin"> + <managerEventInstance class="EVENT_FLAG_CALL"> + <synopsis>Raised when a channel joins a Confbridge conference.</synopsis> + <syntax> + <parameter name="Conference"> + <para>The name of the Confbridge conference.</para> + </parameter> + <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" /> + <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" /> + </syntax> + <see-also> + <ref type="managerEvent">ConfbridgeLeave</ref> + <ref type="application">ConfBridge</ref> + </see-also> + </managerEventInstance> + </managerEvent> + <managerEvent language="en_US" name="ConfbridgeLeave"> + <managerEventInstance class="EVENT_FLAG_CALL"> + <synopsis>Raised when a channel leaves a Confbridge conference.</synopsis> + <syntax> + <parameter name="Conference"> + <para>The name of the Confbridge conference.</para> + </parameter> + <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" /> + <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" /> + </syntax> + <see-also> + <ref type="managerEvent">ConfbridgeJoin</ref> + <ref type="application">ConfBridge</ref> + </see-also> + </managerEventInstance> + </managerEvent> + <managerEvent language="en_US" name="ConfbridgeRecord"> + <managerEventInstance class="EVENT_FLAG_CALL"> + <synopsis>Raised when a conference starts recording.</synopsis> + <syntax> + <parameter name="Conference"> + <para>The name of the Confbridge conference.</para> + </parameter> + <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" /> + </syntax> + <see-also> + <ref type="managerEvent">ConfbridgeStopRecord</ref> + <ref type="application">ConfBridge</ref> + </see-also> + </managerEventInstance> + </managerEvent> + <managerEvent language="en_US" name="ConfbridgeStopRecord"> + <managerEventInstance class="EVENT_FLAG_CALL"> + <synopsis>Raised when a conference that was recording stops recording.</synopsis> + <syntax> + <parameter name="Conference"> + <para>The name of the Confbridge conference.</para> + </parameter> + <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" /> + </syntax> + <see-also> + <ref type="managerEvent">ConfbridgeRecord</ref> + <ref type="application">ConfBridge</ref> + </see-also> + </managerEventInstance> + </managerEvent> + <managerEvent language="en_US" name="ConfbridgeMute"> + <managerEventInstance class="EVENT_FLAG_CALL"> + <synopsis>Raised when a Confbridge participant mutes.</synopsis> + <syntax> + <parameter name="Conference"> + <para>The name of the Confbridge conference.</para> + </parameter> + <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" /> + <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" /> + </syntax> + <see-also> + <ref type="managerEvent">ConfbridgeUnmute</ref> + <ref type="application">ConfBridge</ref> + </see-also> + </managerEventInstance> + </managerEvent> + <managerEvent language="en_US" name="ConfbridgeUnmute"> + <managerEventInstance class="EVENT_FLAG_CALL"> + <synopsis>Raised when a confbridge participant unmutes.</synopsis> + <syntax> + <parameter name="Conference"> + <para>The name of the Confbridge conference.</para> + </parameter> + <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" /> + <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" /> + </syntax> + <see-also> + <ref type="managerEvent">ConfbridgeMute</ref> + <ref type="application">ConfBridge</ref> + </see-also> + </managerEventInstance> + </managerEvent> + + <managerEvent language="en_US" name="ConfbridgeTalking"> + <managerEventInstance class="EVENT_FLAG_CALL"> + <synopsis>Raised when a confbridge participant unmutes.</synopsis> + <syntax> + <parameter name="Conference"> + <para>The name of the Confbridge conference.</para> + </parameter> + <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" /> + <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" /> + <parameter name="TalkingStatus"> + <enumlist> + <enum name="on"/> + <enum name="off"/> + </enumlist> + </parameter> + </syntax> + <see-also> + <ref type="application">ConfBridge</ref> + </see-also> + </managerEventInstance> + </managerEvent> +***/ + +static struct stasis_message_router *bridge_state_router; +static struct stasis_message_router *channel_state_router; + +static void append_event_header(struct ast_str **fields_string, + const char *header, const char *value) +{ + struct ast_str *working_str = *fields_string; + + if (!working_str) { + working_str = ast_str_create(128); + if (!working_str) { + return; + } + *fields_string = working_str; + } + + ast_str_append(&working_str, 0, + "%s: %s\r\n", + header, value); +} + +static void stasis_confbridge_cb(void *data, struct stasis_subscription *sub, + struct stasis_topic *topic, + struct stasis_message *message) +{ + struct ast_bridge_blob *blob = stasis_message_data(message); + const char *type = ast_bridge_blob_json_type(blob); + const char *conference_name; + RAII_VAR(struct ast_str *, bridge_text, NULL, ast_free); + RAII_VAR(struct ast_str *, channel_text, NULL, ast_free); + RAII_VAR(struct ast_str *, extra_text, NULL, ast_free); + char *event; + + if (!blob || !type) { + ast_assert(0); + return; + } + + if (!strcmp("confbridge_start", type)) { + event = "ConfbridgeStart"; + } else if (!strcmp("confbridge_end", type)) { + event = "ConfbridgeEnd"; + } else if (!strcmp("confbridge_leave", type)) { + event = "ConfbridgeLeave"; + } else if (!strcmp("confbridge_join", type)) { + event = "ConfbridgeJoin"; + } else if (!strcmp("confbridge_record", type)) { + event = "ConfbridgeRecord"; + } else if (!strcmp("confbridge_stop_record", type)) { + event = "ConfbridgeStopRecord"; + } else if (!strcmp("confbridge_mute", type)) { + event = "ConfbridgeMute"; + } else if (!strcmp("confbridge_unmute", type)) { + event = "ConfbridgeUnmute"; + } else if (!strcmp("confbridge_talking", type)) { + const char *talking_status = ast_json_string_get(ast_json_object_get(blob->blob, "talking_status")); + event = "ConfbridgeTalking"; + + if (!talking_status) { + return; + } + + append_event_header(&extra_text, "TalkingStatus", talking_status); + + } else { + return; + } + + conference_name = ast_json_string_get(ast_json_object_get(blob->blob, "conference")); + + if (!conference_name) { + ast_assert(0); + return; + } + + bridge_text = ast_manager_build_bridge_state_string(blob->bridge, ""); + if (blob->channel) { + channel_text = ast_manager_build_channel_state_string(blob->channel); + } + + manager_event(EVENT_FLAG_CALL, event, + "Conference: %s\r\n" + "%s" + "%s" + "%s", + conference_name, + ast_str_buffer(bridge_text), + channel_text ? ast_str_buffer(channel_text) : "", + extra_text ? ast_str_buffer(extra_text) : ""); +} + +static struct stasis_message_type *confbridge_msg_type; + +struct stasis_message_type *confbridge_message_type(void) +{ + return confbridge_msg_type; +} + +void manager_confbridge_shutdown(void) { + ao2_cleanup(confbridge_msg_type); + confbridge_msg_type = NULL; + + if (bridge_state_router) { + stasis_message_router_unsubscribe(bridge_state_router); + bridge_state_router = NULL; + } + + if (channel_state_router) { + stasis_message_router_unsubscribe(channel_state_router); + channel_state_router = NULL; + } +} + +int manager_confbridge_init(void) +{ + if (!(confbridge_msg_type = stasis_message_type_create("confbridge"))) { + return -1; + } + + bridge_state_router = stasis_message_router_create( + stasis_caching_get_topic(ast_bridge_topic_all_cached())); + + if (!bridge_state_router) { + return -1; + } + + if (stasis_message_router_add(bridge_state_router, + confbridge_message_type(), + stasis_confbridge_cb, + NULL)) { + manager_confbridge_shutdown(); + return -1; + } + + channel_state_router = stasis_message_router_create( + stasis_caching_get_topic(ast_channel_topic_all_cached())); + + if (!channel_state_router) { + manager_confbridge_shutdown(); + return -1; + } + + if (stasis_message_router_add(channel_state_router, + confbridge_message_type(), + stasis_confbridge_cb, + NULL)) { + manager_confbridge_shutdown(); + return -1; + } + + return 0; +} diff --git a/apps/confbridge/include/confbridge.h b/apps/confbridge/include/confbridge.h index 245561376..f0620149a 100644 --- a/apps/confbridge/include/confbridge.h +++ b/apps/confbridge/include/confbridge.h @@ -222,6 +222,8 @@ struct confbridge_conference { AST_LIST_HEAD_NOLOCK(, confbridge_user) waiting_list; /*!< List of users waiting to join the conference bridge */ }; +extern struct ao2_container *conference_bridges; + struct post_join_action { int (*func)(struct confbridge_user *user); AST_LIST_ENTRY(post_join_action) list; @@ -460,4 +462,65 @@ void conf_remove_user_waiting(struct confbridge_conference *conference, struct c * \retval non-zero failure */ int conf_add_post_join_action(struct confbridge_user *user, int (*func)(struct confbridge_user *user)); + +/*! + * \since 12.0 + * \brief get the confbridge stasis message type + * + * \retval stasis message type for confbridge messages if it's available + * \retval NULL if it isn't + */ +struct stasis_message_type *confbridge_message_type(void); + +/*! + * \since 12.0 + * \brief register stasis message routers to handle manager events for confbridge messages + * + * \retval 0 success + * \retval non-zero failure + */ +int manager_confbridge_init(void); + +/*! + * \since 12.0 + * \brief unregister stasis message routers to handle manager events for confbridge messages + */ +void manager_confbridge_shutdown(void); + +/*! + * \brief Get ConfBridge record channel technology struct. + * \since 12.0.0 + * + * \return ConfBridge record channel technology. + */ +struct ast_channel_tech *conf_record_get_tech(void); + +/*! + * \brief Get ConfBridge announce channel technology struct. + * \since 12.0.0 + * + * \return ConfBridge announce channel technology. + */ +struct ast_channel_tech *conf_announce_get_tech(void); + +/*! + * \brief Remove the announcer channel from the conference. + * \since 12.0.0 + * + * \param chan Either channel in the announcer channel pair. + * + * \return Nothing + */ +void conf_announce_channel_depart(struct ast_channel *chan); + +/*! + * \brief Push the announcer channel into the conference. + * \since 12.0.0 + * + * \param ast Either channel in the announcer channel pair. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int conf_announce_channel_push(struct ast_channel *ast); #endif |