diff --git a/CHANGES b/CHANGES
index f5c705ed7f983ebbd7ec4a457500ba571f25fb9f..ec85f5ded464e1ecd950e051ff42861d2d68045f 100644
--- a/CHANGES
+++ b/CHANGES
@@ -50,6 +50,19 @@ AMI (Asterisk Manager Interface)
  * The AMI event 'UserEvent' from app_userevent now contains the channel state
    fields. The channel state fields will come before the body fields.
 
+ * The AMI events 'ParkedCall', 'ParkedCallTimeOut', 'ParkedCallGiveUp', and
+   'UnParkedCall' have changed significantly in the new res_parking module.
+   First, channel snapshot data is included for both the parker and the parkee
+   in lieu of the "From" and "Channel" fields. They follow standard channel
+   snapshot format but each field is suffixed with 'Parker' or 'Parkee'
+   depending on which side it applies to. The 'Exten' field is replaced with
+   'ParkingSpace' since the registration of extensions as for parking spaces
+   is no longer mandatory.
+
+ * The AMI event 'Parkinglot' (response to 'Parkinglots' command) in a similar
+   fashion has changed the field names 'StartExten' and 'StopExten' to
+   'StartSpace' and 'StopSpace' respectively.
+
  * The deprecated use of | (pipe) as a separator in the channelvars setting in
    manager.conf has been removed.
 
@@ -59,8 +72,23 @@ AMI (Asterisk Manager Interface)
    event, the various ChanVariable fields will contain a suffix that specifies
    which channel they correspond to.
 
+ * The AMI 'Status' response event to the AMI Status action replaces the
+   BridgedChannel and BridgedUniqueid headers with the BridgeID header to
+   indicate what bridge the channel is currently in.
+
 Channel Drivers
 ------------------
+ * When a channel driver is configured to enable jiterbuffers, they are now
+   applied unconditionally when a channel joins a bridge. If a jitterbuffer
+   is already set for that channel when it enters, such as by the JITTERBUFFER
+   function, then the existing jitterbuffer will be used and the one set by
+   the channel driver will not be applied.
+
+chan_local
+------------------
+ * The /b option is removed.
+
+ * chan_local moved into the system core and is no longer a loadable module.
 
 chan_mobile
 ------------------
@@ -86,13 +114,19 @@ Features
 
  * Add support for automixmonitor to the BRIDGE_FEATURES channel variable.
 
- * PARKINGSLOT and PARKEDLOT channel variables will now be set for a parked
-   channel even when comebactoorigin=yes
+ * Parking has been pulled from core and placed into a separate module called
+   res_parking. See Parking changes below for more details.
 
  * You can now have the settings for a channel updated using the FEATURE()
    and FEATUREMAP() functions inherited to child channels by setting
    FEATURE(inherit)=yes.
 
+Functions
+------------------
+ * JITTERBUFFER now accepts an argument of 'disabled' which can be used
+   to remove jitterbuffers previously set on a channel with JITTERBUFFER.
+   The value of this setting is ignored when disabled is used for the argument.
+
 Logging
 -------------------
  * When performing queue pause/unpause on an interface without specifying an
@@ -110,6 +144,51 @@ MeetMe
   if a denoiser is attached to the channel; this option gives them the ability
   to remove the denoiser without having to unload func_speex.
 
+Parking
+-------------------
+ * Parking is now implemented as a module instead of as core functionality.
+   The preferred way to configure parking is now through res_parking.conf while
+   configuration through features.conf is not currently supported.
+
+ * Parked calls are now placed in bridges. This is a largely architectural change,
+   but it could have some implications in allowing for new parked call retrieval
+   methods and the contents of parking lots will be visible though certain bridge
+   commands.
+
+ * The order of arguments for the new parking applications are different from the
+   old ones to be more intuitive. Timeout and return context/exten/priority are now
+   implemented as options. parking_lot_name is now the first parameter. See the
+   application documentation for Park, ParkedCall, and ParkAndAnnounce for more
+   in-depth information as well as syntax.
+
+ * Extensions are no longer automatically created in the dialplan to park calls,
+   pickup parked calls, etc by default.
+
+ * adsipark is no longer supported under the new parking model
+
+ * The PARKINGSLOT channel variable has been deprecated in favor of PARKING_SPACE
+   to match the naming scheme of the new system.
+
+ * PARKING_SPACE and PARKEDLOT channel variables will now be set for a parked
+   channel even when comebactoorigin=yes
+
+ * New CLI command 'parking show' allows you to inspect the currently in use
+   parking lots. 'parking show <parkinglot>' will also show the parked calls
+   in that specific parking lot.
+
+ * The CLI command 'parkedcalls' is now deprecated in favor of
+   'parking show <parkinglot>'.
+
+ * The AMI command 'ParkedCalls' will now accept a 'ParkingLot' argument which
+   can be used to get a list of parked calls only for a specific parking lot.
+
+ * The ParkAndAnnounce application is now provided through res_parking instead
+   of through the separate app_parkandannounce module.
+
+ * ParkAndAnnounce will no longer go to the next position in dialplan on timeout
+   by default. Instead, it will follow the timeout rules of the parking lot. The
+   old behavior can be reproduced by using the 'c' option.
+
 Queue
 -------------------
  * Add queue available hint.  exten => 8501,hint,Queue:markq_avail
diff --git a/UPGRADE.txt b/UPGRADE.txt
index 39f08049b9606e273ac2a05d1c494cf0241df00f..e2e7090081685fe77441bac55cc6a37cb0c4ae2b 100644
--- a/UPGRADE.txt
+++ b/UPGRADE.txt
@@ -69,6 +69,9 @@ chan_dahdi:
    pauses dialing for one second.
  - The default for inband_on_proceeding has changed to no.
 
+chan_local:
+ - The /b option is removed.
+
 Dialplan:
  - All channel and global variable names are evaluated in a case-sensitive manner.
    In previous versions of Asterisk, variables created and evaluated in the
@@ -81,6 +84,18 @@ Dialplan:
    Uppercase variants apply them to the calling party while lowercase variants
    apply them to the called party.
 
+Features:
+ - The features.conf [applicationmap] <FeatureName>  ActivatedBy option is
+   no longer honored.  The feature is activated by which channel
+   DYNAMIC_FEATURES includes the feature is on.  Use predial to set different
+   values of DYNAMIC_FEATURES on the channels
+
+Parking:
+ - The arguments for the Park, ParkedCall, and ParkAndAnnounce applications have
+   been modified significantly. See the application documents for specific details.
+   Also parking lot configuration is now done in res_parking.conf instead of
+   features.conf
+
 From 10 to 11:
 
 Voicemail:
diff --git a/addons/chan_ooh323.c b/addons/chan_ooh323.c
index a393e8e0ef8ce4ff693937764af0b5faa867bf97..61d9245fe18866604ec60851398af04202942d48 100644
--- a/addons/chan_ooh323.c
+++ b/addons/chan_ooh323.c
@@ -117,7 +117,6 @@ static struct ast_channel_tech ooh323_tech = {
 	.fixup = ooh323_fixup,
 	.send_html = 0,
 	.queryoption = ooh323_queryoption,
-	.bridge = ast_rtp_instance_bridge,		/* XXX chan unlocked ? */
 	.early_bridge = ast_rtp_instance_early_bridge,
 	.func_channel_read = function_ooh323_read,
 	.func_channel_write = function_ooh323_write,
diff --git a/apps/app_bridgewait.c b/apps/app_bridgewait.c
new file mode 100644
index 0000000000000000000000000000000000000000..ca12f0d30f53200cc7927afebad841d0ddf913d4
--- /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 8c98ed7b723fa1ea057925f66f19cc34a49db1d4..f636e02480187e88b2751898630731de8d0e6816 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 c5adb78adf6e47ad229218c3093ae5b2c423c01a..8e45907819c1a8d54bd768ca00dee51bfd6025f2 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 d4cce4b46c4148cc4069165dc9135e1fa74ea461..5107bcd5cc10b9ee42443b79c0106b37dd23ee0d 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 d3d37216aa5ca47c689d0fb99345a048de3bc2b7..35c9ad8bfb9c76a6cf613be1d87c7ccbe085df7c 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 4a80a3d13ea269bebed442b03a7e8e12a824dde3..722f15541757cd9545900d0cfa11a61b8eeaba6c 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 83f583bb3777339268f41a9f5a4ae176eac967a1..43f1967087bf246abf4a0255fa5b188ade1cb935 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 6e7976ec9fd4ba4d269339d8b626754b3ee02f51..e8d4903f0b1d9d4513a43811a9aba0d8ff6d0158 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 6d6ccae261d2ea94424f04821ab883084b74221d..0000000000000000000000000000000000000000
--- 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 c63cd071ed0191c55ed99d5d95aa19d4766438c9..8a96b64b21b5afe5d900d97ee19c647ec93d958d 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
  */
@@ -5098,6 +5103,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
@@ -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 0000000000000000000000000000000000000000..46e074b2064be9d1eafce9373f596607451670e0
--- /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 0000000000000000000000000000000000000000..18f971f35d7fb9d0845ff4997b1a11bcacd36523
--- /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 1bca2d5c2f3c4fab4b3e56ce06c15eb80596470c..6cec255221be14ad64162747f6c15dbdfbe2b9fb 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 0000000000000000000000000000000000000000..56fedb98e834d30f3e6fc0fec2efcbf8f09ebe61
--- /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 2455613769f34077175c7c97f33c9f0feec4e6fb..f0620149a0c7a9be28f78c8cf7c435ecb4f0c92d 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
diff --git a/bridges/bridge_builtin_features.c b/bridges/bridge_builtin_features.c
index 428d6deda24a48087f30b8bf610ede50d6cba2fc..493b5c89136d34d3697f062b3168b8057609ae46 100644
--- a/bridges/bridge_builtin_features.c
+++ b/bridges/bridge_builtin_features.c
@@ -47,8 +47,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/file.h"
 #include "asterisk/app.h"
 #include "asterisk/astobj2.h"
+#include "asterisk/pbx.h"
+#include "asterisk/parking.h"
 
-/*! \brief Helper function that presents dialtone and grabs extension */
+/*!
+ * \brief Helper function that presents dialtone and grabs extension
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
 static int grab_transfer(struct ast_channel *chan, char *exten, size_t exten_len, const char *context)
 {
 	int res;
@@ -56,15 +63,35 @@ static int grab_transfer(struct ast_channel *chan, char *exten, size_t exten_len
 	/* Play the simple "transfer" prompt out and wait */
 	res = ast_stream_and_wait(chan, "pbx-transfer", AST_DIGIT_ANY);
 	ast_stopstream(chan);
-
-	/* If the person hit a DTMF digit while the above played back stick it into the buffer */
+	if (res < 0) {
+		/* Hangup or error */
+		return -1;
+	}
 	if (res) {
-		exten[0] = (char)res;
+		/* Store the DTMF digit that interrupted playback of the file. */
+		exten[0] = res;
 	}
 
 	/* Drop to dialtone so they can enter the extension they want to transfer to */
-	res = ast_app_dtget(chan, context, exten, exten_len, 100, 1000);
-
+/* BUGBUG the timeout needs to be configurable from features.conf. */
+	res = ast_app_dtget(chan, context, exten, exten_len, exten_len - 1, 3000);
+	if (res < 0) {
+		/* Hangup or error */
+		res = -1;
+	} else if (!res) {
+		/* 0 for invalid extension dialed. */
+		if (ast_strlen_zero(exten)) {
+			ast_debug(1, "%s dialed no digits.\n", ast_channel_name(chan));
+		} else {
+			ast_debug(1, "%s dialed '%s@%s' does not exist.\n",
+				ast_channel_name(chan), exten, context);
+		}
+		ast_stream_and_wait(chan, "pbx-invalid", AST_DIGIT_NONE);
+		res = -1;
+	} else {
+		/* Dialed extension is valid. */
+		res = 0;
+	}
 	return res;
 }
 
@@ -78,8 +105,10 @@ static struct ast_channel *dial_transfer(struct ast_channel *caller, const char
 	/* Fill the variable with the extension and context we want to call */
 	snprintf(destination, sizeof(destination), "%s@%s", exten, context);
 
-	/* Now we request that chan_local prepare to call the destination */
-	if (!(chan = ast_request("Local", ast_channel_nativeformats(caller), caller, destination, &cause))) {
+	/* Now we request a local channel to prepare to call the destination */
+	chan = ast_request("Local", ast_channel_nativeformats(caller), caller, destination,
+		&cause);
+	if (!chan) {
 		return NULL;
 	}
 
@@ -100,67 +129,124 @@ static struct ast_channel *dial_transfer(struct ast_channel *caller, const char
 	return chan;
 }
 
+/*!
+ * \internal
+ * \brief Determine the transfer context to use.
+ * \since 12.0.0
+ *
+ * \param transferer Channel initiating the transfer.
+ * \param context User supplied context if available.  May be NULL.
+ *
+ * \return The context to use for the transfer.
+ */
+static const char *get_transfer_context(struct ast_channel *transferer, const char *context)
+{
+	if (!ast_strlen_zero(context)) {
+		return context;
+	}
+	context = pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT");
+	if (!ast_strlen_zero(context)) {
+		return context;
+	}
+	context = ast_channel_macrocontext(transferer);
+	if (!ast_strlen_zero(context)) {
+		return context;
+	}
+	context = ast_channel_context(transferer);
+	if (!ast_strlen_zero(context)) {
+		return context;
+	}
+	return "default";
+}
+
 /*! \brief Internal built in feature for blind transfers */
 static int feature_blind_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
 {
 	char exten[AST_MAX_EXTENSION] = "";
 	struct ast_channel *chan = NULL;
 	struct ast_bridge_features_blind_transfer *blind_transfer = hook_pvt;
-	const char *context = (blind_transfer && !ast_strlen_zero(blind_transfer->context) ? blind_transfer->context : ast_channel_context(bridge_channel->chan));
+	const char *context;
+	struct ast_exten *park_exten;
+
+/* BUGBUG the peer needs to be put on hold for the transfer. */
+	ast_channel_lock(bridge_channel->chan);
+	context = ast_strdupa(get_transfer_context(bridge_channel->chan,
+		blind_transfer ? blind_transfer->context : NULL));
+	ast_channel_unlock(bridge_channel->chan);
 
 	/* Grab the extension to transfer to */
-	if (!grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
-		ast_stream_and_wait(bridge_channel->chan, "pbx-invalid", AST_DIGIT_ANY);
+	if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
+		return 0;
+	}
+
+	/* Parking blind transfer override - phase this out for something more general purpose in the future. */
+	park_exten = ast_get_parking_exten(exten, bridge_channel->chan, context);
+	if (park_exten) {
+		/* We are transfering the transferee to a parking lot. */
+		if (ast_park_blind_xfer(bridge, bridge_channel, park_exten)) {
+			ast_log(LOG_ERROR, "%s attempted to transfer to park application and failed.\n", ast_channel_name(bridge_channel->chan));
+		};
 		return 0;
 	}
 
+/* BUGBUG just need to ast_async_goto the peer so this bridge will go away and not accumulate local channels and bridges if the destination is to an application. */
+/* ast_async_goto actually is a blind transfer. */
+/* BUGBUG Use the bridge count to determine if can do DTMF transfer features.  If count is not 2 then don't allow it. */
+
 	/* Get a channel that is the destination we wish to call */
-	if (!(chan = dial_transfer(bridge_channel->chan, exten, context))) {
-		ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_ANY);
+	chan = dial_transfer(bridge_channel->chan, exten, context);
+	if (!chan) {
 		return 0;
 	}
 
-	/* This is sort of the fun part. We impart the above channel onto the bridge, and have it take our place. */
-	ast_bridge_impart(bridge, chan, bridge_channel->chan, NULL, 1);
+	/* Impart the new channel onto the bridge, and have it take our place. */
+	if (ast_bridge_impart(bridge_channel->bridge, chan, bridge_channel->chan, NULL, 1)) {
+		ast_hangup(chan);
+		return 0;
+	}
 
 	return 0;
 }
 
-/*! \brief Attended transfer feature to turn it into a threeway call */
-static int attended_threeway_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+/*! Attended transfer code */
+enum atxfer_code {
+	/*! Party C hungup or other reason to abandon the transfer. */
+	ATXFER_INCOMPLETE,
+	/*! Transfer party C to party A. */
+	ATXFER_COMPLETE,
+	/*! Turn the transfer into a threeway call. */
+	ATXFER_THREEWAY,
+	/*! Hangup party C and return party B to the bridge. */
+	ATXFER_ABORT,
+};
+
+/*! \brief Attended transfer feature to complete transfer */
+static int attended_transfer_complete(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
 {
-	/*
-	 * This is sort of abusing the depart state but in this instance
-	 * it is only going to be handled by feature_attended_transfer()
-	 * so it is okay.
-	 */
-	ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_DEPART);
+	enum atxfer_code *transfer_code = hook_pvt;
+
+	*transfer_code = ATXFER_COMPLETE;
+	ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
 	return 0;
 }
 
-/*! \brief Attended transfer abort feature */
-static int attended_abort_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+/*! \brief Attended transfer feature to turn it into a threeway call */
+static int attended_transfer_threeway(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
 {
-	struct ast_bridge_channel *called_bridge_channel = NULL;
-
-	/* It is possible (albeit unlikely) that the bridge channels list may change, so we have to ensure we do all of our magic while locked */
-	ao2_lock(bridge);
+	enum atxfer_code *transfer_code = hook_pvt;
 
-	if (AST_LIST_FIRST(&bridge->channels) != bridge_channel) {
-		called_bridge_channel = AST_LIST_FIRST(&bridge->channels);
-	} else {
-		called_bridge_channel = AST_LIST_LAST(&bridge->channels);
-	}
-
-	/* Now we basically eject the other channel from the bridge. This will cause their thread to hang them up, and our own code to consider the transfer failed. */
-	if (called_bridge_channel) {
-		ast_bridge_change_state(called_bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
-	}
-
-	ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+	*transfer_code = ATXFER_THREEWAY;
+	ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+	return 0;
+}
 
-	ao2_unlock(bridge);
+/*! \brief Attended transfer feature to abort transfer */
+static int attended_transfer_abort(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+	enum atxfer_code *transfer_code = hook_pvt;
 
+	*transfer_code = ATXFER_ABORT;
+	ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
 	return 0;
 }
 
@@ -168,71 +254,159 @@ static int attended_abort_transfer(struct ast_bridge *bridge, struct ast_bridge_
 static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
 {
 	char exten[AST_MAX_EXTENSION] = "";
-	struct ast_channel *chan = NULL;
-	struct ast_bridge *attended_bridge = NULL;
-	struct ast_bridge_features caller_features, called_features;
-	enum ast_bridge_channel_state attended_bridge_result;
+	struct ast_channel *peer;
+	struct ast_bridge *attended_bridge;
+	struct ast_bridge_features caller_features;
+	int xfer_failed;
 	struct ast_bridge_features_attended_transfer *attended_transfer = hook_pvt;
-	const char *context = (attended_transfer && !ast_strlen_zero(attended_transfer->context) ? attended_transfer->context : ast_channel_context(bridge_channel->chan));
+	const char *context;
+	enum atxfer_code transfer_code = ATXFER_INCOMPLETE;
+
+	bridge = ast_bridge_channel_merge_inhibit(bridge_channel, +1);
+
+/* BUGBUG the peer needs to be put on hold for the transfer. */
+	ast_channel_lock(bridge_channel->chan);
+	context = ast_strdupa(get_transfer_context(bridge_channel->chan,
+		attended_transfer ? attended_transfer->context : NULL));
+	ast_channel_unlock(bridge_channel->chan);
 
 	/* Grab the extension to transfer to */
-	if (!grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
-		ast_stream_and_wait(bridge_channel->chan, "pbx-invalid", AST_DIGIT_ANY);
+	if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
+		ast_bridge_merge_inhibit(bridge, -1);
+		ao2_ref(bridge, -1);
 		return 0;
 	}
 
 	/* Get a channel that is the destination we wish to call */
-	if (!(chan = dial_transfer(bridge_channel->chan, exten, context))) {
-		ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_ANY);
+	peer = dial_transfer(bridge_channel->chan, exten, context);
+	if (!peer) {
+		ast_bridge_merge_inhibit(bridge, -1);
+		ao2_ref(bridge, -1);
+/* BUGBUG beeperr needs to be configurable from features.conf */
+		ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
 		return 0;
 	}
 
-	/* Create a bridge to use to talk to the person we are calling */
-	if (!(attended_bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_1TO1MIX, 0))) {
-		ast_hangup(chan);
-		ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_ANY);
+/* BUGBUG bridging API features does not support features.conf featuremap */
+/* BUGBUG bridging API features does not support the features.conf atxfer bounce between C & B channels */
+	/* Setup a DTMF menu to control the transfer. */
+	if (ast_bridge_features_init(&caller_features)
+		|| ast_bridge_hangup_hook(&caller_features,
+			attended_transfer_complete, &transfer_code, NULL, 0)
+		|| ast_bridge_dtmf_hook(&caller_features,
+			attended_transfer && !ast_strlen_zero(attended_transfer->abort)
+				? attended_transfer->abort : "*1",
+			attended_transfer_abort, &transfer_code, NULL, 0)
+		|| ast_bridge_dtmf_hook(&caller_features,
+			attended_transfer && !ast_strlen_zero(attended_transfer->complete)
+				? attended_transfer->complete : "*2",
+			attended_transfer_complete, &transfer_code, NULL, 0)
+		|| ast_bridge_dtmf_hook(&caller_features,
+			attended_transfer && !ast_strlen_zero(attended_transfer->threeway)
+				? attended_transfer->threeway : "*3",
+			attended_transfer_threeway, &transfer_code, NULL, 0)) {
+		ast_bridge_features_cleanup(&caller_features);
+		ast_hangup(peer);
+		ast_bridge_merge_inhibit(bridge, -1);
+		ao2_ref(bridge, -1);
+/* BUGBUG beeperr needs to be configurable from features.conf */
+		ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
 		return 0;
 	}
 
-	/* Setup our called features structure so that if they hang up we immediately get thrown out of the bridge */
-	ast_bridge_features_init(&called_features);
-	ast_bridge_features_set_flag(&called_features, AST_BRIDGE_FLAG_DISSOLVE);
+	/* Create a bridge to use to talk to the person we are calling */
+	attended_bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_1TO1MIX,
+		AST_BRIDGE_FLAG_DISSOLVE_HANGUP);
+	if (!attended_bridge) {
+		ast_bridge_features_cleanup(&caller_features);
+		ast_hangup(peer);
+		ast_bridge_merge_inhibit(bridge, -1);
+		ao2_ref(bridge, -1);
+/* BUGBUG beeperr needs to be configurable from features.conf */
+		ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
+		return 0;
+	}
+	ast_bridge_merge_inhibit(attended_bridge, +1);
 
 	/* This is how this is going down, we are imparting the channel we called above into this bridge first */
-	ast_bridge_impart(attended_bridge, chan, NULL, &called_features, 1);
+/* BUGBUG we should impart the peer as an independent and move it to the original bridge. */
+	if (ast_bridge_impart(attended_bridge, peer, NULL, NULL, 0)) {
+		ast_bridge_destroy(attended_bridge);
+		ast_bridge_features_cleanup(&caller_features);
+		ast_hangup(peer);
+		ast_bridge_merge_inhibit(bridge, -1);
+		ao2_ref(bridge, -1);
+/* BUGBUG beeperr needs to be configurable from features.conf */
+		ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
+		return 0;
+	}
 
-	/* Before we join setup a features structure with the hangup option, just in case they want to use DTMF */
-	ast_bridge_features_init(&caller_features);
-	ast_bridge_features_enable(&caller_features, AST_BRIDGE_BUILTIN_HANGUP,
-				   (attended_transfer && !ast_strlen_zero(attended_transfer->complete) ? attended_transfer->complete : "*1"), NULL);
-	ast_bridge_features_hook(&caller_features, (attended_transfer && !ast_strlen_zero(attended_transfer->threeway) ? attended_transfer->threeway : "*2"),
-				 attended_threeway_transfer, NULL, NULL);
-	ast_bridge_features_hook(&caller_features, (attended_transfer && !ast_strlen_zero(attended_transfer->abort) ? attended_transfer->abort : "*3"),
-				 attended_abort_transfer, NULL, NULL);
+	/*
+	 * For the caller we want to join the bridge in a blocking
+	 * fashion so we don't spin around in this function doing
+	 * nothing while waiting.
+	 */
+	ast_bridge_join(attended_bridge, bridge_channel->chan, NULL, &caller_features, NULL, 0);
 
-	/* But for the caller we want to join the bridge in a blocking fashion so we don't spin around in this function doing nothing while waiting */
-	attended_bridge_result = ast_bridge_join(attended_bridge, bridge_channel->chan, NULL, &caller_features, NULL);
+/*
+ * BUGBUG there is a small window where the channel does not point to the bridge_channel.
+ *
+ * This window is expected to go away when atxfer is redesigned
+ * to fully support existing functionality.  There will be one
+ * and only one ast_bridge_channel structure per channel.
+ */
+	/* Point the channel back to the original bridge and bridge_channel. */
+	ast_bridge_channel_lock(bridge_channel);
+	ast_channel_lock(bridge_channel->chan);
+	ast_channel_internal_bridge_channel_set(bridge_channel->chan, bridge_channel);
+	ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge);
+	ast_channel_unlock(bridge_channel->chan);
+	ast_bridge_channel_unlock(bridge_channel);
+
+	/* Wait for peer thread to exit bridge and die. */
+	if (!ast_autoservice_start(bridge_channel->chan)) {
+		ast_bridge_depart(peer);
+		ast_autoservice_stop(bridge_channel->chan);
+	} else {
+		ast_bridge_depart(peer);
+	}
 
-	/* Since the above returned the caller features structure is of no more use */
+	/* Now that all channels are out of it we can destroy the bridge and the feature structures */
+	ast_bridge_destroy(attended_bridge);
 	ast_bridge_features_cleanup(&caller_features);
 
-	/* Drop the channel we are transferring to out of the above bridge since it has ended */
-	if ((attended_bridge_result != AST_BRIDGE_CHANNEL_STATE_HANGUP) && !ast_bridge_depart(attended_bridge, chan)) {
-		/* If the user wants to turn this into a threeway transfer then do so, otherwise they take our place */
-		if (attended_bridge_result == AST_BRIDGE_CHANNEL_STATE_DEPART) {
-			/* We want to impart them upon the bridge and just have us return to it as normal */
-			ast_bridge_impart(bridge, chan, NULL, NULL, 1);
-		} else {
-			ast_bridge_impart(bridge, chan, bridge_channel->chan, NULL, 1);
+	xfer_failed = -1;
+	switch (transfer_code) {
+	case ATXFER_INCOMPLETE:
+		/* Peer hungup */
+		break;
+	case ATXFER_COMPLETE:
+		/* The peer takes our place in the bridge. */
+		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+		xfer_failed = ast_bridge_impart(bridge_channel->bridge, peer, bridge_channel->chan, NULL, 1);
+		break;
+	case ATXFER_THREEWAY:
+		/*
+		 * Transferer wants to convert to a threeway call.
+		 *
+		 * Just impart the peer onto the bridge and have us return to it
+		 * as normal.
+		 */
+		xfer_failed = ast_bridge_impart(bridge_channel->bridge, peer, NULL, NULL, 1);
+		break;
+	case ATXFER_ABORT:
+		/* Transferer decided not to transfer the call after all. */
+		break;
+	}
+	ast_bridge_merge_inhibit(bridge, -1);
+	ao2_ref(bridge, -1);
+	if (xfer_failed) {
+		ast_hangup(peer);
+		if (!ast_check_hangup_locked(bridge_channel->chan)) {
+			ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
 		}
-	} else {
-		ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_ANY);
 	}
 
-	/* Now that all channels are out of it we can destroy the bridge and the called features structure */
-	ast_bridge_features_cleanup(&called_features);
-	ast_bridge_destroy(attended_bridge);
-
 	return 0;
 }
 
diff --git a/bridges/bridge_builtin_interval_features.c b/bridges/bridge_builtin_interval_features.c
new file mode 100644
index 0000000000000000000000000000000000000000..a0e767ed3bf05f1e89ea26e81e62b9b89ee22e31
--- /dev/null
+++ b/bridges/bridge_builtin_interval_features.c
@@ -0,0 +1,215 @@
+/*
+ * 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 Built in bridging interval features
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ *
+ * \ingroup bridges
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$REVISION: 381278 $")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/file.h"
+#include "asterisk/app.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/test.h"
+
+#include "asterisk/say.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/musiconhold.h"
+
+static int bridge_features_duration_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+	struct ast_bridge_features_limits *limits = hook_pvt;
+
+	if (!ast_strlen_zero(limits->duration_sound)) {
+		ast_stream_and_wait(bridge_channel->chan, limits->duration_sound, AST_DIGIT_NONE);
+	}
+
+	ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+
+	ast_test_suite_event_notify("BRIDGE_TIMELIMIT", "Channel1: %s", ast_channel_name(bridge_channel->chan));
+	return -1;
+}
+
+static void limits_interval_playback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_bridge_features_limits *limits, const char *file)
+{
+	if (!strcasecmp(file, "timeleft")) {
+		unsigned int remaining = ast_tvdiff_ms(limits->quitting_time, ast_tvnow()) / 1000;
+		unsigned int min;
+		unsigned int sec;
+
+		if (remaining <= 0) {
+			return;
+		}
+
+		if ((remaining / 60) > 1) {
+			min = remaining / 60;
+			sec = remaining % 60;
+		} else {
+			min = 0;
+			sec = remaining;
+		}
+
+		ast_stream_and_wait(bridge_channel->chan, "vm-youhave", AST_DIGIT_NONE);
+		if (min) {
+			ast_say_number(bridge_channel->chan, min, AST_DIGIT_NONE,
+				ast_channel_language(bridge_channel->chan), NULL);
+			ast_stream_and_wait(bridge_channel->chan, "queue-minutes", AST_DIGIT_NONE);
+		}
+		if (sec) {
+			ast_say_number(bridge_channel->chan, sec, AST_DIGIT_NONE,
+				ast_channel_language(bridge_channel->chan), NULL);
+			ast_stream_and_wait(bridge_channel->chan, "queue-seconds", AST_DIGIT_NONE);
+		}
+	} else {
+		ast_stream_and_wait(bridge_channel->chan, file, AST_DIGIT_NONE);
+	}
+
+	/*
+	 * It may be necessary to resume music on hold after we finish
+	 * playing the announcment.
+	 *
+	 * XXX We have no idea what MOH class was in use before playing
+	 * the file.
+	 */
+	if (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_MOH)) {
+		ast_moh_start(bridge_channel->chan, NULL, NULL);
+	}
+}
+
+static int bridge_features_connect_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+	struct ast_bridge_features_limits *limits = hook_pvt;
+
+	if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+		return -1;
+	}
+
+	limits_interval_playback(bridge, bridge_channel, limits, limits->connect_sound);
+	return -1;
+}
+
+static int bridge_features_warning_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+	struct ast_bridge_features_limits *limits = hook_pvt;
+
+	if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+		/* If we aren't in the wait state, something more important than this warning is happening and we should skip it. */
+		limits_interval_playback(bridge, bridge_channel, limits, limits->warning_sound);
+	}
+
+	return !limits->frequency ? -1 : limits->frequency;
+}
+
+static void copy_bridge_features_limits(struct ast_bridge_features_limits *dst, struct ast_bridge_features_limits *src)
+{
+	dst->duration = src->duration;
+	dst->warning = src->warning;
+	dst->frequency = src->frequency;
+	dst->quitting_time = src->quitting_time;
+
+	ast_string_field_set(dst, duration_sound, src->duration_sound);
+	ast_string_field_set(dst, warning_sound, src->warning_sound);
+	ast_string_field_set(dst, connect_sound, src->connect_sound);
+}
+
+static int bridge_builtin_set_limits(struct ast_bridge_features *features, struct ast_bridge_features_limits *limits, int remove_on_pull)
+{
+	struct ast_bridge_features_limits *feature_limits;
+
+	if (!limits->duration) {
+		return -1;
+	}
+
+	if (features->limits) {
+		ast_log(LOG_ERROR, "Tried to apply limits to a feature set that already has limits.\n");
+		return -1;
+	}
+
+	feature_limits = ast_malloc(sizeof(*feature_limits));
+	if (!feature_limits) {
+		return -1;
+	}
+
+	if (ast_bridge_features_limits_construct(feature_limits)) {
+		return -1;
+	}
+
+	copy_bridge_features_limits(feature_limits, limits);
+	features->limits = feature_limits;
+
+/* BUGBUG feature interval hooks need to be reimplemented to be more stand alone. */
+	if (ast_bridge_interval_hook(features, feature_limits->duration,
+		bridge_features_duration_callback, feature_limits, NULL, remove_on_pull)) {
+		ast_log(LOG_ERROR, "Failed to schedule the duration limiter to the bridge channel.\n");
+		return -1;
+	}
+
+	feature_limits->quitting_time = ast_tvadd(ast_tvnow(), ast_samp2tv(feature_limits->duration, 1000));
+
+	if (!ast_strlen_zero(feature_limits->connect_sound)) {
+		if (ast_bridge_interval_hook(features, 1,
+			bridge_features_connect_callback, feature_limits, NULL, remove_on_pull)) {
+			ast_log(LOG_WARNING, "Failed to schedule connect sound to the bridge channel.\n");
+		}
+	}
+
+	if (feature_limits->warning && feature_limits->warning < feature_limits->duration) {
+		if (ast_bridge_interval_hook(features, feature_limits->duration - feature_limits->warning,
+			bridge_features_warning_callback, feature_limits, NULL, remove_on_pull)) {
+			ast_log(LOG_WARNING, "Failed to schedule warning sound playback to the bridge channel.\n");
+		}
+	}
+
+	return 0;
+}
+
+static int unload_module(void)
+{
+	return 0;
+}
+
+static int load_module(void)
+{
+	ast_bridge_interval_register(AST_BRIDGE_BUILTIN_INTERVAL_LIMITS, bridge_builtin_set_limits);
+
+	/* Bump up our reference count so we can't be unloaded. */
+	ast_module_ref(ast_module_info->self);
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Built in bridging interval features");
diff --git a/bridges/bridge_holding.c b/bridges/bridge_holding.c
new file mode 100644
index 0000000000000000000000000000000000000000..fe0a7303fc2a478a15c571bb7280a4f0a10c38e1
--- /dev/null
+++ b/bridges/bridge_holding.c
@@ -0,0 +1,311 @@
+/*
+ * 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 Bridging technology for storing channels in a bridge for
+ *        the purpose of holding, parking, queues, and other such
+ *        states where a channel may need to be in a bridge but not
+ *        actually communicating with anything.
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ *
+ * \ingroup bridges
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_technology.h"
+#include "asterisk/frame.h"
+#include "asterisk/musiconhold.h"
+
+enum role_flags {
+	HOLDING_ROLE_PARTICIPANT = (1 << 0),
+	HOLDING_ROLE_ANNOUNCER = (1 << 1),
+};
+
+/* BUGBUG Add IDLE_MODE_HOLD option to put channel on hold using AST_CONTROL_HOLD/AST_CONTROL_UNHOLD while in bridge */
+/* BUGBUG Add IDLE_MODE_SILENCE to send silence media frames to channel while in bridge (uses a silence generator) */
+/* BUGBUG A channel without the holding_participant role will assume IDLE_MODE_MOH with the default music class. */
+enum idle_modes {
+	IDLE_MODE_NONE = 0,
+	IDLE_MODE_MOH,
+	IDLE_MODE_RINGING,
+};
+
+/*! \brief Structure which contains per-channel role information */
+struct holding_channel {
+	struct ast_flags holding_roles;
+	enum idle_modes idle_mode;
+};
+
+static void participant_stop_hold_audio(struct ast_bridge_channel *bridge_channel)
+{
+	struct holding_channel *hc = bridge_channel->tech_pvt;
+	if (!hc) {
+		return;
+	}
+
+	switch (hc->idle_mode) {
+	case IDLE_MODE_MOH:
+		ast_moh_stop(bridge_channel->chan);
+		break;
+	case IDLE_MODE_RINGING:
+		ast_indicate(bridge_channel->chan, -1);
+		break;
+	case IDLE_MODE_NONE:
+		break;
+	}
+}
+
+static void participant_reaction_announcer_join(struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_channel *chan;
+	chan = bridge_channel->chan;
+	participant_stop_hold_audio(bridge_channel);
+	if (ast_set_write_format_by_id(chan, AST_FORMAT_SLINEAR)) {
+		ast_log(LOG_WARNING, "Could not make participant %s compatible.\n", ast_channel_name(chan));
+	}
+}
+
+/* This should only be called on verified holding_participants. */
+static void participant_start_hold_audio(struct ast_bridge_channel *bridge_channel)
+{
+	struct holding_channel *hc = bridge_channel->tech_pvt;
+	const char *moh_class;
+
+	if (!hc) {
+		return;
+	}
+
+	switch(hc->idle_mode) {
+	case IDLE_MODE_MOH:
+		moh_class = ast_bridge_channel_get_role_option(bridge_channel, "holding_participant", "moh_class");
+		ast_moh_start(bridge_channel->chan, ast_strlen_zero(moh_class) ? NULL : moh_class, NULL);
+		break;
+	case IDLE_MODE_RINGING:
+		ast_indicate(bridge_channel->chan, AST_CONTROL_RINGING);
+		break;
+	case IDLE_MODE_NONE:
+		break;
+	}
+}
+
+static void handle_participant_join(struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *announcer_channel)
+{
+	struct ast_channel *us = bridge_channel->chan;
+	struct holding_channel *hc = bridge_channel->tech_pvt;
+	const char *idle_mode = ast_bridge_channel_get_role_option(bridge_channel, "holding_participant", "idle_mode");
+
+
+	if (!hc) {
+		return;
+	}
+
+	if (ast_strlen_zero(idle_mode)) {
+		hc->idle_mode = IDLE_MODE_NONE;
+	} else if (!strcmp(idle_mode, "musiconhold")) {
+		hc->idle_mode = IDLE_MODE_MOH;
+	} else if (!strcmp(idle_mode, "ringing")) {
+		hc->idle_mode = IDLE_MODE_RINGING;
+	} else {
+		ast_debug(2, "channel %s idle mode '%s' doesn't match any expected idle mode\n", ast_channel_name(us), idle_mode);
+	}
+
+	/* If the announcer channel isn't present, we need to set up ringing, music on hold, or whatever. */
+	if (!announcer_channel) {
+		participant_start_hold_audio(bridge_channel);
+		return;
+	}
+
+	/* If it is present though, we need to establish compatability. */
+	if (ast_set_write_format_by_id(us, AST_FORMAT_SLINEAR)) {
+		ast_log(LOG_WARNING, "Could not make participant %s compatible.\n", ast_channel_name(us));
+	}
+}
+
+static int holding_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_bridge_channel *other_channel;
+	struct ast_bridge_channel *announcer_channel;
+	struct holding_channel *hc;
+	struct ast_channel *us = bridge_channel->chan; /* The joining channel */
+
+	if (!(hc = ast_calloc(1, sizeof(*hc)))) {
+		return -1;
+	}
+
+	bridge_channel->tech_pvt = hc;
+
+	/* The bridge pvt holds the announcer channel if we have one. */
+	announcer_channel = bridge->tech_pvt;
+
+	if (ast_bridge_channel_has_role(bridge_channel, "announcer")) {
+		/* If another announcer already exists, scrap the holding channel struct so we know to ignore it in the future */
+		if (announcer_channel) {
+			bridge_channel->tech_pvt = NULL;
+			ast_free(hc);
+			ast_log(LOG_WARNING, "A second announcer channel %s attempted to enter a holding bridge.\n",
+				ast_channel_name(announcer_channel->chan));
+			return -1;
+		}
+
+		bridge->tech_pvt = bridge_channel;
+		ast_set_flag(&hc->holding_roles, HOLDING_ROLE_ANNOUNCER);
+
+		/* The announcer should always be made compatible with signed linear */
+		if (ast_set_read_format_by_id(us, AST_FORMAT_SLINEAR)) {
+			ast_log(LOG_ERROR, "Could not make announcer %s compatible.\n", ast_channel_name(us));
+		}
+
+		/* Make everyone compatible. While we are at it we should stop music on hold and ringing. */
+		AST_LIST_TRAVERSE(&bridge->channels, other_channel, entry) {
+			/* Skip the reaction if we are the channel in question */
+			if (bridge_channel == other_channel) {
+				continue;
+			}
+			participant_reaction_announcer_join(other_channel);
+		}
+
+		return 0;
+	}
+
+	/* If the entering channel isn't an announcer then we need to setup it's properties and put it in its holding state if necessary */
+	ast_set_flag(&hc->holding_roles, HOLDING_ROLE_PARTICIPANT);
+	handle_participant_join(bridge_channel, announcer_channel);
+	return 0;
+}
+
+static void participant_reaction_announcer_leave(struct ast_bridge_channel *bridge_channel)
+{
+	struct holding_channel *hc = bridge_channel->tech_pvt;
+
+	if (!hc) {
+		/* We are dealing with a channel that failed to join properly. Skip it. */
+		return;
+	}
+
+	ast_bridge_channel_restore_formats(bridge_channel);
+	if (ast_test_flag(&hc->holding_roles, HOLDING_ROLE_PARTICIPANT)) {
+		participant_start_hold_audio(bridge_channel);
+	}
+}
+
+static void holding_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_bridge_channel *other_channel;
+	struct holding_channel *hc = bridge_channel->tech_pvt;
+
+	if (!hc) {
+		return;
+	}
+
+	if (!ast_test_flag(&hc->holding_roles, HOLDING_ROLE_ANNOUNCER)) {
+		/* It's not an announcer so nothing needs to react to its departure. Just free the tech_pvt. */
+		if (!bridge->tech_pvt) {
+			/* Since no announcer is in the channel, we may be playing MOH/ringing. Stop that. */
+			participant_stop_hold_audio(bridge_channel);
+		}
+		ast_free(hc);
+		bridge_channel->tech_pvt = NULL;
+		return;
+	}
+
+	/* When the announcer leaves, the other channels should reset their formats and go back to moh/ringing */
+	AST_LIST_TRAVERSE(&bridge->channels, other_channel, entry) {
+		participant_reaction_announcer_leave(other_channel);
+	}
+
+	/* Since the announcer is leaving, we should clear the tech_pvt pointing to it */
+	bridge->tech_pvt = NULL;
+
+	ast_free(hc);
+	bridge_channel->tech_pvt = NULL;
+}
+
+static int holding_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+{
+	struct ast_bridge_channel *cur;
+	struct holding_channel *hc = bridge_channel->tech_pvt;
+
+	/* If there is no tech_pvt, then the channel failed to allocate one when it joined and is borked. Don't listen to him. */
+	if (!hc) {
+		return -1;
+	}
+
+	/* If we aren't an announcer, we never have any business writing anything. */
+	if (!ast_test_flag(&hc->holding_roles, HOLDING_ROLE_ANNOUNCER)) {
+		return -1;
+	}
+
+	/* Ok, so we are the announcer and there are one or more people available to receive our writes. Let's do it. */
+	AST_LIST_TRAVERSE(&bridge->channels, cur, entry) {
+		if (bridge_channel == cur || !cur->tech_pvt) {
+			continue;
+		}
+
+		ast_bridge_channel_queue_frame(cur, frame);
+	}
+
+	return 0;
+}
+
+static struct ast_bridge_technology holding_bridge = {
+	.name = "holding_bridge",
+	.capabilities = AST_BRIDGE_CAPABILITY_HOLDING,
+	.preference = AST_BRIDGE_PREFERENCE_BASE_HOLDING,
+	.write = holding_bridge_write,
+	.join = holding_bridge_join,
+	.leave = holding_bridge_leave,
+};
+
+static int unload_module(void)
+{
+	ast_format_cap_destroy(holding_bridge.format_capabilities);
+	return ast_bridge_technology_unregister(&holding_bridge);
+}
+
+static int load_module(void)
+{
+	if (!(holding_bridge.format_capabilities = ast_format_cap_alloc())) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	ast_format_cap_add_all_by_type(holding_bridge.format_capabilities, AST_FORMAT_TYPE_AUDIO);
+	ast_format_cap_add_all_by_type(holding_bridge.format_capabilities, AST_FORMAT_TYPE_VIDEO);
+	ast_format_cap_add_all_by_type(holding_bridge.format_capabilities, AST_FORMAT_TYPE_TEXT);
+
+	return ast_bridge_technology_register(&holding_bridge);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Holding bridge module");
+
diff --git a/bridges/bridge_multiplexed.c b/bridges/bridge_multiplexed.c
deleted file mode 100644
index 309ad47e3ec8fc79a476848c217c97252b7377c9..0000000000000000000000000000000000000000
--- a/bridges/bridge_multiplexed.c
+++ /dev/null
@@ -1,513 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2008, Digium, Inc.
- *
- * Joshua Colp <jcolp@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 Two channel bridging module which groups bridges into batches of threads
- *
- * \author Joshua Colp <jcolp@digium.com>
- *
- * \ingroup bridges
- */
-
-/*** MODULEINFO
-	<support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-
-#include "asterisk/module.h"
-#include "asterisk/channel.h"
-#include "asterisk/bridging.h"
-#include "asterisk/bridging_technology.h"
-#include "asterisk/frame.h"
-#include "asterisk/astobj2.h"
-
-/*! \brief Number of buckets our multiplexed thread container can have */
-#define MULTIPLEXED_BUCKETS 53
-
-/*! \brief Number of bridges we handle in a single thread */
-#define MULTIPLEXED_MAX_BRIDGES		4
-
-/*! \brief Structure which represents a single thread handling multiple 2 channel bridges */
-struct multiplexed_thread {
-	/*! Thread itself */
-	pthread_t thread;
-	/*! Channels serviced by this thread */
-	struct ast_channel *chans[2 * MULTIPLEXED_MAX_BRIDGES];
-	/*! Pipe used to wake up the multiplexed thread */
-	int pipe[2];
-	/*! Number of channels actually being serviced by this thread */
-	unsigned int service_count;
-	/*! Number of bridges in this thread */
-	unsigned int bridges;
-	/*! TRUE if the thread is waiting on channels */
-	unsigned int waiting:1;
-};
-
-/*! \brief Container of all operating multiplexed threads */
-static struct ao2_container *muxed_threads;
-
-/*! \brief Callback function for finding a free multiplexed thread */
-static int find_multiplexed_thread(void *obj, void *arg, int flags)
-{
-	struct multiplexed_thread *muxed_thread = obj;
-
-	return (muxed_thread->bridges < MULTIPLEXED_MAX_BRIDGES) ? CMP_MATCH | CMP_STOP : 0;
-}
-
-/*! \brief Destroy callback for a multiplexed thread structure */
-static void destroy_multiplexed_thread(void *obj)
-{
-	struct multiplexed_thread *muxed_thread = obj;
-
-	if (muxed_thread->pipe[0] > -1) {
-		close(muxed_thread->pipe[0]);
-	}
-	if (muxed_thread->pipe[1] > -1) {
-		close(muxed_thread->pipe[1]);
-	}
-}
-
-/*! \brief Create function which finds/reserves/references a multiplexed thread structure */
-static int multiplexed_bridge_create(struct ast_bridge *bridge)
-{
-	struct multiplexed_thread *muxed_thread;
-
-	ao2_lock(muxed_threads);
-
-	/* Try to find an existing thread to handle our additional channels */
-	muxed_thread = ao2_callback(muxed_threads, 0, find_multiplexed_thread, NULL);
-	if (!muxed_thread) {
-		int flags;
-
-		/* If we failed we will have to create a new one from scratch */
-		muxed_thread = ao2_alloc(sizeof(*muxed_thread), destroy_multiplexed_thread);
-		if (!muxed_thread) {
-			ast_debug(1, "Failed to find or create a new multiplexed thread for bridge '%p'\n", bridge);
-			ao2_unlock(muxed_threads);
-			return -1;
-		}
-
-		muxed_thread->pipe[0] = muxed_thread->pipe[1] = -1;
-		/* Setup a pipe so we can poke the thread itself when needed */
-		if (pipe(muxed_thread->pipe)) {
-			ast_debug(1, "Failed to create a pipe for poking a multiplexed thread for bridge '%p'\n", bridge);
-			ao2_ref(muxed_thread, -1);
-			ao2_unlock(muxed_threads);
-			return -1;
-		}
-
-		/* Setup each pipe for non-blocking operation */
-		flags = fcntl(muxed_thread->pipe[0], F_GETFL);
-		if (fcntl(muxed_thread->pipe[0], F_SETFL, flags | O_NONBLOCK) < 0) {
-			ast_log(LOG_WARNING, "Failed to setup first nudge pipe for non-blocking operation on %p (%d: %s)\n", bridge, errno, strerror(errno));
-			ao2_ref(muxed_thread, -1);
-			ao2_unlock(muxed_threads);
-			return -1;
-		}
-		flags = fcntl(muxed_thread->pipe[1], F_GETFL);
-		if (fcntl(muxed_thread->pipe[1], F_SETFL, flags | O_NONBLOCK) < 0) {
-			ast_log(LOG_WARNING, "Failed to setup second nudge pipe for non-blocking operation on %p (%d: %s)\n", bridge, errno, strerror(errno));
-			ao2_ref(muxed_thread, -1);
-			ao2_unlock(muxed_threads);
-			return -1;
-		}
-
-		/* Set up default parameters */
-		muxed_thread->thread = AST_PTHREADT_NULL;
-
-		/* Finally link us into the container so others may find us */
-		ao2_link(muxed_threads, muxed_thread);
-		ast_debug(1, "Created multiplexed thread '%p' for bridge '%p'\n", muxed_thread, bridge);
-	} else {
-		ast_debug(1, "Found multiplexed thread '%p' for bridge '%p'\n", muxed_thread, bridge);
-	}
-
-	/* Increase the number of bridges using this multiplexed bridge */
-	++muxed_thread->bridges;
-
-	ao2_unlock(muxed_threads);
-
-	bridge->bridge_pvt = muxed_thread;
-
-	return 0;
-}
-
-/*!
- * \internal
- * \brief Nudges the multiplex thread.
- * \since 12.0.0
- *
- * \param muxed_thread Controller to poke the thread.
- *
- * \note This function assumes the muxed_thread is locked.
- *
- * \return Nothing
- */
-static void multiplexed_nudge(struct multiplexed_thread *muxed_thread)
-{
-	int nudge = 0;
-
-	if (muxed_thread->thread == AST_PTHREADT_NULL) {
-		return;
-	}
-
-	if (write(muxed_thread->pipe[1], &nudge, sizeof(nudge)) != sizeof(nudge)) {
-		ast_log(LOG_ERROR, "We couldn't poke multiplexed thread '%p'... something is VERY wrong\n", muxed_thread);
-	}
-
-	while (muxed_thread->waiting) {
-		sched_yield();
-	}
-}
-
-/*! \brief Destroy function which unreserves/unreferences/removes a multiplexed thread structure */
-static int multiplexed_bridge_destroy(struct ast_bridge *bridge)
-{
-	struct multiplexed_thread *muxed_thread;
-	pthread_t thread;
-
-	muxed_thread = bridge->bridge_pvt;
-	if (!muxed_thread) {
-		return -1;
-	}
-	bridge->bridge_pvt = NULL;
-
-	ao2_lock(muxed_threads);
-
-	if (--muxed_thread->bridges) {
-		/* Other bridges are still using the multiplexed thread. */
-		ao2_unlock(muxed_threads);
-	} else {
-		ast_debug(1, "Unlinking multiplexed thread '%p' since nobody is using it anymore\n",
-			muxed_thread);
-		ao2_unlink(muxed_threads, muxed_thread);
-		ao2_unlock(muxed_threads);
-
-		/* Stop the multiplexed bridge thread. */
-		ao2_lock(muxed_thread);
-		multiplexed_nudge(muxed_thread);
-		thread = muxed_thread->thread;
-		muxed_thread->thread = AST_PTHREADT_STOP;
-		ao2_unlock(muxed_thread);
-
-		if (thread != AST_PTHREADT_NULL) {
-			/* Wait for multiplexed bridge thread to die. */
-			pthread_join(thread, NULL);
-		}
-	}
-
-	ao2_ref(muxed_thread, -1);
-	return 0;
-}
-
-/*! \brief Thread function that executes for multiplexed threads */
-static void *multiplexed_thread_function(void *data)
-{
-	struct multiplexed_thread *muxed_thread = data;
-	int fds = muxed_thread->pipe[0];
-
-	ast_debug(1, "Starting actual thread for multiplexed thread '%p'\n", muxed_thread);
-
-	ao2_lock(muxed_thread);
-
-	while (muxed_thread->thread != AST_PTHREADT_STOP) {
-		struct ast_channel *winner;
-		int to = -1;
-		int outfd = -1;
-
-		if (1 < muxed_thread->service_count) {
-			struct ast_channel *first;
-
-			/* Move channels around so not just the first one gets priority */
-			first = muxed_thread->chans[0];
-			memmove(muxed_thread->chans, muxed_thread->chans + 1,
-				sizeof(struct ast_channel *) * (muxed_thread->service_count - 1));
-			muxed_thread->chans[muxed_thread->service_count - 1] = first;
-		}
-
-		muxed_thread->waiting = 1;
-		ao2_unlock(muxed_thread);
-		winner = ast_waitfor_nandfds(muxed_thread->chans, muxed_thread->service_count, &fds, 1, NULL, &outfd, &to);
-		muxed_thread->waiting = 0;
-		ao2_lock(muxed_thread);
-		if (muxed_thread->thread == AST_PTHREADT_STOP) {
-			break;
-		}
-
-		if (outfd > -1) {
-			int nudge;
-
-			if (read(muxed_thread->pipe[0], &nudge, sizeof(nudge)) < 0) {
-				if (errno != EINTR && errno != EAGAIN) {
-					ast_log(LOG_WARNING, "read() failed for pipe on multiplexed thread '%p': %s\n", muxed_thread, strerror(errno));
-				}
-			}
-		}
-		if (winner && ast_channel_internal_bridge(winner)) {
-			struct ast_bridge *bridge;
-			int stop = 0;
-
-			ao2_unlock(muxed_thread);
-			while ((bridge = ast_channel_internal_bridge(winner)) && ao2_trylock(bridge)) {
-				sched_yield();
-				if (muxed_thread->thread == AST_PTHREADT_STOP) {
-					stop = 1;
-					break;
-				}
-			}
-			if (!stop && bridge) {
-				ast_bridge_handle_trip(bridge, NULL, winner, -1);
-				ao2_unlock(bridge);
-			}
-			ao2_lock(muxed_thread);
-		}
-	}
-
-	ao2_unlock(muxed_thread);
-
-	ast_debug(1, "Stopping actual thread for multiplexed thread '%p'\n", muxed_thread);
-	ao2_ref(muxed_thread, -1);
-
-	return NULL;
-}
-
-/*!
- * \internal
- * \brief Check to see if the multiplexed bridge thread needs to be started.
- * \since 12.0.0
- *
- * \param muxed_thread Controller to check if need to start thread.
- *
- * \note This function assumes the muxed_thread is locked.
- *
- * \return Nothing
- */
-static void multiplexed_thread_start(struct multiplexed_thread *muxed_thread)
-{
-	if (muxed_thread->service_count && muxed_thread->thread == AST_PTHREADT_NULL) {
-		ao2_ref(muxed_thread, +1);
-		if (ast_pthread_create(&muxed_thread->thread, NULL, multiplexed_thread_function, muxed_thread)) {
-			muxed_thread->thread = AST_PTHREADT_NULL;/* For paranoia's sake. */
-			ao2_ref(muxed_thread, -1);
-			ast_log(LOG_WARNING, "Failed to create the common thread for multiplexed thread '%p', trying next time\n",
-				muxed_thread);
-		}
-	}
-}
-
-/*!
- * \internal
- * \brief Add a channel to the multiplexed bridge.
- * \since 12.0.0
- *
- * \param muxed_thread Controller to add a channel.
- * \param chan Channel to add to the channel service array.
- *
- * \return Nothing
- */
-static void multiplexed_chan_add(struct multiplexed_thread *muxed_thread, struct ast_channel *chan)
-{
-	int idx;
-
-	ao2_lock(muxed_thread);
-
-	multiplexed_nudge(muxed_thread);
-
-	/* Check if already in the channel service array for safety. */
-	for (idx = 0; idx < muxed_thread->service_count; ++idx) {
-		if (muxed_thread->chans[idx] == chan) {
-			break;
-		}
-	}
-	if (idx == muxed_thread->service_count) {
-		/* Channel to add was not already in the array. */
-		if (muxed_thread->service_count < ARRAY_LEN(muxed_thread->chans)) {
-			muxed_thread->chans[muxed_thread->service_count++] = chan;
-		} else {
-			ast_log(LOG_ERROR, "Could not add channel %s to multiplexed thread %p.  Array not large enough.\n",
-				ast_channel_name(chan), muxed_thread);
-			ast_assert(0);
-		}
-	}
-
-	multiplexed_thread_start(muxed_thread);
-
-	ao2_unlock(muxed_thread);
-}
-
-/*!
- * \internal
- * \brief Remove a channel from the multiplexed bridge.
- * \since 12.0.0
- *
- * \param muxed_thread Controller to remove a channel.
- * \param chan Channel to remove from the channel service array.
- *
- * \return Nothing
- */
-static void multiplexed_chan_remove(struct multiplexed_thread *muxed_thread, struct ast_channel *chan)
-{
-	int idx;
-
-	ao2_lock(muxed_thread);
-
-	multiplexed_nudge(muxed_thread);
-
-	/* Remove channel from service array. */
-	for (idx = 0; idx < muxed_thread->service_count; ++idx) {
-		if (muxed_thread->chans[idx] != chan) {
-			continue;
-		}
-		muxed_thread->chans[idx] = muxed_thread->chans[--muxed_thread->service_count];
-		break;
-	}
-
-	multiplexed_thread_start(muxed_thread);
-
-	ao2_unlock(muxed_thread);
-}
-
-/*! \brief Join function which actually adds the channel into the array to be monitored */
-static int multiplexed_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
-	struct ast_channel *c0 = AST_LIST_FIRST(&bridge->channels)->chan;
-	struct ast_channel *c1 = AST_LIST_LAST(&bridge->channels)->chan;
-	struct multiplexed_thread *muxed_thread = bridge->bridge_pvt;
-
-	ast_debug(1, "Adding channel '%s' to multiplexed thread '%p' for monitoring\n", ast_channel_name(bridge_channel->chan), muxed_thread);
-
-	multiplexed_chan_add(muxed_thread, bridge_channel->chan);
-
-	/* If the second channel has not yet joined do not make things compatible */
-	if (c0 == c1) {
-		return 0;
-	}
-
-	if ((ast_format_cmp(ast_channel_writeformat(c0), ast_channel_readformat(c1)) == AST_FORMAT_CMP_EQUAL) &&
-		(ast_format_cmp(ast_channel_readformat(c0), ast_channel_writeformat(c1)) == AST_FORMAT_CMP_EQUAL) &&
-		(ast_format_cap_identical(ast_channel_nativeformats(c0), ast_channel_nativeformats(c1)))) {
-		return 0;
-	}
-
-	return ast_channel_make_compatible(c0, c1);
-}
-
-/*! \brief Leave function which actually removes the channel from the array */
-static int multiplexed_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
-	struct multiplexed_thread *muxed_thread = bridge->bridge_pvt;
-
-	ast_debug(1, "Removing channel '%s' from multiplexed thread '%p'\n", ast_channel_name(bridge_channel->chan), muxed_thread);
-
-	multiplexed_chan_remove(muxed_thread, bridge_channel->chan);
-
-	return 0;
-}
-
-/*! \brief Suspend function which means control of the channel is going elsewhere */
-static void multiplexed_bridge_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
-	struct multiplexed_thread *muxed_thread = bridge->bridge_pvt;
-
-	ast_debug(1, "Suspending channel '%s' from multiplexed thread '%p'\n", ast_channel_name(bridge_channel->chan), muxed_thread);
-
-	multiplexed_chan_remove(muxed_thread, bridge_channel->chan);
-}
-
-/*! \brief Unsuspend function which means control of the channel is coming back to us */
-static void multiplexed_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
-	struct multiplexed_thread *muxed_thread = bridge->bridge_pvt;
-
-	ast_debug(1, "Unsuspending channel '%s' from multiplexed thread '%p'\n", ast_channel_name(bridge_channel->chan), muxed_thread);
-
-	multiplexed_chan_add(muxed_thread, bridge_channel->chan);
-}
-
-/*! \brief Write function for writing frames into the bridge */
-static enum ast_bridge_write_result multiplexed_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
-{
-	struct ast_bridge_channel *other;
-
-	/* If this is the only channel in this bridge then immediately exit */
-	if (AST_LIST_FIRST(&bridge->channels) == AST_LIST_LAST(&bridge->channels)) {
-		return AST_BRIDGE_WRITE_FAILED;
-	}
-
-	/* Find the channel we actually want to write to */
-	if (!(other = (AST_LIST_FIRST(&bridge->channels) == bridge_channel ? AST_LIST_LAST(&bridge->channels) : AST_LIST_FIRST(&bridge->channels)))) {
-		return AST_BRIDGE_WRITE_FAILED;
-	}
-
-	/* Write the frame out if they are in the waiting state... don't worry about freeing it, the bridging core will take care of it */
-	if (other->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
-		ast_write(other->chan, frame);
-	}
-
-	return AST_BRIDGE_WRITE_SUCCESS;
-}
-
-static struct ast_bridge_technology multiplexed_bridge = {
-	.name = "multiplexed_bridge",
-	.capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX,
-	.preference = AST_BRIDGE_PREFERENCE_HIGH,
-	.create = multiplexed_bridge_create,
-	.destroy = multiplexed_bridge_destroy,
-	.join = multiplexed_bridge_join,
-	.leave = multiplexed_bridge_leave,
-	.suspend = multiplexed_bridge_suspend,
-	.unsuspend = multiplexed_bridge_unsuspend,
-	.write = multiplexed_bridge_write,
-};
-
-static int unload_module(void)
-{
-	int res = ast_bridge_technology_unregister(&multiplexed_bridge);
-
-	ao2_ref(muxed_threads, -1);
-	multiplexed_bridge.format_capabilities = ast_format_cap_destroy(multiplexed_bridge.format_capabilities);
-
-	return res;
-}
-
-static int load_module(void)
-{
-	if (!(muxed_threads = ao2_container_alloc(MULTIPLEXED_BUCKETS, NULL, NULL))) {
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	if (!(multiplexed_bridge.format_capabilities = ast_format_cap_alloc())) {
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	ast_format_cap_add_all_by_type(multiplexed_bridge.format_capabilities, AST_FORMAT_TYPE_AUDIO);
-	ast_format_cap_add_all_by_type(multiplexed_bridge.format_capabilities, AST_FORMAT_TYPE_VIDEO);
-	ast_format_cap_add_all_by_type(multiplexed_bridge.format_capabilities, AST_FORMAT_TYPE_TEXT);
-	return ast_bridge_technology_register(&multiplexed_bridge);
-}
-
-AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Multiplexed two channel bridging module");
diff --git a/bridges/bridge_native_rtp.c b/bridges/bridge_native_rtp.c
new file mode 100644
index 0000000000000000000000000000000000000000..1117e5aedb5505028888190e9bbe17bbed398b27
--- /dev/null
+++ b/bridges/bridge_native_rtp.c
@@ -0,0 +1,414 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Joshua Colp <jcolp@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 Native RTP bridging module
+ *
+ * \author Joshua Colp <jcolp@digium.com>
+ *
+ * \ingroup bridges
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_technology.h"
+#include "asterisk/frame.h"
+#include "asterisk/rtp_engine.h"
+#include "asterisk/audiohook.h"
+
+/*! \brief Forward declarations for frame hook usage */
+static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+static void native_rtp_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+
+/*! \brief Internal structure which contains information about bridged RTP channels */
+struct native_rtp_bridge_data {
+	/*! \brief Framehook used to intercept certain control frames */
+	int id;
+};
+
+/*! \brief Frame hook that is called to intercept hold/unhold */
+static struct ast_frame *native_rtp_framehook(struct ast_channel *chan, struct ast_frame *f, enum ast_framehook_event event, void *data)
+{
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+
+	if (!f || (event != AST_FRAMEHOOK_EVENT_WRITE)) {
+		return f;
+	}
+
+	ast_channel_lock(chan);
+	bridge = ast_channel_get_bridge(chan);
+	ast_channel_unlock(chan);
+
+	/* It's safe for NULL to be passed to both of these, bridge_channel isn't used at all */
+	if (bridge) {
+		if (f->subclass.integer == AST_CONTROL_HOLD) {
+			native_rtp_bridge_leave(ast_channel_internal_bridge(chan), NULL);
+		} else if ((f->subclass.integer == AST_CONTROL_UNHOLD) || (f->subclass.integer == AST_CONTROL_UPDATE_RTP_PEER)) {
+			native_rtp_bridge_join(ast_channel_internal_bridge(chan), NULL);
+		}
+	}
+
+	return f;
+}
+
+/*! \brief Internal helper function which checks whether the channels are compatible with our native bridging */
+static int native_rtp_bridge_capable(struct ast_channel *chan)
+{
+	if (ast_channel_monitor(chan) || (ast_channel_audiohooks(chan) &&
+		!ast_audiohook_write_list_empty(ast_channel_audiohooks(chan))) ||
+		!ast_framehook_list_is_empty(ast_channel_framehooks(chan))) {
+		return 0;
+	} else {
+		return 1;
+	}
+}
+
+/*! \brief Internal helper function which gets all RTP information (glue and instances) relating to the given channels */
+static enum ast_rtp_glue_result native_rtp_bridge_get(struct ast_channel *c0, struct ast_channel *c1, struct ast_rtp_glue **glue0,
+	struct ast_rtp_glue **glue1, struct ast_rtp_instance **instance0, struct ast_rtp_instance **instance1,
+	struct ast_rtp_instance **vinstance0, struct ast_rtp_instance **vinstance1)
+{
+	enum ast_rtp_glue_result audio_glue0_res = AST_RTP_GLUE_RESULT_FORBID, video_glue0_res = AST_RTP_GLUE_RESULT_FORBID;
+	enum ast_rtp_glue_result audio_glue1_res = AST_RTP_GLUE_RESULT_FORBID, video_glue1_res = AST_RTP_GLUE_RESULT_FORBID;
+
+	if (!(*glue0 = ast_rtp_instance_get_glue(ast_channel_tech(c0)->type)) ||
+		(c1 && !(*glue1 = ast_rtp_instance_get_glue(ast_channel_tech(c1)->type)))) {
+		return AST_RTP_GLUE_RESULT_FORBID;
+	}
+
+	audio_glue0_res = (*glue0)->get_rtp_info(c0, instance0);
+	video_glue0_res = (*glue0)->get_vrtp_info ? (*glue0)->get_vrtp_info(c0, vinstance0) : AST_RTP_GLUE_RESULT_FORBID;
+
+	if (c1) {
+		audio_glue1_res = (*glue1)->get_rtp_info(c1, instance1);
+		video_glue1_res = (*glue1)->get_vrtp_info ? (*glue1)->get_vrtp_info(c1, vinstance1) : AST_RTP_GLUE_RESULT_FORBID;
+	}
+
+	/* Apply any limitations on direct media bridging that may be present */
+	if (audio_glue0_res == audio_glue1_res && audio_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {
+		if ((*glue0)->allow_rtp_remote && !((*glue0)->allow_rtp_remote(c0, *instance1))) {
+			/* If the allow_rtp_remote indicates that remote isn't allowed, revert to local bridge */
+			audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
+		} else if ((*glue1)->allow_rtp_remote && !((*glue1)->allow_rtp_remote(c1, *instance0))) {
+			audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
+		}
+	}
+	if (c1 && video_glue0_res == video_glue1_res && video_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {
+		if ((*glue0)->allow_vrtp_remote && !((*glue0)->allow_vrtp_remote(c0, *instance1))) {
+			/* if the allow_vrtp_remote indicates that remote isn't allowed, revert to local bridge */
+			video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
+		} else if ((*glue1)->allow_vrtp_remote && !((*glue1)->allow_vrtp_remote(c1, *instance0))) {
+			video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
+		}
+	}
+
+	/* If we are carrying video, and both sides are not going to remotely bridge... fail the native bridge */
+	if (video_glue0_res != AST_RTP_GLUE_RESULT_FORBID && (audio_glue0_res != AST_RTP_GLUE_RESULT_REMOTE || video_glue0_res != AST_RTP_GLUE_RESULT_REMOTE)) {
+		audio_glue0_res = AST_RTP_GLUE_RESULT_FORBID;
+	}
+	if (c1 && video_glue1_res != AST_RTP_GLUE_RESULT_FORBID && (audio_glue1_res != AST_RTP_GLUE_RESULT_REMOTE || video_glue1_res != AST_RTP_GLUE_RESULT_REMOTE)) {
+		audio_glue1_res = AST_RTP_GLUE_RESULT_FORBID;
+	}
+
+	/* If any sort of bridge is forbidden just completely bail out and go back to generic bridging */
+	if (audio_glue0_res == AST_RTP_GLUE_RESULT_FORBID || (c1 && audio_glue1_res == AST_RTP_GLUE_RESULT_FORBID)) {
+		return AST_RTP_GLUE_RESULT_FORBID;
+	}
+
+	return audio_glue0_res;
+}
+
+static int native_rtp_bridge_compatible(struct ast_bridge *bridge)
+{
+	struct ast_bridge_channel *c0 = AST_LIST_FIRST(&bridge->channels);
+	struct ast_bridge_channel *c1 = AST_LIST_LAST(&bridge->channels);
+	enum ast_rtp_glue_result native_type;
+	struct ast_rtp_glue *glue0, *glue1;
+	struct ast_rtp_instance *instance0 = NULL, *instance1 = NULL, *vinstance0 = NULL, *vinstance1 = NULL;
+	RAII_VAR(struct ast_format_cap *, cap0, ast_format_cap_alloc_nolock(), ast_format_cap_destroy);
+	RAII_VAR(struct ast_format_cap *, cap1, ast_format_cap_alloc_nolock(), ast_format_cap_destroy);
+	int read_ptime0, read_ptime1, write_ptime0, write_ptime1;
+
+	/* We require two channels before even considering native bridging */
+	if (bridge->num_channels != 2) {
+		ast_debug(1, "Bridge '%s' can not use native RTP bridge as two channels are required\n",
+			bridge->uniqueid);
+		return 0;
+	}
+
+	if (!native_rtp_bridge_capable(c0->chan)) {
+		ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has features which prevent it\n",
+			bridge->uniqueid, ast_channel_name(c0->chan));
+		return 0;
+	}
+
+	if (!native_rtp_bridge_capable(c1->chan)) {
+		ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has features which prevent it\n",
+			bridge->uniqueid, ast_channel_name(c1->chan));
+		return 0;
+	}
+
+	if ((native_type = native_rtp_bridge_get(c0->chan, c1->chan, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1))
+		== AST_RTP_GLUE_RESULT_FORBID) {
+		ast_debug(1, "Bridge '%s' can not use native RTP bridge as it was forbidden while getting details\n",
+			bridge->uniqueid);
+		return 0;
+	}
+
+	if (ao2_container_count(c0->features->dtmf_hooks) && ast_rtp_instance_dtmf_mode_get(instance0)) {
+		ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has DTMF hooks\n",
+			bridge->uniqueid, ast_channel_name(c0->chan));
+		return 0;
+	}
+
+	if (ao2_container_count(c1->features->dtmf_hooks) && ast_rtp_instance_dtmf_mode_get(instance1)) {
+		ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has DTMF hooks\n",
+			bridge->uniqueid, ast_channel_name(c1->chan));
+		return 0;
+	}
+
+	if ((native_type == AST_RTP_GLUE_RESULT_LOCAL) && ((ast_rtp_instance_get_engine(instance0)->local_bridge !=
+		ast_rtp_instance_get_engine(instance1)->local_bridge) ||
+		(ast_rtp_instance_get_engine(instance0)->dtmf_compatible &&
+			!ast_rtp_instance_get_engine(instance0)->dtmf_compatible(c0->chan, instance0, c1->chan, instance1)))) {
+		ast_debug(1, "Bridge '%s' can not use local native RTP bridge as local bridge or DTMF is not compatible\n",
+			bridge->uniqueid);
+		return 0;
+	}
+
+	/* Make sure that codecs match */
+	if (glue0->get_codec) {
+		glue0->get_codec(c0->chan, cap0);
+	}
+	if (glue1->get_codec) {
+		glue1->get_codec(c1->chan, cap1);
+	}
+	if (!ast_format_cap_is_empty(cap0) && !ast_format_cap_is_empty(cap1) && !ast_format_cap_has_joint(cap0, cap1)) {
+		char tmp0[256] = { 0, }, tmp1[256] = { 0, };
+
+		ast_debug(1, "Channel codec0 = %s is not codec1 = %s, cannot native bridge in RTP.\n",
+			ast_getformatname_multiple(tmp0, sizeof(tmp0), cap0),
+			ast_getformatname_multiple(tmp1, sizeof(tmp1), cap1));
+		return 0;
+	}
+
+	read_ptime0 = (ast_codec_pref_getsize(&ast_rtp_instance_get_codecs(instance0)->pref, ast_channel_rawreadformat(c0->chan))).cur_ms;
+	read_ptime1 = (ast_codec_pref_getsize(&ast_rtp_instance_get_codecs(instance1)->pref, ast_channel_rawreadformat(c1->chan))).cur_ms;
+	write_ptime0 = (ast_codec_pref_getsize(&ast_rtp_instance_get_codecs(instance0)->pref, ast_channel_rawwriteformat(c0->chan))).cur_ms;
+	write_ptime1 = (ast_codec_pref_getsize(&ast_rtp_instance_get_codecs(instance1)->pref, ast_channel_rawwriteformat(c1->chan))).cur_ms;
+
+	if (read_ptime0 != write_ptime1 || read_ptime1 != write_ptime0) {
+		ast_debug(1, "Packetization differs between RTP streams (%d != %d or %d != %d). Cannot native bridge in RTP\n",
+				read_ptime0, write_ptime1, read_ptime1, write_ptime0);
+		return 0;
+	}
+
+	return 1;
+}
+
+/*! \brief Helper function which adds frame hook to bridge channel */
+static int native_rtp_bridge_framehook_attach(struct ast_bridge_channel *bridge_channel)
+{
+	struct native_rtp_bridge_data *data = ao2_alloc(sizeof(*data), NULL);
+	static struct ast_framehook_interface hook = {
+		.version = AST_FRAMEHOOK_INTERFACE_VERSION,
+		.event_cb = native_rtp_framehook,
+	};
+
+	if (!data) {
+		return -1;
+	}
+
+	ast_channel_lock(bridge_channel->chan);
+
+	if (!(data->id = ast_framehook_attach(bridge_channel->chan, &hook)) < 0) {
+		ast_channel_unlock(bridge_channel->chan);
+		ao2_cleanup(data);
+		return -1;
+	}
+
+	ast_channel_unlock(bridge_channel->chan);
+
+	bridge_channel->bridge_pvt = data;
+
+	return 0;
+}
+
+/*! \brief Helper function which removes frame hook from bridge channel */
+static void native_rtp_bridge_framehook_detach(struct ast_bridge_channel *bridge_channel)
+{
+	RAII_VAR(struct native_rtp_bridge_data *, data, bridge_channel->bridge_pvt, ao2_cleanup);
+
+	if (!data) {
+		return;
+	}
+
+	ast_channel_lock(bridge_channel->chan);
+	ast_framehook_detach(bridge_channel->chan, data->id);
+	ast_channel_unlock(bridge_channel->chan);
+	bridge_channel->bridge_pvt = NULL;
+}
+
+static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_bridge_channel *c0 = AST_LIST_FIRST(&bridge->channels);
+	struct ast_bridge_channel *c1 = AST_LIST_LAST(&bridge->channels);
+	enum ast_rtp_glue_result native_type;
+	struct ast_rtp_glue *glue0, *glue1;
+	struct ast_rtp_instance *instance0 = NULL, *instance1 = NULL, *vinstance0 = NULL;
+	struct ast_rtp_instance *vinstance1 = NULL, *tinstance0 = NULL, *tinstance1 = NULL;
+	RAII_VAR(struct ast_format_cap *, cap0, ast_format_cap_alloc_nolock(), ast_format_cap_destroy);
+	RAII_VAR(struct ast_format_cap *, cap1, ast_format_cap_alloc_nolock(), ast_format_cap_destroy);
+
+	native_rtp_bridge_framehook_detach(c0);
+	if (native_rtp_bridge_framehook_attach(c0)) {
+		return -1;
+	}
+
+	native_rtp_bridge_framehook_detach(c1);
+	if (native_rtp_bridge_framehook_attach(c1)) {
+		native_rtp_bridge_framehook_detach(c0);
+		return -1;
+	}
+
+	native_type = native_rtp_bridge_get(c0->chan, c1->chan, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1);
+
+	if (glue0->get_codec) {
+		glue0->get_codec(c0->chan, cap0);
+	}
+	if (glue1->get_codec) {
+		glue1->get_codec(c1->chan, cap1);
+	}
+
+	if (native_type == AST_RTP_GLUE_RESULT_LOCAL) {
+		if (ast_rtp_instance_get_engine(instance0)->local_bridge) {
+			ast_rtp_instance_get_engine(instance0)->local_bridge(instance0, instance1);
+		}
+		if (ast_rtp_instance_get_engine(instance1)->local_bridge) {
+			ast_rtp_instance_get_engine(instance1)->local_bridge(instance1, instance0);
+		}
+		ast_rtp_instance_set_bridged(instance0, instance1);
+		ast_rtp_instance_set_bridged(instance1, instance0);
+	} else {
+		glue0->update_peer(c0->chan, instance1, vinstance1, tinstance1, cap1, 0);
+		glue1->update_peer(c1->chan, instance0, vinstance0, tinstance0, cap0, 0);
+	}
+
+	return 0;
+}
+
+static void native_rtp_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+	native_rtp_bridge_join(bridge, bridge_channel);
+}
+
+static void native_rtp_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_bridge_channel *c0 = AST_LIST_FIRST(&bridge->channels) ? AST_LIST_FIRST(&bridge->channels) : bridge_channel;
+	struct ast_bridge_channel *c1 = AST_LIST_LAST(&bridge->channels);
+	enum ast_rtp_glue_result native_type;
+	struct ast_rtp_glue *glue0, *glue1 = NULL;
+	struct ast_rtp_instance *instance0 = NULL, *instance1 = NULL, *vinstance0 = NULL, *vinstance1 = NULL;
+
+	native_rtp_bridge_framehook_detach(c0);
+	if (c1) {
+		native_rtp_bridge_framehook_detach(c1);
+	}
+
+	native_type = native_rtp_bridge_get(c0->chan, c1 ? c1->chan : NULL, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1);
+
+	if (native_type == AST_RTP_GLUE_RESULT_LOCAL) {
+		if (ast_rtp_instance_get_engine(instance0)->local_bridge) {
+			ast_rtp_instance_get_engine(instance0)->local_bridge(instance0, NULL);
+		}
+		if (instance1 && ast_rtp_instance_get_engine(instance1)->local_bridge) {
+			ast_rtp_instance_get_engine(instance1)->local_bridge(instance1, NULL);
+		}
+		ast_rtp_instance_set_bridged(instance0, instance1);
+		if (instance1) {
+			ast_rtp_instance_set_bridged(instance1, instance0);
+		}
+	} else {
+		glue0->update_peer(c0->chan, NULL, NULL, NULL, NULL, 0);
+		if (glue1) {
+			glue1->update_peer(c1->chan, NULL, NULL, NULL, NULL, 0);
+		}
+	}
+}
+
+static int native_rtp_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+{
+	struct ast_bridge_channel *other = ast_bridge_channel_peer(bridge_channel);
+
+	if (!other) {
+		return -1;
+	}
+
+	/* The bridging core takes care of freeing the passed in frame. */
+	ast_bridge_channel_queue_frame(other, frame);
+
+	return 0;
+}
+
+static struct ast_bridge_technology native_rtp_bridge = {
+	.name = "native_rtp",
+	.capabilities = AST_BRIDGE_CAPABILITY_NATIVE,
+	.preference = AST_BRIDGE_PREFERENCE_BASE_NATIVE,
+	.join = native_rtp_bridge_join,
+	.unsuspend = native_rtp_bridge_unsuspend,
+	.leave = native_rtp_bridge_leave,
+	.suspend = native_rtp_bridge_leave,
+	.write = native_rtp_bridge_write,
+	.compatible = native_rtp_bridge_compatible,
+};
+
+static int unload_module(void)
+{
+	ast_format_cap_destroy(native_rtp_bridge.format_capabilities);
+	return ast_bridge_technology_unregister(&native_rtp_bridge);
+}
+
+static int load_module(void)
+{
+	if (!(native_rtp_bridge.format_capabilities = ast_format_cap_alloc())) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	ast_format_cap_add_all_by_type(native_rtp_bridge.format_capabilities, AST_FORMAT_TYPE_AUDIO);
+	ast_format_cap_add_all_by_type(native_rtp_bridge.format_capabilities, AST_FORMAT_TYPE_VIDEO);
+	ast_format_cap_add_all_by_type(native_rtp_bridge.format_capabilities, AST_FORMAT_TYPE_TEXT);
+
+	return ast_bridge_technology_register(&native_rtp_bridge);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Native RTP bridging module");
diff --git a/bridges/bridge_simple.c b/bridges/bridge_simple.c
index 947983bae495bc2ff8dc6d24de1049dba6fcbea4..3e53b31c0e1849b7f361927228954c54e6e8d921 100644
--- a/bridges/bridge_simple.c
+++ b/bridges/bridge_simple.c
@@ -66,32 +66,26 @@ static int simple_bridge_join(struct ast_bridge *bridge, struct ast_bridge_chann
 	return ast_channel_make_compatible(c0, c1);
 }
 
-static enum ast_bridge_write_result simple_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+static int simple_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
 {
 	struct ast_bridge_channel *other;
 
-	/* If this is the only channel in this bridge then immediately exit */
-	if (AST_LIST_FIRST(&bridge->channels) == AST_LIST_LAST(&bridge->channels)) {
-		return AST_BRIDGE_WRITE_FAILED;
-	}
-
 	/* Find the channel we actually want to write to */
-	if (!(other = (AST_LIST_FIRST(&bridge->channels) == bridge_channel ? AST_LIST_LAST(&bridge->channels) : AST_LIST_FIRST(&bridge->channels)))) {
-		return AST_BRIDGE_WRITE_FAILED;
+	other = ast_bridge_channel_peer(bridge_channel);
+	if (!other) {
+		return -1;
 	}
 
-	/* Write the frame out if they are in the waiting state... don't worry about freeing it, the bridging core will take care of it */
-	if (other->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
-		ast_write(other->chan, frame);
-	}
+	/* The bridging core takes care of freeing the passed in frame. */
+	ast_bridge_channel_queue_frame(other, frame);
 
-	return AST_BRIDGE_WRITE_SUCCESS;
+	return 0;
 }
 
 static struct ast_bridge_technology simple_bridge = {
 	.name = "simple_bridge",
-	.capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX | AST_BRIDGE_CAPABILITY_THREAD,
-	.preference = AST_BRIDGE_PREFERENCE_MEDIUM,
+	.capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX,
+	.preference = AST_BRIDGE_PREFERENCE_BASE_1TO1MIX,
 	.join = simple_bridge_join,
 	.write = simple_bridge_write,
 };
diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c
index 613601a1f154e7828f7f6aed3a86afb42cd7670a..4583435a08d471d7dbab34ac9d862da445779aa6 100644
--- a/bridges/bridge_softmix.c
+++ b/bridges/bridge_softmix.c
@@ -100,13 +100,15 @@ struct softmix_channel {
 	struct ast_frame read_frame;
 	/*! DSP for detecting silence */
 	struct ast_dsp *dsp;
-	/*! Bit used to indicate if a channel is talking or not. This affects how
-	 * the channel's audio is mixed back to it. */
-	int talking:1;
-	/*! Bit used to indicate that the channel provided audio for this mixing interval */
-	int have_audio:1;
-	/*! Bit used to indicate that a frame is available to be written out to the channel */
-	int have_frame:1;
+	/*!
+	 * \brief TRUE if a channel is talking.
+	 *
+	 * \note This affects how the channel's audio is mixed back to
+	 * it.
+	 */
+	unsigned int talking:1;
+	/*! TRUE if the channel provided audio for this mixing interval */
+	unsigned int have_audio:1;
 	/*! Buffer containing final mixed audio from all sources */
 	short final_buf[MAX_DATALEN];
 	/*! Buffer containing only the audio from the channel */
@@ -117,28 +119,36 @@ struct softmix_channel {
 
 struct softmix_bridge_data {
 	struct ast_timer *timer;
+	/*! Lock for signaling the mixing thread. */
+	ast_mutex_t lock;
+	/*! Condition, used if we need to wake up the mixing thread. */
+	ast_cond_t cond;
+	/*! Thread handling the mixing */
+	pthread_t thread;
 	unsigned int internal_rate;
 	unsigned int internal_mixing_interval;
+	/*! TRUE if the mixing thread should stop */
+	unsigned int stop:1;
 };
 
 struct softmix_stats {
-		/*! Each index represents a sample rate used above the internal rate. */
-		unsigned int sample_rates[16];
-		/*! Each index represents the number of channels using the same index in the sample_rates array.  */
-		unsigned int num_channels[16];
-		/*! the number of channels above the internal sample rate */
-		unsigned int num_above_internal_rate;
-		/*! the number of channels at the internal sample rate */
-		unsigned int num_at_internal_rate;
-		/*! the absolute highest sample rate supported by any channel in the bridge */
-		unsigned int highest_supported_rate;
-		/*! Is the sample rate locked by the bridge, if so what is that rate.*/
-		unsigned int locked_rate;
+	/*! Each index represents a sample rate used above the internal rate. */
+	unsigned int sample_rates[16];
+	/*! Each index represents the number of channels using the same index in the sample_rates array.  */
+	unsigned int num_channels[16];
+	/*! the number of channels above the internal sample rate */
+	unsigned int num_above_internal_rate;
+	/*! the number of channels at the internal sample rate */
+	unsigned int num_at_internal_rate;
+	/*! the absolute highest sample rate supported by any channel in the bridge */
+	unsigned int highest_supported_rate;
+	/*! Is the sample rate locked by the bridge, if so what is that rate.*/
+	unsigned int locked_rate;
 };
 
 struct softmix_mixing_array {
-	int max_num_entries;
-	int used_entries;
+	unsigned int max_num_entries;
+	unsigned int used_entries;
 	int16_t **buffers;
 };
 
@@ -213,7 +223,7 @@ static void softmix_translate_helper_change_rate(struct softmix_translate_helper
 /*!
  * \internal
  * \brief Get the next available audio on the softmix channel's read stream
- * and determine if it should be mixed out or not on the write stream. 
+ * and determine if it should be mixed out or not on the write stream.
  *
  * \retval pointer to buffer containing the exact number of samples requested on success.
  * \retval NULL if no samples are present
@@ -295,54 +305,9 @@ static void softmix_translate_helper_cleanup(struct softmix_translate_helper *tr
 	}
 }
 
-static void softmix_bridge_data_destroy(void *obj)
-{
-	struct softmix_bridge_data *softmix_data = obj;
-
-	if (softmix_data->timer) {
-		ast_timer_close(softmix_data->timer);
-		softmix_data->timer = NULL;
-	}
-}
-
-/*! \brief Function called when a bridge is created */
-static int softmix_bridge_create(struct ast_bridge *bridge)
-{
-	struct softmix_bridge_data *softmix_data;
-
-	if (!(softmix_data = ao2_alloc(sizeof(*softmix_data), softmix_bridge_data_destroy))) {
-		return -1;
-	}
-	if (!(softmix_data->timer = ast_timer_open())) {
-		ao2_ref(softmix_data, -1);
-		return -1;
-	}
-
-	/* start at 8khz, let it grow from there */
-	softmix_data->internal_rate = 8000;
-	softmix_data->internal_mixing_interval = DEFAULT_SOFTMIX_INTERVAL;
-
-	bridge->bridge_pvt = softmix_data;
-	return 0;
-}
-
-/*! \brief Function called when a bridge is destroyed */
-static int softmix_bridge_destroy(struct ast_bridge *bridge)
-{
-	struct softmix_bridge_data *softmix_data;
-
-	softmix_data = bridge->bridge_pvt;
-	if (!softmix_data) {
-		return -1;
-	}
-	ao2_ref(softmix_data, -1);
-	bridge->bridge_pvt = NULL;
-	return 0;
-}
-
 static void set_softmix_bridge_data(int rate, int interval, struct ast_bridge_channel *bridge_channel, int reset)
 {
-	struct softmix_channel *sc = bridge_channel->bridge_pvt;
+	struct softmix_channel *sc = bridge_channel->tech_pvt;
 	unsigned int channel_read_rate = ast_format_rate(ast_channel_rawreadformat(bridge_channel->chan));
 
 	ast_mutex_lock(&sc->lock);
@@ -382,39 +347,89 @@ static void set_softmix_bridge_data(int rate, int interval, struct ast_bridge_ch
 	ast_mutex_unlock(&sc->lock);
 }
 
+/*!
+ * \internal
+ * \brief Poke the mixing thread in case it is waiting for an active channel.
+ * \since 12.0.0
+ *
+ * \param softmix_data Bridge mixing data.
+ *
+ * \return Nothing
+ */
+static void softmix_poke_thread(struct softmix_bridge_data *softmix_data)
+{
+	ast_mutex_lock(&softmix_data->lock);
+	ast_cond_signal(&softmix_data->cond);
+	ast_mutex_unlock(&softmix_data->lock);
+}
+
+/*! \brief Function called when a channel is unsuspended from the bridge */
+static void softmix_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+	if (bridge->tech_pvt) {
+		softmix_poke_thread(bridge->tech_pvt);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Indicate a source change to the channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel source is changing.
+ *
+ * \return Nothing
+ */
+static void softmix_src_change(struct ast_bridge_channel *bridge_channel)
+{
+	ast_bridge_channel_queue_control_data(bridge_channel, AST_CONTROL_SRCCHANGE, NULL, 0);
+}
+
 /*! \brief Function called when a channel is joined into the bridge */
 static int softmix_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
 {
 	struct softmix_channel *sc;
-	struct softmix_bridge_data *softmix_data = bridge->bridge_pvt;
+	struct softmix_bridge_data *softmix_data;
+
+	softmix_data = bridge->tech_pvt;
+	if (!softmix_data) {
+		return -1;
+	}
 
 	/* Create a new softmix_channel structure and allocate various things on it */
 	if (!(sc = ast_calloc(1, sizeof(*sc)))) {
 		return -1;
 	}
 
+	softmix_src_change(bridge_channel);
+
 	/* Can't forget the lock */
 	ast_mutex_init(&sc->lock);
 
 	/* Can't forget to record our pvt structure within the bridged channel structure */
-	bridge_channel->bridge_pvt = sc;
+	bridge_channel->tech_pvt = sc;
 
 	set_softmix_bridge_data(softmix_data->internal_rate,
-		softmix_data->internal_mixing_interval ? softmix_data->internal_mixing_interval : DEFAULT_SOFTMIX_INTERVAL,
+		softmix_data->internal_mixing_interval
+			? softmix_data->internal_mixing_interval
+			: DEFAULT_SOFTMIX_INTERVAL,
 		bridge_channel, 0);
 
+	softmix_poke_thread(softmix_data);
 	return 0;
 }
 
 /*! \brief Function called when a channel leaves the bridge */
-static int softmix_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+static void softmix_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
 {
-	struct softmix_channel *sc = bridge_channel->bridge_pvt;
+	struct softmix_channel *sc = bridge_channel->tech_pvt;
 
-	if (!(bridge_channel->bridge_pvt)) {
-		return 0;
+	if (!sc) {
+		return;
 	}
-	bridge_channel->bridge_pvt = NULL;
+	bridge_channel->tech_pvt = NULL;
+
+	softmix_src_change(bridge_channel);
 
 	/* Drop mutex lock */
 	ast_mutex_destroy(&sc->lock);
@@ -427,111 +442,122 @@ static int softmix_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_cha
 
 	/* Eep! drop ourselves */
 	ast_free(sc);
-
-	return 0;
 }
 
 /*!
  * \internal
- * \brief If the bridging core passes DTMF to us, then they want it to be distributed out to all memebers. Do that here.
+ * \brief Pass the given frame to everyone else.
+ * \since 12.0.0
+ *
+ * \param bridge What bridge to distribute frame.
+ * \param bridge_channel Channel to optionally not pass frame to. (NULL to pass to everyone)
+ * \param frame Frame to pass.
+ *
+ * \return Nothing
  */
-static void softmix_pass_dtmf(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+static void softmix_pass_everyone_else(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
 {
-	struct ast_bridge_channel *tmp;
-	AST_LIST_TRAVERSE(&bridge->channels, tmp, entry) {
-		if (tmp == bridge_channel) {
+	struct ast_bridge_channel *cur;
+
+	AST_LIST_TRAVERSE(&bridge->channels, cur, entry) {
+		if (cur == bridge_channel) {
 			continue;
 		}
-		ast_write(tmp->chan, frame);
+		ast_bridge_channel_queue_frame(cur, frame);
 	}
 }
 
 static void softmix_pass_video_top_priority(struct ast_bridge *bridge, struct ast_frame *frame)
 {
-	struct ast_bridge_channel *tmp;
-	AST_LIST_TRAVERSE(&bridge->channels, tmp, entry) {
-		if (tmp->suspended) {
+	struct ast_bridge_channel *cur;
+
+	AST_LIST_TRAVERSE(&bridge->channels, cur, entry) {
+		if (cur->suspended) {
 			continue;
 		}
-		if (ast_bridge_is_video_src(bridge, tmp->chan) == 1) {
-			ast_write(tmp->chan, frame);
+		if (ast_bridge_is_video_src(bridge, cur->chan) == 1) {
+			ast_bridge_channel_queue_frame(cur, frame);
 			break;
 		}
 	}
 }
 
-static void softmix_pass_video_all(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame, int echo)
+/*!
+ * \internal
+ * \brief Determine what to do with a video frame.
+ * \since 12.0.0
+ *
+ * \param bridge Which bridge is getting the frame
+ * \param bridge_channel Which channel is writing the frame.
+ * \param frame What is being written.
+ *
+ * \return Nothing
+ */
+static void softmix_bridge_write_video(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
 {
-	struct ast_bridge_channel *tmp;
-	AST_LIST_TRAVERSE(&bridge->channels, tmp, entry) {
-		if (tmp->suspended) {
-			continue;
+	struct softmix_channel *sc;
+	int video_src_priority;
+
+	/* Determine if the video frame should be distributed or not */
+	switch (bridge->video_mode.mode) {
+	case AST_BRIDGE_VIDEO_MODE_NONE:
+		break;
+	case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
+		video_src_priority = ast_bridge_is_video_src(bridge, bridge_channel->chan);
+		if (video_src_priority == 1) {
+			/* Pass to me and everyone else. */
+			softmix_pass_everyone_else(bridge, NULL, frame);
 		}
-		if ((tmp->chan == bridge_channel->chan) && !echo) {
-			continue;
+		break;
+	case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
+		sc = bridge_channel->tech_pvt;
+		ast_mutex_lock(&sc->lock);
+		ast_bridge_update_talker_src_video_mode(bridge, bridge_channel->chan,
+			sc->video_talker.energy_average,
+			ast_format_get_video_mark(&frame->subclass.format));
+		ast_mutex_unlock(&sc->lock);
+		video_src_priority = ast_bridge_is_video_src(bridge, bridge_channel->chan);
+		if (video_src_priority == 1) {
+			int num_src = ast_bridge_number_video_src(bridge);
+			int echo = num_src > 1 ? 0 : 1;
+
+			softmix_pass_everyone_else(bridge, echo ? NULL : bridge_channel, frame);
+		} else if (video_src_priority == 2) {
+			softmix_pass_video_top_priority(bridge, frame);
 		}
-		ast_write(tmp->chan, frame);
+		break;
 	}
 }
 
-/*! \brief Function called when a channel writes a frame into the bridge */
-static enum ast_bridge_write_result softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+/*!
+ * \internal
+ * \brief Determine what to do with a voice frame.
+ * \since 12.0.0
+ *
+ * \param bridge Which bridge is getting the frame
+ * \param bridge_channel Which channel is writing the frame.
+ * \param frame What is being written.
+ *
+ * \return Nothing
+ */
+static void softmix_bridge_write_voice(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
 {
-	struct softmix_channel *sc = bridge_channel->bridge_pvt;
-	struct softmix_bridge_data *softmix_data = bridge->bridge_pvt;
+	struct softmix_channel *sc = bridge_channel->tech_pvt;
+	struct softmix_bridge_data *softmix_data = bridge->tech_pvt;
 	int totalsilence = 0;
 	int cur_energy = 0;
 	int silence_threshold = bridge_channel->tech_args.silence_threshold ?
 		bridge_channel->tech_args.silence_threshold :
 		DEFAULT_SOFTMIX_SILENCE_THRESHOLD;
 	char update_talking = -1;  /* if this is set to 0 or 1, tell the bridge that the channel has started or stopped talking. */
-	int res = AST_BRIDGE_WRITE_SUCCESS;
-
-	/* Only accept audio frames, all others are unsupported */
-	if (frame->frametype == AST_FRAME_DTMF_END || frame->frametype == AST_FRAME_DTMF_BEGIN) {
-		softmix_pass_dtmf(bridge, bridge_channel, frame);
-		goto bridge_write_cleanup;
-	} else if (frame->frametype != AST_FRAME_VOICE && frame->frametype != AST_FRAME_VIDEO) {
-		res = AST_BRIDGE_WRITE_UNSUPPORTED;
-		goto bridge_write_cleanup;
-	} else if (frame->datalen == 0) {
-		goto bridge_write_cleanup;
-	}
-
-	/* Determine if this video frame should be distributed or not */
-	if (frame->frametype == AST_FRAME_VIDEO) {
-		int num_src = ast_bridge_number_video_src(bridge);
-		int video_src_priority = ast_bridge_is_video_src(bridge, bridge_channel->chan);
-
-		switch (bridge->video_mode.mode) {
-		case AST_BRIDGE_VIDEO_MODE_NONE:
-			break;
-		case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
-			if (video_src_priority == 1) {
-				softmix_pass_video_all(bridge, bridge_channel, frame, 1);
-			}
-			break;
-		case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
-			ast_mutex_lock(&sc->lock);
-			ast_bridge_update_talker_src_video_mode(bridge, bridge_channel->chan, sc->video_talker.energy_average, ast_format_get_video_mark(&frame->subclass.format));
-			ast_mutex_unlock(&sc->lock);
-			if (video_src_priority == 1) {
-				int echo = num_src > 1 ? 0 : 1;
-				softmix_pass_video_all(bridge, bridge_channel, frame, echo);
-			} else if (video_src_priority == 2) {
-				softmix_pass_video_top_priority(bridge, frame);
-			}
-			break;
-		}
-		goto bridge_write_cleanup;
-	}
 
-	/* If we made it here, we are going to write the frame into the conference */
+	/* Write the frame into the conference */
 	ast_mutex_lock(&sc->lock);
 	ast_dsp_silence_with_energy(sc->dsp, frame, &totalsilence, &cur_energy);
 
 	if (bridge->video_mode.mode == AST_BRIDGE_VIDEO_MODE_TALKER_SRC) {
 		int cur_slot = sc->video_talker.energy_history_cur_slot;
+
 		sc->video_talker.energy_accum -= sc->video_talker.energy_history[cur_slot];
 		sc->video_talker.energy_accum += cur_energy;
 		sc->video_talker.energy_history[cur_slot] = cur_energy;
@@ -568,50 +594,77 @@ static enum ast_bridge_write_result softmix_bridge_write(struct ast_bridge *brid
 		ast_slinfactory_feed(&sc->factory, frame);
 	}
 
-	/* If a frame is ready to be written out, do so */
-	if (sc->have_frame) {
-		ast_write(bridge_channel->chan, &sc->write_frame);
-		sc->have_frame = 0;
-	}
-
 	/* Alllll done */
 	ast_mutex_unlock(&sc->lock);
 
 	if (update_talking != -1) {
-		ast_bridge_notify_talking(bridge, bridge_channel, update_talking);
+		ast_bridge_notify_talking(bridge_channel, update_talking);
 	}
-
-	return res;
-
-bridge_write_cleanup:
-	/* Even though the frame is not being written into the conference because it is not audio,
-	 * we should use this opportunity to check to see if a frame is ready to be written out from
-	 * the conference to the channel. */
-	ast_mutex_lock(&sc->lock);
-	if (sc->have_frame) {
-		ast_write(bridge_channel->chan, &sc->write_frame);
-		sc->have_frame = 0;
-	}
-	ast_mutex_unlock(&sc->lock);
-
-	return res;
 }
 
-/*! \brief Function called when the channel's thread is poked */
-static int softmix_bridge_poke(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+/*!
+ * \internal
+ * \brief Determine what to do with a control frame.
+ * \since 12.0.0
+ *
+ * \param bridge Which bridge is getting the frame
+ * \param bridge_channel Which channel is writing the frame.
+ * \param frame What is being written.
+ *
+ * \return Nothing
+ */
+static void softmix_bridge_write_control(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
 {
-	struct softmix_channel *sc = bridge_channel->bridge_pvt;
+/* BUGBUG need to look at channel roles to determine what to do with control frame. */
+	/*! \todo BUGBUG softmix_bridge_write_control() not written */
+}
 
-	ast_mutex_lock(&sc->lock);
+/*!
+ * \internal
+ * \brief Determine what to do with a frame written into the bridge.
+ * \since 12.0.0
+ *
+ * \param bridge Which bridge is getting the frame
+ * \param bridge_channel Which channel is writing the frame.
+ * \param frame What is being written.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * \note On entry, bridge is already locked.
+ */
+static int softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+{
+	int res = 0;
 
-	if (sc->have_frame) {
-		ast_write(bridge_channel->chan, &sc->write_frame);
-		sc->have_frame = 0;
+	if (!bridge->tech_pvt || !bridge_channel->tech_pvt) {
+		return -1;
 	}
 
-	ast_mutex_unlock(&sc->lock);
+	switch (frame->frametype) {
+	case AST_FRAME_DTMF_BEGIN:
+	case AST_FRAME_DTMF_END:
+		softmix_pass_everyone_else(bridge, bridge_channel, frame);
+		break;
+	case AST_FRAME_VOICE:
+		softmix_bridge_write_voice(bridge, bridge_channel, frame);
+		break;
+	case AST_FRAME_VIDEO:
+		softmix_bridge_write_video(bridge, bridge_channel, frame);
+		break;
+	case AST_FRAME_CONTROL:
+		softmix_bridge_write_control(bridge, bridge_channel, frame);
+		break;
+	case AST_FRAME_BRIDGE_ACTION:
+		softmix_pass_everyone_else(bridge, bridge_channel, frame);
+		break;
+	default:
+		ast_debug(3, "Frame type %d unsupported\n", frame->frametype);
+		res = -1;
+		break;
+	}
 
-	return 0;
+	return res;
 }
 
 static void gather_softmix_stats(struct softmix_stats *stats,
@@ -648,7 +701,7 @@ static void gather_softmix_stats(struct softmix_stats *stats,
  * \brief Analyse mixing statistics and change bridges internal rate
  * if necessary.
  *
- * \retval 0, no changes to internal rate 
+ * \retval 0, no changes to internal rate
  * \ratval 1, internal rate was changed, update all the channels on the next mixing iteration.
  */
 static unsigned int analyse_softmix_stats(struct softmix_stats *stats, struct softmix_bridge_data *softmix_data)
@@ -665,7 +718,8 @@ static unsigned int analyse_softmix_stats(struct softmix_stats *stats, struct so
 		 * from the current rate we are using. */
 		if (softmix_data->internal_rate != stats->locked_rate) {
 			softmix_data->internal_rate = stats->locked_rate;
-			ast_debug(1, " Bridge is locked in at sample rate %d\n", softmix_data->internal_rate);
+			ast_debug(1, "Bridge is locked in at sample rate %d\n",
+				softmix_data->internal_rate);
 			return 1;
 		}
 	} else if (stats->num_above_internal_rate >= 2) {
@@ -704,13 +758,15 @@ static unsigned int analyse_softmix_stats(struct softmix_stats *stats, struct so
 			}
 		}
 
-		ast_debug(1, " Bridge changed from %d To %d\n", softmix_data->internal_rate, best_rate);
+		ast_debug(1, "Bridge changed from %d To %d\n",
+			softmix_data->internal_rate, best_rate);
 		softmix_data->internal_rate = best_rate;
 		return 1;
 	} else if (!stats->num_at_internal_rate && !stats->num_above_internal_rate) {
 		/* In this case, the highest supported rate is actually lower than the internal rate */
 		softmix_data->internal_rate = stats->highest_supported_rate;
-		ast_debug(1, " Bridge changed from %d to %d\n", softmix_data->internal_rate, stats->highest_supported_rate);
+		ast_debug(1, "Bridge changed from %d to %d\n",
+			softmix_data->internal_rate, stats->highest_supported_rate);
 		return 1;
 	}
 	return 0;
@@ -745,38 +801,38 @@ static int softmix_mixing_array_grow(struct softmix_mixing_array *mixing_array,
 	return 0;
 }
 
-/*! \brief Function which acts as the mixing thread */
-static int softmix_bridge_thread(struct ast_bridge *bridge)
+/*!
+ * \brief Mixing loop.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int softmix_mixing_loop(struct ast_bridge *bridge)
 {
 	struct softmix_stats stats = { { 0 }, };
 	struct softmix_mixing_array mixing_array;
-	struct softmix_bridge_data *softmix_data;
+	struct softmix_bridge_data *softmix_data = bridge->tech_pvt;
 	struct ast_timer *timer;
 	struct softmix_translate_helper trans_helper;
 	int16_t buf[MAX_DATALEN];
 	unsigned int stat_iteration_counter = 0; /* counts down, gather stats at zero and reset. */
 	int timingfd;
 	int update_all_rates = 0; /* set this when the internal sample rate has changed */
-	int i, x;
+	unsigned int idx;
+	unsigned int x;
 	int res = -1;
 
-	softmix_data = bridge->bridge_pvt;
-	if (!softmix_data) {
-		goto softmix_cleanup;
-	}
-
-	ao2_ref(softmix_data, 1);
 	timer = softmix_data->timer;
 	timingfd = ast_timer_fd(timer);
 	softmix_translate_helper_init(&trans_helper, softmix_data->internal_rate);
 	ast_timer_set_rate(timer, (1000 / softmix_data->internal_mixing_interval));
 
 	/* Give the mixing array room to grow, memory is cheap but allocations are expensive. */
-	if (softmix_mixing_array_init(&mixing_array, bridge->num + 10)) {
+	if (softmix_mixing_array_init(&mixing_array, bridge->num_channels + 10)) {
 		goto softmix_cleanup;
 	}
 
-	while (!bridge->stop && !bridge->refresh && bridge->array_num) {
+	while (!softmix_data->stop && bridge->num_active) {
 		struct ast_bridge_channel *bridge_channel;
 		int timeout = -1;
 		enum ast_format_id cur_slin_id = ast_format_slin_by_rate(softmix_data->internal_rate);
@@ -793,8 +849,8 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
 		}
 
 		/* Grow the mixing array buffer as participants are added. */
-		if (mixing_array.max_num_entries < bridge->num
-			&& softmix_mixing_array_grow(&mixing_array, bridge->num + 5)) {
+		if (mixing_array.max_num_entries < bridge->num_channels
+			&& softmix_mixing_array_grow(&mixing_array, bridge->num_channels + 5)) {
 			goto softmix_cleanup;
 		}
 
@@ -815,7 +871,7 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
 
 		/* Go through pulling audio from each factory that has it available */
 		AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
-			struct softmix_channel *sc = bridge_channel->bridge_pvt;
+			struct softmix_channel *sc = bridge_channel->tech_pvt;
 
 			/* Update the sample rate to match the bridge's native sample rate if necessary. */
 			if (update_all_rates) {
@@ -842,15 +898,15 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
 
 		/* mix it like crazy */
 		memset(buf, 0, softmix_datalen);
-		for (i = 0; i < mixing_array.used_entries; i++) {
-			for (x = 0; x < softmix_samples; x++) {
-				ast_slinear_saturated_add(buf + x, mixing_array.buffers[i] + x);
+		for (idx = 0; idx < mixing_array.used_entries; ++idx) {
+			for (x = 0; x < softmix_samples; ++x) {
+				ast_slinear_saturated_add(buf + x, mixing_array.buffers[idx] + x);
 			}
 		}
 
 		/* Next step go through removing the channel's own audio and creating a good frame... */
 		AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
-			struct softmix_channel *sc = bridge_channel->bridge_pvt;
+			struct softmix_channel *sc = bridge_channel->tech_pvt;
 
 			if (bridge_channel->suspended) {
 				continue;
@@ -869,13 +925,10 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
 			/* process the softmix channel's new write audio */
 			softmix_process_write_audio(&trans_helper, ast_channel_rawwriteformat(bridge_channel->chan), sc);
 
-			/* The frame is now ready for use... */
-			sc->have_frame = 1;
-
 			ast_mutex_unlock(&sc->lock);
 
-			/* Poke bridged channel thread just in case */
-			pthread_kill(bridge_channel->thread, SIGURG);
+			/* A frame is now ready for the channel. */
+			ast_bridge_channel_queue_frame(bridge_channel, &sc->write_frame);
 		}
 
 		update_all_rates = 0;
@@ -885,17 +938,17 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
 		}
 		stat_iteration_counter--;
 
-		ao2_unlock(bridge);
+		ast_bridge_unlock(bridge);
 		/* cleanup any translation frame data from the previous mixing iteration. */
 		softmix_translate_helper_cleanup(&trans_helper);
 		/* Wait for the timing source to tell us to wake up and get things done */
 		ast_waitfor_n_fd(&timingfd, 1, &timeout, NULL);
 		if (ast_timer_ack(timer, 1) < 0) {
 			ast_log(LOG_ERROR, "Failed to acknowledge timer in softmix bridge.\n");
-			ao2_lock(bridge);
+			ast_bridge_lock(bridge);
 			goto softmix_cleanup;
 		}
-		ao2_lock(bridge);
+		ast_bridge_lock(bridge);
 
 		/* make sure to detect mixing interval changes if they occur. */
 		if (bridge->internal_mixing_interval && (bridge->internal_mixing_interval != softmix_data->internal_mixing_interval)) {
@@ -910,23 +963,141 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
 softmix_cleanup:
 	softmix_translate_helper_destroy(&trans_helper);
 	softmix_mixing_array_destroy(&mixing_array);
-	if (softmix_data) {
-		ao2_ref(softmix_data, -1);
-	}
 	return res;
 }
 
+/*!
+ * \internal
+ * \brief Mixing thread.
+ * \since 12.0.0
+ *
+ * \note The thread does not have its own reference to the
+ * bridge.  The lifetime of the thread is tied to the lifetime
+ * of the mixing technology association with the bridge.
+ */
+static void *softmix_mixing_thread(void *data)
+{
+	struct ast_bridge *bridge = data;
+	struct softmix_bridge_data *softmix_data;
+
+	ast_bridge_lock(bridge);
+	if (bridge->callid) {
+		ast_callid_threadassoc_add(bridge->callid);
+	}
+
+	ast_debug(1, "Bridge %s: starting mixing thread\n", bridge->uniqueid);
+
+	softmix_data = bridge->tech_pvt;
+	while (!softmix_data->stop) {
+		if (!bridge->num_active) {
+			/* Wait for something to happen to the bridge. */
+			ast_bridge_unlock(bridge);
+			ast_mutex_lock(&softmix_data->lock);
+			if (!softmix_data->stop) {
+				ast_cond_wait(&softmix_data->cond, &softmix_data->lock);
+			}
+			ast_mutex_unlock(&softmix_data->lock);
+			ast_bridge_lock(bridge);
+			continue;
+		}
+
+		if (softmix_mixing_loop(bridge)) {
+			/*
+			 * A mixing error occurred.  Sleep and try again later so we
+			 * won't flood the logs.
+			 */
+			ast_bridge_unlock(bridge);
+			sleep(1);
+			ast_bridge_lock(bridge);
+		}
+	}
+
+	ast_bridge_unlock(bridge);
+
+	ast_debug(1, "Bridge %s: stopping mixing thread\n", bridge->uniqueid);
+
+	return NULL;
+}
+
+static void softmix_bridge_data_destroy(struct softmix_bridge_data *softmix_data)
+{
+	if (softmix_data->timer) {
+		ast_timer_close(softmix_data->timer);
+		softmix_data->timer = NULL;
+	}
+	ast_mutex_destroy(&softmix_data->lock);
+	ast_free(softmix_data);
+}
+
+/*! \brief Function called when a bridge is created */
+static int softmix_bridge_create(struct ast_bridge *bridge)
+{
+	struct softmix_bridge_data *softmix_data;
+
+	softmix_data = ast_calloc(1, sizeof(*softmix_data));
+	if (!softmix_data) {
+		return -1;
+	}
+	ast_mutex_init(&softmix_data->lock);
+	softmix_data->timer = ast_timer_open();
+	if (!softmix_data->timer) {
+		softmix_bridge_data_destroy(softmix_data);
+		return -1;
+	}
+	/* start at 8khz, let it grow from there */
+	softmix_data->internal_rate = 8000;
+	softmix_data->internal_mixing_interval = DEFAULT_SOFTMIX_INTERVAL;
+
+	bridge->tech_pvt = softmix_data;
+
+	/* Start the mixing thread. */
+	if (ast_pthread_create(&softmix_data->thread, NULL, softmix_mixing_thread, bridge)) {
+		softmix_data->thread = AST_PTHREADT_NULL;
+		softmix_bridge_data_destroy(softmix_data);
+		bridge->tech_pvt = NULL;
+		return -1;
+	}
+
+	return 0;
+}
+
+/*! \brief Function called when a bridge is destroyed */
+static void softmix_bridge_destroy(struct ast_bridge *bridge)
+{
+	struct softmix_bridge_data *softmix_data;
+	pthread_t thread;
+
+	softmix_data = bridge->tech_pvt;
+	if (!softmix_data) {
+		return;
+	}
+
+	/* Stop the mixing thread. */
+	ast_mutex_lock(&softmix_data->lock);
+	softmix_data->stop = 1;
+	ast_cond_signal(&softmix_data->cond);
+	thread = softmix_data->thread;
+	softmix_data->thread = AST_PTHREADT_NULL;
+	ast_mutex_unlock(&softmix_data->lock);
+	if (thread != AST_PTHREADT_NULL) {
+		ast_debug(1, "Waiting for mixing thread to die.\n");
+		pthread_join(thread, NULL);
+	}
+
+	softmix_bridge_data_destroy(softmix_data);
+	bridge->tech_pvt = NULL;
+}
+
 static struct ast_bridge_technology softmix_bridge = {
 	.name = "softmix",
-	.capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX | AST_BRIDGE_CAPABILITY_THREAD | AST_BRIDGE_CAPABILITY_MULTITHREADED | AST_BRIDGE_CAPABILITY_OPTIMIZE | AST_BRIDGE_CAPABILITY_VIDEO,
-	.preference = AST_BRIDGE_PREFERENCE_LOW,
+	.capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX,
+	.preference = AST_BRIDGE_PREFERENCE_BASE_MULTIMIX,
 	.create = softmix_bridge_create,
 	.destroy = softmix_bridge_destroy,
 	.join = softmix_bridge_join,
 	.leave = softmix_bridge_leave,
+	.unsuspend = softmix_bridge_unsuspend,
 	.write = softmix_bridge_write,
-	.thread = softmix_bridge_thread,
-	.poke = softmix_bridge_poke,
 };
 
 static int unload_module(void)
diff --git a/channels/chan_agent.c b/channels/chan_agent.c
index 3fb6891ccb4898e4658fc515855593a39d18baad..d72254ee75d914504ef411d4345caf985ba12898 100644
--- a/channels/chan_agent.c
+++ b/channels/chan_agent.c
@@ -31,7 +31,6 @@
  * \ingroup channel_drivers
  */
 /*** MODULEINFO
-        <depend>chan_local</depend>
         <depend>res_monitor</depend>
 	<support_level>core</support_level>
  ***/
@@ -346,6 +345,7 @@ static char *complete_agent_logoff_cmd(const char *line, const char *word, int p
 static struct ast_channel* agent_get_base_channel(struct ast_channel *chan);
 static int agent_logoff(const char *agent, int soft);
 
+/* BUGBUG This channel driver is totally hosed until it is rewritten. */
 /*! \brief Channel interface description for PBX integration */
 static struct ast_channel_tech agent_tech = {
 	.type = "Agent",
@@ -2589,5 +2589,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Agent Proxy Channel",
 		.unload = unload_module,
 		.reload = reload,
 		.load_pri = AST_MODPRI_CHANNEL_DRIVER,
-		.nonoptreq = "res_monitor,chan_local",
+		.nonoptreq = "res_monitor",
 	       );
diff --git a/channels/chan_bridge.c b/channels/chan_bridge.c
deleted file mode 100644
index 8eac76a829abd63a291e06c7816764a48517d6de..0000000000000000000000000000000000000000
--- a/channels/chan_bridge.c
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 1999 - 2008, Digium, Inc.
- *
- * Joshua Colp <jcolp@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
- *
- * \author Joshua Colp <jcolp@digium.com>
- *
- * \brief Bridge Interaction Channel
- *
- * \ingroup channel_drivers
- */
-
-/*** MODULEINFO
-	<support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include <fcntl.h>
-#include <sys/signal.h>
-
-#include "asterisk/lock.h"
-#include "asterisk/channel.h"
-#include "asterisk/config.h"
-#include "asterisk/module.h"
-#include "asterisk/pbx.h"
-#include "asterisk/sched.h"
-#include "asterisk/io.h"
-#include "asterisk/acl.h"
-#include "asterisk/callerid.h"
-#include "asterisk/file.h"
-#include "asterisk/cli.h"
-#include "asterisk/app.h"
-#include "asterisk/bridging.h"
-#include "asterisk/astobj2.h"
-
-static struct ast_channel *bridge_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause);
-static int bridge_call(struct ast_channel *ast, const char *dest, int timeout);
-static int bridge_hangup(struct ast_channel *ast);
-static struct ast_frame *bridge_read(struct ast_channel *ast);
-static int bridge_write(struct ast_channel *ast, struct ast_frame *f);
-static struct ast_channel *bridge_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge);
-
-static struct ast_channel_tech bridge_tech = {
-	.type = "Bridge",
-	.description = "Bridge Interaction Channel",
-	.requester = bridge_request,
-	.call = bridge_call,
-	.hangup = bridge_hangup,
-	.read = bridge_read,
-	.write = bridge_write,
-	.write_video = bridge_write,
-	.exception = bridge_read,
-	.bridged_channel = bridge_bridgedchannel,
-};
-
-struct bridge_pvt {
-	struct ast_channel *input;  /*!< Input channel - talking to source */
-	struct ast_channel *output; /*!< Output channel - talking to bridge */
-};
-
-/*! \brief Called when the user of this channel wants to get the actual channel in the bridge */
-static struct ast_channel *bridge_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge)
-{
-	struct bridge_pvt *p = ast_channel_tech_pvt(chan);
-	return (chan == p->input) ? p->output : bridge;
-}
-
-/*! \brief Called when a frame should be read from the channel */
-static struct ast_frame  *bridge_read(struct ast_channel *ast)
-{
-	return &ast_null_frame;
-}
-
-/*! \brief Called when a frame should be written out to a channel */
-static int bridge_write(struct ast_channel *ast, struct ast_frame *f)
-{
-	struct bridge_pvt *p = ast_channel_tech_pvt(ast);
-	struct ast_channel *other = NULL;
-
-	ao2_lock(p);
-	/* only write frames to output. */
-	if (p->input == ast) {
-		other = p->output;
-		if (other) {
-			ast_channel_ref(other);
-		}
-	}
-	ao2_unlock(p);
-
-	if (other) {
-		ast_channel_unlock(ast);
-		ast_queue_frame(other, f);
-		ast_channel_lock(ast);
-		other = ast_channel_unref(other);
-	}
-
-	return 0;
-}
-
-/*! \brief Called when the channel should actually be dialed */
-static int bridge_call(struct ast_channel *ast, const char *dest, int timeout)
-{
-	struct bridge_pvt *p = ast_channel_tech_pvt(ast);
-
-	/* If no bridge has been provided on the input channel, bail out */
-	if (!ast_channel_internal_bridge(ast)) {
-		return -1;
-	}
-
-	/* Impart the output channel upon the given bridge of the input channel */
-	return ast_bridge_impart(ast_channel_internal_bridge(p->input), p->output, NULL, NULL, 0)
-		? -1 : 0;
-}
-
-/*! \brief Called when a channel should be hung up */
-static int bridge_hangup(struct ast_channel *ast)
-{
-	struct bridge_pvt *p = ast_channel_tech_pvt(ast);
-
-	if (!p) {
-		return 0;
-	}
-
-	ao2_lock(p);
-	if (p->input == ast) {
-		p->input = NULL;
-	} else if (p->output == ast) {
-		p->output = NULL;
-	}
-	ao2_unlock(p);
-
-	ast_channel_tech_pvt_set(ast, NULL);
-	ao2_ref(p, -1);
-
-	return 0;
-}
-
-/*! \brief Called when we want to place a call somewhere, but not actually call it... yet */
-static struct ast_channel *bridge_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause)
-{
-	struct bridge_pvt *p = NULL;
-	struct ast_format slin;
-
-	/* Try to allocate memory for our very minimal pvt structure */
-	if (!(p = ao2_alloc(sizeof(*p), NULL))) {
-		return NULL;
-	}
-
-	/* Try to grab two Asterisk channels to use as input and output channels */
-	if (!(p->input = ast_channel_alloc(1, AST_STATE_UP, 0, 0, "", "", "", requestor ? ast_channel_linkedid(requestor) : NULL, 0, "Bridge/%p-input", p))) {
-		ao2_ref(p, -1);
-		return NULL;
-	}
-	if (!(p->output = ast_channel_alloc(1, AST_STATE_UP, 0, 0, "", "", "", requestor ? ast_channel_linkedid(requestor) : NULL, 0, "Bridge/%p-output", p))) {
-		p->input = ast_channel_release(p->input);
-		ao2_ref(p, -1);
-		return NULL;
-	}
-
-	/* Setup parameters on both new channels */
-	ast_channel_tech_set(p->input, &bridge_tech);
-	ast_channel_tech_set(p->output, &bridge_tech);
-
-	ao2_ref(p, 2);
-	ast_channel_tech_pvt_set(p->input, p);
-	ast_channel_tech_pvt_set(p->output, p);
-
-	ast_format_set(&slin, AST_FORMAT_SLINEAR, 0);
-
-	ast_format_cap_add(ast_channel_nativeformats(p->input), &slin);
-	ast_format_cap_add(ast_channel_nativeformats(p->output), &slin);
-	ast_format_copy(ast_channel_readformat(p->input), &slin);
-	ast_format_copy(ast_channel_readformat(p->output), &slin);
-	ast_format_copy(ast_channel_rawreadformat(p->input), &slin);
-	ast_format_copy(ast_channel_rawreadformat(p->output), &slin);
-	ast_format_copy(ast_channel_writeformat(p->input), &slin);
-	ast_format_copy(ast_channel_writeformat(p->output), &slin);
-	ast_format_copy(ast_channel_rawwriteformat(p->input), &slin);
-	ast_format_copy(ast_channel_rawwriteformat(p->output), &slin);
-
-	ast_answer(p->output);
-	ast_answer(p->input);
-
-	/* remove the reference from the alloc. The channels now own the pvt. */
-	ao2_ref(p, -1);
-	return p->input;
-}
-
-/*! \brief Load module into PBX, register channel */
-static int load_module(void)
-{
-	if (!(bridge_tech.capabilities = ast_format_cap_alloc())) {
-		return AST_MODULE_LOAD_FAILURE;
-	}
-
-	ast_format_cap_add_all(bridge_tech.capabilities);
-	/* Make sure we can register our channel type */
-	if (ast_channel_register(&bridge_tech)) {
-		ast_log(LOG_ERROR, "Unable to register channel class 'Bridge'\n");
-		return AST_MODULE_LOAD_FAILURE;
-	}
-	return AST_MODULE_LOAD_SUCCESS;
-}
-
-/*! \brief Unload the bridge interaction channel from Asterisk */
-static int unload_module(void)
-{
-	ast_channel_unregister(&bridge_tech);
-	bridge_tech.capabilities = ast_format_cap_destroy(bridge_tech.capabilities);
-	return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Bridge Interaction Channel",
-	.load = load_module,
-	.unload = unload_module,
-	.load_pri = AST_MODPRI_CHANNEL_DRIVER,
-);
diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c
index 44fe3f4fb90d10006fac980ec8aeade54f2a3529..1f9ee5a56fda98ec21ed556e040de3482c054470 100644
--- a/channels/chan_dahdi.c
+++ b/channels/chan_dahdi.c
@@ -1553,6 +1553,7 @@ static int dahdi_func_write(struct ast_channel *chan, const char *function, char
 static int dahdi_devicestate(const char *data);
 static int dahdi_cc_callback(struct ast_channel *inbound, const char *dest, ast_cc_callback_fn callback);
 
+/* BUGBUG The DAHDI channel driver needs its own native bridge technology. */
 static struct ast_channel_tech dahdi_tech = {
 	.type = "DAHDI",
 	.description = tdesc,
diff --git a/channels/chan_gulp.c b/channels/chan_gulp.c
index 74859534b1100389dd1b93938d4f843d584295bf..4af137f282f9fc758aca7a174cd2168b7226fd7f 100644
--- a/channels/chan_gulp.c
+++ b/channels/chan_gulp.c
@@ -134,7 +134,6 @@ static struct ast_channel_tech gulp_tech = {
 	.send_text = gulp_sendtext,
 	.send_digit_begin = gulp_digit_begin,
 	.send_digit_end = gulp_digit_end,
-	.bridge = ast_rtp_instance_bridge,
 	.call = gulp_call,
 	.hangup = gulp_hangup,
 	.answer = gulp_answer,
diff --git a/channels/chan_h323.c b/channels/chan_h323.c
index f6364a1186ca524e225a42a1b44221a23b3bd105..2476f5065f8dddcecd67b50b4bb7812d74112bb0 100644
--- a/channels/chan_h323.c
+++ b/channels/chan_h323.c
@@ -275,7 +275,6 @@ static struct ast_channel_tech oh323_tech = {
 	.write = oh323_write,
 	.indicate = oh323_indicate,
 	.fixup = oh323_fixup,
-	.bridge = ast_rtp_instance_bridge,
 };
 
 static const char* redirectingreason2str(int redirectingreason)
diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c
index 112a99375366b9e2bdf954437a610d3c0a5a46ce..49ce66cb7921d1045c899d614c146bb94c2aa865 100644
--- a/channels/chan_iax2.c
+++ b/channels/chan_iax2.c
@@ -102,6 +102,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/data.h"
 #include "asterisk/netsock2.h"
 #include "asterisk/security_events.h"
+#include "asterisk/bridging.h"
 
 #include "iax2/include/iax2.h"
 #include "iax2/include/firmware.h"
@@ -1258,6 +1259,7 @@ static void sched_delay_remove(struct sockaddr_in *sin, callno_entry entry);
 static void network_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message);
 static void acl_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message);
 
+/* BUGBUG The IAX2 channel driver needs its own native bridge technology. */
 static struct ast_channel_tech iax2_tech = {
 	.type = "IAX2",
 	.description = tdesc,
@@ -9221,130 +9223,6 @@ static void spawn_dp_lookup(int callno, const char *context, const char *calledn
 	}
 }
 
-struct iax_dual {
-	struct ast_channel *chan1;
-	struct ast_channel *chan2;
-	char *park_exten;
-	char *park_context;
-};
-
-static void *iax_park_thread(void *stuff)
-{
-	struct iax_dual *d;
-	int res;
-	int ext = 0;
-
-	d = stuff;
-
-	ast_debug(4, "IAX Park: Transferer channel %s, Transferee %s\n",
-		ast_channel_name(d->chan2), ast_channel_name(d->chan1));
-
-	res = ast_park_call_exten(d->chan1, d->chan2, d->park_exten, d->park_context, 0, &ext);
-	if (res) {
-		/* Parking failed. */
-		ast_hangup(d->chan1);
-	} else {
-		ast_log(LOG_NOTICE, "Parked on extension '%d'\n", ext);
-	}
-	ast_hangup(d->chan2);
-
-	ast_free(d->park_exten);
-	ast_free(d->park_context);
-	ast_free(d);
-	return NULL;
-}
-
-/*! DO NOT hold any locks while calling iax_park */
-static int iax_park(struct ast_channel *chan1, struct ast_channel *chan2, const char *park_exten, const char *park_context)
-{
-	struct iax_dual *d;
-	struct ast_channel *chan1m, *chan2m;/* Chan2m: The transferer, chan1m: The transferee */
-	pthread_t th;
-
-	chan1m = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, ast_channel_accountcode(chan2), ast_channel_exten(chan1), ast_channel_context(chan1), ast_channel_linkedid(chan1), ast_channel_amaflags(chan1), "Parking/%s", ast_channel_name(chan1));
-	chan2m = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, ast_channel_accountcode(chan2), ast_channel_exten(chan2), ast_channel_context(chan2), ast_channel_linkedid(chan2), ast_channel_amaflags(chan2), "IAXPeer/%s", ast_channel_name(chan2));
-	d = ast_calloc(1, sizeof(*d));
-	if (!chan1m || !chan2m || !d) {
-		if (chan1m) {
-			ast_hangup(chan1m);
-		}
-		if (chan2m) {
-			ast_hangup(chan2m);
-		}
-		ast_free(d);
-		return -1;
-	}
-	d->park_exten = ast_strdup(park_exten);
-	d->park_context = ast_strdup(park_context);
-	if (!d->park_exten || !d->park_context) {
-		ast_hangup(chan1m);
-		ast_hangup(chan2m);
-		ast_free(d->park_exten);
-		ast_free(d->park_context);
-		ast_free(d);
-		return -1;
-	}
-
-	/* Make formats okay */
-	ast_format_copy(ast_channel_readformat(chan1m), ast_channel_readformat(chan1));
-	ast_format_copy(ast_channel_writeformat(chan1m), ast_channel_writeformat(chan1));
-
-	/* Prepare for taking over the channel */
-	if (ast_channel_masquerade(chan1m, chan1)) {
-		ast_hangup(chan1m);
-		ast_hangup(chan2m);
-		ast_free(d->park_exten);
-		ast_free(d->park_context);
-		ast_free(d);
-		return -1;
-	}
-
-	/* Setup the extensions and such */
-	ast_channel_context_set(chan1m, ast_channel_context(chan1));
-	ast_channel_exten_set(chan1m, ast_channel_exten(chan1));
-	ast_channel_priority_set(chan1m, ast_channel_priority(chan1));
-
-	ast_do_masquerade(chan1m);
-
-	/* We make a clone of the peer channel too, so we can play
-	   back the announcement */
-
-	/* Make formats okay */
-	ast_format_copy(ast_channel_readformat(chan2m), ast_channel_readformat(chan2));
-	ast_format_copy(ast_channel_writeformat(chan2m), ast_channel_writeformat(chan2));
-	ast_channel_parkinglot_set(chan2m, ast_channel_parkinglot(chan2));
-
-	/* Prepare for taking over the channel */
-	if (ast_channel_masquerade(chan2m, chan2)) {
-		ast_hangup(chan1m);
-		ast_hangup(chan2m);
-		ast_free(d->park_exten);
-		ast_free(d->park_context);
-		ast_free(d);
-		return -1;
-	}
-
-	/* Setup the extensions and such */
-	ast_channel_context_set(chan2m, ast_channel_context(chan2));
-	ast_channel_exten_set(chan2m, ast_channel_exten(chan2));
-	ast_channel_priority_set(chan2m, ast_channel_priority(chan2));
-
-	ast_do_masquerade(chan2m);
-
-	d->chan1 = chan1m;	/* Transferee */
-	d->chan2 = chan2m;	/* Transferer */
-	if (ast_pthread_create_detached_background(&th, NULL, iax_park_thread, d) < 0) {
-		/* Could not start thread */
-		ast_hangup(chan1m);
-		ast_hangup(chan2m);
-		ast_free(d->park_exten);
-		ast_free(d->park_context);
-		ast_free(d);
-		return -1;
-	}
-	return 0;
-}
-
 static int check_provisioning(struct sockaddr_in *sin, int sockfd, char *si, unsigned int ver)
 {
 	unsigned int ourver;
@@ -10774,56 +10652,28 @@ static int socket_process_helper(struct iax2_thread *thread)
 				break;
 			case IAX_COMMAND_TRANSFER:
 			{
-				struct ast_channel *bridged_chan;
-				struct ast_channel *owner;
-
 				iax2_lock_owner(fr->callno);
 				if (!iaxs[fr->callno]) {
 					/* Initiating call went away before we could transfer. */
 					break;
 				}
-				owner = iaxs[fr->callno]->owner;
-				bridged_chan = owner ? ast_bridged_channel(owner) : NULL;
-				if (bridged_chan && ies.called_number) {
-					const char *context;
-
-					context = ast_strdupa(iaxs[fr->callno]->context);
+				if (iaxs[fr->callno]->owner) {
+					struct ast_channel *owner = iaxs[fr->callno]->owner;
+					char *context = ast_strdupa(iaxs[fr->callno]->context);
 
 					ast_channel_ref(owner);
-					ast_channel_ref(bridged_chan);
 					ast_channel_unlock(owner);
 					ast_mutex_unlock(&iaxsl[fr->callno]);
 
-					/* Set BLINDTRANSFER channel variables */
-					pbx_builtin_setvar_helper(owner, "BLINDTRANSFER", ast_channel_name(bridged_chan));
-					pbx_builtin_setvar_helper(bridged_chan, "BLINDTRANSFER", ast_channel_name(owner));
-
-					/* DO NOT hold any locks while calling ast_parking_ext_valid() */
-					if (ast_parking_ext_valid(ies.called_number, owner, context)) {
-						ast_debug(1, "Parking call '%s'\n", ast_channel_name(bridged_chan));
-						if (iax_park(bridged_chan, owner, ies.called_number, context)) {
-							ast_log(LOG_WARNING, "Failed to park call '%s'\n",
-								ast_channel_name(bridged_chan));
-						}
-					} else {
-						if (ast_async_goto(bridged_chan, context, ies.called_number, 1)) {
-							ast_log(LOG_WARNING,
-								"Async goto of '%s' to '%s@%s' failed\n",
-								ast_channel_name(bridged_chan), ies.called_number, context);
-						} else {
-							ast_debug(1, "Async goto of '%s' to '%s@%s' started\n",
-								ast_channel_name(bridged_chan), ies.called_number, context);
-						}
+					if (ast_bridge_transfer_blind(owner, ies.called_number,
+								context, NULL, NULL) != AST_BRIDGE_TRANSFER_SUCCESS) {
+						ast_log(LOG_WARNING, "Blind transfer of '%s' to '%s@%s' failed\n",
+							ast_channel_name(owner), ies.called_number,
+							context);
 					}
-					ast_channel_unref(owner);
-					ast_channel_unref(bridged_chan);
 
+					ast_channel_unref(owner);
 					ast_mutex_lock(&iaxsl[fr->callno]);
-				} else {
-					ast_debug(1, "Async goto not applicable on call %d\n", fr->callno);
-					if (owner) {
-						ast_channel_unlock(owner);
-					}
 				}
 
 				break;
diff --git a/channels/chan_jingle.c b/channels/chan_jingle.c
index 717afae6750632bcb39d2b62ccb050ef928a95b1..42dccf666a8083bfc4b3238b75b8f81ac4847e89 100644
--- a/channels/chan_jingle.c
+++ b/channels/chan_jingle.c
@@ -205,7 +205,6 @@ static struct ast_channel_tech jingle_tech = {
 	.send_text = jingle_sendtext,
 	.send_digit_begin = jingle_digit_begin,
 	.send_digit_end = jingle_digit_end,
-	.bridge = ast_rtp_instance_bridge,
 	.call = jingle_call,
 	.hangup = jingle_hangup,
 	.answer = jingle_answer,
diff --git a/channels/chan_local.c b/channels/chan_local.c
deleted file mode 100644
index be4586fc89ace9f29d276006afae1862ba5b7b1a..0000000000000000000000000000000000000000
--- a/channels/chan_local.c
+++ /dev/null
@@ -1,1452 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 1999 - 2005, Digium, Inc.
- *
- * Mark Spencer <markster@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
- *
- * \author Mark Spencer <markster@digium.com>
- *
- * \brief Local Proxy Channel
- *
- * \ingroup channel_drivers
- */
-
-/*** MODULEINFO
-	<support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include <fcntl.h>
-#include <sys/signal.h>
-
-#include "asterisk/lock.h"
-#include "asterisk/causes.h"
-#include "asterisk/channel.h"
-#include "asterisk/config.h"
-#include "asterisk/module.h"
-#include "asterisk/pbx.h"
-#include "asterisk/sched.h"
-#include "asterisk/io.h"
-#include "asterisk/acl.h"
-#include "asterisk/callerid.h"
-#include "asterisk/file.h"
-#include "asterisk/cli.h"
-#include "asterisk/app.h"
-#include "asterisk/musiconhold.h"
-#include "asterisk/manager.h"
-#include "asterisk/stringfields.h"
-#include "asterisk/devicestate.h"
-#include "asterisk/astobj2.h"
-
-/*** DOCUMENTATION
-	<manager name="LocalOptimizeAway" language="en_US">
-		<synopsis>
-			Optimize away a local channel when possible.
-		</synopsis>
-		<syntax>
-			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
-			<parameter name="Channel" required="true">
-				<para>The channel name to optimize away.</para>
-			</parameter>
-		</syntax>
-		<description>
-			<para>A local channel created with "/n" will not automatically optimize away.
-			Calling this command on the local channel will clear that flag and allow
-			it to optimize away if it's bridged or when it becomes bridged.</para>
-		</description>
-	</manager>
- ***/
-
-static const char tdesc[] = "Local Proxy Channel Driver";
-
-#define IS_OUTBOUND(a,b) (a == b->chan ? 1 : 0)
-
-static struct ao2_container *locals;
-
-static unsigned int name_sequence = 0;
-
-static struct ast_jb_conf g_jb_conf = {
-	.flags = 0,
-	.max_size = -1,
-	.resync_threshold = -1,
-	.impl = "",
-	.target_extra = -1,
-};
-
-static struct ast_channel *local_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause);
-static int local_digit_begin(struct ast_channel *ast, char digit);
-static int local_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
-static int local_call(struct ast_channel *ast, const char *dest, int timeout);
-static int local_hangup(struct ast_channel *ast);
-static int local_answer(struct ast_channel *ast);
-static struct ast_frame *local_read(struct ast_channel *ast);
-static int local_write(struct ast_channel *ast, struct ast_frame *f);
-static int local_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
-static int local_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
-static int local_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen);
-static int local_sendtext(struct ast_channel *ast, const char *text);
-static int local_devicestate(const char *data);
-static struct ast_channel *local_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge);
-static int local_queryoption(struct ast_channel *ast, int option, void *data, int *datalen);
-static int local_setoption(struct ast_channel *chan, int option, void *data, int datalen);
-
-/* PBX interface structure for channel registration */
-static struct ast_channel_tech local_tech = {
-	.type = "Local",
-	.description = tdesc,
-	.requester = local_request,
-	.send_digit_begin = local_digit_begin,
-	.send_digit_end = local_digit_end,
-	.call = local_call,
-	.hangup = local_hangup,
-	.answer = local_answer,
-	.read = local_read,
-	.write = local_write,
-	.write_video = local_write,
-	.exception = local_read,
-	.indicate = local_indicate,
-	.fixup = local_fixup,
-	.send_html = local_sendhtml,
-	.send_text = local_sendtext,
-	.devicestate = local_devicestate,
-	.bridged_channel = local_bridgedchannel,
-	.queryoption = local_queryoption,
-	.setoption = local_setoption,
-};
-
-/*!
- * \brief the local pvt structure for all channels
- *
- * The local channel pvt has two ast_chan objects - the "owner" and the "next channel", the outbound channel
- *
- * ast_chan owner -> local_pvt -> ast_chan chan -> yet-another-pvt-depending-on-channel-type
- */
-struct local_pvt {
-	struct ast_channel *owner;      /*!< Master Channel - Bridging happens here */
-	struct ast_channel *chan;       /*!< Outbound channel - PBX is run here */
-	struct ast_format_cap *reqcap;  /*!< Requested format capabilities */
-	struct ast_jb_conf jb_conf;     /*!< jitterbuffer configuration for this local channel */
-	unsigned int flags;             /*!< Private flags */
-	char context[AST_MAX_CONTEXT];  /*!< Context to call */
-	char exten[AST_MAX_EXTENSION];  /*!< Extension to call */
-};
-
-#define LOCAL_ALREADY_MASQED  (1 << 0) /*!< Already masqueraded */
-#define LOCAL_LAUNCHED_PBX    (1 << 1) /*!< PBX was launched */
-#define LOCAL_NO_OPTIMIZATION (1 << 2) /*!< Do not optimize using masquerading */
-#define LOCAL_BRIDGE          (1 << 3) /*!< Report back the "true" channel as being bridged to */
-#define LOCAL_MOH_PASSTHRU    (1 << 4) /*!< Pass through music on hold start/stop frames */
-
-/*!
- * \brief Send a pvt in with no locks held and get all locks
- *
- * \note NO locks should be held prior to calling this function
- * \note The pvt must have a ref held before calling this function
- * \note if outchan or outowner is set != NULL after calling this function
- *       those channels are locked and reffed.
- * \note Batman.
- */
-static void awesome_locking(struct local_pvt *p, struct ast_channel **outchan, struct ast_channel **outowner)
-{
-	struct ast_channel *chan = NULL;
-	struct ast_channel *owner = NULL;
-
-	ao2_lock(p);
-	for (;;) {
-		if (p->chan) {
-			chan = p->chan;
-			ast_channel_ref(chan);
-		}
-		if (p->owner) {
-			owner = p->owner;
-			ast_channel_ref(owner);
-		}
-		ao2_unlock(p);
-
-		/* if we don't have both channels, then this is very easy */
-		if (!owner || !chan) {
-			if (owner) {
-				ast_channel_lock(owner);
-			} else if(chan) {
-				ast_channel_lock(chan);
-			}
-		} else {
-			/* lock both channels first, then get the pvt lock */
-			ast_channel_lock_both(chan, owner);
-		}
-		ao2_lock(p);
-
-		/* Now that we have all the locks, validate that nothing changed */
-		if (p->owner != owner || p->chan != chan) {
-			if (owner) {
-				ast_channel_unlock(owner);
-				owner = ast_channel_unref(owner);
-			}
-			if (chan) {
-				ast_channel_unlock(chan);
-				chan = ast_channel_unref(chan);
-			}
-			continue;
-		}
-
-		break;
-	}
-	*outowner = p->owner;
-	*outchan = p->chan;
-}
-
-/* Called with ast locked */
-static int local_setoption(struct ast_channel *ast, int option, void *data, int datalen)
-{
-	int res = 0;
-	struct local_pvt *p;
-	struct ast_channel *otherchan = NULL;
-	ast_chan_write_info_t *write_info;
-
-	if (option != AST_OPTION_CHANNEL_WRITE) {
-		return -1;
-	}
-
-	write_info = data;
-
-	if (write_info->version != AST_CHAN_WRITE_INFO_T_VERSION) {
-		ast_log(LOG_ERROR, "The chan_write_info_t type has changed, and this channel hasn't been updated!\n");
-		return -1;
-	}
-
-	if (!strcmp(write_info->function, "CHANNEL")
-		&& !strncasecmp(write_info->data, "hangup_handler_", 15)) {
-		/* Block CHANNEL(hangup_handler_xxx) writes to the other local channel. */
-		return 0;
-	}
-
-	/* get the tech pvt */
-	if (!(p = ast_channel_tech_pvt(ast))) {
-		return -1;
-	}
-	ao2_ref(p, 1);
-	ast_channel_unlock(ast); /* Held when called, unlock before locking another channel */
-
-	/* get the channel we are supposed to write to */
-	ao2_lock(p);
-	otherchan = (write_info->chan == p->owner) ? p->chan : p->owner;
-	if (!otherchan || otherchan == write_info->chan) {
-		res = -1;
-		otherchan = NULL;
-		ao2_unlock(p);
-		goto setoption_cleanup;
-	}
-	ast_channel_ref(otherchan);
-
-	/* clear the pvt lock before grabbing the channel */
-	ao2_unlock(p);
-
-	ast_channel_lock(otherchan);
-	res = write_info->write_fn(otherchan, write_info->function, write_info->data, write_info->value);
-	ast_channel_unlock(otherchan);
-
-setoption_cleanup:
-	ao2_ref(p, -1);
-	if (otherchan) {
-		ast_channel_unref(otherchan);
-	}
-	ast_channel_lock(ast); /* Lock back before we leave */
-	return res;
-}
-
-/*! \brief Adds devicestate to local channels */
-static int local_devicestate(const char *data)
-{
-	char *exten = ast_strdupa(data);
-	char *context;
-	char *opts;
-	int res;
-	struct local_pvt *lp;
-	struct ao2_iterator it;
-
-	/* Strip options if they exist */
-	opts = strchr(exten, '/');
-	if (opts) {
-		*opts = '\0';
-	}
-
-	context = strchr(exten, '@');
-	if (!context) {
-		ast_log(LOG_WARNING,
-			"Someone used Local/%s somewhere without a @context. This is bad.\n", data);
-		return AST_DEVICE_INVALID;
-	}
-	*context++ = '\0';
-
-	ast_debug(3, "Checking if extension %s@%s exists (devicestate)\n", exten, context);
-	res = ast_exists_extension(NULL, context, exten, 1, NULL);
-	if (!res) {
-		return AST_DEVICE_INVALID;
-	}
-
-	res = AST_DEVICE_NOT_INUSE;
-
-	it = ao2_iterator_init(locals, 0);
-	for (; (lp = ao2_iterator_next(&it)); ao2_ref(lp, -1)) {
-		int is_inuse;
-
-		ao2_lock(lp);
-		is_inuse = !strcmp(exten, lp->exten)
-			&& !strcmp(context, lp->context)
-			&& lp->owner
-			&& ast_test_flag(lp, LOCAL_LAUNCHED_PBX);
-		ao2_unlock(lp);
-		if (is_inuse) {
-			res = AST_DEVICE_INUSE;
-			ao2_ref(lp, -1);
-			break;
-		}
-	}
-	ao2_iterator_destroy(&it);
-
-	return res;
-}
-
-/*! \brief Return the bridged channel of a Local channel */
-static struct ast_channel *local_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge)
-{
-	struct local_pvt *p = ast_channel_tech_pvt(bridge);
-	struct ast_channel *bridged = bridge;
-
-	if (!p) {
-		ast_debug(1, "Asked for bridged channel on '%s'/'%s', returning <none>\n",
-			ast_channel_name(chan), ast_channel_name(bridge));
-		return NULL;
-	}
-
-	ao2_lock(p);
-
-	if (ast_test_flag(p, LOCAL_BRIDGE)) {
-		/* Find the opposite channel */
-		bridged = (bridge == p->owner ? p->chan : p->owner);
-
-		/* Now see if the opposite channel is bridged to anything */
-		if (!bridged) {
-			bridged = bridge;
-		} else if (ast_channel_internal_bridged_channel(bridged)) {
-			bridged = ast_channel_internal_bridged_channel(bridged);
-		}
-	}
-
-	ao2_unlock(p);
-
-	return bridged;
-}
-
-/* Called with ast locked */
-static int local_queryoption(struct ast_channel *ast, int option, void *data, int *datalen)
-{
-	struct local_pvt *p;
-	struct ast_channel *bridged = NULL;
-	struct ast_channel *tmp = NULL;
-	int res = 0;
-
-	if (option != AST_OPTION_T38_STATE) {
-		/* AST_OPTION_T38_STATE is the only supported option at this time */
-		return -1;
-	}
-
-	/* for some reason the channel is not locked in channel.c when this function is called */
-	if (!(p = ast_channel_tech_pvt(ast))) {
-		return -1;
-	}
-
-	ao2_lock(p);
-	if (!(tmp = IS_OUTBOUND(ast, p) ? p->owner : p->chan)) {
-		ao2_unlock(p);
-		return -1;
-	}
-	ast_channel_ref(tmp);
-	ao2_unlock(p);
-	ast_channel_unlock(ast); /* Held when called, unlock before locking another channel */
-
-	ast_channel_lock(tmp);
-	if (!(bridged = ast_bridged_channel(tmp))) {
-		res = -1;
-		ast_channel_unlock(tmp);
-		goto query_cleanup;
-	}
-	ast_channel_ref(bridged);
-	ast_channel_unlock(tmp);
-
-query_cleanup:
-	if (bridged) {
-		res = ast_channel_queryoption(bridged, option, data, datalen, 0);
-		bridged = ast_channel_unref(bridged);
-	}
-	if (tmp) {
-		tmp = ast_channel_unref(tmp);
-	}
-	ast_channel_lock(ast); /* Lock back before we leave */
-
-	return res;
-}
-
-/*!
- * \brief queue a frame onto either the p->owner or p->chan
- *
- * \note the local_pvt MUST have it's ref count bumped before entering this function and
- * decremented after this function is called.  This is a side effect of the deadlock
- * avoidance that is necessary to lock 2 channels and a tech_pvt.  Without a ref counted
- * local_pvt, it is impossible to guarantee it will not be destroyed by another thread
- * during deadlock avoidance.
- */
-static int local_queue_frame(struct local_pvt *p, int isoutbound, struct ast_frame *f,
-	struct ast_channel *us, int us_locked)
-{
-	struct ast_channel *other = NULL;
-
-	/* Recalculate outbound channel */
-	other = isoutbound ? p->owner : p->chan;
-	if (!other) {
-		return 0;
-	}
-
-	/* do not queue frame if generator is on both local channels */
-	if (us && ast_channel_generator(us) && ast_channel_generator(other)) {
-		return 0;
-	}
-
-	/* grab a ref on the channel before unlocking the pvt,
-	 * other can not go away from us now regardless of locking */
-	ast_channel_ref(other);
-	if (us && us_locked) {
-		ast_channel_unlock(us);
-	}
-	ao2_unlock(p);
-
-	if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_RINGING) {
-		ast_setstate(other, AST_STATE_RINGING);
-	}
-	ast_queue_frame(other, f);
-
-	other = ast_channel_unref(other);
-	if (us && us_locked) {
-		ast_channel_lock(us);
-	}
-	ao2_lock(p);
-
-	return 0;
-}
-
-static int local_answer(struct ast_channel *ast)
-{
-	struct local_pvt *p = ast_channel_tech_pvt(ast);
-	int isoutbound;
-	int res = -1;
-
-	if (!p) {
-		return -1;
-	}
-
-	ao2_ref(p, 1);
-	ao2_lock(p);
-	isoutbound = IS_OUTBOUND(ast, p);
-	if (isoutbound) {
-		/* Pass along answer since somebody answered us */
-		struct ast_frame answer = { AST_FRAME_CONTROL, { AST_CONTROL_ANSWER } };
-
-		res = local_queue_frame(p, isoutbound, &answer, ast, 1);
-	} else {
-		ast_log(LOG_WARNING, "Huh?  Local is being asked to answer?\n");
-	}
-	ao2_unlock(p);
-	ao2_ref(p, -1);
-	return res;
-}
-
-/*!
- * \internal
- * \note This function assumes that we're only called from the "outbound" local channel side
- *
- * \note it is assummed p is locked and reffed before entering this function
- */
-static void check_bridge(struct ast_channel *ast, struct local_pvt *p)
-{
-	struct ast_channel *owner;
-	struct ast_channel *chan;
-	struct ast_channel *bridged_chan;
-	struct ast_frame *f;
-
-	/* Do a few conditional checks early on just to see if this optimization is possible */
-	if (ast_test_flag(p, LOCAL_NO_OPTIMIZATION | LOCAL_ALREADY_MASQED)
-		|| !p->chan || !p->owner) {
-		return;
-	}
-
-	/* Safely get the channel bridged to p->chan */
-	chan = ast_channel_ref(p->chan);
-
-	ao2_unlock(p); /* don't call bridged channel with the pvt locked */
-	bridged_chan = ast_bridged_channel(chan);
-	ao2_lock(p);
-
-	chan = ast_channel_unref(chan);
-
-	/* since we had to unlock p to get the bridged chan, validate our
-	 * data once again and verify the bridged channel is what we expect
-	 * it to be in order to perform this optimization */
-	if (ast_test_flag(p, LOCAL_NO_OPTIMIZATION | LOCAL_ALREADY_MASQED)
-		|| !p->chan || !p->owner
-		|| (ast_channel_internal_bridged_channel(p->chan) != bridged_chan)) {
-		return;
-	}
-
-	/* only do the masquerade if we are being called on the outbound channel,
-	   if it has been bridged to another channel and if there are no pending
-	   frames on the owner channel (because they would be transferred to the
-	   outbound channel during the masquerade)
-	*/
-	if (!ast_channel_internal_bridged_channel(p->chan) /* Not ast_bridged_channel!  Only go one step! */
-		|| !AST_LIST_EMPTY(ast_channel_readq(p->owner))
-		|| ast != p->chan /* Sanity check (should always be false) */) {
-		return;
-	}
-
-	/* Masquerade bridged channel into owner */
-	/* Lock everything we need, one by one, and give up if
-	   we can't get everything.  Remember, we'll get another
-	   chance in just a little bit */
-	if (ast_channel_trylock(ast_channel_internal_bridged_channel(p->chan))) {
-		return;
-	}
-	if (ast_check_hangup(ast_channel_internal_bridged_channel(p->chan))
-		|| ast_channel_trylock(p->owner)) {
-		ast_channel_unlock(ast_channel_internal_bridged_channel(p->chan));
-		return;
-	}
-
-	/*
-	 * At this point we have 4 locks:
-	 * p, p->chan (same as ast), p->chan->_bridge, p->owner
-	 *
-	 * Flush a voice or video frame on the outbound channel to make
-	 * the queue empty faster so we can get optimized out.
-	 */
-	f = AST_LIST_FIRST(ast_channel_readq(p->chan));
-	if (f && (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO)) {
-		AST_LIST_REMOVE_HEAD(ast_channel_readq(p->chan), frame_list);
-		ast_frfree(f);
-		f = AST_LIST_FIRST(ast_channel_readq(p->chan));
-	}
-
-	if (f
-		|| ast_check_hangup(p->owner)
-		|| ast_channel_masquerade(p->owner, ast_channel_internal_bridged_channel(p->chan))) {
-		ast_channel_unlock(p->owner);
-		ast_channel_unlock(ast_channel_internal_bridged_channel(p->chan));
-		return;
-	}
-
-	/* Masquerade got setup. */
-	ast_debug(4, "Masquerading %s <- %s\n",
-		ast_channel_name(p->owner),
-		ast_channel_name(ast_channel_internal_bridged_channel(p->chan)));
-	if (ast_channel_monitor(p->owner)
-		&& !ast_channel_monitor(ast_channel_internal_bridged_channel(p->chan))) {
-		struct ast_channel_monitor *tmp;
-
-		/* If a local channel is being monitored, we don't want a masquerade
-		 * to cause the monitor to go away. Since the masquerade swaps the monitors,
-		 * pre-swapping the monitors before the masquerade will ensure that the monitor
-		 * ends up where it is expected.
-		 */
-		tmp = ast_channel_monitor(p->owner);
-		ast_channel_monitor_set(p->owner, ast_channel_monitor(ast_channel_internal_bridged_channel(p->chan)));
-		ast_channel_monitor_set(ast_channel_internal_bridged_channel(p->chan), tmp);
-	}
-	if (ast_channel_audiohooks(p->chan)) {
-		struct ast_audiohook_list *audiohooks_swapper;
-
-		audiohooks_swapper = ast_channel_audiohooks(p->chan);
-		ast_channel_audiohooks_set(p->chan, ast_channel_audiohooks(p->owner));
-		ast_channel_audiohooks_set(p->owner, audiohooks_swapper);
-	}
-
-	/* If any Caller ID was set, preserve it after masquerade like above. We must check
-	 * to see if Caller ID was set because otherwise we'll mistakingly copy info not
-	 * set from the dialplan and will overwrite the real channel Caller ID. The reason
-	 * for this whole preswapping action is because the Caller ID is set on the channel
-	 * thread (which is the to be masqueraded away local channel) before both local
-	 * channels are optimized away.
-	 */
-	if (ast_channel_caller(p->owner)->id.name.valid || ast_channel_caller(p->owner)->id.number.valid
-		|| ast_channel_caller(p->owner)->id.subaddress.valid || ast_channel_caller(p->owner)->ani.name.valid
-		|| ast_channel_caller(p->owner)->ani.number.valid || ast_channel_caller(p->owner)->ani.subaddress.valid) {
-		SWAP(*ast_channel_caller(p->owner), *ast_channel_caller(ast_channel_internal_bridged_channel(p->chan)));
-	}
-	if (ast_channel_redirecting(p->owner)->from.name.valid || ast_channel_redirecting(p->owner)->from.number.valid
-		|| ast_channel_redirecting(p->owner)->from.subaddress.valid || ast_channel_redirecting(p->owner)->to.name.valid
-		|| ast_channel_redirecting(p->owner)->to.number.valid || ast_channel_redirecting(p->owner)->to.subaddress.valid) {
-		SWAP(*ast_channel_redirecting(p->owner), *ast_channel_redirecting(ast_channel_internal_bridged_channel(p->chan)));
-	}
-	if (ast_channel_dialed(p->owner)->number.str || ast_channel_dialed(p->owner)->subaddress.valid) {
-		SWAP(*ast_channel_dialed(p->owner), *ast_channel_dialed(ast_channel_internal_bridged_channel(p->chan)));
-	}
-	ast_app_group_update(p->chan, p->owner);
-	ast_set_flag(p, LOCAL_ALREADY_MASQED);
-
-	ast_channel_unlock(p->owner);
-	ast_channel_unlock(ast_channel_internal_bridged_channel(p->chan));
-
-	/* Do the masquerade now. */
-	owner = ast_channel_ref(p->owner);
-	ao2_unlock(p);
-	ast_channel_unlock(ast);
-	ast_do_masquerade(owner);
-	ast_channel_unref(owner);
-	ast_channel_lock(ast);
-	ao2_lock(p);
-}
-
-static struct ast_frame  *local_read(struct ast_channel *ast)
-{
-	return &ast_null_frame;
-}
-
-static int local_write(struct ast_channel *ast, struct ast_frame *f)
-{
-	struct local_pvt *p = ast_channel_tech_pvt(ast);
-	int res = -1;
-	int isoutbound;
-
-	if (!p) {
-		return -1;
-	}
-
-	/* Just queue for delivery to the other side */
-	ao2_ref(p, 1); /* ref for local_queue_frame */
-	ao2_lock(p);
-	isoutbound = IS_OUTBOUND(ast, p);
-
-	if (isoutbound
-		&& (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO)) {
-		check_bridge(ast, p);
-	}
-
-	if (!ast_test_flag(p, LOCAL_ALREADY_MASQED)) {
-		res = local_queue_frame(p, isoutbound, f, ast, 1);
-	} else {
-		ast_debug(1, "Not posting to '%s' queue since already masqueraded out\n",
-			ast_channel_name(ast));
-		res = 0;
-	}
-	ao2_unlock(p);
-	ao2_ref(p, -1);
-
-	return res;
-}
-
-static int local_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
-{
-	struct local_pvt *p = ast_channel_tech_pvt(newchan);
-
-	if (!p) {
-		return -1;
-	}
-
-	ao2_lock(p);
-
-	if ((p->owner != oldchan) && (p->chan != oldchan)) {
-		ast_log(LOG_WARNING, "Old channel %p wasn't %p or %p\n", oldchan, p->owner, p->chan);
-		ao2_unlock(p);
-		return -1;
-	}
-	if (p->owner == oldchan) {
-		p->owner = newchan;
-	} else {
-		p->chan = newchan;
-	}
-
-	/* Do not let a masquerade cause a Local channel to be bridged to itself! */
-	if (!ast_check_hangup(newchan)
-		&& ((p->owner && ast_channel_internal_bridged_channel(p->owner) == p->chan)
-			|| (p->chan && ast_channel_internal_bridged_channel(p->chan) == p->owner))) {
-		ast_log(LOG_WARNING, "You can not bridge a Local channel to itself!\n");
-		ao2_unlock(p);
-		ast_queue_hangup(newchan);
-		return -1;
-	}
-
-	ao2_unlock(p);
-	return 0;
-}
-
-static int local_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
-{
-	struct local_pvt *p = ast_channel_tech_pvt(ast);
-	int res = 0;
-	struct ast_frame f = { AST_FRAME_CONTROL, };
-	int isoutbound;
-
-	if (!p) {
-		return -1;
-	}
-
-	ao2_ref(p, 1); /* ref for local_queue_frame */
-
-	/* If this is an MOH hold or unhold, do it on the Local channel versus real channel */
-	if (!ast_test_flag(p, LOCAL_MOH_PASSTHRU) && condition == AST_CONTROL_HOLD) {
-		ast_moh_start(ast, data, NULL);
-	} else if (!ast_test_flag(p, LOCAL_MOH_PASSTHRU) && condition == AST_CONTROL_UNHOLD) {
-		ast_moh_stop(ast);
-	} else if (condition == AST_CONTROL_CONNECTED_LINE || condition == AST_CONTROL_REDIRECTING) {
-		struct ast_channel *this_channel;
-		struct ast_channel *the_other_channel;
-
-		/* A connected line update frame may only contain a partial amount of data, such
-		 * as just a source, or just a ton, and not the full amount of information. However,
-		 * the collected information is all stored in the outgoing channel's connectedline
-		 * structure, so when receiving a connected line update on an outgoing local channel,
-		 * we need to transmit the collected connected line information instead of whatever
-		 * happens to be in this control frame. The same applies for redirecting information, which
-		 * is why it is handled here as well.*/
-		ao2_lock(p);
-		isoutbound = IS_OUTBOUND(ast, p);
-		if (isoutbound) {
-			this_channel = p->chan;
-			the_other_channel = p->owner;
-		} else {
-			this_channel = p->owner;
-			the_other_channel = p->chan;
-		}
-		if (the_other_channel) {
-			unsigned char frame_data[1024];
-
-			if (condition == AST_CONTROL_CONNECTED_LINE) {
-				if (isoutbound) {
-					ast_connected_line_copy_to_caller(ast_channel_caller(the_other_channel), ast_channel_connected(this_channel));
-				}
-				f.datalen = ast_connected_line_build_data(frame_data, sizeof(frame_data), ast_channel_connected(this_channel), NULL);
-			} else {
-				f.datalen = ast_redirecting_build_data(frame_data, sizeof(frame_data), ast_channel_redirecting(this_channel), NULL);
-			}
-			f.subclass.integer = condition;
-			f.data.ptr = frame_data;
-			res = local_queue_frame(p, isoutbound, &f, ast, 1);
-		}
-		ao2_unlock(p);
-	} else {
-		/* Queue up a frame representing the indication as a control frame */
-		ao2_lock(p);
-		/*
-		 * Block -1 stop tones events if we are to be optimized out.  We
-		 * don't need a flurry of these events on a local channel chain
-		 * when initially connected to slow the optimization process.
-		 */
-		if (0 <= condition || ast_test_flag(p, LOCAL_NO_OPTIMIZATION)) {
-			isoutbound = IS_OUTBOUND(ast, p);
-			f.subclass.integer = condition;
-			f.data.ptr = (void *) data;
-			f.datalen = datalen;
-			res = local_queue_frame(p, isoutbound, &f, ast, 1);
-
-			if (!res && condition == AST_CONTROL_T38_PARAMETERS
-				&& datalen == sizeof(struct ast_control_t38_parameters)) {
-				const struct ast_control_t38_parameters *parameters = data;
-
-				if (parameters->request_response == AST_T38_REQUEST_PARMS) {
-					res = AST_T38_REQUEST_PARMS;
-				}
-			}
-		} else {
-			ast_debug(4, "Blocked indication %d\n", condition);
-		}
-		ao2_unlock(p);
-	}
-
-	ao2_ref(p, -1);
-	return res;
-}
-
-static int local_digit_begin(struct ast_channel *ast, char digit)
-{
-	struct local_pvt *p = ast_channel_tech_pvt(ast);
-	int res = -1;
-	struct ast_frame f = { AST_FRAME_DTMF_BEGIN, };
-	int isoutbound;
-
-	if (!p) {
-		return -1;
-	}
-
-	ao2_ref(p, 1); /* ref for local_queue_frame */
-	ao2_lock(p);
-	isoutbound = IS_OUTBOUND(ast, p);
-	f.subclass.integer = digit;
-	res = local_queue_frame(p, isoutbound, &f, ast, 0);
-	ao2_unlock(p);
-	ao2_ref(p, -1);
-
-	return res;
-}
-
-static int local_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
-{
-	struct local_pvt *p = ast_channel_tech_pvt(ast);
-	int res = -1;
-	struct ast_frame f = { AST_FRAME_DTMF_END, };
-	int isoutbound;
-
-	if (!p) {
-		return -1;
-	}
-
-	ao2_ref(p, 1); /* ref for local_queue_frame */
-	ao2_lock(p);
-	isoutbound = IS_OUTBOUND(ast, p);
-	f.subclass.integer = digit;
-	f.len = duration;
-	res = local_queue_frame(p, isoutbound, &f, ast, 0);
-	ao2_unlock(p);
-	ao2_ref(p, -1);
-
-	return res;
-}
-
-static int local_sendtext(struct ast_channel *ast, const char *text)
-{
-	struct local_pvt *p = ast_channel_tech_pvt(ast);
-	int res = -1;
-	struct ast_frame f = { AST_FRAME_TEXT, };
-	int isoutbound;
-
-	if (!p) {
-		return -1;
-	}
-
-	ao2_ref(p, 1); /* ref for local_queue_frame */
-	ao2_lock(p);
-	isoutbound = IS_OUTBOUND(ast, p);
-	f.data.ptr = (char *) text;
-	f.datalen = strlen(text) + 1;
-	res = local_queue_frame(p, isoutbound, &f, ast, 0);
-	ao2_unlock(p);
-	ao2_ref(p, -1);
-	return res;
-}
-
-static int local_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen)
-{
-	struct local_pvt *p = ast_channel_tech_pvt(ast);
-	int res = -1;
-	struct ast_frame f = { AST_FRAME_HTML, };
-	int isoutbound;
-
-	if (!p) {
-		return -1;
-	}
-
-	ao2_ref(p, 1); /* ref for local_queue_frame */
-	ao2_lock(p);
-	isoutbound = IS_OUTBOUND(ast, p);
-	f.subclass.integer = subclass;
-	f.data.ptr = (char *)data;
-	f.datalen = datalen;
-	res = local_queue_frame(p, isoutbound, &f, ast, 0);
-	ao2_unlock(p);
-	ao2_ref(p, -1);
-
-	return res;
-}
-
-/*! \brief Initiate new call, part of PBX interface
- *         dest is the dial string */
-static int local_call(struct ast_channel *ast, const char *dest, int timeout)
-{
-	struct local_pvt *p = ast_channel_tech_pvt(ast);
-	int pvt_locked = 0;
-
-	struct ast_channel *owner = NULL;
-	struct ast_channel *chan = NULL;
-	int res;
-	struct ast_var_t *varptr;
-	struct ast_var_t *clone_var;
-	char *reduced_dest = ast_strdupa(dest);
-	char *slash;
-	const char *exten;
-	const char *context;
-
-	if (!p) {
-		return -1;
-	}
-
-	/* since we are letting go of channel locks that were locked coming into
-	 * this function, then we need to give the tech pvt a ref */
-	ao2_ref(p, 1);
-	ast_channel_unlock(ast);
-
-	awesome_locking(p, &chan, &owner);
-	pvt_locked = 1;
-
-	if (owner != ast) {
-		res = -1;
-		goto return_cleanup;
-	}
-
-	if (!owner || !chan) {
-		res = -1;
-		goto return_cleanup;
-	}
-
-	/*
-	 * Note that cid_num and cid_name aren't passed in the ast_channel_alloc
-	 * call, so it's done here instead.
-	 *
-	 * All these failure points just return -1. The individual strings will
-	 * be cleared when we destroy the channel.
-	 */
-	ast_party_redirecting_copy(ast_channel_redirecting(chan), ast_channel_redirecting(owner));
-
-	ast_party_dialed_copy(ast_channel_dialed(chan), ast_channel_dialed(owner));
-
-	ast_connected_line_copy_to_caller(ast_channel_caller(chan), ast_channel_connected(owner));
-	ast_connected_line_copy_from_caller(ast_channel_connected(chan), ast_channel_caller(owner));
-
-	ast_channel_language_set(chan, ast_channel_language(owner));
-	ast_channel_accountcode_set(chan, ast_channel_accountcode(owner));
-	ast_channel_musicclass_set(chan, ast_channel_musicclass(owner));
-	ast_cdr_update(chan);
-
-	ast_channel_cc_params_init(chan, ast_channel_get_cc_config_params(owner));
-
-	/* Make sure we inherit the AST_CAUSE_ANSWERED_ELSEWHERE if it's set on the queue/dial call request in the dialplan */
-	if (ast_channel_hangupcause(ast) == AST_CAUSE_ANSWERED_ELSEWHERE) {
-		ast_channel_hangupcause_set(chan, AST_CAUSE_ANSWERED_ELSEWHERE);
-	}
-
-	/* copy the channel variables from the incoming channel to the outgoing channel */
-	/* Note that due to certain assumptions, they MUST be in the same order */
-	AST_LIST_TRAVERSE(ast_channel_varshead(owner), varptr, entries) {
-		clone_var = ast_var_assign(varptr->name, varptr->value);
-		if (clone_var) {
-			AST_LIST_INSERT_TAIL(ast_channel_varshead(chan), clone_var, entries);
-		}
-	}
-	ast_channel_datastore_inherit(owner, chan);
-	/* If the local channel has /n or /b on the end of it,
-	 * we need to lop that off for our argument to setting
-	 * up the CC_INTERFACES variable
-	 */
-	if ((slash = strrchr(reduced_dest, '/'))) {
-		*slash = '\0';
-	}
-	ast_set_cc_interfaces_chanvar(chan, reduced_dest);
-
-	exten = ast_strdupa(ast_channel_exten(chan));
-	context = ast_strdupa(ast_channel_context(chan));
-
-	ao2_unlock(p);
-	pvt_locked = 0;
-
-	ast_channel_unlock(chan);
-
-	if (!ast_exists_extension(chan, context, exten, 1,
-		S_COR(ast_channel_caller(owner)->id.number.valid, ast_channel_caller(owner)->id.number.str, NULL))) {
-		ast_log(LOG_NOTICE, "No such extension/context %s@%s while calling Local channel\n", exten, context);
-		res = -1;
-		chan = ast_channel_unref(chan); /* we already unlocked it, so clear it here so the cleanup label won't touch it. */
-		goto return_cleanup;
-	}
-
-	/*** DOCUMENTATION
-		<managerEventInstance>
-			<synopsis>Raised when two halves of a Local Channel form a bridge.</synopsis>
-			<syntax>
-				<parameter name="Channel1">
-					<para>The name of the Local Channel half that bridges to another channel.</para>
-				</parameter>
-				<parameter name="Channel2">
-					<para>The name of the Local Channel half that executes the dialplan.</para>
-				</parameter>
-				<parameter name="Context">
-					<para>The context in the dialplan that Channel2 starts in.</para>
-				</parameter>
-				<parameter name="Exten">
-					<para>The extension in the dialplan that Channel2 starts in.</para>
-				</parameter>
-				<parameter name="LocalOptimization">
-					<enumlist>
-						<enum name="Yes"/>
-						<enum name="No"/>
-					</enumlist>
-				</parameter>
-			</syntax>
-		</managerEventInstance>
-	***/
-	manager_event(EVENT_FLAG_CALL, "LocalBridge",
-		"Channel1: %s\r\n"
-		"Channel2: %s\r\n"
-		"Uniqueid1: %s\r\n"
-		"Uniqueid2: %s\r\n"
-		"Context: %s\r\n"
-		"Exten: %s\r\n"
-		"LocalOptimization: %s\r\n",
-		ast_channel_name(p->owner), ast_channel_name(p->chan),
-		ast_channel_uniqueid(p->owner), ast_channel_uniqueid(p->chan),
-		p->context, p->exten,
-		ast_test_flag(p, LOCAL_NO_OPTIMIZATION) ? "Yes" : "No");
-
-
-	/* Start switch on sub channel */
-	res = ast_pbx_start(chan);
-	if (!res) {
-		ao2_lock(p);
-		ast_set_flag(p, LOCAL_LAUNCHED_PBX);
-		ao2_unlock(p);
-	}
-	chan = ast_channel_unref(chan); /* chan is already unlocked, clear it here so the cleanup lable won't touch it. */
-
-return_cleanup:
-	if (p) {
-		if (pvt_locked) {
-			ao2_unlock(p);
-		}
-		ao2_ref(p, -1);
-	}
-	if (chan) {
-		ast_channel_unlock(chan);
-		chan = ast_channel_unref(chan);
-	}
-
-	/* owner is supposed to be == to ast,  if it
-	 * is, don't unlock it because ast must exit locked */
-	if (owner) {
-		if (owner != ast) {
-			ast_channel_unlock(owner);
-			ast_channel_lock(ast);
-		}
-		owner = ast_channel_unref(owner);
-	} else {
-		/* we have to exit with ast locked */
-		ast_channel_lock(ast);
-	}
-
-	return res;
-}
-
-/*! \brief Hangup a call through the local proxy channel */
-static int local_hangup(struct ast_channel *ast)
-{
-	struct local_pvt *p = ast_channel_tech_pvt(ast);
-	int isoutbound;
-	int hangup_chan = 0;
-	int res = 0;
-	struct ast_frame f = { AST_FRAME_CONTROL, { AST_CONTROL_HANGUP }, .data.uint32 = ast_channel_hangupcause(ast) };
-	struct ast_channel *owner = NULL;
-	struct ast_channel *chan = NULL;
-
-	if (!p) {
-		return -1;
-	}
-
-	/* give the pvt a ref since we are unlocking the channel. */
-	ao2_ref(p, 1);
-
-	/* the pvt isn't going anywhere, we gave it a ref */
-	ast_channel_unlock(ast);
-
-	/* lock everything */
-	awesome_locking(p, &chan, &owner);
-
-	if (ast != chan && ast != owner) {
-		res = -1;
-		goto local_hangup_cleanup;
-	}
-
-	isoutbound = IS_OUTBOUND(ast, p); /* just comparing pointer of ast */
-
-	if (p->chan && ast_channel_hangupcause(ast) == AST_CAUSE_ANSWERED_ELSEWHERE) {
-		ast_channel_hangupcause_set(p->chan, AST_CAUSE_ANSWERED_ELSEWHERE);
-		ast_debug(2, "This local call has AST_CAUSE_ANSWERED_ELSEWHERE set.\n");
-	}
-
-	if (isoutbound) {
-		const char *status = pbx_builtin_getvar_helper(p->chan, "DIALSTATUS");
-
-		if (status && p->owner) {
-			ast_channel_hangupcause_set(p->owner, ast_channel_hangupcause(p->chan));
-			pbx_builtin_setvar_helper(p->owner, "CHANLOCALSTATUS", status);
-		}
-
-		ast_clear_flag(p, LOCAL_LAUNCHED_PBX);
-		p->chan = NULL;
-	} else {
-		if (p->chan) {
-			ast_queue_hangup(p->chan);
-		}
-		p->owner = NULL;
-	}
-
-	ast_channel_tech_pvt_set(ast, NULL); /* this is one of our locked channels, doesn't matter which */
-
-	if (!p->owner && !p->chan) {
-		ao2_unlock(p);
-
-		ao2_unlink(locals, p);
-		ao2_ref(p, -1);
-		p = NULL;
-		res = 0;
-		goto local_hangup_cleanup;
-	}
-	if (p->chan && !ast_test_flag(p, LOCAL_LAUNCHED_PBX)) {
-		/* Need to actually hangup since there is no PBX */
-		hangup_chan = 1;
-	} else {
-		local_queue_frame(p, isoutbound, &f, NULL, 0);
-	}
-
-local_hangup_cleanup:
-	if (p) {
-		ao2_unlock(p);
-		ao2_ref(p, -1);
-	}
-	if (owner) {
-		ast_channel_unlock(owner);
-		owner = ast_channel_unref(owner);
-	}
-	if (chan) {
-		ast_channel_unlock(chan);
-		if (hangup_chan) {
-			ast_hangup(chan);
-		}
-		chan = ast_channel_unref(chan);
-	}
-
-	/* leave with the same stupid channel locked that came in */
-	ast_channel_lock(ast);
-	return res;
-}
-
-/*!
- * \internal
- * \brief struct local_pvt destructor.
- *
- * \param vdoomed Void local_pvt to destroy.
- *
- * \return Nothing
- */
-static void local_pvt_destructor(void *vdoomed)
-{
-	struct local_pvt *doomed = vdoomed;
-
-	doomed->reqcap = ast_format_cap_destroy(doomed->reqcap);
-
-	ast_module_unref(ast_module_info->self);
-}
-
-/*! \brief Create a call structure */
-static struct local_pvt *local_alloc(const char *data, struct ast_format_cap *cap)
-{
-	struct local_pvt *tmp = NULL;
-	char *parse;
-	char *c = NULL;
-	char *opts = NULL;
-
-	if (!(tmp = ao2_alloc(sizeof(*tmp), local_pvt_destructor))) {
-		return NULL;
-	}
-	if (!(tmp->reqcap = ast_format_cap_dup(cap))) {
-		ao2_ref(tmp, -1);
-		return NULL;
-	}
-
-	ast_module_ref(ast_module_info->self);
-
-	/* Initialize private structure information */
-	parse = ast_strdupa(data);
-
-	memcpy(&tmp->jb_conf, &g_jb_conf, sizeof(tmp->jb_conf));
-
-	/* Look for options */
-	if ((opts = strchr(parse, '/'))) {
-		*opts++ = '\0';
-		if (strchr(opts, 'n'))
-			ast_set_flag(tmp, LOCAL_NO_OPTIMIZATION);
-		if (strchr(opts, 'j')) {
-			if (ast_test_flag(tmp, LOCAL_NO_OPTIMIZATION))
-				ast_set_flag(&tmp->jb_conf, AST_JB_ENABLED);
-			else {
-				ast_log(LOG_ERROR, "You must use the 'n' option with the 'j' option to enable the jitter buffer\n");
-			}
-		}
-		if (strchr(opts, 'b')) {
-			ast_set_flag(tmp, LOCAL_BRIDGE);
-		}
-		if (strchr(opts, 'm')) {
-			ast_set_flag(tmp, LOCAL_MOH_PASSTHRU);
-		}
-	}
-
-	/* Look for a context */
-	if ((c = strchr(parse, '@'))) {
-		*c++ = '\0';
-	}
-
-	ast_copy_string(tmp->context, c ? c : "default", sizeof(tmp->context));
-	ast_copy_string(tmp->exten, parse, sizeof(tmp->exten));
-
-	ao2_link(locals, tmp);
-
-	return tmp; /* this is returned with a ref */
-}
-
-/*! \brief Start new local channel */
-static struct ast_channel *local_new(struct local_pvt *p, int state, const char *linkedid, struct ast_callid *callid)
-{
-	struct ast_channel *owner;
-	struct ast_channel *chan;
-	struct ast_format fmt;
-	int generated_seqno = ast_atomic_fetchadd_int((int *)&name_sequence, +1);
-
-	/*
-	 * Allocate two new Asterisk channels
-	 *
-	 * Make sure that the ;2 channel gets the same linkedid as ;1.
-	 * You can't pass linkedid to both allocations since if linkedid
-	 * isn't set, then each channel will generate its own linkedid.
-	 */
-	if (!(owner = ast_channel_alloc(1, state, NULL, NULL, NULL,
-			p->exten, p->context, linkedid, 0,
-			"Local/%s@%s-%08x;1", p->exten, p->context, generated_seqno))
-		|| !(chan = ast_channel_alloc(1, AST_STATE_RING, NULL, NULL, NULL,
-			p->exten, p->context, ast_channel_linkedid(owner), 0,
-			"Local/%s@%s-%08x;2", p->exten, p->context, generated_seqno))) {
-		if (owner) {
-			owner = ast_channel_release(owner);
-		}
-		ast_log(LOG_WARNING, "Unable to allocate channel structure(s)\n");
-		return NULL;
-	}
-
-	if (callid) {
-		ast_channel_callid_set(owner, callid);
-		ast_channel_callid_set(chan, callid);
-	}
-
-	ast_channel_tech_set(owner, &local_tech);
-	ast_channel_tech_set(chan, &local_tech);
-	ast_channel_tech_pvt_set(owner, p);
-	ast_channel_tech_pvt_set(chan, p);
-
-	ast_format_cap_copy(ast_channel_nativeformats(owner), p->reqcap);
-	ast_format_cap_copy(ast_channel_nativeformats(chan), p->reqcap);
-
-	/* Determine our read/write format and set it on each channel */
-	ast_best_codec(p->reqcap, &fmt);
-	ast_format_copy(ast_channel_writeformat(owner), &fmt);
-	ast_format_copy(ast_channel_writeformat(chan), &fmt);
-	ast_format_copy(ast_channel_rawwriteformat(owner), &fmt);
-	ast_format_copy(ast_channel_rawwriteformat(chan), &fmt);
-	ast_format_copy(ast_channel_readformat(owner), &fmt);
-	ast_format_copy(ast_channel_readformat(chan), &fmt);
-	ast_format_copy(ast_channel_rawreadformat(owner), &fmt);
-	ast_format_copy(ast_channel_rawreadformat(chan), &fmt);
-
-	ast_set_flag(ast_channel_flags(owner), AST_FLAG_DISABLE_DEVSTATE_CACHE);
-	ast_set_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_DEVSTATE_CACHE);
-
-	p->owner = owner;
-	p->chan = chan;
-
-	ast_jb_configure(owner, &p->jb_conf);
-
-	return owner;
-}
-
-/*! \brief Part of PBX interface */
-static struct ast_channel *local_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause)
-{
-	struct local_pvt *p;
-	struct ast_channel *chan;
-	struct ast_callid *callid = ast_read_threadstorage_callid();
-
-	/* Allocate a new private structure and then Asterisk channel */
-	p = local_alloc(data, cap);
-	if (!p) {
-		chan = NULL;
-		goto local_request_end;
-	}
-	chan = local_new(p, AST_STATE_DOWN, requestor ? ast_channel_linkedid(requestor) : NULL, callid);
-	if (!chan) {
-		ao2_unlink(locals, p);
-	} else if (ast_channel_cc_params_init(chan, requestor ? ast_channel_get_cc_config_params((struct ast_channel *)requestor) : NULL)) {
-		ao2_unlink(locals, p);
-		p->owner = ast_channel_release(p->owner);
-		p->chan = ast_channel_release(p->chan);
-		chan = NULL;
-	}
-	ao2_ref(p, -1); /* kill the ref from the alloc */
-
-local_request_end:
-
-	if (callid) {
-		ast_callid_unref(callid);
-	}
-
-	return chan;
-}
-
-/*! \brief CLI command "local show channels" */
-static char *locals_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	struct local_pvt *p = NULL;
-	struct ao2_iterator it;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "local show channels";
-		e->usage =
-			"Usage: local show channels\n"
-			"       Provides summary information on active local proxy channels.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 3) {
-		return CLI_SHOWUSAGE;
-	}
-
-	if (ao2_container_count(locals) == 0) {
-		ast_cli(a->fd, "No local channels in use\n");
-		return RESULT_SUCCESS;
-	}
-
-	it = ao2_iterator_init(locals, 0);
-	while ((p = ao2_iterator_next(&it))) {
-		ao2_lock(p);
-		ast_cli(a->fd, "%s -- %s@%s\n", p->owner ? ast_channel_name(p->owner) : "<unowned>", p->exten, p->context);
-		ao2_unlock(p);
-		ao2_ref(p, -1);
-	}
-	ao2_iterator_destroy(&it);
-
-	return CLI_SUCCESS;
-}
-
-static struct ast_cli_entry cli_local[] = {
-	AST_CLI_DEFINE(locals_show, "List status of local channels"),
-};
-
-static int manager_optimize_away(struct mansession *s, const struct message *m)
-{
-	const char *channel;
-	struct local_pvt *p;
-	struct local_pvt *found;
-	struct ast_channel *chan;
-
-	channel = astman_get_header(m, "Channel");
-	if (ast_strlen_zero(channel)) {
-		astman_send_error(s, m, "'Channel' not specified.");
-		return 0;
-	}
-
-	chan = ast_channel_get_by_name(channel);
-	if (!chan) {
-		astman_send_error(s, m, "Channel does not exist.");
-		return 0;
-	}
-
-	p = ast_channel_tech_pvt(chan);
-	ast_channel_unref(chan);
-
-	found = p ? ao2_find(locals, p, 0) : NULL;
-	if (found) {
-		ao2_lock(found);
-		ast_clear_flag(found, LOCAL_NO_OPTIMIZATION);
-		ao2_unlock(found);
-		ao2_ref(found, -1);
-		astman_send_ack(s, m, "Queued channel to be optimized away");
-	} else {
-		astman_send_error(s, m, "Unable to find channel");
-	}
-
-	return 0;
-}
-
-
-static int locals_cmp_cb(void *obj, void *arg, int flags)
-{
-	return (obj == arg) ? CMP_MATCH : 0;
-}
-
-/*! \brief Load module into PBX, register channel */
-static int load_module(void)
-{
-	if (!(local_tech.capabilities = ast_format_cap_alloc())) {
-		return AST_MODULE_LOAD_FAILURE;
-	}
-	ast_format_cap_add_all(local_tech.capabilities);
-
-	locals = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, locals_cmp_cb);
-	if (!locals) {
-		ast_format_cap_destroy(local_tech.capabilities);
-		return AST_MODULE_LOAD_FAILURE;
-	}
-
-	/* Make sure we can register our channel type */
-	if (ast_channel_register(&local_tech)) {
-		ast_log(LOG_ERROR, "Unable to register channel class 'Local'\n");
-		ao2_ref(locals, -1);
-		ast_format_cap_destroy(local_tech.capabilities);
-		return AST_MODULE_LOAD_FAILURE;
-	}
-	ast_cli_register_multiple(cli_local, sizeof(cli_local) / sizeof(struct ast_cli_entry));
-	ast_manager_register_xml("LocalOptimizeAway", EVENT_FLAG_SYSTEM|EVENT_FLAG_CALL, manager_optimize_away);
-
-	return AST_MODULE_LOAD_SUCCESS;
-}
-
-/*! \brief Unload the local proxy channel from Asterisk */
-static int unload_module(void)
-{
-	struct local_pvt *p = NULL;
-	struct ao2_iterator it;
-
-	/* First, take us out of the channel loop */
-	ast_cli_unregister_multiple(cli_local, sizeof(cli_local) / sizeof(struct ast_cli_entry));
-	ast_manager_unregister("LocalOptimizeAway");
-	ast_channel_unregister(&local_tech);
-
-	it = ao2_iterator_init(locals, 0);
-	while ((p = ao2_iterator_next(&it))) {
-		if (p->owner) {
-			ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
-		}
-		ao2_ref(p, -1);
-	}
-	ao2_iterator_destroy(&it);
-	ao2_ref(locals, -1);
-
-	ast_format_cap_destroy(local_tech.capabilities);
-	return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Local Proxy Channel (Note: used internally by other modules)",
-		.load = load_module,
-		.unload = unload_module,
-		.load_pri = AST_MODPRI_CHANNEL_DRIVER,
-	);
diff --git a/channels/chan_mgcp.c b/channels/chan_mgcp.c
index e254823bbabf2b618f4171f1eeed4d939ad51f4c..1f0830762b18ae487f28ff8759b210bc4098d872 100644
--- a/channels/chan_mgcp.c
+++ b/channels/chan_mgcp.c
@@ -83,6 +83,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/chanvars.h"
 #include "asterisk/pktccops.h"
 #include "asterisk/stasis.h"
+#include "asterisk/bridging.h"
 
 /*
  * Define to work around buggy dlink MGCP phone firmware which
@@ -480,7 +481,6 @@ static struct ast_channel_tech mgcp_tech = {
 	.fixup = mgcp_fixup,
 	.send_digit_begin = mgcp_senddigit_begin,
 	.send_digit_end = mgcp_senddigit_end,
-	.bridge = ast_rtp_instance_bridge,
 	.func_channel_read = acf_channel_read,
 };
 
@@ -3213,56 +3213,55 @@ static void *mgcp_ss(void *data)
 	return NULL;
 }
 
-static int attempt_transfer(struct mgcp_endpoint *p)
+/*! \brief Complete an attended transfer
+ *
+ * \param p The endpoint performing the attended transfer
+ * \param sub The sub-channel completing the attended transfer
+ *
+ * \note p->sub is the currently active sub-channel (the channel the phone is using)
+ * \note p->sub->next is the sub-channel not in use, potentially on hold
+ *
+ * \retval 0 when channel should be hung up
+ * \retval 1 when channel should not be hung up
+ */
+static int attempt_transfer(struct mgcp_endpoint *p, struct mgcp_subchannel *sub)
 {
-	/* *************************
-	 * I hope this works.
-	 * Copied out of chan_zap
-	 * Cross your fingers
-	 * *************************/
-
-	/* In order to transfer, we need at least one of the channels to
-	   actually be in a call bridge.  We can't conference two applications
-	   together (but then, why would we want to?) */
-	if (ast_bridged_channel(p->sub->owner)) {
-		/* The three-way person we're about to transfer to could still be in MOH, so
-		   stop it now */
-		ast_queue_control(p->sub->next->owner, AST_CONTROL_UNHOLD);
-		if (ast_channel_state(p->sub->owner) == AST_STATE_RINGING) {
-			ast_queue_control(p->sub->next->owner, AST_CONTROL_RINGING);
-		}
-		if (ast_channel_masquerade(p->sub->next->owner, ast_bridged_channel(p->sub->owner))) {
-			ast_log(LOG_WARNING, "Unable to masquerade %s as %s\n",
-				ast_channel_name(ast_bridged_channel(p->sub->owner)), ast_channel_name(p->sub->next->owner));
-			return -1;
-		}
-		/* Orphan the channel */
-		unalloc_sub(p->sub->next);
-	} else if (ast_bridged_channel(p->sub->next->owner)) {
-		if (ast_channel_state(p->sub->owner) == AST_STATE_RINGING) {
-			ast_queue_control(p->sub->next->owner, AST_CONTROL_RINGING);
-		}
-		ast_queue_control(p->sub->next->owner, AST_CONTROL_UNHOLD);
-		if (ast_channel_masquerade(p->sub->owner, ast_bridged_channel(p->sub->next->owner))) {
-			ast_log(LOG_WARNING, "Unable to masquerade %s as %s\n",
-				ast_channel_name(ast_bridged_channel(p->sub->next->owner)), ast_channel_name(p->sub->owner));
-			return -1;
+	enum ast_transfer_result res;
+
+	/* Ensure that the other channel goes off hold and that it is indicating properly */
+	ast_queue_control(sub->next->owner, AST_CONTROL_UNHOLD);
+	if (ast_channel_state(sub->owner) == AST_STATE_RINGING) {
+		ast_queue_control(sub->next->owner, AST_CONTROL_RINGING);
+	}
+
+	ast_mutex_unlock(&p->sub->next->lock);
+	ast_mutex_unlock(&p->sub->lock);
+	res = ast_bridge_transfer_attended(sub->owner, sub->next->owner, NULL);
+
+	/* Subs are only freed when the endpoint itself is destroyed, so they will continue to exist
+	 * after ast_bridge_transfer_attended returns making this safe without reference counting
+	 */
+	ast_mutex_lock(&p->sub->lock);
+	ast_mutex_lock(&p->sub->next->lock);
+
+	if (res != AST_BRIDGE_TRANSFER_SUCCESS) {
+		/* If transferring fails hang up the other channel if present and us */
+		if (sub->next->owner) {
+			ast_channel_softhangup_internal_flag_add(sub->next->owner, AST_SOFTHANGUP_DEV);
+			mgcp_queue_hangup(sub->next);
 		}
-		/*swap_subs(p, SUB_THREEWAY, SUB_REAL);*/
-		ast_verb(3, "Swapping %d for %d on %s@%s\n", p->sub->id, p->sub->next->id, p->name, p->parent->name);
-		p->sub = p->sub->next;
-		unalloc_sub(p->sub->next);
-		/* Tell the caller not to hangup */
+		sub->next->alreadygone = 1;
+		return 0;
+	}
+
+	unalloc_sub(sub->next);
+
+	/* If the active sub is NOT the one completing the transfer change it to be, and hang up the other sub */
+	if (p->sub != sub) {
+		p->sub = sub;
 		return 1;
-	} else {
-		ast_debug(1, "Neither %s nor %s are in a bridge, nothing to transfer\n",
-			ast_channel_name(p->sub->owner), ast_channel_name(p->sub->next->owner));
-		ast_channel_softhangup_internal_flag_add(p->sub->next->owner, AST_SOFTHANGUP_DEV);
-		if (p->sub->next->owner) {
-			p->sub->next->alreadygone = 1;
-			mgcp_queue_hangup(p->sub->next);
-		}
 	}
+
 	return 0;
 }
 
@@ -3511,13 +3510,8 @@ static int handle_request(struct mgcp_subchannel *sub, struct mgcp_request *req,
 				/* We're allowed to transfer, we have two avtive calls and */
 				/* we made at least one of the calls.  Let's try and transfer */
 				ast_mutex_lock(&p->sub->next->lock);
-				res = attempt_transfer(p);
-				if (res < 0) {
-					if (p->sub->next->owner) {
-						sub->next->alreadygone = 1;
-						mgcp_queue_hangup(sub->next);
-					}
-				} else if (res) {
+				res = attempt_transfer(p, sub);
+				if (res) {
 					ast_log(LOG_WARNING, "Transfer attempt failed\n");
 					ast_mutex_unlock(&p->sub->next->lock);
 					return -1;
diff --git a/channels/chan_misdn.c b/channels/chan_misdn.c
index 2bc6f1e35e2b696eeae9763450aefb8d8f997469..aaa7170b3a9a28c8bb7dd26f338e97c9da3c22b7 100644
--- a/channels/chan_misdn.c
+++ b/channels/chan_misdn.c
@@ -8091,6 +8091,7 @@ static int misdn_send_text(struct ast_channel *chan, const char *text)
 	return 0;
 }
 
+/* BUGBUG The mISDN channel driver needs its own native bridge technology. (More like just never give it one.) */
 static struct ast_channel_tech misdn_tech = {
 	.type = misdn_type,
 	.description = "Channel driver for mISDN Support (Bri/Pri)",
diff --git a/channels/chan_motif.c b/channels/chan_motif.c
index 0836972e167581261096b3d104aaacb359f35cb3..56b06b14571172af9aa05b6d88fb2eeddeea01fb 100644
--- a/channels/chan_motif.c
+++ b/channels/chan_motif.c
@@ -360,7 +360,6 @@ static struct ast_channel_tech jingle_tech = {
 	.send_text = jingle_sendtext,
 	.send_digit_begin = jingle_digit_begin,
 	.send_digit_end = jingle_digit_end,
-	.bridge = ast_rtp_instance_bridge,
 	.call = jingle_call,
 	.hangup = jingle_hangup,
 	.answer = jingle_answer,
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index 88965fc73a94f01de15f60c534d55ce3023973f4..f7a528b681df4cbdae5b8dea9fcce15f23a67bba 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -176,7 +176,6 @@
 /*** MODULEINFO
 	<use type="module">res_crypto</use>
 	<use type="module">res_http_websocket</use>
-	<depend>chan_local</depend>
 	<support_level>core</support_level>
  ***/
 
@@ -295,6 +294,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "sip/include/security_events.h"
 #include "asterisk/sip_api.h"
 #include "asterisk/app.h"
+#include "asterisk/bridging.h"
 #include "asterisk/stasis_endpoints.h"
 
 /*** DOCUMENTATION
@@ -1202,8 +1202,6 @@ static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sock
 					      struct sip_request *req, const char *uri);
 static struct sip_pvt *get_sip_pvt_byid_locked(const char *callid, const char *totag, const char *fromtag);
 static void check_pendings(struct sip_pvt *p);
-static void *sip_park_thread(void *stuff);
-static int sip_park(struct ast_channel *chan1, struct ast_channel *chan2, struct sip_request *req, uint32_t seqno, const char *park_exten, const char *park_context);
 
 static void *sip_pickup_thread(void *stuff);
 static int sip_pickup(struct ast_channel *chan);
@@ -1544,7 +1542,6 @@ struct ast_channel_tech sip_tech = {
 	.fixup = sip_fixup,			/* called with chan locked */
 	.send_digit_begin = sip_senddigit_begin,	/* called with chan unlocked */
 	.send_digit_end = sip_senddigit_end,
-	.bridge = ast_rtp_instance_bridge,			/* XXX chan unlocked ? */
 	.early_bridge = ast_rtp_instance_early_bridge,
 	.send_text = sip_sendtext,		/* called with chan locked */
 	.func_channel_read = sip_acf_channel_read,
@@ -24426,160 +24423,6 @@ static void handle_response(struct sip_pvt *p, int resp, const char *rest, struc
 	}
 }
 
-
-/*! \brief Park SIP call support function
-	Starts in a new thread, then parks the call
-	XXX Should we add a wait period after streaming audio and before hangup?? Sometimes the
-		audio can't be heard before hangup
-*/
-static void *sip_park_thread(void *stuff)
-{
-	struct ast_channel *transferee, *transferer;	/* Chan1: The transferee, Chan2: The transferer */
-	struct sip_dual *d;
-	int ext;
-	int res;
-
-	d = stuff;
-	transferee = d->chan1;
-	transferer = d->chan2;
-
-	ast_debug(4, "SIP Park: Transferer channel %s, Transferee %s\n", ast_channel_name(transferer), ast_channel_name(transferee));
-
-	res = ast_park_call_exten(transferee, transferer, d->park_exten, d->park_context, 0, &ext);
-
-	sip_pvt_lock(ast_channel_tech_pvt(transferer));
-#ifdef WHEN_WE_KNOW_THAT_THE_CLIENT_SUPPORTS_MESSAGE
-	if (res) {
-		destroy_msg_headers(ast_channel_tech_pvt(transferer));
-		ast_string_field_set(ast_channel_tech_pvt(transferer), msg_body, "Unable to park call.");
-		transmit_message(ast_channel_tech_pvt(transferer), 0, 0);
-	} else {
-		/* Then tell the transferer what happened */
-		destroy_msg_headers(ast_channel_tech_pvt(transferer));
-		sprintf(buf, "Call parked on extension '%d'.", ext);
-		ast_string_field_set(ast_channel_tech_pvt(transferer), msg_body, buf);
-		transmit_message(ast_channel_tech_pvt(transferer), 0, 0);
-	}
-#endif
-
-	/* Any way back to the current call??? */
-	/* Transmit response to the REFER request */
-	if (!res)	{
-		/* Transfer succeeded */
-		append_history(ast_channel_tech_pvt(transferer), "SIPpark", "Parked call on %d", ext);
-		transmit_notify_with_sipfrag(ast_channel_tech_pvt(transferer), d->seqno, "200 OK", TRUE);
-		sip_pvt_unlock(ast_channel_tech_pvt(transferer));
-		ast_channel_hangupcause_set(transferer, AST_CAUSE_NORMAL_CLEARING);
-		ast_hangup(transferer); /* This will cause a BYE */
-		ast_debug(1, "SIP Call parked on extension '%d'\n", ext);
-	} else {
-		transmit_notify_with_sipfrag(ast_channel_tech_pvt(transferer), d->seqno, "503 Service Unavailable", TRUE);
-		append_history(ast_channel_tech_pvt(transferer), "SIPpark", "Parking failed\n");
-		sip_pvt_unlock(ast_channel_tech_pvt(transferer));
-		ast_debug(1, "SIP Call parked failed \n");
-		/* Do not hangup call */
-	}
-	deinit_req(&d->req);
-	ast_free(d->park_exten);
-	ast_free(d->park_context);
-	ast_free(d);
-	return NULL;
-}
-
-/*! DO NOT hold any locks while calling sip_park */
-static int sip_park(struct ast_channel *chan1, struct ast_channel *chan2, struct sip_request *req, uint32_t seqno, const char *park_exten, const char *park_context)
-{
-	struct sip_dual *d;
-	struct ast_channel *transferee, *transferer;
-	pthread_t th;
-
-	transferee = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, ast_channel_accountcode(chan1), ast_channel_exten(chan1), ast_channel_context(chan1), ast_channel_linkedid(chan1), ast_channel_amaflags(chan1), "Parking/%s", ast_channel_name(chan1));
-	transferer = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, ast_channel_accountcode(chan2), ast_channel_exten(chan2), ast_channel_context(chan2), ast_channel_linkedid(chan2), ast_channel_amaflags(chan2), "SIPPeer/%s", ast_channel_name(chan2));
-	d = ast_calloc(1, sizeof(*d));
-	if (!transferee || !transferer || !d) {
-		if (transferee) {
-			ast_hangup(transferee);
-		}
-		if (transferer) {
-			ast_hangup(transferer);
-		}
-		ast_free(d);
-		return -1;
-	}
-	d->park_exten = ast_strdup(park_exten);
-	d->park_context = ast_strdup(park_context);
-	if (!d->park_exten || !d->park_context) {
-		ast_hangup(transferee);
-		ast_hangup(transferer);
-		ast_free(d->park_exten);
-		ast_free(d->park_context);
-		ast_free(d);
-		return -1;
-	}
-
-	/* Make formats okay */
-	ast_format_copy(ast_channel_readformat(transferee), ast_channel_readformat(chan1));
-	ast_format_copy(ast_channel_writeformat(transferee), ast_channel_writeformat(chan1));
-
-	/* Prepare for taking over the channel */
-	if (ast_channel_masquerade(transferee, chan1)) {
-		ast_hangup(transferee);
-		ast_hangup(transferer);
-		ast_free(d->park_exten);
-		ast_free(d->park_context);
-		ast_free(d);
-		return -1;
-	}
-
-	/* Setup the extensions and such */
-	ast_channel_context_set(transferee, ast_channel_context(chan1));
-	ast_channel_exten_set(transferee, ast_channel_exten(chan1));
-	ast_channel_priority_set(transferee, ast_channel_priority(chan1));
-
-	ast_do_masquerade(transferee);
-
-	/* We make a clone of the peer channel too, so we can play
-	   back the announcement */
-
-	/* Make formats okay */
-	ast_format_copy(ast_channel_readformat(transferer), ast_channel_readformat(chan2));
-	ast_format_copy(ast_channel_writeformat(transferer), ast_channel_writeformat(chan2));
-	ast_channel_parkinglot_set(transferer, ast_channel_parkinglot(chan2));
-
-	/* Prepare for taking over the channel */
-	if (ast_channel_masquerade(transferer, chan2)) {
-		ast_hangup(transferer);
-		ast_free(d->park_exten);
-		ast_free(d->park_context);
-		ast_free(d);
-		return -1;
-	}
-
-	/* Setup the extensions and such */
-	ast_channel_context_set(transferer, ast_channel_context(chan2));
-	ast_channel_exten_set(transferer, ast_channel_exten(chan2));
-	ast_channel_priority_set(transferer, ast_channel_priority(chan2));
-
-	ast_do_masquerade(transferer);
-
-	/* Save original request for followup */
-	copy_request(&d->req, req);
-	d->chan1 = transferee;	/* Transferee */
-	d->chan2 = transferer;	/* Transferer */
-	d->seqno = seqno;
-	if (ast_pthread_create_detached_background(&th, NULL, sip_park_thread, d) < 0) {
-		/* Could not start thread */
-		deinit_req(&d->req);
-		ast_free(d->park_exten);
-		ast_free(d->park_context);
-		ast_free(d);	/* We don't need it anymore. If thread is created, d will be free'd
-				   by sip_park_thread() */
-		return -1;
-	}
-	return 0;
-}
-
-
 /*! \brief SIP pickup support function
  *	Starts in a new thread, then pickup the call
  */
@@ -26170,6 +26013,10 @@ static void parse_oli(struct sip_request *req, struct ast_channel *chan)
  *	If this function is successful, only the transferer pvt lock will remain on return.  Setting nounlock indicates
  *	to handle_request_do() that the pvt's owner it locked does not require an unlock.
  */
+
+/* XXX XXX XXX XXX XXX XXX
+ * This function is COMPLETELY broken at the moment. It *will* crash if called
+ */
 static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *current, struct sip_request *req, uint32_t seqno, int *nounlock)
 {
 	struct sip_dual target;		/* Chan 1: Call from tranferer to Asterisk */
@@ -26370,6 +26217,44 @@ static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *
 	return 1;
 }
 
+/*!
+ * Data to set on a channel that runs dialplan
+ * at the completion of a blind transfer
+ */
+struct blind_transfer_cb_data {
+	/*! Contents of the REFER's Referred-by header */
+	const char *referred_by;
+	/*! Domain of the URI in the REFER's Refer-To header */
+	const char *domain;
+	/*! Contents of what to place in a Replaces header of an INVITE */
+	const char *replaces;
+	/*! Redirecting information to set on the channel */
+	struct ast_party_redirecting redirecting;
+	/*! Parts of the redirecting structure that are to be updated */
+	struct ast_set_party_redirecting update_redirecting;
+};
+
+/*!
+ * \internal
+ * \brief Callback called on new outbound channel during blind transfer
+ *
+ * We use this opportunity to populate the channel with data from the REFER
+ * so that, if necessary, we can include proper information on any new INVITE
+ * we may send out.
+ *
+ * \param chan The new outbound channel
+ * \user_data A blind_transfer_cb_data struct
+ */
+static void blind_transfer_cb(struct ast_channel *chan, void *user_data)
+{
+	struct blind_transfer_cb_data *cb_data = user_data;
+
+	pbx_builtin_setvar_helper(chan, "SIPTRANSFER", "yes");
+	pbx_builtin_setvar_helper(chan, "SIPTRANSFER_REFERER", cb_data->referred_by);
+	pbx_builtin_setvar_helper(chan, "SIPTRANSFER_REPLACES", cb_data->replaces);
+	pbx_builtin_setvar_helper(chan, "SIPDOMAIN", cb_data->domain);
+	ast_channel_update_redirecting(chan, &cb_data->redirecting, &cb_data->update_redirecting);
+}
 
 /*! \brief Handle incoming REFER request */
 /*! \page SIP_REFER SIP transfer Support (REFER)
@@ -26436,22 +26321,13 @@ static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *
 	*/
 static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint32_t seqno, int *nounlock)
 {
-	/*!
-	 * Chan1: Call between asterisk and transferer
-	 * Chan2: Call between asterisk and transferee
-	 */
-	struct sip_dual current = { 0, };
-	struct ast_channel *chans[2] = { 0, };
 	char *refer_to = NULL;
-	char *refer_to_domain = NULL;
 	char *refer_to_context = NULL;
-	char *referred_by = NULL;
-	char *callid = NULL;
-	int localtransfer = 0;
-	int attendedtransfer = 0;
 	int res = 0;
-	struct ast_party_redirecting redirecting;
-	struct ast_set_party_redirecting update_redirecting;
+	struct blind_transfer_cb_data cb_data;
+	enum ast_transfer_result transfer_res;
+	RAII_VAR(struct ast_channel *, transferer, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_str *, replaces_str, NULL, ast_free_ptr);
 
 	if (req->debug) {
 		ast_verbose("Call %s got a SIP call transfer from %s: (REFER)!\n",
@@ -26469,8 +26345,7 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint
 			sip_alreadygone(p);
 			pvt_set_needdestroy(p, "outside of dialog");
 		}
-		res = 0;
-		goto handle_refer_cleanup;
+		return 0;
 	}
 
 	/* Check if transfer is allowed from this device */
@@ -26479,24 +26354,21 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint
 		transmit_response(p, "603 Declined (policy)", req);
 		append_history(p, "Xfer", "Refer failed. Allowtransfer == closed.");
 		/* Do not destroy SIP session */
-		res = 0;
-		goto handle_refer_cleanup;
+		return 0;
 	}
 
 	if (!req->ignore && ast_test_flag(&p->flags[0], SIP_GOTREFER)) {
 		/* Already have a pending REFER */
 		transmit_response(p, "491 Request pending", req);
 		append_history(p, "Xfer", "Refer failed. Request pending.");
-		res = 0;
-		goto handle_refer_cleanup;
+		return 0;
 	}
 
 	/* Allocate memory for call transfer data */
 	if (!p->refer && !sip_refer_alloc(p)) {
 		transmit_response(p, "500 Internal Server Error", req);
 		append_history(p, "Xfer", "Refer failed. Memory allocation error.");
-		res = -3;
-		goto handle_refer_cleanup;
+		return -3;
 	}
 
 	res = get_refer_info(p, req);	/* Extract headers */
@@ -26530,9 +26402,9 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint
 			}
 			break;
 		}
-		res = 0;
-		goto handle_refer_cleanup;
+		return 0;
 	}
+
 	if (ast_strlen_zero(p->context)) {
 		ast_string_field_set(p, context, sip_cfg.default_context);
 	}
@@ -26553,70 +26425,16 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint
 	/* Is this a repeat of a current request? Ignore it */
 	/* Don't know what else to do right now. */
 	if (req->ignore) {
-		goto handle_refer_cleanup;
-	}
-
-	/* If this is a blind transfer, we have the following
-	channels to work with:
-	- chan1, chan2: The current call between transferer and transferee (2 channels)
-	- target_channel: A new call from the transferee to the target (1 channel)
-	We need to stay tuned to what happens in order to be able
-	to bring back the call to the transferer */
-
-	/* If this is a attended transfer, we should have all call legs within reach:
-	- chan1, chan2: The call between the transferer and transferee (2 channels)
-	- target_channel, targetcall_pvt: The call between the transferer and the target (2 channels)
-	We want to bridge chan2 with targetcall_pvt!
-
-	The replaces call id in the refer message points
-	to the call leg between Asterisk and the transferer.
-	So we need to connect the target and the transferee channel
-	and hangup the two other channels silently
-
-	If the target is non-local, the call ID could be on a remote
-	machine and we need to send an INVITE with replaces to the
-	target. We basically handle this as a blind transfer
-	and let the sip_call function catch that we need replaces
-	header in the INVITE.
-	*/
+		return 0;
+	}
 
 	/* Get the transferer's channel */
-	chans[0] = current.chan1 = p->owner;
-
-	/* Find the other part of the bridge (2) - transferee */
-	chans[1] = current.chan2 = ast_bridged_channel(current.chan1);
-
-	ast_channel_ref(current.chan1);
-	if (current.chan2) {
-		ast_channel_ref(current.chan2);
-	}
+	transferer = ast_channel_ref(p->owner);
 
 	if (sipdebug) {
-		ast_debug(3, "SIP %s transfer: Transferer channel %s, transferee channel %s\n",
+		ast_debug(3, "SIP %s transfer: Transferer channel %s\n",
 			p->refer->attendedtransfer ? "attended" : "blind",
-			ast_channel_name(current.chan1),
-			current.chan2 ? ast_channel_name(current.chan2) : "<none>");
-	}
-
-	if (!current.chan2 && !p->refer->attendedtransfer) {
-		/* No bridged channel, propably IVR or echo or similar... */
-		/* Guess we should masquerade or something here */
-		/* Until we figure it out, refuse transfer of such calls */
-		if (sipdebug) {
-			ast_debug(3, "Refused SIP transfer on non-bridged channel.\n");
-		}
-		p->refer->status = REFER_FAILED;
-		append_history(p, "Xfer", "Refer failed. Non-bridged channel.");
-		transmit_response(p, "603 Declined", req);
-		res = -1;
-		goto handle_refer_cleanup;
-	}
-
-	if (current.chan2) {
-		if (sipdebug) {
-			ast_debug(4, "Got SIP transfer, applying to bridged peer '%s'\n", ast_channel_name(current.chan2));
-		}
-		ast_queue_control(current.chan1, AST_CONTROL_UNHOLD);
+			ast_channel_name(transferer));
 	}
 
 	ast_set_flag(&p->flags[0], SIP_GOTREFER);
@@ -26627,8 +26445,9 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint
 	/* Attended transfer: Find all call legs and bridge transferee with target*/
 	if (p->refer->attendedtransfer) {
 		/* both p and p->owner _MUST_ be locked while calling local_attended_transfer */
-		if ((res = local_attended_transfer(p, &current, req, seqno, nounlock))) {
-			goto handle_refer_cleanup; /* We're done with the transfer */
+		if ((res = local_attended_transfer(p, NULL, req, seqno, nounlock))) {
+			ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+			return res;
 		}
 		/* Fall through for remote transfers that we did not find locally */
 		if (sipdebug) {
@@ -26639,210 +26458,74 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint
 
 	/* Copy data we can not safely access after letting the pvt lock go. */
 	refer_to = ast_strdupa(p->refer->refer_to);
-	refer_to_domain = ast_strdupa(p->refer->refer_to_domain);
 	refer_to_context = ast_strdupa(p->refer->refer_to_context);
-	referred_by = ast_strdupa(p->refer->referred_by);
-	callid = ast_strdupa(p->callid);
-	localtransfer = p->refer->localtransfer;
-	attendedtransfer = p->refer->attendedtransfer;
-
-	if (!*nounlock) {
-		ast_channel_unlock(p->owner);
-		*nounlock = 1;
-	}
-	sip_pvt_unlock(p);
-
-	/* Parking a call.  DO NOT hold any locks while calling ast_parking_ext_valid() */
-	if (localtransfer && ast_parking_ext_valid(refer_to, current.chan1, refer_to_context)) {
-		sip_pvt_lock(p);
-		ast_clear_flag(&p->flags[0], SIP_GOTREFER);
-		p->refer->status = REFER_200OK;
-		append_history(p, "Xfer", "REFER to call parking.");
-		sip_pvt_unlock(p);
 
-		ast_manager_event_multichan(EVENT_FLAG_CALL, "Transfer", 2, chans,
-			"TransferMethod: SIP\r\n"
-			"TransferType: Blind\r\n"
-			"Channel: %s\r\n"
-			"Uniqueid: %s\r\n"
-			"SIP-Callid: %s\r\n"
-			"TargetChannel: %s\r\n"
-			"TargetUniqueid: %s\r\n"
-			"TransferExten: %s\r\n"
-			"Transfer2Parking: Yes\r\n",
-			ast_channel_name(current.chan1),
-			ast_channel_uniqueid(current.chan1),
-			callid,
-			ast_channel_name(current.chan2),
-			ast_channel_uniqueid(current.chan2),
-			refer_to);
+	ast_party_redirecting_init(&cb_data.redirecting);
+	memset(&cb_data.update_redirecting, 0, sizeof(cb_data.update_redirecting));
+	change_redirecting_information(p, req, &cb_data.redirecting, &cb_data.update_redirecting, 0);
 
-		if (sipdebug) {
-			ast_debug(4, "SIP transfer to parking: trying to park %s. Parked by %s\n", ast_channel_name(current.chan2), ast_channel_name(current.chan1));
-		}
+	cb_data.domain = ast_strdupa(p->refer->refer_to_domain);
+	cb_data.referred_by = ast_strdupa(p->refer->referred_by);
 
-		/* DO NOT hold any locks while calling sip_park */
-		if (sip_park(current.chan2, current.chan1, req, seqno, refer_to, refer_to_context)) {
-			sip_pvt_lock(p);
-			transmit_notify_with_sipfrag(p, seqno, "500 Internal Server Error", TRUE);
-		} else {
-			sip_pvt_lock(p);
+	if (!ast_strlen_zero(p->refer->replaces_callid)) {
+		replaces_str = ast_str_create(128);
+		if (!replaces_str) {
+			ast_log(LOG_NOTICE, "Unable to create Replaces string for remote attended transfer. Transfer failed\n");
+			ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+			ast_party_redirecting_free(&cb_data.redirecting);
+			return -1;
 		}
-		goto handle_refer_cleanup;
-	}
-
-	/* Blind transfers and remote attended xfers.
-	 * Locks should not be held while calling pbx_builtin_setvar_helper. This function
-	 * locks the channel being passed into it.*/
-	if (current.chan1 && current.chan2) {
-		ast_debug(3, "chan1->name: %s\n", ast_channel_name(current.chan1));
-		pbx_builtin_setvar_helper(current.chan1, "BLINDTRANSFER", ast_channel_name(current.chan2));
+		ast_str_append(&replaces_str, 0, "%s%s%s%s%s", p->refer->replaces_callid,
+				!ast_strlen_zero(p->refer->replaces_callid_totag) ? ";to-tag=" : "",
+				S_OR(p->refer->replaces_callid_totag, ""),
+				!ast_strlen_zero(p->refer->replaces_callid_fromtag) ? ";from-tag=" : "",
+				S_OR(p->refer->replaces_callid_fromtag, ""));
+		cb_data.replaces = ast_str_buffer(replaces_str);
+	} else {
+		cb_data.replaces = NULL;
 	}
 
-	if (current.chan2) {
-		pbx_builtin_setvar_helper(current.chan2, "BLINDTRANSFER", ast_channel_name(current.chan1));
-		pbx_builtin_setvar_helper(current.chan2, "SIPDOMAIN", refer_to_domain);
-		pbx_builtin_setvar_helper(current.chan2, "SIPTRANSFER", "yes");
-		/* One for the new channel */
-		pbx_builtin_setvar_helper(current.chan2, "_SIPTRANSFER", "yes");
-		/* Attended transfer to remote host, prepare headers for the INVITE */
-		if (!ast_strlen_zero(referred_by)) {
-			pbx_builtin_setvar_helper(current.chan2, "_SIPTRANSFER_REFERER", referred_by);
-		}
-
-		/* When a call is transferred to voicemail from a Digium phone, there may be
-		 * a Diversion header present in the REFER with an appropriate reason parameter
-		 * set. We need to update the redirecting information appropriately.
-		 */
-		ast_channel_lock(p->owner);
-		sip_pvt_lock(p);
-		ast_party_redirecting_init(&redirecting);
-		memset(&update_redirecting, 0, sizeof(update_redirecting));
-		change_redirecting_information(p, req, &redirecting, &update_redirecting, FALSE);
-
-		/* Do not hold the pvt lock during a call that causes an indicate or an async_goto.
-		 * Those functions lock channels which will invalidate locking order if the pvt lock
-		 * is held.*/
-		sip_pvt_unlock(p);
+	if (!*nounlock) {
 		ast_channel_unlock(p->owner);
-		ast_channel_update_redirecting(current.chan2, &redirecting, &update_redirecting);
-		ast_party_redirecting_free(&redirecting);
+		*nounlock = 1;
 	}
 
+	sip_pvt_unlock(p);
+	transfer_res = ast_bridge_transfer_blind(transferer, refer_to, refer_to_context, blind_transfer_cb, &cb_data);
 	sip_pvt_lock(p);
-	/* Generate a Replaces string to be used in the INVITE during attended transfer */
-	if (!ast_strlen_zero(p->refer->replaces_callid)) {
-		char tempheader[SIPBUFSIZE];
-		snprintf(tempheader, sizeof(tempheader), "%s%s%s%s%s", p->refer->replaces_callid,
-			p->refer->replaces_callid_totag ? ";to-tag=" : "",
-			p->refer->replaces_callid_totag,
-			p->refer->replaces_callid_fromtag ? ";from-tag=" : "",
-			p->refer->replaces_callid_fromtag);
-
-		if (current.chan2) {
-			sip_pvt_unlock(p);
-			pbx_builtin_setvar_helper(current.chan2, "_SIPTRANSFER_REPLACES", tempheader);
-			sip_pvt_lock(p);
-		}
-	}
-
-	/* Connect the call */
 
-	/* FAKE ringing if not attended transfer */
-	if (!p->refer->attendedtransfer) {
-		transmit_notify_with_sipfrag(p, seqno, "180 Ringing", FALSE);
-	}
-
-	/* For blind transfer, this will lead to a new call */
-	/* For attended transfer to remote host, this will lead to
-	   a new SIP call with a replaces header, if the dial plan allows it
-	*/
-	if (!current.chan2) {
-		/* We have no bridge, so we're talking with Asterisk somehow */
-		/* We need to masquerade this call */
-		/* What to do to fix this situation:
-		   * Set up the new call in a new channel
-		   * Let the new channel masq into this channel
-		   Please add that code here :-)
-		*/
+	switch (transfer_res) {
+	case AST_BRIDGE_TRANSFER_INVALID:
+		res = -1;
 		p->refer->status = REFER_FAILED;
 		transmit_notify_with_sipfrag(p, seqno, "503 Service Unavailable (can't handle one-legged xfers)", TRUE);
-		ast_clear_flag(&p->flags[0], SIP_GOTREFER);
 		append_history(p, "Xfer", "Refer failed (only bridged calls).");
+		break;
+	case AST_BRIDGE_TRANSFER_NOT_PERMITTED:
 		res = -1;
-		goto handle_refer_cleanup;
-	}
-	ast_set_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER);	/* Delay hangup */
-
-	sip_pvt_unlock(p);
-
-	/* For blind transfers, move the call to the new extensions. For attended transfers on multiple
-	 * servers - generate an INVITE with Replaces. Either way, let the dial plan decided
-	 * indicate before masquerade so the indication actually makes it to the real channel
-	 * when using local channels with MOH passthru */
-	ast_indicate(current.chan2, AST_CONTROL_UNHOLD);
-	res = ast_async_goto(current.chan2, refer_to_context, refer_to, 1);
-
-	if (!res) {
-		ast_manager_event_multichan(EVENT_FLAG_CALL, "Transfer", 2, chans,
-			"TransferMethod: SIP\r\n"
-			"TransferType: Blind\r\n"
-			"Channel: %s\r\n"
-			"Uniqueid: %s\r\n"
-			"SIP-Callid: %s\r\n"
-			"TargetChannel: %s\r\n"
-			"TargetUniqueid: %s\r\n"
-			"TransferExten: %s\r\n"
-			"TransferContext: %s\r\n",
-			ast_channel_name(current.chan1),
-			ast_channel_uniqueid(current.chan1),
-			callid,
-			ast_channel_name(current.chan2),
-			ast_channel_uniqueid(current.chan2),
-			refer_to,
-			refer_to_context);
-		/* Success  - we have a new channel */
-		ast_debug(3, "%s transfer succeeded. Telling transferer.\n", attendedtransfer? "Attended" : "Blind");
-
-		/* XXX - what to we put in CEL 'extra' for attended transfers to external systems? NULL for now */
-		ast_channel_lock(current.chan1);
-		ast_cel_report_event(current.chan1, p->refer->attendedtransfer? AST_CEL_ATTENDEDTRANSFER : AST_CEL_BLINDTRANSFER, NULL, p->refer->attendedtransfer ? NULL : p->refer->refer_to, current.chan2);
-		ast_channel_unlock(current.chan1);
-
-		sip_pvt_lock(p);
-		transmit_notify_with_sipfrag(p, seqno, "200 Ok", TRUE);
-		if (p->refer->localtransfer) {
-			p->refer->status = REFER_200OK;
-		}
-		if (p->owner) {
-			ast_channel_hangupcause_set(p->owner, AST_CAUSE_NORMAL_CLEARING);
-		}
-		append_history(p, "Xfer", "Refer succeeded.");
-		ast_clear_flag(&p->flags[0], SIP_GOTREFER);
-		/* Do not hangup call, the other side do that when we say 200 OK */
-		/* We could possibly implement a timer here, auto congestion */
-		res = 0;
-	} else {
-		sip_pvt_lock(p);
-		ast_clear_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER);	/* Don't delay hangup */
-		ast_debug(3, "%s transfer failed. Resuming original call.\n", p->refer->attendedtransfer? "Attended" : "Blind");
-		append_history(p, "Xfer", "Refer failed.");
-		/* Failure of some kind */
 		p->refer->status = REFER_FAILED;
-		transmit_notify_with_sipfrag(p, seqno, "503 Service Unavailable", TRUE);
-		ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+		transmit_notify_with_sipfrag(p, seqno, "403 Forbidden", TRUE);
+		append_history(p, "Xfer", "Refer failed (bridge does not permit transfers)");
+		break;
+	case AST_BRIDGE_TRANSFER_FAIL:
 		res = -1;
+		p->refer->status = REFER_FAILED;
+		transmit_notify_with_sipfrag(p, seqno, "500 Internal Server Error", TRUE);
+		append_history(p, "Xfer", "Refer failed (internal error)");
+		break;
+	case AST_BRIDGE_TRANSFER_SUCCESS:
+		res = 0;
+		p->refer->status = REFER_200OK;
+		transmit_notify_with_sipfrag(p, seqno, "200 OK", TRUE);
+		ast_set_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER);
+		append_history(p, "Xfer", "Refer succeeded.");
+		break;
+	default:
+		break;
 	}
 
-handle_refer_cleanup:
-	if (current.chan1) {
-		ast_channel_unref(current.chan1);
-	}
-	if (current.chan2) {
-		ast_channel_unref(current.chan2);
-	}
-
-	/* Make sure we exit with the pvt locked */
+	ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+	ast_party_redirecting_free(&cb_data.redirecting);
 	return res;
 }
 
@@ -32928,7 +32611,7 @@ static int sip_set_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance *i
 
 	/* Disable early RTP bridge  */
 	if ((instance || vinstance || tinstance) &&
-		!ast_bridged_channel(chan) &&
+		!ast_channel_is_bridged(chan) &&
 		!sip_cfg.directrtpsetup) {
 		sip_pvt_unlock(p);
 		ast_channel_unlock(chan);
@@ -35058,5 +34741,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Session Initiation Pr
 		.unload = unload_module,
 		.reload = reload,
 		.load_pri = AST_MODPRI_CHANNEL_DRIVER,
-		.nonoptreq = "res_crypto,chan_local,res_http_websocket",
+		.nonoptreq = "res_crypto,res_http_websocket",
 	       );
diff --git a/channels/chan_skinny.c b/channels/chan_skinny.c
index b3951d5e48294913b89553e018703d9a77222139..cd194d5a8e0f43d9bbcd71128fc0fbf42abae715 100644
--- a/channels/chan_skinny.c
+++ b/channels/chan_skinny.c
@@ -1653,7 +1653,6 @@ static struct ast_channel_tech skinny_tech = {
 	.fixup = skinny_fixup,
 	.send_digit_begin = skinny_senddigit_begin,
 	.send_digit_end = skinny_senddigit_end,
-	.bridge = ast_rtp_instance_bridge,
 };
 
 static int skinny_extensionstate_cb(char *context, char *id, struct ast_state_cb_info *info, void *data);
diff --git a/channels/chan_unistim.c b/channels/chan_unistim.c
index dea796364564947c042e94330e8fb3435c0d8aa9..121e2f0b1530368502d3b8c19c6ea8603cda2b61 100644
--- a/channels/chan_unistim.c
+++ b/channels/chan_unistim.c
@@ -708,7 +708,6 @@ static struct ast_channel_tech unistim_tech = {
 	.send_digit_begin = unistim_senddigit_begin,
 	.send_digit_end = unistim_senddigit_end,
 	.send_text = unistim_sendtext,
-	.bridge = ast_rtp_instance_bridge,
 };
 
 static void send_start_rtp(struct unistim_subchannel *);
@@ -5826,7 +5825,6 @@ static char *unistim_show_info(struct ast_cli_entry *e, int cmd, struct ast_cli_
 	struct unistim_line *line;
 	struct unistim_subchannel *sub;
 	struct unistimsession *s;
-	struct ast_channel *tmp;
 
 	switch (cmd) {
 	case CLI_INIT:
@@ -5870,14 +5868,9 @@ static char *unistim_show_info(struct ast_cli_entry *e, int cmd, struct ast_cli_
 			if (!sub) {
 				continue;
 			}
-			if (!sub->owner) {
-				tmp = (void *) -42;
-			} else {
-				tmp = ast_channel_internal_bridged_channel(sub->owner);
-			}
 			ast_cli(a->fd,
-					"-->subtype=%s chan=%p rtp=%p bridge=%p line=%p alreadygone=%d softkey=%d\n",
-					subtype_tostr(sub->subtype), sub->owner, sub->rtp, tmp, sub->parent,
+					"-->subtype=%s chan=%p rtp=%p line=%p alreadygone=%d softkey=%d\n",
+					subtype_tostr(sub->subtype), sub->owner, sub->rtp, sub->parent,
 					sub->alreadygone, sub->softkey);
 		}
 		AST_LIST_UNLOCK(&device->subs);
diff --git a/channels/chan_vpb.cc b/channels/chan_vpb.cc
index 58fc73f699ce4c1ebccf9b24112ab78d9906396d..d1747cb066f9905dfc1bebb59980152d98b078b0 100644
--- a/channels/chan_vpb.cc
+++ b/channels/chan_vpb.cc
@@ -2267,13 +2267,7 @@ static void *do_chanreads(void *pvt)
 			else 
 				bridgerec = 0;
 		} else {
-			ast_verb(5, "%s: chanreads: No native bridge.\n", p->dev);
-			if (ast_channel_internal_bridged_channel(p->owner)) {
-				ast_verb(5, "%s: chanreads: Got Asterisk bridge with [%s].\n", p->dev, ast_channel_name(ast_channel_internal_bridged_channel(p->owner)));
-				bridgerec = 1;
-			} else {
-				bridgerec = 0;
-			}
+			bridgerec = ast_channel_is_bridged(p->owner) ? 1 : 0;
 		}
 
 /*		if ((p->owner->_state != AST_STATE_UP) || !bridgerec) */
diff --git a/configs/features.conf.sample b/configs/features.conf.sample
index 701ccdf37f6d26a1da5d9e345dd06d91d35938d8..12fb3151c9c00c85a065ed4381e679db729d23f7 100644
--- a/configs/features.conf.sample
+++ b/configs/features.conf.sample
@@ -157,7 +157,7 @@ context => parkedcalls          ; Which context parked calls are in (default par
 ;
 ;    Set(__DYNAMIC_FEATURES=myfeature1#myfeature2#myfeature3)
 ;
-; (Note: The two leading underscores allow these feature settings to be set on
+; (Note: The two leading underscores allow these feature settings to be set
 ;  on the outbound channels, as well.  Otherwise, only the original channel
 ;  will have access to these features.)
 ;
@@ -176,10 +176,10 @@ context => parkedcalls          ; Which context parked calls are in (default par
 ;                   application on the same channel that activated the feature. "peer"
 ;                   means run the application on the opposite channel from the one that
 ;                   has activated the feature.
-;  ActivatedBy   -> This is which channel is allowed to activate this feature. Valid
-;                   values are "caller", "callee", and "both". "both" is the default.
-;                   The "caller" is the channel that executed the Dial application, while
-;                   the "callee" is the channel called by the Dial application.
+;  ActivatedBy   -> ActivatedBy is no longer honored.  The feature is activated by which
+;                   channel DYNAMIC_FEATURES includes the feature is on.  Use predial
+;                   to set different values of DYNAMIC_FEATURES on the channels.
+;                   Historic values are: "caller", "callee", and "both".
 ;  Application   -> This is the application to execute.
 ;  AppArguments  -> These are the arguments to be passed into the application.  If you need
 ;                   commas in your arguments, you should use either the second or third
@@ -194,8 +194,9 @@ context => parkedcalls          ; Which context parked calls are in (default par
 ;   applications. When applications are used in extensions.conf, they are executed
 ;   by the PBX core. In this case, these applications are executed outside of the
 ;   PBX core, so it does *not* make sense to use any application which has any
-;   concept of dialplan flow. Examples of this would be things like Macro, Goto,
-;   Background, WaitExten, and many more.
+;   concept of dialplan flow. Examples of this would be things like Goto,
+;   Background, WaitExten, and many more.  The exceptions to this are Gosub and
+;   Macro routines which must complete for the call to continue.
 ;
 ; Enabling these features means that the PBX needs to stay in the media flow and
 ; media will not be re-directed if DTMF is sent in the media stream.
diff --git a/configs/res_parking.conf.sample b/configs/res_parking.conf.sample
new file mode 100644
index 0000000000000000000000000000000000000000..b8308e6ab4fedd4e4b1f44e594700f96910e3eaa
--- /dev/null
+++ b/configs/res_parking.conf.sample
@@ -0,0 +1,48 @@
+[general]
+;parkeddynamic = yes        ; Enables dynamically created parkinglots. (default is no)
+
+
+; A parking lot named 'default' will automatically be used when no other
+; named parking lot is indicated for use by the park application or a
+; channel's parkinglot function and PARKINGLOT channel variable.
+
+[default]                                 ; based on the old default from features.conf.sample
+parkext => 700
+;parkext_exclusive=yes
+parkpos => 701-720
+context => parkedcalls
+;parkinghints = no
+;parkingtime => 45
+;comebacktoorigin = yes
+;comebackdialtime = 30
+;comebackcontext = parkedcallstimeout
+;courtesytone = beep
+;parkedplay = caller
+;parkedcalltransfers = caller
+;parkedcallreparking = caller
+;parkedcallhangup = caller
+;findslot => next
+;parkedmusicclass = default
+
+; Parking lots can now be any named configuration category aside from
+; 'general' which is reserved for general options.
+;
+; You can set parkinglot with the CHANNEL dialplan function or by setting
+; 'parkinglot' directly in the channel configuration file.
+;
+; (Note: Leading '0's and any non-numerical characters on parkpos
+; extensions will be ignored. Parkext on the other hand can be any string.)
+;
+;[edvina2]
+;context => edvina2_park
+;parkpos => 800-850
+;findslot => next
+;comebacktoorigin = no
+;comebackdialtime = 90
+;comebackcontext = edvina2_park-timeout
+;parkedmusicclass = edvina
+;
+; Since edvina2 doesn't define parkext, extensions won't automatically be
+; created for parking to it or for retrieving calls from it. These can be
+; created manually in the dial plan by using the Park and ParkedCall
+; applications.
diff --git a/funcs/func_channel.c b/funcs/func_channel.c
index db9434d834a433cc7c602ee125ea53c84dca225f..93792440074295aebeb9082f5348d69ecd1a89d5 100644
--- a/funcs/func_channel.c
+++ b/funcs/func_channel.c
@@ -343,6 +343,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 	</function>
  ***/
 
+/*
+ * BUGBUG add CHANNEL(after_bridge_goto)=<parseable-goto> Sets an after bridge goto datastore property on the channel.
+ * CHANNEL(after_bridge_goto)=<empty> Deletes any after bridge goto datastore property on the channel.
+ *
+ * BUGBUG add CHANNEL(dtmf_features)=tkhwx sets channel dtmf features to specified. (transfer, park, hangup, monitor, mixmonitor)
+ */
+
 #define locked_copy_string(chan, dest, source, len) \
 	do { \
 		ast_channel_lock(chan); \
diff --git a/funcs/func_frame_trace.c b/funcs/func_frame_trace.c
index 6410d12b80211d56e025706abae1bb270e0cc70e..94a2c13d0829d64005c86de3157abfd3f17e1945 100644
--- a/funcs/func_frame_trace.c
+++ b/funcs/func_frame_trace.c
@@ -376,6 +376,10 @@ static void print_frame(struct ast_frame *frame)
 		ast_verbose("Digit: 0x%02X '%c'\n", frame->subclass.integer,
 			frame->subclass.integer < ' ' ? ' ' : frame->subclass.integer);
 		break;
+	case AST_FRAME_BRIDGE_ACTION:
+		ast_verbose("FrameType: Bridge\n");
+		ast_verbose("SubClass: %d\n", frame->subclass.integer);
+		break;
 	}
 
 	ast_verbose("Src: %s\n", ast_strlen_zero(frame->src) ? "NOT PRESENT" : frame->src);
diff --git a/funcs/func_jitterbuffer.c b/funcs/func_jitterbuffer.c
index 066d9d2f626ec9c8b1e6dd0c98409eb11b5a3f7b..a00361043c2ac6210f0a8b9975189d582e8c36bc 100644
--- a/funcs/func_jitterbuffer.c
+++ b/funcs/func_jitterbuffer.c
@@ -36,6 +36,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/module.h"
 #include "asterisk/channel.h"
 #include "asterisk/framehook.h"
+#include "asterisk/frame.h"
 #include "asterisk/pbx.h"
 #include "asterisk/abstract_jb.h"
 #include "asterisk/timing.h"
@@ -48,7 +49,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 		</synopsis>
 		<syntax>
 			<parameter name="jitterbuffer type" required="true">
-				<para>Jitterbuffer type can be either <literal>fixed</literal> or <literal>adaptive</literal>.</para>
+				<para>Jitterbuffer type can be <literal>fixed</literal>, <literal>adaptive</literal>, or
+					<literal>disabled</literal>.</para>
 				<para>Used as follows. </para>
 				<para>Set(JITTERBUFFER(type)=max_size[,resync_threshold[,target_extra]])</para>
 				<para>Set(JITTERBUFFER(type)=default) </para>
@@ -70,85 +72,31 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 			<para>exten => 1,1,Set(JITTERBUFFER(fixed)=200,1500);Fixed with max size 200ms resync threshold 1500. </para>
 			<para>exten => 1,1,Set(JITTERBUFFER(adaptive)=default);Adaptive with defaults. </para>
 			<para>exten => 1,1,Set(JITTERBUFFER(adaptive)=200,,60);Adaptive with max size 200ms, default resync threshold and 40ms target extra. </para>
+			<para>exten => 1,n,Set(JITTERBUFFER(disabled)=);Remove previously applied jitterbuffer </para>
+			<note><para>If a channel specifies a jitterbuffer due to channel driver configuration and
+			the JITTERBUFFER function has set a jitterbuffer for that channel, the jitterbuffer set by
+			the JITTERBUFFER function will take priority and the jitterbuffer set by the channel
+			configuration will not be applied.</para></note>
 		</description>
 	</function>
  ***/
 
-#define DEFAULT_TIMER_INTERVAL 20
-#define DEFAULT_SIZE  200
-#define DEFAULT_TARGET_EXTRA  40
-#define DEFAULT_RESYNC  1000
-#define DEFAULT_TYPE AST_JB_FIXED
-
-struct jb_framedata {
-	const struct ast_jb_impl *jb_impl;
-	struct ast_jb_conf jb_conf;
-	struct timeval start_tv;
-	struct ast_format last_format;
-	struct ast_timer *timer;
-	int timer_interval; /* ms between deliveries */
-	int timer_fd;
-	int first;
-	void *jb_obj;
-};
-
-static void jb_framedata_destroy(struct jb_framedata *framedata)
-{
-	if (framedata->timer) {
-		ast_timer_close(framedata->timer);
-		framedata->timer = NULL;
-	}
-	if (framedata->jb_impl && framedata->jb_obj) {
-		struct ast_frame *f;
-		while (framedata->jb_impl->remove(framedata->jb_obj, &f) == AST_JB_IMPL_OK) {
-			ast_frfree(f);
-		}
-		framedata->jb_impl->destroy(framedata->jb_obj);
-		framedata->jb_obj = NULL;
-	}
-	ast_free(framedata);
-}
-
-static void jb_conf_default(struct ast_jb_conf *conf)
-{
-	conf->max_size = DEFAULT_SIZE;
-	conf->resync_threshold = DEFAULT_RESYNC;
-	ast_copy_string(conf->impl, "fixed", sizeof(conf->impl));
-	conf->target_extra = DEFAULT_TARGET_EXTRA;
-}
-
-/* set defaults */
-static int jb_framedata_init(struct jb_framedata *framedata, const char *data, const char *value)
+static int jb_helper(struct ast_channel *chan, const char *cmd, char *data, const char *value)
 {
-	int jb_impl_type = DEFAULT_TYPE;
-
-	/* Initialize defaults */
-	framedata->timer_fd = -1;
-	jb_conf_default(&framedata->jb_conf);
-	if (!(framedata->jb_impl = ast_jb_get_impl(jb_impl_type))) {
-		return -1;
-	}
-	if (!(framedata->timer = ast_timer_open())) {
-		return -1;
-	}
-	framedata->timer_fd = ast_timer_fd(framedata->timer);
-	framedata->timer_interval = DEFAULT_TIMER_INTERVAL;
-	ast_timer_set_rate(framedata->timer, 1000 / framedata->timer_interval);
-	framedata->start_tv = ast_tvnow();
-
+	struct ast_jb_conf jb_conf;
 
+	/* Initialize and set jb_conf */
+	ast_jb_conf_default(&jb_conf);
 
 	/* Now check user options to see if any of the defaults need to change. */
 	if (!ast_strlen_zero(data)) {
-		if (!strcasecmp(data, "fixed")) {
-			jb_impl_type = AST_JB_FIXED;
-		} else if (!strcasecmp(data, "adaptive")) {
-			jb_impl_type = AST_JB_ADAPTIVE;
-		} else {
+		if (strcasecmp(data, "fixed") &&
+				strcasecmp(data, "adaptive") &&
+				strcasecmp(data, "disabled")) {
 			ast_log(LOG_WARNING, "Unknown Jitterbuffer type %s. Failed to create jitterbuffer.\n", data);
 			return -1;
 		}
-		ast_copy_string(framedata->jb_conf.impl, data, sizeof(framedata->jb_conf.impl));
+		ast_copy_string(jb_conf.impl, data, sizeof(jb_conf.impl));
 	}
 
 	if (!ast_strlen_zero(value) && strcasecmp(value, "default")) {
@@ -162,17 +110,17 @@ static int jb_framedata_init(struct jb_framedata *framedata, const char *data, c
 
 		AST_STANDARD_APP_ARGS(args, parse);
 		if (!ast_strlen_zero(args.max_size)) {
-			res |= ast_jb_read_conf(&framedata->jb_conf,
+			res |= ast_jb_read_conf(&jb_conf,
 				"jbmaxsize",
 				args.max_size);
 		}
 		if (!ast_strlen_zero(args.resync_threshold)) {
-			res |= ast_jb_read_conf(&framedata->jb_conf,
+			res |= ast_jb_read_conf(&jb_conf,
 				"jbresyncthreshold",
 				args.resync_threshold);
 		}
 		if (!ast_strlen_zero(args.target_extra)) {
-			res |= ast_jb_read_conf(&framedata->jb_conf,
+			res |= ast_jb_read_conf(&jb_conf,
 				"jbtargetextra",
 				args.target_extra);
 		}
@@ -181,198 +129,12 @@ static int jb_framedata_init(struct jb_framedata *framedata, const char *data, c
 		}
 	}
 
-	/* now that all the user parsing is done and nothing will change, create the jb obj */
-	framedata->jb_obj = framedata->jb_impl->create(&framedata->jb_conf);
-	return 0;
-}
-
-static void datastore_destroy_cb(void *data) {
-	ast_free(data);
-	ast_debug(1, "JITTERBUFFER datastore destroyed\n");
-}
-
-static const struct ast_datastore_info jb_datastore = {
-	.type = "jitterbuffer",
-	.destroy = datastore_destroy_cb
-};
-
-static void hook_destroy_cb(void *framedata)
-{
-	ast_debug(1, "JITTERBUFFER hook destroyed\n");
-	jb_framedata_destroy((struct jb_framedata *) framedata);
-}
-
-static struct ast_frame *hook_event_cb(struct ast_channel *chan, struct ast_frame *frame, enum ast_framehook_event event, void *data)
-{
-	struct jb_framedata *framedata = data;
-	struct timeval now_tv;
-	unsigned long now;
-	int putframe = 0; /* signifies if audio frame was placed into the buffer or not */
-
-	switch (event) {
-	case AST_FRAMEHOOK_EVENT_READ:
-		break;
-	case AST_FRAMEHOOK_EVENT_ATTACHED:
-	case AST_FRAMEHOOK_EVENT_DETACHED:
-	case AST_FRAMEHOOK_EVENT_WRITE:
-		return frame;
-	}
-
-	if (ast_channel_fdno(chan) == AST_JITTERBUFFER_FD && framedata->timer) {
-		if (ast_timer_ack(framedata->timer, 1) < 0) {
-			ast_log(LOG_ERROR, "Failed to acknowledge timer in jitter buffer\n");
-			return frame;
-		}
-	}
-
-	if (!frame) {
-		return frame;
-	}
-
-	now_tv = ast_tvnow();
-	now = ast_tvdiff_ms(now_tv, framedata->start_tv);
-
-	if (frame->frametype == AST_FRAME_VOICE) {
-		int res;
-		struct ast_frame *jbframe;
-
-		if (!ast_test_flag(frame, AST_FRFLAG_HAS_TIMING_INFO) || frame->len < 2 || frame->ts < 0) {
-			/* only frames with timing info can enter the jitterbuffer */
-			return frame;
-		}
-
-		jbframe = ast_frisolate(frame);
-		ast_format_copy(&framedata->last_format, &frame->subclass.format);
-
-		if (frame->len && (frame->len != framedata->timer_interval)) {
-			framedata->timer_interval = frame->len;
-			ast_timer_set_rate(framedata->timer, 1000 / framedata->timer_interval);
-		}
-		if (!framedata->first) {
-			framedata->first = 1;
-			res = framedata->jb_impl->put_first(framedata->jb_obj, jbframe, now);
-		} else {
-			res = framedata->jb_impl->put(framedata->jb_obj, jbframe, now);
-		}
-		if (res == AST_JB_IMPL_OK) {
-			frame = &ast_null_frame;
-		}
-		putframe = 1;
-	}
-
-	if (frame->frametype == AST_FRAME_NULL) {
-		int res;
-		long next = framedata->jb_impl->next(framedata->jb_obj);
-
-		/* If now is earlier than the next expected output frame
-		 * from the jitterbuffer we may choose to pass on retrieving
-		 * a frame during this read iteration.  The only exception
-		 * to this rule is when an audio frame is placed into the buffer
-		 * and the time for the next frame to come out of the buffer is
-		 * at least within the timer_interval of the next output frame. By
-		 * doing this we are able to feed off the timing of the input frames
-		 * and only rely on our jitterbuffer timer when frames are dropped.
-		 * During testing, this hybrid form of timing gave more reliable results. */
-		if (now < next) {
-			long int diff = next - now;
-			if (!putframe) {
-				return frame;
-			} else if (diff >= framedata->timer_interval) {
-				return frame;
-			}
-		}
-
-		res = framedata->jb_impl->get(framedata->jb_obj, &frame, now, framedata->timer_interval);
-		switch (res) {
-		case AST_JB_IMPL_OK:
-			/* got it, and pass it through */
-			break;
-		case AST_JB_IMPL_DROP:
-			ast_frfree(frame);
-			frame = &ast_null_frame;
-			break;
-		case AST_JB_IMPL_INTERP:
-			if (framedata->last_format.id) {
-				struct ast_frame tmp = { 0, };
-				tmp.frametype = AST_FRAME_VOICE;
-				ast_format_copy(&tmp.subclass.format, &framedata->last_format);
-				/* example: 8000hz / (1000 / 20ms) = 160 samples */
-				tmp.samples = ast_format_rate(&framedata->last_format) / (1000 / framedata->timer_interval);
-				tmp.delivery = ast_tvadd(framedata->start_tv, ast_samp2tv(next, 1000));
-				tmp.offset = AST_FRIENDLY_OFFSET;
-				tmp.src  = "func_jitterbuffer interpolation";
-				frame = ast_frdup(&tmp);
-				break;
-			}
-			/* else fall through */
-		case AST_JB_IMPL_NOFRAME:
-			frame = &ast_null_frame;
-			break;
-		}
-	}
-
-	return frame;
-}
-
-static int jb_helper(struct ast_channel *chan, const char *cmd, char *data, const char *value)
-{
-	struct jb_framedata *framedata;
-	struct ast_datastore *datastore = NULL;
-	struct ast_framehook_interface interface = {
-		.version = AST_FRAMEHOOK_INTERFACE_VERSION,
-		.event_cb = hook_event_cb,
-		.destroy_cb = hook_destroy_cb,
-	};
-	int i = 0;
-
-	if (!(framedata = ast_calloc(1, sizeof(*framedata)))) {
-		return 0;
-	}
-
-	if (jb_framedata_init(framedata, data, value)) {
-		jb_framedata_destroy(framedata);
-		return 0;
-	}
-
-	interface.data = framedata;
-
-	ast_channel_lock(chan);
-	i = ast_framehook_attach(chan, &interface);
-	if (i >= 0) {
-		int *id;
-		if ((datastore = ast_channel_datastore_find(chan, &jb_datastore, NULL))) {
-			id = datastore->data;
-			ast_framehook_detach(chan, *id);
-			ast_channel_datastore_remove(chan, datastore);
-		}
-
-		if (!(datastore = ast_datastore_alloc(&jb_datastore, NULL))) {
-			ast_framehook_detach(chan, i);
-			ast_channel_unlock(chan);
-			return 0;
-		}
-
-		if (!(id = ast_calloc(1, sizeof(int)))) {
-			ast_datastore_free(datastore);
-			ast_framehook_detach(chan, i);
-			ast_channel_unlock(chan);
-			return 0;
-		}
-
-		*id = i; /* Store off the id. The channel is still locked so it is safe to access this ptr. */
-		datastore->data = id;
-		ast_channel_datastore_add(chan, datastore);
-
-		ast_channel_set_fd(chan, AST_JITTERBUFFER_FD, framedata->timer_fd);
-	} else {
-		jb_framedata_destroy(framedata);
-		framedata = NULL;
-	}
-	ast_channel_unlock(chan);
+	ast_jb_create_framehook(chan, &jb_conf, 0);
 
 	return 0;
 }
 
+
 static struct ast_custom_function jb_function = {
 	.name = "JITTERBUFFER",
 	.write = jb_helper,
diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h
index 7e1ef13ec490ec8336e4f7c08fbd3cef2e6210f6..3fe35e58ceecd697b25a6c683167fc97a80aaa1d 100644
--- a/include/asterisk/_private.h
+++ b/include/asterisk/_private.h
@@ -51,6 +51,24 @@ int ast_msg_init(void);             /*!< Provided by message.c */
 void ast_msg_shutdown(void);        /*!< Provided by message.c */
 int aco_init(void);             /*!< Provided by config_options.c */
 
+/*!
+ * \brief Initialize the bridging system.
+ * \since 12.0.0
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_bridging_init(void);
+
+/*!
+ * \brief Initialize the local proxy channel.
+ * \since 12.0.0
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_local_init(void);
+
 /*!
  * \brief Reload asterisk modules.
  * \param name the name of the module to reload
diff --git a/include/asterisk/abstract_jb.h b/include/asterisk/abstract_jb.h
index 3e6bedd269cc36cd3d974f40a052418115a6c621..6a4d0610d126729fd3bb82884687d654ed6b7bd9 100644
--- a/include/asterisk/abstract_jb.h
+++ b/include/asterisk/abstract_jb.h
@@ -246,6 +246,14 @@ void ast_jb_destroy(struct ast_channel *chan);
  */
 int ast_jb_read_conf(struct ast_jb_conf *conf, const char *varname, const char *value);
 
+/*!
+ * \since 12.0
+ * \brief Sets a jitterbuffer frame hook on the channel based on the channel's stored
+ *        jitterbuffer configuration
+ *
+ * \param chan Which channel is being set up
+ */
+void ast_jb_enable_for_channel(struct ast_channel *chan);
 
 /*!
  * \brief Configures a jitterbuffer on a channel.
@@ -257,7 +265,6 @@ int ast_jb_read_conf(struct ast_jb_conf *conf, const char *varname, const char *
  */
 void ast_jb_configure(struct ast_channel *chan, const struct ast_jb_conf *conf);
 
-
 /*!
  * \brief Copies a channel's jitterbuffer configuration.
  * \param chan channel.
@@ -274,6 +281,25 @@ void ast_jb_empty_and_reset(struct ast_channel *c0, struct ast_channel *c1);
 
 const struct ast_jb_impl *ast_jb_get_impl(enum ast_jb_type type);
 
+/*!
+ * \since 12
+ * \brief Sets the contents of an ast_jb_conf struct to the default jitterbuffer settings
+ *
+ * \param conf Which jitterbuffer is being set
+ */
+void ast_jb_conf_default(struct ast_jb_conf *conf);
+
+/*!
+ * \since 12
+ * \brief Applies a jitterbuffer framehook to a channel based on a provided jitterbuffer config
+ *
+ * \param chan Which channel the jitterbuffer is being set on
+ * \param jb_conf Configuration to use for the jitterbuffer
+ * \param prefer_existing If this is true and a jitterbuffer already exists for the channel,
+ *        use the existing jitterbuffer
+ */
+void ast_jb_create_framehook(struct ast_channel *chan, struct ast_jb_conf *jb_conf, int prefer_existing);
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif
diff --git a/include/asterisk/bridging.h b/include/asterisk/bridging.h
index 2890b1dfa7d90446350d01eff299c374699458b5..77fb54e8ddbe7e4e5d0619679431f7339183b44c 100644
--- a/include/asterisk/bridging.h
+++ b/include/asterisk/bridging.h
@@ -1,8 +1,9 @@
 /*
  * Asterisk -- An open source telephony toolkit.
  *
- * Copyright (C) 2007 - 2009, Digium, Inc.
+ * Copyright (C) 2007 - 2009, 2013 Digium, Inc.
  *
+ * Richard Mudgett <rmudgett@digium.com>
  * Joshua Colp <jcolp@digium.com>
  *
  * See http://www.asterisk.org for more information about
@@ -16,10 +17,16 @@
  * at the top of the source tree.
  */
 
-/*! \file
+/*!
+ * \file
  * \brief Channel Bridging API
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
  * \author Joshua Colp <jcolp@digium.com>
  * \ref AstBridging
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
  */
 
 /*!
@@ -63,54 +70,41 @@ extern "C" {
 #endif
 
 #include "asterisk/bridging_features.h"
+#include "asterisk/bridging_roles.h"
 #include "asterisk/dsp.h"
+#include "asterisk/uuid.h"
 
 /*! \brief Capabilities for a bridge technology */
 enum ast_bridge_capability {
-	/*! Bridge is only capable of mixing 2 channels */
-	AST_BRIDGE_CAPABILITY_1TO1MIX = (1 << 1),
-	/*! Bridge is capable of mixing 2 or more channels */
-	AST_BRIDGE_CAPABILITY_MULTIMIX = (1 << 2),
-	/*! Bridge should natively bridge two channels if possible */
-	AST_BRIDGE_CAPABILITY_NATIVE = (1 << 3),
-	/*! Bridge should run using the multithreaded model */
-	AST_BRIDGE_CAPABILITY_MULTITHREADED = (1 << 4),
-	/*! Bridge should run a central bridge thread */
-	AST_BRIDGE_CAPABILITY_THREAD = (1 << 5),
-	/*! Bridge technology can do video mixing (or something along those lines) */
-	AST_BRIDGE_CAPABILITY_VIDEO = (1 << 6),
-	/*! Bridge technology can optimize things based on who is talking */
-	AST_BRIDGE_CAPABILITY_OPTIMIZE = (1 << 7),
+	/*! Bridge technology can service calls on hold. */
+	AST_BRIDGE_CAPABILITY_HOLDING = (1 << 0),
+	/*! Bridge waits for channel to answer.  Passes early media. (XXX Not supported yet) */
+	AST_BRIDGE_CAPABILITY_EARLY = (1 << 1),
+	/*! Bridge is capable of natively bridging two channels. (Smart bridge only) */
+	AST_BRIDGE_CAPABILITY_NATIVE = (1 << 2),
+	/*! Bridge is capable of mixing at most two channels. (Smart bridgeable) */
+	AST_BRIDGE_CAPABILITY_1TO1MIX = (1 << 3),
+	/*! Bridge is capable of mixing an arbitrary number of channels. (Smart bridgeable) */
+	AST_BRIDGE_CAPABILITY_MULTIMIX = (1 << 4),
 };
 
 /*! \brief State information about a bridged channel */
 enum ast_bridge_channel_state {
 	/*! Waiting for a signal (Channel in the bridge) */
 	AST_BRIDGE_CHANNEL_STATE_WAIT = 0,
-	/*! Bridged channel has ended itself (it has hung up) */
+	/*! Bridged channel was forced out and should be hung up (Bridge may dissolve.) */
 	AST_BRIDGE_CHANNEL_STATE_END,
 	/*! Bridged channel was forced out and should be hung up */
 	AST_BRIDGE_CHANNEL_STATE_HANGUP,
-	/*! Bridged channel was ast_bridge_depart() from the bridge without being hung up */
-	AST_BRIDGE_CHANNEL_STATE_DEPART,
-	/*! Bridged channel is executing a feature hook */
-	AST_BRIDGE_CHANNEL_STATE_FEATURE,
-	/*! Bridged channel is sending a DTMF stream out */
-	AST_BRIDGE_CHANNEL_STATE_DTMF,
-	/*! Bridged channel began talking */
-	AST_BRIDGE_CHANNEL_STATE_START_TALKING,
-	/*! Bridged channel has stopped talking */
-	AST_BRIDGE_CHANNEL_STATE_STOP_TALKING,
 };
 
-/*! \brief Return values for bridge technology write function */
-enum ast_bridge_write_result {
-	/*! Bridge technology wrote out frame fine */
-	AST_BRIDGE_WRITE_SUCCESS = 0,
-	/*! Bridge technology attempted to write out the frame but failed */
-	AST_BRIDGE_WRITE_FAILED,
-	/*! Bridge technology does not support writing out a frame of this type */
-	AST_BRIDGE_WRITE_UNSUPPORTED,
+enum ast_bridge_channel_thread_state {
+	/*! Bridge channel thread is idle/waiting. */
+	AST_BRIDGE_CHANNEL_THREAD_IDLE,
+	/*! Bridge channel thread is writing a normal/simple frame. */
+	AST_BRIDGE_CHANNEL_THREAD_SIMPLE,
+	/*! Bridge channel thread is processing a frame. */
+	AST_BRIDGE_CHANNEL_THREAD_FRAME,
 };
 
 struct ast_bridge_technology;
@@ -136,6 +130,7 @@ struct ast_bridge_tech_optimizations {
  * \brief Structure that contains information regarding a channel in a bridge
  */
 struct ast_bridge_channel {
+/* BUGBUG cond is only here because of external party suspend/unsuspend support. */
 	/*! Condition, used if we want to wake up a thread waiting on the bridged channel */
 	ast_cond_t cond;
 	/*! Current bridged channel state */
@@ -144,29 +139,103 @@ struct ast_bridge_channel {
 	struct ast_channel *chan;
 	/*! Asterisk channel we are swapping with (if swapping) */
 	struct ast_channel *swap;
-	/*! Bridge this channel is participating in */
+	/*!
+	 * \brief Bridge this channel is participating in
+	 *
+	 * \note The bridge pointer cannot change while the bridge or
+	 * bridge_channel is locked.
+	 */
 	struct ast_bridge *bridge;
-	/*! Private information unique to the bridge technology */
+	/*!
+	 * \brief Bridge class private channel data.
+	 *
+	 * \note This information is added when the channel is pushed
+	 * into the bridge and removed when it is pulled from the
+	 * bridge.
+	 */
 	void *bridge_pvt;
-	/*! Thread handling the bridged channel */
+	/*!
+	 * \brief Private information unique to the bridge technology.
+	 *
+	 * \note This information is added when the channel joins the
+	 * bridge's technology and removed when it leaves the bridge's
+	 * technology.
+	 */
+	void *tech_pvt;
+	/*! Thread handling the bridged channel (Needed by ast_bridge_depart) */
 	pthread_t thread;
-	/*! Additional file descriptors to look at */
-	int fds[4];
-	/*! Bit to indicate whether the channel is suspended from the bridge or not */
+	/* v-- These flags change while the bridge is locked or before the channel is in the bridge. */
+	/*! TRUE if the channel is in a bridge. */
+	unsigned int in_bridge:1;
+	/*! TRUE if the channel just joined the bridge. */
+	unsigned int just_joined:1;
+	/*! TRUE if the channel is suspended from the bridge. */
 	unsigned int suspended:1;
-	/*! Bit to indicate if a imparted channel is allowed to get hungup after leaving the bridge by the bridging core. */
-	unsigned int allow_impart_hangup:1;
+	/*! TRUE if the channel must wait for an ast_bridge_depart to reclaim the channel. */
+	unsigned int depart_wait:1;
+	/* ^-- These flags change while the bridge is locked or before the channel is in the bridge. */
 	/*! Features structure for features that are specific to this channel */
 	struct ast_bridge_features *features;
 	/*! Technology optimization parameters used by bridging technologies capable of
 	 *  optimizing based upon talk detection. */
 	struct ast_bridge_tech_optimizations tech_args;
-	/*! Queue of DTMF digits used for DTMF streaming */
-	char dtmf_stream_q[8];
+	/*! Copy of read format used by chan before join */
+	struct ast_format read_format;
+	/*! Copy of write format used by chan before join */
+	struct ast_format write_format;
 	/*! Call ID associated with bridge channel */
 	struct ast_callid *callid;
+	/*! A clone of the roles living on chan when the bridge channel joins the bridge. This may require some opacification */
+	struct bridge_roles_datastore *bridge_roles;
 	/*! Linked list information */
 	AST_LIST_ENTRY(ast_bridge_channel) entry;
+	/*! Queue of outgoing frames to the channel. */
+	AST_LIST_HEAD_NOLOCK(, ast_frame) wr_queue;
+	/*! Pipe to alert thread when frames are put into the wr_queue. */
+	int alert_pipe[2];
+	/*! TRUE if the bridge channel thread is waiting on channels (needs to be atomically settable) */
+	int waiting;
+	/*!
+	 * \brief The bridge channel thread activity.
+	 *
+	 * \details Used by local channel optimization to determine if
+	 * the thread is in an acceptable state to optimize.
+	 *
+	 * \note Needs to be atomically settable.
+	 */
+	enum ast_bridge_channel_thread_state activity;
+};
+
+enum ast_bridge_action_type {
+	/*! Bridged channel is to detect a feature hook */
+	AST_BRIDGE_ACTION_FEATURE,
+	/*! Bridged channel is to act on an interval hook */
+	AST_BRIDGE_ACTION_INTERVAL,
+	/*! Bridged channel is to send a DTMF stream out */
+	AST_BRIDGE_ACTION_DTMF_STREAM,
+	/*! Bridged channel is to indicate talking start */
+	AST_BRIDGE_ACTION_TALKING_START,
+	/*! Bridged channel is to indicate talking stop */
+	AST_BRIDGE_ACTION_TALKING_STOP,
+	/*! Bridge channel is to play the indicated sound file. */
+	AST_BRIDGE_ACTION_PLAY_FILE,
+	/*! Bridge channel is to get parked. */
+	AST_BRIDGE_ACTION_PARK,
+	/*! Bridge channel is to run the indicated application. */
+	AST_BRIDGE_ACTION_RUN_APP,
+	/*! Bridge channel is to execute a blind transfer. */
+	AST_BRIDGE_ACTION_BLIND_TRANSFER,
+
+	/*
+	 * Bridge actions put after this comment must never be put onto
+	 * the bridge_channel wr_queue because they have other resources
+	 * that must be freed.
+	 */
+
+	/*! Bridge reconfiguration deferred technology destruction. */
+	AST_BRIDGE_ACTION_DEFERRED_TECH_DESTROY = 1000,
+	/*! Bridge deferred dissolving. */
+	AST_BRIDGE_ACTION_DEFERRED_DISSOLVING,
 };
 
 enum ast_bridge_video_mode_type {
@@ -206,14 +275,152 @@ struct ast_bridge_video_mode {
 	} mode_data;
 };
 
+/*!
+ * \brief Destroy the bridge.
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_bridge_destructor_fn)(struct ast_bridge *self);
+
+/*!
+ * \brief The bridge is being dissolved.
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \details
+ * The bridge is being dissolved.  Remove any external
+ * references to the bridge so it can be destroyed.
+ *
+ * \note On entry, self must NOT be locked.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_bridge_dissolving_fn)(struct ast_bridge *self);
+
+/*!
+ * \brief Push this channel into the bridge.
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to push.
+ * \param swap Bridge channel to swap places with if not NULL.
+ *
+ * \details
+ * Setup any channel hooks controlled by the bridge.  Allocate
+ * bridge_channel->bridge_pvt and initialize any resources put
+ * in bridge_channel->bridge_pvt if needed.  If there is a swap
+ * channel, use it as a guide to setting up the bridge_channel.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.  The channel did not get pushed.
+ */
+typedef int (*ast_bridge_push_channel_fn)(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap);
+
+/*!
+ * \brief Pull this channel from the bridge.
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to pull.
+ *
+ * \details
+ * Remove any channel hooks controlled by the bridge.  Release
+ * any resources held by bridge_channel->bridge_pvt and release
+ * bridge_channel->bridge_pvt.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_bridge_pull_channel_fn)(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel);
+
+/*!
+ * \brief Notify the bridge that this channel was just masqueraded.
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel that was masqueraded.
+ *
+ * \details
+ * A masquerade just happened to this channel.  The bridge needs
+ * to re-evaluate this a channel in the bridge.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_bridge_notify_masquerade_fn)(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel);
+
+/*!
+ * \brief Get the merge priority of this bridge.
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Merge priority
+ */
+typedef int (*ast_bridge_merge_priority_fn)(struct ast_bridge *self);
+
+/*!
+ * \brief Bridge virtual methods table definition.
+ *
+ * \note Any changes to this struct must be reflected in
+ * ast_bridge_alloc() validity checking.
+ */
+struct ast_bridge_methods {
+	/*! Bridge class name for log messages. */
+	const char *name;
+	/*! Destroy the bridge. */
+	ast_bridge_destructor_fn destroy;
+	/*! The bridge is being dissolved.  Remove any references to the bridge. */
+	ast_bridge_dissolving_fn dissolving;
+	/*! Push the bridge channel into the bridge. */
+	ast_bridge_push_channel_fn push;
+	/*! Pull the bridge channel from the bridge. */
+	ast_bridge_pull_channel_fn pull;
+	/*! Notify the bridge of a masquerade with the channel. */
+	ast_bridge_notify_masquerade_fn notify_masquerade;
+	/*! Get the bridge merge priority. */
+	ast_bridge_merge_priority_fn get_merge_priority;
+};
+
 /*!
  * \brief Structure that contains information about a bridge
  */
 struct ast_bridge {
-	/*! Number of channels participating in the bridge */
-	int num;
+	/*! Bridge virtual method table. */
+	const struct ast_bridge_methods *v_table;
+	/*! Immutable bridge UUID. */
+	char uniqueid[AST_UUID_STR_LEN];
+	/*! Bridge technology that is handling the bridge */
+	struct ast_bridge_technology *technology;
+	/*! Private information unique to the bridge technology */
+	void *tech_pvt;
+	/*! Call ID associated with the bridge */
+	struct ast_callid *callid;
+	/*! Linked list of channels participating in the bridge */
+	AST_LIST_HEAD_NOLOCK(, ast_bridge_channel) channels;
+	/*! Queue of actions to perform on the bridge. */
+	AST_LIST_HEAD_NOLOCK(, ast_frame) action_queue;
 	/*! The video mode this bridge is using */
 	struct ast_bridge_video_mode video_mode;
+	/*! Bridge flags to tweak behavior */
+	struct ast_flags feature_flags;
+	/*! Allowed bridge technology capabilities when AST_BRIDGE_FLAG_SMART enabled. */
+	uint32_t allowed_capabilities;
+	/*! Number of channels participating in the bridge */
+	unsigned int num_channels;
+	/*! Number of active channels in the bridge. */
+	unsigned int num_active;
+	/*!
+	 * \brief Count of the active temporary requests to inhibit bridge merges.
+	 * Zero if merges are allowed.
+	 *
+	 * \note Temporary as in try again in a moment.
+	 */
+	unsigned int inhibit_merge;
 	/*! The internal sample rate this bridge is mixed at when multiple channels are being mixed.
 	 *  If this value is 0, the bridge technology may auto adjust the internal mixing rate. */
 	unsigned int internal_sample_rate;
@@ -221,36 +428,83 @@ struct ast_bridge {
 	 * for bridge technologies that mix audio. When set to 0, the bridge tech must choose a
 	 * default interval for itself. */
 	unsigned int internal_mixing_interval;
-	/*! Bit to indicate that the bridge thread is waiting on channels in the bridge array */
-	unsigned int waiting:1;
-	/*! Bit to indicate the bridge thread should stop */
-	unsigned int stop:1;
-	/*! Bit to indicate the bridge thread should refresh itself */
-	unsigned int refresh:1;
-	/*! Bridge flags to tweak behavior */
-	struct ast_flags feature_flags;
-	/*! Bridge technology that is handling the bridge */
-	struct ast_bridge_technology *technology;
-	/*! Private information unique to the bridge technology */
-	void *bridge_pvt;
-	/*! Thread running the bridge */
-	pthread_t thread;
-	/*! Enabled features information */
-	struct ast_bridge_features features;
-	/*! Array of channels that the bridge thread is currently handling */
-	struct ast_channel **array;
-	/*! Number of channels in the above array */
-	size_t array_num;
-	/*! Number of channels the array can handle */
-	size_t array_size;
-	/*! Call ID associated with the bridge */
-	struct ast_callid *callid;
-	/*! Linked list of channels participating in the bridge */
-	AST_LIST_HEAD_NOLOCK(, ast_bridge_channel) channels;
-	};
+	/*! TRUE if the bridge was reconfigured. */
+	unsigned int reconfigured:1;
+	/*! TRUE if the bridge has been dissolved.  Any channel that now tries to join is immediately ejected. */
+	unsigned int dissolved:1;
+};
+
+/*!
+ * \brief Register the new bridge with the system.
+ * \since 12.0.0
+ *
+ * \param bridge What to register. (Tolerates a NULL pointer)
+ *
+ * \code
+ * struct ast_bridge *ast_bridge_basic_new(uint32_t capabilities, int flags, uint32 dtmf_features)
+ * {
+ *     void *bridge;
+ *
+ *     bridge = ast_bridge_alloc(sizeof(struct ast_bridge_basic), &ast_bridge_basic_v_table);
+ *     bridge = ast_bridge_base_init(bridge, capabilities, flags);
+ *     bridge = ast_bridge_basic_init(bridge, dtmf_features);
+ *     bridge = ast_bridge_register(bridge);
+ *     return bridge;
+ * }
+ * \endcode
+ *
+ * \note This must be done after a bridge constructor has
+ * completed setting up the new bridge but before it returns.
+ *
+ * \note After a bridge is registered, ast_bridge_destroy() must
+ * eventually be called to get rid of the bridge.
+ *
+ * \retval bridge on success.
+ * \retval NULL on error.
+ */
+struct ast_bridge *ast_bridge_register(struct ast_bridge *bridge);
+
+/*!
+ * \internal
+ * \brief Allocate the bridge class object memory.
+ * \since 12.0.0
+ *
+ * \param size Size of the bridge class structure to allocate.
+ * \param v_table Bridge class virtual method table.
+ *
+ * \retval bridge on success.
+ * \retval NULL on error.
+ */
+struct ast_bridge *ast_bridge_alloc(size_t size, const struct ast_bridge_methods *v_table);
+
+/*! \brief Bridge base class virtual method table. */
+extern struct ast_bridge_methods ast_bridge_base_v_table;
 
 /*!
- * \brief Create a new bridge
+ * \brief Initialize the base class of the bridge.
+ *
+ * \param self Bridge to operate upon. (Tolerates a NULL pointer)
+ * \param capabilities The capabilities that we require to be used on the bridge
+ * \param flags Flags that will alter the behavior of the bridge
+ *
+ * \retval self on success
+ * \retval NULL on failure, self is already destroyed
+ *
+ * Example usage:
+ *
+ * \code
+ * struct ast_bridge *bridge;
+ * bridge = ast_bridge_alloc(sizeof(*bridge), &ast_bridge_base_v_table);
+ * bridge = ast_bridge_base_init(bridge, AST_BRIDGE_CAPABILITY_1TO1MIX, AST_BRIDGE_FLAG_DISSOLVE_HANGUP);
+ * \endcode
+ *
+ * This creates a no frills two party bridge that will be
+ * destroyed once one of the channels hangs up.
+ */
+struct ast_bridge *ast_bridge_base_init(struct ast_bridge *self, uint32_t capabilities, unsigned int flags);
+
+/*!
+ * \brief Create a new base class bridge
  *
  * \param capabilities The capabilities that we require to be used on the bridge
  * \param flags Flags that will alter the behavior of the bridge
@@ -262,13 +516,27 @@ struct ast_bridge {
  *
  * \code
  * struct ast_bridge *bridge;
- * bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_1TO1MIX, AST_BRIDGE_FLAG_DISSOLVE);
+ * bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_1TO1MIX, AST_BRIDGE_FLAG_DISSOLVE_HANGUP);
  * \endcode
  *
- * This creates a simple two party bridge that will be destroyed once one of
- * the channels hangs up.
+ * This creates a no frills two party bridge that will be
+ * destroyed once one of the channels hangs up.
  */
-struct ast_bridge *ast_bridge_new(uint32_t capabilities, int flags);
+struct ast_bridge *ast_bridge_base_new(uint32_t capabilities, unsigned int flags);
+
+/*!
+ * \brief Try locking the bridge.
+ *
+ * \param bridge Bridge to try locking
+ *
+ * \retval 0 on success.
+ * \retval non-zero on error.
+ */
+#define ast_bridge_trylock(bridge)	_ast_bridge_trylock(bridge, __FILE__, __PRETTY_FUNCTION__, __LINE__, #bridge)
+static inline int _ast_bridge_trylock(struct ast_bridge *bridge, const char *file, const char *function, int line, const char *var)
+{
+	return __ao2_trylock(bridge, AO2_LOCK_REQ_MUTEX, file, function, line, var);
+}
 
 /*!
  * \brief Lock the bridge.
@@ -296,24 +564,18 @@ static inline void _ast_bridge_unlock(struct ast_bridge *bridge, const char *fil
 	__ao2_unlock(bridge, file, function, line, var);
 }
 
-/*!
- * \brief See if it is possible to create a bridge
- *
- * \param capabilities The capabilities that the bridge will use
- *
- * \retval 1 if possible
- * \retval 0 if not possible
- *
- * Example usage:
- *
- * \code
- * int possible = ast_bridge_check(AST_BRIDGE_CAPABILITY_1TO1MIX);
- * \endcode
- *
- * This sees if it is possible to create a bridge capable of bridging two channels
- * together.
- */
-int ast_bridge_check(uint32_t capabilities);
+/*! \brief Lock two bridges. */
+#define ast_bridge_lock_both(bridge1, bridge2)		\
+	do {											\
+		for (;;) {									\
+			ast_bridge_lock(bridge1);				\
+			if (!ast_bridge_trylock(bridge2)) {		\
+				break;								\
+			}										\
+			ast_bridge_unlock(bridge1);				\
+			sched_yield();							\
+		}											\
+	} while (0)
 
 /*!
  * \brief Destroy a bridge
@@ -329,10 +591,20 @@ int ast_bridge_check(uint32_t capabilities);
  * ast_bridge_destroy(bridge);
  * \endcode
  *
- * This destroys a bridge that was previously created using ast_bridge_new.
+ * This destroys a bridge that was previously created.
  */
 int ast_bridge_destroy(struct ast_bridge *bridge);
 
+/*!
+ * \brief Notify bridging that this channel was just masqueraded.
+ * \since 12.0.0
+ *
+ * \param chan Channel just involved in a masquerade
+ *
+ * \return Nothing
+ */
+void ast_bridge_notify_masquerade(struct ast_channel *chan);
+
 /*!
  * \brief Join (blocking) a channel to a bridge
  *
@@ -341,13 +613,17 @@ int ast_bridge_destroy(struct ast_bridge *bridge);
  * \param swap Channel to swap out if swapping
  * \param features Bridge features structure
  * \param tech_args Optional Bridging tech optimization parameters for this channel.
+ * \param pass_reference TRUE if the bridge reference is being passed by the caller.
+ *
+ * \note Absolutely _NO_ locks should be held before calling
+ * this function since it blocks.
  *
  * \retval state that channel exited the bridge with
  *
  * Example usage:
  *
  * \code
- * ast_bridge_join(bridge, chan, NULL, NULL);
+ * ast_bridge_join(bridge, chan, NULL, NULL, NULL, 0);
  * \endcode
  *
  * This adds a channel pointed to by the chan pointer to the bridge pointed to by
@@ -365,16 +641,23 @@ enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge,
 	struct ast_channel *chan,
 	struct ast_channel *swap,
 	struct ast_bridge_features *features,
-	struct ast_bridge_tech_optimizations *tech_args);
+	struct ast_bridge_tech_optimizations *tech_args,
+	int pass_reference);
 
 /*!
  * \brief Impart (non-blocking) a channel onto a bridge
  *
  * \param bridge Bridge to impart on
  * \param chan Channel to impart
- * \param swap Channel to swap out if swapping
- * \param features Bridge features structure
- * \param allow_hangup  Indicates if the bridge thread should manage hanging up of the channel or not.
+ * \param swap Channel to swap out if swapping.  NULL if not swapping.
+ * \param features Bridge features structure.
+ * \param independent TRUE if caller does not want to reclaim the channel using ast_bridge_depart().
+ *
+ * \note The features parameter must be NULL or obtained by
+ * ast_bridge_features_new().  You must not dereference features
+ * after calling even if the call fails.
+ *
+ * \note chan is locked by this function.
  *
  * \retval 0 on success
  * \retval -1 on failure
@@ -385,42 +668,60 @@ enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge,
  * ast_bridge_impart(bridge, chan, NULL, NULL, 0);
  * \endcode
  *
- * This adds a channel pointed to by the chan pointer to the bridge pointed to by
- * the bridge pointer. This function will return immediately and will not wait
- * until the channel is no longer part of the bridge.
- *
- * If this channel will be replacing another channel the other channel can be specified
- * in the swap parameter. The other channel will be thrown out of the bridge in an
- * atomic fashion.
- *
- * If channel specific features are enabled a pointer to the features structure
- * can be specified in the features parameter.
+ * \details
+ * This adds a channel pointed to by the chan pointer to the
+ * bridge pointed to by the bridge pointer.  This function will
+ * return immediately and will not wait until the channel is no
+ * longer part of the bridge.
+ *
+ * If this channel will be replacing another channel the other
+ * channel can be specified in the swap parameter.  The other
+ * channel will be thrown out of the bridge in an atomic
+ * fashion.
+ *
+ * If channel specific features are enabled, a pointer to the
+ * features structure can be specified in the features
+ * parameter.
+ *
+ * \note If you impart a channel as not independent you MUST
+ * ast_bridge_depart() the channel if this call succeeds.  The
+ * bridge channel thread is created join-able.  The implication
+ * is that the channel is special and will not behave like a
+ * normal channel.
+ *
+ * \note If you impart a channel as independent you must not
+ * ast_bridge_depart() the channel.  The bridge channel thread
+ * is created non-join-able.  The channel must be treated as if
+ * it were placed into the bridge by ast_bridge_join().
+ * Channels placed into a bridge by ast_bridge_join() are
+ * removed by a third party using ast_bridge_remove().
  */
-int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, int allow_hangup);
+int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, int independent);
 
 /*!
  * \brief Depart a channel from a bridge
  *
- * \param bridge Bridge to depart from
  * \param chan Channel to depart
  *
+ * \note chan is locked by this function.
+ *
  * \retval 0 on success
  * \retval -1 on failure
  *
  * Example usage:
  *
  * \code
- * ast_bridge_depart(bridge, chan);
+ * ast_bridge_depart(chan);
  * \endcode
  *
- * This removes the channel pointed to by the chan pointer from the bridge
- * pointed to by the bridge pointer and gives control to the calling thread.
+ * This removes the channel pointed to by the chan pointer from any bridge
+ * it may be in and gives control to the calling thread.
  * This does not hang up the channel.
  *
  * \note This API call can only be used on channels that were added to the bridge
- *       using the ast_bridge_impart API call.
+ *       using the ast_bridge_impart API call with the independent flag FALSE.
  */
-int ast_bridge_depart(struct ast_bridge *bridge, struct ast_channel *chan);
+int ast_bridge_depart(struct ast_channel *chan);
 
 /*!
  * \brief Remove a channel from a bridge
@@ -449,8 +750,14 @@ int ast_bridge_remove(struct ast_bridge *bridge, struct ast_channel *chan);
 /*!
  * \brief Merge two bridges together
  *
- * \param bridge0 First bridge
- * \param bridge1 Second bridge
+ * \param dst_bridge Destination bridge of merge.
+ * \param src_bridge Source bridge of merge.
+ * \param merge_best_direction TRUE if don't care about which bridge merges into the other.
+ * \param kick_me Array of channels to kick from the bridges.
+ * \param num_kick Number of channels in the kick_me array.
+ *
+ * \note Absolutely _NO_ bridge or channel locks should be held
+ * before calling this function.
  *
  * \retval 0 on success
  * \retval -1 on failure
@@ -458,16 +765,57 @@ int ast_bridge_remove(struct ast_bridge *bridge, struct ast_channel *chan);
  * Example usage:
  *
  * \code
- * ast_bridge_merge(bridge0, bridge1);
+ * ast_bridge_merge(dst_bridge, src_bridge, 0, NULL, 0);
  * \endcode
  *
- * This merges the bridge pointed to by bridge1 with the bridge pointed to by bridge0.
- * In reality all of the channels in bridge1 are simply moved to bridge0.
+ * This moves the channels in src_bridge into the bridge pointed
+ * to by dst_bridge.
+ */
+int ast_bridge_merge(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, int merge_best_direction, struct ast_channel **kick_me, unsigned int num_kick);
+
+/*!
+ * \brief Move a channel from one bridge to another.
+ * \since 12.0.0
+ *
+ * \param dst_bridge Destination bridge of bridge channel move.
+ * \param src_bridge Source bridge of bridge channel move.
+ * \param chan Channel to move.
+ * \param swap Channel to replace in dst_bridge.
+ * \param attempt_recovery TRUE if failure attempts to push channel back into original bridge.
+ *
+ * \note Absolutely _NO_ bridge or channel locks should be held
+ * before calling this function.
  *
- * \note The second bridge specified is not destroyed when this operation is
- *       completed.
+ * \retval 0 on success.
+ * \retval -1 on failure.
  */
-int ast_bridge_merge(struct ast_bridge *bridge0, struct ast_bridge *bridge1);
+int ast_bridge_move(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_channel *chan, struct ast_channel *swap, int attempt_recovery);
+
+/*!
+ * \brief Adjust the bridge merge inhibit request count.
+ * \since 12.0.0
+ *
+ * \param bridge What to operate on.
+ * \param request Inhibit request increment.
+ *     (Positive to add requests.  Negative to remove requests.)
+ *
+ * \return Nothing
+ */
+void ast_bridge_merge_inhibit(struct ast_bridge *bridge, int request);
+
+/*!
+ * \brief Adjust the bridge_channel's bridge merge inhibit request count.
+ * \since 12.0.0
+ *
+ * \param bridge_channel What to operate on.
+ * \param request Inhibit request increment.
+ *     (Positive to add requests.  Negative to remove requests.)
+ *
+ * \note This API call is meant for internal bridging operations.
+ *
+ * \retval bridge adjusted merge inhibit with reference count.
+ */
+struct ast_bridge *ast_bridge_channel_merge_inhibit(struct ast_bridge_channel *bridge_channel, int request);
 
 /*!
  * \brief Suspend a channel temporarily from a bridge
@@ -517,7 +865,94 @@ int ast_bridge_suspend(struct ast_bridge *bridge, struct ast_channel *chan);
 int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan);
 
 /*!
- * \brief Change the state of a bridged channel
+ * \brief Check and optimize out the unreal channels between bridges.
+ * \since 12.0.0
+ *
+ * \param chan Unreal channel writing a frame into the channel driver.
+ * \param peer Other unreal channel in the pair.
+ *
+ * \note It is assumed that chan is already locked.
+ *
+ * \retval 0 if unreal channels were not optimized out.
+ * \retval non-zero if unreal channels were optimized out.
+ */
+int ast_bridge_unreal_optimized_out(struct ast_channel *chan, struct ast_channel *peer);
+
+/*!
+ * \brief Try locking the bridge_channel.
+ *
+ * \param bridge_channel What to try locking
+ *
+ * \retval 0 on success.
+ * \retval non-zero on error.
+ */
+#define ast_bridge_channel_trylock(bridge_channel)	_ast_bridge_channel_trylock(bridge_channel, __FILE__, __PRETTY_FUNCTION__, __LINE__, #bridge_channel)
+static inline int _ast_bridge_channel_trylock(struct ast_bridge_channel *bridge_channel, const char *file, const char *function, int line, const char *var)
+{
+	return __ao2_trylock(bridge_channel, AO2_LOCK_REQ_MUTEX, file, function, line, var);
+}
+
+/*!
+ * \brief Lock the bridge_channel.
+ *
+ * \param bridge_channel What to lock
+ *
+ * \return Nothing
+ */
+#define ast_bridge_channel_lock(bridge_channel)	_ast_bridge_channel_lock(bridge_channel, __FILE__, __PRETTY_FUNCTION__, __LINE__, #bridge_channel)
+static inline void _ast_bridge_channel_lock(struct ast_bridge_channel *bridge_channel, const char *file, const char *function, int line, const char *var)
+{
+	__ao2_lock(bridge_channel, AO2_LOCK_REQ_MUTEX, file, function, line, var);
+}
+
+/*!
+ * \brief Unlock the bridge_channel.
+ *
+ * \param bridge_channel What to unlock
+ *
+ * \return Nothing
+ */
+#define ast_bridge_channel_unlock(bridge_channel)	_ast_bridge_channel_unlock(bridge_channel, __FILE__, __PRETTY_FUNCTION__, __LINE__, #bridge_channel)
+static inline void _ast_bridge_channel_unlock(struct ast_bridge_channel *bridge_channel, const char *file, const char *function, int line, const char *var)
+{
+	__ao2_unlock(bridge_channel, file, function, line, var);
+}
+
+/*!
+ * \brief Lock the bridge associated with the bridge channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel that wants to lock the bridge.
+ *
+ * \details
+ * This is an upstream lock operation.  The defined locking
+ * order is bridge then bridge_channel.
+ *
+ * \note On entry, neither the bridge nor bridge_channel is locked.
+ *
+ * \note The bridge_channel->bridge pointer changes because of a
+ * bridge-merge/channel-move operation between bridges.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_lock_bridge(struct ast_bridge_channel *bridge_channel);
+
+/*!
+ * \brief Set bridge channel state to leave bridge (if not leaving already) with no lock.
+ *
+ * \param bridge_channel Channel to change the state on
+ * \param new_state The new state to place the channel into
+ *
+ * \note This API call is only meant to be used within the
+ * bridging module and hook callbacks to request the channel
+ * exit the bridge.
+ *
+ * \note This function assumes the bridge_channel is locked.
+ */
+void ast_bridge_change_state_nolock(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state);
+
+/*!
+ * \brief Set bridge channel state to leave bridge (if not leaving already).
  *
  * \param bridge_channel Channel to change the state on
  * \param new_state The new state to place the channel into
@@ -525,17 +960,265 @@ int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan);
  * Example usage:
  *
  * \code
- * ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT);
+ * ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
  * \endcode
  *
- * This places the channel pointed to by bridge_channel into the state
- * AST_BRIDGE_CHANNEL_STATE_WAIT.
+ * This places the channel pointed to by bridge_channel into the
+ * state AST_BRIDGE_CHANNEL_STATE_HANGUP if it was
+ * AST_BRIDGE_CHANNEL_STATE_WAIT before.
  *
- * \note This API call is only meant to be used in feature hook callbacks to
- *       make sure the channel either hangs up or returns to the bridge.
+ * \note This API call is only meant to be used within the
+ * bridging module and hook callbacks to request the channel
+ * exit the bridge.
  */
 void ast_bridge_change_state(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state);
 
+/*!
+ * \brief Put an action onto the specified bridge.
+ * \since 12.0.0
+ *
+ * \param bridge What to queue the action on.
+ * \param action What to do.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ *
+ * \note This API call is meant for internal bridging operations.
+ * \note BUGBUG This may get moved.
+ */
+int ast_bridge_queue_action(struct ast_bridge *bridge, struct ast_frame *action);
+
+/*!
+ * \brief Write a frame to the specified bridge_channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to queue the frame.
+ * \param fr Frame to write.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ *
+ * \note This API call is meant for internal bridging operations.
+ * \note BUGBUG This may get moved.
+ */
+int ast_bridge_channel_queue_frame(struct ast_bridge_channel *bridge_channel, struct ast_frame *fr);
+
+/*!
+ * \brief Used to queue an action frame onto a bridge channel and write an action frame into a bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel work with.
+ * \param action Type of bridge action frame.
+ * \param data Frame payload data to pass.
+ * \param datalen Frame payload data length to pass.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_bridge_channel_post_action_data)(struct ast_bridge_channel *bridge_channel, enum ast_bridge_action_type action, const void *data, size_t datalen);
+
+/*!
+ * \brief Queue an action frame onto the bridge channel with data.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel to queue the frame onto.
+ * \param action Type of bridge action frame.
+ * \param data Frame payload data to pass.
+ * \param datalen Frame payload data length to pass.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_queue_action_data(struct ast_bridge_channel *bridge_channel, enum ast_bridge_action_type action, const void *data, size_t datalen);
+
+/*!
+ * \brief Write an action frame into the bridge with data.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is putting the frame into the bridge.
+ * \param action Type of bridge action frame.
+ * \param data Frame payload data to pass.
+ * \param datalen Frame payload data length to pass.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_write_action_data(struct ast_bridge_channel *bridge_channel, enum ast_bridge_action_type action, const void *data, size_t datalen);
+
+/*!
+ * \brief Queue a control frame onto the bridge channel with data.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel to queue the frame onto.
+ * \param control Type of control frame.
+ * \param data Frame payload data to pass.
+ * \param datalen Frame payload data length to pass.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_queue_control_data(struct ast_bridge_channel *bridge_channel, enum ast_control_frame_type control, const void *data, size_t datalen);
+
+/*!
+ * \brief Write a control frame into the bridge with data.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is putting the frame into the bridge.
+ * \param control Type of control frame.
+ * \param data Frame payload data to pass.
+ * \param datalen Frame payload data length to pass.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_write_control_data(struct ast_bridge_channel *bridge_channel, enum ast_control_frame_type control, const void *data, size_t datalen);
+
+/*!
+ * \brief Run an application on the bridge channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel to run the application on.
+ * \param app_name Dialplan application name.
+ * \param app_args Arguments for the application. (NULL tolerant)
+ * \param moh_class MOH class to request bridge peers to hear while application is running.
+ *     NULL if no MOH.
+ *     Empty if default MOH class.
+ *
+ * \note This is intended to be called by bridge hooks.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_run_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class);
+
+/*!
+ * \brief Write a bridge action run application frame into the bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is putting the frame into the bridge
+ * \param app_name Dialplan application name.
+ * \param app_args Arguments for the application. (NULL or empty for no arguments)
+ * \param moh_class MOH class to request bridge peers to hear while application is running.
+ *     NULL if no MOH.
+ *     Empty if default MOH class.
+ *
+ * \note This is intended to be called by bridge hooks.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_write_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class);
+
+/*!
+ * \brief Queue a bridge action run application frame onto the bridge channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel to put the frame onto
+ * \param app_name Dialplan application name.
+ * \param app_args Arguments for the application. (NULL or empty for no arguments)
+ * \param moh_class MOH class to request bridge peers to hear while application is running.
+ *     NULL if no MOH.
+ *     Empty if default MOH class.
+ *
+ * \note This is intended to be called by bridge hooks.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_queue_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class);
+
+/*!
+ * \brief Custom interpretation of the playfile name.
+ *
+ * \param bridge_channel Which channel to play the file on
+ * \param playfile Sound filename to play.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_bridge_custom_play_fn)(struct ast_bridge_channel *bridge_channel, const char *playfile);
+
+/*!
+ * \brief Play a file on the bridge channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel to play the file on
+ * \param custom_play Call this function to play the playfile. (NULL if normal sound file to play)
+ * \param playfile Sound filename to play.
+ * \param moh_class MOH class to request bridge peers to hear while file is played.
+ *     NULL if no MOH.
+ *     Empty if default MOH class.
+ *
+ * \note This is intended to be called by bridge hooks.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class);
+
+/*!
+ * \brief Have a bridge channel park a channel in the bridge
+ * \since 12.0.0
+ *
+ * \param bridge_channel Bridge channel performing the parking
+ * \param parkee_uuid Unique id of the channel we want to park
+ * \param parker_uuid Unique id of the channel parking the call
+ * \param app_data string indicating data used for park application (NULL allowed)
+ *
+ * \note This is intended to be called by bridge hooks.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_write_park(struct ast_bridge_channel *bridge_channel, const char *parkee_uuid,
+	const char *parker_uuid, const char *app_data);
+
+/*!
+ * \brief Write a bridge action play file frame into the bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is putting the frame into the bridge
+ * \param custom_play Call this function to play the playfile. (NULL if normal sound file to play)
+ * \param playfile Sound filename to play.
+ * \param moh_class MOH class to request bridge peers to hear while file is played.
+ *     NULL if no MOH.
+ *     Empty if default MOH class.
+ *
+ * \note This is intended to be called by bridge hooks.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_write_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class);
+
+/*!
+ * \brief Queue a bridge action play file frame onto the bridge channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel to put the frame onto.
+ * \param custom_play Call this function to play the playfile. (NULL if normal sound file to play)
+ * \param playfile Sound filename to play.
+ * \param moh_class MOH class to request bridge peers to hear while file is played.
+ *     NULL if no MOH.
+ *     Empty if default MOH class.
+ *
+ * \note This is intended to be called by bridge hooks.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_queue_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class);
+
+/*!
+ * \brief Restore the formats of a bridge channel's channel to how they were before bridge_channel_join
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to restore
+ */
+void ast_bridge_channel_restore_formats(struct ast_bridge_channel *bridge_channel);
+
+/*!
+ * \brief Get the peer bridge channel of a two party bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel What to get the peer of.
+ *
+ * \note On entry, bridge_channel->bridge is already locked.
+ *
+ * \note This is an internal bridge function.
+ *
+ * \retval peer on success.
+ * \retval NULL no peer channel.
+ */
+struct ast_bridge_channel *ast_bridge_channel_peer(struct ast_bridge_channel *bridge_channel);
+
 /*!
  * \brief Adjust the internal mixing sample rate of a bridge
  * used during multimix mode.
@@ -594,8 +1277,297 @@ int ast_bridge_is_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
  */
 void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *chan);
 
+enum ast_transfer_result {
+	/*! The transfer completed successfully */
+	AST_BRIDGE_TRANSFER_SUCCESS,
+	/*! A bridge involved does not permit transferring */
+	AST_BRIDGE_TRANSFER_NOT_PERMITTED,
+	/*! The current bridge setup makes transferring an invalid operation */
+	AST_BRIDGE_TRANSFER_INVALID,
+	/*! The transfer operation failed for a miscellaneous reason */
+	AST_BRIDGE_TRANSFER_FAIL,
+};
+
+typedef void (*transfer_channel_cb)(struct ast_channel *chan, void *user_data);
+
+/*!
+ * \brief Blind transfer target to the extension and context provided
+ *
+ * The channel given is bridged to one or multiple channels. Depending on
+ * the bridge and the number of participants, the entire bridge could be
+ * transferred to the given destination, or a single channel may be redirected.
+ *
+ * Callers may also provide a callback to be called on the channel that will
+ * be running dialplan. The user data passed into ast_bridge_transfer_blind
+ * will be given as the argument to the callback to be interpreted as desired.
+ * This callback is guaranteed to be called in the same thread as
+ * ast_bridge_transfer_blind() and before ast_bridge_transfer_blind() returns.
+ *
+ * \note Absolutely _NO_ channel locks should be held before
+ * calling this function.
+ *
+ * \param transferer The channel performing the blind transfer
+ * \param exten The dialplan extension to send the call to
+ * \param context The dialplan context to send the call to
+ * \param new_channel_cb A callback to be called on the channel that will
+ *        be executing dialplan
+ * \param user_data Argument for new_channel_cb
+ * \return The success or failure result of the blind transfer
+ */
+enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transferer,
+		const char *exten, const char *context,
+		transfer_channel_cb new_channel_cb, void *user_data);
+
+/*!
+ * \brief Attended transfer
+ *
+ * The two channels are both transferer channels. The first is the channel
+ * that is bridged to the transferee (or if unbridged, the 'first' call of
+ * the transfer). The second is the channel that is bridged to the transfer
+ * target (or if unbridged, the 'second' call of the transfer).
+ *
+ * Like with a blind transfer, a frame hook can be provided to monitor the
+ * resulting call after the transfer completes. If the transfer fails, the
+ * hook will not be attached to any call.
+ *
+ * \note Absolutely _NO_ channel locks should be held before
+ * calling this function.
+ *
+ * \param to_transferee Transferer channel on initial call (presumably bridged to transferee)
+ * \param to_transfer_target Transferer channel on consultation call (presumably bridged to transfer target)
+ * \param hook A frame hook to attach to the resultant call
+ * \return The success or failure of the attended transfer
+ */
+enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_transferee,
+		struct ast_channel *to_transfer_target, struct ast_framehook *hook);
+/*!
+ * \brief Set channel to goto specific location after the bridge.
+ * \since 12.0.0
+ *
+ * \param chan Channel to setup after bridge goto location.
+ * \param context Context to goto after bridge.
+ * \param exten Exten to goto after bridge.
+ * \param priority Priority to goto after bridge.
+ *
+ * \note chan is locked by this function.
+ *
+ * \details Add a channel datastore to setup the goto location
+ * when the channel leaves the bridge and run a PBX from there.
+ *
+ * \return Nothing
+ */
+void ast_after_bridge_set_goto(struct ast_channel *chan, const char *context, const char *exten, int priority);
+
+/*!
+ * \brief Set channel to run the h exten after the bridge.
+ * \since 12.0.0
+ *
+ * \param chan Channel to setup after bridge goto location.
+ * \param context Context to goto after bridge.
+ *
+ * \note chan is locked by this function.
+ *
+ * \details Add a channel datastore to setup the goto location
+ * when the channel leaves the bridge and run a PBX from there.
+ *
+ * \return Nothing
+ */
+void ast_after_bridge_set_h(struct ast_channel *chan, const char *context);
+
+/*!
+ * \brief Set channel to go on in the dialplan after the bridge.
+ * \since 12.0.0
+ *
+ * \param chan Channel to setup after bridge goto location.
+ * \param context Current context of the caller channel.
+ * \param exten Current exten of the caller channel.
+ * \param priority Current priority of the caller channel
+ * \param parseable_goto User specified goto string from dialplan.
+ *
+ * \note chan is locked by this function.
+ *
+ * \details Add a channel datastore to setup the goto location
+ * when the channel leaves the bridge and run a PBX from there.
+ *
+ * If parseable_goto then use the given context/exten/priority
+ *   as the relative position for the parseable_goto.
+ * Else goto the given context/exten/priority+1.
+ *
+ * \return Nothing
+ */
+void ast_after_bridge_set_go_on(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *parseable_goto);
+
+/*!
+ * \brief Setup any after bridge goto location to begin execution.
+ * \since 12.0.0
+ *
+ * \param chan Channel to setup after bridge goto location.
+ *
+ * \note chan is locked by this function.
+ *
+ * \details Pull off any after bridge goto location datastore and
+ * setup for dialplan execution there.
+ *
+ * \retval 0 on success.  The goto location is set for a PBX to run it.
+ * \retval non-zero on error or no goto location.
+ *
+ * \note If the after bridge goto is set to run an h exten it is
+ * run here immediately.
+ */
+int ast_after_bridge_goto_setup(struct ast_channel *chan);
+
+/*!
+ * \brief Run a PBX on any after bridge goto location.
+ * \since 12.0.0
+ *
+ * \param chan Channel to execute after bridge goto location.
+ *
+ * \note chan is locked by this function.
+ *
+ * \details Pull off any after bridge goto location datastore
+ * and run a PBX at that location.
+ *
+ * \note On return, the chan pointer is no longer valid because
+ * the channel has hung up.
+ *
+ * \return Nothing
+ */
+void ast_after_bridge_goto_run(struct ast_channel *chan);
+
+/*!
+ * \brief Discard channel after bridge goto location.
+ * \since 12.0.0
+ *
+ * \param chan Channel to discard after bridge goto location.
+ *
+ * \note chan is locked by this function.
+ *
+ * \return Nothing
+ */
+void ast_after_bridge_goto_discard(struct ast_channel *chan);
+
+/*! Reason the the after bridge callback will not be called. */
+enum ast_after_bridge_cb_reason {
+	/*! The datastore is being destroyed.  Likely due to hangup. */
+	AST_AFTER_BRIDGE_CB_REASON_DESTROY,
+	/*! Something else replaced the callback with another. */
+	AST_AFTER_BRIDGE_CB_REASON_REPLACED,
+	/*! The callback was removed because of a masquerade. (fixup) */
+	AST_AFTER_BRIDGE_CB_REASON_MASQUERADE,
+	/*! The channel departed bridge. */
+	AST_AFTER_BRIDGE_CB_REASON_DEPART,
+	/*! Was explicitly removed by external code. */
+	AST_AFTER_BRIDGE_CB_REASON_REMOVED,
+};
+
+/*!
+ * \brief After bridge callback failed.
+ * \since 12.0.0
+ *
+ * \param reason Reason callback is failing.
+ * \param data Extra data what setup the callback wanted to pass.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_after_bridge_cb_failed)(enum ast_after_bridge_cb_reason reason, void *data);
+
+/*!
+ * \brief After bridge callback function.
+ * \since 12.0.0
+ *
+ * \param chan Channel just leaving bridging system.
+ * \param data Extra data what setup the callback wanted to pass.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_after_bridge_cb)(struct ast_channel *chan, void *data);
+
+/*!
+ * \brief Discard channel after bridge callback.
+ * \since 12.0.0
+ *
+ * \param chan Channel to discard after bridge callback.
+ * \param reason Why are we doing this.
+ *
+ * \note chan is locked by this function.
+ *
+ * \return Nothing
+ */
+void ast_after_bridge_callback_discard(struct ast_channel *chan, enum ast_after_bridge_cb_reason reason);
+
+/*!
+ * \brief Setup an after bridge callback for when the channel leaves the bridging system.
+ * \since 12.0.0
+ *
+ * \param chan Channel to setup an after bridge callback on.
+ * \param callback Function to call when the channel leaves the bridging system.
+ * \param failed Function to call when it will not be calling the callback.
+ * \param data Extra data to pass with the callback.
+ *
+ * \note chan is locked by this function.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_after_bridge_callback_set(struct ast_channel *chan, ast_after_bridge_cb callback, ast_after_bridge_cb_failed failed, void *data);
+
+/*!
+ * \brief Get a container of all channels in the bridge
+ * \since 12.0.0
+ *
+ * \param bridge The bridge which is already locked.
+ *
+ * \retval NULL Failed to create container
+ * \retval non-NULL Container of channels in the bridge
+ */
+struct ao2_container *ast_bridge_peers_nolock(struct ast_bridge *bridge);
+
+/*!
+ * \brief Get a container of all channels in the bridge
+ * \since 12.0.0
+ *
+ * \param bridge The bridge
+ *
+ * \note The returned container is a snapshot of channels in the
+ * bridge when called.
+ *
+ * \retval NULL Failed to create container
+ * \retval non-NULL Container of channels in the bridge
+ */
+struct ao2_container *ast_bridge_peers(struct ast_bridge *bridge);
+
+/*!
+ * \brief Get the channel's bridge peer only if the bridge is two-party.
+ * \since 12.0.0
+ *
+ * \param bridge The bridge which is already locked.
+ * \param chan Channel desiring the bridge peer channel.
+ *
+ * \note The returned peer channel is the current peer in the
+ * bridge when called.
+ *
+ * \retval NULL Channel not in a bridge or the bridge is not two-party.
+ * \retval non-NULL Reffed peer channel at time of calling.
+ */
+struct ast_channel *ast_bridge_peer_nolock(struct ast_bridge *bridge, struct ast_channel *chan);
+
+/*!
+ * \brief Get the channel's bridge peer only if the bridge is two-party.
+ * \since 12.0.0
+ *
+ * \param bridge The bridge
+ * \param chan Channel desiring the bridge peer channel.
+ *
+ * \note The returned peer channel is the current peer in the
+ * bridge when called.
+ *
+ * \retval NULL Channel not in a bridge or the bridge is not two-party.
+ * \retval non-NULL Reffed peer channel at time of calling.
+ */
+struct ast_channel *ast_bridge_peer(struct ast_bridge *bridge, struct ast_channel *chan);
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif
 
-#endif /* _ASTERISK_BRIDGING_H */
+#endif	/* _ASTERISK_BRIDGING_H */
diff --git a/include/asterisk/bridging_basic.h b/include/asterisk/bridging_basic.h
new file mode 100644
index 0000000000000000000000000000000000000000..cc42354dc9bc2a5e37bdf13b1c433b24e1dded34
--- /dev/null
+++ b/include/asterisk/bridging_basic.h
@@ -0,0 +1,107 @@
+/*
+ * 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 Basic bridge subclass API.
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+#ifndef _ASTERISK_BRIDGING_BASIC_H
+#define _ASTERISK_BRIDGING_BASIC_H
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+/* ------------------------------------------------------------------- */
+
+/*!
+ * \brief Get DTMF feature flags from the channel.
+ * \since 12.0.0
+ *
+ * \param chan Channel to get DTMF features datastore.
+ *
+ * \note The channel should be locked before calling this function.
+ *
+ * \retval flags on success.
+ * \retval NULL on error.
+ */
+struct ast_flags *ast_bridge_features_ds_get(struct ast_channel *chan);
+
+/*!
+ * \brief Set basic bridge DTMF feature flags datastore on the channel.
+ * \since 12.0.0
+ *
+ * \param chan Channel to set DTMF features datastore.
+ * \param flags Builtin DTMF feature flags. (ast_bridge_config flags)
+ *
+ * \note The channel must be locked before calling this function.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_bridge_features_ds_set(struct ast_channel *chan, struct ast_flags *flags);
+
+/*!
+ * \brief Setup DTMF feature hooks using the channel features datastore property.
+ * \since 12.0.0
+ *
+ * \param bridge_channel What to setup DTMF features on.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_bridge_channel_setup_features(struct ast_bridge_channel *bridge_channel);
+
+/*! \brief Bridge basic class virtual method table. */
+extern struct ast_bridge_methods ast_bridge_basic_v_table;
+
+/*!
+ * \brief Create a new basic class bridge
+ *
+ * \retval a pointer to a new bridge on success
+ * \retval NULL on failure
+ *
+ * Example usage:
+ *
+ * \code
+ * struct ast_bridge *bridge;
+ * bridge = ast_bridge_basic_new();
+ * \endcode
+ *
+ * This creates a basic two party bridge with any configured
+ * DTMF features enabled that will be destroyed once one of the
+ * channels hangs up.
+ */
+struct ast_bridge *ast_bridge_basic_new(void);
+
+/*! Initialize the basic bridge class for use by the system. */
+void ast_bridging_init_basic(void);
+
+/* ------------------------------------------------------------------- */
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif	/* _ASTERISK_BRIDGING_BASIC_H */
diff --git a/include/asterisk/bridging_features.h b/include/asterisk/bridging_features.h
index 1323a6da98f1167c4fe689889e5dfd3e2c5759c1..bb792a81556c337af963cf5d00e6038202263dea 100644
--- a/include/asterisk/bridging_features.h
+++ b/include/asterisk/bridging_features.h
@@ -30,10 +30,36 @@ extern "C" {
 
 /*! \brief Flags used for bridge features */
 enum ast_bridge_feature_flags {
-	/*! Upon hangup the bridge should be discontinued */
-	AST_BRIDGE_FLAG_DISSOLVE = (1 << 0),
+	/*! Upon channel hangup all bridge participants should be kicked out. */
+	AST_BRIDGE_FLAG_DISSOLVE_HANGUP = (1 << 0),
+	/*! The last channel to leave the bridge dissolves it. */
+	AST_BRIDGE_FLAG_DISSOLVE_EMPTY = (1 << 1),
 	/*! Move between bridging technologies as needed. */
-	AST_BRIDGE_FLAG_SMART = (1 << 1),
+	AST_BRIDGE_FLAG_SMART = (1 << 2),
+	/*! Bridge channels cannot be merged from this bridge. */
+	AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM = (1 << 3),
+	/*! Bridge channels cannot be merged to this bridge. */
+	AST_BRIDGE_FLAG_MERGE_INHIBIT_TO = (1 << 4),
+	/*! Bridge channels cannot be local channel swap optimized from this bridge. */
+	AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM = (1 << 5),
+	/*! Bridge channels cannot be local channel swap optimized to this bridge. */
+	AST_BRIDGE_FLAG_SWAP_INHIBIT_TO = (1 << 6),
+	/*! Bridge channels can be moved to another bridge only by masquerade (ConfBridge) */
+	AST_BRIDGE_FLAG_MASQUERADE_ONLY = (1 << 7),
+	/*! Bridge does not allow transfers of channels out */
+	AST_BRIDGE_FLAG_TRANSFER_PROHIBITED = (1 << 6),
+	/*! Bridge transfers require transfer of entire bridge rather than individual channels */
+	AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY = (1 << 7),
+};
+
+/*! \brief Flags used for per bridge channel features */
+enum ast_bridge_channel_feature_flags {
+	/*! Upon channel hangup all bridge participants should be kicked out. */
+	AST_BRIDGE_CHANNEL_FLAG_DISSOLVE_HANGUP = (1 << 0),
+	/*! This channel leaves the bridge if all participants have this flag set. */
+	AST_BRIDGE_CHANNEL_FLAG_LONELY = (1 << 1),
+	/*! This channel cannot be moved to another bridge. */
+	AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE = (1 << 2),
 };
 
 /*! \brief Built in DTMF features */
@@ -52,32 +78,66 @@ enum ast_bridge_builtin_feature {
 	 * AST_BRIDGE_CHANNEL_STATE_END.
 	 */
 	AST_BRIDGE_BUILTIN_HANGUP,
+	/*!
+	 * DTMF based Park
+	 *
+	 * \details The bridge is parked and the channel hears the
+	 * parking slot to which it was parked.
+	 */
+	AST_BRIDGE_BUILTIN_PARKCALL,
+/* BUGBUG does Monitor and/or MixMonitor require a two party bridge?  MixMonitor is used by ConfBridge so maybe it doesn't. */
+	/*!
+	 * DTMF one-touch-record toggle using Monitor app.
+	 *
+	 * \note Only valid on two party bridges.
+	 */
+	AST_BRIDGE_BUILTIN_AUTOMON,
+	/*!
+	 * DTMF one-touch-record toggle using MixMonitor app.
+	 *
+	 * \note Only valid on two party bridges.
+	 */
+	AST_BRIDGE_BUILTIN_AUTOMIXMON,
 
 	/*! End terminator for list of built in features. Must remain last. */
 	AST_BRIDGE_BUILTIN_END
 };
 
+enum ast_bridge_builtin_interval {
+	/*! Apply Call Duration Limits */
+	AST_BRIDGE_BUILTIN_INTERVAL_LIMITS,
+
+	/*! End terminator for list of built in interval features. Must remain last. */
+	AST_BRIDGE_BUILTIN_INTERVAL_END
+};
+
 struct ast_bridge;
 struct ast_bridge_channel;
 
 /*!
- * \brief Features hook callback type
+ * \brief Hook callback type
  *
  * \param bridge The bridge that the channel is part of
  * \param bridge_channel Channel executing the feature
  * \param hook_pvt Private data passed in when the hook was created
  *
- * \retval 0 success
- * \retval -1 failure
+ * For interval hooks:
+ * \retval 0 Setup to fire again at the last interval.
+ * \retval positive Setup to fire again at the new interval returned.
+ * \retval -1 Remove the callback hook.
+ *
+ * For other hooks:
+ * \retval 0 Keep the callback hook.
+ * \retval -1 Remove the callback hook.
  */
-typedef int (*ast_bridge_features_hook_callback)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt);
+typedef int (*ast_bridge_hook_callback)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt);
 
 /*!
- * \brief Features hook pvt destructor callback
+ * \brief Hook pvt destructor callback
  *
- * \param hook_pvt Private data passed in when the hook was create to destroy
+ * \param hook_pvt Private data passed in when the hook was created to destroy
  */
-typedef void (*ast_bridge_features_hook_pvt_destructor)(void *hook_pvt);
+typedef void (*ast_bridge_hook_pvt_destructor)(void *hook_pvt);
 
 /*!
  * \brief Talking indicator callback
@@ -86,13 +146,13 @@ typedef void (*ast_bridge_features_hook_pvt_destructor)(void *hook_pvt);
  * to receive updates on when a bridge_channel has started and stopped
  * talking
  *
- * \param bridge The bridge that the channel is part of
  * \param bridge_channel Channel executing the feature
+ * \param talking TRUE if the channel is now talking
  *
  * \retval 0 success
  * \retval -1 failure
  */
-typedef void (*ast_bridge_talking_indicate_callback)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *pvt_data);
+typedef void (*ast_bridge_talking_indicate_callback)(struct ast_bridge_channel *bridge_channel, void *pvt_data, int talking);
 
 
 typedef void (*ast_bridge_talking_indicate_destructor)(void *pvt_data);
@@ -100,30 +160,68 @@ typedef void (*ast_bridge_talking_indicate_destructor)(void *pvt_data);
 /*!
  * \brief Maximum length of a DTMF feature string
  */
-#define MAXIMUM_DTMF_FEATURE_STRING 8
+#define MAXIMUM_DTMF_FEATURE_STRING (11 + 1)
 
-/*!
- * \brief Structure that is the essence of a features hook
- */
-struct ast_bridge_features_hook {
+/*! Extra parameters for a DTMF feature hook. */
+struct ast_bridge_hook_dtmf {
 	/*! DTMF String that is examined during a feature hook lookup */
-	char dtmf[MAXIMUM_DTMF_FEATURE_STRING];
-	/*! Callback that is called when DTMF string is matched */
-	ast_bridge_features_hook_callback callback;
+	char code[MAXIMUM_DTMF_FEATURE_STRING];
+};
+
+/*! Extra parameters for an interval timer hook. */
+struct ast_bridge_hook_timer {
+	/*! Time at which the hook should actually trip */
+	struct timeval trip_time;
+	/*! Heap index for interval hook */
+	ssize_t heap_index;
+	/*! Interval that the hook should execute at in milliseconds */
+	unsigned int interval;
+	/*! Sequence number for the hook to ensure expiration ordering */
+	unsigned int seqno;
+};
+
+/* BUGBUG Need to be able to selectively remove DTMF, hangup, and interval hooks. */
+/*! \brief Structure that is the essence of a feature hook. */
+struct ast_bridge_hook {
+	/*! Linked list information */
+	AST_LIST_ENTRY(ast_bridge_hook) entry;
+	/*! Callback that is called when hook is tripped */
+	ast_bridge_hook_callback callback;
 	/*! Callback to destroy hook_pvt data right before destruction. */
-	ast_bridge_features_hook_pvt_destructor destructor;
+	ast_bridge_hook_pvt_destructor destructor;
 	/*! Unique data that was passed into us */
 	void *hook_pvt;
-	/*! Linked list information */
-	AST_LIST_ENTRY(ast_bridge_features_hook) entry;
+	/*! TRUE if the hook is removed when the channel is pulled from the bridge. */
+	unsigned int remove_on_pull:1;
+	/*! Extra hook parameters. */
+	union {
+		/*! Extra parameters for a DTMF feature hook. */
+		struct ast_bridge_hook_dtmf dtmf;
+		/*! Extra parameters for an interval timer hook. */
+		struct ast_bridge_hook_timer timer;
+	} parms;
 };
 
+#define BRIDGE_FEATURES_INTERVAL_RATE 10
+
 /*!
  * \brief Structure that contains features information
  */
 struct ast_bridge_features {
-	/*! Attached DTMF based feature hooks */
-	AST_LIST_HEAD_NOLOCK(, ast_bridge_features_hook) hooks;
+	/*! Attached DTMF feature hooks */
+	struct ao2_container *dtmf_hooks;
+	/*! Attached hangup interception hooks container */
+	struct ao2_container *hangup_hooks;
+	/*! Attached bridge channel join interception hooks container */
+	struct ao2_container *join_hooks;
+	/*! Attached bridge channel leave interception hooks container */
+	struct ao2_container *leave_hooks;
+	/*! Attached interval hooks */
+	struct ast_heap *interval_hooks;
+	/*! Used to determine when interval based features should be checked */
+	struct ast_timer *interval_timer;
+	/*! Limits feature data */
+	struct ast_bridge_features_limits *limits;
 	/*! Callback to indicate when a bridge channel has started and stopped talking */
 	ast_bridge_talking_indicate_callback talker_cb;
 	/*! Callback to destroy any pvt data stored for the talker. */
@@ -132,19 +230,21 @@ struct ast_bridge_features {
 	void *talker_pvt_data;
 	/*! Feature flags that are enabled */
 	struct ast_flags feature_flags;
-	/*! Bit to indicate that the feature_flags and hook list is setup */
+	/*! Used to assign the sequence number to the next interval hook added. */
+	unsigned int interval_sequence;
+	/*! TRUE if feature_flags is setup */
 	unsigned int usable:1;
-	/*! Bit to indicate whether the channel/bridge is muted or not */
+	/*! TRUE if the channel/bridge is muted. */
 	unsigned int mute:1;
-	/*! Bit to indicate whether DTMF should be passed into the bridge tech or not.  */
+	/*! TRUE if DTMF should be passed into the bridge tech.  */
 	unsigned int dtmf_passthrough:1;
-
 };
 
 /*!
  * \brief Structure that contains configuration information for the blind transfer built in feature
  */
 struct ast_bridge_features_blind_transfer {
+/* BUGBUG the context should be figured out based upon TRANSFER_CONTEXT channel variable of A/B or current context of A/B. More appropriate for when channel moved to other bridges. */
 	/*! Context to use for transfers */
 	char context[AST_MAX_CONTEXT];
 };
@@ -153,14 +253,38 @@ struct ast_bridge_features_blind_transfer {
  * \brief Structure that contains configuration information for the attended transfer built in feature
  */
 struct ast_bridge_features_attended_transfer {
+/* BUGBUG the context should be figured out based upon TRANSFER_CONTEXT channel variable of A/B or current context of A/B. More appropriate for when channel moved to other bridges. */
+	/*! Context to use for transfers */
+	char context[AST_MAX_CONTEXT];
 	/*! DTMF string used to abort the transfer */
 	char abort[MAXIMUM_DTMF_FEATURE_STRING];
 	/*! DTMF string used to turn the transfer into a three way conference */
 	char threeway[MAXIMUM_DTMF_FEATURE_STRING];
 	/*! DTMF string used to complete the transfer */
 	char complete[MAXIMUM_DTMF_FEATURE_STRING];
-	/*! Context to use for transfers */
-	char context[AST_MAX_CONTEXT];
+};
+
+/*!
+ * \brief Structure that contains configuration information for the limits feature
+ */
+struct ast_bridge_features_limits {
+	/*! Maximum duration that the channel is allowed to be in the bridge (specified in milliseconds) */
+	unsigned int duration;
+	/*! Duration into the call when warnings should begin (specified in milliseconds or 0 to disable) */
+	unsigned int warning;
+	/*! Interval between the warnings (specified in milliseconds or 0 to disable) */
+	unsigned int frequency;
+
+	AST_DECLARE_STRING_FIELDS(
+		/*! Sound file to play when the maximum duration is reached (if empty, then nothing will be played) */
+		AST_STRING_FIELD(duration_sound);
+		/*! Sound file to play when the warning time is reached (if empty, then the remaining time will be played) */
+		AST_STRING_FIELD(warning_sound);
+		/*! Sound file to play when the call is first entered (if empty, then the remaining time will be played) */
+		AST_STRING_FIELD(connect_sound);
+	);
+	/*! Time when the bridge will be terminated by the limits feature */
+	struct timeval quitting_time;
 };
 
 /*!
@@ -182,7 +306,7 @@ struct ast_bridge_features_attended_transfer {
  * This registers the function bridge_builtin_attended_transfer as the function responsible for the built in
  * attended transfer feature.
  */
-int ast_bridge_features_register(enum ast_bridge_builtin_feature feature, ast_bridge_features_hook_callback callback, const char *dtmf);
+int ast_bridge_features_register(enum ast_bridge_builtin_feature feature, ast_bridge_hook_callback callback, const char *dtmf);
 
 /*!
  * \brief Unregister a handler for a built in feature
@@ -203,13 +327,154 @@ int ast_bridge_features_register(enum ast_bridge_builtin_feature feature, ast_br
 int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature);
 
 /*!
- * \brief Attach a custom hook to a bridge features structure
+ * \brief Attach interval hooks to a bridge features structure
+ *
+ * \param features Bridge features structure
+ * \param limits Configured limits applicable to the channel
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+typedef int (*ast_bridge_builtin_set_limits_fn)(struct ast_bridge_features *features, struct ast_bridge_features_limits *limits, int remove_on_pull);
+
+/*!
+ * \brief Register a handler for a built in interval feature
+ *
+ * \param interval The interval feature that the handler will be responsible for
+ * \param callback the Callback function that will handle it
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * Example usage:
+ *
+ * \code
+ * ast_bridge_interval_register(AST_BRIDGE_BUILTIN_INTERVAL_LIMITS, bridge_builtin_set_limits);
+ * \endcode
+ *
+ * This registers the function bridge_builtin_set_limits as the function responsible for the built in
+ * duration limit feature.
+ */
+int ast_bridge_interval_register(enum ast_bridge_builtin_interval interval, ast_bridge_builtin_set_limits_fn callback);
+
+/*!
+ * \brief Unregisters a handler for a built in interval feature
+ *
+ * \param interval the interval feature to unregister
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * Example usage:
+ *
+ * \code
+ * ast_bridge_interval_unregister(AST_BRIDGE_BULTIN_INTERVAL_LIMITS)
+ * /endcode
+ *
+ * This unregisters the function that is handling the built in duration limit feature.
+ */
+int ast_bridge_interval_unregister(enum ast_bridge_builtin_interval interval);
+
+/*!
+ * \brief Attach a bridge channel join hook to a bridge features structure
+ *
+ * \param features Bridge features structure
+ * \param callback Function to execute upon activation
+ * \param hook_pvt Unique data
+ * \param destructor Optional destructor callback for hook_pvt data
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * Example usage:
+ *
+ * \code
+ * struct ast_bridge_features features;
+ * ast_bridge_features_init(&features);
+ * ast_bridge_join_hook(&features, join_callback, NULL, NULL, 0);
+ * \endcode
+ *
+ * This makes the bridging core call join_callback when a
+ * channel successfully joins the bridging system.  A pointer to
+ * useful data may be provided to the hook_pvt parameter.
+ */
+int ast_bridge_join_hook(struct ast_bridge_features *features,
+	ast_bridge_hook_callback callback,
+	void *hook_pvt,
+	ast_bridge_hook_pvt_destructor destructor,
+	int remove_on_pull);
+
+/*!
+ * \brief Attach a bridge channel leave hook to a bridge features structure
+ *
+ * \param features Bridge features structure
+ * \param callback Function to execute upon activation
+ * \param hook_pvt Unique data
+ * \param destructor Optional destructor callback for hook_pvt data
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * Example usage:
+ *
+ * \code
+ * struct ast_bridge_features features;
+ * ast_bridge_features_init(&features);
+ * ast_bridge_leave_hook(&features, leave_callback, NULL, NULL, 0);
+ * \endcode
+ *
+ * This makes the bridging core call leave_callback when a
+ * channel successfully leaves the bridging system.  A pointer
+ * to useful data may be provided to the hook_pvt parameter.
+ */
+int ast_bridge_leave_hook(struct ast_bridge_features *features,
+	ast_bridge_hook_callback callback,
+	void *hook_pvt,
+	ast_bridge_hook_pvt_destructor destructor,
+	int remove_on_pull);
+
+/*!
+ * \brief Attach a hangup hook to a bridge features structure
+ *
+ * \param features Bridge features structure
+ * \param callback Function to execute upon activation
+ * \param hook_pvt Unique data
+ * \param destructor Optional destructor callback for hook_pvt data
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * Example usage:
+ *
+ * \code
+ * struct ast_bridge_features features;
+ * ast_bridge_features_init(&features);
+ * ast_bridge_hangup_hook(&features, hangup_callback, NULL, NULL, 0);
+ * \endcode
+ *
+ * This makes the bridging core call hangup_callback if a
+ * channel that has this hook hangs up.  A pointer to useful
+ * data may be provided to the hook_pvt parameter.
+ */
+int ast_bridge_hangup_hook(struct ast_bridge_features *features,
+	ast_bridge_hook_callback callback,
+	void *hook_pvt,
+	ast_bridge_hook_pvt_destructor destructor,
+	int remove_on_pull);
+
+/*!
+ * \brief Attach a DTMF hook to a bridge features structure
  *
  * \param features Bridge features structure
  * \param dtmf DTMF string to be activated upon
  * \param callback Function to execute upon activation
  * \param hook_pvt Unique data
  * \param destructor Optional destructor callback for hook_pvt data
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
  *
  * \retval 0 on success
  * \retval -1 on failure
@@ -219,21 +484,48 @@ int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature);
  * \code
  * struct ast_bridge_features features;
  * ast_bridge_features_init(&features);
- * ast_bridge_features_hook(&features, "#", pound_callback, NULL, NULL);
+ * ast_bridge_dtmf_hook(&features, "#", pound_callback, NULL, NULL, 0);
  * \endcode
  *
  * This makes the bridging core call pound_callback if a channel that has this
  * feature structure inputs the DTMF string '#'. A pointer to useful data may be
  * provided to the hook_pvt parameter.
- *
- * \note It is important that the callback set the bridge channel state back to
- *       AST_BRIDGE_CHANNEL_STATE_WAIT or the bridge thread will not service the channel.
  */
-int ast_bridge_features_hook(struct ast_bridge_features *features,
+int ast_bridge_dtmf_hook(struct ast_bridge_features *features,
 	const char *dtmf,
-	ast_bridge_features_hook_callback callback,
+	ast_bridge_hook_callback callback,
 	void *hook_pvt,
-	ast_bridge_features_hook_pvt_destructor destructor);
+	ast_bridge_hook_pvt_destructor destructor,
+	int remove_on_pull);
+
+/*!
+ * \brief attach an interval hook to a bridge features structure
+ *
+ * \param features Bridge features structure
+ * \param interval The interval that the hook should execute at in milliseconds
+ * \param callback Function to execute upon activation
+ * \param hook_pvt Unique data
+ * \param destructor Optional destructor callback for hook_pvt data
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * \code
+ * struct ast_bridge_features features;
+ * ast_bridge_features_init(&features);
+ * ast_bridge_interval_hook(&features, 1000, playback_callback, NULL, NULL, 0);
+ * \endcode
+ *
+ * This makes the bridging core call playback_callback every second. A pointer to useful
+ * data may be provided to the hook_pvt parameter.
+ */
+int ast_bridge_interval_hook(struct ast_bridge_features *features,
+	unsigned int interval,
+	ast_bridge_hook_callback callback,
+	void *hook_pvt,
+	ast_bridge_hook_pvt_destructor destructor,
+	int remove_on_pull);
 
 /*!
  * \brief Set a callback on the features structure to receive talking notifications on.
@@ -257,6 +549,8 @@ void ast_bridge_features_set_talk_detector(struct ast_bridge_features *features,
  * \param feature Feature to enable
  * \param dtmf Optionally the DTMF stream to trigger the feature, if not specified it will be the default
  * \param config Configuration structure unique to the built in type
+ * \param destructor Optional destructor callback for config data
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
  *
  * \retval 0 on success
  * \retval -1 on failure
@@ -266,19 +560,72 @@ void ast_bridge_features_set_talk_detector(struct ast_bridge_features *features,
  * \code
  * struct ast_bridge_features features;
  * ast_bridge_features_init(&features);
- * ast_bridge_features_enable(&features, AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, NULL);
+ * ast_bridge_features_enable(&features, AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, NULL, NULL, 0);
  * \endcode
  *
  * This enables the attended transfer DTMF option using the default DTMF string. An alternate
  * string may be provided using the dtmf parameter. Internally this is simply setting up a hook
  * to a built in feature callback function.
  */
-int ast_bridge_features_enable(struct ast_bridge_features *features, enum ast_bridge_builtin_feature feature, const char *dtmf, void *config);
+int ast_bridge_features_enable(struct ast_bridge_features *features,
+	enum ast_bridge_builtin_feature feature,
+	const char *dtmf,
+	void *config,
+	ast_bridge_hook_pvt_destructor destructor,
+	int remove_on_pull);
+
+/*!
+ * \brief Constructor function for ast_bridge_features_limits
+ *
+ * \param limits pointer to a ast_bridge_features_limits struct that has been allocted, but not initialized
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int ast_bridge_features_limits_construct(struct ast_bridge_features_limits *limits);
+
+/*!
+ * \brief Destructor function for ast_bridge_features_limits
+ *
+ * \param limits pointer to an ast_bridge_features_limits struct that needs to be destroyed
+ *
+ * This function does not free memory allocated to the ast_bridge_features_limits struct, it only frees elements within the struct.
+ * You must still call ast_free on the the struct if you allocated it with malloc.
+ */
+void ast_bridge_features_limits_destroy(struct ast_bridge_features_limits *limits);
 
 /*!
- * \brief Set a flag on a bridge features structure
+ * \brief Limit the amount of time a channel may stay in the bridge and optionally play warning messages as time runs out
  *
  * \param features Bridge features structure
+ * \param limits Configured limits applicable to the channel
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * Example usage:
+ *
+ * \code
+ * struct ast_bridge_features features;
+ * struct ast_bridge_features_limits limits;
+ * ast_bridge_features_init(&features);
+ * ast_bridge_features_limits_construct(&limits);
+ * ast_bridge_features_set_limits(&features, &limits, 0);
+ * ast_bridge_features_limits_destroy(&limits);
+ * \endcode
+ *
+ * This sets the maximum time the channel can be in the bridge to 10 seconds and does not play any warnings.
+ *
+ * \note This API call can only be used on a features structure that will be used in association with a bridge channel.
+ * \note The ast_bridge_features_limits structure must remain accessible for the lifetime of the features structure.
+ */
+int ast_bridge_features_set_limits(struct ast_bridge_features *features, struct ast_bridge_features_limits *limits, int remove_on_pull);
+
+/*!
+ * \brief Set a flag on a bridge channel features structure
+ *
+ * \param features Bridge channel features structure
  * \param flag Flag to enable
  *
  * \return Nothing
@@ -288,13 +635,13 @@ int ast_bridge_features_enable(struct ast_bridge_features *features, enum ast_br
  * \code
  * struct ast_bridge_features features;
  * ast_bridge_features_init(&features);
- * ast_bridge_features_set_flag(&features, AST_BRIDGE_FLAG_DISSOLVE);
+ * ast_bridge_features_set_flag(&features, AST_BRIDGE_CHANNEL_FLAG_DISSOLVE_HANGUP);
  * \endcode
  *
- * This sets the AST_BRIDGE_FLAG_DISSOLVE feature to be enabled on the features structure
- * 'features'.
+ * This sets the AST_BRIDGE_CHANNEL_FLAG_DISSOLVE_HANGUP feature
+ * to be enabled on the features structure 'features'.
  */
-void ast_bridge_features_set_flag(struct ast_bridge_features *features, enum ast_bridge_feature_flags flag);
+void ast_bridge_features_set_flag(struct ast_bridge_features *features, unsigned int flag);
 
 /*!
  * \brief Initialize bridge features structure
@@ -341,26 +688,39 @@ int ast_bridge_features_init(struct ast_bridge_features *features);
 void ast_bridge_features_cleanup(struct ast_bridge_features *features);
 
 /*!
- * \brief Play a DTMF stream into a bridge, optionally not to a given channel
+ * \brief Allocate a new bridge features struct.
+ * \since 12.0.0
  *
- * \param bridge Bridge to play stream into
- * \param dtmf DTMF to play
- * \param chan Channel to optionally not play to
+ * Example usage:
  *
- * \retval 0 on success
- * \retval -1 on failure
+ * \code
+ * struct ast_bridge_features *features;
+ * features = ast_bridge_features_new();
+ * ast_bridge_features_destroy(features);
+ * \endcode
+ *
+ * \retval features New allocated features struct.
+ * \retval NULL on error.
+ */
+struct ast_bridge_features *ast_bridge_features_new(void);
+
+/*!
+ * \brief Destroy an allocated bridge features struct.
+ * \since 12.0.0
+ *
+ * \param features Bridge features structure
  *
  * Example usage:
  *
  * \code
- * ast_bridge_dtmf_stream(bridge, "0123456789", NULL);
+ * struct ast_bridge_features *features;
+ * features = ast_bridge_features_new();
+ * ast_bridge_features_destroy(features);
  * \endcode
  *
- * This sends the DTMF digits '0123456789' to all channels in the bridge pointed to
- * by the bridge pointer. Optionally a channel may be excluded by passing it's channel pointer
- * using the chan parameter.
+ * \return Nothing
  */
-int ast_bridge_dtmf_stream(struct ast_bridge *bridge, const char *dtmf, struct ast_channel *chan);
+void ast_bridge_features_destroy(struct ast_bridge_features *features);
 
 #if defined(__cplusplus) || defined(c_plusplus)
 }
diff --git a/include/asterisk/bridging_roles.h b/include/asterisk/bridging_roles.h
new file mode 100644
index 0000000000000000000000000000000000000000..3acb67faefb79ff1094a4ee1d5f04733c725d940
--- /dev/null
+++ b/include/asterisk/bridging_roles.h
@@ -0,0 +1,135 @@
+/*
+ * 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 Channel Bridging Roles API
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#ifndef _ASTERISK_BRIDGING_ROLES_H
+#define _ASTERISK_BRIDGING_ROLES_H
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+#include "asterisk/linkedlists.h"
+
+#define AST_ROLE_LEN 32
+
+/*!
+ * \brief Adds a bridge role to a channel
+ *
+ * \param chan Acquirer of the requested role
+ * \param role_name Name of the role being attached
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int ast_channel_add_bridge_role(struct ast_channel *chan, const char *role_name);
+
+/*!
+ * \brief Removes a bridge role from a channel
+ *
+ * \param chan Channel the role is being removed from
+ * \param role_name Name of the role being removed
+ */
+void ast_channel_remove_bridge_role(struct ast_channel *chan, const char *role_name);
+
+/*!
+ * \brief Set a role option on a channel
+ * \param channel Channel receiving the role option
+ * \param role_name Role the role option is applied to
+ * \param option Name of the option
+ * \param value Value of the option
+ *
+ * \param 0 on success
+ * \retval -1 on failure
+ */
+int ast_channel_set_bridge_role_option(struct ast_channel *channel, const char *role_name, const char *option, const char *value);
+
+/*!
+ * \brief Check to see if a bridge channel inherited a specific role from its channel
+ *
+ * \param bridge_channel The bridge channel being checked
+ * \param role_name Name of the role being checked
+ *
+ * \retval 0 The bridge channel does not have the requested role
+ * \retval 1 The bridge channel does have the requested role
+ *
+ * \note Before a bridge_channel can effectively check roles against a bridge, ast_bridge_roles_bridge_channel_establish_roles
+ *       should be called on the bridge_channel so that roles and their respective role options can be copied from the channel
+ *       datastore into the bridge_channel roles list. Otherwise this function will just return 0 because the list will be NULL.
+ */
+int ast_bridge_channel_has_role(struct ast_bridge_channel *bridge_channel, const char *role_name);
+
+/*!
+ * \brief Retrieve the value of a requested role option from a bridge channel
+ *
+ * \param bridge_channel The bridge channel we are retrieving the option from
+ * \param role_name Name of the role the option will be retrieved from
+ * \param option Name of the option we are retrieving the value of
+ *
+ * \retval NULL If either the role does not exist on the bridge_channel or the role does exist but the option has not been set
+ * \retval The value of the option
+ *
+ * \note See ast_bridge_roles_channel_set_role_option note about the need to call ast_bridge_roles_bridge_channel_establish_roles.
+ *
+ * \note The returned character pointer is only valid as long as the bridge_channel is guaranteed to be alive and hasn't had
+ *       ast_bridge_roles_bridge_channel_clear_roles called against it (as this will free all roles and role options in the bridge
+ *       channel). If you need this value after one of these destruction events occurs, you must make a local copy while it is
+ *       still valid.
+ */
+const char *ast_bridge_channel_get_role_option(struct ast_bridge_channel *bridge_channel, const char *role_name, const char *option);
+
+/*!
+ * \brief Clone the roles from a bridge_channel's attached ast_channel onto the bridge_channel's role list
+ *
+ * \param bridge_channel The bridge channel that we are preparing
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * \details
+ * This function should always be called when the bridge_channel binds to an ast_channel at some point before the bridge_channel
+ * joins or is imparted onto a bridge. Failure to do so will result in an empty role list. While the list remains established,
+ * changes to roles on the ast_channel will not propogate to the bridge channel and roles can not be re-established on the bridge
+ * channel without first clearing the roles with ast_bridge_roles_bridge_channel_clear_roles.
+ */
+int ast_bridge_channel_establish_roles(struct ast_bridge_channel *bridge_channel);
+
+/*!
+ * \brief Clear all roles from a bridge_channel's role list
+ *
+ * \param bridge_channel the bridge channel that we are scrubbing
+ *
+ * \details
+ * If roles are already established on a bridge channel, ast_bridge_roles_bridge_channel_establish_roles will fail unconditionally
+ * without changing any roles. In order to update a bridge channel's roles, they must first be cleared from the bridge channel using
+ * this function.
+ *
+ * \note
+ * ast_bridge_roles_bridge_channel_clear_roles also serves as the destructor for the role list of a bridge channel.
+ */
+void ast_bridge_channel_clear_roles(struct ast_bridge_channel *bridge_channel);
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* _ASTERISK_BRIDGING_ROLES_H */
diff --git a/include/asterisk/bridging_technology.h b/include/asterisk/bridging_technology.h
index d09c4bec45384ad28ce4ef03bf46f84efe60aa53..2bb2170f0146b2799bf52a33293a6aedaa8bb01e 100644
--- a/include/asterisk/bridging_technology.h
+++ b/include/asterisk/bridging_technology.h
@@ -28,14 +28,17 @@
 extern "C" {
 #endif
 
-/*! \brief Preference for choosing the bridge technology */
+/*!
+ * \brief Base preference values for choosing a bridge technology.
+ *
+ * \note Higher is more preference.
+ */
 enum ast_bridge_preference {
-	/*! Bridge technology should have high precedence over other bridge technologies */
-	AST_BRIDGE_PREFERENCE_HIGH = 0,
-	/*! Bridge technology is decent, not the best but should still be considered over low */
-	AST_BRIDGE_PREFERENCE_MEDIUM,
-	/*! Bridge technology is low, it should not be considered unless it is absolutely needed */
-	AST_BRIDGE_PREFERENCE_LOW,
+	AST_BRIDGE_PREFERENCE_BASE_HOLDING  = 50,
+	AST_BRIDGE_PREFERENCE_BASE_EARLY    = 100,
+	AST_BRIDGE_PREFERENCE_BASE_NATIVE   = 90,
+	AST_BRIDGE_PREFERENCE_BASE_1TO1MIX  = 50,
+	AST_BRIDGE_PREFERENCE_BASE_MULTIMIX = 10,
 };
 
 /*!
@@ -49,31 +52,68 @@ struct ast_bridge_technology {
 	uint32_t capabilities;
 	/*! Preference level that should be used when determining whether to use this bridge technology or not */
 	enum ast_bridge_preference preference;
-	/*! Callback for when a bridge is being created */
+	/*!
+	 * \brief Callback for when a bridge is being created.
+	 *
+	 * \retval 0 on success
+	 * \retval -1 on failure
+	 *
+	 * \note On entry, bridge may or may not already be locked.
+	 * However, it can be accessed as if it were locked.
+	 */
 	int (*create)(struct ast_bridge *bridge);
-	/*! Callback for when a bridge is being destroyed */
-	int (*destroy)(struct ast_bridge *bridge);
-	/*! Callback for when a channel is being added to a bridge */
+	/*!
+	 * \brief Callback for when a bridge is being destroyed
+	 *
+	 * \note On entry, bridge must NOT be locked.
+	 */
+	void (*destroy)(struct ast_bridge *bridge);
+	/*!
+	 * \brief Callback for when a channel is being added to a bridge.
+	 *
+	 * \retval 0 on success
+	 * \retval -1 on failure
+	 *
+	 * \note On entry, bridge is already locked.
+	 */
 	int (*join)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
-	/*! Callback for when a channel is leaving a bridge */
-	int (*leave)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
-	/*! Callback for when a channel is suspended from the bridge */
+	/*!
+	 * \brief Callback for when a channel is leaving a bridge
+	 *
+	 * \note On entry, bridge is already locked.
+	 */
+	void (*leave)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+	/*!
+	 * \brief Callback for when a channel is suspended from the bridge
+	 *
+	 * \note On entry, bridge is already locked.
+	 */
 	void (*suspend)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
-	/*! Callback for when a channel is unsuspended from the bridge */
+	/*!
+	 * \brief Callback for when a channel is unsuspended from the bridge
+	 *
+	 * \note On entry, bridge is already locked.
+	 */
 	void (*unsuspend)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
-	/*! Callback to see if a channel is compatible with the bridging technology */
-	int (*compatible)(struct ast_bridge_channel *bridge_channel);
-	/*! Callback for writing a frame into the bridging technology */
-	enum ast_bridge_write_result (*write)(struct ast_bridge *bridge, struct ast_bridge_channel *bridged_channel, struct ast_frame *frame);
-	/*! Callback for when a file descriptor trips */
-	int (*fd)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int fd);
-	/*! Callback for replacement thread function */
-	int (*thread)(struct ast_bridge *bridge);
-	/*! Callback for poking a bridge thread */
-	int (*poke)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+	/*!
+	 * \brief Callback to see if the bridge is compatible with the bridging technology.
+	 *
+	 * \retval 0 if not compatible
+	 * \retval non-zero if compatible
+	 */
+	int (*compatible)(struct ast_bridge *bridge);
+	/*!
+	 * \brief Callback for writing a frame into the bridging technology.
+	 *
+	 * \retval 0 on success
+	 * \retval -1 on failure
+	 *
+	 * \note On entry, bridge is already locked.
+	 */
+	int (*write)(struct ast_bridge *bridge, struct ast_bridge_channel *bridged_channel, struct ast_frame *frame);
 	/*! Formats that the bridge technology supports */
 	struct ast_format_cap *format_capabilities;
-	/*! Bit to indicate whether the bridge technology is currently suspended or not */
+	/*! TRUE if the bridge technology is currently suspended. */
 	unsigned int suspended:1;
 	/*! Module this bridge technology belongs to. Is used for reference counting when creating/destroying a bridge. */
 	struct ast_module *mod;
@@ -125,27 +165,6 @@ int __ast_bridge_technology_register(struct ast_bridge_technology *technology, s
  */
 int ast_bridge_technology_unregister(struct ast_bridge_technology *technology);
 
-/*!
- * \brief Feed notification that a frame is waiting on a channel into the bridging core
- *
- * \param bridge The bridge that the notification should influence
- * \param bridge_channel Bridge channel the notification was received on (if known)
- * \param chan Channel the notification was received on (if known)
- * \param outfd File descriptor that the notification was received on (if known)
- *
- * Example usage:
- *
- * \code
- * ast_bridge_handle_trip(bridge, NULL, chan, -1);
- * \endcode
- *
- * This tells the bridging core that a frame has been received on
- * the channel pointed to by chan and that it should be read and handled.
- *
- * \note This should only be used by bridging technologies.
- */
-void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_channel *chan, int outfd);
-
 /*!
  * \brief Lets the bridging indicate when a bridge channel has stopped or started talking.
  *
@@ -155,12 +174,11 @@ void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel
  * application, this function has been created to allow the bridging technology to communicate
  * that information with the bridging core.
  *
- * \param bridge The bridge that the channel is a part of.
  * \param bridge_channel The bridge channel that has either started or stopped talking.
  * \param started_talking set to 1 when this indicates the channel has started talking set to 0
  * when this indicates the channel has stopped talking.
  */
-void ast_bridge_notify_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int started_talking);
+void ast_bridge_notify_talking(struct ast_bridge_channel *bridge_channel, int started_talking);
 
 /*!
  * \brief Suspend a bridge technology from consideration
diff --git a/include/asterisk/ccss.h b/include/asterisk/ccss.h
index cf2f2199669b0f7dd2e5abaffcf76ee798fa51de..d8101cddf93a39d9a05fa6f54a1d2ca4fc29dba7 100644
--- a/include/asterisk/ccss.h
+++ b/include/asterisk/ccss.h
@@ -1485,10 +1485,12 @@ int ast_cc_agent_set_interfaces_chanvar(struct ast_channel *chan);
  * \verbatim extension@context \endverbatim as a starting point
  *
  * \details
- * The CC_INTERFACES channel variable will have the interfaces that should be
- * called back for a specific PBX instance. This version of the function is used
- * mainly by chan_local, wherein we need to set CC_INTERFACES based on an extension
- * and context that appear in the middle of the tree of dialed interfaces
+ * The CC_INTERFACES channel variable will have the interfaces
+ * that should be called back for a specific PBX instance.  This
+ * version of the function is used mainly by local channels,
+ * wherein we need to set CC_INTERFACES based on an extension
+ * and context that appear in the middle of the tree of dialed
+ * interfaces.
  *
  * \note
  * This function will lock the channel as well as the list of monitors stored
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index 939c8db513dc766a8be8d272570a5d4be24569a4..91373cfbda3250196fb4de41a164749d674797e2 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -643,6 +643,7 @@ struct ast_channel_tech {
 	/*! \brief Handle an exception, reading a frame */
 	struct ast_frame * (* const exception)(struct ast_channel *chan);
 
+/* BUGBUG this tech callback is to be removed. */
 	/*! \brief Bridge two channels of the same type together */
 	enum ast_bridge_result (* const bridge)(struct ast_channel *c0, struct ast_channel *c1, int flags,
 						struct ast_frame **fo, struct ast_channel **rc, int timeoutms);
@@ -671,6 +672,7 @@ struct ast_channel_tech {
 	/*! \brief Write a text frame, in standard format */
 	int (* const write_text)(struct ast_channel *chan, struct ast_frame *frame);
 
+/* BUGBUG this tech callback is to be removed. */
 	/*! \brief Find bridged channel */
 	struct ast_channel *(* const bridged_channel)(struct ast_channel *chan, struct ast_channel *bridge);
 
@@ -686,6 +688,7 @@ struct ast_channel_tech {
 	 */
 	int (* func_channel_write)(struct ast_channel *chan, const char *function, char *data, const char *value);
 
+/* BUGBUG this tech callback is to be removed. */
 	/*! \brief Retrieve base channel (agent and local) */
 	struct ast_channel* (* get_base_channel)(struct ast_channel *chan);
 
@@ -896,10 +899,6 @@ enum {
 	 *  a message aimed at preventing a subsequent hangup exten being run at the pbx_run
 	 *  level */
 	AST_FLAG_BRIDGE_HANGUP_RUN = (1 << 17),
-	/*! This flag indicates that the hangup exten should NOT be run when the
-	 *  bridge terminates, this will allow the hangup in the pbx loop to be run instead.
-	 *  */
-	AST_FLAG_BRIDGE_HANGUP_DONT = (1 << 18),
 	/*! Disable certain workarounds.  This reintroduces certain bugs, but allows
 	 *  some non-traditional dialplans (like AGI) to continue to function.
 	 */
@@ -928,7 +927,6 @@ enum {
 	AST_FEATURE_AUTOMON =      (1 << 4),
 	AST_FEATURE_PARKCALL =     (1 << 5),
 	AST_FEATURE_AUTOMIXMON =   (1 << 6),
-	AST_FEATURE_NO_H_EXTEN =   (1 << 7),
 	AST_FEATURE_WARNING_ACTIVE = (1 << 8),
 };
 
@@ -4038,6 +4036,9 @@ void ast_channel_timingfunc_set(struct ast_channel *chan, ast_timing_func_t valu
 struct ast_bridge *ast_channel_internal_bridge(const struct ast_channel *chan);
 void ast_channel_internal_bridge_set(struct ast_channel *chan, struct ast_bridge *value);
 
+struct ast_bridge_channel *ast_channel_internal_bridge_channel(const struct ast_channel *chan);
+void ast_channel_internal_bridge_channel_set(struct ast_channel *chan, struct ast_bridge_channel *value);
+
 struct ast_channel *ast_channel_internal_bridged_channel(const struct ast_channel *chan);
 void ast_channel_internal_bridged_channel_set(struct ast_channel *chan, struct ast_channel *value);
 
@@ -4138,4 +4139,69 @@ struct varshead *ast_channel_get_manager_vars(struct ast_channel *chan);
  */
 struct stasis_topic *ast_channel_topic(struct ast_channel *chan);
 
+/*!
+ * \brief Get the bridge associated with a channel
+ * \since 12.0.0
+ *
+ * \param chan The channel whose bridge we want
+ *
+ * \details
+ * The bridge returned has its reference count incremented.  Use
+ * ao2_cleanup() or ao2_ref() in order to decrement the
+ * reference count when you are finished with the bridge.
+ *
+ * \note This function expects the channel to be locked prior to
+ * being called and will not grab the channel lock.
+ *
+ * \retval NULL No bridge present on the channel
+ * \retval non-NULL The bridge the channel is in
+ */
+struct ast_bridge *ast_channel_get_bridge(const struct ast_channel *chan);
+
+/*!
+ * \brief Determine if a channel is in a bridge
+ * \since 12.0.0
+ *
+ * \param chan The channel to test
+ *
+ * \note This function expects the channel to be locked prior to
+ * being called and will not grab the channel lock.
+ *
+ * \retval 0 The channel is not bridged
+ * \retval non-zero The channel is bridged
+ */
+int ast_channel_is_bridged(const struct ast_channel *chan);
+
+/*!
+ * \brief Get the channel's bridge peer only if the bridge is two-party.
+ * \since 12.0.0
+ *
+ * \param chan Channel desiring the bridge peer channel.
+ *
+ * \note The returned peer channel is the current peer in the
+ * bridge when called.
+ *
+ * \retval NULL Channel not in a bridge or the bridge is not two-party.
+ * \retval non-NULL Reffed peer channel at time of calling.
+ */
+struct ast_channel *ast_channel_bridge_peer(struct ast_channel *chan);
+
+/*!
+ * \brief Get a reference to the channel's bridge pointer.
+ * \since 12.0.0
+ *
+ * \param chan The channel whose bridge channel is desired
+ *
+ * \note This increases the reference count of the bridge_channel.
+ * Use ao2_ref() or ao2_cleanup() to decrement the refcount when
+ * you are finished with it.
+ *
+ * \note It is expected that the channel is locked prior to
+ * placing this call.
+ *
+ * \retval NULL The channel has no bridge_channel
+ * \retval non-NULL A reference to the bridge_channel
+ */
+struct ast_bridge_channel *ast_channel_get_bridge_channel(struct ast_channel *chan);
+
 #endif /* _ASTERISK_CHANNEL_H */
diff --git a/include/asterisk/config_options.h b/include/asterisk/config_options.h
index 64d8d5089326ad55a78d8a104ffd1329567a6ec7..739d83130032bacd53fa10b836aae3382f3a6786 100644
--- a/include/asterisk/config_options.h
+++ b/include/asterisk/config_options.h
@@ -575,6 +575,16 @@ int __aco_option_register(struct aco_info *info, const char *name, enum aco_matc
  */
 int aco_option_register_deprecated(struct aco_info *info, const char *name, struct aco_type **types, const char *aliased_to);
 
+/*!
+ * \brief Read the flags of a config option - useful when using a custom callback for a config option
+ * \since 12
+ *
+ * \param option Pointer to the aco_option struct
+ *
+ * \retval value of the flags on the config option
+ */
+unsigned int aco_option_get_flags(const struct aco_option *option);
+
 /*! \note  Everything below this point is to handle converting varargs
  * containing field names, to varargs containing a count of args, followed
  * by the offset of each of the field names in the struct type that is
diff --git a/include/asterisk/core_local.h b/include/asterisk/core_local.h
new file mode 100644
index 0000000000000000000000000000000000000000..693c93b4679ea936a4e300560269c157e3cd2f82
--- /dev/null
+++ b/include/asterisk/core_local.h
@@ -0,0 +1,98 @@
+/*
+ * 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 Local proxy channel special access.
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+#ifndef _ASTERISK_CORE_LOCAL_H
+#define _ASTERISK_CORE_LOCAL_H
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+/* Forward declare some struct names */
+struct ast_channel;
+struct ast_bridge;
+struct ast_bridge_features;
+
+/* ------------------------------------------------------------------- */
+
+/*!
+ * \brief Get the other local channel in the pair.
+ * \since 12.0.0
+ *
+ * \param ast Local channel to get peer.
+ *
+ * \note On entry, ast must be locked.
+ *
+ * \retval peer reffed on success.
+ * \retval NULL if no peer or error.
+ */
+struct ast_channel *ast_local_get_peer(struct ast_channel *ast);
+
+/*!
+ * \brief Setup the outgoing local channel to join a bridge on ast_call().
+ * \since 12.0.0
+ *
+ * \param ast Either channel of a local channel pair.
+ * \param bridge Bridge to join.
+ * \param swap Channel to swap with when joining.
+ * \param features Bridge features structure.
+ *
+ * \note The features parameter must be NULL or obtained by
+ * ast_bridge_features_new().  You must not dereference features
+ * after calling even if the call fails.
+ *
+ * \note Intended to be called after ast_request() and before
+ * ast_call() on a local channel.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_local_setup_bridge(struct ast_channel *ast, struct ast_bridge *bridge, struct ast_channel *swap, struct ast_bridge_features *features);
+
+/*!
+ * \brief Setup the outgoing local channel to masquerade into a channel on ast_call().
+ * \since 12.0.0
+ *
+ * \param ast Either channel of a local channel pair.
+ * \param masq Channel to masquerade into.
+ *
+ * \note Intended to be called after ast_request() and before
+ * ast_call() on a local channel.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_local_setup_masquerade(struct ast_channel *ast, struct ast_channel *masq);
+
+/* ------------------------------------------------------------------- */
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif	/* _ASTERISK_CORE_LOCAL_H */
diff --git a/include/asterisk/core_unreal.h b/include/asterisk/core_unreal.h
new file mode 100644
index 0000000000000000000000000000000000000000..7cb68f162307607d84df9f4d7c8c64ba575a9e62
--- /dev/null
+++ b/include/asterisk/core_unreal.h
@@ -0,0 +1,191 @@
+/*
+ * 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 Unreal channel derivative framework.
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+#ifndef _ASTERISK_CORE_UNREAL_H
+#define _ASTERISK_CORE_UNREAL_H
+
+#include "asterisk/astobj2.h"
+#include "asterisk/channel.h"
+#include "asterisk/abstract_jb.h"
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+/* Forward declare some struct names */
+struct ast_format_cap;
+struct ast_callid;
+
+/* ------------------------------------------------------------------- */
+
+/*!
+ * \brief The base pvt structure for local channel derivatives.
+ *
+ * The unreal pvt has two ast_chan objects - the "owner" and the "next channel", the outbound channel
+ *
+ * ast_chan owner -> ast_unreal_pvt -> ast_chan chan
+ */
+struct ast_unreal_pvt {
+	struct ast_channel *owner;      /*!< Master Channel - ;1 side */
+	struct ast_channel *chan;       /*!< Outbound channel - ;2 side */
+	struct ast_format_cap *reqcap;  /*!< Requested format capabilities */
+	struct ast_jb_conf jb_conf;     /*!< jitterbuffer configuration */
+	unsigned int flags;             /*!< Private option flags */
+	/*! Base name of the unreal channels.  exten@context or other name. */
+	char name[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2];
+};
+
+#define AST_UNREAL_IS_OUTBOUND(a, b) ((a) == (b)->chan ? 1 : 0)
+
+#define AST_UNREAL_CARETAKER_THREAD (1 << 0) /*!< The ;2 side launched a PBX, was pushed into a bridge, or was masqueraded into an application. */
+#define AST_UNREAL_NO_OPTIMIZATION  (1 << 1) /*!< Do not optimize out the unreal channels */
+#define AST_UNREAL_MOH_INTERCEPT    (1 << 2) /*!< Intercept and act on hold/unhold control frames */
+
+/*!
+ * \brief Send an unreal pvt in with no locks held and get all locks
+ *
+ * \note NO locks should be held prior to calling this function
+ * \note The pvt must have a ref held before calling this function
+ * \note if outchan or outowner is set != NULL after calling this function
+ *       those channels are locked and reffed.
+ * \note Batman.
+ */
+void ast_unreal_lock_all(struct ast_unreal_pvt *p, struct ast_channel **outchan, struct ast_channel **outowner);
+
+/*!
+ * \brief Hangup one end (maybe both ends) of an unreal channel derivative.
+ * \since 12.0.0
+ *
+ * \param p Private channel struct (reffed)
+ * \param ast Channel being hung up.  (locked)
+ *
+ * \note Common hangup code for unreal channels.  Derived
+ * channels will need to deal with any additional resources.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_unreal_hangup(struct ast_unreal_pvt *p, struct ast_channel *ast);
+
+/*! Unreal channel framework struct ast_channel_tech.send_digit_begin callback */
+int ast_unreal_digit_begin(struct ast_channel *ast, char digit);
+
+/*! Unreal channel framework struct ast_channel_tech.send_digit_end callback */
+int ast_unreal_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
+
+/*! Unreal channel framework struct ast_channel_tech.answer callback */
+int ast_unreal_answer(struct ast_channel *ast);
+
+/*! Unreal channel framework struct ast_channel_tech.read and struct ast_channel_tech.exception callback */
+struct ast_frame *ast_unreal_read(struct ast_channel *ast);
+
+/*! Unreal channel framework struct ast_channel_tech.write callback */
+int ast_unreal_write(struct ast_channel *ast, struct ast_frame *f);
+
+/*! Unreal channel framework struct ast_channel_tech.indicate callback */
+int ast_unreal_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
+
+/*! Unreal channel framework struct ast_channel_tech.fixup callback */
+int ast_unreal_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+
+/*! Unreal channel framework struct ast_channel_tech.send_html callback */
+int ast_unreal_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen);
+
+/*! Unreal channel framework struct ast_channel_tech.send_text callback */
+int ast_unreal_sendtext(struct ast_channel *ast, const char *text);
+
+/*! Unreal channel framework struct ast_channel_tech.queryoption callback */
+int ast_unreal_queryoption(struct ast_channel *ast, int option, void *data, int *datalen);
+
+/*! Unreal channel framework struct ast_channel_tech.setoption callback */
+int ast_unreal_setoption(struct ast_channel *chan, int option, void *data, int datalen);
+
+/*!
+ * \brief struct ast_unreal_pvt destructor.
+ * \since 12.0.0
+ *
+ * \param vdoomed Object to destroy.
+ *
+ * \return Nothing
+ */
+void ast_unreal_destructor(void *vdoomed);
+
+/*!
+ * \brief Allocate the base unreal struct for a derivative.
+ * \since 12.0.0
+ *
+ * \param size Size of the unreal struct to allocate.
+ * \param destructor Destructor callback.
+ * \param cap Format capabilities to give the unreal private struct.
+ *
+ * \retval pvt on success.
+ * \retval NULL on error.
+ */
+struct ast_unreal_pvt *ast_unreal_alloc(size_t size, ao2_destructor_fn destructor, struct ast_format_cap *cap);
+
+/*!
+ * \brief Create the semi1 and semi2 unreal channels.
+ * \since 12.0.0
+ *
+ * \param p Unreal channel private struct.
+ * \param tech Channel technology to use.
+ * \param semi1_state State to start the semi1(owner) channel in.
+ * \param semi2_state State to start the semi2(outgoing chan) channel in.
+ * \param exten Exten to start the chennels in. (NULL if s)
+ * \param context Context to start the channels in. (NULL if default)
+ * \param requestor Channel requesting creation. (NULL if none)
+ * \param callid Thread callid to use.
+ *
+ * \retval semi1_channel on success.
+ * \retval NULL on error.
+ */
+struct ast_channel *ast_unreal_new_channels(struct ast_unreal_pvt *p,
+	const struct ast_channel_tech *tech, int semi1_state, int semi2_state,
+	const char *exten, const char *context, const struct ast_channel *requestor,
+	struct ast_callid *callid);
+
+/*!
+ * \brief Setup unreal owner and chan channels before initiating call.
+ * \since 12.0.0
+ *
+ * \param semi1 Owner channel of unreal channel pair.
+ * \param semi2 Outgoing channel of unreal channel pair.
+ *
+ * \note On entry, the semi1 and semi2 channels are already locked.
+ *
+ * \return Nothing
+ */
+void ast_unreal_call_setup(struct ast_channel *semi1, struct ast_channel *semi2);
+
+/* ------------------------------------------------------------------- */
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif	/* _ASTERISK_CORE_UNREAL_H */
diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h
index abb0c2e28d4b853cde6496e712ffdab78f492a25..bedc3a25d9c022135c5954aca0b6a79e5a4560b8 100644
--- a/include/asterisk/frame.h
+++ b/include/asterisk/frame.h
@@ -120,6 +120,8 @@ enum ast_frame_type {
 	AST_FRAME_MODEM,
 	/*! DTMF begin event, subclass is the digit */
 	AST_FRAME_DTMF_BEGIN,
+	/*! Internal bridge module action. */
+	AST_FRAME_BRIDGE_ACTION,
 };
 #define AST_FRAME_DTMF AST_FRAME_DTMF_END
 
diff --git a/include/asterisk/framehook.h b/include/asterisk/framehook.h
index 52993b55c9785cff426ab1d0dbda684786b86b05..10d525ca731541fa2b6d1a25a676fe2dd62b5ba4 100644
--- a/include/asterisk/framehook.h
+++ b/include/asterisk/framehook.h
@@ -228,7 +228,7 @@ struct ast_framehook_interface {
  * provide it during the event and destruction callbacks.  It is entirely up to the
  * application using this API to manage the memory associated with the data pointer.
  *
- * \retval On success, positive id representing this hook on the channel 
+ * \retval On success, non-negative id representing this hook on the channel
  * \retval On failure, -1
  */
 int ast_framehook_attach(struct ast_channel *chan, struct ast_framehook_interface *i);
diff --git a/include/asterisk/manager.h b/include/asterisk/manager.h
index e0160c6b576accd66635cb32828816fe83f455db..4e9b8d14a7c2074059a0e35575ccf5cad00217c5 100644
--- a/include/asterisk/manager.h
+++ b/include/asterisk/manager.h
@@ -347,6 +347,55 @@ struct ast_str *ast_manager_build_channel_state_string_suffix(
 struct ast_str *ast_manager_build_channel_state_string(
 		const struct ast_channel_snapshot *snapshot);
 
+/*! \brief Struct representing a snapshot of bridge state */
+struct ast_bridge_snapshot;
+
+/*!
+ * \brief Generate the AMI message body from a bridge snapshot
+ * \since 12
+ *
+ * \param snapshot the bridge snapshot for which to generate an AMI message
+ *                 body
+ *
+ * \retval NULL on error
+ * \retval ast_str* on success (must be ast_freed by caller)
+ */
+struct ast_str *ast_manager_build_bridge_state_string(
+	const struct ast_bridge_snapshot *snapshot,
+	const char *suffix);
+
+/*! \brief Struct containing info for an AMI event to send out. */
+struct ast_manager_event_blob {
+	int event_flags;		/*!< Flags the event should be raised with. */
+	const char *manager_event;	/*!< The event to be raised, should be a string literal. */
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(extra_fields);	/*!< Extra fields to include in the event. */
+	);
+};
+
+/*!
+ * \since 12
+ * \brief Construct a \ref snapshot_manager_event.
+ *
+ * \param event_flags Flags the event should be raised with.
+ * \param manager_event The event to be raised, should be a string literal.
+ * \param extra_fields_fmt Format string for extra fields to include.
+ *                         Or NO_EXTRA_FIELDS for no extra fields.
+ *
+ * \return New \ref ast_manager_snapshot_event object.
+ * \return \c NULL on error.
+ */
+struct ast_manager_event_blob *
+__attribute__((format(printf, 3, 4)))
+ast_manager_event_blob_create(
+	int event_flags,
+	const char *manager_event,
+	const char *extra_fields_fmt,
+	...);
+
+/*! GCC warns about blank or NULL format strings. So, shenanigans! */
+#define NO_EXTRA_FIELDS "%s", ""
+
 /*!
  * \brief Initialize support for AMI channel events.
  * \return 0 on success.
@@ -355,4 +404,12 @@ struct ast_str *ast_manager_build_channel_state_string(
  */
 int manager_channels_init(void);
 
+/*!
+ * \brief Initialize support for AMI channel events.
+ * \return 0 on success.
+ * \return non-zero on error.
+ * \since 12
+ */
+int manager_bridging_init(void);
+
 #endif /* _ASTERISK_MANAGER_H */
diff --git a/include/asterisk/parking.h b/include/asterisk/parking.h
new file mode 100644
index 0000000000000000000000000000000000000000..176eddb04202820f3f0ad49aac505c9bc2a1fb87
--- /dev/null
+++ b/include/asterisk/parking.h
@@ -0,0 +1,184 @@
+/*
+ * 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 Call Parking API
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk/stringfields.h"
+
+#define PARK_APPLICATION "Park"
+
+/*!
+ * \brief Defines the type of parked call message being published
+ * \since 12
+ */
+enum ast_parked_call_event_type {
+	PARKED_CALL = 0,
+	PARKED_CALL_TIMEOUT,
+	PARKED_CALL_GIVEUP,
+	PARKED_CALL_UNPARKED,
+	PARKED_CALL_FAILED,
+};
+
+/*!
+ * \brief A parked call message payload
+ * \since 12
+ */
+struct ast_parked_call_payload {
+	struct ast_channel_snapshot *parkee;             /*!< Snapshot of the channel that is parked */
+	struct ast_channel_snapshot *parker;             /*!< Snapshot of the channel that parked the call */
+	struct ast_channel_snapshot *retriever;          /*!< Snapshot of the channel that retrieved the call */
+	enum ast_parked_call_event_type event_type;      /*!< Reason for issuing the parked call message */
+	long unsigned int timeout;                       /*!< Time remaining before the call times out (seconds ) */
+	long unsigned int duration;                      /*!< How long the parkee has been parked (seconds) */
+	unsigned int parkingspace;                       /*!< Which Parking Space the parkee occupies */
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(parkinglot);                /*!< Name of the parking lot used to park the parkee */
+	);
+};
+
+/*!
+ * \brief Constructor for parked_call_payload objects
+ * \since 12
+ *
+ * \param event_type What kind of parked call event is happening
+ * \param parkee_snapshot channel snapshot of the parkee
+ * \param parker_snapshot channel snapshot of the parker
+ * \param retriever_snapshot channel snapshot of the retriever (NULL allowed)
+ * \param parkinglot name of the parking lot where the parked call is parked
+ * \param parkingspace what numerical parking space the parked call is parked in
+ * \param timeout how long the parked call can remain at the point this snapshot is created before timing out
+ * \param duration how long the parked call has currently been parked
+ *
+ * \retval NULL if the parked call payload can't be allocated
+ * \retval reference to a newly created parked call payload
+ */
+struct ast_parked_call_payload *ast_parked_call_payload_create(enum ast_parked_call_event_type event_type,
+		struct ast_channel_snapshot *parkee_snapshot, struct ast_channel_snapshot *parker_snapshot,
+		struct ast_channel_snapshot *retriever_snapshot, const char *parkinglot,
+		unsigned int parkingspace, unsigned long int timeout, unsigned long int duration);
+
+/*!
+ * \brief initialize parking stasis types
+ * \since 12
+ */
+void ast_parking_stasis_init(void);
+
+/*!
+ * \brief disable parking stasis types
+ * \since 12
+ */
+void ast_parking_stasis_disable(void);
+
+/*!
+ * \brief accessor for the parking stasis topic
+ * \since 12
+ *
+ * \retval NULL if the parking topic hasn't been created or has been disabled
+ * \retval a pointer to the parking topic
+ */
+struct stasis_topic *ast_parking_topic(void);
+
+/*!
+ * \brief accessor for the parked call stasis message type
+ * \since 12
+ *
+ * \retval NULL if the parking topic hasn't been created or has been canceled
+ * \retval a pointer to the parked call message type
+ */
+struct stasis_message_type *ast_parked_call_type(void);
+
+/*!
+ * \brief invoke an installable park callback to asynchronously park a bridge_channel in a bridge
+ * \since 12
+ *
+ * \param bridge_channel the bridge channel that initiated parking
+ * \parkee_uuid channel id of the channel being parked
+ * \parker_uuid channel id of the channel that initiated parking
+ * \param app_data string of application data that might be applied to parking
+ */
+void ast_bridge_channel_park(struct ast_bridge_channel *bridge_channel,
+	const char *parkee_uuid,
+	const char *parker_uuid,
+	const char *app_data);
+
+typedef int (*ast_park_blind_xfer_fn)(struct ast_bridge *bridge, struct ast_bridge_channel *parker,
+	struct ast_exten *park_exten);
+
+/*!
+ * \brief install a callback for handling blind transfers to a parking extension
+ * \since 12
+ *
+ * \param parking_func Function to use for transfers to 'Park' applications
+ */
+void ast_install_park_blind_xfer_func(ast_park_blind_xfer_fn park_blind_xfer_func);
+
+/*!
+ * \brief uninstall a callback for handling blind transfers to a parking extension
+ * \since 12
+ */
+void ast_uninstall_park_blind_xfer_func(void);
+
+/*!
+ * \brief use the installed park blind xfer func
+ * \since 12
+ *
+ * \param bridge Bridge being transferred from
+ * \param bridge_channel Bridge channel initiating the transfer
+ * \param app_data arguments to the park application
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int ast_park_blind_xfer(struct ast_bridge *bridge, struct ast_bridge_channel *parker,
+		struct ast_exten *park_exten);
+
+typedef void (*ast_bridge_channel_park_fn)(struct ast_bridge_channel *parkee, const char *parkee_uuid,
+	const char *parker_uuid, const char *app_data);
+
+/*!
+ * \brief Install a function for ast_bridge_channel_park
+ * \since 12
+ *
+ * \param bridge_channel_park_func function callback to use for ast_bridge_channel_park
+ */
+void ast_install_bridge_channel_park_func(ast_bridge_channel_park_fn bridge_channel_park_func);
+
+/*!
+ * \brief Uninstall the ast_bridge_channel_park function callback
+ * \since 12
+ */
+void ast_uninstall_bridge_channel_park_func(void);
+
+
+/*!
+ * \brief Determines whether a certain extension is a park application extension or not.
+ * \since 12
+ *
+ * \param exten_str string representation of the extension sought
+ * \param chan channel the extension is sought for
+ * \param context context the extension is sought from
+ *
+ * \retval pointer to the extension if the extension is a park extension
+ * \retval NULL if the extension was not a park extension
+ */
+struct ast_exten *ast_get_parking_exten(const char *exten_str, struct ast_channel *chan, const char *context);
diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h
index c8a144b73f8578aefea5183b60d9290ea9ae4009..e2567f50816116d40b27d447971502ed4231f2fa 100644
--- a/include/asterisk/rtp_engine.h
+++ b/include/asterisk/rtp_engine.h
@@ -1540,24 +1540,6 @@ int ast_rtp_instance_fd(struct ast_rtp_instance *instance, int rtcp);
  */
 struct ast_rtp_glue *ast_rtp_instance_get_glue(const char *type);
 
-/*!
- * \brief Bridge two channels that use RTP instances
- *
- * \param c0 First channel part of the bridge
- * \param c1 Second channel part of the bridge
- * \param flags Bridging flags
- * \param fo If a frame needs to be passed up it is stored here
- * \param rc Channel that passed the above frame up
- * \param timeoutms How long the channels should be bridged for
- *
- * \retval Bridge result
- *
- * \note This should only be used by channel drivers in their technology declaration.
- *
- * \since 1.8
- */
-enum ast_bridge_result ast_rtp_instance_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms);
-
 /*!
  * \brief Get the other RTP instance that an instance is bridged to
  *
@@ -1578,6 +1560,16 @@ enum ast_bridge_result ast_rtp_instance_bridge(struct ast_channel *c0, struct as
  */
 struct ast_rtp_instance *ast_rtp_instance_get_bridged(struct ast_rtp_instance *instance);
 
+/*!
+ * \brief Set the other RTP instance that an instance is bridged to
+ *
+ * \param instance The RTP instance that we want to set the bridged value on
+ * \param bridged The RTP instance they are bridged to
+ *
+ * \since 12
+ */
+void ast_rtp_instance_set_bridged(struct ast_rtp_instance *instance, struct ast_rtp_instance *bridged);
+
 /*!
  * \brief Make two channels compatible for early bridging
  *
diff --git a/include/asterisk/stasis_bridging.h b/include/asterisk/stasis_bridging.h
new file mode 100644
index 0000000000000000000000000000000000000000..1b547a7d537af233a78a39dd23110cc0ef79eedb
--- /dev/null
+++ b/include/asterisk/stasis_bridging.h
@@ -0,0 +1,238 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Kinsey Moore <kmoore@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.
+ */
+
+#ifndef _STASIS_BRIDGING_H
+#define _STASIS_BRIDGING_H
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+#include "asterisk/stringfields.h"
+#include "asterisk/utils.h"
+#include "asterisk/lock.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+
+/*!
+ * \brief Structure that contains a snapshot of information about a bridge
+ */
+struct ast_bridge_snapshot {
+	AST_DECLARE_STRING_FIELDS(
+		/*! Immutable bridge UUID. */
+		AST_STRING_FIELD(uniqueid);
+		/*! Bridge technology that is handling the bridge */
+		AST_STRING_FIELD(technology);
+	);
+	/*! AO2 container of bare channel uniqueid strings participating in the bridge.
+	 * Allocated from ast_str_container_alloc() */
+	struct ao2_container *channels;
+	/*! Bridge flags to tweak behavior */
+	struct ast_flags feature_flags;
+	/*! Number of channels participating in the bridge */
+	unsigned int num_channels;
+	/*! Number of active channels in the bridge. */
+	unsigned int num_active;
+};
+
+/*!
+ * \since 12
+ * \brief Generate a snapshot of the bridge state. This is an ao2 object, so
+ * ao2_cleanup() to deallocate.
+ *
+ * \param bridge The bridge from which to generate a snapshot
+ *
+ * \retval AO2 refcounted snapshot on success
+ * \retval NULL on error
+ */
+struct ast_bridge_snapshot *ast_bridge_snapshot_create(struct ast_bridge *bridge);
+
+/*!
+ * \since 12
+ * \brief Message type for \ref ast_bridge_snapshot.
+ *
+ * \retval Message type for \ref ast_bridge_snapshot.
+ */
+struct stasis_message_type *ast_bridge_snapshot_type(void);
+
+/*!
+ * \since 12
+ * \brief A topic which publishes the events for a particular bridge.
+ *
+ * If the given \a bridge is \c NULL, ast_bridge_topic_all() is returned.
+ *
+ * \param bridge Bridge for which to get a topic or \c NULL.
+ *
+ * \retval Topic for bridge's events.
+ * \retval ast_bridge_topic_all() if \a bridge is \c NULL.
+ */
+struct stasis_topic *ast_bridge_topic(struct ast_bridge *bridge);
+
+/*!
+ * \since 12
+ * \brief A topic which publishes the events for all bridges.
+ * \retval Topic for all bridge events.
+ */
+struct stasis_topic *ast_bridge_topic_all(void);
+
+/*!
+ * \since 12
+ * \brief A caching topic which caches \ref ast_bridge_snapshot messages from
+ * ast_bridge_events_all(void).
+ *
+ * \retval Caching topic for all bridge events.
+ */
+struct stasis_caching_topic *ast_bridge_topic_all_cached(void);
+
+/*!
+ * \since 12
+ * \brief Publish the state of a bridge
+ *
+ * \param bridge The bridge for which to publish state
+ */
+void ast_bridge_publish_state(struct ast_bridge *bridge);
+
+/*! \brief Message representing the merge of two bridges */
+struct ast_bridge_merge_message {
+	struct ast_bridge_snapshot *from;	/*!< Bridge from which channels will be removed during the merge */
+	struct ast_bridge_snapshot *to;		/*!< Bridge to which channels will be added during the merge */
+};
+
+/*!
+ * \since 12
+ * \brief Message type for \ref ast_bridge_merge_message.
+ *
+ * \retval Message type for \ref ast_bridge_merge_message.
+ */
+struct stasis_message_type *ast_bridge_merge_message_type(void);
+
+/*!
+ * \since 12
+ * \brief Publish a bridge merge
+ *
+ * \param to The bridge to which channels are being added
+ * \param from The bridge from which channels are being removed
+ */
+void ast_bridge_publish_merge(struct ast_bridge *to, struct ast_bridge *from);
+
+/*!
+ * \since 12
+ * \brief Blob of data associated with a bridge.
+ *
+ * The \c blob is actually a JSON object of structured data. It has a "type" field
+ * which contains the type string describing this blob.
+ */
+struct ast_bridge_blob {
+	/*! Bridge blob is associated with (or NULL for global/all bridges) */
+	struct ast_bridge_snapshot *bridge;
+	/*! Channel blob is associated with (may be NULL for some messages) */
+	struct ast_channel_snapshot *channel;
+	/*! JSON blob of data */
+	struct ast_json *blob;
+};
+
+/*!
+ * \since 12
+ * \brief Message type for \ref channel enter bridge blob messages.
+ *
+ * \retval Message type for \ref channel enter bridge blob messages.
+ */
+struct stasis_message_type *ast_channel_entered_bridge_type(void);
+
+/*!
+ * \since 12
+ * \brief Message type for \ref channel leave bridge blob messages.
+ *
+ * \retval Message type for \ref channel leave bridge blob messages.
+ */
+struct stasis_message_type *ast_channel_left_bridge_type(void);
+
+/*!
+ * \since 12
+ * \brief Creates a \ref ast_bridge_blob message.
+ *
+ * The \a blob JSON object requires a \c "type" field describing the blob. It
+ * should also be treated as immutable and not modified after it is put into the
+ * message.
+ *
+ * \param bridge Channel blob is associated with, or NULL for global/all bridges.
+ * \param blob JSON object representing the data.
+ * \return \ref ast_bridge_blob message.
+ * \return \c NULL on error
+ */
+struct stasis_message *ast_bridge_blob_create(struct stasis_message_type *type,
+	struct ast_bridge *bridge,
+	struct ast_channel *chan,
+	struct ast_json *blob);
+
+/*!
+ * \since 12
+ * \brief Extracts the type field from a \ref ast_bridge_blob.
+ *
+ * Returned \c char* is still owned by \a obj
+ *
+ * \param obj Channel blob object.
+ *
+ * \retval Type field value from the blob.
+ * \retval \c NULL on error.
+ */
+const char *ast_bridge_blob_json_type(struct ast_bridge_blob *obj);
+
+/*!
+ * \since 12
+ * \brief Publish a bridge channel enter event
+ *
+ * \param bridge The bridge a channel entered
+ * \param chan The channel that entered the bridge
+ */
+void ast_bridge_publish_enter(struct ast_bridge *bridge, struct ast_channel *chan);
+
+/*!
+ * \since 12
+ * \brief Publish a bridge channel leave event
+ *
+ * \param bridge The bridge a channel left
+ * \param chan The channel that left the bridge
+ */
+void ast_bridge_publish_leave(struct ast_bridge *bridge, struct ast_channel *chan);
+
+/*!
+ * \brief Build a JSON object from a \ref ast_bridge_snapshot.
+ * \return JSON object representing bridge snapshot.
+ * \return \c NULL on error
+ */
+struct ast_json *ast_bridge_snapshot_to_json(const struct ast_bridge_snapshot *snapshot);
+
+/*!
+ * \brief Dispose of the stasis bridging topics and message types
+ */
+void ast_stasis_bridging_shutdown(void);
+
+/*!
+ * \brief Initialize the stasis bridging topic and message types
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int ast_stasis_bridging_init(void);
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif	/* _STASIS_BRIDGING_H */
diff --git a/main/abstract_jb.c b/main/abstract_jb.c
index 88a9b8e9171dd4e443ca7af292a2eec37ca63d0c..6e20b86cb93271ad0b5a1a60bdb27dcd13ba4eb6 100644
--- a/main/abstract_jb.c
+++ b/main/abstract_jb.c
@@ -41,6 +41,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/channel.h"
 #include "asterisk/term.h"
 #include "asterisk/utils.h"
+#include "asterisk/pbx.h"
+#include "asterisk/timing.h"
 
 #include "asterisk/abstract_jb.h"
 #include "fixedjitterbuf.h"
@@ -567,6 +569,13 @@ int ast_jb_read_conf(struct ast_jb_conf *conf, const char *varname, const char *
 	return 0;
 }
 
+void ast_jb_enable_for_channel(struct ast_channel *chan)
+{
+	struct ast_jb_conf conf = ast_channel_jb(chan)->conf;
+	if (ast_test_flag(&conf, AST_JB_ENABLED)) {
+		ast_jb_create_framehook(chan, &conf, 1);
+	}
+}
 
 void ast_jb_configure(struct ast_channel *chan, const struct ast_jb_conf *conf)
 {
@@ -800,3 +809,303 @@ const struct ast_jb_impl *ast_jb_get_impl(enum ast_jb_type type)
 	}
 	return NULL;
 }
+
+#define DEFAULT_TIMER_INTERVAL 20
+#define DEFAULT_SIZE  200
+#define DEFAULT_TARGET_EXTRA  40
+#define DEFAULT_RESYNC  1000
+#define DEFAULT_TYPE AST_JB_FIXED
+
+struct jb_framedata {
+	const struct ast_jb_impl *jb_impl;
+	struct ast_jb_conf jb_conf;
+	struct timeval start_tv;
+	struct ast_format last_format;
+	struct ast_timer *timer;
+	int timer_interval; /* ms between deliveries */
+	int timer_fd;
+	int first;
+	void *jb_obj;
+};
+
+static void jb_framedata_destroy(struct jb_framedata *framedata)
+{
+	if (framedata->timer) {
+		ast_timer_close(framedata->timer);
+		framedata->timer = NULL;
+	}
+	if (framedata->jb_impl && framedata->jb_obj) {
+		struct ast_frame *f;
+		while (framedata->jb_impl->remove(framedata->jb_obj, &f) == AST_JB_IMPL_OK) {
+			ast_frfree(f);
+		}
+		framedata->jb_impl->destroy(framedata->jb_obj);
+		framedata->jb_obj = NULL;
+	}
+	ast_free(framedata);
+}
+
+void ast_jb_conf_default(struct ast_jb_conf *conf)
+{
+	conf->max_size = DEFAULT_SIZE;
+	conf->resync_threshold = DEFAULT_RESYNC;
+	ast_copy_string(conf->impl, "fixed", sizeof(conf->impl));
+	conf->target_extra = DEFAULT_TARGET_EXTRA;
+}
+
+static void datastore_destroy_cb(void *data) {
+	ast_free(data);
+	ast_debug(1, "JITTERBUFFER datastore destroyed\n");
+}
+
+static const struct ast_datastore_info jb_datastore = {
+	.type = "jitterbuffer",
+	.destroy = datastore_destroy_cb
+};
+
+static void hook_destroy_cb(void *framedata)
+{
+	ast_debug(1, "JITTERBUFFER hook destroyed\n");
+	jb_framedata_destroy((struct jb_framedata *) framedata);
+}
+
+static struct ast_frame *hook_event_cb(struct ast_channel *chan, struct ast_frame *frame, enum ast_framehook_event event, void *data)
+{
+	struct jb_framedata *framedata = data;
+	struct timeval now_tv;
+	unsigned long now;
+	int putframe = 0; /* signifies if audio frame was placed into the buffer or not */
+
+	switch (event) {
+	case AST_FRAMEHOOK_EVENT_READ:
+		break;
+	case AST_FRAMEHOOK_EVENT_ATTACHED:
+	case AST_FRAMEHOOK_EVENT_DETACHED:
+	case AST_FRAMEHOOK_EVENT_WRITE:
+		return frame;
+	}
+
+	if (ast_channel_fdno(chan) == AST_JITTERBUFFER_FD && framedata->timer) {
+		if (ast_timer_ack(framedata->timer, 1) < 0) {
+			ast_log(LOG_ERROR, "Failed to acknowledge timer in jitter buffer\n");
+			return frame;
+		}
+	}
+
+	if (!frame) {
+		return frame;
+	}
+
+	now_tv = ast_tvnow();
+	now = ast_tvdiff_ms(now_tv, framedata->start_tv);
+
+	if (frame->frametype == AST_FRAME_VOICE) {
+		int res;
+		struct ast_frame *jbframe;
+
+		if (!ast_test_flag(frame, AST_FRFLAG_HAS_TIMING_INFO) || frame->len < 2 || frame->ts < 0) {
+			/* only frames with timing info can enter the jitterbuffer */
+			return frame;
+		}
+
+		jbframe = ast_frisolate(frame);
+		ast_format_copy(&framedata->last_format, &frame->subclass.format);
+
+		if (frame->len && (frame->len != framedata->timer_interval)) {
+			framedata->timer_interval = frame->len;
+			ast_timer_set_rate(framedata->timer, 1000 / framedata->timer_interval);
+		}
+		if (!framedata->first) {
+			framedata->first = 1;
+			res = framedata->jb_impl->put_first(framedata->jb_obj, jbframe, now);
+		} else {
+			res = framedata->jb_impl->put(framedata->jb_obj, jbframe, now);
+		}
+		if (res == AST_JB_IMPL_OK) {
+			frame = &ast_null_frame;
+		}
+		putframe = 1;
+	}
+
+	if (frame->frametype == AST_FRAME_NULL) {
+		int res;
+		long next = framedata->jb_impl->next(framedata->jb_obj);
+
+		/* If now is earlier than the next expected output frame
+		 * from the jitterbuffer we may choose to pass on retrieving
+		 * a frame during this read iteration.  The only exception
+		 * to this rule is when an audio frame is placed into the buffer
+		 * and the time for the next frame to come out of the buffer is
+		 * at least within the timer_interval of the next output frame. By
+		 * doing this we are able to feed off the timing of the input frames
+		 * and only rely on our jitterbuffer timer when frames are dropped.
+		 * During testing, this hybrid form of timing gave more reliable results. */
+		if (now < next) {
+			long int diff = next - now;
+			if (!putframe) {
+				return frame;
+			} else if (diff >= framedata->timer_interval) {
+				return frame;
+			}
+		}
+
+		res = framedata->jb_impl->get(framedata->jb_obj, &frame, now, framedata->timer_interval);
+		switch (res) {
+		case AST_JB_IMPL_OK:
+			/* got it, and pass it through */
+			break;
+		case AST_JB_IMPL_DROP:
+			ast_frfree(frame);
+			frame = &ast_null_frame;
+			break;
+		case AST_JB_IMPL_INTERP:
+			if (framedata->last_format.id) {
+				struct ast_frame tmp = { 0, };
+				tmp.frametype = AST_FRAME_VOICE;
+				ast_format_copy(&tmp.subclass.format, &framedata->last_format);
+				/* example: 8000hz / (1000 / 20ms) = 160 samples */
+				tmp.samples = ast_format_rate(&framedata->last_format) / (1000 / framedata->timer_interval);
+				tmp.delivery = ast_tvadd(framedata->start_tv, ast_samp2tv(next, 1000));
+				tmp.offset = AST_FRIENDLY_OFFSET;
+				tmp.src  = "func_jitterbuffer interpolation";
+				frame = ast_frdup(&tmp);
+				break;
+			}
+			/* else fall through */
+		case AST_JB_IMPL_NOFRAME:
+			frame = &ast_null_frame;
+			break;
+		}
+	}
+
+	if (frame->frametype == AST_FRAME_CONTROL) {
+		switch(frame->subclass.integer) {
+		case AST_CONTROL_SRCUPDATE:
+		case AST_CONTROL_SRCCHANGE:
+			framedata->jb_impl->force_resync(framedata->jb_obj);
+			break;
+		default:
+			break;
+		}
+	}
+
+	return frame;
+}
+
+/* set defaults */
+static int jb_framedata_init(struct jb_framedata *framedata, struct ast_jb_conf *jb_conf)
+{
+	int jb_impl_type = DEFAULT_TYPE;
+	/* Initialize defaults */
+	framedata->timer_fd = -1;
+	memcpy(&framedata->jb_conf, jb_conf, sizeof(*jb_conf));
+
+	/* Figure out implementation type from the configuration implementation string */
+	if (!ast_strlen_zero(jb_conf->impl)) {
+		if (!strcasecmp(jb_conf->impl, "fixed")) {
+			jb_impl_type = AST_JB_FIXED;
+		} else if (!strcasecmp(jb_conf->impl, "adaptive")) {
+			jb_impl_type = AST_JB_ADAPTIVE;
+		} else {
+			ast_log(LOG_WARNING, "Unknown Jitterbuffer type %s. Failed to create jitterbuffer.\n", jb_conf->impl);
+			return -1;
+		}
+	}
+
+	if (!(framedata->jb_impl = ast_jb_get_impl(jb_impl_type))) {
+		return -1;
+	}
+
+	if (!(framedata->timer = ast_timer_open())) {
+		return -1;
+	}
+
+	framedata->timer_fd = ast_timer_fd(framedata->timer);
+	framedata->timer_interval = DEFAULT_TIMER_INTERVAL;
+	ast_timer_set_rate(framedata->timer, 1000 / framedata->timer_interval);
+	framedata->start_tv = ast_tvnow();
+
+	framedata->jb_obj = framedata->jb_impl->create(&framedata->jb_conf);
+	return 0;
+}
+
+
+void ast_jb_create_framehook(struct ast_channel *chan, struct ast_jb_conf *jb_conf, int prefer_existing)
+{
+	struct jb_framedata *framedata;
+	struct ast_datastore *datastore = NULL;
+	struct ast_framehook_interface interface = {
+		.version = AST_FRAMEHOOK_INTERFACE_VERSION,
+		.event_cb = hook_event_cb,
+		.destroy_cb = hook_destroy_cb,
+	};
+	int i = 0;
+
+	/* If disabled, strip any existing jitterbuffer and don't replace it. */
+	if (!strcasecmp(jb_conf->impl, "disabled")) {
+		int *id;
+		ast_channel_lock(chan);
+		if ((datastore = ast_channel_datastore_find(chan, &jb_datastore, NULL))) {
+			id = datastore->data;
+			ast_framehook_detach(chan, *id);
+			ast_channel_datastore_remove(chan, datastore);
+		}
+		ast_channel_unlock(chan);
+		return;
+	}
+
+	if (!(framedata = ast_calloc(1, sizeof(*framedata)))) {
+		return;
+	}
+
+	if (jb_framedata_init(framedata, jb_conf)) {
+		jb_framedata_destroy(framedata);
+		return;
+	}
+
+	interface.data = framedata;
+
+	ast_channel_lock(chan);
+	i = ast_framehook_attach(chan, &interface);
+	if (i >= 0) {
+		int *id;
+		if ((datastore = ast_channel_datastore_find(chan, &jb_datastore, NULL))) {
+			/* There is already a jitterbuffer on the channel. */
+			if (prefer_existing) {
+				/* We prefer the existing jitterbuffer, so remove the new one and keep the old one. */
+				ast_framehook_detach(chan, i);
+				ast_channel_unlock(chan);
+				return;
+			}
+			/* We prefer the new jitterbuffer, so strip the old one. */
+			id = datastore->data;
+			ast_framehook_detach(chan, *id);
+			ast_channel_datastore_remove(chan, datastore);
+		}
+
+		if (!(datastore = ast_datastore_alloc(&jb_datastore, NULL))) {
+			ast_framehook_detach(chan, i);
+			ast_channel_unlock(chan);
+			return;
+		}
+
+		if (!(id = ast_calloc(1, sizeof(int)))) {
+			ast_datastore_free(datastore);
+			ast_framehook_detach(chan, i);
+			ast_channel_unlock(chan);
+			return;
+		}
+
+		*id = i; /* Store off the id. The channel is still locked so it is safe to access this ptr. */
+		datastore->data = id;
+		ast_channel_datastore_add(chan, datastore);
+
+		ast_channel_set_fd(chan, AST_JITTERBUFFER_FD, framedata->timer_fd);
+	} else {
+		jb_framedata_destroy(framedata);
+		framedata = NULL;
+	}
+	ast_channel_unlock(chan);
+
+	return;
+}
diff --git a/main/asterisk.c b/main/asterisk.c
index 933aae63d3cd8e35338a4ff6d708d7cbad68683a..d8062d3b1d2df22cd30042c04dbe8fbef867d702 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -4277,11 +4277,6 @@ int main(int argc, char *argv[])
 
 	ast_http_init();		/* Start the HTTP server, if needed */
 
-	if (init_manager()) {
-		printf("%s", term_quit());
-		exit(1);
-	}
-
 	if (ast_cdr_engine_init()) {
 		printf("%s", term_quit());
 		exit(1);
@@ -4330,6 +4325,16 @@ int main(int argc, char *argv[])
 		exit(1);
 	}
 
+	if (ast_bridging_init()) {
+		printf("%s", term_quit());
+		exit(1);
+	}
+
+	if (init_manager()) {
+		printf("%s", term_quit());
+		exit(1);
+	}
+
 	if (ast_enum_init()) {
 		printf("%s", term_quit());
 		exit(1);
@@ -4340,6 +4345,11 @@ int main(int argc, char *argv[])
 		exit(1);
 	}
 
+	if (ast_local_init()) {
+		printf("%s", term_quit());
+		exit(1);
+	}
+
 	if ((moduleresult = load_modules(0))) {		/* Load modules */
 		printf("%s", term_quit());
 		exit(moduleresult == -2 ? 2 : 1);
diff --git a/main/bridging.c b/main/bridging.c
index 875e8503c396926b3e233f369b58d24bc215c6c5..f332dfab21f5c24ef35c7e45d6e269514b42a738 100644
--- a/main/bridging.c
+++ b/main/bridging.c
@@ -40,12 +40,28 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/lock.h"
 #include "asterisk/linkedlists.h"
 #include "asterisk/bridging.h"
+#include "asterisk/bridging_basic.h"
 #include "asterisk/bridging_technology.h"
+#include "asterisk/stasis_bridging.h"
 #include "asterisk/app.h"
 #include "asterisk/file.h"
 #include "asterisk/module.h"
 #include "asterisk/astobj2.h"
+#include "asterisk/pbx.h"
 #include "asterisk/test.h"
+#include "asterisk/_private.h"
+
+#include "asterisk/heap.h"
+#include "asterisk/say.h"
+#include "asterisk/timing.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/features.h"
+#include "asterisk/cli.h"
+#include "asterisk/parking.h"
+
+/*! All bridges container. */
+static struct ao2_container *bridges;
 
 static AST_RWLIST_HEAD_STATIC(bridge_technologies, ast_bridge_technology);
 
@@ -56,6 +72,8 @@ static AST_RWLIST_HEAD_STATIC(bridge_technologies, ast_bridge_technology);
 #define BRIDGE_ARRAY_GROW 32
 
 static void cleanup_video_mode(struct ast_bridge *bridge);
+static int bridge_make_compatible(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+static void bridge_features_remove_on_pull(struct ast_bridge_features *features);
 
 /*! Default DTMF keys for built in features */
 static char builtin_features_dtmf[AST_BRIDGE_BUILTIN_END][MAXIMUM_DTMF_FEATURE_STRING];
@@ -63,13 +81,76 @@ static char builtin_features_dtmf[AST_BRIDGE_BUILTIN_END][MAXIMUM_DTMF_FEATURE_S
 /*! Function handlers for the built in features */
 static void *builtin_features_handlers[AST_BRIDGE_BUILTIN_END];
 
+/*! Function handlers for built in interval features */
+static ast_bridge_builtin_set_limits_fn builtin_interval_handlers[AST_BRIDGE_BUILTIN_INTERVAL_END];
+
+/*! Bridge manager service request */
+struct bridge_manager_request {
+	/*! List of bridge service requests. */
+	AST_LIST_ENTRY(bridge_manager_request) node;
+	/*! Refed bridge requesting service. */
+	struct ast_bridge *bridge;
+};
+
+struct bridge_manager_controller {
+	/*! Condition, used to wake up the bridge manager thread. */
+	ast_cond_t cond;
+	/*! Queue of bridge service requests. */
+	AST_LIST_HEAD_NOLOCK(, bridge_manager_request) service_requests;
+	/*! Manager thread */
+	pthread_t thread;
+	/*! TRUE if the manager needs to stop. */
+	unsigned int stop:1;
+};
+
+/*! Bridge manager controller. */
+static struct bridge_manager_controller *bridge_manager;
+
+/*!
+ * \internal
+ * \brief Request service for a bridge from the bridge manager.
+ * \since 12.0.0
+ *
+ * \param bridge Requesting service.
+ *
+ * \return Nothing
+ */
+static void bridge_manager_service_req(struct ast_bridge *bridge)
+{
+	struct bridge_manager_request *request;
+
+	ao2_lock(bridge_manager);
+	if (bridge_manager->stop) {
+		ao2_unlock(bridge_manager);
+		return;
+	}
+
+	/* Create the service request. */
+	request = ast_calloc(1, sizeof(*request));
+	if (!request) {
+		/* Well. This isn't good. */
+		ao2_unlock(bridge_manager);
+		return;
+	}
+	ao2_ref(bridge, +1);
+	request->bridge = bridge;
+
+	/* Put request into the queue and wake the bridge manager. */
+	AST_LIST_INSERT_TAIL(&bridge_manager->service_requests, request, node);
+	ast_cond_signal(&bridge_manager->cond);
+	ao2_unlock(bridge_manager);
+}
+
 int __ast_bridge_technology_register(struct ast_bridge_technology *technology, struct ast_module *module)
 {
-	struct ast_bridge_technology *current = NULL;
+	struct ast_bridge_technology *current;
 
 	/* Perform a sanity check to make sure the bridge technology conforms to our needed requirements */
-	if (ast_strlen_zero(technology->name) || !technology->capabilities || !technology->write) {
-		ast_log(LOG_WARNING, "Bridge technology %s failed registration sanity check.\n", technology->name);
+	if (ast_strlen_zero(technology->name)
+		|| !technology->capabilities
+		|| !technology->write) {
+		ast_log(LOG_WARNING, "Bridge technology %s failed registration sanity check.\n",
+			technology->name);
 		return -1;
 	}
 
@@ -78,7 +159,8 @@ int __ast_bridge_technology_register(struct ast_bridge_technology *technology, s
 	/* Look for duplicate bridge technology already using this name, or already registered */
 	AST_RWLIST_TRAVERSE(&bridge_technologies, current, entry) {
 		if ((!strcasecmp(current->name, technology->name)) || (current == technology)) {
-			ast_log(LOG_WARNING, "A bridge technology of %s already claims to exist in our world.\n", technology->name);
+			ast_log(LOG_WARNING, "A bridge technology of %s already claims to exist in our world.\n",
+				technology->name);
 			AST_RWLIST_UNLOCK(&bridge_technologies);
 			return -1;
 		}
@@ -99,7 +181,7 @@ int __ast_bridge_technology_register(struct ast_bridge_technology *technology, s
 
 int ast_bridge_technology_unregister(struct ast_bridge_technology *technology)
 {
-	struct ast_bridge_technology *current = NULL;
+	struct ast_bridge_technology *current;
 
 	AST_RWLIST_WRLOCK(&bridge_technologies);
 
@@ -118,127 +200,192 @@ int ast_bridge_technology_unregister(struct ast_bridge_technology *technology)
 	return current ? 0 : -1;
 }
 
+void ast_bridge_channel_lock_bridge(struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_bridge *bridge;
+
+	for (;;) {
+		/* Safely get the bridge pointer */
+		ast_bridge_channel_lock(bridge_channel);
+		bridge = bridge_channel->bridge;
+		ao2_ref(bridge, +1);
+		ast_bridge_channel_unlock(bridge_channel);
+
+		/* Lock the bridge and see if it is still the bridge we need to lock. */
+		ast_bridge_lock(bridge);
+		if (bridge == bridge_channel->bridge) {
+			ao2_ref(bridge, -1);
+			return;
+		}
+		ast_bridge_unlock(bridge);
+		ao2_ref(bridge, -1);
+	}
+}
+
 static void bridge_channel_poke(struct ast_bridge_channel *bridge_channel)
 {
-	ao2_lock(bridge_channel);
-	pthread_kill(bridge_channel->thread, SIGURG);
-	ast_cond_signal(&bridge_channel->cond);
-	ao2_unlock(bridge_channel);
+	if (!pthread_equal(pthread_self(), bridge_channel->thread)) {
+		while (bridge_channel->waiting) {
+			pthread_kill(bridge_channel->thread, SIGURG);
+			sched_yield();
+		}
+	}
 }
 
-/*! \note This function assumes the bridge_channel is locked. */
-static void ast_bridge_change_state_nolock(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state)
+void ast_bridge_change_state_nolock(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state)
 {
+/* BUGBUG need cause code for the bridge_channel leaving the bridge. */
+	if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+		return;
+	}
+
+	ast_debug(1, "Setting %p(%s) state from:%d to:%d\n",
+		bridge_channel, ast_channel_name(bridge_channel->chan), bridge_channel->state,
+		new_state);
+
 	/* Change the state on the bridge channel */
 	bridge_channel->state = new_state;
 
-	/* Only poke the channel's thread if it is not us */
-	if (!pthread_equal(pthread_self(), bridge_channel->thread)) {
-		pthread_kill(bridge_channel->thread, SIGURG);
-		ast_cond_signal(&bridge_channel->cond);
-	}
+	bridge_channel_poke(bridge_channel);
 }
 
 void ast_bridge_change_state(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state)
 {
-	ao2_lock(bridge_channel);
+	ast_bridge_channel_lock(bridge_channel);
 	ast_bridge_change_state_nolock(bridge_channel, new_state);
-	ao2_unlock(bridge_channel);
-}
-
-/*!
- * \brief Helper function to poke the bridge thread
- *
- * \note This function assumes the bridge is locked.
- */
-static void bridge_poke(struct ast_bridge *bridge)
-{
-	/* Poke the thread just in case */
-	if (bridge->thread != AST_PTHREADT_NULL && bridge->thread != AST_PTHREADT_STOP) {
-		pthread_kill(bridge->thread, SIGURG);
-	}
+	ast_bridge_channel_unlock(bridge_channel);
 }
 
 /*!
  * \internal
- * \brief Stop the bridge.
+ * \brief Put an action onto the specified bridge. Don't dup the action frame.
  * \since 12.0.0
  *
- * \note This function assumes the bridge is locked.
+ * \param bridge What to queue the action on.
+ * \param action What to do.
  *
  * \return Nothing
  */
-static void bridge_stop(struct ast_bridge *bridge)
+static void bridge_queue_action_nodup(struct ast_bridge *bridge, struct ast_frame *action)
 {
-	pthread_t thread = bridge->thread;
+	ast_debug(1, "Bridge %s: queueing action type:%d sub:%d\n",
+		bridge->uniqueid, action->frametype, action->subclass.integer);
 
-	bridge->stop = 1;
-	bridge_poke(bridge);
-	ao2_unlock(bridge);
-	pthread_join(thread, NULL);
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
+	AST_LIST_INSERT_TAIL(&bridge->action_queue, action, frame_list);
+	ast_bridge_unlock(bridge);
+	bridge_manager_service_req(bridge);
 }
 
-/*!
- * \brief Helper function to add a channel to the bridge array
- *
- * \note This function assumes the bridge is locked.
- */
-static void bridge_array_add(struct ast_bridge *bridge, struct ast_channel *chan)
+int ast_bridge_queue_action(struct ast_bridge *bridge, struct ast_frame *action)
 {
-	/* We have to make sure the bridge thread is not using the bridge array before messing with it */
-	while (bridge->waiting) {
-		bridge_poke(bridge);
-		sched_yield();
+	struct ast_frame *dup;
+
+	dup = ast_frdup(action);
+	if (!dup) {
+		return -1;
 	}
+	bridge_queue_action_nodup(bridge, dup);
+	return 0;
+}
 
-	bridge->array[bridge->array_num++] = chan;
+int ast_bridge_channel_queue_frame(struct ast_bridge_channel *bridge_channel, struct ast_frame *fr)
+{
+	struct ast_frame *dup;
+	char nudge = 0;
+
+	if (bridge_channel->suspended
+		/* Also defer DTMF frames. */
+		&& fr->frametype != AST_FRAME_DTMF_BEGIN
+		&& fr->frametype != AST_FRAME_DTMF_END
+		&& !ast_is_deferrable_frame(fr)) {
+		/* Drop non-deferable frames when suspended. */
+		return 0;
+	}
 
-	ast_debug(1, "Added channel %s(%p) to bridge array on %p, new count is %d\n",
-		ast_channel_name(chan), chan, bridge, (int) bridge->array_num);
+	dup = ast_frdup(fr);
+	if (!dup) {
+		return -1;
+	}
 
-	/* If the next addition of a channel will exceed our array size grow it out */
-	if (bridge->array_num == bridge->array_size) {
-		struct ast_channel **new_array;
+	ast_bridge_channel_lock(bridge_channel);
+	if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+		/* Drop frames on channels leaving the bridge. */
+		ast_bridge_channel_unlock(bridge_channel);
+		ast_frfree(dup);
+		return 0;
+	}
 
-		ast_debug(1, "Growing bridge array on %p from %d to %d\n",
-			bridge, (int) bridge->array_size, (int) bridge->array_size + BRIDGE_ARRAY_GROW);
-		new_array = ast_realloc(bridge->array,
-			(bridge->array_size + BRIDGE_ARRAY_GROW) * sizeof(*bridge->array));
-		if (!new_array) {
-			return;
-		}
-		bridge->array = new_array;
-		bridge->array_size += BRIDGE_ARRAY_GROW;
+	AST_LIST_INSERT_TAIL(&bridge_channel->wr_queue, dup, frame_list);
+	if (write(bridge_channel->alert_pipe[1], &nudge, sizeof(nudge)) != sizeof(nudge)) {
+		ast_log(LOG_ERROR, "We couldn't write alert pipe for %p(%s)... something is VERY wrong\n",
+			bridge_channel, ast_channel_name(bridge_channel->chan));
 	}
+	ast_bridge_channel_unlock(bridge_channel);
+	return 0;
 }
 
-/*! \brief Helper function to remove a channel from the bridge array
- *
- * \note This function assumes the bridge is locked.
- */
-static void bridge_array_remove(struct ast_bridge *bridge, struct ast_channel *chan)
+void ast_bridge_channel_queue_action_data(struct ast_bridge_channel *bridge_channel, enum ast_bridge_action_type action, const void *data, size_t datalen)
 {
-	int idx;
+	struct ast_frame frame = {
+		.frametype = AST_FRAME_BRIDGE_ACTION,
+		.subclass.integer = action,
+		.datalen = datalen,
+		.data.ptr = (void *) data,
+	};
 
-	/* We have to make sure the bridge thread is not using the bridge array before messing with it */
-	while (bridge->waiting) {
-		bridge_poke(bridge);
-		sched_yield();
-	}
+	ast_bridge_channel_queue_frame(bridge_channel, &frame);
+}
 
-	for (idx = 0; idx < bridge->array_num; ++idx) {
-		if (bridge->array[idx] == chan) {
-			--bridge->array_num;
-			bridge->array[idx] = bridge->array[bridge->array_num];
-			ast_debug(1, "Removed channel %p from bridge array on %p, new count is %d\n",
-				chan, bridge, (int) bridge->array_num);
-			break;
+void ast_bridge_channel_queue_control_data(struct ast_bridge_channel *bridge_channel, enum ast_control_frame_type control, const void *data, size_t datalen)
+{
+	struct ast_frame frame = {
+		.frametype = AST_FRAME_CONTROL,
+		.subclass.integer = control,
+		.datalen = datalen,
+		.data.ptr = (void *) data,
+	};
+
+	ast_bridge_channel_queue_frame(bridge_channel, &frame);
+}
+
+void ast_bridge_channel_restore_formats(struct ast_bridge_channel *bridge_channel)
+{
+	/* Restore original formats of the channel as they came in */
+	if (ast_format_cmp(ast_channel_readformat(bridge_channel->chan), &bridge_channel->read_format) == AST_FORMAT_CMP_NOT_EQUAL) {
+		ast_debug(1, "Bridge is returning %p(%s) to read format %s\n",
+			bridge_channel, ast_channel_name(bridge_channel->chan),
+			ast_getformatname(&bridge_channel->read_format));
+		if (ast_set_read_format(bridge_channel->chan, &bridge_channel->read_format)) {
+			ast_debug(1, "Bridge failed to return %p(%s) to read format %s\n",
+				bridge_channel, ast_channel_name(bridge_channel->chan),
+				ast_getformatname(&bridge_channel->read_format));
+		}
+	}
+	if (ast_format_cmp(ast_channel_writeformat(bridge_channel->chan), &bridge_channel->write_format) == AST_FORMAT_CMP_NOT_EQUAL) {
+		ast_debug(1, "Bridge is returning %p(%s) to write format %s\n",
+			bridge_channel, ast_channel_name(bridge_channel->chan),
+			ast_getformatname(&bridge_channel->write_format));
+		if (ast_set_write_format(bridge_channel->chan, &bridge_channel->write_format)) {
+			ast_debug(1, "Bridge failed to return %p(%s) to write format %s\n",
+				bridge_channel, ast_channel_name(bridge_channel->chan),
+				ast_getformatname(&bridge_channel->write_format));
 		}
 	}
 }
 
-/*! \brief Helper function to find a bridge channel given a channel */
+/*!
+ * \internal
+ * \brief Helper function to find a bridge channel given a channel.
+ *
+ * \param bridge What to search
+ * \param chan What to search for.
+ *
+ * \note On entry, bridge is already locked.
+ *
+ * \retval bridge_channel if channel is in the bridge.
+ * \retval NULL if not in bridge.
+ */
 static struct ast_bridge_channel *find_bridge_channel(struct ast_bridge *bridge, struct ast_channel *chan)
 {
 	struct ast_bridge_channel *bridge_channel;
@@ -254,1513 +401,5443 @@ static struct ast_bridge_channel *find_bridge_channel(struct ast_bridge *bridge,
 
 /*!
  * \internal
- * \brief Force out all channels that are not already going out of the bridge.
+ * \brief Dissolve the bridge.
  * \since 12.0.0
  *
  * \param bridge Bridge to eject all channels
  *
+ * \details
+ * Force out all channels that are not already going out of the
+ * bridge.  Any new channels joining will leave immediately.
+ *
  * \note On entry, bridge is already locked.
  *
  * \return Nothing
  */
-static void bridge_force_out_all(struct ast_bridge *bridge)
+static void bridge_dissolve(struct ast_bridge *bridge)
 {
 	struct ast_bridge_channel *bridge_channel;
+	struct ast_frame action = {
+		.frametype = AST_FRAME_BRIDGE_ACTION,
+		.subclass.integer = AST_BRIDGE_ACTION_DEFERRED_DISSOLVING,
+	};
 
-	AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
-		ao2_lock(bridge_channel);
-		switch (bridge_channel->state) {
-		case AST_BRIDGE_CHANNEL_STATE_END:
-		case AST_BRIDGE_CHANNEL_STATE_HANGUP:
-		case AST_BRIDGE_CHANNEL_STATE_DEPART:
-			break;
-		default:
-			ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
-			break;
-		}
-		ao2_unlock(bridge_channel);
+	if (bridge->dissolved) {
+		return;
 	}
-}
+	bridge->dissolved = 1;
 
-/*! \brief Internal function to see whether a bridge should dissolve, and if so do it */
-static void bridge_check_dissolve(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
-	if (!ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE)
-		&& (!bridge_channel->features
-			|| !bridge_channel->features->usable
-			|| !ast_test_flag(&bridge_channel->features->feature_flags, AST_BRIDGE_FLAG_DISSOLVE))) {
-		return;
+	ast_debug(1, "Bridge %s: dissolving bridge\n", bridge->uniqueid);
+
+/* BUGBUG need a cause code on the bridge for the later ejected channels. */
+	AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
 	}
 
-	ast_debug(1, "Dissolving bridge %p\n", bridge);
-	bridge_force_out_all(bridge);
+	/* Must defer dissolving bridge because it is already locked. */
+	ast_bridge_queue_action(bridge, &action);
 }
 
-/*! \brief Internal function to handle DTMF from a channel */
-static struct ast_frame *bridge_handle_dtmf(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+/*!
+ * \internal
+ * \brief Check if a bridge should dissolve and do it.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel causing the check.
+ *
+ * \note On entry, bridge_channel->bridge is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_dissolve_check(struct ast_bridge_channel *bridge_channel)
 {
-	struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features);
-	struct ast_bridge_features_hook *hook;
+	struct ast_bridge *bridge = bridge_channel->bridge;
 
-	/* If the features structure we grabbed is not usable immediately return the frame */
-	if (!features->usable) {
-		return frame;
+	if (bridge->dissolved) {
+		return;
 	}
 
-	/* See if this DTMF matches the beginnings of any feature hooks, if so we switch to the feature state to either execute the feature or collect more DTMF */
-	AST_LIST_TRAVERSE(&features->hooks, hook, entry) {
-		if (hook->dtmf[0] == frame->subclass.integer) {
-			ast_frfree(frame);
-			frame = NULL;
-			ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_FEATURE);
-			break;
-		}
+	if (!bridge->num_channels
+		&& ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_EMPTY)) {
+		/* Last channel leaving the bridge turns off the lights. */
+		bridge_dissolve(bridge);
+		return;
 	}
 
-	return frame;
-}
-
-/*! \brief Internal function used to determine whether a control frame should be dropped or not */
-static int bridge_drop_control_frame(int subclass)
-{
-	switch (subclass) {
-	case AST_CONTROL_ANSWER:
-	case -1:
-		return 1;
+	switch (bridge_channel->state) {
+	case AST_BRIDGE_CHANNEL_STATE_END:
+		/* Do we need to dissolve the bridge because this channel hung up? */
+		if (ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_HANGUP)
+			|| (bridge_channel->features->usable
+				&& ast_test_flag(&bridge_channel->features->feature_flags,
+					AST_BRIDGE_CHANNEL_FLAG_DISSOLVE_HANGUP))) {
+			bridge_dissolve(bridge);
+			return;
+		}
+		break;
 	default:
-		return 0;
+		break;
 	}
+/* BUGBUG need to implement AST_BRIDGE_CHANNEL_FLAG_LONELY support here */
 }
 
-void ast_bridge_notify_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int started_talking)
+/*!
+ * \internal
+ * \brief Pull the bridge channel out of its current bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to pull.
+ *
+ * \note On entry, bridge_channel->bridge is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_pull(struct ast_bridge_channel *bridge_channel)
 {
-	if (started_talking) {
-		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_START_TALKING);
-	} else {
-		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_STOP_TALKING);
-	}
-}
+	struct ast_bridge *bridge = bridge_channel->bridge;
 
-void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_channel *chan, int outfd)
-{
-	/* If no bridge channel has been provided and the actual channel has been provided find it */
-	if (chan && !bridge_channel) {
-		bridge_channel = find_bridge_channel(bridge, chan);
+	if (!bridge_channel->in_bridge) {
+		return;
 	}
-
-	/* If a bridge channel with actual channel is present read a frame and handle it */
-	if (chan && bridge_channel) {
-		struct ast_frame *frame;
-
-		if (bridge->features.mute
-			|| (bridge_channel->features && bridge_channel->features->mute)) {
-			frame = ast_read_noaudio(chan);
-		} else {
-			frame = ast_read(chan);
-		}
-		/* This is pretty simple... see if they hung up */
-		if (!frame || (frame->frametype == AST_FRAME_CONTROL && frame->subclass.integer == AST_CONTROL_HANGUP)) {
-			/* Signal the thread that is handling the bridged channel that it should be ended */
-			ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
-		} else if (frame->frametype == AST_FRAME_CONTROL && bridge_drop_control_frame(frame->subclass.integer)) {
-			ast_debug(1, "Dropping control frame %d from bridge channel %p\n",
-				frame->subclass.integer, bridge_channel);
-		} else if (frame->frametype == AST_FRAME_DTMF_BEGIN || frame->frametype == AST_FRAME_DTMF_END) {
-			int dtmf_passthrough = bridge_channel->features ?
-				bridge_channel->features->dtmf_passthrough :
-				bridge->features.dtmf_passthrough;
-
-			if (frame->frametype == AST_FRAME_DTMF_BEGIN) {
-				frame = bridge_handle_dtmf(bridge, bridge_channel, frame);
-			}
-
-			if (frame && dtmf_passthrough) {
-				bridge->technology->write(bridge, bridge_channel, frame);
-			}
-		} else {
-			/* Simply write the frame out to the bridge technology if it still exists */
-			bridge->technology->write(bridge, bridge_channel, frame);
-		}
-
-		if (frame) {
-			ast_frfree(frame);
+	bridge_channel->in_bridge = 0;
+
+	ast_debug(1, "Bridge %s: pulling %p(%s)\n",
+		bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan));
+
+/* BUGBUG This is where incoming HOLD/UNHOLD memory should write UNHOLD into bridge. (if not local optimizing) */
+/* BUGBUG This is where incoming DTMF begin/end memory should write DTMF end into bridge. (if not local optimizing) */
+	if (!bridge_channel->just_joined) {
+		/* Tell the bridge technology we are leaving so they tear us down */
+		ast_debug(1, "Bridge %s: %p(%s) is leaving %s technology\n",
+			bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+			bridge->technology->name);
+		if (bridge->technology->leave) {
+			bridge->technology->leave(bridge, bridge_channel);
 		}
-		return;
 	}
 
-	/* If a file descriptor actually tripped pass it off to the bridge technology */
-	if (outfd > -1 && bridge->technology->fd) {
-		bridge->technology->fd(bridge, bridge_channel, outfd);
-		return;
+	/* Remove channel from the bridge */
+	if (!bridge_channel->suspended) {
+		--bridge->num_active;
 	}
+	--bridge->num_channels;
+	AST_LIST_REMOVE(&bridge->channels, bridge_channel, entry);
+	bridge->v_table->pull(bridge, bridge_channel);
 
-	/* If all else fails just poke the bridge */
-	if (bridge->technology->poke && bridge_channel) {
-		bridge->technology->poke(bridge, bridge_channel);
-		return;
-	}
+	ast_bridge_channel_clear_roles(bridge_channel);
+
+	bridge_dissolve_check(bridge_channel);
+
+	bridge->reconfigured = 1;
+	ast_bridge_publish_leave(bridge, bridge_channel->chan);
 }
 
-/*! \brief Generic thread loop, TODO: Rethink this/improve it */
-static int generic_thread_loop(struct ast_bridge *bridge)
+/*!
+ * \internal
+ * \brief Push the bridge channel into its specified bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to push.
+ *
+ * \note On entry, bridge_channel->bridge is already locked.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.  The channel did not get pushed.
+ */
+static int bridge_channel_push(struct ast_bridge_channel *bridge_channel)
 {
-	while (!bridge->stop && !bridge->refresh && bridge->array_num) {
-		struct ast_channel *winner;
-		int to = -1;
+	struct ast_bridge *bridge = bridge_channel->bridge;
+	struct ast_bridge_channel *swap;
 
-		/* Move channels around for priority reasons if we have more than one channel in our array */
-		if (bridge->array_num > 1) {
-			struct ast_channel *first = bridge->array[0];
-			memmove(bridge->array, bridge->array + 1, sizeof(struct ast_channel *) * (bridge->array_num - 1));
-			bridge->array[(bridge->array_num - 1)] = first;
-		}
+	ast_assert(!bridge_channel->in_bridge);
+
+	swap = find_bridge_channel(bridge, bridge_channel->swap);
+	bridge_channel->swap = NULL;
 
-		/* Wait on the channels */
-		bridge->waiting = 1;
-		ao2_unlock(bridge);
-		winner = ast_waitfor_n(bridge->array, (int) bridge->array_num, &to);
-		bridge->waiting = 0;
-		ao2_lock(bridge);
+	if (swap) {
+		ast_debug(1, "Bridge %s: pushing %p(%s) by swapping with %p(%s)\n",
+			bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+			swap, ast_channel_name(swap->chan));
+	} else {
+		ast_debug(1, "Bridge %s: pushing %p(%s)\n",
+			bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan));
+	}
 
-		/* Process whatever they did */
-		ast_bridge_handle_trip(bridge, NULL, winner, -1);
+	/* Add channel to the bridge */
+	if (bridge->dissolved
+		|| bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT
+		|| (swap && swap->state != AST_BRIDGE_CHANNEL_STATE_WAIT)
+		|| bridge->v_table->push(bridge, bridge_channel, swap)
+		|| ast_bridge_channel_establish_roles(bridge_channel)) {
+		ast_debug(1, "Bridge %s: pushing %p(%s) into bridge failed\n",
+			bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan));
+		return -1;
+	}
+	bridge_channel->in_bridge = 1;
+	bridge_channel->just_joined = 1;
+	AST_LIST_INSERT_TAIL(&bridge->channels, bridge_channel, entry);
+	++bridge->num_channels;
+	if (!bridge_channel->suspended) {
+		++bridge->num_active;
+	}
+	if (swap) {
+		ast_bridge_change_state(swap, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+		bridge_channel_pull(swap);
 	}
 
+	bridge->reconfigured = 1;
+	ast_bridge_publish_enter(bridge, bridge_channel->chan);
 	return 0;
 }
 
-/*! \brief Bridge thread function */
-static void *bridge_thread(void *data)
+/*! \brief Internal function to handle DTMF from a channel */
+static struct ast_frame *bridge_handle_dtmf(struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
 {
-	struct ast_bridge *bridge = data;
-	int res = 0;
+	struct ast_bridge_features *features = bridge_channel->features;
+	struct ast_bridge_hook *hook;
+	char dtmf[2];
 
-	ao2_lock(bridge);
-
-	if (bridge->callid) {
-		ast_callid_threadassoc_add(bridge->callid);
+/* BUGBUG the feature hook matching needs to be done here.  Any matching feature hook needs to be queued onto the bridge_channel.  Also the feature hook digit timeout needs to be handled. */
+/* BUGBUG the AMI atxfer action just sends DTMF end events to initiate DTMF atxfer and dial the extension.  Another reason the DTMF hook matching needs rework. */
+	/* See if this DTMF matches the beginnings of any feature hooks, if so we switch to the feature state to either execute the feature or collect more DTMF */
+	dtmf[0] = frame->subclass.integer;
+	dtmf[1] = '\0';
+	hook = ao2_find(features->dtmf_hooks, dtmf, OBJ_PARTIAL_KEY);
+	if (hook) {
+		struct ast_frame action = {
+			.frametype = AST_FRAME_BRIDGE_ACTION,
+			.subclass.integer = AST_BRIDGE_ACTION_FEATURE,
+		};
+
+		ast_frfree(frame);
+		frame = NULL;
+		ast_bridge_channel_queue_frame(bridge_channel, &action);
+		ao2_ref(hook, -1);
 	}
 
-	ast_debug(1, "Started bridge thread for %p\n", bridge);
-
-	/* Loop around until we are told to stop */
-	while (!bridge->stop && bridge->array_num && !res) {
-		/* In case the refresh bit was set simply set it back to off */
-		bridge->refresh = 0;
-
-		ast_debug(1, "Launching bridge thread function %p for bridge %p\n",
-			bridge->technology->thread ? bridge->technology->thread : generic_thread_loop,
-			bridge);
+	return frame;
+}
 
-		/*
-		 * Execute the appropriate thread function.  If the technology
-		 * does not provide one we use the generic one.
-		 */
-		res = bridge->technology->thread
-			? bridge->technology->thread(bridge)
-			: generic_thread_loop(bridge);
+/*!
+ * \internal
+ * \brief Handle bridge hangup event.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is hanging up.
+ *
+ * \return Nothing
+ */
+static void bridge_handle_hangup(struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_bridge_features *features = bridge_channel->features;
+	struct ast_bridge_hook *hook;
+	struct ao2_iterator iter;
+
+	/* Run any hangup hooks. */
+	iter = ao2_iterator_init(features->hangup_hooks, 0);
+	for (; (hook = ao2_iterator_next(&iter)); ao2_ref(hook, -1)) {
+		int failed;
+
+		failed = hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+		if (failed) {
+			ast_debug(1, "Hangup hook %p is being removed from %p(%s)\n",
+				hook, bridge_channel, ast_channel_name(bridge_channel->chan));
+			ao2_unlink(features->hangup_hooks, hook);
+		}
 	}
+	ao2_iterator_destroy(&iter);
 
-	ast_debug(1, "Ending bridge thread for %p\n", bridge);
+	/* Default hangup action. */
+	ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+}
 
-	/* Indicate the bridge thread is no longer active */
-	bridge->thread = AST_PTHREADT_NULL;
-	ao2_unlock(bridge);
+static int bridge_channel_interval_ready(struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_bridge_features *features = bridge_channel->features;
+	struct ast_bridge_hook *hook;
+	int ready;
 
-	ao2_ref(bridge, -1);
+	ast_heap_wrlock(features->interval_hooks);
+	hook = ast_heap_peek(features->interval_hooks, 1);
+	ready = hook && ast_tvdiff_ms(hook->parms.timer.trip_time, ast_tvnow()) <= 0;
+	ast_heap_unlock(features->interval_hooks);
 
-	return NULL;
+	return ready;
 }
 
-/*! \brief Helper function used to find the "best" bridge technology given a specified capabilities */
-static struct ast_bridge_technology *find_best_technology(uint32_t capabilities)
+void ast_bridge_notify_talking(struct ast_bridge_channel *bridge_channel, int started_talking)
 {
-	struct ast_bridge_technology *current = NULL, *best = NULL;
+	struct ast_frame action = {
+		.frametype = AST_FRAME_BRIDGE_ACTION,
+		.subclass.integer = started_talking
+			? AST_BRIDGE_ACTION_TALKING_START : AST_BRIDGE_ACTION_TALKING_STOP,
+	};
 
-	AST_RWLIST_RDLOCK(&bridge_technologies);
-	AST_RWLIST_TRAVERSE(&bridge_technologies, current, entry) {
-		if (current->suspended) {
-			ast_debug(1, "Bridge technology %s is suspended. Skipping.\n", current->name);
-			continue;
-		}
-		if (!(current->capabilities & capabilities)) {
-			ast_debug(1, "Bridge technology %s does not have the capabilities we need.\n", current->name);
-			continue;
-		}
-		if (best && best->preference < current->preference) {
-			ast_debug(1, "Bridge technology %s has preference %d while %s has preference %d. Skipping.\n", current->name, current->preference, best->name, best->preference);
-			continue;
-		}
-		best = current;
-	}
+	ast_bridge_channel_queue_frame(bridge_channel, &action);
+}
 
-	if (best) {
-		/* Increment it's module reference count if present so it does not get unloaded while in use */
-		ast_module_ref(best->mod);
-		ast_debug(1, "Chose bridge technology %s\n", best->name);
-	}
+static void bridge_channel_write_frame(struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+{
+	ast_bridge_channel_lock_bridge(bridge_channel);
+/*
+ * BUGBUG need to implement a deferred write queue for when there is no peer channel in the bridge (yet or it was kicked).
+ *
+ * The tech decides if a frame needs to be pushed back for deferral.
+ * simple_bridge/native_bridge are likely the only techs that will do this.
+ */
+	bridge_channel->bridge->technology->write(bridge_channel->bridge, bridge_channel, frame);
+	ast_bridge_unlock(bridge_channel->bridge);
+}
 
-	AST_RWLIST_UNLOCK(&bridge_technologies);
+void ast_bridge_channel_write_action_data(struct ast_bridge_channel *bridge_channel, enum ast_bridge_action_type action, const void *data, size_t datalen)
+{
+	struct ast_frame frame = {
+		.frametype = AST_FRAME_BRIDGE_ACTION,
+		.subclass.integer = action,
+		.datalen = datalen,
+		.data.ptr = (void *) data,
+	};
 
-	return best;
+	bridge_channel_write_frame(bridge_channel, &frame);
 }
 
-static void destroy_bridge(void *obj)
+void ast_bridge_channel_write_control_data(struct ast_bridge_channel *bridge_channel, enum ast_control_frame_type control, const void *data, size_t datalen)
 {
-	struct ast_bridge *bridge = obj;
+	struct ast_frame frame = {
+		.frametype = AST_FRAME_CONTROL,
+		.subclass.integer = control,
+		.datalen = datalen,
+		.data.ptr = (void *) data,
+	};
 
-	ast_debug(1, "Actually destroying bridge %p, nobody wants it anymore\n", bridge);
+	bridge_channel_write_frame(bridge_channel, &frame);
+}
 
-	/* There should not be any channels left in the bridge. */
-	ast_assert(AST_LIST_EMPTY(&bridge->channels));
+static int run_app_helper(struct ast_channel *chan, const char *app_name, const char *app_args)
+{
+	int res = 0;
 
-	/* Pass off the bridge to the technology to destroy if needed */
-	if (bridge->technology->destroy) {
-		ast_debug(1, "Giving bridge technology %s the bridge structure %p to destroy\n", bridge->technology->name, bridge);
-		if (bridge->technology->destroy(bridge)) {
-			ast_debug(1, "Bridge technology %s failed to destroy bridge structure %p... some memory may have leaked\n",
-				bridge->technology->name, bridge);
+	if (!strcasecmp("Gosub", app_name)) {
+		ast_app_exec_sub(NULL, chan, app_args, 0);
+	} else if (!strcasecmp("Macro", app_name)) {
+		ast_app_exec_macro(NULL, chan, app_args);
+	} else {
+		struct ast_app *app;
+
+		app = pbx_findapp(app_name);
+		if (!app) {
+			ast_log(LOG_WARNING, "Could not find application (%s)\n", app_name);
+		} else {
+			res = pbx_exec(chan, app, app_args);
 		}
 	}
-
-	cleanup_video_mode(bridge);
-
-	/* Clean up the features configuration */
-	ast_bridge_features_cleanup(&bridge->features);
-
-	/* We are no longer using the bridge technology so decrement the module reference count on it */
-	ast_module_unref(bridge->technology->mod);
-
-	/* Drop the array of channels */
-	ast_free(bridge->array);
+	return res;
 }
 
-struct ast_bridge *ast_bridge_new(uint32_t capabilities, int flags)
+void ast_bridge_channel_run_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
 {
-	struct ast_bridge *bridge;
-	struct ast_bridge_technology *bridge_technology;
-
-	/* If we need to be a smart bridge see if we can move between 1to1 and multimix bridges */
-	if (flags & AST_BRIDGE_FLAG_SMART) {
-		if (!ast_bridge_check((capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX)
-			? AST_BRIDGE_CAPABILITY_MULTIMIX : AST_BRIDGE_CAPABILITY_1TO1MIX)) {
-			return NULL;
+	if (moh_class) {
+		if (ast_strlen_zero(moh_class)) {
+			ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
+				NULL, 0);
+		} else {
+			ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
+				moh_class, strlen(moh_class) + 1);
 		}
 	}
+	if (run_app_helper(bridge_channel->chan, app_name, S_OR(app_args, ""))) {
+		/* Break the bridge if the app returns non-zero. */
+		bridge_handle_hangup(bridge_channel);
+	}
+	if (moh_class) {
+		ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_UNHOLD,
+			NULL, 0);
+	}
+}
 
-	/*
-	 * If capabilities were provided use our helper function to find
-	 * the "best" bridge technology, otherwise we can just look for
-	 * the most basic capability needed, single 1to1 mixing.
-	 */
-	bridge_technology = capabilities
-		? find_best_technology(capabilities)
-		: find_best_technology(AST_BRIDGE_CAPABILITY_1TO1MIX);
+struct bridge_run_app {
+	/*! Offset into app_name[] where the MOH class name starts.  (zero if no MOH) */
+	int moh_offset;
+	/*! Offset into app_name[] where the application argument string starts. (zero if no arguments) */
+	int app_args_offset;
+	/*! Application name to run. */
+	char app_name[0];
+};
 
-	/* If no bridge technology was found we can't possibly do bridging so fail creation of the bridge */
-	if (!bridge_technology) {
-		return NULL;
-	}
+/*!
+ * \internal
+ * \brief Handle the run application bridge action.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel to run the application on.
+ * \param data Action frame data to run the application.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_run_app(struct ast_bridge_channel *bridge_channel, struct bridge_run_app *data)
+{
+	ast_bridge_channel_run_app(bridge_channel, data->app_name,
+		data->app_args_offset ? &data->app_name[data->app_args_offset] : NULL,
+		data->moh_offset ? &data->app_name[data->moh_offset] : NULL);
+}
 
-	/* We have everything we need to create this bridge... so allocate the memory, link things together, and fire her up! */
-	bridge = ao2_alloc(sizeof(*bridge), destroy_bridge);
-	if (!bridge) {
-		ast_module_unref(bridge_technology->mod);
-		return NULL;
+static void payload_helper_app(ast_bridge_channel_post_action_data post_it,
+	struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
+{
+	struct bridge_run_app *app_data;
+	size_t len_name = strlen(app_name) + 1;
+	size_t len_args = ast_strlen_zero(app_args) ? 0 : strlen(app_args) + 1;
+	size_t len_moh = !moh_class ? 0 : strlen(moh_class) + 1;
+	size_t len_data = sizeof(*app_data) + len_name + len_args + len_moh;
+
+	/* Fill in application run frame data. */
+	app_data = alloca(len_data);
+	app_data->app_args_offset = len_args ? len_name : 0;
+	app_data->moh_offset = len_moh ? len_name + len_args : 0;
+	strcpy(app_data->app_name, app_name);/* Safe */
+	if (len_args) {
+		strcpy(&app_data->app_name[app_data->app_args_offset], app_args);/* Safe */
 	}
-
-	bridge->technology = bridge_technology;
-	bridge->thread = AST_PTHREADT_NULL;
-
-	/* Create an array of pointers for the channels that will be joining us */
-	bridge->array = ast_malloc(BRIDGE_ARRAY_START * sizeof(*bridge->array));
-	if (!bridge->array) {
-		ao2_ref(bridge, -1);
-		return NULL;
+	if (moh_class) {
+		strcpy(&app_data->app_name[app_data->moh_offset], moh_class);/* Safe */
 	}
-	bridge->array_size = BRIDGE_ARRAY_START;
 
-	ast_set_flag(&bridge->feature_flags, flags);
+	post_it(bridge_channel, AST_BRIDGE_ACTION_RUN_APP, app_data, len_data);
+}
 
-	/* Pass off the bridge to the technology to manipulate if needed */
-	if (bridge->technology->create) {
-		ast_debug(1, "Giving bridge technology %s the bridge structure %p to setup\n", bridge->technology->name, bridge);
-		if (bridge->technology->create(bridge)) {
-			ast_debug(1, "Bridge technology %s failed to setup bridge structure %p\n", bridge->technology->name, bridge);
-			ao2_ref(bridge, -1);
-			return NULL;
-		}
-	}
+void ast_bridge_channel_write_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
+{
+	payload_helper_app(ast_bridge_channel_write_action_data,
+		bridge_channel, app_name, app_args, moh_class);
+}
 
-	return bridge;
+void ast_bridge_channel_queue_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
+{
+	payload_helper_app(ast_bridge_channel_queue_action_data,
+		bridge_channel, app_name, app_args, moh_class);
 }
 
-int ast_bridge_check(uint32_t capabilities)
+void ast_bridge_channel_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
 {
-	struct ast_bridge_technology *bridge_technology;
+	if (moh_class) {
+		if (ast_strlen_zero(moh_class)) {
+			ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
+				NULL, 0);
+		} else {
+			ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
+				moh_class, strlen(moh_class) + 1);
+		}
+	}
+	if (custom_play) {
+		custom_play(bridge_channel, playfile);
+	} else {
+		ast_stream_and_wait(bridge_channel->chan, playfile, AST_DIGIT_NONE);
+	}
+	if (moh_class) {
+		ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_UNHOLD,
+			NULL, 0);
+	}
 
-	if (!(bridge_technology = find_best_technology(capabilities))) {
-		return 0;
+	/*
+	 * It may be necessary to resume music on hold after we finish
+	 * playing the announcment.
+	 *
+	 * XXX We have no idea what MOH class was in use before playing
+	 * the file.
+	 */
+	if (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_MOH)) {
+		ast_moh_start(bridge_channel->chan, NULL, NULL);
 	}
+}
 
-	ast_module_unref(bridge_technology->mod);
+struct bridge_playfile {
+	/*! Call this function to play the playfile. (NULL if normal sound file to play) */
+	ast_bridge_custom_play_fn custom_play;
+	/*! Offset into playfile[] where the MOH class name starts.  (zero if no MOH)*/
+	int moh_offset;
+	/*! Filename to play. */
+	char playfile[0];
+};
 
-	return 1;
+/*!
+ * \internal
+ * \brief Handle the playfile bridge action.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel to play a file on.
+ * \param payload Action frame payload to play a file.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_playfile(struct ast_bridge_channel *bridge_channel, struct bridge_playfile *payload)
+{
+	ast_bridge_channel_playfile(bridge_channel, payload->custom_play, payload->playfile,
+		payload->moh_offset ? &payload->playfile[payload->moh_offset] : NULL);
 }
 
-int ast_bridge_destroy(struct ast_bridge *bridge)
+static void payload_helper_playfile(ast_bridge_channel_post_action_data post_it,
+	struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
 {
-	ao2_lock(bridge);
-
-	if (bridge->callid) {
-		bridge->callid = ast_callid_unref(bridge->callid);
+	struct bridge_playfile *payload;
+	size_t len_name = strlen(playfile) + 1;
+	size_t len_moh = !moh_class ? 0 : strlen(moh_class) + 1;
+	size_t len_payload = sizeof(*payload) + len_name + len_moh;
+
+	/* Fill in play file frame data. */
+	payload = alloca(len_payload);
+	payload->custom_play = custom_play;
+	payload->moh_offset = len_moh ? len_name : 0;
+	strcpy(payload->playfile, playfile);/* Safe */
+	if (moh_class) {
+		strcpy(&payload->playfile[payload->moh_offset], moh_class);/* Safe */
 	}
 
-	if (bridge->thread != AST_PTHREADT_NULL) {
-		bridge_stop(bridge);
-	}
+	post_it(bridge_channel, AST_BRIDGE_ACTION_PLAY_FILE, payload, len_payload);
+}
 
-	ast_debug(1, "Telling all channels in bridge %p to leave the party\n", bridge);
+void ast_bridge_channel_write_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
+{
+	payload_helper_playfile(ast_bridge_channel_write_action_data,
+		bridge_channel, custom_play, playfile, moh_class);
+}
+
+void ast_bridge_channel_queue_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
+{
+	payload_helper_playfile(ast_bridge_channel_queue_action_data,
+		bridge_channel, custom_play, playfile, moh_class);
+}
 
-	/* Drop every bridged channel, the last one will cause the bridge thread (if it exists) to exit */
-	bridge_force_out_all(bridge);
+struct bridge_park {
+	int parker_uuid_offset;
+	int app_data_offset;
+	/* buffer used for holding those strings */
+	char parkee_uuid[0];
+};
 
-	ao2_unlock(bridge);
+static void bridge_channel_park(struct ast_bridge_channel *bridge_channel, struct bridge_park *payload)
+{
+	ast_bridge_channel_park(bridge_channel, payload->parkee_uuid,
+		&payload->parkee_uuid[payload->parker_uuid_offset],
+		payload->app_data_offset ? &payload->parkee_uuid[payload->app_data_offset] : NULL);
+}
 
-	ao2_ref(bridge, -1);
+static void payload_helper_park(ast_bridge_channel_post_action_data post_it,
+	struct ast_bridge_channel *bridge_channel,
+	const char *parkee_uuid,
+	const char *parker_uuid,
+	const char *app_data)
+{
+	struct bridge_park *payload;
+	size_t len_parkee_uuid = strlen(parkee_uuid) + 1;
+	size_t len_parker_uuid = strlen(parker_uuid) + 1;
+	size_t len_app_data = !app_data ? 0 : strlen(app_data) + 1;
+	size_t len_payload = sizeof(*payload) + len_parker_uuid + len_parkee_uuid + len_app_data;
+
+	payload = alloca(len_payload);
+	payload->app_data_offset = len_app_data ? len_parkee_uuid + len_parker_uuid : 0;
+	payload->parker_uuid_offset = len_parkee_uuid;
+	strcpy(payload->parkee_uuid, parkee_uuid);
+	strcpy(&payload->parkee_uuid[payload->parker_uuid_offset], parker_uuid);
+	if (app_data) {
+		strcpy(&payload->parkee_uuid[payload->app_data_offset], app_data);
+	}
 
-	return 0;
+	post_it(bridge_channel, AST_BRIDGE_ACTION_PARK, payload, len_payload);
 }
 
-static int bridge_make_compatible(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+void ast_bridge_channel_write_park(struct ast_bridge_channel *bridge_channel, const char *parkee_uuid, const char *parker_uuid, const char *app_data)
 {
-	struct ast_format formats[2];
-	ast_format_copy(&formats[0], ast_channel_readformat(bridge_channel->chan));
-	ast_format_copy(&formats[1], ast_channel_writeformat(bridge_channel->chan));
+	payload_helper_park(ast_bridge_channel_write_action_data,
+		bridge_channel, parkee_uuid, parker_uuid, app_data);
+}
 
-	/* Are the formats currently in use something this bridge can handle? */
-	if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, ast_channel_readformat(bridge_channel->chan))) {
-		struct ast_format best_format;
+/*!
+ * \internal
+ * \brief Feed notification that a frame is waiting on a channel into the bridging core
+ *
+ * \param bridge_channel Bridge channel the notification was received on
+ */
+static void bridge_handle_trip(struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_frame *frame;
 
-		ast_best_codec(bridge->technology->format_capabilities, &best_format);
+	if (bridge_channel->features->mute) {
+		frame = ast_read_noaudio(bridge_channel->chan);
+	} else {
+		frame = ast_read(bridge_channel->chan);
+	}
 
-		/* Read format is a no go... */
-		if (option_debug) {
-			char codec_buf[512];
-			ast_debug(1, "Bridge technology %s wants to read any of formats %s but channel has %s\n", bridge->technology->name,
-				ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities),
-				ast_getformatname(&formats[0]));
+	if (!frame) {
+		bridge_handle_hangup(bridge_channel);
+		return;
+	}
+	switch (frame->frametype) {
+	case AST_FRAME_NULL:
+		/* Just discard it. */
+		ast_frfree(frame);
+		return;
+	case AST_FRAME_CONTROL:
+		switch (frame->subclass.integer) {
+		case AST_CONTROL_HANGUP:
+			bridge_handle_hangup(bridge_channel);
+			ast_frfree(frame);
+			return;
+/* BUGBUG This is where incoming HOLD/UNHOLD memory should register.  Write UNHOLD into bridge when this channel is pulled. */
+		default:
+			break;
 		}
-		/* Switch read format to the best one chosen */
-		if (ast_set_read_format(bridge_channel->chan, &best_format)) {
-			ast_log(LOG_WARNING, "Failed to set channel %s to read format %s\n", ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
-			return -1;
+		break;
+	case AST_FRAME_DTMF_BEGIN:
+		frame = bridge_handle_dtmf(bridge_channel, frame);
+		if (!frame) {
+			return;
 		}
-		ast_debug(1, "Bridge %p put channel %s into read format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
-	} else {
-		ast_debug(1, "Bridge %p is happy that channel %s already has read format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&formats[0]));
+		/* Fall through */
+	case AST_FRAME_DTMF_END:
+		if (!bridge_channel->features->dtmf_passthrough) {
+			ast_frfree(frame);
+			return;
+		}
+/* BUGBUG This is where incoming DTMF begin/end memory should register.  Write DTMF end into bridge when this channel is pulled. */
+		break;
+	default:
+		break;
 	}
 
-	if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, &formats[1])) {
-		struct ast_format best_format;
+/* BUGBUG bridge join or impart needs to do CONNECTED_LINE updates if the channels are being swapped and it is a 1-1 bridge. */
 
-		ast_best_codec(bridge->technology->format_capabilities, &best_format);
+	/* Simply write the frame out to the bridge technology. */
+/* BUGBUG The tech is where AST_CONTROL_ANSWER hook should go. (early bridge) */
+/* BUGBUG The tech is where incoming BUSY/CONGESTION hangup should happen? (early bridge) */
+	bridge_channel_write_frame(bridge_channel, frame);
+	ast_frfree(frame);
+}
 
-		/* Write format is a no go... */
-		if (option_debug) {
-			char codec_buf[512];
-			ast_debug(1, "Bridge technology %s wants to write any of formats %s but channel has %s\n", bridge->technology->name,
-				ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities),
-				ast_getformatname(&formats[1]));
+/*!
+ * \internal
+ * \brief Complete joining new channels to the bridge.
+ * \since 12.0.0
+ *
+ * \param bridge Check for new channels on this bridge.
+ *
+ * \note On entry, bridge is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_complete_join(struct ast_bridge *bridge)
+{
+	struct ast_bridge_channel *bridge_channel;
+
+	if (bridge->dissolved) {
+		/*
+		 * No sense in completing the join on channels for a dissolved
+		 * bridge.  They are just going to be removed soon anyway.
+		 * However, we do have reason to abort here because the bridge
+		 * technology may not be able to handle the number of channels
+		 * still in the bridge.
+		 */
+		return;
+	}
+
+	AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+		if (!bridge_channel->just_joined) {
+			continue;
 		}
-		/* Switch write format to the best one chosen */
-		if (ast_set_write_format(bridge_channel->chan, &best_format)) {
-			ast_log(LOG_WARNING, "Failed to set channel %s to write format %s\n", ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
-			return -1;
+
+		/* Make the channel compatible with the bridge */
+		bridge_make_compatible(bridge, bridge_channel);
+
+		/* Tell the bridge technology we are joining so they set us up */
+		ast_debug(1, "Bridge %s: %p(%s) is joining %s technology\n",
+			bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+			bridge->technology->name);
+		if (bridge->technology->join
+			&& bridge->technology->join(bridge, bridge_channel)) {
+			ast_debug(1, "Bridge %s: %p(%s) failed to join %s technology\n",
+				bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+				bridge->technology->name);
 		}
-		ast_debug(1, "Bridge %p put channel %s into write format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
-	} else {
-		ast_debug(1, "Bridge %p is happy that channel %s already has write format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&formats[1]));
-	}
 
-	return 0;
+		bridge_channel->just_joined = 0;
+	}
 }
 
-/*! \brief Perform the smart bridge operation. Basically sees if a new bridge technology should be used instead of the current one. */
-static int smart_bridge_operation(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int count)
+/*! \brief Helper function used to find the "best" bridge technology given specified capabilities */
+static struct ast_bridge_technology *find_best_technology(uint32_t capabilities, struct ast_bridge *bridge)
 {
-	uint32_t new_capabilities = 0;
-	struct ast_bridge_technology *new_technology;
-	struct ast_bridge_technology *old_technology = bridge->technology;
-	struct ast_bridge temp_bridge = {
-		.technology = bridge->technology,
-		.bridge_pvt = bridge->bridge_pvt,
-	};
-	struct ast_bridge_channel *bridge_channel2;
+	struct ast_bridge_technology *current;
+	struct ast_bridge_technology *best = NULL;
 
-	/* Based on current feature determine whether we want to change bridge technologies or not */
-	if (bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX) {
-		if (count <= 2) {
-			ast_debug(1, "Bridge %p channel count (%d) is within limits for bridge technology %s, not performing smart bridge operation.\n",
-				bridge, count, bridge->technology->name);
-			return 0;
+	AST_RWLIST_RDLOCK(&bridge_technologies);
+	AST_RWLIST_TRAVERSE(&bridge_technologies, current, entry) {
+		if (current->suspended) {
+			ast_debug(1, "Bridge technology %s is suspended. Skipping.\n",
+				current->name);
+			continue;
 		}
-		new_capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX;
-	} else if (bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) {
-		if (count > 2) {
-			ast_debug(1, "Bridge %p channel count (%d) is within limits for bridge technology %s, not performing smart bridge operation.\n",
-				bridge, count, bridge->technology->name);
-			return 0;
+		if (!(current->capabilities & capabilities)) {
+			ast_debug(1, "Bridge technology %s does not have any capabilities we want.\n",
+				current->name);
+			continue;
+		}
+		if (best && current->preference <= best->preference) {
+			ast_debug(1, "Bridge technology %s has less preference than %s (%d <= %d). Skipping.\n",
+				current->name, best->name, current->preference, best->preference);
+			continue;
 		}
-		new_capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX;
+		if (current->compatible && !current->compatible(bridge)) {
+			ast_debug(1, "Bridge technology %s is not compatible with properties of existing bridge.\n",
+				current->name);
+			continue;
+		}
+		best = current;
 	}
 
-	if (!new_capabilities) {
-		ast_debug(1, "Bridge '%p' has no new capabilities, not performing smart bridge operation.\n", bridge);
-		return 0;
+	if (best) {
+		/* Increment it's module reference count if present so it does not get unloaded while in use */
+		ast_module_ref(best->mod);
+		ast_debug(1, "Chose bridge technology %s\n", best->name);
 	}
 
-	/* Attempt to find a new bridge technology to satisfy the capabilities */
-	if (!(new_technology = find_best_technology(new_capabilities))) {
-		return -1;
-	}
+	AST_RWLIST_UNLOCK(&bridge_technologies);
 
-	ast_debug(1, "Performing smart bridge operation on bridge %p, moving from bridge technology %s to %s\n",
-		bridge, old_technology->name, new_technology->name);
+	return best;
+}
 
-	/* If a thread is currently executing for the current technology tell it to stop */
-	if (bridge->thread != AST_PTHREADT_NULL) {
-		/*
-		 * If the new bridge technology also needs a thread simply tell
-		 * the bridge thread to refresh itself.  This has the benefit of
-		 * not incurring the cost/time of tearing down and bringing up a
-		 * new thread.
-		 */
-		if (new_technology->capabilities & AST_BRIDGE_CAPABILITY_THREAD) {
-			ast_debug(1, "Telling current bridge thread for bridge %p to refresh\n", bridge);
-			bridge->refresh = 1;
-			bridge_poke(bridge);
-		} else {
-			ast_debug(1, "Telling current bridge thread for bridge %p to stop\n", bridge);
-			bridge_stop(bridge);
-		}
-	}
+struct tech_deferred_destroy {
+	struct ast_bridge_technology *tech;
+	void *tech_pvt;
+};
 
-	/*
-	 * Since we are soon going to pass this bridge to a new
-	 * technology we need to NULL out the bridge_pvt pointer but
-	 * don't worry as it still exists in temp_bridge, ditto for the
-	 * old technology.
-	 */
-	bridge->bridge_pvt = NULL;
-	bridge->technology = new_technology;
+/*!
+ * \internal
+ * \brief Deferred destruction of bridge tech private structure.
+ * \since 12.0.0
+ *
+ * \param bridge What to execute the action on.
+ * \param action Deferred bridge tech destruction.
+ *
+ * \note On entry, bridge must not be locked.
+ *
+ * \return Nothing
+ */
+static void bridge_tech_deferred_destroy(struct ast_bridge *bridge, struct ast_frame *action)
+{
+	struct tech_deferred_destroy *deferred = action->data.ptr;
+	struct ast_bridge dummy_bridge = {
+		.technology = deferred->tech,
+		.tech_pvt = deferred->tech_pvt,
+		};
+
+	ast_copy_string(dummy_bridge.uniqueid, bridge->uniqueid, sizeof(dummy_bridge.uniqueid));
+	ast_debug(1, "Bridge %s: calling %s technology destructor (deferred, dummy)\n",
+		dummy_bridge.uniqueid, dummy_bridge.technology->name);
+	dummy_bridge.technology->destroy(&dummy_bridge);
+	ast_module_unref(dummy_bridge.technology->mod);
+}
 
-	/* Pass the bridge to the new bridge technology so it can set it up */
-	if (new_technology->create) {
-		ast_debug(1, "Giving bridge technology %s the bridge structure %p to setup\n",
-			new_technology->name, bridge);
-		if (new_technology->create(bridge)) {
-			ast_debug(1, "Bridge technology %s failed to setup bridge structure %p\n",
-				new_technology->name, bridge);
-		}
+/*!
+ * \internal
+ * \brief Handle bridge action frame.
+ * \since 12.0.0
+ *
+ * \param bridge What to execute the action on.
+ * \param action What to do.
+ *
+ * \note On entry, bridge is already locked.
+ * \note Can be called by the bridge destructor.
+ *
+ * \return Nothing
+ */
+static void bridge_action_bridge(struct ast_bridge *bridge, struct ast_frame *action)
+{
+#if 0	/* In case we need to know when the destructor is calling us. */
+	int in_destructor = !ao2_ref(bridge, 0);
+#endif
+
+	switch (action->subclass.integer) {
+	case AST_BRIDGE_ACTION_DEFERRED_TECH_DESTROY:
+		ast_bridge_unlock(bridge);
+		bridge_tech_deferred_destroy(bridge, action);
+		ast_bridge_lock(bridge);
+		break;
+	case AST_BRIDGE_ACTION_DEFERRED_DISSOLVING:
+		ast_bridge_unlock(bridge);
+		bridge->v_table->dissolving(bridge);
+		ast_bridge_lock(bridge);
+		break;
+	default:
+		/* Unexpected deferred action type.  Should never happen. */
+		ast_assert(0);
+		break;
 	}
+}
 
-	/* Move existing channels over to the new technology, while taking them away from the old one */
-	AST_LIST_TRAVERSE(&bridge->channels, bridge_channel2, entry) {
-		/* Skip over channel that initiated the smart bridge operation */
-		if (bridge_channel == bridge_channel2) {
-			continue;
-		}
+/*!
+ * \internal
+ * \brief Do any pending bridge actions.
+ * \since 12.0.0
+ *
+ * \param bridge What to do actions on.
+ *
+ * \note On entry, bridge is already locked.
+ * \note Can be called by the bridge destructor.
+ *
+ * \return Nothing
+ */
+static void bridge_handle_actions(struct ast_bridge *bridge)
+{
+	struct ast_frame *action;
 
-		/* First we part them from the old technology */
-		if (old_technology->leave) {
-			ast_debug(1, "Giving bridge technology %s notification that %p is leaving bridge %p (really %p)\n",
-				old_technology->name, bridge_channel2, &temp_bridge, bridge);
-			if (old_technology->leave(&temp_bridge, bridge_channel2)) {
-				ast_debug(1, "Bridge technology %s failed to allow %p (really %p) to leave bridge %p\n",
-					old_technology->name, bridge_channel2, &temp_bridge, bridge);
-			}
+	while ((action = AST_LIST_REMOVE_HEAD(&bridge->action_queue, frame_list))) {
+		switch (action->frametype) {
+		case AST_FRAME_BRIDGE_ACTION:
+			bridge_action_bridge(bridge, action);
+			break;
+		default:
+			/* Unexpected deferred frame type.  Should never happen. */
+			ast_assert(0);
+			break;
 		}
+		ast_frfree(action);
+	}
+}
 
-		/* Second we make them compatible again with the bridge */
-		bridge_make_compatible(bridge, bridge_channel2);
+static void destroy_bridge(void *obj)
+{
+	struct ast_bridge *bridge = obj;
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
 
-		/* Third we join them to the new technology */
-		if (new_technology->join) {
-			ast_debug(1, "Giving bridge technology %s notification that %p is joining bridge %p\n",
-				new_technology->name, bridge_channel2, bridge);
-			if (new_technology->join(bridge, bridge_channel2)) {
-				ast_debug(1, "Bridge technology %s failed to join %p to bridge %p\n",
-					new_technology->name, bridge_channel2, bridge);
-			}
-		}
+	ast_debug(1, "Bridge %s: actually destroying %s bridge, nobody wants it anymore\n",
+		bridge->uniqueid, bridge->v_table->name);
 
-		/* Fourth we tell them to wake up so they become aware that the above has happened */
-		bridge_channel_poke(bridge_channel2);
+	msg = stasis_cache_clear_create(ast_bridge_snapshot_type(), bridge->uniqueid);
+	if (msg) {
+		stasis_publish(ast_bridge_topic(bridge), msg);
 	}
 
-	/* Now that all the channels have been moved over we need to get rid of all the information the old technology may have left around */
-	if (old_technology->destroy) {
-		ast_debug(1, "Giving bridge technology %s the bridge structure %p (really %p) to destroy\n",
-			old_technology->name, &temp_bridge, bridge);
-		if (old_technology->destroy(&temp_bridge)) {
-			ast_debug(1, "Bridge technology %s failed to destroy bridge structure %p (really %p)... some memory may have leaked\n",
-				old_technology->name, &temp_bridge, bridge);
+	/* Do any pending actions in the context of destruction. */
+	ast_bridge_lock(bridge);
+	bridge_handle_actions(bridge);
+	ast_bridge_unlock(bridge);
+
+	/* There should not be any channels left in the bridge. */
+	ast_assert(AST_LIST_EMPTY(&bridge->channels));
+
+	ast_debug(1, "Bridge %s: calling %s bridge destructor\n",
+		bridge->uniqueid, bridge->v_table->name);
+	bridge->v_table->destroy(bridge);
+
+	/* Pass off the bridge to the technology to destroy if needed */
+	if (bridge->technology) {
+		ast_debug(1, "Bridge %s: calling %s technology destructor\n",
+			bridge->uniqueid, bridge->technology->name);
+		if (bridge->technology->destroy) {
+			bridge->technology->destroy(bridge);
 		}
+		ast_module_unref(bridge->technology->mod);
+		bridge->technology = NULL;
 	}
 
-	/* Finally if the old technology has module referencing remove our reference, we are no longer going to use it */
-	ast_module_unref(old_technology->mod);
+	if (bridge->callid) {
+		bridge->callid = ast_callid_unref(bridge->callid);
+	}
 
-	return 0;
+	cleanup_video_mode(bridge);
 }
 
-/*! \brief Run in a multithreaded model. Each joined channel does writing/reading in their own thread. TODO: Improve */
-static enum ast_bridge_channel_state bridge_channel_join_multithreaded(struct ast_bridge_channel *bridge_channel)
+struct ast_bridge *ast_bridge_register(struct ast_bridge *bridge)
 {
-	int fds[4] = { -1, }, nfds = 0, i = 0, outfd = -1, ms = -1;
-	struct ast_channel *chan;
-
-	/* Add any file descriptors we may want to monitor */
-	if (bridge_channel->bridge->technology->fd) {
-		for (i = 0; i < 4; i ++) {
-			if (bridge_channel->fds[i] >= 0) {
-				fds[nfds++] = bridge_channel->fds[i];
-			}
+	if (bridge) {
+		ast_bridge_publish_state(bridge);
+		if (!ao2_link(bridges, bridge)) {
+			ast_bridge_destroy(bridge);
+			bridge = NULL;
 		}
 	}
+	return bridge;
+}
 
-	ao2_unlock(bridge_channel->bridge);
+struct ast_bridge *ast_bridge_alloc(size_t size, const struct ast_bridge_methods *v_table)
+{
+	struct ast_bridge *bridge;
 
-	ao2_lock(bridge_channel);
-	/* Wait for data to either come from the channel or us to be signalled */
-	if (!bridge_channel->suspended) {
-		ao2_unlock(bridge_channel);
-		ast_debug(10, "Going into a multithreaded waitfor for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge);
-		chan = ast_waitfor_nandfds(&bridge_channel->chan, 1, fds, nfds, NULL, &outfd, &ms);
-	} else {
-		ast_debug(10, "Going into a multithreaded signal wait for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge);
-		ast_cond_wait(&bridge_channel->cond, ao2_object_get_lockaddr(bridge_channel));
-		ao2_unlock(bridge_channel);
-		chan = NULL;
+	/* Check v_table that all methods are present. */
+	if (!v_table
+		|| !v_table->name
+		|| !v_table->destroy
+		|| !v_table->dissolving
+		|| !v_table->push
+		|| !v_table->pull
+		|| !v_table->notify_masquerade
+		|| !v_table->get_merge_priority) {
+		ast_log(LOG_ERROR, "Virtual method table for bridge class %s not complete.\n",
+			v_table && v_table->name ? v_table->name : "<unknown>");
+		ast_assert(0);
+		return NULL;
 	}
 
-	ao2_lock(bridge_channel->bridge);
-
-	if (!bridge_channel->suspended) {
-		ast_bridge_handle_trip(bridge_channel->bridge, bridge_channel, chan, outfd);
+	bridge = ao2_alloc(size, destroy_bridge);
+	if (bridge) {
+		bridge->v_table = v_table;
 	}
-
-	return bridge_channel->state;
+	return bridge;
 }
 
-/*! \brief Run in a singlethreaded model. Each joined channel yields itself to the main bridge thread. TODO: Improve */
-static enum ast_bridge_channel_state bridge_channel_join_singlethreaded(struct ast_bridge_channel *bridge_channel)
+struct ast_bridge *ast_bridge_base_init(struct ast_bridge *self, uint32_t capabilities, unsigned int flags)
 {
-	ao2_unlock(bridge_channel->bridge);
-	ao2_lock(bridge_channel);
-	if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
-		ast_debug(1, "Going into a single threaded signal wait for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge);
-		ast_cond_wait(&bridge_channel->cond, ao2_object_get_lockaddr(bridge_channel));
+	if (!self) {
+		return NULL;
 	}
-	ao2_unlock(bridge_channel);
-	ao2_lock(bridge_channel->bridge);
 
-	return bridge_channel->state;
-}
+	ast_uuid_generate_str(self->uniqueid, sizeof(self->uniqueid));
+	ast_set_flag(&self->feature_flags, flags);
+	self->allowed_capabilities = capabilities;
 
-/*! \brief Internal function that suspends a channel from a bridge */
-static void bridge_channel_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
-	ao2_lock(bridge_channel);
-	bridge_channel->suspended = 1;
-	bridge_array_remove(bridge, bridge_channel->chan);
-	ao2_unlock(bridge_channel);
+	/* Use our helper function to find the "best" bridge technology. */
+	self->technology = find_best_technology(capabilities, self);
+	if (!self->technology) {
+		ast_debug(1, "Bridge %s: Could not create.  No technology available to support it.\n",
+			self->uniqueid);
+		ao2_ref(self, -1);
+		return NULL;
+	}
 
-	if (bridge->technology->suspend) {
-		bridge->technology->suspend(bridge, bridge_channel);
+	/* Pass off the bridge to the technology to manipulate if needed */
+	ast_debug(1, "Bridge %s: calling %s technology constructor\n",
+		self->uniqueid, self->technology->name);
+	if (self->technology->create && self->technology->create(self)) {
+		ast_debug(1, "Bridge %s: failed to setup %s technology\n",
+			self->uniqueid, self->technology->name);
+		ao2_ref(self, -1);
+		return NULL;
 	}
-}
 
-/*! \brief Internal function that unsuspends a channel from a bridge */
-static void bridge_channel_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
-	ao2_lock(bridge_channel);
-	bridge_channel->suspended = 0;
-	bridge_array_add(bridge, bridge_channel->chan);
-	ast_cond_signal(&bridge_channel->cond);
-	ao2_unlock(bridge_channel);
-
-	if (bridge->technology->unsuspend) {
-		bridge->technology->unsuspend(bridge, bridge_channel);
+	if (!ast_bridge_topic(self)) {
+		ao2_ref(self, -1);
+		return NULL;
 	}
+
+	return self;
 }
 
 /*!
- * \brief Internal function that executes a feature on a bridge channel
- * \note Neither the bridge nor the bridge_channel locks should be held when entering
- * this function.
+ * \internal
+ * \brief ast_bridge base class destructor.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \note Stub because of nothing to do.
+ *
+ * \return Nothing
  */
-static void bridge_channel_feature(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+static void bridge_base_destroy(struct ast_bridge *self)
 {
-	struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features);
-	struct ast_bridge_features_hook *hook = NULL;
-	char dtmf[MAXIMUM_DTMF_FEATURE_STRING] = "";
-	int look_for_dtmf = 1, dtmf_len = 0;
+}
 
-	/* The channel is now under our control and we don't really want any begin frames to do our DTMF matching so disable 'em at the core level */
-	ast_set_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_END_DTMF_ONLY);
+/*!
+ * \internal
+ * \brief The bridge is being dissolved.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \return Nothing
+ */
+static void bridge_base_dissolving(struct ast_bridge *self)
+{
+	ao2_unlink(bridges, self);
+}
 
-	/* Wait for DTMF on the channel and put it into a buffer. If the buffer matches any feature hook execute the hook. */
-	while (look_for_dtmf) {
-		int res = ast_waitfordigit(bridge_channel->chan, 3000);
+/*!
+ * \internal
+ * \brief ast_bridge base push method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to push.
+ * \param swap Bridge channel to swap places with if not NULL.
+ *
+ * \note On entry, self is already locked.
+ * \note Stub because of nothing to do.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int bridge_base_push(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
+{
+	return 0;
+}
 
-		/* If the above timed out simply exit */
-		if (!res) {
-			ast_debug(1, "DTMF feature string collection on bridge channel %p timed out\n", bridge_channel);
-			break;
-		} else if (res < 0) {
-			ast_debug(1, "DTMF feature string collection failed on bridge channel %p for some reason\n", bridge_channel);
-			break;
-		}
+/*!
+ * \internal
+ * \brief ast_bridge base pull method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to pull.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_base_pull(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel)
+{
+	bridge_features_remove_on_pull(bridge_channel->features);
+}
 
-		/* Add the above DTMF into the DTMF string so we can do our matching */
-		dtmf[dtmf_len++] = res;
+/*!
+ * \internal
+ * \brief ast_bridge base notify_masquerade method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel that was masqueraded.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_base_notify_masquerade(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel)
+{
+	self->reconfigured = 1;
+}
 
-		ast_debug(1, "DTMF feature string on bridge channel %p is now '%s'\n", bridge_channel, dtmf);
+/*!
+ * \internal
+ * \brief Get the merge priority of this bridge.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Merge priority
+ */
+static int bridge_base_get_merge_priority(struct ast_bridge *self)
+{
+	return 0;
+}
 
-		/* Assume that we do not want to look for DTMF any longer */
-		look_for_dtmf = 0;
+struct ast_bridge_methods ast_bridge_base_v_table = {
+	.name = "base",
+	.destroy = bridge_base_destroy,
+	.dissolving = bridge_base_dissolving,
+	.push = bridge_base_push,
+	.pull = bridge_base_pull,
+	.notify_masquerade = bridge_base_notify_masquerade,
+	.get_merge_priority = bridge_base_get_merge_priority,
+};
+
+struct ast_bridge *ast_bridge_base_new(uint32_t capabilities, unsigned int flags)
+{
+	void *bridge;
 
-		/* See if a DTMF feature hook matches or can match */
-		AST_LIST_TRAVERSE(&features->hooks, hook, entry) {
-			/* If this hook matches just break out now */
-			if (!strcmp(hook->dtmf, dtmf)) {
-				ast_debug(1, "DTMF feature hook %p matched DTMF string '%s' on bridge channel %p\n", hook, dtmf, bridge_channel);
-				look_for_dtmf = 0;
-				break;
-			} else if (!strncmp(hook->dtmf, dtmf, dtmf_len)) {
-				ast_debug(1, "DTMF feature hook %p can match DTMF string '%s', it wants '%s', on bridge channel %p\n", hook, dtmf, hook->dtmf, bridge_channel);
-				look_for_dtmf = 1;
-			} else {
-				ast_debug(1, "DTMF feature hook %p does not match DTMF string '%s', it wants '%s', on bridge channel %p\n", hook, dtmf, hook->dtmf, bridge_channel);
-			}
-		}
+	bridge = ast_bridge_alloc(sizeof(struct ast_bridge), &ast_bridge_base_v_table);
+	bridge = ast_bridge_base_init(bridge, capabilities, flags);
+	bridge = ast_bridge_register(bridge);
+	return bridge;
+}
 
-		/* If we have reached the maximum length of a DTMF feature string bail out */
-		if (dtmf_len == MAXIMUM_DTMF_FEATURE_STRING) {
-			break;
-		}
-	}
+int ast_bridge_destroy(struct ast_bridge *bridge)
+{
+	ast_debug(1, "Bridge %s: telling all channels to leave the party\n", bridge->uniqueid);
+	ast_bridge_lock(bridge);
+	bridge_dissolve(bridge);
+	ast_bridge_unlock(bridge);
 
-	/* Since we are done bringing DTMF in return to using both begin and end frames */
-	ast_clear_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_END_DTMF_ONLY);
+	ao2_ref(bridge, -1);
 
-	/* If a hook was actually matched execute it on this channel, otherwise stream up the DTMF to the other channels */
-	if (hook) {
-		hook->callback(bridge, bridge_channel, hook->hook_pvt);
-		/*
-		 * If we are handing the channel off to an external hook for
-		 * ownership, we are not guaranteed what kind of state it will
-		 * come back in.  If the channel hungup, we need to detect that
-		 * here if the hook did not already change the state.
-		 */
-		if (bridge_channel->chan && ast_check_hangup_locked(bridge_channel->chan)) {
-			ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
-		}
-	} else {
-		ast_bridge_dtmf_stream(bridge, dtmf, bridge_channel->chan);
-	}
+	return 0;
 }
 
-static void bridge_channel_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+static int bridge_make_compatible(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
 {
-	struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features);
+	struct ast_format read_format;
+	struct ast_format write_format;
+	struct ast_format best_format;
+	char codec_buf[512];
+
+	ast_format_copy(&read_format, ast_channel_readformat(bridge_channel->chan));
+	ast_format_copy(&write_format, ast_channel_writeformat(bridge_channel->chan));
+
+	/* Are the formats currently in use something this bridge can handle? */
+	if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, ast_channel_readformat(bridge_channel->chan))) {
+		ast_best_codec(bridge->technology->format_capabilities, &best_format);
+
+		/* Read format is a no go... */
+		ast_debug(1, "Bridge technology %s wants to read any of formats %s but channel has %s\n",
+			bridge->technology->name,
+			ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities),
+			ast_getformatname(&read_format));
 
-	if (features && features->talker_cb) {
-		features->talker_cb(bridge, bridge_channel, features->talker_pvt_data);
+		/* Switch read format to the best one chosen */
+		if (ast_set_read_format(bridge_channel->chan, &best_format)) {
+			ast_log(LOG_WARNING, "Failed to set channel %s to read format %s\n",
+				ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
+			return -1;
+		}
+		ast_debug(1, "Bridge %s put channel %s into read format %s\n",
+			bridge->uniqueid, ast_channel_name(bridge_channel->chan),
+			ast_getformatname(&best_format));
+	} else {
+		ast_debug(1, "Bridge %s is happy that channel %s already has read format %s\n",
+			bridge->uniqueid, ast_channel_name(bridge_channel->chan),
+			ast_getformatname(&read_format));
 	}
-}
 
-/*! \brief Internal function that plays back DTMF on a bridge channel */
-static void bridge_channel_dtmf_stream(struct ast_bridge_channel *bridge_channel)
-{
-	char dtmf_q[8] = "";
+	if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, &write_format)) {
+		ast_best_codec(bridge->technology->format_capabilities, &best_format);
+
+		/* Write format is a no go... */
+		ast_debug(1, "Bridge technology %s wants to write any of formats %s but channel has %s\n",
+			bridge->technology->name,
+			ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities),
+			ast_getformatname(&write_format));
 
-	ast_copy_string(dtmf_q, bridge_channel->dtmf_stream_q, sizeof(dtmf_q));
-	bridge_channel->dtmf_stream_q[0] = '\0';
+		/* Switch write format to the best one chosen */
+		if (ast_set_write_format(bridge_channel->chan, &best_format)) {
+			ast_log(LOG_WARNING, "Failed to set channel %s to write format %s\n",
+				ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
+			return -1;
+		}
+		ast_debug(1, "Bridge %s put channel %s into write format %s\n",
+			bridge->uniqueid, ast_channel_name(bridge_channel->chan),
+			ast_getformatname(&best_format));
+	} else {
+		ast_debug(1, "Bridge %s is happy that channel %s already has write format %s\n",
+			bridge->uniqueid, ast_channel_name(bridge_channel->chan),
+			ast_getformatname(&write_format));
+	}
 
-	ast_debug(1, "Playing DTMF stream '%s' out to bridge channel %p\n", dtmf_q, bridge_channel);
-	ast_dtmf_stream(bridge_channel->chan, NULL, dtmf_q, 250, 0);
+	return 0;
 }
 
-/*! \brief Join a channel to a bridge and handle anything the bridge may want us to do */
-static enum ast_bridge_channel_state bridge_channel_join(struct ast_bridge_channel *bridge_channel)
+/*!
+ * \internal
+ * \brief Perform the smart bridge operation.
+ * \since 12.0.0
+ *
+ * \param bridge Work on this bridge.
+ *
+ * \details
+ * Basically see if a new bridge technology should be used instead
+ * of the current one.
+ *
+ * \note On entry, bridge is already locked.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int smart_bridge_operation(struct ast_bridge *bridge)
 {
-	struct ast_format formats[2];
-	enum ast_bridge_channel_state state;
-	ast_format_copy(&formats[0], ast_channel_readformat(bridge_channel->chan));
-	ast_format_copy(&formats[1], ast_channel_writeformat(bridge_channel->chan));
+	uint32_t new_capabilities;
+	struct ast_bridge_technology *new_technology;
+	struct ast_bridge_technology *old_technology = bridge->technology;
+	struct ast_bridge_channel *bridge_channel;
+	struct ast_frame *deferred_action;
+	struct ast_bridge dummy_bridge = {
+		.technology = bridge->technology,
+		.tech_pvt = bridge->tech_pvt,
+	};
 
-	/* Record the thread that will be the owner of us */
-	bridge_channel->thread = pthread_self();
+	if (bridge->dissolved) {
+		ast_debug(1, "Bridge %s is dissolved, not performing smart bridge operation.\n",
+			bridge->uniqueid);
+		return 0;
+	}
 
-	ast_debug(1, "Joining bridge channel %p to bridge %p\n", bridge_channel, bridge_channel->bridge);
+	/* Determine new bridge technology capabilities needed. */
+	if (2 < bridge->num_channels) {
+		new_capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX;
+		new_capabilities &= bridge->allowed_capabilities;
+	} else {
+		new_capabilities = AST_BRIDGE_CAPABILITY_NATIVE | AST_BRIDGE_CAPABILITY_1TO1MIX;
+		new_capabilities &= bridge->allowed_capabilities;
+		if (!new_capabilities
+			&& (bridge->allowed_capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)) {
+			/* Allow switching between different multimix bridge technologies. */
+			new_capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX;
+		}
+	}
 
-	ao2_lock(bridge_channel->bridge);
+	/* Find a bridge technology to satisfy the new capabilities. */
+	new_technology = find_best_technology(new_capabilities, bridge);
+	if (!new_technology) {
+		int is_compatible = 0;
+
+		if (old_technology->compatible) {
+			is_compatible = old_technology->compatible(bridge);
+		} else if (old_technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) {
+			is_compatible = 1;
+		} else if (bridge->num_channels <= 2
+			&& (old_technology->capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX)) {
+			is_compatible = 1;
+		}
 
-	if (!bridge_channel->bridge->callid) {
-		bridge_channel->bridge->callid = ast_read_threadstorage_callid();
+		if (is_compatible) {
+			ast_debug(1, "Bridge %s could not get a new technology, staying with old technology.\n",
+				bridge->uniqueid);
+			return 0;
+		}
+		ast_log(LOG_WARNING, "Bridge %s has no technology available to support it.\n",
+			bridge->uniqueid);
+		return -1;
+	}
+	if (new_technology == old_technology) {
+		ast_debug(1, "Bridge %s is already using the new technology.\n",
+			bridge->uniqueid);
+		ast_module_unref(old_technology->mod);
+		return 0;
 	}
 
-	/* Add channel into the bridge */
-	AST_LIST_INSERT_TAIL(&bridge_channel->bridge->channels, bridge_channel, entry);
-	bridge_channel->bridge->num++;
-
-	bridge_array_add(bridge_channel->bridge, bridge_channel->chan);
+	ast_copy_string(dummy_bridge.uniqueid, bridge->uniqueid, sizeof(dummy_bridge.uniqueid));
 
-	if (bridge_channel->swap) {
-		struct ast_bridge_channel *bridge_channel2;
+	if (old_technology->destroy) {
+		struct tech_deferred_destroy deferred_tech_destroy = {
+			.tech = dummy_bridge.technology,
+			.tech_pvt = dummy_bridge.tech_pvt,
+		};
+		struct ast_frame action = {
+			.frametype = AST_FRAME_BRIDGE_ACTION,
+			.subclass.integer = AST_BRIDGE_ACTION_DEFERRED_TECH_DESTROY,
+			.data.ptr = &deferred_tech_destroy,
+			.datalen = sizeof(deferred_tech_destroy),
+		};
 
 		/*
-		 * If we are performing a swap operation we do not need to
-		 * execute the smart bridge operation as the actual number of
-		 * channels involved will not have changed, we just need to tell
-		 * the other channel to leave.
+		 * We need to defer the bridge technology destroy callback
+		 * because we have the bridge locked.
 		 */
-		bridge_channel2 = find_bridge_channel(bridge_channel->bridge, bridge_channel->swap);
-		bridge_channel->swap = NULL;
-		if (bridge_channel2) {
-			ast_debug(1, "Swapping bridge channel %p out from bridge %p so bridge channel %p can slip in\n", bridge_channel2, bridge_channel->bridge, bridge_channel);
-			ast_bridge_change_state(bridge_channel2, AST_BRIDGE_CHANNEL_STATE_HANGUP);
-		}
-	} else if (ast_test_flag(&bridge_channel->bridge->feature_flags, AST_BRIDGE_FLAG_SMART)) {
-		/* Perform the smart bridge operation, basically see if we need to move around between technologies */
-		smart_bridge_operation(bridge_channel->bridge, bridge_channel, bridge_channel->bridge->num);
-	}
-
-	/* Make the channel compatible with the bridge */
-	bridge_make_compatible(bridge_channel->bridge, bridge_channel);
-
-	/* Tell the bridge technology we are joining so they set us up */
-	if (bridge_channel->bridge->technology->join) {
-		ast_debug(1, "Giving bridge technology %s notification that %p is joining bridge %p\n", bridge_channel->bridge->technology->name, bridge_channel, bridge_channel->bridge);
-		if (bridge_channel->bridge->technology->join(bridge_channel->bridge, bridge_channel)) {
-			ast_debug(1, "Bridge technology %s failed to join %p to bridge %p\n", bridge_channel->bridge->technology->name, bridge_channel, bridge_channel->bridge);
-		}
-	}
-
-	/* Actually execute the respective threading model, and keep our bridge thread alive */
-	while (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
-		/* Update bridge pointer on channel */
-		ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge);
-		/* If the technology requires a thread and one is not running, start it up */
-		if (bridge_channel->bridge->thread == AST_PTHREADT_NULL
-			&& (bridge_channel->bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_THREAD)) {
-			bridge_channel->bridge->stop = 0;
-			ast_debug(1, "Starting a bridge thread for bridge %p\n", bridge_channel->bridge);
-			ao2_ref(bridge_channel->bridge, +1);
-			if (ast_pthread_create(&bridge_channel->bridge->thread, NULL, bridge_thread, bridge_channel->bridge)) {
-				ast_debug(1, "Failed to create a bridge thread for bridge %p, giving it another go.\n", bridge_channel->bridge);
-				ao2_ref(bridge_channel->bridge, -1);
-				continue;
-			}
-		}
-		/* Execute the threading model */
-		state = (bridge_channel->bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTITHREADED)
-			? bridge_channel_join_multithreaded(bridge_channel)
-			: bridge_channel_join_singlethreaded(bridge_channel);
-		/* Depending on the above state see what we need to do */
-		switch (state) {
-		case AST_BRIDGE_CHANNEL_STATE_FEATURE:
-			bridge_channel_suspend(bridge_channel->bridge, bridge_channel);
-			ao2_unlock(bridge_channel->bridge);
-			bridge_channel_feature(bridge_channel->bridge, bridge_channel);
-			ao2_lock(bridge_channel->bridge);
-			ao2_lock(bridge_channel);
-			if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_FEATURE) {
-				ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT);
-			}
-			ao2_unlock(bridge_channel);
-			bridge_channel_unsuspend(bridge_channel->bridge, bridge_channel);
-			break;
-		case AST_BRIDGE_CHANNEL_STATE_DTMF:
-			bridge_channel_suspend(bridge_channel->bridge, bridge_channel);
-			ao2_unlock(bridge_channel->bridge);
-			bridge_channel_dtmf_stream(bridge_channel);
-			ao2_lock(bridge_channel->bridge);
-			ao2_lock(bridge_channel);
-			if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_DTMF) {
-				ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT);
-			}
-			ao2_unlock(bridge_channel);
-			bridge_channel_unsuspend(bridge_channel->bridge, bridge_channel);
-			break;
-		case AST_BRIDGE_CHANNEL_STATE_START_TALKING:
-		case AST_BRIDGE_CHANNEL_STATE_STOP_TALKING:
-			ao2_unlock(bridge_channel->bridge);
-			bridge_channel_talking(bridge_channel->bridge, bridge_channel);
-			ao2_lock(bridge_channel->bridge);
-			ao2_lock(bridge_channel);
-			switch (bridge_channel->state) {
-			case AST_BRIDGE_CHANNEL_STATE_START_TALKING:
-			case AST_BRIDGE_CHANNEL_STATE_STOP_TALKING:
-				ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT);
-				break;
-			default:
-				break;
-			}
-			ao2_unlock(bridge_channel);
-			break;
-		default:
-			break;
+		deferred_action = ast_frdup(&action);
+		if (!deferred_action) {
+			ast_module_unref(new_technology->mod);
+			return -1;
 		}
+	} else {
+		deferred_action = NULL;
 	}
 
-	ast_channel_internal_bridge_set(bridge_channel->chan, NULL);
+	/*
+	 * We are now committed to changing the bridge technology.  We
+	 * must not release the bridge lock until we have installed the
+	 * new bridge technology.
+	 */
+	ast_debug(1, "Bridge %s: switching %s technology to %s\n",
+		bridge->uniqueid, old_technology->name, new_technology->name);
 
-	/* See if we need to dissolve the bridge itself if they hung up */
-	if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_END) {
-		bridge_check_dissolve(bridge_channel->bridge, bridge_channel);
+	/*
+	 * Since we are soon going to pass this bridge to a new
+	 * technology we need to NULL out the tech_pvt pointer but
+	 * don't worry as it still exists in dummy_bridge, ditto for the
+	 * old technology.
+	 */
+	bridge->tech_pvt = NULL;
+	bridge->technology = new_technology;
+
+	/* Setup the new bridge technology. */
+	ast_debug(1, "Bridge %s: calling %s technology constructor\n",
+		bridge->uniqueid, new_technology->name);
+	if (new_technology->create && new_technology->create(bridge)) {
+		ast_log(LOG_WARNING, "Bridge %s: failed to setup bridge technology %s\n",
+			bridge->uniqueid, new_technology->name);
+		bridge->tech_pvt = dummy_bridge.tech_pvt;
+		bridge->technology = dummy_bridge.technology;
+		ast_module_unref(new_technology->mod);
+		return -1;
 	}
 
-	/* Tell the bridge technology we are leaving so they tear us down */
-	if (bridge_channel->bridge->technology->leave) {
-		ast_debug(1, "Giving bridge technology %s notification that %p is leaving bridge %p\n", bridge_channel->bridge->technology->name, bridge_channel, bridge_channel->bridge);
-		if (bridge_channel->bridge->technology->leave(bridge_channel->bridge, bridge_channel)) {
-			ast_debug(1, "Bridge technology %s failed to leave %p from bridge %p\n", bridge_channel->bridge->technology->name, bridge_channel, bridge_channel->bridge);
+	/* Move existing channels over to the new technology. */
+	AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+		if (bridge_channel->just_joined) {
+			/*
+			 * This channel has not completed joining the bridge so it is
+			 * not in the old bridge technology.
+			 */
+			continue;
+		}
+
+		/* First we part them from the old technology */
+		ast_debug(1, "Bridge %s: %p(%s) is leaving %s technology (dummy)\n",
+			dummy_bridge.uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+			old_technology->name);
+		if (old_technology->leave) {
+			old_technology->leave(&dummy_bridge, bridge_channel);
 		}
-	}
 
-	/* Remove channel from the bridge */
-	bridge_channel->bridge->num--;
-	AST_LIST_REMOVE(&bridge_channel->bridge->channels, bridge_channel, entry);
+		/* Second we make them compatible again with the bridge */
+		bridge_make_compatible(bridge, bridge_channel);
 
-	bridge_array_remove(bridge_channel->bridge, bridge_channel->chan);
+		/* Third we join them to the new technology */
+		ast_debug(1, "Bridge %s: %p(%s) is joining %s technology\n",
+			bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+			new_technology->name);
+		if (new_technology->join && new_technology->join(bridge, bridge_channel)) {
+			ast_debug(1, "Bridge %s: %p(%s) failed to join %s technology\n",
+				bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+				new_technology->name);
+		}
+	}
 
-	/* Perform the smart bridge operation if needed since a channel has left */
-	if (ast_test_flag(&bridge_channel->bridge->feature_flags, AST_BRIDGE_FLAG_SMART)) {
-		smart_bridge_operation(bridge_channel->bridge, NULL, bridge_channel->bridge->num);
+	/*
+	 * Now that all the channels have been moved over we need to get
+	 * rid of all the information the old technology may have left
+	 * around.
+	 */
+	if (old_technology->destroy) {
+		ast_debug(1, "Bridge %s: deferring %s technology destructor\n",
+			bridge->uniqueid, old_technology->name);
+		bridge_queue_action_nodup(bridge, deferred_action);
+	} else {
+		ast_debug(1, "Bridge %s: calling %s technology destructor\n",
+			bridge->uniqueid, old_technology->name);
+		ast_module_unref(old_technology->mod);
 	}
 
-	ao2_unlock(bridge_channel->bridge);
+	return 0;
+}
 
-	/* Restore original formats of the channel as they came in */
-	if (ast_format_cmp(ast_channel_readformat(bridge_channel->chan), &formats[0]) == AST_FORMAT_CMP_NOT_EQUAL) {
-		ast_debug(1, "Bridge is returning %p to read format %s(%d)\n", bridge_channel, ast_getformatname(&formats[0]), formats[0].id);
-		if (ast_set_read_format(bridge_channel->chan, &formats[0])) {
-			ast_debug(1, "Bridge failed to return channel %p to read format %s(%d)\n", bridge_channel, ast_getformatname(&formats[0]), formats[0].id);
-		}
+/*!
+ * \internal
+ * \brief Notify the bridge that it has been reconfigured.
+ * \since 12.0.0
+ *
+ * \param bridge Reconfigured bridge.
+ *
+ * \details
+ * After a series of bridge_channel_push and
+ * bridge_channel_pull calls, you need to call this function
+ * to cause the bridge to complete restruturing for the change
+ * in the channel makeup of the bridge.
+ *
+ * \note On entry, the bridge is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_reconfigured(struct ast_bridge *bridge)
+{
+	if (!bridge->reconfigured) {
+		return;
 	}
-	if (ast_format_cmp(ast_channel_writeformat(bridge_channel->chan), &formats[1]) == AST_FORMAT_CMP_NOT_EQUAL) {
-		ast_debug(1, "Bridge is returning %p to write format %s(%d)\n", bridge_channel, ast_getformatname(&formats[1]), formats[1].id);
-		if (ast_set_write_format(bridge_channel->chan, &formats[1])) {
-			ast_debug(1, "Bridge failed to return channel %p to write format %s(%d)\n", bridge_channel, ast_getformatname(&formats[1]), formats[1].id);
-		}
+	bridge->reconfigured = 0;
+	if (ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_SMART)
+		&& smart_bridge_operation(bridge)) {
+		/* Smart bridge failed. */
+		bridge_dissolve(bridge);
+		return;
+	}
+	bridge_complete_join(bridge);
+}
+
+/*!
+ * \internal
+ * \brief Suspend a channel from a bridge.
+ *
+ * \param bridge_channel Channel to suspend.
+ *
+ * \note This function assumes bridge_channel->bridge is locked.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_suspend_nolock(struct ast_bridge_channel *bridge_channel)
+{
+	bridge_channel->suspended = 1;
+	if (bridge_channel->in_bridge) {
+		--bridge_channel->bridge->num_active;
 	}
 
-	return bridge_channel->state;
+	/* Get technology bridge threads off of the channel. */
+	if (bridge_channel->bridge->technology->suspend) {
+		bridge_channel->bridge->technology->suspend(bridge_channel->bridge, bridge_channel);
+	}
 }
 
-static void bridge_channel_destroy(void *obj)
+/*!
+ * \internal
+ * \brief Suspend a channel from a bridge.
+ *
+ * \param bridge_channel Channel to suspend.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_suspend(struct ast_bridge_channel *bridge_channel)
 {
-	struct ast_bridge_channel *bridge_channel = obj;
+	ast_bridge_channel_lock_bridge(bridge_channel);
+	bridge_channel_suspend_nolock(bridge_channel);
+	ast_bridge_unlock(bridge_channel->bridge);
+}
 
-	if (bridge_channel->callid) {
-		bridge_channel->callid = ast_callid_unref(bridge_channel->callid);
+/*!
+ * \internal
+ * \brief Unsuspend a channel from a bridge.
+ *
+ * \param bridge_channel Channel to unsuspend.
+ *
+ * \note This function assumes bridge_channel->bridge is locked.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_unsuspend_nolock(struct ast_bridge_channel *bridge_channel)
+{
+	bridge_channel->suspended = 0;
+	if (bridge_channel->in_bridge) {
+		++bridge_channel->bridge->num_active;
+	}
+
+	/* Wake technology bridge threads to take care of channel again. */
+	if (bridge_channel->bridge->technology->unsuspend) {
+		bridge_channel->bridge->technology->unsuspend(bridge_channel->bridge, bridge_channel);
+	}
+
+	/* Wake suspended channel. */
+	ast_bridge_channel_lock(bridge_channel);
+	ast_cond_signal(&bridge_channel->cond);
+	ast_bridge_channel_unlock(bridge_channel);
+}
+
+/*!
+ * \internal
+ * \brief Unsuspend a channel from a bridge.
+ *
+ * \param bridge_channel Channel to unsuspend.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_unsuspend(struct ast_bridge_channel *bridge_channel)
+{
+	ast_bridge_channel_lock_bridge(bridge_channel);
+	bridge_channel_unsuspend_nolock(bridge_channel);
+	ast_bridge_unlock(bridge_channel->bridge);
+}
+
+/*! \brief Internal function that activates interval hooks on a bridge channel */
+static void bridge_channel_interval(struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_bridge_hook *hook;
+	struct timeval start;
+
+	ast_heap_wrlock(bridge_channel->features->interval_hooks);
+	start = ast_tvnow();
+	while ((hook = ast_heap_peek(bridge_channel->features->interval_hooks, 1))) {
+		int interval;
+		unsigned int execution_time;
+
+		if (ast_tvdiff_ms(hook->parms.timer.trip_time, start) > 0) {
+			ast_debug(1, "Hook %p on %p(%s) wants to happen in the future, stopping our traversal\n",
+				hook, bridge_channel, ast_channel_name(bridge_channel->chan));
+			break;
+		}
+		ao2_ref(hook, +1);
+		ast_heap_unlock(bridge_channel->features->interval_hooks);
+
+		ast_debug(1, "Executing hook %p on %p(%s)\n",
+			hook, bridge_channel, ast_channel_name(bridge_channel->chan));
+		interval = hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+
+		ast_heap_wrlock(bridge_channel->features->interval_hooks);
+		if (ast_heap_peek(bridge_channel->features->interval_hooks,
+			hook->parms.timer.heap_index) != hook
+			|| !ast_heap_remove(bridge_channel->features->interval_hooks, hook)) {
+			/* Interval hook is already removed from the bridge_channel. */
+			ao2_ref(hook, -1);
+			continue;
+		}
+		ao2_ref(hook, -1);
+
+		if (interval < 0) {
+			ast_debug(1, "Removed interval hook %p from %p(%s)\n",
+				hook, bridge_channel, ast_channel_name(bridge_channel->chan));
+			ao2_ref(hook, -1);
+			continue;
+		}
+		if (interval) {
+			/* Set new interval for the hook. */
+			hook->parms.timer.interval = interval;
+		}
+
+		ast_debug(1, "Updating interval hook %p with interval %u on %p(%s)\n",
+			hook, hook->parms.timer.interval, bridge_channel,
+			ast_channel_name(bridge_channel->chan));
+
+		/* resetting start */
+		start = ast_tvnow();
+
+		/*
+		 * Resetup the interval hook for the next interval.  We may need
+		 * to skip over any missed intervals because the hook was
+		 * delayed or took too long.
+		 */
+		execution_time = ast_tvdiff_ms(start, hook->parms.timer.trip_time);
+		while (hook->parms.timer.interval < execution_time) {
+			execution_time -= hook->parms.timer.interval;
+		}
+		hook->parms.timer.trip_time = ast_tvadd(start, ast_samp2tv(hook->parms.timer.interval - execution_time, 1000));
+		hook->parms.timer.seqno = ast_atomic_fetchadd_int((int *) &bridge_channel->features->interval_sequence, +1);
+
+		if (ast_heap_push(bridge_channel->features->interval_hooks, hook)) {
+			/* Could not push the hook back onto the heap. */
+			ao2_ref(hook, -1);
+		}
+	}
+	ast_heap_unlock(bridge_channel->features->interval_hooks);
+}
+
+static void bridge_channel_write_dtmf_stream(struct ast_bridge_channel *bridge_channel, const char *dtmf)
+{
+	ast_bridge_channel_write_action_data(bridge_channel,
+		AST_BRIDGE_ACTION_DTMF_STREAM, dtmf, strlen(dtmf) + 1);
+}
+
+/*!
+ * \brief Internal function that executes a feature on a bridge channel
+ * \note Neither the bridge nor the bridge_channel locks should be held when entering
+ * this function.
+ */
+static void bridge_channel_feature(struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_bridge_features *features = bridge_channel->features;
+	struct ast_bridge_hook *hook = NULL;
+	char dtmf[MAXIMUM_DTMF_FEATURE_STRING] = "";
+	size_t dtmf_len = 0;
+
+	/* The channel is now under our control and we don't really want any begin frames to do our DTMF matching so disable 'em at the core level */
+	ast_set_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_END_DTMF_ONLY);
+
+	/* Wait for DTMF on the channel and put it into a buffer. If the buffer matches any feature hook execute the hook. */
+	do {
+		int res;
+
+		/* If the above timed out simply exit */
+		res = ast_waitfordigit(bridge_channel->chan, 3000);
+		if (!res) {
+			ast_debug(1, "DTMF feature string collection on %p(%s) timed out\n",
+				bridge_channel, ast_channel_name(bridge_channel->chan));
+			break;
+		}
+		if (res < 0) {
+			ast_debug(1, "DTMF feature string collection failed on %p(%s) for some reason\n",
+				bridge_channel, ast_channel_name(bridge_channel->chan));
+			break;
+		}
+
+/* BUGBUG need to record the duration of DTMF digits so when the string is played back, they are reproduced. */
+		/* Add the above DTMF into the DTMF string so we can do our matching */
+		dtmf[dtmf_len++] = res;
+		ast_debug(1, "DTMF feature string on %p(%s) is now '%s'\n",
+			bridge_channel, ast_channel_name(bridge_channel->chan), dtmf);
+
+		/* See if a DTMF feature hook matches or can match */
+		hook = ao2_find(features->dtmf_hooks, dtmf, OBJ_PARTIAL_KEY);
+		if (!hook) {
+			ast_debug(1, "No DTMF feature hooks on %p(%s) match '%s'\n",
+				bridge_channel, ast_channel_name(bridge_channel->chan), dtmf);
+			break;
+		}
+		if (strlen(hook->parms.dtmf.code) == dtmf_len) {
+			ast_debug(1, "DTMF feature hook %p matched DTMF string '%s' on %p(%s)\n",
+				hook, dtmf, bridge_channel, ast_channel_name(bridge_channel->chan));
+			break;
+		}
+		ao2_ref(hook, -1);
+		hook = NULL;
+
+		/* Stop if we have reached the maximum length of a DTMF feature string. */
+	} while (dtmf_len < ARRAY_LEN(dtmf) - 1);
+
+	/* Since we are done bringing DTMF in return to using both begin and end frames */
+	ast_clear_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_END_DTMF_ONLY);
+
+	/* If a hook was actually matched execute it on this channel, otherwise stream up the DTMF to the other channels */
+	if (hook) {
+		int failed;
+
+		failed = hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+		if (failed) {
+			ast_debug(1, "DTMF hook %p is being removed from %p(%s)\n",
+				hook, bridge_channel, ast_channel_name(bridge_channel->chan));
+			ao2_unlink(features->dtmf_hooks, hook);
+		}
+		ao2_ref(hook, -1);
+
+		/*
+		 * If we are handing the channel off to an external hook for
+		 * ownership, we are not guaranteed what kind of state it will
+		 * come back in.  If the channel hungup, we need to detect that
+		 * here if the hook did not already change the state.
+		 */
+		if (bridge_channel->chan && ast_check_hangup_locked(bridge_channel->chan)) {
+			bridge_handle_hangup(bridge_channel);
+		}
+	} else if (features->dtmf_passthrough) {
+		bridge_channel_write_dtmf_stream(bridge_channel, dtmf);
+	}
+}
+
+static void bridge_channel_talking(struct ast_bridge_channel *bridge_channel, int talking)
+{
+	struct ast_bridge_features *features = bridge_channel->features;
+
+	if (features->talker_cb) {
+		features->talker_cb(bridge_channel, features->talker_pvt_data, talking);
+	}
+}
+
+/*! \brief Internal function that plays back DTMF on a bridge channel */
+static void bridge_channel_dtmf_stream(struct ast_bridge_channel *bridge_channel, const char *dtmf)
+{
+	ast_debug(1, "Playing DTMF stream '%s' out to %p(%s)\n",
+		dtmf, bridge_channel, ast_channel_name(bridge_channel->chan));
+	ast_dtmf_stream(bridge_channel->chan, NULL, dtmf, 0, 0);
+}
+
+struct blind_transfer_data {
+	char exten[AST_MAX_EXTENSION];
+	char context[AST_MAX_CONTEXT];
+};
+
+static void bridge_channel_blind_transfer(struct ast_bridge_channel *bridge_channel,
+		struct blind_transfer_data *blind_data)
+{
+	ast_async_goto(bridge_channel->chan, blind_data->context, blind_data->exten, 1);
+	bridge_handle_hangup(bridge_channel);
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel bridge action frame.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to execute the action on.
+ * \param action What to do.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_action(struct ast_bridge_channel *bridge_channel, struct ast_frame *action)
+{
+	switch (action->subclass.integer) {
+	case AST_BRIDGE_ACTION_INTERVAL:
+		bridge_channel_suspend(bridge_channel);
+		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+		bridge_channel_interval(bridge_channel);
+		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+		bridge_channel_unsuspend(bridge_channel);
+		break;
+	case AST_BRIDGE_ACTION_FEATURE:
+		bridge_channel_suspend(bridge_channel);
+		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+		bridge_channel_feature(bridge_channel);
+		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+		bridge_channel_unsuspend(bridge_channel);
+		break;
+	case AST_BRIDGE_ACTION_DTMF_STREAM:
+		bridge_channel_suspend(bridge_channel);
+		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+		bridge_channel_dtmf_stream(bridge_channel, action->data.ptr);
+		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+		bridge_channel_unsuspend(bridge_channel);
+		break;
+	case AST_BRIDGE_ACTION_TALKING_START:
+	case AST_BRIDGE_ACTION_TALKING_STOP:
+		bridge_channel_talking(bridge_channel,
+			action->subclass.integer == AST_BRIDGE_ACTION_TALKING_START);
+		break;
+	case AST_BRIDGE_ACTION_PLAY_FILE:
+		bridge_channel_suspend(bridge_channel);
+		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+		bridge_channel_playfile(bridge_channel, action->data.ptr);
+		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+		bridge_channel_unsuspend(bridge_channel);
+		break;
+	case AST_BRIDGE_ACTION_PARK:
+		bridge_channel_suspend(bridge_channel);
+		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+		bridge_channel_park(bridge_channel, action->data.ptr);
+		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+		bridge_channel_unsuspend(bridge_channel);
+		break;
+	case AST_BRIDGE_ACTION_RUN_APP:
+		bridge_channel_suspend(bridge_channel);
+		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+		bridge_channel_run_app(bridge_channel, action->data.ptr);
+		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+		bridge_channel_unsuspend(bridge_channel);
+		break;
+	case AST_BRIDGE_ACTION_BLIND_TRANSFER:
+		bridge_channel_blind_transfer(bridge_channel, action->data.ptr);
+		break;
+	default:
+		break;
+	}
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel control frame action.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to execute the control frame action on.
+ * \param fr Control frame to handle.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_control(struct ast_bridge_channel *bridge_channel, struct ast_frame *fr)
+{
+	struct ast_channel *chan;
+	struct ast_option_header *aoh;
+	int is_caller;
+	int intercept_failed;
+
+	chan = bridge_channel->chan;
+	switch (fr->subclass.integer) {
+	case AST_CONTROL_REDIRECTING:
+		is_caller = !ast_test_flag(ast_channel_flags(chan), AST_FLAG_OUTGOING);
+		bridge_channel_suspend(bridge_channel);
+		intercept_failed = ast_channel_redirecting_sub(NULL, chan, fr, 1)
+			&& ast_channel_redirecting_macro(NULL, chan, fr, is_caller, 1);
+		bridge_channel_unsuspend(bridge_channel);
+		if (intercept_failed) {
+			ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
+		}
+		break;
+	case AST_CONTROL_CONNECTED_LINE:
+		is_caller = !ast_test_flag(ast_channel_flags(chan), AST_FLAG_OUTGOING);
+		bridge_channel_suspend(bridge_channel);
+		intercept_failed = ast_channel_connected_line_sub(NULL, chan, fr, 1)
+			&& ast_channel_connected_line_macro(NULL, chan, fr, is_caller, 1);
+		bridge_channel_unsuspend(bridge_channel);
+		if (intercept_failed) {
+			ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
+		}
+		break;
+	case AST_CONTROL_HOLD:
+	case AST_CONTROL_UNHOLD:
+/*
+ * BUGBUG bridge_channels should remember sending/receiving an outstanding HOLD to/from the bridge
+ *
+ * When the sending channel is pulled from the bridge it needs to write into the bridge an UNHOLD before being pulled.
+ * When the receiving channel is pulled from the bridge it needs to generate its own UNHOLD.
+ * Something similar needs to be done for DTMF begin/end.
+ */
+	case AST_CONTROL_VIDUPDATE:
+	case AST_CONTROL_SRCUPDATE:
+	case AST_CONTROL_SRCCHANGE:
+	case AST_CONTROL_T38_PARAMETERS:
+/* BUGBUG may have to do something with a jitter buffer for these. */
+		ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
+		break;
+	case AST_CONTROL_OPTION:
+		/*
+		 * Forward option Requests, but only ones we know are safe These
+		 * are ONLY sent by chan_iax2 and I'm not convinced that they
+		 * are useful. I haven't deleted them entirely because I just am
+		 * not sure of the ramifications of removing them.
+		 */
+		aoh = fr->data.ptr;
+		if (aoh && aoh->flag == AST_OPTION_FLAG_REQUEST) {
+			switch (ntohs(aoh->option)) {
+			case AST_OPTION_TONE_VERIFY:
+			case AST_OPTION_TDD:
+			case AST_OPTION_RELAXDTMF:
+			case AST_OPTION_AUDIO_MODE:
+			case AST_OPTION_DIGIT_DETECT:
+			case AST_OPTION_FAX_DETECT:
+				ast_channel_setoption(chan, ntohs(aoh->option), aoh->data,
+					fr->datalen - sizeof(*aoh), 0);
+				break;
+			default:
+				break;
+			}
+		}
+		break;
+	case AST_CONTROL_ANSWER:
+		if (ast_channel_state(chan) != AST_STATE_UP) {
+			ast_answer(chan);
+		} else {
+			ast_indicate(chan, -1);
+		}
+		break;
+	default:
+		ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
+		break;
+	}
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel write frame to channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to write outgoing frame.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_write(struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_frame *fr;
+	char nudge;
+
+	ast_bridge_channel_lock(bridge_channel);
+	if (read(bridge_channel->alert_pipe[0], &nudge, sizeof(nudge)) < 0) {
+		if (errno != EINTR && errno != EAGAIN) {
+			ast_log(LOG_WARNING, "read() failed for alert pipe on %p(%s): %s\n",
+				bridge_channel, ast_channel_name(bridge_channel->chan), strerror(errno));
+		}
+	}
+	fr = AST_LIST_REMOVE_HEAD(&bridge_channel->wr_queue, frame_list);
+	ast_bridge_channel_unlock(bridge_channel);
+	if (!fr) {
+		return;
+	}
+	switch (fr->frametype) {
+	case AST_FRAME_BRIDGE_ACTION:
+		bridge_channel_handle_action(bridge_channel, fr);
+		break;
+	case AST_FRAME_CONTROL:
+		bridge_channel_handle_control(bridge_channel, fr);
+		break;
+	case AST_FRAME_NULL:
+		break;
+	default:
+		/* Write the frame to the channel. */
+		bridge_channel->activity = AST_BRIDGE_CHANNEL_THREAD_SIMPLE;
+		ast_write(bridge_channel->chan, fr);
+		break;
+	}
+	ast_frfree(fr);
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel interval expiration.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to check interval on.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_interval(struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_timer *interval_timer;
+
+	interval_timer = bridge_channel->features->interval_timer;
+	if (interval_timer) {
+		if (ast_wait_for_input(ast_timer_fd(interval_timer), 0) == 1) {
+			ast_timer_ack(interval_timer, 1);
+			if (bridge_channel_interval_ready(bridge_channel)) {
+/* BUGBUG since this is now only run by the channel thread, there is no need to queue the action once this intervals become a first class wait item in bridge_channel_wait(). */
+				struct ast_frame interval_action = {
+					.frametype = AST_FRAME_BRIDGE_ACTION,
+					.subclass.integer = AST_BRIDGE_ACTION_INTERVAL,
+				};
+
+				ast_bridge_channel_queue_frame(bridge_channel, &interval_action);
+			}
+		}
+	}
+}
+
+/*!
+ * \internal
+ * \brief Wait for something to happen on the bridge channel and handle it.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to wait.
+ *
+ * \note Each channel does writing/reading in their own thread.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_wait(struct ast_bridge_channel *bridge_channel)
+{
+	int ms = -1;
+	int outfd;
+	struct ast_channel *chan;
+
+	/* Wait for data to either come from the channel or us to be signaled */
+	ast_bridge_channel_lock(bridge_channel);
+	if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+	} else if (bridge_channel->suspended) {
+/* BUGBUG the external party use of suspended will go away as will these references because this is the bridge channel thread */
+		ast_debug(1, "Bridge %s: %p(%s) is going into a signal wait\n",
+			bridge_channel->bridge->uniqueid, bridge_channel,
+			ast_channel_name(bridge_channel->chan));
+		ast_cond_wait(&bridge_channel->cond, ao2_object_get_lockaddr(bridge_channel));
+	} else {
+		ast_debug(10, "Bridge %s: %p(%s) is going into a waitfor\n",
+			bridge_channel->bridge->uniqueid, bridge_channel,
+			ast_channel_name(bridge_channel->chan));
+		bridge_channel->waiting = 1;
+		ast_bridge_channel_unlock(bridge_channel);
+		outfd = -1;
+/* BUGBUG need to make the next expiring active interval setup ms timeout rather than holding up the chan reads. */
+		chan = ast_waitfor_nandfds(&bridge_channel->chan, 1,
+			&bridge_channel->alert_pipe[0], 1, NULL, &outfd, &ms);
+		bridge_channel->waiting = 0;
+		if (ast_channel_softhangup_internal_flag(bridge_channel->chan) & AST_SOFTHANGUP_UNBRIDGE) {
+			ast_channel_clear_softhangup(bridge_channel->chan, AST_SOFTHANGUP_UNBRIDGE);
+			ast_bridge_channel_lock_bridge(bridge_channel);
+			bridge_channel->bridge->reconfigured = 1;
+			bridge_reconfigured(bridge_channel->bridge);
+			ast_bridge_unlock(bridge_channel->bridge);
+		}
+		ast_bridge_channel_lock(bridge_channel);
+		bridge_channel->activity = AST_BRIDGE_CHANNEL_THREAD_FRAME;
+		ast_bridge_channel_unlock(bridge_channel);
+		if (!bridge_channel->suspended
+			&& bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+			if (chan) {
+				bridge_channel_handle_interval(bridge_channel);
+				bridge_handle_trip(bridge_channel);
+			} else if (-1 < outfd) {
+				bridge_channel_handle_write(bridge_channel);
+			}
+		}
+		bridge_channel->activity = AST_BRIDGE_CHANNEL_THREAD_IDLE;
+		return;
+	}
+	ast_bridge_channel_unlock(bridge_channel);
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel join event.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is joining.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_join(struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_bridge_features *features = bridge_channel->features;
+	struct ast_bridge_hook *hook;
+	struct ao2_iterator iter;
+
+	/* Run any join hooks. */
+	iter = ao2_iterator_init(features->join_hooks, AO2_ITERATOR_UNLINK);
+	hook = ao2_iterator_next(&iter);
+	if (hook) {
+		bridge_channel_suspend(bridge_channel);
+		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+		do {
+			hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+			ao2_ref(hook, -1);
+		} while ((hook = ao2_iterator_next(&iter)));
+		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+		bridge_channel_unsuspend(bridge_channel);
+	}
+	ao2_iterator_destroy(&iter);
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel leave event.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is leaving.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_leave(struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_bridge_features *features = bridge_channel->features;
+	struct ast_bridge_hook *hook;
+	struct ao2_iterator iter;
+
+	/* Run any leave hooks. */
+	iter = ao2_iterator_init(features->leave_hooks, AO2_ITERATOR_UNLINK);
+	hook = ao2_iterator_next(&iter);
+	if (hook) {
+		bridge_channel_suspend(bridge_channel);
+		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+		do {
+			hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+			ao2_ref(hook, -1);
+		} while ((hook = ao2_iterator_next(&iter)));
+		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+		bridge_channel_unsuspend(bridge_channel);
+	}
+	ao2_iterator_destroy(&iter);
+}
+
+/*! \brief Join a channel to a bridge and handle anything the bridge may want us to do */
+static void bridge_channel_join(struct ast_bridge_channel *bridge_channel)
+{
+	ast_format_copy(&bridge_channel->read_format, ast_channel_readformat(bridge_channel->chan));
+	ast_format_copy(&bridge_channel->write_format, ast_channel_writeformat(bridge_channel->chan));
+
+	ast_debug(1, "Bridge %s: %p(%s) is joining\n",
+		bridge_channel->bridge->uniqueid,
+		bridge_channel, ast_channel_name(bridge_channel->chan));
+
+	/*
+	 * Get "in the bridge" before pushing the channel for any
+	 * masquerades on the channel to happen before bridging.
+	 */
+	ast_channel_lock(bridge_channel->chan);
+	ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge);
+	ast_channel_unlock(bridge_channel->chan);
+
+	/* Add the jitterbuffer if the channel requires it */
+	ast_jb_enable_for_channel(bridge_channel->chan);
+
+	/*
+	 * Directly locking the bridge is safe here because nobody else
+	 * knows about this bridge_channel yet.
+	 */
+	ast_bridge_lock(bridge_channel->bridge);
+
+	if (!bridge_channel->bridge->callid) {
+		bridge_channel->bridge->callid = ast_read_threadstorage_callid();
+	}
+
+	if (bridge_channel_push(bridge_channel)) {
+		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+	}
+	bridge_reconfigured(bridge_channel->bridge);
+
+	if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+		/*
+		 * Indicate a source change since this channel is entering the
+		 * bridge system only if the bridge technology is not MULTIMIX
+		 * capable.  The MULTIMIX technology has already done it.
+		 */
+		if (!(bridge_channel->bridge->technology->capabilities
+			& AST_BRIDGE_CAPABILITY_MULTIMIX)) {
+			ast_indicate(bridge_channel->chan, AST_CONTROL_SRCCHANGE);
+		}
+
+		ast_bridge_unlock(bridge_channel->bridge);
+		bridge_channel_handle_join(bridge_channel);
+		while (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+			/* Wait for something to do. */
+			bridge_channel_wait(bridge_channel);
+		}
+		bridge_channel_handle_leave(bridge_channel);
+		ast_bridge_channel_lock_bridge(bridge_channel);
+	}
+
+	bridge_channel_pull(bridge_channel);
+	bridge_reconfigured(bridge_channel->bridge);
+
+	ast_bridge_unlock(bridge_channel->bridge);
+
+	/* Indicate a source change since this channel is leaving the bridge system. */
+	ast_indicate(bridge_channel->chan, AST_CONTROL_SRCCHANGE);
+
+/* BUGBUG Revisit in regards to moving channels between bridges and local channel optimization. */
+/* BUGBUG This is where outgoing HOLD/UNHOLD memory should write UNHOLD to channel. */
+	/* Complete any partial DTMF digit before exiting the bridge. */
+	if (ast_channel_sending_dtmf_digit(bridge_channel->chan)) {
+		ast_bridge_end_dtmf(bridge_channel->chan,
+			ast_channel_sending_dtmf_digit(bridge_channel->chan),
+			ast_channel_sending_dtmf_tv(bridge_channel->chan), "bridge end");
+	}
+
+	/*
+	 * Wait for any dual redirect to complete.
+	 *
+	 * Must be done while "still in the bridge" for ast_async_goto()
+	 * to work right.
+	 */
+	while (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT)) {
+		sched_yield();
+	}
+	ast_channel_lock(bridge_channel->chan);
+	ast_channel_internal_bridge_set(bridge_channel->chan, NULL);
+	ast_channel_unlock(bridge_channel->chan);
+
+	ast_bridge_channel_restore_formats(bridge_channel);
+}
+
+/*!
+ * \internal
+ * \brief Close a pipe.
+ * \since 12.0.0
+ *
+ * \param my_pipe What to close.
+ *
+ * \return Nothing
+ */
+static void pipe_close(int *my_pipe)
+{
+	if (my_pipe[0] > -1) {
+		close(my_pipe[0]);
+		my_pipe[0] = -1;
+	}
+	if (my_pipe[1] > -1) {
+		close(my_pipe[1]);
+		my_pipe[1] = -1;
+	}
+}
+
+/*!
+ * \internal
+ * \brief Initialize a pipe as non-blocking.
+ * \since 12.0.0
+ *
+ * \param my_pipe What to initialize.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int pipe_init_nonblock(int *my_pipe)
+{
+	int flags;
+
+	my_pipe[0] = -1;
+	my_pipe[1] = -1;
+	if (pipe(my_pipe)) {
+		ast_log(LOG_WARNING, "Can't create pipe! Try increasing max file descriptors with ulimit -n\n");
+		return -1;
+	}
+	flags = fcntl(my_pipe[0], F_GETFL);
+	if (fcntl(my_pipe[0], F_SETFL, flags | O_NONBLOCK) < 0) {
+		ast_log(LOG_WARNING, "Unable to set read pipe nonblocking! (%d: %s)\n",
+			errno, strerror(errno));
+		return -1;
+	}
+	flags = fcntl(my_pipe[1], F_GETFL);
+	if (fcntl(my_pipe[1], F_SETFL, flags | O_NONBLOCK) < 0) {
+		ast_log(LOG_WARNING, "Unable to set write pipe nonblocking! (%d: %s)\n",
+			errno, strerror(errno));
+		return -1;
+	}
+	return 0;
+}
+
+/* Destroy elements of the bridge channel structure and the bridge channel structure itself */
+static void bridge_channel_destroy(void *obj)
+{
+	struct ast_bridge_channel *bridge_channel = obj;
+	struct ast_frame *fr;
+
+	if (bridge_channel->callid) {
+		bridge_channel->callid = ast_callid_unref(bridge_channel->callid);
+	}
+
+	if (bridge_channel->bridge) {
+		ao2_ref(bridge_channel->bridge, -1);
+		bridge_channel->bridge = NULL;
+	}
+
+	/* Flush any unhandled wr_queue frames. */
+	while ((fr = AST_LIST_REMOVE_HEAD(&bridge_channel->wr_queue, frame_list))) {
+		ast_frfree(fr);
+	}
+	pipe_close(bridge_channel->alert_pipe);
+
+	ast_cond_destroy(&bridge_channel->cond);
+}
+
+static struct ast_bridge_channel *bridge_channel_alloc(struct ast_bridge *bridge)
+{
+	struct ast_bridge_channel *bridge_channel;
+
+	bridge_channel = ao2_alloc(sizeof(struct ast_bridge_channel), bridge_channel_destroy);
+	if (!bridge_channel) {
+		return NULL;
+	}
+	ast_cond_init(&bridge_channel->cond, NULL);
+	if (pipe_init_nonblock(bridge_channel->alert_pipe)) {
+		ao2_ref(bridge_channel, -1);
+		return NULL;
+	}
+	if (bridge) {
+		bridge_channel->bridge = bridge;
+		ao2_ref(bridge_channel->bridge, +1);
+	}
+
+	return bridge_channel;
+}
+
+struct after_bridge_cb_ds {
+	/*! Desired callback function. */
+	ast_after_bridge_cb callback;
+	/*! After bridge callback will not be called and destroy any resources data may contain. */
+	ast_after_bridge_cb_failed failed;
+	/*! Extra data to pass to the callback. */
+	void *data;
+};
+
+/*!
+ * \internal
+ * \brief Destroy the after bridge callback datastore.
+ * \since 12.0.0
+ *
+ * \param data After bridge callback data to destroy.
+ *
+ * \return Nothing
+ */
+static void after_bridge_cb_destroy(void *data)
+{
+	struct after_bridge_cb_ds *after_bridge = data;
+
+	if (after_bridge->failed) {
+		after_bridge->failed(AST_AFTER_BRIDGE_CB_REASON_DESTROY, after_bridge->data);
+		after_bridge->failed = NULL;
+	}
+}
+
+/*!
+ * \internal
+ * \brief Fixup the after bridge callback datastore.
+ * \since 12.0.0
+ *
+ * \param data After bridge callback data to fixup.
+ * \param old_chan The datastore is moving from this channel.
+ * \param new_chan The datastore is moving to this channel.
+ *
+ * \return Nothing
+ */
+static void after_bridge_cb_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+{
+	/* There can be only one.  Discard any already on the new channel. */
+	ast_after_bridge_callback_discard(new_chan, AST_AFTER_BRIDGE_CB_REASON_MASQUERADE);
+}
+
+static const struct ast_datastore_info after_bridge_cb_info = {
+	.type = "after-bridge-cb",
+	.destroy = after_bridge_cb_destroy,
+	.chan_fixup = after_bridge_cb_fixup,
+};
+
+/*!
+ * \internal
+ * \brief Remove channel after the bridge callback and return it.
+ * \since 12.0.0
+ *
+ * \param chan Channel to remove after bridge callback.
+ *
+ * \retval datastore on success.
+ * \retval NULL on error or not found.
+ */
+static struct ast_datastore *after_bridge_cb_remove(struct ast_channel *chan)
+{
+	struct ast_datastore *datastore;
+
+	ast_channel_lock(chan);
+	datastore = ast_channel_datastore_find(chan, &after_bridge_cb_info, NULL);
+	if (datastore && ast_channel_datastore_remove(chan, datastore)) {
+		datastore = NULL;
+	}
+	ast_channel_unlock(chan);
+
+	return datastore;
+}
+
+void ast_after_bridge_callback_discard(struct ast_channel *chan, enum ast_after_bridge_cb_reason reason)
+{
+	struct ast_datastore *datastore;
+
+	datastore = after_bridge_cb_remove(chan);
+	if (datastore) {
+		struct after_bridge_cb_ds *after_bridge = datastore->data;
+
+		if (after_bridge && after_bridge->failed) {
+			after_bridge->failed(reason, after_bridge->data);
+			after_bridge->failed = NULL;
+		}
+		ast_datastore_free(datastore);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Run any after bridge callback if possible.
+ * \since 12.0.0
+ *
+ * \param chan Channel to run after bridge callback.
+ *
+ * \return Nothing
+ */
+static void after_bridge_callback_run(struct ast_channel *chan)
+{
+	struct ast_datastore *datastore;
+	struct after_bridge_cb_ds *after_bridge;
+
+	if (ast_check_hangup(chan)) {
+		return;
+	}
+
+	/* Get after bridge goto datastore. */
+	datastore = after_bridge_cb_remove(chan);
+	if (!datastore) {
+		return;
+	}
+
+	after_bridge = datastore->data;
+	if (after_bridge) {
+		after_bridge->failed = NULL;
+		after_bridge->callback(chan, after_bridge->data);
+	}
+
+	/* Discard after bridge callback datastore. */
+	ast_datastore_free(datastore);
+}
+
+int ast_after_bridge_callback_set(struct ast_channel *chan, ast_after_bridge_cb callback, ast_after_bridge_cb_failed failed, void *data)
+{
+	struct ast_datastore *datastore;
+	struct after_bridge_cb_ds *after_bridge;
+
+	/* Sanity checks. */
+	ast_assert(chan != NULL);
+	if (!chan || !callback) {
+		return -1;
+	}
+
+	/* Create a new datastore. */
+	datastore = ast_datastore_alloc(&after_bridge_cb_info, NULL);
+	if (!datastore) {
+		return -1;
+	}
+	after_bridge = ast_calloc(1, sizeof(*after_bridge));
+	if (!after_bridge) {
+		ast_datastore_free(datastore);
+		return -1;
+	}
+
+	/* Initialize it. */
+	after_bridge->callback = callback;
+	after_bridge->failed = failed;
+	after_bridge->data = data;
+	datastore->data = after_bridge;
+
+	/* Put it on the channel replacing any existing one. */
+	ast_channel_lock(chan);
+	ast_after_bridge_callback_discard(chan, AST_AFTER_BRIDGE_CB_REASON_REPLACED);
+	ast_channel_datastore_add(chan, datastore);
+	ast_channel_unlock(chan);
+
+	return 0;
+}
+
+struct after_bridge_goto_ds {
+	/*! Goto string that can be parsed by ast_parseable_goto(). */
+	const char *parseable_goto;
+	/*! Specific goto context or default context for parseable_goto. */
+	const char *context;
+	/*! Specific goto exten or default exten for parseable_goto. */
+	const char *exten;
+	/*! Specific goto priority or default priority for parseable_goto. */
+	int priority;
+	/*! TRUE if the peer should run the h exten. */
+	unsigned int run_h_exten:1;
+	/*! Specific goto location */
+	unsigned int specific:1;
+};
+
+/*!
+ * \internal
+ * \brief Destroy the after bridge goto datastore.
+ * \since 12.0.0
+ *
+ * \param data After bridge goto data to destroy.
+ *
+ * \return Nothing
+ */
+static void after_bridge_goto_destroy(void *data)
+{
+	struct after_bridge_goto_ds *after_bridge = data;
+
+	ast_free((char *) after_bridge->parseable_goto);
+	ast_free((char *) after_bridge->context);
+	ast_free((char *) after_bridge->exten);
+}
+
+/*!
+ * \internal
+ * \brief Fixup the after bridge goto datastore.
+ * \since 12.0.0
+ *
+ * \param data After bridge goto data to fixup.
+ * \param old_chan The datastore is moving from this channel.
+ * \param new_chan The datastore is moving to this channel.
+ *
+ * \return Nothing
+ */
+static void after_bridge_goto_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+{
+	/* There can be only one.  Discard any already on the new channel. */
+	ast_after_bridge_goto_discard(new_chan);
+}
+
+static const struct ast_datastore_info after_bridge_goto_info = {
+	.type = "after-bridge-goto",
+	.destroy = after_bridge_goto_destroy,
+	.chan_fixup = after_bridge_goto_fixup,
+};
+
+/*!
+ * \internal
+ * \brief Remove channel goto location after the bridge and return it.
+ * \since 12.0.0
+ *
+ * \param chan Channel to remove after bridge goto location.
+ *
+ * \retval datastore on success.
+ * \retval NULL on error or not found.
+ */
+static struct ast_datastore *after_bridge_goto_remove(struct ast_channel *chan)
+{
+	struct ast_datastore *datastore;
+
+	ast_channel_lock(chan);
+	datastore = ast_channel_datastore_find(chan, &after_bridge_goto_info, NULL);
+	if (datastore && ast_channel_datastore_remove(chan, datastore)) {
+		datastore = NULL;
+	}
+	ast_channel_unlock(chan);
+
+	return datastore;
+}
+
+void ast_after_bridge_goto_discard(struct ast_channel *chan)
+{
+	struct ast_datastore *datastore;
+
+	datastore = after_bridge_goto_remove(chan);
+	if (datastore) {
+		ast_datastore_free(datastore);
+	}
+}
+
+int ast_after_bridge_goto_setup(struct ast_channel *chan)
+{
+	struct ast_datastore *datastore;
+	struct after_bridge_goto_ds *after_bridge;
+	int goto_failed = -1;
+
+	/* Determine if we are going to setup a dialplan location and where. */
+	if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
+		/* An async goto has already setup a location. */
+		ast_channel_clear_softhangup(chan, AST_SOFTHANGUP_ASYNCGOTO);
+		if (!ast_check_hangup(chan)) {
+			goto_failed = 0;
+		}
+		return goto_failed;
+	}
+
+	/* Get after bridge goto datastore. */
+	datastore = after_bridge_goto_remove(chan);
+	if (!datastore) {
+		return goto_failed;
+	}
+
+	after_bridge = datastore->data;
+	if (after_bridge->run_h_exten) {
+		if (ast_exists_extension(chan, after_bridge->context, "h", 1,
+			S_COR(ast_channel_caller(chan)->id.number.valid,
+				ast_channel_caller(chan)->id.number.str, NULL))) {
+			ast_debug(1, "Running after bridge goto h exten %s,h,1\n",
+				ast_channel_context(chan));
+			ast_pbx_h_exten_run(chan, after_bridge->context);
+		}
+	} else if (!ast_check_hangup(chan)) {
+		if (after_bridge->specific) {
+			goto_failed = ast_explicit_goto(chan, after_bridge->context,
+				after_bridge->exten, after_bridge->priority);
+		} else if (!ast_strlen_zero(after_bridge->parseable_goto)) {
+			char *context;
+			char *exten;
+			int priority;
+
+			/* Option F(x) for Bridge(), Dial(), and Queue() */
+
+			/* Save current dialplan location in case of failure. */
+			context = ast_strdupa(ast_channel_context(chan));
+			exten = ast_strdupa(ast_channel_exten(chan));
+			priority = ast_channel_priority(chan);
+
+			/* Set current dialplan position to default dialplan position */
+			ast_explicit_goto(chan, after_bridge->context, after_bridge->exten,
+				after_bridge->priority);
+
+			/* Then perform the goto */
+			goto_failed = ast_parseable_goto(chan, after_bridge->parseable_goto);
+			if (goto_failed) {
+				/* Restore original dialplan location. */
+				ast_channel_context_set(chan, context);
+				ast_channel_exten_set(chan, exten);
+				ast_channel_priority_set(chan, priority);
+			}
+		} else {
+			/* Option F() for Bridge(), Dial(), and Queue() */
+			goto_failed = ast_goto_if_exists(chan, after_bridge->context,
+				after_bridge->exten, after_bridge->priority + 1);
+		}
+		if (!goto_failed) {
+			ast_debug(1, "Setup after bridge goto location to %s,%s,%d.\n",
+				ast_channel_context(chan),
+				ast_channel_exten(chan),
+				ast_channel_priority(chan));
+		}
+	}
+
+	/* Discard after bridge goto datastore. */
+	ast_datastore_free(datastore);
+
+	return goto_failed;
+}
+
+void ast_after_bridge_goto_run(struct ast_channel *chan)
+{
+	int goto_failed;
+
+	goto_failed = ast_after_bridge_goto_setup(chan);
+	if (goto_failed || ast_pbx_run(chan)) {
+		ast_hangup(chan);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Set after bridge goto location of channel.
+ * \since 12.0.0
+ *
+ * \param chan Channel to setup after bridge goto location.
+ * \param run_h_exten TRUE if the h exten should be run.
+ * \param specific TRUE if the context/exten/priority is exactly specified.
+ * \param context Context to goto after bridge.
+ * \param exten Exten to goto after bridge. (Could be NULL if run_h_exten)
+ * \param priority Priority to goto after bridge.
+ * \param parseable_goto User specified goto string. (Could be NULL)
+ *
+ * \details Add a channel datastore to setup the goto location
+ * when the channel leaves the bridge and run a PBX from there.
+ *
+ * If run_h_exten then execute the h exten found in the given context.
+ * Else if specific then goto the given context/exten/priority.
+ * Else if parseable_goto then use the given context/exten/priority
+ *   as the relative position for the parseable_goto.
+ * Else goto the given context/exten/priority+1.
+ *
+ * \return Nothing
+ */
+static void __after_bridge_set_goto(struct ast_channel *chan, int run_h_exten, int specific, const char *context, const char *exten, int priority, const char *parseable_goto)
+{
+	struct ast_datastore *datastore;
+	struct after_bridge_goto_ds *after_bridge;
+
+	/* Sanity checks. */
+	ast_assert(chan != NULL);
+	if (!chan) {
+		return;
+	}
+	if (run_h_exten) {
+		ast_assert(run_h_exten && context);
+		if (!context) {
+			return;
+		}
+	} else {
+		ast_assert(context && exten && 0 < priority);
+		if (!context || !exten || priority < 1) {
+			return;
+		}
+	}
+
+	/* Create a new datastore. */
+	datastore = ast_datastore_alloc(&after_bridge_goto_info, NULL);
+	if (!datastore) {
+		return;
+	}
+	after_bridge = ast_calloc(1, sizeof(*after_bridge));
+	if (!after_bridge) {
+		ast_datastore_free(datastore);
+		return;
+	}
+
+	/* Initialize it. */
+	after_bridge->parseable_goto = ast_strdup(parseable_goto);
+	after_bridge->context = ast_strdup(context);
+	after_bridge->exten = ast_strdup(exten);
+	after_bridge->priority = priority;
+	after_bridge->run_h_exten = run_h_exten ? 1 : 0;
+	after_bridge->specific = specific ? 1 : 0;
+	datastore->data = after_bridge;
+	if ((parseable_goto && !after_bridge->parseable_goto)
+		|| (context && !after_bridge->context)
+		|| (exten && !after_bridge->exten)) {
+		ast_datastore_free(datastore);
+		return;
+	}
+
+	/* Put it on the channel replacing any existing one. */
+	ast_channel_lock(chan);
+	ast_after_bridge_goto_discard(chan);
+	ast_channel_datastore_add(chan, datastore);
+	ast_channel_unlock(chan);
+}
+
+void ast_after_bridge_set_goto(struct ast_channel *chan, const char *context, const char *exten, int priority)
+{
+	__after_bridge_set_goto(chan, 0, 1, context, exten, priority, NULL);
+}
+
+void ast_after_bridge_set_h(struct ast_channel *chan, const char *context)
+{
+	__after_bridge_set_goto(chan, 1, 0, context, NULL, 1, NULL);
+}
+
+void ast_after_bridge_set_go_on(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *parseable_goto)
+{
+	char *p_goto;
+
+	if (!ast_strlen_zero(parseable_goto)) {
+		p_goto = ast_strdupa(parseable_goto);
+		ast_replace_subargument_delimiter(p_goto);
+	} else {
+		p_goto = NULL;
+	}
+	__after_bridge_set_goto(chan, 0, 0, context, exten, priority, p_goto);
+}
+
+void ast_bridge_notify_masquerade(struct ast_channel *chan)
+{
+	struct ast_bridge_channel *bridge_channel;
+	struct ast_bridge *bridge;
+
+	/* Safely get the bridge_channel pointer for the chan. */
+	ast_channel_lock(chan);
+	bridge_channel = ast_channel_get_bridge_channel(chan);
+	ast_channel_unlock(chan);
+	if (!bridge_channel) {
+		/* Not in a bridge */
+		return;
+	}
+
+	ast_bridge_channel_lock_bridge(bridge_channel);
+	bridge = bridge_channel->bridge;
+	if (bridge_channel == find_bridge_channel(bridge, chan)) {
+/* BUGBUG this needs more work.  The channels need to be made compatible again if the formats change. The bridge_channel thread needs to monitor for this case. */
+		/* The channel we want to notify is still in a bridge. */
+		bridge->v_table->notify_masquerade(bridge, bridge_channel);
+		bridge_reconfigured(bridge);
+	}
+	ast_bridge_unlock(bridge);
+	ao2_ref(bridge_channel, -1);
+}
+
+/*
+ * BUGBUG make ast_bridge_join() require features to be allocated just like ast_bridge_impart() and not expect the struct back.
+ *
+ * This change is really going to break ConfBridge.  All other
+ * users are easily changed.  However, it is needed so the
+ * bridging code can manipulate features on all channels
+ * consistently no matter how they joined.
+ *
+ * Need to update the features parameter doxygen when this
+ * change is made to be like ast_bridge_impart().
+ */
+enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge,
+	struct ast_channel *chan,
+	struct ast_channel *swap,
+	struct ast_bridge_features *features,
+	struct ast_bridge_tech_optimizations *tech_args,
+	int pass_reference)
+{
+	struct ast_bridge_channel *bridge_channel;
+	enum ast_bridge_channel_state state;
+
+	bridge_channel = bridge_channel_alloc(bridge);
+	if (pass_reference) {
+		ao2_ref(bridge, -1);
+	}
+	if (!bridge_channel) {
+		state = AST_BRIDGE_CHANNEL_STATE_HANGUP;
+		goto join_exit;
+	}
+/* BUGBUG features cannot be NULL when passed in. When it is changed to allocated we can do like ast_bridge_impart() and allocate one. */
+	ast_assert(features != NULL);
+	if (!features) {
+		ao2_ref(bridge_channel, -1);
+		state = AST_BRIDGE_CHANNEL_STATE_HANGUP;
+		goto join_exit;
+	}
+	if (tech_args) {
+		bridge_channel->tech_args = *tech_args;
+	}
+
+	/* Initialize various other elements of the bridge channel structure that we can't do above */
+	ast_channel_lock(chan);
+	ast_channel_internal_bridge_channel_set(chan, bridge_channel);
+	ast_channel_unlock(chan);
+	bridge_channel->thread = pthread_self();
+	bridge_channel->chan = chan;
+	bridge_channel->swap = swap;
+	bridge_channel->features = features;
+
+	bridge_channel_join(bridge_channel);
+	state = bridge_channel->state;
+
+	/* Cleanup all the data in the bridge channel after it leaves the bridge. */
+	ast_channel_lock(chan);
+	ast_channel_internal_bridge_channel_set(chan, NULL);
+	ast_channel_unlock(chan);
+	bridge_channel->chan = NULL;
+	bridge_channel->swap = NULL;
+	bridge_channel->features = NULL;
+
+	ao2_ref(bridge_channel, -1);
+
+join_exit:;
+/* BUGBUG this is going to cause problems for DTMF atxfer attended bridge between B & C.  Maybe an ast_bridge_join_internal() that does not do the after bridge goto for this case. */
+	after_bridge_callback_run(chan);
+	if (!(ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO)
+		&& !ast_after_bridge_goto_setup(chan)) {
+		/* Claim the after bridge goto is an async goto destination. */
+		ast_channel_lock(chan);
+		ast_softhangup_nolock(chan, AST_SOFTHANGUP_ASYNCGOTO);
+		ast_channel_unlock(chan);
+	}
+	return state;
+}
+
+/*! \brief Thread responsible for imparted bridged channels to be departed */
+static void *bridge_channel_depart_thread(void *data)
+{
+	struct ast_bridge_channel *bridge_channel = data;
+
+	if (bridge_channel->callid) {
+		ast_callid_threadassoc_add(bridge_channel->callid);
+	}
+
+	bridge_channel_join(bridge_channel);
+
+	/* cleanup */
+	bridge_channel->swap = NULL;
+	ast_bridge_features_destroy(bridge_channel->features);
+	bridge_channel->features = NULL;
+
+	ast_after_bridge_callback_discard(bridge_channel->chan, AST_AFTER_BRIDGE_CB_REASON_DEPART);
+	ast_after_bridge_goto_discard(bridge_channel->chan);
+
+	return NULL;
+}
+
+/*! \brief Thread responsible for independent imparted bridged channels */
+static void *bridge_channel_ind_thread(void *data)
+{
+	struct ast_bridge_channel *bridge_channel = data;
+	struct ast_channel *chan;
+
+	if (bridge_channel->callid) {
+		ast_callid_threadassoc_add(bridge_channel->callid);
+	}
+
+	bridge_channel_join(bridge_channel);
+	chan = bridge_channel->chan;
+
+	/* cleanup */
+	ast_channel_lock(chan);
+	ast_channel_internal_bridge_channel_set(chan, NULL);
+	ast_channel_unlock(chan);
+	bridge_channel->chan = NULL;
+	bridge_channel->swap = NULL;
+	ast_bridge_features_destroy(bridge_channel->features);
+	bridge_channel->features = NULL;
+
+	ao2_ref(bridge_channel, -1);
+
+	after_bridge_callback_run(chan);
+	ast_after_bridge_goto_run(chan);
+	return NULL;
+}
+
+int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, int independent)
+{
+	int res;
+	struct ast_bridge_channel *bridge_channel;
+
+	/* Supply an empty features structure if the caller did not. */
+	if (!features) {
+		features = ast_bridge_features_new();
+		if (!features) {
+			return -1;
+		}
+	}
+
+	/* Try to allocate a structure for the bridge channel */
+	bridge_channel = bridge_channel_alloc(bridge);
+	if (!bridge_channel) {
+		ast_bridge_features_destroy(features);
+		return -1;
+	}
+
+	/* Setup various parameters */
+	ast_channel_lock(chan);
+	ast_channel_internal_bridge_channel_set(chan, bridge_channel);
+	ast_channel_unlock(chan);
+	bridge_channel->chan = chan;
+	bridge_channel->swap = swap;
+	bridge_channel->features = features;
+	bridge_channel->depart_wait = independent ? 0 : 1;
+	bridge_channel->callid = ast_read_threadstorage_callid();
+
+	/* Actually create the thread that will handle the channel */
+	if (independent) {
+		/* Independently imparted channels cannot have a PBX. */
+		ast_assert(!ast_channel_pbx(chan));
+
+		res = ast_pthread_create_detached(&bridge_channel->thread, NULL,
+			bridge_channel_ind_thread, bridge_channel);
+	} else {
+		/* Imparted channels to be departed should not have a PBX either. */
+		ast_assert(!ast_channel_pbx(chan));
+
+		res = ast_pthread_create(&bridge_channel->thread, NULL,
+			bridge_channel_depart_thread, bridge_channel);
+	}
+
+	if (res) {
+		/* cleanup */
+		ast_channel_lock(chan);
+		ast_channel_internal_bridge_channel_set(chan, NULL);
+		ast_channel_unlock(chan);
+		bridge_channel->chan = NULL;
+		bridge_channel->swap = NULL;
+		ast_bridge_features_destroy(bridge_channel->features);
+		bridge_channel->features = NULL;
+
+		ao2_ref(bridge_channel, -1);
+		return -1;
+	}
+
+	return 0;
+}
+
+int ast_bridge_depart(struct ast_channel *chan)
+{
+	struct ast_bridge_channel *bridge_channel;
+	int departable;
+
+	ast_channel_lock(chan);
+	bridge_channel = ast_channel_internal_bridge_channel(chan);
+	departable = bridge_channel && bridge_channel->depart_wait;
+	ast_channel_unlock(chan);
+	if (!departable) {
+		ast_log(LOG_ERROR, "Channel %s cannot be departed.\n",
+			ast_channel_name(chan));
+		/*
+		 * Should never happen.  It likely means that
+		 * ast_bridge_depart() is called by two threads for the same
+		 * channel, the channel was never imparted to be departed, or it
+		 * has already been departed.
+		 */
+		ast_assert(0);
+		return -1;
+	}
+
+	/*
+	 * We are claiming the reference held by the depart bridge
+	 * channel thread.
+	 */
+
+	ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+
+	/* Wait for the depart thread to die */
+	ast_debug(1, "Waiting for %p(%s) bridge thread to die.\n",
+		bridge_channel, ast_channel_name(bridge_channel->chan));
+	pthread_join(bridge_channel->thread, NULL);
+
+	ast_channel_lock(chan);
+	ast_channel_internal_bridge_channel_set(chan, NULL);
+	ast_channel_unlock(chan);
+
+	/* We can get rid of the bridge_channel after the depart thread has died. */
+	ao2_ref(bridge_channel, -1);
+	return 0;
+}
+
+int ast_bridge_remove(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+	struct ast_bridge_channel *bridge_channel;
+
+	ast_bridge_lock(bridge);
+
+	/* Try to find the channel that we want to remove */
+	if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
+		ast_bridge_unlock(bridge);
+		return -1;
+	}
+
+	ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+
+	ast_bridge_unlock(bridge);
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Point the bridge_channel to a new bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel What is to point to a new bridge.
+ * \param new_bridge Where the bridge channel should point.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_change_bridge(struct ast_bridge_channel *bridge_channel, struct ast_bridge *new_bridge)
+{
+	struct ast_bridge *old_bridge;
+
+	ao2_ref(new_bridge, +1);
+	ast_bridge_channel_lock(bridge_channel);
+	ast_channel_lock(bridge_channel->chan);
+	old_bridge = bridge_channel->bridge;
+	bridge_channel->bridge = new_bridge;
+	ast_channel_internal_bridge_set(bridge_channel->chan, new_bridge);
+	ast_channel_unlock(bridge_channel->chan);
+	ast_bridge_channel_unlock(bridge_channel);
+	ao2_ref(old_bridge, -1);
+}
+
+/*!
+ * \internal
+ * \brief Do the merge of two bridges.
+ * \since 12.0.0
+ *
+ * \param dst_bridge Destination bridge of merge.
+ * \param src_bridge Source bridge of merge.
+ * \param kick_me Array of channels to kick from the bridges.
+ * \param num_kick Number of channels in the kick_me array.
+ *
+ * \return Nothing
+ *
+ * \note The two bridges are assumed already locked.
+ *
+ * This moves the channels in src_bridge into the bridge pointed
+ * to by dst_bridge.
+ */
+static void bridge_merge_do(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_bridge_channel **kick_me, unsigned int num_kick)
+{
+	struct ast_bridge_channel *bridge_channel;
+	unsigned int idx;
+
+	ast_debug(1, "Merging bridge %s into bridge %s\n",
+		src_bridge->uniqueid, dst_bridge->uniqueid);
+
+	ast_bridge_publish_merge(dst_bridge, src_bridge);
+
+	/*
+	 * Move channels from src_bridge over to dst_bridge.
+	 *
+	 * We must use AST_LIST_TRAVERSE_SAFE_BEGIN() because
+	 * bridge_channel_pull() alters the list we are traversing.
+	 */
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&src_bridge->channels, bridge_channel, entry) {
+		if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+			/*
+			 * The channel is already leaving let it leave normally because
+			 * pulling it may delete hooks that should run for this channel.
+			 */
+			continue;
+		}
+		if (ast_test_flag(&bridge_channel->features->feature_flags,
+			AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE)) {
+			continue;
+		}
+
+		if (kick_me) {
+			for (idx = 0; idx < num_kick; ++idx) {
+				if (bridge_channel == kick_me[idx]) {
+					ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+					break;
+				}
+			}
+		}
+		bridge_channel_pull(bridge_channel);
+		if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+			/*
+			 * The channel died as a result of being pulled or it was
+			 * kicked.  Leave it pointing to the original bridge.
+			 */
+			continue;
+		}
+
+		/* Point to new bridge.*/
+		bridge_channel_change_bridge(bridge_channel, dst_bridge);
+
+		if (bridge_channel_push(bridge_channel)) {
+			ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+		}
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+
+	if (kick_me) {
+		/*
+		 * Now we can kick any channels in the dst_bridge without
+		 * potentially dissolving the bridge.
+		 */
+		for (idx = 0; idx < num_kick; ++idx) {
+			bridge_channel = kick_me[idx];
+			ast_bridge_channel_lock(bridge_channel);
+			if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+				ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+				bridge_channel_pull(bridge_channel);
+			}
+			ast_bridge_channel_unlock(bridge_channel);
+		}
+	}
+
+	bridge_reconfigured(dst_bridge);
+	bridge_reconfigured(src_bridge);
+
+	ast_debug(1, "Merged bridge %s into bridge %s\n",
+		src_bridge->uniqueid, dst_bridge->uniqueid);
+}
+
+struct merge_direction {
+	/*! Destination merge bridge. */
+	struct ast_bridge *dest;
+	/*! Source merge bridge. */
+	struct ast_bridge *src;
+};
+
+/*!
+ * \internal
+ * \brief Determine which bridge should merge into the other.
+ * \since 12.0.0
+ *
+ * \param bridge1 A bridge for merging
+ * \param bridge2 A bridge for merging
+ *
+ * \note The two bridges are assumed already locked.
+ *
+ * \return Which bridge merges into which or NULL bridges if cannot merge.
+ */
+static struct merge_direction bridge_merge_determine_direction(struct ast_bridge *bridge1, struct ast_bridge *bridge2)
+{
+	struct merge_direction merge = { NULL, NULL };
+	int bridge1_priority;
+	int bridge2_priority;
+
+	if (!ast_test_flag(&bridge1->feature_flags,
+			AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)
+		&& !ast_test_flag(&bridge2->feature_flags,
+			AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) {
+		/*
+		 * Can merge either way.  Merge to the higher priority merge
+		 * bridge.  Otherwise merge to the larger bridge.
+		 */
+		bridge1_priority = bridge1->v_table->get_merge_priority(bridge1);
+		bridge2_priority = bridge2->v_table->get_merge_priority(bridge2);
+		if (bridge2_priority < bridge1_priority) {
+			merge.dest = bridge1;
+			merge.src = bridge2;
+		} else if (bridge1_priority < bridge2_priority) {
+			merge.dest = bridge2;
+			merge.src = bridge1;
+		} else {
+			/* Merge to the larger bridge. */
+			if (bridge2->num_channels <= bridge1->num_channels) {
+				merge.dest = bridge1;
+				merge.src = bridge2;
+			} else {
+				merge.dest = bridge2;
+				merge.src = bridge1;
+			}
+		}
+	} else if (!ast_test_flag(&bridge1->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_TO)
+		&& !ast_test_flag(&bridge2->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) {
+		/* Can merge only one way. */
+		merge.dest = bridge1;
+		merge.src = bridge2;
+	} else if (!ast_test_flag(&bridge2->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_TO)
+		&& !ast_test_flag(&bridge1->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) {
+		/* Can merge only one way. */
+		merge.dest = bridge2;
+		merge.src = bridge1;
+	}
+
+	return merge;
+}
+
+/*!
+ * \internal
+ * \brief Merge two bridges together
+ * \since 12.0.0
+ *
+ * \param dst_bridge Destination bridge of merge.
+ * \param src_bridge Source bridge of merge.
+ * \param merge_best_direction TRUE if don't care about which bridge merges into the other.
+ * \param kick_me Array of channels to kick from the bridges.
+ * \param num_kick Number of channels in the kick_me array.
+ *
+ * \note The dst_bridge and src_bridge are assumed already locked.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int bridge_merge_locked(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, int merge_best_direction, struct ast_channel **kick_me, unsigned int num_kick)
+{
+	struct merge_direction merge;
+	struct ast_bridge_channel **kick_them = NULL;
+
+	/* Sanity check. */
+	ast_assert(dst_bridge && src_bridge && dst_bridge != src_bridge && (!num_kick || kick_me));
+
+	if (dst_bridge->dissolved || src_bridge->dissolved) {
+		ast_debug(1, "Can't merge bridges %s and %s, at least one bridge is dissolved.\n",
+			src_bridge->uniqueid, dst_bridge->uniqueid);
+		return -1;
+	}
+	if (ast_test_flag(&dst_bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)
+		|| ast_test_flag(&src_bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)) {
+		ast_debug(1, "Can't merge bridges %s and %s, masquerade only.\n",
+			src_bridge->uniqueid, dst_bridge->uniqueid);
+		return -1;
+	}
+	if (dst_bridge->inhibit_merge || src_bridge->inhibit_merge) {
+		ast_debug(1, "Can't merge bridges %s and %s, merging temporarily inhibited.\n",
+			src_bridge->uniqueid, dst_bridge->uniqueid);
+		return -1;
+	}
+
+	if (merge_best_direction) {
+		merge = bridge_merge_determine_direction(dst_bridge, src_bridge);
+	} else {
+		merge.dest = dst_bridge;
+		merge.src = src_bridge;
+	}
+
+	if (!merge.dest
+		|| ast_test_flag(&merge.dest->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_TO)
+		|| ast_test_flag(&merge.src->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) {
+		ast_debug(1, "Can't merge bridges %s and %s, merging inhibited.\n",
+			src_bridge->uniqueid, dst_bridge->uniqueid);
+		return -1;
+	}
+	if (merge.src->num_channels < 2) {
+		/*
+		 * For a two party bridge, a channel may be temporarily removed
+		 * from the source bridge or the initial bridge members have not
+		 * joined yet.
+		 */
+		ast_debug(1, "Can't merge bridge %s into bridge %s, not enough channels in source bridge.\n",
+			merge.src->uniqueid, merge.dest->uniqueid);
+		return -1;
+	}
+	if (2 + num_kick < merge.dest->num_channels + merge.src->num_channels
+		&& !(merge.dest->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)
+		&& (!ast_test_flag(&merge.dest->feature_flags, AST_BRIDGE_FLAG_SMART)
+			|| !(merge.dest->allowed_capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX))) {
+		ast_debug(1, "Can't merge bridge %s into bridge %s, multimix is needed and it cannot be acquired.\n",
+			merge.src->uniqueid, merge.dest->uniqueid);
+		return -1;
+	}
+
+	if (num_kick) {
+		unsigned int num_to_kick = 0;
+		unsigned int idx;
+
+		kick_them = ast_alloca(num_kick * sizeof(*kick_them));
+		for (idx = 0; idx < num_kick; ++idx) {
+			kick_them[num_to_kick] = find_bridge_channel(merge.src, kick_me[idx]);
+			if (!kick_them[num_to_kick]) {
+				kick_them[num_to_kick] = find_bridge_channel(merge.dest, kick_me[idx]);
+			}
+			if (kick_them[num_to_kick]) {
+				++num_to_kick;
+			}
+		}
+
+		if (num_to_kick != num_kick) {
+			ast_debug(1, "Can't merge bridge %s into bridge %s, at least one kicked channel is not in either bridge.\n",
+				merge.src->uniqueid, merge.dest->uniqueid);
+			return -1;
+		}
+	}
+
+	bridge_merge_do(merge.dest, merge.src, kick_them, num_kick);
+	return 0;
+}
+
+int ast_bridge_merge(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, int merge_best_direction, struct ast_channel **kick_me, unsigned int num_kick)
+{
+	int res;
+
+	/* Sanity check. */
+	ast_assert(dst_bridge && src_bridge);
+
+	ast_bridge_lock_both(dst_bridge, src_bridge);
+	res = bridge_merge_locked(dst_bridge, src_bridge, merge_best_direction, kick_me, num_kick);
+	ast_bridge_unlock(src_bridge);
+	ast_bridge_unlock(dst_bridge);
+	return res;
+}
+
+/*!
+ * \internal
+ * \brief Move a bridge channel from one bridge to another.
+ * \since 12.0.0
+ *
+ * \param dst_bridge Destination bridge of bridge channel move.
+ * \param bridge_channel Channel moving from one bridge to another.
+ * \param attempt_recovery TRUE if failure attempts to push channel back into original bridge.
+ *
+ * \note The dst_bridge and bridge_channel->bridge are assumed already locked.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.
+ */
+static int bridge_move_do(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bridge_channel, int attempt_recovery)
+{
+	struct ast_bridge *orig_bridge;
+	int was_in_bridge;
+	int res = 0;
+
+/* BUGBUG need bridge move stasis event and a success/fail event. */
+	if (bridge_channel->swap) {
+		ast_debug(1, "Moving %p(%s) into bridge %s swapping with %s\n",
+			bridge_channel, ast_channel_name(bridge_channel->chan), dst_bridge->uniqueid,
+			ast_channel_name(bridge_channel->swap));
+	} else {
+		ast_debug(1, "Moving %p(%s) into bridge %s\n",
+			bridge_channel, ast_channel_name(bridge_channel->chan), dst_bridge->uniqueid);
+	}
+
+	orig_bridge = bridge_channel->bridge;
+	was_in_bridge = bridge_channel->in_bridge;
+
+	bridge_channel_pull(bridge_channel);
+	if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+		/*
+		 * The channel died as a result of being pulled.  Leave it
+		 * pointing to the original bridge.
+		 */
+		bridge_reconfigured(orig_bridge);
+		return -1;
+	}
+
+	/* Point to new bridge.*/
+	ao2_ref(orig_bridge, +1);/* Keep a ref in case the push fails. */
+	bridge_channel_change_bridge(bridge_channel, dst_bridge);
+
+	if (bridge_channel_push(bridge_channel)) {
+		/* Try to put the channel back into the original bridge. */
+		if (attempt_recovery && was_in_bridge) {
+			/* Point back to original bridge. */
+			bridge_channel_change_bridge(bridge_channel, orig_bridge);
+
+			if (bridge_channel_push(bridge_channel)) {
+				ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+			}
+		} else {
+			ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+		}
+		res = -1;
+	}
+
+	bridge_reconfigured(dst_bridge);
+	bridge_reconfigured(orig_bridge);
+	ao2_ref(orig_bridge, -1);
+	return res;
+}
+
+/*!
+ * \internal
+ * \brief Move a channel from one bridge to another.
+ * \since 12.0.0
+ *
+ * \param dst_bridge Destination bridge of bridge channel move.
+ * \param src_bridge Source bridge of bridge channel move.
+ * \param chan Channel to move.
+ * \param swap Channel to replace in dst_bridge.
+ * \param attempt_recovery TRUE if failure attempts to push channel back into original bridge.
+ *
+ * \note The dst_bridge and src_bridge are assumed already locked.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.
+ */
+static int bridge_move_locked(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_channel *chan, struct ast_channel *swap, int attempt_recovery)
+{
+	struct ast_bridge_channel *bridge_channel;
+
+	if (dst_bridge->dissolved || src_bridge->dissolved) {
+		ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, at least one bridge is dissolved.\n",
+			ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+		return -1;
+	}
+	if (ast_test_flag(&dst_bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)
+		|| ast_test_flag(&src_bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)) {
+		ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, masquerade only.\n",
+			ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+		return -1;
+	}
+	if (dst_bridge->inhibit_merge || src_bridge->inhibit_merge) {
+		ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, temporarily inhibited.\n",
+			ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+		return -1;
+	}
+
+	bridge_channel = find_bridge_channel(src_bridge, chan);
+	if (!bridge_channel) {
+		ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, channel not in bridge.\n",
+			ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+		return -1;
+	}
+	if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+		ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, channel leaving bridge.\n",
+			ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+		return -1;
+	}
+	if (ast_test_flag(&bridge_channel->features->feature_flags,
+		AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE)) {
+		ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, channel immovable.\n",
+			ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+		return -1;
+	}
+
+	if (swap) {
+		struct ast_bridge_channel *bridge_channel_swap;
+
+		bridge_channel_swap = find_bridge_channel(dst_bridge, swap);
+		if (!bridge_channel_swap) {
+			ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, swap channel %s not in bridge.\n",
+				ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid,
+				ast_channel_name(swap));
+			return -1;
+		}
+		if (bridge_channel_swap->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+			ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, swap channel %s leaving bridge.\n",
+				ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid,
+				ast_channel_name(swap));
+			return -1;
+		}
+	}
+
+	bridge_channel->swap = swap;
+	return bridge_move_do(dst_bridge, bridge_channel, attempt_recovery);
+}
+
+int ast_bridge_move(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_channel *chan, struct ast_channel *swap, int attempt_recovery)
+{
+	int res;
+
+	ast_bridge_lock_both(dst_bridge, src_bridge);
+	res = bridge_move_locked(dst_bridge, src_bridge, chan, swap, attempt_recovery);
+	ast_bridge_unlock(src_bridge);
+	ast_bridge_unlock(dst_bridge);
+	return res;
+}
+
+struct ast_bridge_channel *ast_bridge_channel_peer(struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_bridge *bridge = bridge_channel->bridge;
+	struct ast_bridge_channel *other = NULL;
+
+	if (bridge_channel->in_bridge && bridge->num_channels == 2) {
+		AST_LIST_TRAVERSE(&bridge->channels, other, entry) {
+			if (other != bridge_channel) {
+				break;
+			}
+		}
+	}
+
+	return other;
+}
+
+/*!
+ * \internal
+ * \brief Lock the unreal channel stack for chan and prequalify it.
+ * \since 12.0.0
+ *
+ * \param chan Unreal channel writing a frame into the channel driver.
+ *
+ * \note It is assumed that chan is already locked.
+ *
+ * \retval bridge on success with bridge and bridge_channel locked.
+ * \retval NULL if cannot do optimization now.
+ */
+static struct ast_bridge *optimize_lock_chan_stack(struct ast_channel *chan)
+{
+	struct ast_bridge *bridge;
+	struct ast_bridge_channel *bridge_channel;
+
+	if (!AST_LIST_EMPTY(ast_channel_readq(chan))) {
+		return NULL;
+	}
+	bridge_channel = ast_channel_internal_bridge_channel(chan);
+	if (!bridge_channel || ast_bridge_channel_trylock(bridge_channel)) {
+		return NULL;
+	}
+	bridge = bridge_channel->bridge;
+	if (bridge_channel->activity != AST_BRIDGE_CHANNEL_THREAD_SIMPLE
+		|| bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT
+		|| ast_bridge_trylock(bridge)) {
+		ast_bridge_channel_unlock(bridge_channel);
+		return NULL;
+	}
+	if (bridge->inhibit_merge
+		|| bridge->dissolved
+		|| ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)
+		|| !bridge_channel->in_bridge
+		|| !AST_LIST_EMPTY(&bridge_channel->wr_queue)) {
+		ast_bridge_unlock(bridge);
+		ast_bridge_channel_unlock(bridge_channel);
+		return NULL;
+	}
+	return bridge;
+}
+
+/*!
+ * \internal
+ * \brief Lock the unreal channel stack for peer and prequalify it.
+ * \since 12.0.0
+ *
+ * \param peer Other unreal channel in the pair.
+ *
+ * \retval bridge on success with bridge, bridge_channel, and peer locked.
+ * \retval NULL if cannot do optimization now.
+ */
+static struct ast_bridge *optimize_lock_peer_stack(struct ast_channel *peer)
+{
+	struct ast_bridge *bridge;
+	struct ast_bridge_channel *bridge_channel;
+
+	if (ast_channel_trylock(peer)) {
+		return NULL;
+	}
+	if (!AST_LIST_EMPTY(ast_channel_readq(peer))) {
+		ast_channel_unlock(peer);
+		return NULL;
+	}
+	bridge_channel = ast_channel_internal_bridge_channel(peer);
+	if (!bridge_channel || ast_bridge_channel_trylock(bridge_channel)) {
+		ast_channel_unlock(peer);
+		return NULL;
+	}
+	bridge = bridge_channel->bridge;
+	if (bridge_channel->activity != AST_BRIDGE_CHANNEL_THREAD_IDLE
+		|| bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT
+		|| ast_bridge_trylock(bridge)) {
+		ast_bridge_channel_unlock(bridge_channel);
+		ast_channel_unlock(peer);
+		return NULL;
+	}
+	if (bridge->inhibit_merge
+		|| bridge->dissolved
+		|| ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)
+		|| !bridge_channel->in_bridge
+		|| !AST_LIST_EMPTY(&bridge_channel->wr_queue)) {
+		ast_bridge_unlock(bridge);
+		ast_bridge_channel_unlock(bridge_channel);
+		ast_channel_unlock(peer);
+		return NULL;
+	}
+	return bridge;
+}
+
+/*!
+ * \internal
+ * \brief Check and attempt to swap optimize out the unreal channels.
+ * \since 12.0.0
+ *
+ * \param chan_bridge
+ * \param chan_bridge_channel
+ * \param peer_bridge
+ * \param peer_bridge_channel
+ *
+ * \retval 1 if unreal channels failed to optimize out.
+ * \retval 0 if unreal channels were not optimized out.
+ * \retval -1 if unreal channels were optimized out.
+ */
+static int check_swap_optimize_out(struct ast_bridge *chan_bridge,
+	struct ast_bridge_channel *chan_bridge_channel, struct ast_bridge *peer_bridge,
+	struct ast_bridge_channel *peer_bridge_channel)
+{
+	struct ast_bridge *dst_bridge = NULL;
+	struct ast_bridge_channel *dst_bridge_channel = NULL;
+	struct ast_bridge_channel *src_bridge_channel = NULL;
+	int peer_priority;
+	int chan_priority;
+	int res = 0;
+
+	if (!ast_test_flag(&chan_bridge->feature_flags,
+			AST_BRIDGE_FLAG_SWAP_INHIBIT_TO | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM)
+		&& !ast_test_flag(&peer_bridge->feature_flags,
+			AST_BRIDGE_FLAG_SWAP_INHIBIT_TO | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM)) {
+		/*
+		 * Can swap either way.  Swap to the higher priority merge
+		 * bridge.
+		 */
+		chan_priority = chan_bridge->v_table->get_merge_priority(chan_bridge);
+		peer_priority = peer_bridge->v_table->get_merge_priority(peer_bridge);
+		if (chan_bridge->num_channels == 2
+			&& chan_priority <= peer_priority) {
+			dst_bridge = peer_bridge;
+			dst_bridge_channel = peer_bridge_channel;
+			src_bridge_channel = chan_bridge_channel;
+		} else if (peer_bridge->num_channels == 2
+			&& peer_priority <= chan_priority) {
+			dst_bridge = chan_bridge;
+			dst_bridge_channel = chan_bridge_channel;
+			src_bridge_channel = peer_bridge_channel;
+		}
+	} else if (chan_bridge->num_channels == 2
+		&& !ast_test_flag(&chan_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM)
+		&& !ast_test_flag(&peer_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_TO)) {
+		/* Can swap optimize only one way. */
+		dst_bridge = peer_bridge;
+		dst_bridge_channel = peer_bridge_channel;
+		src_bridge_channel = chan_bridge_channel;
+	} else if (peer_bridge->num_channels == 2
+		&& !ast_test_flag(&peer_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM)
+		&& !ast_test_flag(&chan_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_TO)) {
+		/* Can swap optimize only one way. */
+		dst_bridge = chan_bridge;
+		dst_bridge_channel = chan_bridge_channel;
+		src_bridge_channel = peer_bridge_channel;
+	}
+	if (dst_bridge) {
+		struct ast_bridge_channel *other;
+
+		res = 1;
+		other = ast_bridge_channel_peer(src_bridge_channel);
+		if (other && other->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+			ast_debug(1, "Move-swap optimizing %s <-- %s.\n",
+				ast_channel_name(dst_bridge_channel->chan),
+				ast_channel_name(other->chan));
+
+			other->swap = dst_bridge_channel->chan;
+			if (!bridge_move_do(dst_bridge, other, 1)) {
+				ast_bridge_change_state(src_bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+				res = -1;
+			}
+		}
+	}
+	return res;
+}
+
+/*!
+ * \internal
+ * \brief Check and attempt to merge optimize out the unreal channels.
+ * \since 12.0.0
+ *
+ * \param chan_bridge
+ * \param chan_bridge_channel
+ * \param peer_bridge
+ * \param peer_bridge_channel
+ *
+ * \retval 0 if unreal channels were not optimized out.
+ * \retval -1 if unreal channels were optimized out.
+ */
+static int check_merge_optimize_out(struct ast_bridge *chan_bridge,
+	struct ast_bridge_channel *chan_bridge_channel, struct ast_bridge *peer_bridge,
+	struct ast_bridge_channel *peer_bridge_channel)
+{
+	struct merge_direction merge;
+	int res = 0;
+
+	merge = bridge_merge_determine_direction(chan_bridge, peer_bridge);
+	if (!merge.dest) {
+		return res;
+	}
+	if (merge.src->num_channels < 2) {
+		ast_debug(4, "Can't optimize %s -- %s out, not enough channels in bridge %s.\n",
+			ast_channel_name(chan_bridge_channel->chan),
+			ast_channel_name(peer_bridge_channel->chan),
+			merge.src->uniqueid);
+	} else if ((2 + 2) < merge.dest->num_channels + merge.src->num_channels
+		&& !(merge.dest->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)
+		&& (!ast_test_flag(&merge.dest->feature_flags, AST_BRIDGE_FLAG_SMART)
+			|| !(merge.dest->allowed_capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX))) {
+		ast_debug(4, "Can't optimize %s -- %s out, multimix is needed and it cannot be acquired.\n",
+			ast_channel_name(chan_bridge_channel->chan),
+			ast_channel_name(peer_bridge_channel->chan));
+	} else {
+		struct ast_bridge_channel *kick_me[] = {
+			chan_bridge_channel,
+			peer_bridge_channel,
+			};
+
+		ast_debug(1, "Merge optimizing %s -- %s out.\n",
+			ast_channel_name(chan_bridge_channel->chan),
+			ast_channel_name(peer_bridge_channel->chan));
+
+		bridge_merge_do(merge.dest, merge.src, kick_me, ARRAY_LEN(kick_me));
+		res = -1;
+	}
+
+	return res;
+}
+
+int ast_bridge_unreal_optimized_out(struct ast_channel *chan, struct ast_channel *peer)
+{
+	struct ast_bridge *chan_bridge;
+	struct ast_bridge *peer_bridge;
+	struct ast_bridge_channel *chan_bridge_channel;
+	struct ast_bridge_channel *peer_bridge_channel;
+	int res = 0;
+
+	chan_bridge = optimize_lock_chan_stack(chan);
+	if (!chan_bridge) {
+		return res;
+	}
+	chan_bridge_channel = ast_channel_internal_bridge_channel(chan);
+
+	peer_bridge = optimize_lock_peer_stack(peer);
+	if (peer_bridge) {
+		peer_bridge_channel = ast_channel_internal_bridge_channel(peer);
+
+		res = check_swap_optimize_out(chan_bridge, chan_bridge_channel,
+			peer_bridge, peer_bridge_channel);
+		if (!res) {
+			res = check_merge_optimize_out(chan_bridge, chan_bridge_channel,
+				peer_bridge, peer_bridge_channel);
+		} else if (0 < res) {
+			res = 0;
+		}
+
+		/* Release peer locks. */
+		ast_bridge_unlock(peer_bridge);
+		ast_bridge_channel_unlock(peer_bridge_channel);
+		ast_channel_unlock(peer);
+	}
+
+	/* Release chan locks. */
+	ast_bridge_unlock(chan_bridge);
+	ast_bridge_channel_unlock(chan_bridge_channel);
+
+	return res;
+}
+
+/*!
+ * \internal
+ * \brief Adjust the bridge merge inhibit request count.
+ * \since 12.0.0
+ *
+ * \param bridge What to operate on.
+ * \param request Inhibit request increment.
+ *     (Positive to add requests.  Negative to remove requests.)
+ *
+ * \note This function assumes bridge is locked.
+ *
+ * \return Nothing
+ */
+static void bridge_merge_inhibit_nolock(struct ast_bridge *bridge, int request)
+{
+	int new_request;
+
+	new_request = bridge->inhibit_merge + request;
+	ast_assert(0 <= new_request);
+	bridge->inhibit_merge = new_request;
+}
+
+void ast_bridge_merge_inhibit(struct ast_bridge *bridge, int request)
+{
+	ast_bridge_lock(bridge);
+	bridge_merge_inhibit_nolock(bridge, request);
+	ast_bridge_unlock(bridge);
+}
+
+struct ast_bridge *ast_bridge_channel_merge_inhibit(struct ast_bridge_channel *bridge_channel, int request)
+{
+	struct ast_bridge *bridge;
+
+	ast_bridge_channel_lock_bridge(bridge_channel);
+	bridge = bridge_channel->bridge;
+	ao2_ref(bridge, +1);
+	bridge_merge_inhibit_nolock(bridge, request);
+	ast_bridge_unlock(bridge);
+	return bridge;
+}
+
+int ast_bridge_suspend(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+	struct ast_bridge_channel *bridge_channel;
+/* BUGBUG the case of a disolved bridge while channel is suspended is not handled. */
+/* BUGBUG suspend/unsuspend needs to be rethought. The caller must block until it has successfully suspended the channel for temporary control. */
+/* BUGBUG external suspend/unsuspend needs to be eliminated. The channel may be playing a file at the time and stealing it then is not good. */
+
+	ast_bridge_lock(bridge);
+
+	if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
+		ast_bridge_unlock(bridge);
+		return -1;
+	}
+
+	bridge_channel_suspend_nolock(bridge_channel);
+
+	ast_bridge_unlock(bridge);
+
+	return 0;
+}
+
+int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+	struct ast_bridge_channel *bridge_channel;
+/* BUGBUG the case of a disolved bridge while channel is suspended is not handled. */
+
+	ast_bridge_lock(bridge);
+
+	if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
+		ast_bridge_unlock(bridge);
+		return -1;
+	}
+
+	bridge_channel_unsuspend_nolock(bridge_channel);
+
+	ast_bridge_unlock(bridge);
+
+	return 0;
+}
+
+void ast_bridge_technology_suspend(struct ast_bridge_technology *technology)
+{
+	technology->suspended = 1;
+}
+
+void ast_bridge_technology_unsuspend(struct ast_bridge_technology *technology)
+{
+/* BUGBUG unsuspending a bridge technology probably needs to prod all existing bridges to see if they should start using it. */
+	technology->suspended = 0;
+}
+
+int ast_bridge_features_register(enum ast_bridge_builtin_feature feature, ast_bridge_hook_callback callback, const char *dtmf)
+{
+	if (ARRAY_LEN(builtin_features_handlers) <= feature
+		|| builtin_features_handlers[feature]) {
+		return -1;
+	}
+
+	if (!ast_strlen_zero(dtmf)) {
+		ast_copy_string(builtin_features_dtmf[feature], dtmf, sizeof(builtin_features_dtmf[feature]));
+	}
+
+	builtin_features_handlers[feature] = callback;
+
+	return 0;
+}
+
+int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature)
+{
+	if (ARRAY_LEN(builtin_features_handlers) <= feature
+		|| !builtin_features_handlers[feature]) {
+		return -1;
+	}
+
+	builtin_features_handlers[feature] = NULL;
+
+	return 0;
+}
+
+int ast_bridge_interval_register(enum ast_bridge_builtin_interval interval, ast_bridge_builtin_set_limits_fn callback)
+{
+	if (ARRAY_LEN(builtin_interval_handlers) <= interval
+		|| builtin_interval_handlers[interval]) {
+		return -1;
+	}
+
+	builtin_interval_handlers[interval] = callback;
+
+	return 0;
+}
+
+int ast_bridge_interval_unregister(enum ast_bridge_builtin_interval interval)
+{
+	if (ARRAY_LEN(builtin_interval_handlers) <= interval
+		|| !builtin_interval_handlers[interval]) {
+		return -1;
+	}
+
+	builtin_interval_handlers[interval] = NULL;
+
+	return 0;
+
+}
+
+/*!
+ * \internal
+ * \brief Bridge hook destructor.
+ * \since 12.0.0
+ *
+ * \param vhook Object to destroy.
+ *
+ * \return Nothing
+ */
+static void bridge_hook_destroy(void *vhook)
+{
+	struct ast_bridge_hook *hook = vhook;
+
+	if (hook->destructor) {
+		hook->destructor(hook->hook_pvt);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Allocate and setup a generic bridge hook.
+ * \since 12.0.0
+ *
+ * \param size How big an object to allocate.
+ * \param callback Function to execute upon activation
+ * \param hook_pvt Unique data
+ * \param destructor Optional destructor callback for hook_pvt data
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
+ *
+ * \retval hook on success.
+ * \retval NULL on error.
+ */
+static struct ast_bridge_hook *bridge_hook_generic(size_t size,
+	ast_bridge_hook_callback callback,
+	void *hook_pvt,
+	ast_bridge_hook_pvt_destructor destructor,
+	int remove_on_pull)
+{
+	struct ast_bridge_hook *hook;
+
+	/* Allocate new hook and setup it's basic variables */
+	hook = ao2_alloc_options(size, bridge_hook_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+	if (hook) {
+		hook->callback = callback;
+		hook->destructor = destructor;
+		hook->hook_pvt = hook_pvt;
+		hook->remove_on_pull = remove_on_pull;
+	}
+
+	return hook;
+}
+
+int ast_bridge_dtmf_hook(struct ast_bridge_features *features,
+	const char *dtmf,
+	ast_bridge_hook_callback callback,
+	void *hook_pvt,
+	ast_bridge_hook_pvt_destructor destructor,
+	int remove_on_pull)
+{
+	struct ast_bridge_hook *hook;
+	int res;
+
+	/* Allocate new hook and setup it's various variables */
+	hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor,
+		remove_on_pull);
+	if (!hook) {
+		return -1;
+	}
+	ast_copy_string(hook->parms.dtmf.code, dtmf, sizeof(hook->parms.dtmf.code));
+
+	/* Once done we put it in the container. */
+	res = ao2_link(features->dtmf_hooks, hook) ? 0 : -1;
+	ao2_ref(hook, -1);
+
+	return res;
+}
+
+int ast_bridge_hangup_hook(struct ast_bridge_features *features,
+	ast_bridge_hook_callback callback,
+	void *hook_pvt,
+	ast_bridge_hook_pvt_destructor destructor,
+	int remove_on_pull)
+{
+	struct ast_bridge_hook *hook;
+	int res;
+
+	/* Allocate new hook and setup it's various variables */
+	hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor,
+		remove_on_pull);
+	if (!hook) {
+		return -1;
+	}
+
+	/* Once done we put it in the container. */
+	res = ao2_link(features->hangup_hooks, hook) ? 0 : -1;
+	ao2_ref(hook, -1);
+
+	return res;
+}
+
+int ast_bridge_join_hook(struct ast_bridge_features *features,
+	ast_bridge_hook_callback callback,
+	void *hook_pvt,
+	ast_bridge_hook_pvt_destructor destructor,
+	int remove_on_pull)
+{
+	struct ast_bridge_hook *hook;
+	int res;
+
+	/* Allocate new hook and setup it's various variables */
+	hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor,
+		remove_on_pull);
+	if (!hook) {
+		return -1;
+	}
+
+	/* Once done we put it in the container. */
+	res = ao2_link(features->join_hooks, hook) ? 0 : -1;
+	ao2_ref(hook, -1);
+
+	return res;
+}
+
+int ast_bridge_leave_hook(struct ast_bridge_features *features,
+	ast_bridge_hook_callback callback,
+	void *hook_pvt,
+	ast_bridge_hook_pvt_destructor destructor,
+	int remove_on_pull)
+{
+	struct ast_bridge_hook *hook;
+	int res;
+
+	/* Allocate new hook and setup it's various variables */
+	hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor,
+		remove_on_pull);
+	if (!hook) {
+		return -1;
+	}
+
+	/* Once done we put it in the container. */
+	res = ao2_link(features->leave_hooks, hook) ? 0 : -1;
+	ao2_ref(hook, -1);
+
+	return res;
+}
+
+void ast_bridge_features_set_talk_detector(struct ast_bridge_features *features,
+	ast_bridge_talking_indicate_callback talker_cb,
+	ast_bridge_talking_indicate_destructor talker_destructor,
+	void *pvt_data)
+{
+	features->talker_cb = talker_cb;
+	features->talker_destructor_cb = talker_destructor;
+	features->talker_pvt_data = pvt_data;
+}
+
+int ast_bridge_interval_hook(struct ast_bridge_features *features,
+	unsigned int interval,
+	ast_bridge_hook_callback callback,
+	void *hook_pvt,
+	ast_bridge_hook_pvt_destructor destructor,
+	int remove_on_pull)
+{
+	struct ast_bridge_hook *hook;
+	int res;
+
+	if (!features ||!interval || !callback) {
+		return -1;
+	}
+
+	if (!features->interval_timer) {
+		if (!(features->interval_timer = ast_timer_open())) {
+			ast_log(LOG_ERROR, "Failed to open a timer when adding a timed bridging feature.\n");
+			return -1;
+		}
+		ast_timer_set_rate(features->interval_timer, BRIDGE_FEATURES_INTERVAL_RATE);
+	}
+
+	/* Allocate new hook and setup it's various variables */
+	hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor,
+		remove_on_pull);
+	if (!hook) {
+		return -1;
+	}
+	hook->parms.timer.interval = interval;
+	hook->parms.timer.trip_time = ast_tvadd(ast_tvnow(), ast_samp2tv(hook->parms.timer.interval, 1000));
+	hook->parms.timer.seqno = ast_atomic_fetchadd_int((int *) &features->interval_sequence, +1);
+
+	ast_debug(1, "Putting interval hook %p with interval %u in the heap on features %p\n",
+		hook, hook->parms.timer.interval, features);
+	ast_heap_wrlock(features->interval_hooks);
+	res = ast_heap_push(features->interval_hooks, hook);
+	if (res) {
+		/* Could not push the hook onto the heap. */
+		ao2_ref(hook, -1);
+	}
+	ast_heap_unlock(features->interval_hooks);
+
+	return res ? -1 : 0;
+}
+
+int ast_bridge_features_enable(struct ast_bridge_features *features,
+	enum ast_bridge_builtin_feature feature,
+	const char *dtmf,
+	void *config,
+	ast_bridge_hook_pvt_destructor destructor,
+	int remove_on_pull)
+{
+	if (ARRAY_LEN(builtin_features_handlers) <= feature
+		|| !builtin_features_handlers[feature]) {
+		return -1;
+	}
+
+	/* If no alternate DTMF stream was provided use the default one */
+	if (ast_strlen_zero(dtmf)) {
+		dtmf = builtin_features_dtmf[feature];
+		/* If no DTMF is still available (ie: it has been disabled) then error out now */
+		if (ast_strlen_zero(dtmf)) {
+			ast_debug(1, "Failed to enable built in feature %d on %p, no DTMF string is available for it.\n",
+				feature, features);
+			return -1;
+		}
+	}
+
+	/*
+	 * The rest is basically pretty easy.  We create another hook
+	 * using the built in feature's DTMF callback.  Easy as pie.
+	 */
+	return ast_bridge_dtmf_hook(features, dtmf, builtin_features_handlers[feature],
+		config, destructor, remove_on_pull);
+}
+
+int ast_bridge_features_limits_construct(struct ast_bridge_features_limits *limits)
+{
+	memset(limits, 0, sizeof(*limits));
+
+	if (ast_string_field_init(limits, 256)) {
+		ast_free(limits);
+		return -1;
+	}
+
+	return 0;
+}
+
+void ast_bridge_features_limits_destroy(struct ast_bridge_features_limits *limits)
+{
+	ast_string_field_free_memory(limits);
+}
+
+int ast_bridge_features_set_limits(struct ast_bridge_features *features, struct ast_bridge_features_limits *limits, int remove_on_pull)
+{
+	if (builtin_interval_handlers[AST_BRIDGE_BUILTIN_INTERVAL_LIMITS]) {
+		ast_bridge_builtin_set_limits_fn bridge_features_set_limits_callback;
+
+		bridge_features_set_limits_callback = builtin_interval_handlers[AST_BRIDGE_BUILTIN_INTERVAL_LIMITS];
+		return bridge_features_set_limits_callback(features, limits, remove_on_pull);
+	}
+
+	ast_log(LOG_ERROR, "Attempted to set limits without an AST_BRIDGE_BUILTIN_INTERVAL_LIMITS callback registered.\n");
+	return -1;
+}
+
+void ast_bridge_features_set_flag(struct ast_bridge_features *features, unsigned int flag)
+{
+	ast_set_flag(&features->feature_flags, flag);
+	features->usable = 1;
+}
+
+/*!
+ * \internal
+ * \brief ao2 object match remove_on_pull hooks.
+ * \since 12.0.0
+ *
+ * \param obj Feature hook object.
+ * \param arg Not used
+ * \param flags Not used
+ *
+ * \retval CMP_MATCH if hook has remove_on_pull set.
+ * \retval 0 if not match.
+ */
+static int hook_remove_on_pull_match(void *obj, void *arg, int flags)
+{
+	struct ast_bridge_hook *hook = obj;
+
+	if (hook->remove_on_pull) {
+		return CMP_MATCH;
+	} else {
+		return 0;
+	}
+}
+
+/*!
+ * \internal
+ * \brief Remove all remove_on_pull hooks in the container.
+ * \since 12.0.0
+ *
+ * \param hooks Hooks container to work on.
+ *
+ * \return Nothing
+ */
+static void hooks_remove_on_pull_container(struct ao2_container *hooks)
+{
+	ao2_callback(hooks, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE,
+		hook_remove_on_pull_match, NULL);
+}
+
+/*!
+ * \internal
+ * \brief Remove all remove_on_pull hooks in the heap.
+ * \since 12.0.0
+ *
+ * \param hooks Hooks heap to work on.
+ *
+ * \return Nothing
+ */
+static void hooks_remove_on_pull_heap(struct ast_heap *hooks)
+{
+	struct ast_bridge_hook *hook;
+	int changed;
+
+	ast_heap_wrlock(hooks);
+	do {
+		int idx;
+
+		changed = 0;
+		for (idx = ast_heap_size(hooks); idx; --idx) {
+			hook = ast_heap_peek(hooks, idx);
+			if (hook->remove_on_pull) {
+				ast_heap_remove(hooks, hook);
+				ao2_ref(hook, -1);
+				changed = 1;
+			}
+		}
+	} while (changed);
+	ast_heap_unlock(hooks);
+}
+
+/*!
+ * \internal
+ * \brief Remove marked bridge channel feature hooks.
+ * \since 12.0.0
+ *
+ * \param features Bridge featues structure
+ *
+ * \return Nothing
+ */
+static void bridge_features_remove_on_pull(struct ast_bridge_features *features)
+{
+	hooks_remove_on_pull_container(features->dtmf_hooks);
+	hooks_remove_on_pull_container(features->hangup_hooks);
+	hooks_remove_on_pull_container(features->join_hooks);
+	hooks_remove_on_pull_container(features->leave_hooks);
+	hooks_remove_on_pull_heap(features->interval_hooks);
+}
+
+static int interval_hook_time_cmp(void *a, void *b)
+{
+	struct ast_bridge_hook *hook_a = a;
+	struct ast_bridge_hook *hook_b = b;
+	int cmp;
+
+	cmp = ast_tvcmp(hook_b->parms.timer.trip_time, hook_a->parms.timer.trip_time);
+	if (cmp) {
+		return cmp;
+	}
+
+	cmp = hook_b->parms.timer.seqno - hook_a->parms.timer.seqno;
+	return cmp;
+}
+
+/*!
+ * \internal
+ * \brief DTMF hook container sort comparison function.
+ * \since 12.0.0
+ *
+ * \param obj_left pointer to the (user-defined part) of an object.
+ * \param obj_right pointer to the (user-defined part) of an object.
+ * \param flags flags from ao2_callback()
+ *   OBJ_POINTER - if set, 'obj_right', is an object.
+ *   OBJ_KEY - if set, 'obj_right', is a search key item that is not an object.
+ *   OBJ_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object.
+ *
+ * \retval <0 if obj_left < obj_right
+ * \retval =0 if obj_left == obj_right
+ * \retval >0 if obj_left > obj_right
+ */
+static int bridge_dtmf_hook_sort(const void *obj_left, const void *obj_right, int flags)
+{
+	const struct ast_bridge_hook *hook_left = obj_left;
+	const struct ast_bridge_hook *hook_right = obj_right;
+	const char *right_key = obj_right;
+	int cmp;
+
+	switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+	default:
+	case OBJ_POINTER:
+		right_key = hook_right->parms.dtmf.code;
+		/* Fall through */
+	case OBJ_KEY:
+		cmp = strcasecmp(hook_left->parms.dtmf.code, right_key);
+		break;
+	case OBJ_PARTIAL_KEY:
+		cmp = strncasecmp(hook_left->parms.dtmf.code, right_key, strlen(right_key));
+		break;
+	}
+	return cmp;
+}
+
+/* BUGBUG make ast_bridge_features_init() static when make ast_bridge_join() requires features to be allocated. */
+int ast_bridge_features_init(struct ast_bridge_features *features)
+{
+	/* Zero out the structure */
+	memset(features, 0, sizeof(*features));
+
+	/* Initialize the DTMF hooks container */
+	features->dtmf_hooks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX,
+		AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, bridge_dtmf_hook_sort, NULL);
+	if (!features->dtmf_hooks) {
+		return -1;
+	}
+
+	/* Initialize the hangup hooks container */
+	features->hangup_hooks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL,
+		NULL);
+	if (!features->hangup_hooks) {
+		return -1;
+	}
+
+	/* Initialize the join hooks container */
+	features->join_hooks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL,
+		NULL);
+	if (!features->join_hooks) {
+		return -1;
+	}
+
+	/* Initialize the leave hooks container */
+	features->leave_hooks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL,
+		NULL);
+	if (!features->leave_hooks) {
+		return -1;
+	}
+
+	/* Initialize the interval hooks heap */
+	features->interval_hooks = ast_heap_create(8, interval_hook_time_cmp,
+		offsetof(struct ast_bridge_hook, parms.timer.heap_index));
+	if (!features->interval_hooks) {
+		return -1;
+	}
+
+	return 0;
+}
+
+/* BUGBUG make ast_bridge_features_cleanup() static when make ast_bridge_join() requires features to be allocated. */
+void ast_bridge_features_cleanup(struct ast_bridge_features *features)
+{
+	struct ast_bridge_hook *hook;
+
+	/* Destroy the interval hooks heap. */
+	if (features->interval_hooks) {
+		while ((hook = ast_heap_pop(features->interval_hooks))) {
+			ao2_ref(hook, -1);
+		}
+		features->interval_hooks = ast_heap_destroy(features->interval_hooks);
+	}
+
+	if (features->interval_timer) {
+		ast_timer_close(features->interval_timer);
+		features->interval_timer = NULL;
+	}
+
+	/* If the features contains a limits pvt, destroy that as well. */
+	if (features->limits) {
+		ast_bridge_features_limits_destroy(features->limits);
+		ast_free(features->limits);
+		features->limits = NULL;
+	}
+
+	if (features->talker_destructor_cb && features->talker_pvt_data) {
+		features->talker_destructor_cb(features->talker_pvt_data);
+		features->talker_pvt_data = NULL;
+	}
+
+	/* Destroy the leave hooks container. */
+	ao2_cleanup(features->leave_hooks);
+	features->leave_hooks = NULL;
+
+	/* Destroy the join hooks container. */
+	ao2_cleanup(features->join_hooks);
+	features->join_hooks = NULL;
+
+	/* Destroy the hangup hooks container. */
+	ao2_cleanup(features->hangup_hooks);
+	features->hangup_hooks = NULL;
+
+	/* Destroy the DTMF hooks container. */
+	ao2_cleanup(features->dtmf_hooks);
+	features->dtmf_hooks = NULL;
+}
+
+void ast_bridge_features_destroy(struct ast_bridge_features *features)
+{
+	if (!features) {
+		return;
+	}
+	ast_bridge_features_cleanup(features);
+	ast_free(features);
+}
+
+struct ast_bridge_features *ast_bridge_features_new(void)
+{
+	struct ast_bridge_features *features;
+
+	features = ast_malloc(sizeof(*features));
+	if (features) {
+		if (ast_bridge_features_init(features)) {
+			ast_bridge_features_destroy(features);
+			features = NULL;
+		}
+	}
+
+	return features;
+}
+
+void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixing_interval)
+{
+	ast_bridge_lock(bridge);
+	bridge->internal_mixing_interval = mixing_interval;
+	ast_bridge_unlock(bridge);
+}
+
+void ast_bridge_set_internal_sample_rate(struct ast_bridge *bridge, unsigned int sample_rate)
+{
+	ast_bridge_lock(bridge);
+	bridge->internal_sample_rate = sample_rate;
+	ast_bridge_unlock(bridge);
+}
+
+static void cleanup_video_mode(struct ast_bridge *bridge)
+{
+	switch (bridge->video_mode.mode) {
+	case AST_BRIDGE_VIDEO_MODE_NONE:
+		break;
+	case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
+		if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) {
+			ast_channel_unref(bridge->video_mode.mode_data.single_src_data.chan_vsrc);
+		}
+		break;
+	case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
+		if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) {
+			ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_vsrc);
+		}
+		if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) {
+			ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc);
+		}
+	}
+	memset(&bridge->video_mode, 0, sizeof(bridge->video_mode));
+}
+
+void ast_bridge_set_single_src_video_mode(struct ast_bridge *bridge, struct ast_channel *video_src_chan)
+{
+	ast_bridge_lock(bridge);
+	cleanup_video_mode(bridge);
+	bridge->video_mode.mode = AST_BRIDGE_VIDEO_MODE_SINGLE_SRC;
+	bridge->video_mode.mode_data.single_src_data.chan_vsrc = ast_channel_ref(video_src_chan);
+	ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to single source\r\nVideo Mode: %d\r\nVideo Channel: %s", bridge->video_mode.mode, ast_channel_name(video_src_chan));
+	ast_indicate(video_src_chan, AST_CONTROL_VIDUPDATE);
+	ast_bridge_unlock(bridge);
+}
+
+void ast_bridge_set_talker_src_video_mode(struct ast_bridge *bridge)
+{
+	ast_bridge_lock(bridge);
+	cleanup_video_mode(bridge);
+	bridge->video_mode.mode = AST_BRIDGE_VIDEO_MODE_TALKER_SRC;
+	ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to talker source\r\nVideo Mode: %d", bridge->video_mode.mode);
+	ast_bridge_unlock(bridge);
+}
+
+void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct ast_channel *chan, int talker_energy, int is_keyframe)
+{
+	struct ast_bridge_video_talker_src_data *data;
+	/* If the channel doesn't support video, we don't care about it */
+	if (!ast_format_cap_has_type(ast_channel_nativeformats(chan), AST_FORMAT_TYPE_VIDEO)) {
+		return;
+	}
+
+	ast_bridge_lock(bridge);
+	data = &bridge->video_mode.mode_data.talker_src_data;
+
+	if (data->chan_vsrc == chan) {
+		data->average_talking_energy = talker_energy;
+	} else if ((data->average_talking_energy < talker_energy) && is_keyframe) {
+		if (data->chan_old_vsrc) {
+			ast_channel_unref(data->chan_old_vsrc);
+		}
+		if (data->chan_vsrc) {
+			data->chan_old_vsrc = data->chan_vsrc;
+			ast_indicate(data->chan_old_vsrc, AST_CONTROL_VIDUPDATE);
+		}
+		data->chan_vsrc = ast_channel_ref(chan);
+		data->average_talking_energy = talker_energy;
+		ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc));
+		ast_indicate(data->chan_vsrc, AST_CONTROL_VIDUPDATE);
+	} else if ((data->average_talking_energy < talker_energy) && !is_keyframe) {
+		ast_indicate(chan, AST_CONTROL_VIDUPDATE);
+	} else if (!data->chan_vsrc && is_keyframe) {
+		data->chan_vsrc = ast_channel_ref(chan);
+		data->average_talking_energy = talker_energy;
+		ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc));
+		ast_indicate(chan, AST_CONTROL_VIDUPDATE);
+	} else if (!data->chan_old_vsrc && is_keyframe) {
+		data->chan_old_vsrc = ast_channel_ref(chan);
+		ast_indicate(chan, AST_CONTROL_VIDUPDATE);
+	}
+	ast_bridge_unlock(bridge);
+}
+
+int ast_bridge_number_video_src(struct ast_bridge *bridge)
+{
+	int res = 0;
+
+	ast_bridge_lock(bridge);
+	switch (bridge->video_mode.mode) {
+	case AST_BRIDGE_VIDEO_MODE_NONE:
+		break;
+	case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
+		if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) {
+			res = 1;
+		}
+		break;
+	case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
+		if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) {
+			res++;
+		}
+		if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) {
+			res++;
+		}
+	}
+	ast_bridge_unlock(bridge);
+	return res;
+}
+
+int ast_bridge_is_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+	int res = 0;
+
+	ast_bridge_lock(bridge);
+	switch (bridge->video_mode.mode) {
+	case AST_BRIDGE_VIDEO_MODE_NONE:
+		break;
+	case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
+		if (bridge->video_mode.mode_data.single_src_data.chan_vsrc == chan) {
+			res = 1;
+		}
+		break;
+	case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
+		if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc == chan) {
+			res = 1;
+		} else if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc == chan) {
+			res = 2;
+		}
+
+	}
+	ast_bridge_unlock(bridge);
+	return res;
+}
+
+void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+	ast_bridge_lock(bridge);
+	switch (bridge->video_mode.mode) {
+	case AST_BRIDGE_VIDEO_MODE_NONE:
+		break;
+	case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
+		if (bridge->video_mode.mode_data.single_src_data.chan_vsrc == chan) {
+			if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) {
+				ast_channel_unref(bridge->video_mode.mode_data.single_src_data.chan_vsrc);
+			}
+			bridge->video_mode.mode_data.single_src_data.chan_vsrc = NULL;
+		}
+		break;
+	case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
+		if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc == chan) {
+			if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) {
+				ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_vsrc);
+			}
+			bridge->video_mode.mode_data.talker_src_data.chan_vsrc = NULL;
+			bridge->video_mode.mode_data.talker_src_data.average_talking_energy = 0;
+		}
+		if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc == chan) {
+			if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) {
+				ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc);
+			}
+			bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc = NULL;
+		}
 	}
+	ast_bridge_unlock(bridge);
+}
 
-	if (bridge_channel->bridge) {
-		ao2_ref(bridge_channel->bridge, -1);
-		bridge_channel->bridge = NULL;
+static int channel_hash(const void *obj, int flags)
+{
+	const struct ast_channel *chan = obj;
+	const char *name = obj;
+	int hash;
+
+	switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+	default:
+	case OBJ_POINTER:
+		name = ast_channel_name(chan);
+		/* Fall through */
+	case OBJ_KEY:
+		hash = ast_str_hash(name);
+		break;
+	case OBJ_PARTIAL_KEY:
+		/* Should never happen in hash callback. */
+		ast_assert(0);
+		hash = 0;
+		break;
 	}
-	/* Destroy elements of the bridge channel structure and the bridge channel structure itself */
-	ast_cond_destroy(&bridge_channel->cond);
+	return hash;
 }
 
-static struct ast_bridge_channel *bridge_channel_alloc(struct ast_bridge *bridge)
+static int channel_cmp(void *obj, void *arg, int flags)
 {
-	struct ast_bridge_channel *bridge_channel;
+	const struct ast_channel *left = obj;
+	const struct ast_channel *right = arg;
+	const char *right_name = arg;
+	int cmp;
 
-	bridge_channel = ao2_alloc(sizeof(struct ast_bridge_channel), bridge_channel_destroy);
-	if (!bridge_channel) {
-		return NULL;
-	}
-	ast_cond_init(&bridge_channel->cond, NULL);
-	if (bridge) {
-		bridge_channel->bridge = bridge;
-		ao2_ref(bridge_channel->bridge, +1);
+	switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+	default:
+	case OBJ_POINTER:
+		right_name = ast_channel_name(right);
+		/* Fall through */
+	case OBJ_KEY:
+		cmp = strcmp(ast_channel_name(left), right_name);
+		break;
+	case OBJ_PARTIAL_KEY:
+		cmp = strncmp(ast_channel_name(left), right_name, strlen(right_name));
+		break;
 	}
-	return bridge_channel;
+	return cmp ? 0 : CMP_MATCH;
 }
 
-enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge,
-	struct ast_channel *chan,
-	struct ast_channel *swap,
-	struct ast_bridge_features *features,
-	struct ast_bridge_tech_optimizations *tech_args)
+struct ao2_container *ast_bridge_peers_nolock(struct ast_bridge *bridge)
 {
-	struct ast_bridge_channel *bridge_channel;
-	enum ast_bridge_channel_state state;
+	struct ao2_container *channels;
+	struct ast_bridge_channel *iter;
 
-	bridge_channel = bridge_channel_alloc(bridge);
-	if (!bridge_channel) {
-		return AST_BRIDGE_CHANNEL_STATE_HANGUP;
+	channels = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK,
+		13, channel_hash, channel_cmp);
+	if (!channels) {
+		return NULL;
 	}
-	if (tech_args) {
-		bridge_channel->tech_args = *tech_args;
+
+	AST_LIST_TRAVERSE(&bridge->channels, iter, entry) {
+		ao2_link(channels, iter->chan);
 	}
 
-	/* Initialize various other elements of the bridge channel structure that we can't do above */
-	bridge_channel->chan = chan;
-	bridge_channel->swap = swap;
-	bridge_channel->features = features;
+	return channels;
+}
 
-	state = bridge_channel_join(bridge_channel);
+struct ao2_container *ast_bridge_peers(struct ast_bridge *bridge)
+{
+	struct ao2_container *channels;
 
-	/* Cleanup all the data in the bridge channel after it leaves the bridge. */
-	bridge_channel->chan = NULL;
-	bridge_channel->swap = NULL;
-	bridge_channel->features = NULL;
+	ast_bridge_lock(bridge);
+	channels = ast_bridge_peers_nolock(bridge);
+	ast_bridge_unlock(bridge);
 
-	ao2_ref(bridge_channel, -1);
-	return state;
+	return channels;
 }
 
-/*! \brief Thread responsible for imparted bridged channels */
-static void *bridge_channel_thread(void *data)
+struct ast_channel *ast_bridge_peer_nolock(struct ast_bridge *bridge, struct ast_channel *chan)
 {
-	struct ast_bridge_channel *bridge_channel = data;
-	enum ast_bridge_channel_state state;
-
-	if (bridge_channel->callid) {
-		ast_callid_threadassoc_add(bridge_channel->callid);
+	struct ast_channel *peer = NULL;
+	struct ast_bridge_channel *iter;
+
+	/* Asking for the peer channel only makes sense on a two-party bridge. */
+	if (bridge->num_channels == 2
+		&& bridge->technology->capabilities
+			& (AST_BRIDGE_CAPABILITY_NATIVE | AST_BRIDGE_CAPABILITY_1TO1MIX)) {
+		int in_bridge = 0;
+
+		AST_LIST_TRAVERSE(&bridge->channels, iter, entry) {
+			if (iter->chan != chan) {
+				peer = iter->chan;
+			} else {
+				in_bridge = 1;
+			}
+		}
+		if (in_bridge && peer) {
+			ast_channel_ref(peer);
+		} else {
+			peer = NULL;
+		}
 	}
 
-	state = bridge_channel_join(bridge_channel);
-
-	/* If no other thread is going to take the channel then hang it up, or else we would have to service it until something else came along */
-	if (bridge_channel->allow_impart_hangup && (state == AST_BRIDGE_CHANNEL_STATE_END || state == AST_BRIDGE_CHANNEL_STATE_HANGUP)) {
-		ast_hangup(bridge_channel->chan);
-	}
+	return peer;
+}
 
-	/* cleanup */
-	ao2_lock(bridge_channel);
-	bridge_channel->chan = NULL;
-	bridge_channel->swap = NULL;
-	bridge_channel->features = NULL;
-	ao2_unlock(bridge_channel);
+struct ast_channel *ast_bridge_peer(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+	struct ast_channel *peer;
 
-	ao2_ref(bridge_channel, -1);
+	ast_bridge_lock(bridge);
+	peer = ast_bridge_peer_nolock(bridge, chan);
+	ast_bridge_unlock(bridge);
 
-	return NULL;
+	return peer;
 }
 
-int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, int allow_hangup)
+/*!
+ * \internal
+ * \brief Transfer an entire bridge to a specific destination.
+ *
+ * This creates a local channel to dial out and swaps the called local channel
+ * with the transferer channel. By doing so, all participants in the bridge are
+ * connected to the specified destination.
+ *
+ * While this means of transferring would work for both two-party and multi-party
+ * bridges, this method is only used for multi-party bridges since this method would
+ * be less efficient for two-party bridges.
+ *
+ * \param transferer The channel performing a transfer
+ * \param bridge The bridge where the transfer is being performed
+ * \param exten The destination extension for the blind transfer
+ * \param context The destination context for the blind transfer
+ * \param hook Framehook to attach to local channel
+ * \return The success or failure of the operation
+ */
+static enum ast_transfer_result blind_transfer_bridge(struct ast_channel *transferer,
+		struct ast_bridge *bridge, const char *exten, const char *context,
+		transfer_channel_cb new_channel_cb, void *user_data)
 {
-	struct ast_bridge_channel *bridge_channel;
+	struct ast_channel *local;
+	char chan_name[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2];
+	int cause;
+
+	snprintf(chan_name, sizeof(chan_name), "%s@%s", exten, context);
+	local = ast_request("Local", ast_channel_nativeformats(transferer), transferer,
+			chan_name, &cause);
+	if (!local) {
+		return AST_BRIDGE_TRANSFER_FAIL;
+	}
 
-	/* Try to allocate a structure for the bridge channel */
-	bridge_channel = bridge_channel_alloc(bridge);
-	if (!bridge_channel) {
-		return -1;
+	if (new_channel_cb) {
+		new_channel_cb(local, user_data);
 	}
 
-	/* Setup various parameters */
-	bridge_channel->chan = chan;
-	bridge_channel->swap = swap;
-	bridge_channel->features = features;
-	bridge_channel->allow_impart_hangup = allow_hangup;
-	bridge_channel->callid = ast_read_threadstorage_callid();
+	if (ast_call(local, chan_name, 0)) {
+		ast_hangup(local);
+		return AST_BRIDGE_TRANSFER_FAIL;
+	}
+	if (ast_bridge_impart(bridge, local, transferer, NULL, 1)) {
+		ast_hangup(local);
+		return AST_BRIDGE_TRANSFER_FAIL;
+	}
+	return AST_BRIDGE_TRANSFER_SUCCESS;
+}
 
-	/* Actually create the thread that will handle the channel */
-	if (ast_pthread_create(&bridge_channel->thread, NULL, bridge_channel_thread, bridge_channel)) {
-		ao2_ref(bridge_channel, -1);
-		return -1;
+/*!
+ * \internal
+ * \brief Get the transferee channel
+ *
+ * This is only applicable to cases where a transfer is occurring on a
+ * two-party bridge. The channels container passed in is expected to only
+ * contain two channels, the transferer and the transferee. The transferer
+ * channel is passed in as a parameter to ensure we don't return it as
+ * the transferee channel.
+ *
+ * \param channels A two-channel container containing the transferer and transferee
+ * \param transferer The party that is transfering the call
+ * \return The party that is being transferred
+ */
+static struct ast_channel *get_transferee(struct ao2_container *channels, struct ast_channel *transferer)
+{
+	struct ao2_iterator channel_iter;
+	struct ast_channel *transferee;
+
+	for (channel_iter = ao2_iterator_init(channels, 0);
+			(transferee = ao2_iterator_next(&channel_iter));
+			ao2_cleanup(transferee)) {
+		if (transferee != transferer) {
+			break;
+		}
 	}
 
-	return 0;
+	ao2_iterator_destroy(&channel_iter);
+	return transferee;
 }
 
-int ast_bridge_depart(struct ast_bridge *bridge, struct ast_channel *chan)
+/*!
+ * \internal
+ * \brief Queue a blind transfer action on a transferee bridge channel
+ *
+ * This is only relevant for when a blind transfer is performed on a two-party
+ * bridge. The transferee's bridge channel will have a blind transfer bridge
+ * action queued onto it, resulting in the party being redirected to a new
+ * destination
+ *
+ * \param transferee The channel to have the action queued on
+ * \param exten The destination extension for the transferee
+ * \param context The destination context for the transferee
+ * \param hook Frame hook to attach to transferee
+ * \retval 0 Successfully queued the action
+ * \retval non-zero Failed to queue the action
+ */
+static int bridge_channel_queue_blind_transfer(struct ast_channel *transferee,
+		const char *exten, const char *context,
+		transfer_channel_cb new_channel_cb, void *user_data)
 {
-	struct ast_bridge_channel *bridge_channel;
-	pthread_t thread;
+	RAII_VAR(struct ast_bridge_channel *, transferee_bridge_channel, NULL, ao2_cleanup);
+	struct blind_transfer_data blind_data;
 
-	ao2_lock(bridge);
+	ast_channel_lock(transferee);
+	transferee_bridge_channel = ast_channel_get_bridge_channel(transferee);
+	ast_channel_unlock(transferee);
 
-	/* Try to find the channel that we want to depart */
-	if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
-		ao2_unlock(bridge);
+	if (!transferee_bridge_channel) {
 		return -1;
 	}
 
-	ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_DEPART);
-	thread = bridge_channel->thread;
+	if (new_channel_cb) {
+		new_channel_cb(transferee, user_data);
+	}
 
-	ao2_unlock(bridge);
+	ast_copy_string(blind_data.exten, exten, sizeof(blind_data.exten));
+	ast_copy_string(blind_data.context, context, sizeof(blind_data.context));
 
-	pthread_join(thread, NULL);
+/* BUGBUG Why doesn't this function return success/failure? */
+	ast_bridge_channel_queue_action_data(transferee_bridge_channel,
+			AST_BRIDGE_ACTION_BLIND_TRANSFER, &blind_data, sizeof(blind_data));
 
 	return 0;
 }
 
-int ast_bridge_remove(struct ast_bridge *bridge, struct ast_channel *chan)
+enum try_parking_result {
+	PARKING_SUCCESS,
+	PARKING_FAILURE,
+	PARKING_NOT_APPLICABLE,
+};
+
+static enum try_parking_result try_parking(struct ast_bridge *bridge, struct ast_channel *transferer,
+		const char *exten, const char *context)
 {
-	struct ast_bridge_channel *bridge_channel;
+	/* BUGBUG The following is all commented out because the functionality is not
+	 * present yet. The functions referenced here are available at team/jrose/bridge_projects.
+	 * Once the code there has been merged into team/group/bridge_construction,
+	 * this can be uncommented and tested
+	 */
 
-	ao2_lock(bridge);
+#if 0
+	RAII_VAR(struct ast_bridge_channel *, transferer_bridge_channel, NULL, ao2_cleanup);
+	struct ast_exten *parking_exten;
 
-	/* Try to find the channel that we want to remove */
-	if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
-		ao2_unlock(bridge);
-		return -1;
-	}
+	ast_channel_lock(transferer);
+	transfer_bridge_channel = ast_channel_get_bridge_channel(transferer);
+	ast_channel_unlock(transferer);
 
-	ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+	if (!transfer_bridge_channel) {
+		return PARKING_FAILURE;
+	}
 
-	ao2_unlock(bridge);
+	parking_exten = ast_get_parking_exten(exten, NULL, context);
+	if (parking_exten) {
+		return ast_park_blind_xfer(bridge, transferer, parking_exten) == 0 ?
+			PARKING_SUCCESS : PARKING_FAILURE;
+	}
+#endif
 
-	return 0;
+	return PARKING_NOT_APPLICABLE;
 }
 
-int ast_bridge_merge(struct ast_bridge *bridge0, struct ast_bridge *bridge1)
+/*!
+ * \internal
+ * \brief Set the BLINDTRANSFER variable as appropriate on channels involved in the transfer
+ *
+ * The transferer channel will have its BLINDTRANSFER variable set the same as its BRIDGEPEER
+ * variable. This will account for all channels that it is bridged to. The other channels
+ * involved in the transfer will have their BLINDTRANSFER variable set to the transferer
+ * channel's name.
+ *
+ * \param transferer The channel performing the blind transfer
+ * \param channels The channels belonging to the bridge
+ */
+static void set_blind_transfer_variables(struct ast_channel *transferer, struct ao2_container *channels)
 {
-	struct ast_bridge_channel *bridge_channel;
+	struct ao2_iterator iter;
+	struct ast_channel *chan;
+	const char *transferer_name;
+	const char *transferer_bridgepeer;
+
+	ast_channel_lock(transferer);
+	transferer_name = ast_strdupa(ast_channel_name(transferer));
+	transferer_bridgepeer = ast_strdupa(S_OR(pbx_builtin_getvar_helper(transferer, "BRIDGEPEER"), ""));
+	ast_channel_unlock(transferer);
+
+	for (iter = ao2_iterator_init(channels, 0);
+			(chan = ao2_iterator_next(&iter));
+			ao2_cleanup(chan)) {
+		if (chan == transferer) {
+			pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", transferer_bridgepeer);
+		} else {
+			pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", transferer_name);
+		}
+	}
 
-	ao2_lock(bridge0);
-	ao2_lock(bridge1);
+	ao2_iterator_destroy(&iter);
+}
 
-	/* If the first bridge currently has 2 channels and is not capable of becoming a multimixing bridge we can not merge */
-	if (bridge0->num + bridge1->num > 2
-		&& !(bridge0->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)
-		&& !ast_test_flag(&bridge0->feature_flags, AST_BRIDGE_FLAG_SMART)) {
-		ao2_unlock(bridge1);
-		ao2_unlock(bridge0);
-		ast_debug(1, "Can't merge bridge %p into bridge %p, multimix is needed and it could not be acquired.\n", bridge1, bridge0);
-		return -1;
-	}
+enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transferer,
+		const char *exten, const char *context,
+		transfer_channel_cb new_channel_cb, void *user_data)
+{
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+	RAII_VAR(struct ao2_container *, channels, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_channel *, transferee, NULL, ao2_cleanup);
+	int do_bridge_transfer;
+	int transfer_prohibited;
+	enum try_parking_result parking_result;
 
-	ast_debug(1, "Merging channels from bridge %p into bridge %p\n", bridge1, bridge0);
+	ast_channel_lock(transferer);
+	bridge = ast_channel_get_bridge(transferer);
+	ast_channel_unlock(transferer);
 
-	/* Perform smart bridge operation on bridge we are merging into so it can change bridge technology if needed */
-	if (smart_bridge_operation(bridge0, NULL, bridge0->num + bridge1->num)) {
-		ao2_unlock(bridge1);
-		ao2_unlock(bridge0);
-		ast_debug(1, "Can't merge bridge %p into bridge %p, tried to perform smart bridge operation and failed.\n", bridge1, bridge0);
-		return -1;
+	if (!bridge) {
+		return AST_BRIDGE_TRANSFER_INVALID;
 	}
 
-	/* If a thread is currently executing on bridge1 tell it to stop */
-	if (bridge1->thread) {
-		ast_debug(1, "Telling bridge thread on bridge %p to stop as it is being merged into %p\n", bridge1, bridge0);
-		bridge1->thread = AST_PTHREADT_STOP;
+	parking_result = try_parking(bridge, transferer, exten, context);
+	switch (parking_result) {
+	case PARKING_SUCCESS:
+		return AST_BRIDGE_TRANSFER_SUCCESS;
+	case PARKING_FAILURE:
+		return AST_BRIDGE_TRANSFER_FAIL;
+	case PARKING_NOT_APPLICABLE:
+	default:
+		break;
 	}
 
-	/* Move channels from bridge1 over to bridge0 */
-	while ((bridge_channel = AST_LIST_REMOVE_HEAD(&bridge1->channels, entry))) {
-		/* Tell the technology handling bridge1 that the bridge channel is leaving */
-		if (bridge1->technology->leave) {
-			ast_debug(1, "Giving bridge technology %s notification that %p is leaving bridge %p\n", bridge1->technology->name, bridge_channel, bridge1);
-			if (bridge1->technology->leave(bridge1, bridge_channel)) {
-				ast_debug(1, "Bridge technology %s failed to allow %p to leave bridge %p\n", bridge1->technology->name, bridge_channel, bridge1);
-			}
+	{
+		SCOPED_LOCK(lock, bridge, ast_bridge_lock, ast_bridge_unlock);
+		channels = ast_bridge_peers_nolock(bridge);
+		if (!channels) {
+			return AST_BRIDGE_TRANSFER_FAIL;
 		}
+		if (ao2_container_count(channels) <= 1) {
+			return AST_BRIDGE_TRANSFER_INVALID;
+		}
+		transfer_prohibited = ast_test_flag(&bridge->feature_flags,
+				AST_BRIDGE_FLAG_TRANSFER_PROHIBITED);
+		do_bridge_transfer = ast_test_flag(&bridge->feature_flags,
+				AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY) ||
+				ao2_container_count(channels) > 2;
+	}
 
-		/* Drop channel count and reference count on the bridge they are leaving */
-		bridge1->num--;
-		ao2_ref(bridge1, -1);
-
-		bridge_array_remove(bridge1, bridge_channel->chan);
+	if (transfer_prohibited) {
+		return AST_BRIDGE_TRANSFER_NOT_PERMITTED;
+	}
 
-		/* Now add them into the bridge they are joining, increase channel count, and bump up reference count */
-		bridge_channel->bridge = bridge0;
-		AST_LIST_INSERT_TAIL(&bridge0->channels, bridge_channel, entry);
-		bridge0->num++;
-		ao2_ref(bridge0, +1);
+	set_blind_transfer_variables(transferer, channels);
 
-		bridge_array_add(bridge0, bridge_channel->chan);
+	if (do_bridge_transfer) {
+		return blind_transfer_bridge(transferer, bridge, exten, context,
+				new_channel_cb, user_data);
+	}
 
-		/* Make the channel compatible with the new bridge it is joining or else formats would go amuck */
-		bridge_make_compatible(bridge0, bridge_channel);
+	/* Reaching this portion means that we're dealing with a two-party bridge */
 
-		/* Tell the technology handling bridge0 that the bridge channel is joining */
-		if (bridge0->technology->join) {
-			ast_debug(1, "Giving bridge technology %s notification that %p is joining bridge %p\n", bridge0->technology->name, bridge_channel, bridge0);
-			if (bridge0->technology->join(bridge0, bridge_channel)) {
-				ast_debug(1, "Bridge technology %s failed to join %p to bridge %p\n", bridge0->technology->name, bridge_channel, bridge0);
-			}
-		}
+	transferee = get_transferee(channels, transferer);
+	if (!transferee) {
+		return AST_BRIDGE_TRANSFER_FAIL;
+	}
 
-		/* Poke the bridge channel, this will cause it to wake up and execute the proper threading model for the new bridge it is in */
-		bridge_channel_poke(bridge_channel);
+	if (bridge_channel_queue_blind_transfer(transferee, exten, context,
+				new_channel_cb, user_data)) {
+		return AST_BRIDGE_TRANSFER_FAIL;
 	}
 
-	ast_debug(1, "Merged channels from bridge %p into bridge %p\n", bridge1, bridge0);
+	ast_bridge_remove(bridge, transferer);
+	return AST_BRIDGE_TRANSFER_SUCCESS;
+}
 
-	ao2_unlock(bridge1);
-	ao2_unlock(bridge0);
+enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_transferee,
+		struct ast_channel *to_transfer_target, struct ast_framehook *hook)
+{
+	/* First, check the validity of scenario. If invalid, return AST_BRIDGE_TRANSFER_INVALID. The following are invalid:
+	 *     1) atxfer of an unbridged call to an unbridged call
+	 *     2) atxfer of an unbridged call to a multi-party (n > 2) bridge
+	 *     3) atxfer of a multi-party (n > 2) bridge to an unbridged call
+	 * Second, check that the bridge(s) involved allows transfers. If not, return AST_BRIDGE_TRANSFER_NOT_PERMITTED.
+	 * Third, break into different scenarios for different bridge situations:
+	 * If both channels are bridged, perform a bridge merge. Direction of the merge is TBD.
+	 * If channel A is bridged, and channel B is not (e.g. transferring to IVR or blond transfer)
+	 *     Some manner of masquerading is necessary. Presumably, you'd want to move channel A's bridge peer
+	 *     into where channel B is. However, it may be possible to do something a bit different, where a
+	 *     local channel is created and put into channel A's bridge. The local channel's ;2 channel
+	 *     is then masqueraded with channel B in some way.
+	 * If channel A is not bridged and channel B is, then:
+	 *     This is similar to what is done in the previous scenario. Create a local channel and place it
+	 *     into B's bridge. Then masquerade the ;2 leg of the local channel.
+	 */
 
-	return 0;
+	/* XXX STUB */
+	return AST_BRIDGE_TRANSFER_SUCCESS;
 }
 
-int ast_bridge_suspend(struct ast_bridge *bridge, struct ast_channel *chan)
+/*!
+ * \internal
+ * \brief Service the bridge manager request.
+ * \since 12.0.0
+ *
+ * \param bridge requesting service.
+ *
+ * \return Nothing
+ */
+static void bridge_manager_service(struct ast_bridge *bridge)
 {
-	struct ast_bridge_channel *bridge_channel;
+	ast_bridge_lock(bridge);
+	if (bridge->callid) {
+		ast_callid_threadassoc_change(bridge->callid);
+	}
 
-	ao2_lock(bridge);
+	/* Do any pending bridge actions. */
+	bridge_handle_actions(bridge);
+	ast_bridge_unlock(bridge);
+}
 
-	if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
-		ao2_unlock(bridge);
-		return -1;
-	}
+/*!
+ * \internal
+ * \brief Bridge manager service thread.
+ * \since 12.0.0
+ *
+ * \return Nothing
+ */
+static void *bridge_manager_thread(void *data)
+{
+	struct bridge_manager_controller *manager = data;
+	struct bridge_manager_request *request;
+
+	ao2_lock(manager);
+	while (!manager->stop) {
+		request = AST_LIST_REMOVE_HEAD(&manager->service_requests, node);
+		if (!request) {
+			ast_cond_wait(&manager->cond, ao2_object_get_lockaddr(manager));
+			continue;
+		}
+		ao2_unlock(manager);
 
-	bridge_channel_suspend(bridge, bridge_channel);
+		/* Service the bridge. */
+		bridge_manager_service(request->bridge);
+		ao2_ref(request->bridge, -1);
+		ast_free(request);
 
-	ao2_unlock(bridge);
+		ao2_lock(manager);
+	}
+	ao2_unlock(manager);
 
-	return 0;
+	return NULL;
 }
 
-int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan)
+/*!
+ * \internal
+ * \brief Destroy the bridge manager controller.
+ * \since 12.0.0
+ *
+ * \param obj Bridge manager to destroy.
+ *
+ * \return Nothing
+ */
+static void bridge_manager_destroy(void *obj)
 {
-	struct ast_bridge_channel *bridge_channel;
-
-	ao2_lock(bridge);
+	struct bridge_manager_controller *manager = obj;
+	struct bridge_manager_request *request;
+
+	if (manager->thread != AST_PTHREADT_NULL) {
+		/* Stop the manager thread. */
+		ao2_lock(manager);
+		manager->stop = 1;
+		ast_cond_signal(&manager->cond);
+		ao2_unlock(manager);
+		ast_debug(1, "Waiting for bridge manager thread to die.\n");
+		pthread_join(manager->thread, NULL);
+	}
 
-	if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
-		ao2_unlock(bridge);
-		return -1;
+	/* Destroy the service request queue. */
+	while ((request = AST_LIST_REMOVE_HEAD(&manager->service_requests, node))) {
+		ao2_ref(request->bridge, -1);
+		ast_free(request);
 	}
 
-	bridge_channel_unsuspend(bridge, bridge_channel);
+	ast_cond_destroy(&manager->cond);
+}
+
+/*!
+ * \internal
+ * \brief Create the bridge manager controller.
+ * \since 12.0.0
+ *
+ * \retval manager on success.
+ * \retval NULL on error.
+ */
+static struct bridge_manager_controller *bridge_manager_create(void)
+{
+	struct bridge_manager_controller *manager;
 
-	ao2_unlock(bridge);
+	manager = ao2_alloc(sizeof(*manager), bridge_manager_destroy);
+	if (!manager) {
+		/* Well. This isn't good. */
+		return NULL;
+	}
+	ast_cond_init(&manager->cond, NULL);
+	AST_LIST_HEAD_INIT_NOLOCK(&manager->service_requests);
+
+	/* Create the bridge manager thread. */
+	if (ast_pthread_create(&manager->thread, NULL, bridge_manager_thread, manager)) {
+		/* Well. This isn't good either. */
+		manager->thread = AST_PTHREADT_NULL;
+		ao2_ref(manager, -1);
+		manager = NULL;
+	}
 
-	return 0;
+	return manager;
 }
 
-void ast_bridge_technology_suspend(struct ast_bridge_technology *technology)
+/*!
+ * \internal
+ * \brief Bridge ao2 container sort function.
+ * \since 12.0.0
+ *
+ * \param obj_left pointer to the (user-defined part) of an object.
+ * \param obj_right pointer to the (user-defined part) of an object.
+ * \param flags flags from ao2_callback()
+ *   OBJ_POINTER - if set, 'obj_right', is an object.
+ *   OBJ_KEY - if set, 'obj_right', is a search key item that is not an object.
+ *   OBJ_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object.
+ *
+ * \retval <0 if obj_left < obj_right
+ * \retval =0 if obj_left == obj_right
+ * \retval >0 if obj_left > obj_right
+ */
+static int bridge_sort_cmp(const void *obj_left, const void *obj_right, int flags)
 {
-	technology->suspended = 1;
+	const struct ast_bridge *bridge_left = obj_left;
+	const struct ast_bridge *bridge_right = obj_right;
+	const char *right_key = obj_right;
+	int cmp;
+
+	switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+	default:
+	case OBJ_POINTER:
+		right_key = bridge_right->uniqueid;
+		/* Fall through */
+	case OBJ_KEY:
+		cmp = strcmp(bridge_left->uniqueid, right_key);
+		break;
+	case OBJ_PARTIAL_KEY:
+		cmp = strncmp(bridge_left->uniqueid, right_key, strlen(right_key));
+		break;
+	}
+	return cmp;
 }
 
-void ast_bridge_technology_unsuspend(struct ast_bridge_technology *technology)
+struct bridge_complete {
+	/*! Nth match to return. */
+	int state;
+	/*! Which match currently on. */
+	int which;
+};
+
+static int complete_bridge_search(void *obj, void *arg, void *data, int flags)
 {
-	technology->suspended = 0;
+	struct bridge_complete *search = data;
+
+	if (++search->which > search->state) {
+		return CMP_MATCH;
+	}
+	return 0;
 }
 
-int ast_bridge_features_register(enum ast_bridge_builtin_feature feature, ast_bridge_features_hook_callback callback, const char *dtmf)
+static char *complete_bridge(const char *word, int state)
 {
-	if (ARRAY_LEN(builtin_features_handlers) <= feature
-		|| builtin_features_handlers[feature]) {
-		return -1;
+	char *ret;
+	struct ast_bridge *bridge;
+	struct bridge_complete search = {
+		.state = state,
+		};
+
+	bridge = ao2_callback_data(bridges, ast_strlen_zero(word) ? 0 : OBJ_PARTIAL_KEY,
+		complete_bridge_search, (char *) word, &search);
+	if (!bridge) {
+		return NULL;
 	}
+	ret = ast_strdup(bridge->uniqueid);
+	ao2_ref(bridge, -1);
+	return ret;
+}
 
-	if (!ast_strlen_zero(dtmf)) {
-		ast_copy_string(builtin_features_dtmf[feature], dtmf, sizeof(builtin_features_dtmf[feature]));
+static char *handle_bridge_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT_HDR "%-36s %5s %-15s %s\n"
+#define FORMAT_ROW "%-36s %5u %-15s %s\n"
+
+	struct ao2_iterator iter;
+	struct ast_bridge *bridge;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "bridge show all";
+		e->usage =
+			"Usage: bridge show all\n"
+			"       List all bridges\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
 	}
 
-	builtin_features_handlers[feature] = callback;
+/* BUGBUG this command may need to be changed to look at the stasis cache. */
+	ast_cli(a->fd, FORMAT_HDR, "Bridge-ID", "Chans", "Type", "Technology");
+	iter = ao2_iterator_init(bridges, 0);
+	for (; (bridge = ao2_iterator_next(&iter)); ao2_ref(bridge, -1)) {
+		ast_bridge_lock(bridge);
+		ast_cli(a->fd, FORMAT_ROW,
+			bridge->uniqueid,
+			bridge->num_channels,
+			bridge->v_table ? bridge->v_table->name : "<unknown>",
+			bridge->technology ? bridge->technology->name : "<unknown>");
+		ast_bridge_unlock(bridge);
+	}
+	ao2_iterator_destroy(&iter);
+	return CLI_SUCCESS;
 
-	return 0;
+#undef FORMAT_HDR
+#undef FORMAT_ROW
 }
 
-int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature)
+static char *handle_bridge_show_specific(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-	if (ARRAY_LEN(builtin_features_handlers) <= feature
-		|| !builtin_features_handlers[feature]) {
-		return -1;
+	struct ast_bridge *bridge;
+	struct ast_bridge_channel *bridge_channel;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "bridge show";
+		e->usage =
+			"Usage: bridge show <bridge-id>\n"
+			"       Show information about the <bridge-id> bridge\n";
+		return NULL;
+	case CLI_GENERATE:
+		if (a->pos == 2) {
+			return complete_bridge(a->word, a->n);
+		}
+		return NULL;
+	}
+
+/* BUGBUG this command may need to be changed to look at the stasis cache. */
+	if (a->argc != 3) {
+		return CLI_SHOWUSAGE;
 	}
 
-	builtin_features_handlers[feature] = NULL;
+	bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+	if (!bridge) {
+		ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]);
+		return CLI_SUCCESS;
+	}
 
-	return 0;
+	ast_bridge_lock(bridge);
+	ast_cli(a->fd, "Id: %s\n", bridge->uniqueid);
+	ast_cli(a->fd, "Type: %s\n", bridge->v_table ? bridge->v_table->name : "<unknown>");
+	ast_cli(a->fd, "Technology: %s\n",
+		bridge->technology ? bridge->technology->name : "<unknown>");
+	ast_cli(a->fd, "Num-Channels: %u\n", bridge->num_channels);
+	AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+		ast_cli(a->fd, "Channel: %s\n", ast_channel_name(bridge_channel->chan));
+	}
+	ast_bridge_unlock(bridge);
+	ao2_ref(bridge, -1);
+
+	return CLI_SUCCESS;
 }
 
-int ast_bridge_features_hook(struct ast_bridge_features *features,
-	const char *dtmf,
-	ast_bridge_features_hook_callback callback,
-	void *hook_pvt,
-	ast_bridge_features_hook_pvt_destructor destructor)
+static char *handle_bridge_destroy_specific(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-	struct ast_bridge_features_hook *hook;
+	struct ast_bridge *bridge;
 
-	/* Allocate new memory and setup it's various variables */
-	hook = ast_calloc(1, sizeof(*hook));
-	if (!hook) {
-		return -1;
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "bridge destroy";
+		e->usage =
+			"Usage: bridge destroy <bridge-id>\n"
+			"       Destroy the <bridge-id> bridge\n";
+		return NULL;
+	case CLI_GENERATE:
+		if (a->pos == 2) {
+			return complete_bridge(a->word, a->n);
+		}
+		return NULL;
 	}
-	ast_copy_string(hook->dtmf, dtmf, sizeof(hook->dtmf));
-	hook->callback = callback;
-	hook->destructor = destructor;
-	hook->hook_pvt = hook_pvt;
 
-	/* Once done we add it onto the list. Now it will be picked up when DTMF is used */
-	AST_LIST_INSERT_TAIL(&features->hooks, hook, entry);
+	if (a->argc != 3) {
+		return CLI_SHOWUSAGE;
+	}
 
-	features->usable = 1;
+	bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+	if (!bridge) {
+		ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]);
+		return CLI_SUCCESS;
+	}
 
-	return 0;
-}
+	ast_cli(a->fd, "Destroying bridge '%s'\n", a->argv[2]);
+	ast_bridge_destroy(bridge);
 
-void ast_bridge_features_set_talk_detector(struct ast_bridge_features *features,
-	ast_bridge_talking_indicate_callback talker_cb,
-	ast_bridge_talking_indicate_destructor talker_destructor,
-	void *pvt_data)
-{
-	features->talker_cb = talker_cb;
-	features->talker_destructor_cb = talker_destructor;
-	features->talker_pvt_data = pvt_data;
+	return CLI_SUCCESS;
 }
 
-int ast_bridge_features_enable(struct ast_bridge_features *features, enum ast_bridge_builtin_feature feature, const char *dtmf, void *config)
+static char *complete_bridge_participant(const char *bridge_name, const char *line, const char *word, int pos, int state)
 {
-	if (ARRAY_LEN(builtin_features_handlers) <= feature
-		|| !builtin_features_handlers[feature]) {
-		return -1;
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+	struct ast_bridge_channel *bridge_channel;
+	int which;
+	int wordlen;
+
+	bridge = ao2_find(bridges, bridge_name, OBJ_KEY);
+	if (!bridge) {
+		return NULL;
 	}
 
-	/* If no alternate DTMF stream was provided use the default one */
-	if (ast_strlen_zero(dtmf)) {
-		dtmf = builtin_features_dtmf[feature];
-		/* If no DTMF is still available (ie: it has been disabled) then error out now */
-		if (ast_strlen_zero(dtmf)) {
-			ast_debug(1, "Failed to enable built in feature %d on %p, no DTMF string is available for it.\n", feature, features);
-			return -1;
+	{
+		SCOPED_LOCK(bridge_lock, bridge, ast_bridge_lock, ast_bridge_unlock);
+
+		which = 0;
+		wordlen = strlen(word);
+		AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+			if (!strncasecmp(ast_channel_name(bridge_channel->chan), word, wordlen)
+				&& ++which > state) {
+				return ast_strdup(ast_channel_name(bridge_channel->chan));
+			}
 		}
 	}
 
-	/* The rest is basically pretty easy. We create another hook using the built in feature's callback and DTMF, easy as pie. */
-	return ast_bridge_features_hook(features, dtmf, builtin_features_handlers[feature], config, NULL);
+	return NULL;
 }
 
-void ast_bridge_features_set_flag(struct ast_bridge_features *features, enum ast_bridge_feature_flags flag)
+static char *handle_bridge_kick_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-	ast_set_flag(&features->feature_flags, flag);
-	features->usable = 1;
-}
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup);
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "bridge kick";
+		e->usage =
+			"Usage: bridge kick <bridge-id> <channel-name>\n"
+			"       Kick the <channel-name> channel out of the <bridge-id> bridge\n";
+		return NULL;
+	case CLI_GENERATE:
+		if (a->pos == 2) {
+			return complete_bridge(a->word, a->n);
+		}
+		if (a->pos == 3) {
+			return complete_bridge_participant(a->argv[2], a->line, a->word, a->pos, a->n);
+		}
+		return NULL;
+	}
 
-int ast_bridge_features_init(struct ast_bridge_features *features)
-{
-	/* Zero out the structure */
-	memset(features, 0, sizeof(*features));
+	if (a->argc != 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+	if (!bridge) {
+		ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]);
+		return CLI_SUCCESS;
+	}
 
-	/* Initialize the hooks list, just in case */
-	AST_LIST_HEAD_INIT_NOLOCK(&features->hooks);
+	chan = ast_channel_get_by_name_prefix(a->argv[3], strlen(a->argv[3]));
+	if (!chan) {
+		ast_cli(a->fd, "Channel '%s' not found\n", a->argv[3]);
+		return CLI_SUCCESS;
+	}
 
-	return 0;
+/*
+ * BUGBUG the CLI kick needs to get the bridge to decide if it should dissolve.
+ *
+ * Likely the best way to do this is to add a kick method.  The
+ * basic bridge class can then decide to dissolve the bridge if
+ * one of two channels is kicked.
+ *
+ * SIP/foo -- Local;1==Local;2 -- .... -- Local;1==Local;2 -- SIP/bar
+ * Kick a ;1 channel and the chain toward SIP/foo goes away.
+ * Kick a ;2 channel and the chain toward SIP/bar goes away.
+ *
+ * This can leave a local channel chain between the kicked ;1
+ * and ;2 channels that are orphaned until you manually request
+ * one of those channels to hangup or request the bridge to
+ * dissolve.
+ */
+	ast_cli(a->fd, "Kicking channel '%s' from bridge '%s'\n",
+		ast_channel_name(chan), a->argv[2]);
+	ast_bridge_remove(bridge, chan);
+
+	return CLI_SUCCESS;
 }
 
-void ast_bridge_features_cleanup(struct ast_bridge_features *features)
+/*! Bridge technology capabilities to string. */
+static const char *tech_capability2str(uint32_t capabilities)
 {
-	struct ast_bridge_features_hook *hook;
-
-	/* This is relatively simple, hooks are kept as a list on the features structure so we just pop them off and free them */
-	while ((hook = AST_LIST_REMOVE_HEAD(&features->hooks, entry))) {
-		if (hook->destructor) {
-			hook->destructor(hook->hook_pvt);
-		}
-		ast_free(hook);
-	}
-	if (features->talker_destructor_cb && features->talker_pvt_data) {
-		features->talker_destructor_cb(features->talker_pvt_data);
-		features->talker_pvt_data = NULL;
+	const char *type;
+
+	if (capabilities & AST_BRIDGE_CAPABILITY_HOLDING) {
+		type = "Holding";
+	} else if (capabilities & AST_BRIDGE_CAPABILITY_EARLY) {
+		type = "Early";
+	} else if (capabilities & AST_BRIDGE_CAPABILITY_NATIVE) {
+		type = "Native";
+	} else if (capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX) {
+		type = "1to1Mix";
+	} else if (capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) {
+		type = "MultiMix";
+	} else {
+		type = "<Unknown>";
 	}
+	return type;
 }
 
-int ast_bridge_dtmf_stream(struct ast_bridge *bridge, const char *dtmf, struct ast_channel *chan)
+static char *handle_bridge_technology_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-	struct ast_bridge_channel *bridge_channel;
+#define FORMAT_HDR "%-20s %-20s %8s %s\n"
+#define FORMAT_ROW "%-20s %-20s %8d %s\n"
 
-	ao2_lock(bridge);
+	struct ast_bridge_technology *cur;
 
-	AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
-		if (bridge_channel->chan == chan) {
-			continue;
-		}
-		ast_copy_string(bridge_channel->dtmf_stream_q, dtmf, sizeof(bridge_channel->dtmf_stream_q));
-		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_DTMF);
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "bridge technology show";
+		e->usage =
+			"Usage: bridge technology show\n"
+			"       List registered bridge technologies\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
 	}
 
-	ao2_unlock(bridge);
+	ast_cli(a->fd, FORMAT_HDR, "Name", "Type", "Priority", "Suspended");
+	AST_RWLIST_RDLOCK(&bridge_technologies);
+	AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+		const char *type;
 
-	return 0;
-}
+		/* Decode type for display */
+		type = tech_capability2str(cur->capabilities);
 
-void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixing_interval)
-{
-	ao2_lock(bridge);
-	bridge->internal_mixing_interval = mixing_interval;
-	ao2_unlock(bridge);
+		ast_cli(a->fd, FORMAT_ROW, cur->name, type, cur->preference,
+			AST_CLI_YESNO(cur->suspended));
+	}
+	AST_RWLIST_UNLOCK(&bridge_technologies);
+	return CLI_SUCCESS;
+
+#undef FORMAT
 }
 
-void ast_bridge_set_internal_sample_rate(struct ast_bridge *bridge, unsigned int sample_rate)
+static char *complete_bridge_technology(const char *word, int state)
 {
+	struct ast_bridge_technology *cur;
+	char *res;
+	int which;
+	int wordlen;
 
-	ao2_lock(bridge);
-	bridge->internal_sample_rate = sample_rate;
-	ao2_unlock(bridge);
+	which = 0;
+	wordlen = strlen(word);
+	AST_RWLIST_RDLOCK(&bridge_technologies);
+	AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+		if (!strncasecmp(cur->name, word, wordlen) && ++which > state) {
+			res = ast_strdup(cur->name);
+			AST_RWLIST_UNLOCK(&bridge_technologies);
+			return res;
+		}
+	}
+	AST_RWLIST_UNLOCK(&bridge_technologies);
+	return NULL;
 }
 
-static void cleanup_video_mode(struct ast_bridge *bridge)
+static char *handle_bridge_technology_suspend(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-	switch (bridge->video_mode.mode) {
-	case AST_BRIDGE_VIDEO_MODE_NONE:
-		break;
-	case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
-		if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) {
-			ast_channel_unref(bridge->video_mode.mode_data.single_src_data.chan_vsrc);
+	struct ast_bridge_technology *cur;
+	int suspend;
+	int successful;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "bridge technology {suspend|unsuspend}";
+		e->usage =
+			"Usage: bridge technology {suspend|unsuspend} <technology-name>\n"
+			"       Suspend or unsuspend a bridge technology.\n";
+		return NULL;
+	case CLI_GENERATE:
+		if (a->pos == 3) {
+			return complete_bridge_technology(a->word, a->n);
 		}
-		break;
-	case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
-		if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) {
-			ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_vsrc);
+		return NULL;
+	}
+
+	if (a->argc != 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	suspend = !strcasecmp(a->argv[2], "suspend");
+	successful = 0;
+	AST_RWLIST_WRLOCK(&bridge_technologies);
+	AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+		if (!strcasecmp(cur->name, a->argv[3])) {
+			successful = 1;
+			if (suspend) {
+				ast_bridge_technology_suspend(cur);
+			} else {
+				ast_bridge_technology_unsuspend(cur);
+			}
+			break;
 		}
-		if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) {
-			ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc);
+	}
+	AST_RWLIST_UNLOCK(&bridge_technologies);
+
+	if (successful) {
+		if (suspend) {
+			ast_cli(a->fd, "Suspended bridge technology '%s'\n", a->argv[3]);
+		} else {
+			ast_cli(a->fd, "Unsuspended bridge technology '%s'\n", a->argv[3]);
 		}
+	} else {
+		ast_cli(a->fd, "Bridge technology '%s' not found\n", a->argv[3]);
 	}
-	memset(&bridge->video_mode, 0, sizeof(bridge->video_mode));
-}
 
-void ast_bridge_set_single_src_video_mode(struct ast_bridge *bridge, struct ast_channel *video_src_chan)
-{
-	ao2_lock(bridge);
-	cleanup_video_mode(bridge);
-	bridge->video_mode.mode = AST_BRIDGE_VIDEO_MODE_SINGLE_SRC;
-	bridge->video_mode.mode_data.single_src_data.chan_vsrc = ast_channel_ref(video_src_chan);
-	ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to single source\r\nVideo Mode: %d\r\nVideo Channel: %s", bridge->video_mode.mode, ast_channel_name(video_src_chan));
-	ast_indicate(video_src_chan, AST_CONTROL_VIDUPDATE);
-	ao2_unlock(bridge);
+	return CLI_SUCCESS;
 }
 
-void ast_bridge_set_talker_src_video_mode(struct ast_bridge *bridge)
+static struct ast_cli_entry bridge_cli[] = {
+	AST_CLI_DEFINE(handle_bridge_show_all, "List all bridges"),
+	AST_CLI_DEFINE(handle_bridge_show_specific, "Show information about a bridge"),
+	AST_CLI_DEFINE(handle_bridge_destroy_specific, "Destroy a bridge"),
+	AST_CLI_DEFINE(handle_bridge_kick_channel, "Kick a channel from a bridge"),
+	AST_CLI_DEFINE(handle_bridge_technology_show, "List registered bridge technologies"),
+	AST_CLI_DEFINE(handle_bridge_technology_suspend, "Suspend/unsuspend a bridge technology"),
+};
+
+/*!
+ * \internal
+ * \brief Shutdown the bridging system.
+ * \since 12.0.0
+ *
+ * \return Nothing
+ */
+static void bridge_shutdown(void)
 {
-	ao2_lock(bridge);
-	cleanup_video_mode(bridge);
-	bridge->video_mode.mode = AST_BRIDGE_VIDEO_MODE_TALKER_SRC;
-	ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to talker source\r\nVideo Mode: %d", bridge->video_mode.mode);
-	ao2_unlock(bridge);
+	ast_cli_unregister_multiple(bridge_cli, ARRAY_LEN(bridge_cli));
+	ao2_cleanup(bridges);
+	bridges = NULL;
+	ao2_cleanup(bridge_manager);
+	bridge_manager = NULL;
+	ast_stasis_bridging_shutdown();
 }
 
-void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct ast_channel *chan, int talker_energy, int is_keyframe)
+int ast_bridging_init(void)
 {
-	struct ast_bridge_video_talker_src_data *data;
-	/* If the channel doesn't support video, we don't care about it */
-	if (!ast_format_cap_has_type(ast_channel_nativeformats(chan), AST_FORMAT_TYPE_VIDEO)) {
-		return;
+	if (ast_stasis_bridging_init()) {
+		bridge_shutdown();
+		return -1;
 	}
 
-	ao2_lock(bridge);
-	data = &bridge->video_mode.mode_data.talker_src_data;
-
-	if (data->chan_vsrc == chan) {
-		data->average_talking_energy = talker_energy;
-	} else if ((data->average_talking_energy < talker_energy) && is_keyframe) {
-		if (data->chan_old_vsrc) {
-			ast_channel_unref(data->chan_old_vsrc);
-		}
-		if (data->chan_vsrc) {
-			data->chan_old_vsrc = data->chan_vsrc;
-			ast_indicate(data->chan_old_vsrc, AST_CONTROL_VIDUPDATE);
-		}
-		data->chan_vsrc = ast_channel_ref(chan);
-		data->average_talking_energy = talker_energy;
-		ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc));
-		ast_indicate(data->chan_vsrc, AST_CONTROL_VIDUPDATE);
-	} else if ((data->average_talking_energy < talker_energy) && !is_keyframe) {
-		ast_indicate(chan, AST_CONTROL_VIDUPDATE);
-	} else if (!data->chan_vsrc && is_keyframe) {
-		data->chan_vsrc = ast_channel_ref(chan);
-		data->average_talking_energy = talker_energy;
-		ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc));
-		ast_indicate(chan, AST_CONTROL_VIDUPDATE);
-	} else if (!data->chan_old_vsrc && is_keyframe) {
-		data->chan_old_vsrc = ast_channel_ref(chan);
-		ast_indicate(chan, AST_CONTROL_VIDUPDATE);
+	bridge_manager = bridge_manager_create();
+	if (!bridge_manager) {
+		bridge_shutdown();
+		return -1;
 	}
-	ao2_unlock(bridge);
-}
 
-int ast_bridge_number_video_src(struct ast_bridge *bridge)
-{
-	int res = 0;
-
-	ao2_lock(bridge);
-	switch (bridge->video_mode.mode) {
-	case AST_BRIDGE_VIDEO_MODE_NONE:
-		break;
-	case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
-		if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) {
-			res = 1;
-		}
-		break;
-	case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
-		if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) {
-			res++;
-		}
-		if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) {
-			res++;
-		}
+	bridges = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_MUTEX,
+		AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, bridge_sort_cmp, NULL);
+	if (!bridges) {
+		bridge_shutdown();
+		return -1;
 	}
-	ao2_unlock(bridge);
-	return res;
-}
-
-int ast_bridge_is_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
-{
-	int res = 0;
 
-	ao2_lock(bridge);
-	switch (bridge->video_mode.mode) {
-	case AST_BRIDGE_VIDEO_MODE_NONE:
-		break;
-	case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
-		if (bridge->video_mode.mode_data.single_src_data.chan_vsrc == chan) {
-			res = 1;
-		}
-		break;
-	case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
-		if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc == chan) {
-			res = 1;
-		} else if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc == chan) {
-			res = 2;
-		}
+	ast_bridging_init_basic();
 
-	}
-	ao2_unlock(bridge);
-	return res;
-}
+/* BUGBUG need AMI action equivalents to the CLI commands. */
+	ast_cli_register_multiple(bridge_cli, ARRAY_LEN(bridge_cli));
 
-void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
-{
-	ao2_lock(bridge);
-	switch (bridge->video_mode.mode) {
-	case AST_BRIDGE_VIDEO_MODE_NONE:
-		break;
-	case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
-		if (bridge->video_mode.mode_data.single_src_data.chan_vsrc == chan) {
-			if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) {
-				ast_channel_unref(bridge->video_mode.mode_data.single_src_data.chan_vsrc);
-			}
-			bridge->video_mode.mode_data.single_src_data.chan_vsrc = NULL;
-		}
-		break;
-	case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
-		if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc == chan) {
-			if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) {
-				ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_vsrc);
-			}
-			bridge->video_mode.mode_data.talker_src_data.chan_vsrc = NULL;
-			bridge->video_mode.mode_data.talker_src_data.average_talking_energy = 0;
-		}
-		if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc == chan) {
-			if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) {
-				ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc);
-			}
-			bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc = NULL;
-		}
-	}
-	ao2_unlock(bridge);
+	ast_register_atexit(bridge_shutdown);
+	return 0;
 }
diff --git a/main/bridging_basic.c b/main/bridging_basic.c
new file mode 100644
index 0000000000000000000000000000000000000000..00daf771021cb2b436b944e508fa5163cb47c29b
--- /dev/null
+++ b/main/bridging_basic.c
@@ -0,0 +1,159 @@
+/*
+ * 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 Basic bridge class.  It is a subclass of struct ast_bridge.
+ *
+ * \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/utils.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_basic.h"
+#include "asterisk/astobj2.h"
+
+/* ------------------------------------------------------------------- */
+
+static const struct ast_datastore_info dtmf_features_info = {
+	.type = "bridge-dtmf-features",
+	.destroy = ast_free_ptr,
+};
+
+int ast_bridge_features_ds_set(struct ast_channel *chan, struct ast_flags *flags)
+{
+	struct ast_datastore *datastore;
+	struct ast_flags *ds_flags;
+
+	datastore = ast_channel_datastore_find(chan, &dtmf_features_info, NULL);
+	if (datastore) {
+		ds_flags = datastore->data;
+		*ds_flags = *flags;
+		return 0;
+	}
+
+	datastore = ast_datastore_alloc(&dtmf_features_info, NULL);
+	if (!datastore) {
+		return -1;
+	}
+
+	ds_flags = ast_malloc(sizeof(*ds_flags));
+	if (!ds_flags) {
+		ast_datastore_free(datastore);
+		return -1;
+	}
+
+	*ds_flags = *flags;
+	datastore->data = ds_flags;
+	ast_channel_datastore_add(chan, datastore);
+	return 0;
+}
+
+struct ast_flags *ast_bridge_features_ds_get(struct ast_channel *chan)
+{
+	struct ast_datastore *datastore;
+
+	datastore = ast_channel_datastore_find(chan, &dtmf_features_info, NULL);
+	if (!datastore) {
+		return NULL;
+	}
+	return datastore->data;
+}
+
+/*!
+ * \internal
+ * \brief Determine if we should dissolve the bridge from a hangup.
+ * \since 12.0.0
+ *
+ * \param bridge The bridge that the channel is part of
+ * \param bridge_channel Channel executing the feature
+ * \param hook_pvt Private data passed in when the hook was created
+ *
+ * \retval 0 Keep the callback hook.
+ * \retval -1 Remove the callback hook.
+ */
+static int basic_hangup_hook(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+/* BUGBUG Race condition.  If all parties but one hangup at the same time, the bridge may not be dissolved on the remaining party. */
+	ast_bridge_channel_lock_bridge(bridge_channel);
+	if (2 < bridge_channel->bridge->num_channels) {
+		/* Just allow this channel to leave the multi-party bridge. */
+		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+	}
+	ast_bridge_unlock(bridge_channel->bridge);
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief ast_bridge basic push method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to push.
+ * \param swap Bridge channel to swap places with if not NULL.
+ *
+ * \note On entry, self is already locked.
+ * \note Stub because of nothing to do.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int bridge_basic_push(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
+{
+	if (ast_bridge_hangup_hook(bridge_channel->features, basic_hangup_hook, NULL, NULL, 1)
+		|| ast_bridge_channel_setup_features(bridge_channel)) {
+		return -1;
+	}
+
+	return ast_bridge_base_v_table.push(self, bridge_channel, swap);
+}
+
+struct ast_bridge_methods ast_bridge_basic_v_table;
+
+struct ast_bridge *ast_bridge_basic_new(void)
+{
+	void *bridge;
+
+	bridge = ast_bridge_alloc(sizeof(struct ast_bridge), &ast_bridge_basic_v_table);
+	bridge = ast_bridge_base_init(bridge,
+		AST_BRIDGE_CAPABILITY_NATIVE | AST_BRIDGE_CAPABILITY_1TO1MIX
+			| AST_BRIDGE_CAPABILITY_MULTIMIX,
+		AST_BRIDGE_FLAG_DISSOLVE_HANGUP | AST_BRIDGE_FLAG_DISSOLVE_EMPTY
+			| AST_BRIDGE_FLAG_SMART);
+	bridge = ast_bridge_register(bridge);
+	return bridge;
+}
+
+void ast_bridging_init_basic(void)
+{
+	/* Setup bridge basic subclass v_table. */
+	ast_bridge_basic_v_table = ast_bridge_base_v_table;
+	ast_bridge_basic_v_table.name = "basic";
+	ast_bridge_basic_v_table.push = bridge_basic_push;
+}
diff --git a/main/bridging_roles.c b/main/bridging_roles.c
new file mode 100644
index 0000000000000000000000000000000000000000..079cbdb333f69caf2f602b5510ecea8cbee6c6f9
--- /dev/null
+++ b/main/bridging_roles.c
@@ -0,0 +1,462 @@
+/*
+ * 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 Channel Bridging Roles API
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ *
+ * \ingroup bridges
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <signal.h>
+
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/datastore.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_roles.h"
+#include "asterisk/stringfields.h"
+
+struct bridge_role_option {
+	AST_LIST_ENTRY(bridge_role_option) list;
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(option);
+		AST_STRING_FIELD(value);
+	);
+};
+
+struct bridge_role {
+	AST_LIST_ENTRY(bridge_role) list;
+	AST_LIST_HEAD(, bridge_role_option) options;
+	char role[AST_ROLE_LEN];
+};
+
+struct bridge_roles_datastore {
+	AST_LIST_HEAD(, bridge_role) role_list;
+};
+
+/*!
+ * \internal
+ * \brief Destructor function for a bridge role
+ * \since 12.0.0
+ *
+ * \param role bridge_role being destroyed
+ *
+ * \return Nothing
+ */
+static void bridge_role_destroy(struct bridge_role *role)
+{
+	struct bridge_role_option *role_option;
+	while ((role_option = AST_LIST_REMOVE_HEAD(&role->options, list))) {
+		ast_string_field_free_memory(role_option);
+		ast_free(role_option);
+	}
+	ast_free(role);
+}
+
+/*!
+ * \internal
+ * \brief Destructor function for bridge role datastores
+ * \since 12.0.0
+ *
+ * \param data Pointer to the datastore being destroyed
+ *
+ * \return Nothing
+ */
+static void bridge_role_datastore_destroy(void *data)
+{
+	struct bridge_roles_datastore *roles_datastore = data;
+	struct bridge_role *role;
+
+	while ((role = AST_LIST_REMOVE_HEAD(&roles_datastore->role_list, list))) {
+		bridge_role_destroy(role);
+	}
+
+	ast_free(roles_datastore);
+}
+
+static const struct ast_datastore_info bridge_role_info = {
+	.type = "bridge roles",
+	.destroy = bridge_role_datastore_destroy,
+};
+
+/*!
+ * \internal
+ * \brief Setup a bridge role datastore on a channel
+ * \since 12.0.0
+ *
+ * \param chan Chan the datastore is being setup on
+ *
+ * \retval NULL if failed
+ * \retval pointer to the newly created datastore
+ */
+static struct bridge_roles_datastore *setup_bridge_roles_datastore(struct ast_channel *chan)
+{
+	struct ast_datastore *datastore = NULL;
+	struct bridge_roles_datastore *roles_datastore = NULL;
+
+	if (!(datastore = ast_datastore_alloc(&bridge_role_info, NULL))) {
+		return NULL;
+	}
+
+	if (!(roles_datastore = ast_calloc(1, sizeof(*roles_datastore)))) {
+		ast_datastore_free(datastore);
+		return NULL;
+	}
+
+	datastore->data = roles_datastore;
+	ast_channel_datastore_add(chan, datastore);
+	return roles_datastore;
+}
+
+/*!
+ * \internal
+ * \brief Get the bridge_roles_datastore from a channel if it exists. Don't create one if it doesn't.
+ * \since 12.0.0
+ *
+ * \param chan Channel we want the bridge_roles_datastore from
+ *
+ * \retval NULL if we can't find the datastore
+ * \retval pointer to the bridge_roles_datastore
+ */
+static struct bridge_roles_datastore *fetch_bridge_roles_datastore(struct ast_channel *chan)
+{
+	struct ast_datastore *datastore = NULL;
+
+	ast_channel_lock(chan);
+	if (!(datastore = ast_channel_datastore_find(chan, &bridge_role_info, NULL))) {
+		ast_channel_unlock(chan);
+		return NULL;
+	}
+	ast_channel_unlock(chan);
+
+	return datastore->data;
+}
+
+/*!
+ * \internal
+ * \brief Get the bridge_roles_datastore from a channel if it exists. If not, create one.
+ * \since 12.0.0
+ *
+ * \param chan Channel we want the bridge_roles_datastore from
+ *
+ * \retval NULL If we can't find and can't create the datastore
+ * \retval pointer to the bridge_roles_datastore
+ */
+static struct bridge_roles_datastore *fetch_or_create_bridge_roles_datastore(struct ast_channel *chan)
+{
+	struct bridge_roles_datastore *roles_datastore;
+
+	ast_channel_lock(chan);
+	roles_datastore = fetch_bridge_roles_datastore(chan);
+	if (!roles_datastore) {
+		roles_datastore = setup_bridge_roles_datastore(chan);
+	}
+	ast_channel_unlock(chan);
+
+	return roles_datastore;
+}
+
+/*!
+ * \internal
+ * \brief Obtain a role from a bridge_roles_datastore if the datastore has it
+ * \since 12.0.0
+ *
+ * \param roles_datastore The bridge_roles_datastore we are looking for the role of
+ * \param role_name Name of the role being sought
+ *
+ * \retval NULL if the datastore does not have the requested role
+ * \retval pointer to the requested role
+ */
+static struct bridge_role *get_role_from_datastore(struct bridge_roles_datastore *roles_datastore, const char *role_name)
+{
+	struct bridge_role *role;
+
+	AST_LIST_TRAVERSE(&roles_datastore->role_list, role, list) {
+		if (!strcmp(role->role, role_name)) {
+			return role;
+		}
+	}
+
+	return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Obtain a role from a channel structure if the channel's datastore has it
+ * \since 12.0.0
+ *
+ * \param channel The channel we are checking the role of
+ * \param role_name Name of the role sought
+ *
+ * \retval NULL if the channel's datastore does not have the requested role
+ * \retval pointer to the requested role
+ */
+static struct bridge_role *get_role_from_channel(struct ast_channel *channel, const char *role_name)
+{
+	struct bridge_roles_datastore *roles_datastore = fetch_bridge_roles_datastore(channel);
+	return roles_datastore ? get_role_from_datastore(roles_datastore, role_name) : NULL;
+}
+
+/*!
+ * \internal
+ * \brief Obtain a role option from a bridge role if it exists in the bridge role's option list
+ * \since 12.0.0
+ *
+ * \param role a pointer to the bridge role wea re searching for the option of
+ * \param option Name of the option sought
+ *
+ * \retval NULL if the bridge role doesn't have the requested option
+ * \retval pointer to the requested option
+ */
+static struct bridge_role_option *get_role_option(struct bridge_role *role, const char *option)
+{
+	struct bridge_role_option *role_option = NULL;
+	AST_LIST_TRAVERSE(&role->options, role_option, list) {
+		if (!strcmp(role_option->option, option)) {
+			return role_option;
+		}
+	}
+	return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Setup a bridge role on an existing bridge role datastore
+ * \since 12.0.0
+ *
+ * \param roles_datastore bridge_roles_datastore receiving the new role
+ * \param role_name Name of the role being received
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int setup_bridge_role(struct bridge_roles_datastore *roles_datastore, const char *role_name)
+{
+	struct bridge_role *role;
+	role = ast_calloc(1, sizeof(*role));
+
+	if (!role) {
+		return -1;
+	}
+
+	ast_copy_string(role->role, role_name, sizeof(role->role));
+
+	AST_LIST_INSERT_TAIL(&roles_datastore->role_list, role, list);
+	ast_debug(3, "Set role '%s'\n", role_name);
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Setup a bridge role option on an existing bridge role
+ * \since 12.0.0
+ *
+ * \param role The role receiving the option
+ * \param option Name of the option
+ * \param value the option's value
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int setup_bridge_role_option(struct bridge_role *role, const char *option, const char *value)
+{
+	struct bridge_role_option *role_option;
+
+	if (!value) {
+		value = "";
+	}
+
+	role_option = ast_calloc(1, sizeof(*role_option));
+	if (!role_option) {
+		return -1;
+	}
+
+	if (ast_string_field_init(role_option, 32)) {
+		ast_free(role_option);
+		return -1;
+	}
+
+	ast_string_field_set(role_option, option, option);
+	ast_string_field_set(role_option, value, value);
+
+	AST_LIST_INSERT_TAIL(&role->options, role_option, list);
+
+	return 0;
+}
+
+int ast_channel_add_bridge_role(struct ast_channel *chan, const char *role_name)
+{
+	struct bridge_roles_datastore *roles_datastore = fetch_or_create_bridge_roles_datastore(chan);
+
+	if (!roles_datastore) {
+		ast_log(LOG_WARNING, "Unable to set up bridge role datastore on channel %s\n", ast_channel_name(chan));
+		return -1;
+	}
+
+	/* Check to make sure we aren't adding a redundant role */
+	if (get_role_from_datastore(roles_datastore, role_name)) {
+		ast_debug(2, "Bridge role %s is already applied to the channel %s\n", role_name, ast_channel_name(chan));
+		return 0;
+	}
+
+	/* It wasn't already there, so we can just finish setting it up now. */
+	return setup_bridge_role(roles_datastore, role_name);
+}
+
+void ast_channel_remove_bridge_role(struct ast_channel *chan, const char *role_name)
+{
+	struct bridge_roles_datastore *roles_datastore = fetch_bridge_roles_datastore(chan);
+	struct bridge_role *role;
+
+	if (!roles_datastore) {
+		/* The roles datastore didn't already exist, so there is no need to remove a role */
+		ast_debug(2, "Role %s did not exist on channel %s\n", role_name, ast_channel_name(chan));
+		return;
+	}
+
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&roles_datastore->role_list, role, list) {
+		if (!strcmp(role->role, role_name)) {
+			ast_debug(2, "Removing bridge role %s from channel %s\n", role_name, ast_channel_name(chan));
+			AST_LIST_REMOVE_CURRENT(list);
+			bridge_role_destroy(role);
+			return;
+		}
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+
+	ast_debug(2, "Role %s did not exist on channel %s\n", role_name, ast_channel_name(chan));
+}
+
+int ast_channel_set_bridge_role_option(struct ast_channel *channel, const char *role_name, const char *option, const char *value)
+{
+	struct bridge_role *role = get_role_from_channel(channel, role_name);
+	struct bridge_role_option *role_option;
+
+	if (!role) {
+		return -1;
+	}
+
+	role_option = get_role_option(role, option);
+
+	if (role_option) {
+		ast_string_field_set(role_option, value, value);
+		return 0;
+	}
+
+	setup_bridge_role_option(role, option, value);
+
+	return 0;
+}
+
+int ast_bridge_channel_has_role(struct ast_bridge_channel *bridge_channel, const char *role_name)
+{
+	if (!bridge_channel->bridge_roles) {
+		return 0;
+	}
+
+	return get_role_from_datastore(bridge_channel->bridge_roles, role_name) ? 1 : 0;
+}
+
+const char *ast_bridge_channel_get_role_option(struct ast_bridge_channel *bridge_channel, const char *role_name, const char *option)
+{
+	struct bridge_role *role;
+	struct bridge_role_option *role_option = NULL;
+
+	if (!bridge_channel->bridge_roles) {
+		return NULL;
+	}
+
+	role = get_role_from_datastore(bridge_channel->bridge_roles, role_name);
+
+	if (!role) {
+		return NULL;
+	}
+
+	role_option = get_role_option(role, option);
+
+	return role_option ? role_option->value : NULL;
+}
+
+int ast_bridge_channel_establish_roles(struct ast_bridge_channel *bridge_channel)
+{
+	struct bridge_roles_datastore *roles_datastore;
+	struct bridge_role *role = NULL;
+	struct bridge_role_option *role_option;
+
+	if (!bridge_channel->chan) {
+		ast_debug(2, "Attempted to set roles on a bridge channel that has no associated channel. That's a bad idea.\n");
+		return -1;
+	}
+
+	if (bridge_channel->bridge_roles) {
+		ast_debug(2, "Attempted to reset roles while roles were already established. Purge existing roles first.\n");
+		return -1;
+	}
+
+	roles_datastore = fetch_bridge_roles_datastore(bridge_channel->chan);
+	if (!roles_datastore) {
+		/* No roles to establish. */
+		return 0;
+	}
+
+	if (!(bridge_channel->bridge_roles = ast_calloc(1, sizeof(*bridge_channel->bridge_roles)))) {
+		return -1;
+	}
+
+	AST_LIST_TRAVERSE(&roles_datastore->role_list, role, list) {
+		struct bridge_role *this_role_copy;
+
+		if (setup_bridge_role(bridge_channel->bridge_roles, role->role)) {
+			/* We need to abandon the copy because we couldn't setup a role */
+			ast_bridge_channel_clear_roles(bridge_channel);
+			return -1;
+		}
+		this_role_copy = AST_LIST_LAST(&bridge_channel->bridge_roles->role_list);
+
+		AST_LIST_TRAVERSE(&role->options, role_option, list) {
+			if (setup_bridge_role_option(this_role_copy, role_option->option, role_option->value)) {
+				/* We need to abandon the copy because we couldn't setup a role option */
+				ast_bridge_channel_clear_roles(bridge_channel);
+				return -1;
+			}
+		}
+	}
+
+	return 0;
+}
+
+void ast_bridge_channel_clear_roles(struct ast_bridge_channel *bridge_channel)
+{
+	if (bridge_channel->bridge_roles) {
+		bridge_role_datastore_destroy(bridge_channel->bridge_roles);
+		bridge_channel->bridge_roles = NULL;
+	}
+}
diff --git a/main/channel.c b/main/channel.c
index 0b2dedd637a33768f81084804bd7e1f447ab195a..aec43edf74aec8a3e39bbcc99644cf5dafc6400e 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -73,6 +73,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/data.h"
 #include "asterisk/channel_internal.h"
 #include "asterisk/features.h"
+#include "asterisk/bridging.h"
 #include "asterisk/test.h"
 #include "asterisk/stasis_channels.h"
 
@@ -1634,6 +1635,7 @@ int ast_is_deferrable_frame(const struct ast_frame *frame)
 	 * be queued up or not.
 	 */
 	switch (frame->frametype) {
+	case AST_FRAME_BRIDGE_ACTION:
 	case AST_FRAME_CONTROL:
 	case AST_FRAME_TEXT:
 	case AST_FRAME_IMAGE:
@@ -3013,6 +3015,7 @@ int __ast_answer(struct ast_channel *chan, unsigned int delay, int cdr_answer)
 					break;
 				case AST_FRAME_CONTROL:
 				case AST_FRAME_IAX:
+				case AST_FRAME_BRIDGE_ACTION:
 				case AST_FRAME_NULL:
 				case AST_FRAME_CNG:
 					break;
@@ -6237,87 +6240,21 @@ int ast_channel_make_compatible(struct ast_channel *chan, struct ast_channel *pe
 static int __ast_channel_masquerade(struct ast_channel *original, struct ast_channel *clonechan, struct ast_datastore *xfer_ds)
 {
 	int res = -1;
-	struct ast_channel *final_orig, *final_clone, *base;
 
-	for (;;) {
-		final_orig = original;
-		final_clone = clonechan;
-
-		ast_channel_lock_both(original, clonechan);
-
-		if (ast_test_flag(ast_channel_flags(original), AST_FLAG_ZOMBIE)
-			|| ast_test_flag(ast_channel_flags(clonechan), AST_FLAG_ZOMBIE)) {
-			/* Zombies! Run! */
-			ast_log(LOG_WARNING,
-				"Can't setup masquerade. One or both channels is dead. (%s <-- %s)\n",
-				ast_channel_name(original), ast_channel_name(clonechan));
-			ast_channel_unlock(clonechan);
-			ast_channel_unlock(original);
-			return -1;
-		}
-
-		/*
-		 * Each of these channels may be sitting behind a channel proxy
-		 * (i.e. chan_agent) and if so, we don't really want to
-		 * masquerade it, but its proxy
-		 */
-		if (ast_channel_internal_bridged_channel(original)
-			&& (ast_channel_internal_bridged_channel(original) != ast_bridged_channel(original))
-			&& (ast_channel_internal_bridged_channel(ast_channel_internal_bridged_channel(original)) != original)) {
-			final_orig = ast_channel_internal_bridged_channel(original);
-		}
-		if (ast_channel_internal_bridged_channel(clonechan)
-			&& (ast_channel_internal_bridged_channel(clonechan) != ast_bridged_channel(clonechan))
-			&& (ast_channel_internal_bridged_channel(ast_channel_internal_bridged_channel(clonechan)) != clonechan)) {
-			final_clone = ast_channel_internal_bridged_channel(clonechan);
-		}
-		if (ast_channel_tech(final_clone)->get_base_channel
-			&& (base = ast_channel_tech(final_clone)->get_base_channel(final_clone))) {
-			final_clone = base;
-		}
-
-		if ((final_orig != original) || (final_clone != clonechan)) {
-			/*
-			 * Lots and lots of deadlock avoidance.  The main one we're
-			 * competing with is ast_write(), which locks channels
-			 * recursively, when working with a proxy channel.
-			 */
-			if (ast_channel_trylock(final_orig)) {
-				ast_channel_unlock(clonechan);
-				ast_channel_unlock(original);
-
-				/* Try again */
-				continue;
-			}
-			if (ast_channel_trylock(final_clone)) {
-				ast_channel_unlock(final_orig);
-				ast_channel_unlock(clonechan);
-				ast_channel_unlock(original);
-
-				/* Try again */
-				continue;
-			}
-			ast_channel_unlock(clonechan);
-			ast_channel_unlock(original);
-			original = final_orig;
-			clonechan = final_clone;
-
-			if (ast_test_flag(ast_channel_flags(original), AST_FLAG_ZOMBIE)
-				|| ast_test_flag(ast_channel_flags(clonechan), AST_FLAG_ZOMBIE)) {
-				/* Zombies! Run! */
-				ast_log(LOG_WARNING,
-					"Can't setup masquerade. One or both channels is dead. (%s <-- %s)\n",
-					ast_channel_name(original), ast_channel_name(clonechan));
-				ast_channel_unlock(clonechan);
-				ast_channel_unlock(original);
-				return -1;
-			}
-		}
-		break;
+	if (original == clonechan) {
+		ast_log(LOG_WARNING, "Can't masquerade channel '%s' into itself!\n",
+			ast_channel_name(original));
+		return -1;
 	}
 
-	if (original == clonechan) {
-		ast_log(LOG_WARNING, "Can't masquerade channel '%s' into itself!\n", ast_channel_name(original));
+	ast_channel_lock_both(original, clonechan);
+
+	if (ast_test_flag(ast_channel_flags(original), AST_FLAG_ZOMBIE)
+		|| ast_test_flag(ast_channel_flags(clonechan), AST_FLAG_ZOMBIE)) {
+		/* Zombies! Run! */
+		ast_log(LOG_WARNING,
+			"Can't setup masquerade. One or both channels is dead. (%s <-- %s)\n",
+			ast_channel_name(original), ast_channel_name(clonechan));
 		ast_channel_unlock(clonechan);
 		ast_channel_unlock(original);
 		return -1;
@@ -6638,15 +6575,33 @@ static void ast_channel_change_linkedid(struct ast_channel *chan, const char *li
 	ast_cel_linkedid_ref(linkedid);
 }
 
-/*!
-  \brief Propagate the oldest linkedid between associated channels
-
-*/
+/*! \brief Propagate the oldest linkedid between associated channels */
 void ast_channel_set_linkgroup(struct ast_channel *chan, struct ast_channel *peer)
 {
 	const char* linkedid=NULL;
 	struct ast_channel *bridged;
 
+/*
+ * BUGBUG this needs to be updated to not use ast_channel_internal_bridged_channel().
+ * BUGBUG this needs to be updated to not use ast_bridged_channel().
+ *
+ * We may be better off making this a function of the bridging
+ * framework.  Essentially, as each channel joins a bridge, the
+ * oldest linkedid should be propagated between all pairs of
+ * channels.  This should be handled by bridging (unless you're
+ * in an infinite wait bridge...) just like the BRIDGEPEER
+ * channel variable.
+ *
+ * This is currently called in two places:
+ *
+ * (1) In channel masquerade. To some extent this shouldn't
+ * really be done any longer - we don't really want a channel to
+ * have its linkedid change, even if it replaces a channel that
+ * had an older linkedid.  The two channels aren't really
+ * 'related', they're simply swapping with each other.
+ *
+ * (2) In features.c as two channels are bridged.
+ */
 	linkedid = oldest_linkedid(ast_channel_linkedid(chan), ast_channel_linkedid(peer));
 	linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(chan));
 	linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(peer));
@@ -6829,7 +6784,7 @@ void ast_do_masquerade(struct ast_channel *original)
 	 * and new masquerade attempts, the channels container must be
 	 * locked for the entire masquerade.  The original and clonechan
 	 * need to be unlocked earlier to avoid potential deadlocks with
-	 * the chan_local deadlock avoidance method.
+	 * the unreal/local channel deadlock avoidance method.
 	 *
 	 * The container lock blocks competing masquerade attempts from
 	 * starting as well as being necessary for proper locking order
@@ -7146,11 +7101,6 @@ void ast_do_masquerade(struct ast_channel *original)
 
 	/* copy over accuntcode and set peeraccount across the bridge */
 	ast_channel_accountcode_set(original, S_OR(ast_channel_accountcode(clonechan), ""));
-	if (ast_channel_internal_bridged_channel(original)) {
-		/* XXX - should we try to lock original's bridged channel here? */
-		ast_channel_peeraccount_set(ast_channel_internal_bridged_channel(original), S_OR(ast_channel_accountcode(clonechan), ""));
-		ast_cel_report_event(original, AST_CEL_BRIDGE_UPDATE, NULL, NULL, NULL);
-	}
 
 	ast_debug(1, "Putting channel %s in %s/%s formats\n", ast_channel_name(original),
 		ast_getformatname(&wformat), ast_getformatname(&rformat));
@@ -7196,6 +7146,8 @@ void ast_do_masquerade(struct ast_channel *original)
 	ast_channel_unlock(original);
 	ast_channel_unlock(clonechan);
 
+	ast_bridge_notify_masquerade(original);
+
 	if (clone_sending_dtmf_digit) {
 		/*
 		 * The clonechan was sending a DTMF digit that was not completed
@@ -7348,14 +7300,10 @@ int ast_setstate(struct ast_channel *chan, enum ast_channel_state state)
 	return 0;
 }
 
-/*! \brief Find bridged channel */
+/*! BUGBUG ast_bridged_channel() is to be removed. */
 struct ast_channel *ast_bridged_channel(struct ast_channel *chan)
 {
-	struct ast_channel *bridged;
-	bridged = ast_channel_internal_bridged_channel(chan);
-	if (bridged && ast_channel_tech(bridged)->bridged_channel)
-		bridged = ast_channel_tech(bridged)->bridged_channel(chan, bridged);
-	return bridged;
+	return NULL;
 }
 
 static void bridge_playfile(struct ast_channel *chan, struct ast_channel *peer, const char *sound, int remain)
@@ -7723,6 +7671,7 @@ static void bridge_play_sounds(struct ast_channel *c0, struct ast_channel *c1)
 	}
 }
 
+/* BUGBUG ast_channel_bridge() and anything that only it calls will be removed. */
 /*! \brief Bridge two channels together */
 enum ast_bridge_result ast_channel_bridge(struct ast_channel *c0, struct ast_channel *c1,
 					  struct ast_bridge_config *config, struct ast_frame **fo, struct ast_channel **rc)
@@ -11232,3 +11181,48 @@ void ast_channel_unlink(struct ast_channel *chan)
 {
 	ao2_unlink(channels, chan);
 }
+
+struct ast_bridge *ast_channel_get_bridge(const struct ast_channel *chan)
+{
+	struct ast_bridge *bridge;
+
+	bridge = ast_channel_internal_bridge(chan);
+	if (bridge) {
+		ao2_ref(bridge, +1);
+	}
+	return bridge;
+}
+
+int ast_channel_is_bridged(const struct ast_channel *chan)
+{
+	return ast_channel_internal_bridge(chan) != NULL;
+}
+
+struct ast_channel *ast_channel_bridge_peer(struct ast_channel *chan)
+{
+	struct ast_channel *peer;
+	struct ast_bridge *bridge;
+
+	/* Get the bridge the channel is in. */
+	ast_channel_lock(chan);
+	bridge = ast_channel_get_bridge(chan);
+	ast_channel_unlock(chan);
+	if (!bridge) {
+		return NULL;
+	}
+
+	peer = ast_bridge_peer(bridge, chan);
+	ao2_ref(bridge, -1);
+	return peer;
+}
+
+struct ast_bridge_channel *ast_channel_get_bridge_channel(struct ast_channel *chan)
+{
+	struct ast_bridge_channel *bridge_channel;
+
+	bridge_channel = ast_channel_internal_bridge_channel(chan);
+	if (bridge_channel) {
+		ao2_ref(bridge_channel, +1);
+	}
+	return bridge_channel;
+}
diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c
index f3293d59b8038038d14c41acf7cbeac477b58ebd..42aaada6d3eade73a7987896e27d1e128c476adb 100644
--- a/main/channel_internal_api.c
+++ b/main/channel_internal_api.c
@@ -65,6 +65,7 @@ struct ast_channel {
 	void *music_state;				/*!< Music State*/
 	void *generatordata;				/*!< Current generator data if there is any */
 	struct ast_generator *generator;		/*!< Current active data generator */
+/* BUGBUG bridged_channel must be eliminated from ast_channel */
 	struct ast_channel * bridged_channel;			/*!< Who are we bridged to, if we're bridged.
 							 *   Who is proxying for us, if we are proxied (i.e. chan_agent).
 							 *   Do not access directly, use ast_bridged_channel(chan) */
@@ -188,7 +189,9 @@ struct ast_channel {
 
 	unsigned short transfercapability;		/*!< ISDN Transfer Capability - AST_FLAG_DIGITAL is not enough */
 
+/* BUGBUG the bridge pointer must change to an ast_channel_bridge pointer because it will never change while the channel is in the bridging system whereas the bridge could change. */
 	struct ast_bridge *bridge;                      /*!< Bridge this channel is participating in */
+	struct ast_bridge_channel *bridge_channel;/*!< The bridge_channel this channel is linked with. */
 	struct ast_timer *timer;			/*!< timer object that provided timingfd */
 
 	char context[AST_MAX_CONTEXT];			/*!< Dialplan: Current extension context */
@@ -267,7 +270,6 @@ static void channel_data_add_flags(struct ast_data *tree,
 	ast_data_add_bool(tree, "END_DTMF_ONLY", ast_test_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY));
 	ast_data_add_bool(tree, "MASQ_NOSTREAM", ast_test_flag(ast_channel_flags(chan), AST_FLAG_MASQ_NOSTREAM));
 	ast_data_add_bool(tree, "BRIDGE_HANGUP_RUN", ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_RUN));
-	ast_data_add_bool(tree, "BRIDGE_HANGUP_DONT", ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT));
 	ast_data_add_bool(tree, "DISABLE_WORKAROUNDS", ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_WORKAROUNDS));
 	ast_data_add_bool(tree, "DISABLE_DEVSTATE_CACHE", ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_DEVSTATE_CACHE));
 }
@@ -1257,6 +1259,15 @@ void ast_channel_internal_bridge_set(struct ast_channel *chan, struct ast_bridge
 	chan->bridge = value;
 }
 
+struct ast_bridge_channel *ast_channel_internal_bridge_channel(const struct ast_channel *chan)
+{
+	return chan->bridge_channel;
+}
+void ast_channel_internal_bridge_channel_set(struct ast_channel *chan, struct ast_bridge_channel *value)
+{
+	chan->bridge_channel = value;
+}
+
 struct ast_channel *ast_channel_internal_bridged_channel(const struct ast_channel *chan)
 {
 	return chan->bridged_channel;
diff --git a/main/cli.c b/main/cli.c
index 7782b2279b096eda6a1bc18070ccb175eac56b24..22232acbc6c82918ac4e7b59df99509b753f476c 100644
--- a/main/cli.c
+++ b/main/cli.c
@@ -60,6 +60,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/lock.h"
 #include "asterisk/threadstorage.h"
 #include "asterisk/translate.h"
+#include "asterisk/bridging.h"
 
 /*!
  * \brief List of restrictions per user.
@@ -899,7 +900,7 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 			ast_cli(a->fd, FORMAT_STRING2, "Channel", "Location", "State", "Application(Data)");
 		else if (verbose)
 			ast_cli(a->fd, VERBOSE_FORMAT_STRING2, "Channel", "Context", "Extension", "Priority", "State", "Application", "Data",
-				"CallerID", "Duration", "Accountcode", "PeerAccount", "BridgedTo");
+				"CallerID", "Duration", "Accountcode", "PeerAccount", "BridgeID");
 	}
 
 	if (!count && !(iter = ast_channel_iterator_all_new())) {
@@ -907,12 +908,12 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 	}
 
 	for (; iter && (c = ast_channel_iterator_next(iter)); ast_channel_unref(c)) {
-		struct ast_channel *bc;
+		struct ast_bridge *bridge;
 		char durbuf[10] = "-";
 
 		ast_channel_lock(c);
 
-		bc = ast_bridged_channel(c);
+		bridge = ast_channel_get_bridge(c);
 
 		if (!count) {
 			if ((concise || verbose)  && ast_channel_cdr(c) && !ast_tvzero(ast_channel_cdr(c)->start)) {
@@ -935,7 +936,7 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 					S_OR(ast_channel_peeraccount(c), ""),
 					ast_channel_amaflags(c),
 					durbuf,
-					bc ? ast_channel_name(bc) : "(None)",
+					bridge ? bridge->uniqueid : "(Not bridged)",
 					ast_channel_uniqueid(c));
 			} else if (verbose) {
 				ast_cli(a->fd, VERBOSE_FORMAT_STRING, ast_channel_name(c), ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_state2str(ast_channel_state(c)),
@@ -945,7 +946,7 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 					durbuf,
 					S_OR(ast_channel_accountcode(c), ""),
 					S_OR(ast_channel_peeraccount(c), ""),
-					bc ? ast_channel_name(bc) : "(None)");
+					bridge ? bridge->uniqueid : "(Not bridged)");
 			} else {
 				char locbuf[40] = "(None)";
 				char appdata[40] = "(None)";
@@ -958,6 +959,7 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 			}
 		}
 		ast_channel_unlock(c);
+		ao2_cleanup(bridge);
 	}
 
 	if (iter) {
@@ -1412,6 +1414,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 #ifdef CHANNEL_TRACE
 	int trace_enabled;
 #endif
+	struct ast_bridge *bridge;
 
 	switch (cmd) {
 	case CLI_INIT:
@@ -1463,6 +1466,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 	}
 
 	effective_connected_id = ast_channel_connected_effective_id(c);
+	bridge = ast_channel_get_bridge(c);
 
 	ast_str_append(&output, 0,
 		" -- General --\n"
@@ -1490,8 +1494,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 		"     Frames out: %d%s\n"
 		" Time to Hangup: %ld\n"
 		"   Elapsed Time: %s\n"
-		"  Direct Bridge: %s\n"
-		"Indirect Bridge: %s\n"
+		"      Bridge ID: %s\n"
 		" --   PBX   --\n"
 		"        Context: %s\n"
 		"      Extension: %s\n"
@@ -1502,7 +1505,10 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 		"           Data: %s\n"
 		"    Blocking in: %s\n"
 		" Call Identifer: %s\n",
-		ast_channel_name(c), ast_channel_tech(c)->type, ast_channel_uniqueid(c), ast_channel_linkedid(c),
+		ast_channel_name(c),
+		ast_channel_tech(c)->type,
+		ast_channel_uniqueid(c),
+		ast_channel_linkedid(c),
 		S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, "(N/A)"),
 		S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, "(N/A)"),
 		S_COR(ast_channel_connected(c)->id.number.valid, ast_channel_connected(c)->id.number.str, "(N/A)"),
@@ -1511,7 +1517,9 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 		S_COR(effective_connected_id.name.valid, effective_connected_id.name.str, "(N/A)"),
 		S_OR(ast_channel_dialed(c)->number.str, "(N/A)"),
 		ast_channel_language(c),
-		ast_state2str(ast_channel_state(c)), ast_channel_state(c), ast_channel_rings(c),
+		ast_state2str(ast_channel_state(c)),
+		ast_channel_state(c),
+		ast_channel_rings(c),
 		ast_getformatname_multiple(nf, sizeof(nf), ast_channel_nativeformats(c)),
 		ast_getformatname(ast_channel_writeformat(c)),
 		ast_getformatname(ast_channel_readformat(c)),
@@ -1520,11 +1528,19 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 		ast_channel_readtrans(c) ? "Yes" : "No",
 		ast_translate_path_to_str(ast_channel_readtrans(c), &read_transpath),
 		ast_channel_fd(c, 0),
-		ast_channel_fin(c) & ~DEBUGCHAN_FLAG, (ast_channel_fin(c) & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
-		ast_channel_fout(c) & ~DEBUGCHAN_FLAG, (ast_channel_fout(c) & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
-		(long)ast_channel_whentohangup(c)->tv_sec,
-		cdrtime, 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>",
-		ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_callgroup(c), ast_channel_pickupgroup(c), (ast_channel_appl(c) ? ast_channel_appl(c) : "(N/A)" ),
+		ast_channel_fin(c) & ~DEBUGCHAN_FLAG,
+		(ast_channel_fin(c) & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
+		ast_channel_fout(c) & ~DEBUGCHAN_FLAG,
+		(ast_channel_fout(c) & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
+		(long) ast_channel_whentohangup(c)->tv_sec,
+		cdrtime,
+		bridge ? bridge->uniqueid : "(Not bridged)",
+		ast_channel_context(c),
+		ast_channel_exten(c),
+		ast_channel_priority(c),
+		ast_channel_callgroup(c),
+		ast_channel_pickupgroup(c),
+		(ast_channel_appl(c) ? ast_channel_appl(c) : "(N/A)" ),
 		(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)"),
 		S_OR(call_identifier_str, "(None)"));
@@ -1548,6 +1564,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 
 	ast_channel_unlock(c);
 	c = ast_channel_unref(c);
+	ao2_cleanup(bridge);
 
 	ast_cli(a->fd, "%s", ast_str_buffer(output));
 	ast_free(output);
diff --git a/main/config_options.c b/main/config_options.c
index 06b45213194b2e05d8df667822cbd3a3fe77dea5..39a3fbe61c96ddcb57c00d28875ea63aa5a542ff 100644
--- a/main/config_options.c
+++ b/main/config_options.c
@@ -222,6 +222,11 @@ int aco_option_register_deprecated(struct aco_info *info, const char *name, stru
 	return 0;
 }
 
+unsigned int aco_option_get_flags(const struct aco_option *option)
+{
+	return option->flags;
+}
+
 #ifdef AST_XML_DOCS
 /*! \internal
  * \brief Find a particular ast_xml_doc_item from it's parent config_info, types, and name
diff --git a/main/core_local.c b/main/core_local.c
new file mode 100644
index 0000000000000000000000000000000000000000..2e0bcc48aea3dbe6fa9b1fee8988a3d04e0c8d65
--- /dev/null
+++ b/main/core_local.c
@@ -0,0 +1,775 @@
+/*
+ * 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 Local proxy channel driver.
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+/* ------------------------------------------------------------------- */
+
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/cli.h"
+#include "asterisk/manager.h"
+#include "asterisk/devicestate.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/bridging.h"
+#include "asterisk/core_unreal.h"
+#include "asterisk/core_local.h"
+#include "asterisk/_private.h"
+
+/*** DOCUMENTATION
+	<manager name="LocalOptimizeAway" language="en_US">
+		<synopsis>
+			Optimize away a local channel when possible.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+			<parameter name="Channel" required="true">
+				<para>The channel name to optimize away.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>A local channel created with "/n" will not automatically optimize away.
+			Calling this command on the local channel will clear that flag and allow
+			it to optimize away if it's bridged or when it becomes bridged.</para>
+		</description>
+	</manager>
+ ***/
+
+static const char tdesc[] = "Local Proxy Channel Driver";
+
+static struct ao2_container *locals;
+
+static struct ast_channel *local_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause);
+static int local_call(struct ast_channel *ast, const char *dest, int timeout);
+static int local_hangup(struct ast_channel *ast);
+static int local_devicestate(const char *data);
+
+/* PBX interface structure for channel registration */
+static struct ast_channel_tech local_tech = {
+	.type = "Local",
+	.description = tdesc,
+	.requester = local_request,
+	.send_digit_begin = ast_unreal_digit_begin,
+	.send_digit_end = ast_unreal_digit_end,
+	.call = local_call,
+	.hangup = local_hangup,
+	.answer = ast_unreal_answer,
+	.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,
+	.devicestate = local_devicestate,
+	.queryoption = ast_unreal_queryoption,
+	.setoption = ast_unreal_setoption,
+};
+
+/*! What to do with the ;2 channel when ast_call() happens. */
+enum local_call_action {
+	/* The ast_call() will run dialplan on the ;2 channel. */
+	LOCAL_CALL_ACTION_DIALPLAN,
+	/* The ast_call() will impart the ;2 channel into a bridge. */
+	LOCAL_CALL_ACTION_BRIDGE,
+	/* The ast_call() will masquerade the ;2 channel into a channel. */
+	LOCAL_CALL_ACTION_MASQUERADE,
+};
+
+/*! Join a bridge on ast_call() parameters. */
+struct local_bridge {
+	/*! Bridge to join. */
+	struct ast_bridge *join;
+	/*! Channel to swap with when joining bridge. */
+	struct ast_channel *swap;
+	/*! Features that are specific to this channel when pushed into the bridge. */
+	struct ast_bridge_features *features;
+};
+
+/*!
+ * \brief the local pvt structure for all channels
+ *
+ * The local channel pvt has two ast_chan objects - the "owner" and the "next channel", the outbound channel
+ *
+ * ast_chan owner -> local_pvt -> ast_chan chan
+ */
+struct local_pvt {
+	/*! Unreal channel driver base class values. */
+	struct ast_unreal_pvt base;
+	/*! Additional action arguments */
+	union {
+		/*! Make ;2 join a bridge on ast_call(). */
+		struct local_bridge bridge;
+		/*! Make ;2 masquerade into this channel on ast_call(). */
+		struct ast_channel *masq;
+	} action;
+	/*! What to do with the ;2 channel on ast_call(). */
+	enum local_call_action type;
+	/*! Context to call */
+	char context[AST_MAX_CONTEXT];
+	/*! Extension to call */
+	char exten[AST_MAX_EXTENSION];
+};
+
+struct ast_channel *ast_local_get_peer(struct ast_channel *ast)
+{
+	struct local_pvt *p = ast_channel_tech_pvt(ast);
+	struct local_pvt *found;
+	struct ast_channel *peer;
+
+	if (!p) {
+		return NULL;
+	}
+
+	found = p ? ao2_find(locals, p, 0) : NULL;
+	if (!found) {
+		/* ast is either not a local channel or it has alredy been hungup */
+		return NULL;
+	}
+	ao2_lock(found);
+	if (ast == p->base.owner) {
+		peer = p->base.chan;
+	} else if (ast == p->base.chan) {
+		peer = p->base.owner;
+	} else {
+		peer = NULL;
+	}
+	if (peer) {
+		ast_channel_ref(peer);
+	}
+	ao2_unlock(found);
+	ao2_ref(found, -1);
+	return peer;
+}
+
+/*! \brief Adds devicestate to local channels */
+static int local_devicestate(const char *data)
+{
+	int is_inuse = 0;
+	int res = AST_DEVICE_INVALID;
+	char *exten = ast_strdupa(data);
+	char *context;
+	char *opts;
+	struct local_pvt *lp;
+	struct ao2_iterator it;
+
+	/* Strip options if they exist */
+	opts = strchr(exten, '/');
+	if (opts) {
+		*opts = '\0';
+	}
+
+	context = strchr(exten, '@');
+	if (!context) {
+		ast_log(LOG_WARNING,
+			"Someone used Local/%s somewhere without a @context. This is bad.\n", data);
+		return AST_DEVICE_INVALID;
+	}
+	*context++ = '\0';
+
+	it = ao2_iterator_init(locals, 0);
+	for (; (lp = ao2_iterator_next(&it)); ao2_ref(lp, -1)) {
+		ao2_lock(lp);
+		if (!strcmp(exten, lp->exten)
+			&& !strcmp(context, lp->context)) {
+			res = AST_DEVICE_NOT_INUSE;
+			if (lp->base.owner
+				&& ast_test_flag(&lp->base, AST_UNREAL_CARETAKER_THREAD)) {
+				is_inuse = 1;
+			}
+		}
+		ao2_unlock(lp);
+		if (is_inuse) {
+			res = AST_DEVICE_INUSE;
+			ao2_ref(lp, -1);
+			break;
+		}
+	}
+	ao2_iterator_destroy(&it);
+
+	if (res == AST_DEVICE_INVALID) {
+		ast_debug(3, "Checking if extension %s@%s exists (devicestate)\n", exten, context);
+		if (ast_exists_extension(NULL, context, exten, 1, NULL)) {
+			res = AST_DEVICE_NOT_INUSE;
+		}
+	}
+
+	return res;
+}
+
+/*!
+ * \internal
+ * \brief Post the LocalBridge AMI event.
+ * \since 12.0.0
+ *
+ * \param p local_pvt to raise the bridge event.
+ *
+ * \return Nothing
+ */
+static void local_bridge_event(struct local_pvt *p)
+{
+	ao2_lock(p);
+	/*** DOCUMENTATION
+		<managerEventInstance>
+			<synopsis>Raised when two halves of a Local Channel form a bridge.</synopsis>
+			<syntax>
+				<parameter name="Channel1">
+					<para>The name of the Local Channel half that bridges to another channel.</para>
+				</parameter>
+				<parameter name="Channel2">
+					<para>The name of the Local Channel half that executes the dialplan.</para>
+				</parameter>
+				<parameter name="Context">
+					<para>The context in the dialplan that Channel2 starts in.</para>
+				</parameter>
+				<parameter name="Exten">
+					<para>The extension in the dialplan that Channel2 starts in.</para>
+				</parameter>
+				<parameter name="LocalOptimization">
+					<enumlist>
+						<enum name="Yes"/>
+						<enum name="No"/>
+					</enumlist>
+				</parameter>
+			</syntax>
+		</managerEventInstance>
+	***/
+	manager_event(EVENT_FLAG_CALL, "LocalBridge",
+		"Channel1: %s\r\n"
+		"Channel2: %s\r\n"
+		"Uniqueid1: %s\r\n"
+		"Uniqueid2: %s\r\n"
+		"Context: %s\r\n"
+		"Exten: %s\r\n"
+		"LocalOptimization: %s\r\n",
+		ast_channel_name(p->base.owner), ast_channel_name(p->base.chan),
+		ast_channel_uniqueid(p->base.owner), ast_channel_uniqueid(p->base.chan),
+		p->context, p->exten,
+		ast_test_flag(&p->base, AST_UNREAL_NO_OPTIMIZATION) ? "Yes" : "No");
+	ao2_unlock(p);
+}
+
+int ast_local_setup_bridge(struct ast_channel *ast, struct ast_bridge *bridge, struct ast_channel *swap, struct ast_bridge_features *features)
+{
+	struct local_pvt *p;
+	struct local_pvt *found;
+	int res = -1;
+
+	/* Sanity checks. */
+	if (!ast || !bridge) {
+		ast_bridge_features_destroy(features);
+		return -1;
+	}
+
+	ast_channel_lock(ast);
+	p = ast_channel_tech_pvt(ast);
+	ast_channel_unlock(ast);
+
+	found = p ? ao2_find(locals, p, 0) : NULL;
+	if (found) {
+		ao2_lock(found);
+		if (found->type == LOCAL_CALL_ACTION_DIALPLAN
+			&& found->base.owner
+			&& found->base.chan
+			&& !ast_test_flag(&found->base, AST_UNREAL_CARETAKER_THREAD)) {
+			ao2_ref(bridge, +1);
+			if (swap) {
+				ast_channel_ref(swap);
+			}
+			found->type = LOCAL_CALL_ACTION_BRIDGE;
+			found->action.bridge.join = bridge;
+			found->action.bridge.swap = swap;
+			found->action.bridge.features = features;
+			res = 0;
+		} else {
+			ast_bridge_features_destroy(features);
+		}
+		ao2_unlock(found);
+		ao2_ref(found, -1);
+	}
+
+	return res;
+}
+
+int ast_local_setup_masquerade(struct ast_channel *ast, struct ast_channel *masq)
+{
+	struct local_pvt *p;
+	struct local_pvt *found;
+	int res = -1;
+
+	/* Sanity checks. */
+	if (!ast || !masq) {
+		return -1;
+	}
+
+	ast_channel_lock(ast);
+	p = ast_channel_tech_pvt(ast);
+	ast_channel_unlock(ast);
+
+	found = p ? ao2_find(locals, p, 0) : NULL;
+	if (found) {
+		ao2_lock(found);
+		if (found->type == LOCAL_CALL_ACTION_DIALPLAN
+			&& found->base.owner
+			&& found->base.chan
+			&& !ast_test_flag(&found->base, AST_UNREAL_CARETAKER_THREAD)) {
+			ast_channel_ref(masq);
+			found->type = LOCAL_CALL_ACTION_MASQUERADE;
+			found->action.masq = masq;
+			res = 0;
+		}
+		ao2_unlock(found);
+		ao2_ref(found, -1);
+	}
+
+	return res;
+}
+
+/*! \brief Initiate new call, part of PBX interface
+ *         dest is the dial string */
+static int local_call(struct ast_channel *ast, const char *dest, int timeout)
+{
+	struct local_pvt *p = ast_channel_tech_pvt(ast);
+	int pvt_locked = 0;
+
+	struct ast_channel *owner = NULL;
+	struct ast_channel *chan = NULL;
+	int res;
+	char *reduced_dest = ast_strdupa(dest);
+	char *slash;
+	const char *chan_cid;
+
+	if (!p) {
+		return -1;
+	}
+
+	/* since we are letting go of channel locks that were locked coming into
+	 * this function, then we need to give the tech pvt a ref */
+	ao2_ref(p, 1);
+	ast_channel_unlock(ast);
+
+	ast_unreal_lock_all(&p->base, &chan, &owner);
+	pvt_locked = 1;
+
+	if (owner != ast) {
+		res = -1;
+		goto return_cleanup;
+	}
+
+	if (!owner || !chan) {
+		res = -1;
+		goto return_cleanup;
+	}
+
+	ast_unreal_call_setup(owner, chan);
+
+	/*
+	 * If the local channel has /n on the end of it, we need to lop
+	 * that off for our argument to setting up the CC_INTERFACES
+	 * variable.
+	 */
+	if ((slash = strrchr(reduced_dest, '/'))) {
+		*slash = '\0';
+	}
+	ast_set_cc_interfaces_chanvar(chan, reduced_dest);
+
+	ao2_unlock(p);
+	pvt_locked = 0;
+
+	ast_channel_unlock(owner);
+
+	chan_cid = S_COR(ast_channel_caller(chan)->id.number.valid,
+		ast_channel_caller(chan)->id.number.str, NULL);
+	if (chan_cid) {
+		chan_cid = ast_strdupa(chan_cid);
+	}
+	ast_channel_unlock(chan);
+
+	res = -1;
+	switch (p->type) {
+	case LOCAL_CALL_ACTION_DIALPLAN:
+		if (!ast_exists_extension(NULL, p->context, p->exten, 1, chan_cid)) {
+			ast_log(LOG_NOTICE, "No such extension/context %s@%s while calling Local channel\n",
+				p->exten, p->context);
+		} else {
+			local_bridge_event(p);
+
+			/* Start switch on sub channel */
+			res = ast_pbx_start(chan);
+		}
+		break;
+	case LOCAL_CALL_ACTION_BRIDGE:
+		local_bridge_event(p);
+		ast_answer(chan);
+		res = ast_bridge_impart(p->action.bridge.join, chan, p->action.bridge.swap,
+			p->action.bridge.features, 1);
+		ao2_ref(p->action.bridge.join, -1);
+		p->action.bridge.join = NULL;
+		ao2_cleanup(p->action.bridge.swap);
+		p->action.bridge.swap = NULL;
+		p->action.bridge.features = NULL;
+		break;
+	case LOCAL_CALL_ACTION_MASQUERADE:
+		local_bridge_event(p);
+		ast_answer(chan);
+		res = ast_channel_masquerade(p->action.masq, chan);
+		if (!res) {
+			ast_do_masquerade(p->action.masq);
+			/* Chan is now an orphaned zombie.  Destroy it. */
+			ast_hangup(chan);
+		}
+		p->action.masq = ast_channel_unref(p->action.masq);
+		break;
+	}
+	if (!res) {
+		ao2_lock(p);
+		ast_set_flag(&p->base, AST_UNREAL_CARETAKER_THREAD);
+		ao2_unlock(p);
+	}
+
+	/* we already unlocked them, clear them here so the cleanup label won't touch them. */
+	owner = ast_channel_unref(owner);
+	chan = ast_channel_unref(chan);
+
+return_cleanup:
+	if (p) {
+		if (pvt_locked) {
+			ao2_unlock(p);
+		}
+		ao2_ref(p, -1);
+	}
+	if (chan) {
+		ast_channel_unlock(chan);
+		ast_channel_unref(chan);
+	}
+
+	/*
+	 * owner is supposed to be == to ast, if it is, don't unlock it
+	 * because ast must exit locked
+	 */
+	if (owner) {
+		if (owner != ast) {
+			ast_channel_unlock(owner);
+			ast_channel_lock(ast);
+		}
+		ast_channel_unref(owner);
+	} else {
+		/* we have to exit with ast locked */
+		ast_channel_lock(ast);
+	}
+
+	return res;
+}
+
+/*! \brief Hangup a call through the local proxy channel */
+static int local_hangup(struct ast_channel *ast)
+{
+	struct local_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);
+	if (!res) {
+		int unlink;
+
+		ao2_lock(p);
+		unlink = !p->base.owner && !p->base.chan;
+		ao2_unlock(p);
+		if (unlink) {
+			ao2_unlink(locals, p);
+		}
+	}
+	ao2_ref(p, -1);
+
+	return res;
+}
+
+/*!
+ * \internal
+ * \brief struct local_pvt destructor.
+ *
+ * \param vdoomed Object to destroy.
+ *
+ * \return Nothing
+ */
+static void local_pvt_destructor(void *vdoomed)
+{
+	struct local_pvt *doomed = vdoomed;
+
+	switch (doomed->type) {
+	case LOCAL_CALL_ACTION_DIALPLAN:
+		break;
+	case LOCAL_CALL_ACTION_BRIDGE:
+		ao2_cleanup(doomed->action.bridge.join);
+		ao2_cleanup(doomed->action.bridge.swap);
+		ast_bridge_features_destroy(doomed->action.bridge.features);
+		break;
+	case LOCAL_CALL_ACTION_MASQUERADE:
+		ao2_cleanup(doomed->action.masq);
+		break;
+	}
+	ast_unreal_destructor(&doomed->base);
+}
+
+/*! \brief Create a call structure */
+static struct local_pvt *local_alloc(const char *data, struct ast_format_cap *cap)
+{
+	struct local_pvt *pvt;
+	char *parse;
+	char *context;
+	char *opts;
+
+	pvt = (struct local_pvt *) ast_unreal_alloc(sizeof(*pvt), local_pvt_destructor, cap);
+	if (!pvt) {
+		return NULL;
+	}
+
+	parse = ast_strdupa(data);
+
+	/*
+	 * Local channels intercept MOH by default.
+	 *
+	 * This is a silly default because it represents state held by
+	 * the local channels.  Unless local channel optimization is
+	 * disabled, the state will dissapear when the local channels
+	 * optimize out.
+	 */
+	ast_set_flag(&pvt->base, AST_UNREAL_MOH_INTERCEPT);
+
+	/* Look for options */
+	if ((opts = strchr(parse, '/'))) {
+		*opts++ = '\0';
+		if (strchr(opts, 'n')) {
+			ast_set_flag(&pvt->base, AST_UNREAL_NO_OPTIMIZATION);
+		}
+		if (strchr(opts, 'j')) {
+			if (ast_test_flag(&pvt->base, AST_UNREAL_NO_OPTIMIZATION)) {
+				ast_set_flag(&pvt->base.jb_conf, AST_JB_ENABLED);
+			} else {
+				ast_log(LOG_ERROR, "You must use the 'n' option with the 'j' option to enable the jitter buffer\n");
+			}
+		}
+		if (strchr(opts, 'm')) {
+			ast_clear_flag(&pvt->base, AST_UNREAL_MOH_INTERCEPT);
+		}
+	}
+
+	/* Look for a context */
+	if ((context = strchr(parse, '@'))) {
+		*context++ = '\0';
+	}
+
+	ast_copy_string(pvt->context, S_OR(context, "default"), sizeof(pvt->context));
+	ast_copy_string(pvt->exten, parse, sizeof(pvt->exten));
+	snprintf(pvt->base.name, sizeof(pvt->base.name), "%s@%s", pvt->exten, pvt->context);
+
+	return pvt; /* this is returned with a ref */
+}
+
+/*! \brief Part of PBX interface */
+static struct ast_channel *local_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause)
+{
+	struct local_pvt *p;
+	struct ast_channel *chan;
+	struct ast_callid *callid;
+
+	/* Allocate a new private structure and then Asterisk channels */
+	p = local_alloc(data, cap);
+	if (!p) {
+		return NULL;
+	}
+	callid = ast_read_threadstorage_callid();
+	chan = ast_unreal_new_channels(&p->base, &local_tech, AST_STATE_DOWN, AST_STATE_RING,
+		p->exten, p->context, requestor, callid);
+	if (chan) {
+		ao2_link(locals, p);
+	}
+	if (callid) {
+		ast_callid_unref(callid);
+	}
+	ao2_ref(p, -1); /* kill the ref from the alloc */
+
+	return chan;
+}
+
+/*! \brief CLI command "local show channels" */
+static char *locals_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct local_pvt *p;
+	struct ao2_iterator it;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "local show channels";
+		e->usage =
+			"Usage: local show channels\n"
+			"       Provides summary information on active local proxy channels.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 3) {
+		return CLI_SHOWUSAGE;
+	}
+
+	if (ao2_container_count(locals) == 0) {
+		ast_cli(a->fd, "No local channels in use\n");
+		return RESULT_SUCCESS;
+	}
+
+	it = ao2_iterator_init(locals, 0);
+	while ((p = ao2_iterator_next(&it))) {
+		ao2_lock(p);
+		ast_cli(a->fd, "%s -- %s\n",
+			p->base.owner ? ast_channel_name(p->base.owner) : "<unowned>",
+			p->base.name);
+		ao2_unlock(p);
+		ao2_ref(p, -1);
+	}
+	ao2_iterator_destroy(&it);
+
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_local[] = {
+	AST_CLI_DEFINE(locals_show, "List status of local channels"),
+};
+
+static int manager_optimize_away(struct mansession *s, const struct message *m)
+{
+	const char *channel;
+	struct local_pvt *p;
+	struct local_pvt *found;
+	struct ast_channel *chan;
+
+	channel = astman_get_header(m, "Channel");
+	if (ast_strlen_zero(channel)) {
+		astman_send_error(s, m, "'Channel' not specified.");
+		return 0;
+	}
+
+	chan = ast_channel_get_by_name(channel);
+	if (!chan) {
+		astman_send_error(s, m, "Channel does not exist.");
+		return 0;
+	}
+
+	p = ast_channel_tech_pvt(chan);
+	ast_channel_unref(chan);
+
+	found = p ? ao2_find(locals, p, 0) : NULL;
+	if (found) {
+		ao2_lock(found);
+		ast_clear_flag(&found->base, AST_UNREAL_NO_OPTIMIZATION);
+		ao2_unlock(found);
+		ao2_ref(found, -1);
+		astman_send_ack(s, m, "Queued channel to be optimized away");
+	} else {
+		astman_send_error(s, m, "Unable to find channel");
+	}
+
+	return 0;
+}
+
+
+static int locals_cmp_cb(void *obj, void *arg, int flags)
+{
+	return (obj == arg) ? CMP_MATCH : 0;
+}
+
+/*!
+ * \internal
+ * \brief Shutdown the local proxy channel.
+ * \since 12.0.0
+ *
+ * \return Nothing
+ */
+static void local_shutdown(void)
+{
+	struct local_pvt *p;
+	struct ao2_iterator it;
+
+	/* First, take us out of the channel loop */
+	ast_cli_unregister_multiple(cli_local, ARRAY_LEN(cli_local));
+	ast_manager_unregister("LocalOptimizeAway");
+	ast_channel_unregister(&local_tech);
+
+	it = ao2_iterator_init(locals, 0);
+	while ((p = ao2_iterator_next(&it))) {
+		if (p->base.owner) {
+			ast_softhangup(p->base.owner, AST_SOFTHANGUP_APPUNLOAD);
+		}
+		ao2_ref(p, -1);
+	}
+	ao2_iterator_destroy(&it);
+	ao2_ref(locals, -1);
+	locals = NULL;
+
+	ast_format_cap_destroy(local_tech.capabilities);
+}
+
+int ast_local_init(void)
+{
+	if (!(local_tech.capabilities = ast_format_cap_alloc())) {
+		return -1;
+	}
+	ast_format_cap_add_all(local_tech.capabilities);
+
+	locals = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, locals_cmp_cb);
+	if (!locals) {
+		ast_format_cap_destroy(local_tech.capabilities);
+		return -1;
+	}
+
+	/* Make sure we can register our channel type */
+	if (ast_channel_register(&local_tech)) {
+		ast_log(LOG_ERROR, "Unable to register channel class 'Local'\n");
+		ao2_ref(locals, -1);
+		ast_format_cap_destroy(local_tech.capabilities);
+		return -1;
+	}
+	ast_cli_register_multiple(cli_local, ARRAY_LEN(cli_local));
+	ast_manager_register_xml_core("LocalOptimizeAway", EVENT_FLAG_SYSTEM|EVENT_FLAG_CALL, manager_optimize_away);
+
+	ast_register_atexit(local_shutdown);
+	return 0;
+}
diff --git a/main/core_unreal.c b/main/core_unreal.c
new file mode 100644
index 0000000000000000000000000000000000000000..d5e5881117df2eeb6e2d47236805b73757a6a1f5
--- /dev/null
+++ b/main/core_unreal.c
@@ -0,0 +1,855 @@
+/*
+ * 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 Unreal channel derivatives framework for channel drivers like local channels.
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/causes.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/bridging.h"
+#include "asterisk/core_unreal.h"
+
+static unsigned int name_sequence = 0;
+
+void ast_unreal_lock_all(struct ast_unreal_pvt *p, struct ast_channel **outchan, struct ast_channel **outowner)
+{
+	struct ast_channel *chan = NULL;
+	struct ast_channel *owner = NULL;
+
+	ao2_lock(p);
+	for (;;) {
+		if (p->chan) {
+			chan = p->chan;
+			ast_channel_ref(chan);
+		}
+		if (p->owner) {
+			owner = p->owner;
+			ast_channel_ref(owner);
+		}
+		ao2_unlock(p);
+
+		/* if we don't have both channels, then this is very easy */
+		if (!owner || !chan) {
+			if (owner) {
+				ast_channel_lock(owner);
+			} else if(chan) {
+				ast_channel_lock(chan);
+			}
+		} else {
+			/* lock both channels first, then get the pvt lock */
+			ast_channel_lock_both(chan, owner);
+		}
+		ao2_lock(p);
+
+		/* Now that we have all the locks, validate that nothing changed */
+		if (p->owner != owner || p->chan != chan) {
+			if (owner) {
+				ast_channel_unlock(owner);
+				owner = ast_channel_unref(owner);
+			}
+			if (chan) {
+				ast_channel_unlock(chan);
+				chan = ast_channel_unref(chan);
+			}
+			continue;
+		}
+
+		break;
+	}
+	*outowner = p->owner;
+	*outchan = p->chan;
+}
+
+/* Called with ast locked */
+int ast_unreal_setoption(struct ast_channel *ast, int option, void *data, int datalen)
+{
+	int res = 0;
+	struct ast_unreal_pvt *p;
+	struct ast_channel *otherchan = NULL;
+	ast_chan_write_info_t *write_info;
+
+	if (option != AST_OPTION_CHANNEL_WRITE) {
+		return -1;
+	}
+
+	write_info = data;
+
+	if (write_info->version != AST_CHAN_WRITE_INFO_T_VERSION) {
+		ast_log(LOG_ERROR, "The chan_write_info_t type has changed, and this channel hasn't been updated!\n");
+		return -1;
+	}
+
+	if (!strcmp(write_info->function, "CHANNEL")
+		&& !strncasecmp(write_info->data, "hangup_handler_", 15)) {
+		/* Block CHANNEL(hangup_handler_xxx) writes to the other unreal channel. */
+		return 0;
+	}
+
+	/* get the tech pvt */
+	if (!(p = ast_channel_tech_pvt(ast))) {
+		return -1;
+	}
+	ao2_ref(p, 1);
+	ast_channel_unlock(ast); /* Held when called, unlock before locking another channel */
+
+	/* get the channel we are supposed to write to */
+	ao2_lock(p);
+	otherchan = (write_info->chan == p->owner) ? p->chan : p->owner;
+	if (!otherchan || otherchan == write_info->chan) {
+		res = -1;
+		otherchan = NULL;
+		ao2_unlock(p);
+		goto setoption_cleanup;
+	}
+	ast_channel_ref(otherchan);
+
+	/* clear the pvt lock before grabbing the channel */
+	ao2_unlock(p);
+
+	ast_channel_lock(otherchan);
+	res = write_info->write_fn(otherchan, write_info->function, write_info->data, write_info->value);
+	ast_channel_unlock(otherchan);
+
+setoption_cleanup:
+	ao2_ref(p, -1);
+	if (otherchan) {
+		ast_channel_unref(otherchan);
+	}
+	ast_channel_lock(ast); /* Lock back before we leave */
+	return res;
+}
+
+/* Called with ast locked */
+int ast_unreal_queryoption(struct ast_channel *ast, int option, void *data, int *datalen)
+{
+	struct ast_unreal_pvt *p;
+	struct ast_channel *peer;
+	struct ast_channel *other;
+	int res = 0;
+
+	if (option != AST_OPTION_T38_STATE) {
+		/* AST_OPTION_T38_STATE is the only supported option at this time */
+		return -1;
+	}
+
+	/* for some reason the channel is not locked in channel.c when this function is called */
+	if (!(p = ast_channel_tech_pvt(ast))) {
+		return -1;
+	}
+
+	ao2_lock(p);
+	other = AST_UNREAL_IS_OUTBOUND(ast, p) ? p->owner : p->chan;
+	if (!other) {
+		ao2_unlock(p);
+		return -1;
+	}
+	ast_channel_ref(other);
+	ao2_unlock(p);
+	ast_channel_unlock(ast); /* Held when called, unlock before locking another channel */
+
+	peer = ast_channel_bridge_peer(other);
+	if (peer) {
+		res = ast_channel_queryoption(peer, option, data, datalen, 0);
+		ast_channel_unref(peer);
+	}
+	ast_channel_unref(other);
+	ast_channel_lock(ast); /* Lock back before we leave */
+
+	return res;
+}
+
+/*!
+ * \brief queue a frame onto either the p->owner or p->chan
+ *
+ * \note the ast_unreal_pvt MUST have it's ref count bumped before entering this function and
+ * decremented after this function is called.  This is a side effect of the deadlock
+ * avoidance that is necessary to lock 2 channels and a tech_pvt.  Without a ref counted
+ * ast_unreal_pvt, it is impossible to guarantee it will not be destroyed by another thread
+ * during deadlock avoidance.
+ */
+static int unreal_queue_frame(struct ast_unreal_pvt *p, int isoutbound, struct ast_frame *f,
+	struct ast_channel *us, int us_locked)
+{
+	struct ast_channel *other;
+
+	/* Recalculate outbound channel */
+	other = isoutbound ? p->owner : p->chan;
+	if (!other) {
+		return 0;
+	}
+
+	/* do not queue frame if generator is on both unreal channels */
+	if (us && ast_channel_generator(us) && ast_channel_generator(other)) {
+		return 0;
+	}
+
+	/* grab a ref on the channel before unlocking the pvt,
+	 * other can not go away from us now regardless of locking */
+	ast_channel_ref(other);
+	if (us && us_locked) {
+		ast_channel_unlock(us);
+	}
+	ao2_unlock(p);
+
+	if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_RINGING) {
+		ast_setstate(other, AST_STATE_RINGING);
+	}
+	ast_queue_frame(other, f);
+
+	other = ast_channel_unref(other);
+	if (us && us_locked) {
+		ast_channel_lock(us);
+	}
+	ao2_lock(p);
+
+	return 0;
+}
+
+int ast_unreal_answer(struct ast_channel *ast)
+{
+	struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+	int isoutbound;
+	int res = -1;
+
+	if (!p) {
+		return -1;
+	}
+
+	ao2_ref(p, 1);
+	ao2_lock(p);
+	isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+	if (isoutbound) {
+		/* Pass along answer since somebody answered us */
+		struct ast_frame answer = { AST_FRAME_CONTROL, { AST_CONTROL_ANSWER } };
+
+		res = unreal_queue_frame(p, isoutbound, &answer, ast, 1);
+	} else {
+		ast_log(LOG_WARNING, "Huh?  %s is being asked to answer?\n",
+			ast_channel_name(ast));
+	}
+	ao2_unlock(p);
+	ao2_ref(p, -1);
+	return res;
+}
+
+/*!
+ * \internal
+ * \brief Check and optimize out the unreal channels between bridges.
+ * \since 12.0.0
+ *
+ * \param ast Channel writing a frame into the unreal channels.
+ * \param p Unreal channel private.
+ *
+ * \note It is assumed that ast is locked.
+ * \note It is assumed that p is locked.
+ *
+ * \retval 0 if unreal channels were not optimized out.
+ * \retval non-zero if unreal channels were optimized out.
+ */
+static int got_optimized_out(struct ast_channel *ast, struct ast_unreal_pvt *p)
+{
+	/* Do a few conditional checks early on just to see if this optimization is possible */
+	if (ast_test_flag(p, AST_UNREAL_NO_OPTIMIZATION) || !p->chan || !p->owner) {
+		return 0;
+	}
+	if (ast == p->owner) {
+		return ast_bridge_unreal_optimized_out(p->owner, p->chan);
+	}
+	if (ast == p->chan) {
+		return ast_bridge_unreal_optimized_out(p->chan, p->owner);
+	}
+	/* ast is not valid to optimize. */
+	return 0;
+}
+
+struct ast_frame  *ast_unreal_read(struct ast_channel *ast)
+{
+	return &ast_null_frame;
+}
+
+int ast_unreal_write(struct ast_channel *ast, struct ast_frame *f)
+{
+	struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+	int res = -1;
+
+	if (!p) {
+		return -1;
+	}
+
+	/* Just queue for delivery to the other side */
+	ao2_ref(p, 1);
+	ao2_lock(p);
+	switch (f->frametype) {
+	case AST_FRAME_VOICE:
+	case AST_FRAME_VIDEO:
+		if (got_optimized_out(ast, p)) {
+			break;
+		}
+		/* fall through */
+	default:
+		res = unreal_queue_frame(p, AST_UNREAL_IS_OUTBOUND(ast, p), f, ast, 1);
+		break;
+	}
+	ao2_unlock(p);
+	ao2_ref(p, -1);
+
+	return res;
+}
+
+int ast_unreal_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+	struct ast_unreal_pvt *p = ast_channel_tech_pvt(newchan);
+	struct ast_bridge *bridge_owner;
+	struct ast_bridge *bridge_chan;
+
+	if (!p) {
+		return -1;
+	}
+
+	ao2_lock(p);
+
+	if ((p->owner != oldchan) && (p->chan != oldchan)) {
+		ast_log(LOG_WARNING, "Old channel %p wasn't %p or %p\n", oldchan, p->owner, p->chan);
+		ao2_unlock(p);
+		return -1;
+	}
+	if (p->owner == oldchan) {
+		p->owner = newchan;
+	} else {
+		p->chan = newchan;
+	}
+
+	if (ast_check_hangup(newchan) || !p->owner || !p->chan) {
+		ao2_unlock(p);
+		return 0;
+	}
+
+	/* Do not let a masquerade cause an unreal channel to be bridged to itself! */
+	bridge_owner = ast_channel_internal_bridge(p->owner);
+	bridge_chan = ast_channel_internal_bridge(p->chan);
+	if (bridge_owner && bridge_owner == bridge_chan) {
+		ast_log(LOG_WARNING, "You can not bridge an unreal channel (%s) to itself!\n",
+			ast_channel_name(newchan));
+		ao2_unlock(p);
+		ast_queue_hangup(newchan);
+		return -1;
+	}
+
+	ao2_unlock(p);
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Queue up a frame representing the indication as a control frame.
+ * \since 12.0.0
+ *
+ * \param p Unreal private structure.
+ * \param ast Channel indicating the condition.
+ * \param condition What is being indicated.
+ * \param data Extra data.
+ * \param datalen Length of extra data.
+ *
+ * \retval 0 on success.
+ * \retval AST_T38_REQUEST_PARMS if successful and condition is AST_CONTROL_T38_PARAMETERS.
+ * \retval -1 on error.
+ */
+static int unreal_queue_indicate(struct ast_unreal_pvt *p, struct ast_channel *ast, int condition, const void *data, size_t datalen)
+{
+	int res = 0;
+	int isoutbound;
+
+	ao2_lock(p);
+	/*
+	 * Block -1 stop tones events if we are to be optimized out.  We
+	 * don't need a flurry of these events on an unreal channel chain
+	 * when initially connected to slow the optimization process.
+	 */
+	if (0 <= condition || ast_test_flag(p, AST_UNREAL_NO_OPTIMIZATION)) {
+		struct ast_frame f = {
+			.frametype = AST_FRAME_CONTROL,
+			.subclass.integer = condition,
+			.data.ptr = (void *) data,
+			.datalen = datalen,
+		};
+
+		isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+		res = unreal_queue_frame(p, isoutbound, &f, ast, 1);
+		if (!res
+			&& condition == AST_CONTROL_T38_PARAMETERS
+			&& datalen == sizeof(struct ast_control_t38_parameters)) {
+			const struct ast_control_t38_parameters *parameters = data;
+
+			if (parameters->request_response == AST_T38_REQUEST_PARMS) {
+				res = AST_T38_REQUEST_PARMS;
+			}
+		}
+	} else {
+		ast_debug(4, "Blocked indication %d\n", condition);
+	}
+	ao2_unlock(p);
+
+	return res;
+}
+
+/*!
+ * \internal
+ * \brief Handle COLP and redirecting conditions.
+ * \since 12.0.0
+ *
+ * \param p Unreal private structure.
+ * \param ast Channel indicating the condition.
+ * \param condition What is being indicated.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int unreal_colp_redirect_indicate(struct ast_unreal_pvt *p, struct ast_channel *ast, int condition)
+{
+	struct ast_channel *this_channel;
+	struct ast_channel *the_other_channel;
+	int isoutbound;
+	int res = 0;
+
+	/*
+	 * A connected line update frame may only contain a partial
+	 * amount of data, such as just a source, or just a ton, and not
+	 * the full amount of information.  However, the collected
+	 * information is all stored in the outgoing channel's
+	 * connectedline structure, so when receiving a connected line
+	 * update on an outgoing unreal channel, we need to transmit the
+	 * collected connected line information instead of whatever
+	 * happens to be in this control frame.  The same applies for
+	 * redirecting information, which is why it is handled here as
+	 * well.
+	 */
+	ao2_lock(p);
+	isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+	if (isoutbound) {
+		this_channel = p->chan;
+		the_other_channel = p->owner;
+	} else {
+		this_channel = p->owner;
+		the_other_channel = p->chan;
+	}
+	if (the_other_channel) {
+		unsigned char frame_data[1024];
+		struct ast_frame f = {
+			.frametype = AST_FRAME_CONTROL,
+			.subclass.integer = condition,
+			.data.ptr = frame_data,
+		};
+
+		if (condition == AST_CONTROL_CONNECTED_LINE) {
+			ast_connected_line_copy_to_caller(ast_channel_caller(the_other_channel),
+				ast_channel_connected(this_channel));
+			f.datalen = ast_connected_line_build_data(frame_data, sizeof(frame_data),
+				ast_channel_connected(this_channel), NULL);
+		} else {
+			f.datalen = ast_redirecting_build_data(frame_data, sizeof(frame_data),
+				ast_channel_redirecting(this_channel), NULL);
+		}
+		res = unreal_queue_frame(p, isoutbound, &f, ast, 1);
+	}
+	ao2_unlock(p);
+
+	return res;
+}
+
+int ast_unreal_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
+{
+	struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+	int res = 0;
+
+	if (!p) {
+		return -1;
+	}
+
+	ao2_ref(p, 1); /* ref for unreal_queue_frame */
+
+	switch (condition) {
+	case AST_CONTROL_CONNECTED_LINE:
+	case AST_CONTROL_REDIRECTING:
+		res = unreal_colp_redirect_indicate(p, ast, condition);
+		break;
+	case AST_CONTROL_HOLD:
+		if (ast_test_flag(p, AST_UNREAL_MOH_INTERCEPT)) {
+			ast_moh_start(ast, data, NULL);
+			break;
+		}
+		res = unreal_queue_indicate(p, ast, condition, data, datalen);
+		break;
+	case AST_CONTROL_UNHOLD:
+		if (ast_test_flag(p, AST_UNREAL_MOH_INTERCEPT)) {
+			ast_moh_stop(ast);
+			break;
+		}
+		res = unreal_queue_indicate(p, ast, condition, data, datalen);
+		break;
+	default:
+		res = unreal_queue_indicate(p, ast, condition, data, datalen);
+		break;
+	}
+
+	ao2_ref(p, -1);
+	return res;
+}
+
+int ast_unreal_digit_begin(struct ast_channel *ast, char digit)
+{
+	struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+	int res = -1;
+	struct ast_frame f = { AST_FRAME_DTMF_BEGIN, };
+	int isoutbound;
+
+	if (!p) {
+		return -1;
+	}
+
+	ao2_ref(p, 1); /* ref for unreal_queue_frame */
+	ao2_lock(p);
+	isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+	f.subclass.integer = digit;
+	res = unreal_queue_frame(p, isoutbound, &f, ast, 0);
+	ao2_unlock(p);
+	ao2_ref(p, -1);
+
+	return res;
+}
+
+int ast_unreal_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
+{
+	struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+	int res = -1;
+	struct ast_frame f = { AST_FRAME_DTMF_END, };
+	int isoutbound;
+
+	if (!p) {
+		return -1;
+	}
+
+	ao2_ref(p, 1); /* ref for unreal_queue_frame */
+	ao2_lock(p);
+	isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+	f.subclass.integer = digit;
+	f.len = duration;
+	res = unreal_queue_frame(p, isoutbound, &f, ast, 0);
+	ao2_unlock(p);
+	ao2_ref(p, -1);
+
+	return res;
+}
+
+int ast_unreal_sendtext(struct ast_channel *ast, const char *text)
+{
+	struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+	int res = -1;
+	struct ast_frame f = { AST_FRAME_TEXT, };
+	int isoutbound;
+
+	if (!p) {
+		return -1;
+	}
+
+	ao2_ref(p, 1); /* ref for unreal_queue_frame */
+	ao2_lock(p);
+	isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+	f.data.ptr = (char *) text;
+	f.datalen = strlen(text) + 1;
+	res = unreal_queue_frame(p, isoutbound, &f, ast, 0);
+	ao2_unlock(p);
+	ao2_ref(p, -1);
+	return res;
+}
+
+int ast_unreal_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen)
+{
+	struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+	int res = -1;
+	struct ast_frame f = { AST_FRAME_HTML, };
+	int isoutbound;
+
+	if (!p) {
+		return -1;
+	}
+
+	ao2_ref(p, 1); /* ref for unreal_queue_frame */
+	ao2_lock(p);
+	isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+	f.subclass.integer = subclass;
+	f.data.ptr = (char *)data;
+	f.datalen = datalen;
+	res = unreal_queue_frame(p, isoutbound, &f, ast, 0);
+	ao2_unlock(p);
+	ao2_ref(p, -1);
+
+	return res;
+}
+
+void ast_unreal_call_setup(struct ast_channel *semi1, struct ast_channel *semi2)
+{
+	struct ast_var_t *varptr;
+	struct ast_var_t *clone_var;
+
+	/*
+	 * Note that cid_num and cid_name aren't passed in the
+	 * ast_channel_alloc calls in ast_unreal_new_channels().  It's
+	 * done here instead.
+	 */
+	ast_party_redirecting_copy(ast_channel_redirecting(semi2), ast_channel_redirecting(semi1));
+
+	ast_party_dialed_copy(ast_channel_dialed(semi2), ast_channel_dialed(semi1));
+
+	ast_connected_line_copy_to_caller(ast_channel_caller(semi2), ast_channel_connected(semi1));
+	ast_connected_line_copy_from_caller(ast_channel_connected(semi2), ast_channel_caller(semi1));
+
+	ast_channel_language_set(semi2, ast_channel_language(semi1));
+	ast_channel_accountcode_set(semi2, ast_channel_accountcode(semi1));
+	ast_channel_musicclass_set(semi2, ast_channel_musicclass(semi1));
+
+	ast_channel_cc_params_init(semi2, ast_channel_get_cc_config_params(semi1));
+
+	/*
+	 * Make sure we inherit the AST_CAUSE_ANSWERED_ELSEWHERE if it's
+	 * set on the queue/dial call request in the dialplan.
+	 */
+	if (ast_channel_hangupcause(semi1) == AST_CAUSE_ANSWERED_ELSEWHERE) {
+		ast_channel_hangupcause_set(semi2, AST_CAUSE_ANSWERED_ELSEWHERE);
+	}
+
+	/*
+	 * Copy the channel variables from the semi1 channel to the
+	 * outgoing channel.
+	 *
+	 * Note that due to certain assumptions, they MUST be in the
+	 * same order.
+	 */
+	AST_LIST_TRAVERSE(ast_channel_varshead(semi1), varptr, entries) {
+		clone_var = ast_var_assign(varptr->name, varptr->value);
+		if (clone_var) {
+			AST_LIST_INSERT_TAIL(ast_channel_varshead(semi2), clone_var, entries);
+		}
+	}
+	ast_channel_datastore_inherit(semi1, semi2);
+}
+
+int ast_unreal_hangup(struct ast_unreal_pvt *p, struct ast_channel *ast)
+{
+	int hangup_chan = 0;
+	int res = 0;
+	int cause;
+	struct ast_channel *owner = NULL;
+	struct ast_channel *chan = NULL;
+
+	/* the pvt isn't going anywhere, it has a ref */
+	ast_channel_unlock(ast);
+
+	/* lock everything */
+	ast_unreal_lock_all(p, &chan, &owner);
+
+	if (ast != chan && ast != owner) {
+		res = -1;
+		goto unreal_hangup_cleanup;
+	}
+
+	cause = ast_channel_hangupcause(ast);
+
+	if (ast == p->chan) {
+		/* Outgoing side is hanging up. */
+		ast_clear_flag(p, AST_UNREAL_CARETAKER_THREAD);
+		p->chan = NULL;
+		if (p->owner) {
+			const char *status = pbx_builtin_getvar_helper(p->chan, "DIALSTATUS");
+
+			if (status) {
+				ast_channel_hangupcause_set(p->owner, cause);
+				pbx_builtin_setvar_helper(p->owner, "CHANLOCALSTATUS", status);
+			}
+			ast_queue_hangup_with_cause(p->owner, cause);
+		}
+	} else {
+		/* Owner side is hanging up. */
+		p->owner = NULL;
+		if (p->chan) {
+			if (cause == AST_CAUSE_ANSWERED_ELSEWHERE) {
+				ast_channel_hangupcause_set(p->chan, AST_CAUSE_ANSWERED_ELSEWHERE);
+				ast_debug(2, "%s has AST_CAUSE_ANSWERED_ELSEWHERE set.\n",
+					ast_channel_name(p->chan));
+			}
+			if (!ast_test_flag(p, AST_UNREAL_CARETAKER_THREAD)) {
+				/*
+				 * Need to actually hangup p->chan since nothing else is taking
+				 * care of it.
+				 */
+				hangup_chan = 1;
+			} else {
+				ast_queue_hangup_with_cause(p->chan, cause);
+			}
+		}
+	}
+
+	/* this is one of our locked channels, doesn't matter which */
+	ast_channel_tech_pvt_set(ast, NULL);
+	ao2_ref(p, -1);
+
+unreal_hangup_cleanup:
+	ao2_unlock(p);
+	if (owner) {
+		ast_channel_unlock(owner);
+		ast_channel_unref(owner);
+	}
+	if (chan) {
+		ast_channel_unlock(chan);
+		if (hangup_chan) {
+			ast_hangup(chan);
+		}
+		ast_channel_unref(chan);
+	}
+
+	/* leave with the channel locked that came in */
+	ast_channel_lock(ast);
+
+	return res;
+}
+
+void ast_unreal_destructor(void *vdoomed)
+{
+	struct ast_unreal_pvt *doomed = vdoomed;
+
+	doomed->reqcap = ast_format_cap_destroy(doomed->reqcap);
+}
+
+struct ast_unreal_pvt *ast_unreal_alloc(size_t size, ao2_destructor_fn destructor, struct ast_format_cap *cap)
+{
+	struct ast_unreal_pvt *unreal;
+
+	static const struct ast_jb_conf jb_conf = {
+		.flags = 0,
+		.max_size = -1,
+		.resync_threshold = -1,
+		.impl = "",
+		.target_extra = -1,
+	};
+
+	unreal = ao2_alloc(size, destructor);
+	if (!unreal) {
+		return NULL;
+	}
+	unreal->reqcap = ast_format_cap_dup(cap);
+	if (!unreal->reqcap) {
+		ao2_ref(unreal, -1);
+		return NULL;
+	}
+
+	memcpy(&unreal->jb_conf, &jb_conf, sizeof(unreal->jb_conf));
+
+	return unreal;
+}
+
+struct ast_channel *ast_unreal_new_channels(struct ast_unreal_pvt *p,
+	const struct ast_channel_tech *tech, int semi1_state, int semi2_state,
+	const char *exten, const char *context, const struct ast_channel *requestor,
+	struct ast_callid *callid)
+{
+	struct ast_channel *owner;
+	struct ast_channel *chan;
+	const char *linkedid = requestor ? ast_channel_linkedid(requestor) : NULL;
+	struct ast_format fmt;
+	int generated_seqno = ast_atomic_fetchadd_int((int *) &name_sequence, +1);
+
+	/*
+	 * Allocate two new Asterisk channels
+	 *
+	 * Make sure that the ;2 channel gets the same linkedid as ;1.
+	 * You can't pass linkedid to both allocations since if linkedid
+	 * isn't set, then each channel will generate its own linkedid.
+	 */
+	if (!(owner = ast_channel_alloc(1, semi1_state, NULL, NULL, NULL,
+			exten, context, linkedid, 0,
+			"%s/%s-%08x;1", tech->type, p->name, generated_seqno))
+		|| !(chan = ast_channel_alloc(1, semi2_state, NULL, NULL, NULL,
+			exten, context, ast_channel_linkedid(owner), 0,
+			"%s/%s-%08x;2", tech->type, p->name, generated_seqno))) {
+		if (owner) {
+			owner = ast_channel_release(owner);
+		}
+		ast_log(LOG_WARNING, "Unable to allocate channel structure(s)\n");
+		return NULL;
+	}
+
+	if (callid) {
+		ast_channel_callid_set(owner, callid);
+		ast_channel_callid_set(chan, callid);
+	}
+
+	ast_channel_tech_set(owner, tech);
+	ast_channel_tech_set(chan, tech);
+	ast_channel_tech_pvt_set(owner, p);
+	ast_channel_tech_pvt_set(chan, p);
+
+	ast_format_cap_copy(ast_channel_nativeformats(owner), p->reqcap);
+	ast_format_cap_copy(ast_channel_nativeformats(chan), p->reqcap);
+
+	/* Determine our read/write format and set it on each channel */
+	ast_best_codec(p->reqcap, &fmt);
+	ast_format_copy(ast_channel_writeformat(owner), &fmt);
+	ast_format_copy(ast_channel_writeformat(chan), &fmt);
+	ast_format_copy(ast_channel_rawwriteformat(owner), &fmt);
+	ast_format_copy(ast_channel_rawwriteformat(chan), &fmt);
+	ast_format_copy(ast_channel_readformat(owner), &fmt);
+	ast_format_copy(ast_channel_readformat(chan), &fmt);
+	ast_format_copy(ast_channel_rawreadformat(owner), &fmt);
+	ast_format_copy(ast_channel_rawreadformat(chan), &fmt);
+
+	ast_set_flag(ast_channel_flags(owner), AST_FLAG_DISABLE_DEVSTATE_CACHE);
+	ast_set_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_DEVSTATE_CACHE);
+
+	ast_jb_configure(owner, &p->jb_conf);
+
+	if (ast_channel_cc_params_init(owner, requestor
+		? ast_channel_get_cc_config_params((struct ast_channel *) requestor) : NULL)) {
+		ast_channel_release(owner);
+		ast_channel_release(chan);
+		return NULL;
+	}
+
+	/* Give the private a ref for each channel. */
+	ao2_ref(p, +2);
+	p->owner = owner;
+	p->chan = chan;
+
+	return owner;
+}
diff --git a/main/features.c b/main/features.c
index b6cc1916497b41cabb4b0baa52d477a30a427511..be872a80d64ce4769f1f4f3f8ed0f8be7e598ab5 100644
--- a/main/features.c
+++ b/main/features.c
@@ -72,6 +72,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/astobj2.h"
 #include "asterisk/cel.h"
 #include "asterisk/test.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_basic.h"
 
 /*
  * Party A - transferee
@@ -248,129 +250,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 			</variablelist>
 		</description>
 	</application>
-	<application name="ParkedCall" language="en_US">
-		<synopsis>
-			Retrieve a parked call.
-		</synopsis>
-		<syntax>
-			<parameter name="exten">
-				<para>Parking space extension to retrieve a parked call.
-				If not provided then the first available parked call in the
-				parking lot will be retrieved.</para>
-			</parameter>
-			<parameter name="parking_lot_name">
-				<para>Specify from which parking lot to retrieve a parked call.</para>
-				<para>The parking lot used is selected in the following order:</para>
-				<para>1) parking_lot_name option</para>
-				<para>2) <variable>PARKINGLOT</variable> variable</para>
-				<para>3) <literal>CHANNEL(parkinglot)</literal> function
-				(Possibly preset by the channel driver.)</para>
-				<para>4) Default parking lot.</para>
-			</parameter>
-		</syntax>
-		<description>
-			<para>Used to retrieve a parked call from a parking lot.</para>
-			<note>
-				<para>Parking lots automatically create and manage dialplan extensions in
-				the parking lot context.  You do not need to explicitly use this
-				application in your dialplan.  Instead, all you should do is include the
-				parking lot context in your dialplan.</para>
-			</note>
-		</description>
-		<see-also>
-			<ref type="application">Park</ref>
-			<ref type="application">ParkAndAnnounce</ref>
-		</see-also>
-	</application>
-	<application name="Park" language="en_US">
-		<synopsis>
-			Park yourself.
-		</synopsis>
-		<syntax>
-			<parameter name="timeout">
-				<para>A custom parking timeout for this parked call. Value in milliseconds.</para>
-			</parameter>
-			<parameter name="return_context">
-				<para>The context to return the call to after it times out.</para>
-			</parameter>
-			<parameter name="return_exten">
-				<para>The extension to return the call to after it times out.</para>
-			</parameter>
-			<parameter name="return_priority">
-				<para>The priority to return the call to after it times out.</para>
-			</parameter>
-			<parameter name="options">
-				<para>A list of options for this parked call.</para>
-				<optionlist>
-					<option name="r">
-						<para>Send ringing instead of MOH to the parked call.</para>
-					</option>
-					<option name="R">
-						<para>Randomize the selection of a parking space.</para>
-					</option>
-					<option name="s">
-						<para>Silence announcement of the parking space number.</para>
-					</option>
-				</optionlist>
-			</parameter>
-			<parameter name="parking_lot_name">
-				<para>Specify in which parking lot to park a call.</para>
-				<para>The parking lot used is selected in the following order:</para>
-				<para>1) parking_lot_name option</para>
-				<para>2) <variable>PARKINGLOT</variable> variable</para>
-				<para>3) <literal>CHANNEL(parkinglot)</literal> function
-				(Possibly preset by the channel driver.)</para>
-				<para>4) Default parking lot.</para>
-			</parameter>
-		</syntax>
-		<description>
-			<para>Used to park yourself (typically in combination with a supervised
-			transfer to know the parking space).</para>
-			<para>If you set the <variable>PARKINGEXTEN</variable> variable to a
-			parking space extension in the parking lot, Park() will attempt to park the call
-			on that extension.  If the extension is already is in use then execution
-			will continue at the next priority.</para>
-			<para>If the <literal>parkeddynamic</literal> option is enabled in <filename>features.conf</filename>
-			the following variables can be used to dynamically create new parking lots.</para>
-			<para>If you set the <variable>PARKINGDYNAMIC</variable> variable and this parking lot
-			exists then it will be used as a template for the newly created dynamic lot.  Otherwise,
-			the default parking lot will be used.</para>
-			<para>If you set the <variable>PARKINGDYNCONTEXT</variable> variable then the newly created dynamic
-			parking lot will use this context.</para>
-			<para>If you set the <variable>PARKINGDYNEXTEN</variable> variable then the newly created dynamic
-			parking lot will use this extension to access the parking lot.</para>
-			<para>If you set the <variable>PARKINGDYNPOS</variable> variable then the newly created dynamic parking lot
-			will use those parking postitions.</para>
-			<note>
-				<para>This application must be used as the first extension priority
-				to be recognized as a parking access extension.  DTMF transfers
-				and some channel drivers need this distinction to operate properly.
-				The parking access extension in this case is treated like a dialplan
-				hint.</para>
-			</note>
-			<note>
-				<para>Parking lots automatically create and manage dialplan extensions in
-				the parking lot context.  You do not need to explicitly use this
-				application in your dialplan.  Instead, all you should do is include the
-				parking lot context in your dialplan.</para>
-			</note>
-		</description>
-		<see-also>
-			<ref type="application">ParkAndAnnounce</ref>
-			<ref type="application">ParkedCall</ref>
-		</see-also>
-	</application>
-	<manager name="ParkedCalls" language="en_US">
-		<synopsis>
-			List parked calls.
-		</synopsis>
-		<syntax>
-			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
-		</syntax>
-		<description>
-			<para>List parked calls.</para>
-		</description>
-	</manager>
 	<manager name="Park" language="en_US">
 		<synopsis>
 			Park a channel.
@@ -418,17 +297,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 			<para>Bridge together two channels already in the PBX.</para>
 		</description>
 	</manager>
-	<manager name="Parkinglots" language="en_US">
-		<synopsis>
-			Get a list of parking lots
-		</synopsis>
-		<syntax>
-			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
-		</syntax>
-		<description>
-			<para>List all parking lots as a series of AMI events</para>
-		</description>
-	</manager>
 	<function name="FEATURE" language="en_US">
 		<synopsis>
 			Get or set a feature option on a channel.
@@ -538,6 +406,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #define AST_MAX_WATCHERS 256
 #define MAX_DIAL_FEATURE_OPTIONS 30
 
+/* TODO Scrape all of the parking stuff out of features.c */
+
 struct feature_group_exten {
 	AST_LIST_ENTRY(feature_group_exten) entry;
 	AST_DECLARE_STRING_FIELDS(
@@ -1134,65 +1004,40 @@ static void *bridge_call_thread(void *data)
 		ast_channel_data_set(tobj->peer, "(Empty)");
 	}
 
-	ast_bridge_call(tobj->peer, tobj->chan, &tobj->bconfig);
-
 	if (tobj->return_to_pbx) {
-		if (!ast_check_hangup(tobj->peer)) {
-			ast_log(LOG_VERBOSE, "putting peer %s into PBX again\n", ast_channel_name(tobj->peer));
-			if (ast_pbx_start(tobj->peer)) {
-				ast_log(LOG_WARNING, "FAILED continuing PBX on peer %s\n", ast_channel_name(tobj->peer));
-				ast_autoservice_chan_hangup_peer(tobj->chan, tobj->peer);
-			}
-		} else {
-			ast_autoservice_chan_hangup_peer(tobj->chan, tobj->peer);
-		}
-		if (!ast_check_hangup(tobj->chan)) {
-			ast_log(LOG_VERBOSE, "putting chan %s into PBX again\n", ast_channel_name(tobj->chan));
-			if (ast_pbx_start(tobj->chan)) {
-				ast_log(LOG_WARNING, "FAILED continuing PBX on chan %s\n", ast_channel_name(tobj->chan));
-				ast_hangup(tobj->chan);
-			}
-		} else {
-			ast_hangup(tobj->chan);
-		}
-	} else {
-		ast_hangup(tobj->chan);
-		ast_hangup(tobj->peer);
+		ast_after_bridge_set_goto(tobj->chan, ast_channel_context(tobj->chan),
+			ast_channel_exten(tobj->chan), ast_channel_priority(tobj->chan));
+		ast_after_bridge_set_goto(tobj->peer, ast_channel_context(tobj->peer),
+			ast_channel_exten(tobj->peer), ast_channel_priority(tobj->peer));
 	}
 
+	ast_bridge_call(tobj->chan, tobj->peer, &tobj->bconfig);
+
+	ast_after_bridge_goto_run(tobj->chan);
+
 	ast_free(tobj);
 
 	return NULL;
 }
 
 /*!
- * \brief create thread for the parked call
- * \param data
- *
- * Create thread and attributes, call bridge_call_thread
+ * \brief create thread for the bridging call
+ * \param tobj
  */
-static void bridge_call_thread_launch(struct ast_bridge_thread_obj *data)
+static void bridge_call_thread_launch(struct ast_bridge_thread_obj *tobj)
 {
 	pthread_t thread;
-	pthread_attr_t attr;
-	struct sched_param sched;
 
 	/* This needs to be unreffed once it has been associated with the new thread. */
-	data->callid = ast_read_threadstorage_callid();
-
-	pthread_attr_init(&attr);
-	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
-	if (ast_pthread_create(&thread, &attr, bridge_call_thread, data)) {
-		/* Failed to create thread. Ditch the reference to callid. */
-		ast_callid_unref(data->callid);
-		ast_hangup(data->chan);
-		ast_hangup(data->peer);
+	tobj->callid = ast_read_threadstorage_callid();
+
+	if (ast_pthread_create_detached(&thread, NULL, bridge_call_thread, tobj)) {
 		ast_log(LOG_ERROR, "Failed to create bridge_call_thread.\n");
-		return;
+		ast_callid_unref(tobj->callid);
+		ast_hangup(tobj->chan);
+		ast_hangup(tobj->peer);
+		ast_free(tobj);
 	}
-	pthread_attr_destroy(&attr);
-	memset(&sched, 0, sizeof(sched));
-	pthread_setschedparam(thread, SCHED_RR, &sched);
 }
 
 /*!
@@ -2583,11 +2428,6 @@ static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *p
 	if (ast_async_goto(transferee, transferer_real_context, xferto, 1)) {
 		ast_log(LOG_WARNING, "Async goto failed :-(\n");
 		res = -1;
-	} else if (res == AST_FEATURE_RETURN_SUCCESSBREAK) {
-		/* Don't let the after-bridge code run the h-exten */
-		ast_channel_lock(transferee);
-		ast_set_flag(ast_channel_flags(transferee), AST_FLAG_BRIDGE_HANGUP_DONT);
-		ast_channel_unlock(transferee);
 	}
 	check_goto_on_transfer(transferer);
 	return res;
@@ -2774,8 +2614,6 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
 	ast_debug(2, "Dial party C result: newchan:%d, outstate:%d\n", !!newchan, outstate);
 
 	if (!ast_check_hangup(transferer)) {
-		int hangup_dont = 0;
-
 		/* Transferer (party B) is up */
 		ast_debug(1, "Actually doing an attended transfer.\n");
 
@@ -2814,32 +2652,12 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
 		ast_set_flag(&(bconfig.features_caller), AST_FEATURE_DISCONNECT);
 		ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT);
 
-		/*
-		 * ast_bridge_call clears AST_FLAG_BRIDGE_HANGUP_DONT, but we
-		 * don't want that to happen here because the transferer is in
-		 * another bridge already.
-		 */
-		if (ast_test_flag(ast_channel_flags(transferer), AST_FLAG_BRIDGE_HANGUP_DONT)) {
-			hangup_dont = 1;
-		}
-
-		/*
-		 * Don't let the after-bridge code run the h-exten.  It is the
-		 * wrong bridge to run the h-exten after.
-		 */
-		ast_set_flag(ast_channel_flags(transferer), AST_FLAG_BRIDGE_HANGUP_DONT);
-
 		/*
 		 * Let party B and C talk as long as they want while party A
 		 * languishes in autoservice listening to MOH.
 		 */
 		ast_bridge_call(transferer, newchan, &bconfig);
 
-		if (hangup_dont) {
-			/* Restore the AST_FLAG_BRIDGE_HANGUP_DONT flag */
-			ast_set_flag(ast_channel_flags(transferer), AST_FLAG_BRIDGE_HANGUP_DONT);
-		}
-
 		if (ast_check_hangup(newchan) || !ast_check_hangup(transferer)) {
 			ast_autoservice_chan_hangup_peer(transferer, newchan);
 			if (ast_stream_and_wait(transferer, xfersound, "")) {
@@ -3731,6 +3549,7 @@ static int feature_interpret_helper(struct ast_channel *chan, struct ast_channel
 	return res;
 }
 
+#if 0//BUGBUG
 /*!
  * \brief Check the dynamic features
  * \param chan,peer,config,code,sense
@@ -3777,12 +3596,14 @@ static int feature_interpret(struct ast_channel *chan, struct ast_channel *peer,
 
 	return res;
 }
+#endif
 
 
 int ast_feature_detect(struct ast_channel *chan, struct ast_flags *features, const char *code, struct ast_call_feature *feature) {
 	return feature_interpret_helper(chan, NULL, NULL, code, 0, NULL, features, FEATURE_INTERPRET_DETECT, feature);
 }
 
+#if 0//BUGBUG
 /*! \brief Check if a feature exists */
 static int feature_check(struct ast_channel *chan, struct ast_flags *features, char *code) {
 	struct ast_str *chan_dynamic_features;
@@ -3801,11 +3622,15 @@ static int feature_check(struct ast_channel *chan, struct ast_flags *features, c
 
 	return res;
 }
+#endif
 
 static void set_config_flags(struct ast_channel *chan, struct ast_bridge_config *config)
 {
 	int x;
 
+/* BUGBUG there is code that checks AST_BRIDGE_IGNORE_SIGS but no code to set it. */
+/* BUGBUG there is code that checks AST_BRIDGE_REC_CHANNEL_0 but no code to set it. */
+/* BUGBUG there is code that checks AST_BRIDGE_REC_CHANNEL_1 but no code to set it. */
 	ast_clear_flag(config, AST_FLAGS_ALL);
 
 	ast_rdlock_call_features();
@@ -4208,37 +4033,22 @@ void ast_channel_log(char *title, struct ast_channel *chan) /* for debug, this i
 {
 	ast_log(LOG_NOTICE, "______ %s (%lx)______\n", title, (unsigned long) chan);
 	ast_log(LOG_NOTICE, "CHAN: name: %s;  appl: %s; data: %s; contxt: %s;  exten: %s; pri: %d;\n",
-		ast_channel_name(chan), ast_channel_appl(chan), ast_channel_data(chan), ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan));
+		ast_channel_name(chan), ast_channel_appl(chan), ast_channel_data(chan),
+		ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan));
 	ast_log(LOG_NOTICE, "CHAN: acctcode: %s;  dialcontext: %s; amaflags: %x; maccontxt: %s;  macexten: %s; macpri: %d;\n",
-		ast_channel_accountcode(chan), ast_channel_dialcontext(chan), ast_channel_amaflags(chan), ast_channel_macrocontext(chan), ast_channel_macroexten(chan), ast_channel_macropriority(chan));
-	ast_log(LOG_NOTICE, "CHAN: masq: %p;  masqr: %p; _bridge: %p; uniqueID: %s; linkedID:%s\n",
+		ast_channel_accountcode(chan), ast_channel_dialcontext(chan), ast_channel_amaflags(chan),
+		ast_channel_macrocontext(chan), ast_channel_macroexten(chan), ast_channel_macropriority(chan));
+	ast_log(LOG_NOTICE, "CHAN: masq: %p;  masqr: %p; uniqueID: %s; linkedID:%s\n",
 		ast_channel_masq(chan), ast_channel_masqr(chan),
-		ast_channel_internal_bridged_channel(chan), ast_channel_uniqueid(chan), ast_channel_linkedid(chan));
+		ast_channel_uniqueid(chan), ast_channel_linkedid(chan));
 	if (ast_channel_masqr(chan)) {
 		ast_log(LOG_NOTICE, "CHAN: masquerading as: %s;  cdr: %p;\n",
 			ast_channel_name(ast_channel_masqr(chan)), ast_channel_cdr(ast_channel_masqr(chan)));
 	}
-	if (ast_channel_internal_bridged_channel(chan)) {
-		ast_log(LOG_NOTICE, "CHAN: Bridged to %s\n", ast_channel_name(ast_channel_internal_bridged_channel(chan)));
-	}
 
 	ast_log(LOG_NOTICE, "===== done ====\n");
 }
 
-/*!
- * \brief return the first unlocked cdr in a possible chain
- */
-static struct ast_cdr *pick_unlocked_cdr(struct ast_cdr *cdr)
-{
-	struct ast_cdr *cdr_orig = cdr;
-	while (cdr) {
-		if (!ast_test_flag(cdr,AST_CDR_FLAG_LOCKED))
-			return cdr;
-		cdr = cdr->next;
-	}
-	return cdr_orig; /* everybody LOCKED or some other weirdness, like a NULL */
-}
-
 static void set_bridge_features_on_config(struct ast_bridge_config *config, const char *features)
 {
 	const char *feature;
@@ -4249,17 +4059,14 @@ static void set_bridge_features_on_config(struct ast_bridge_config *config, cons
 
 	for (feature = features; *feature; feature++) {
 		struct ast_flags *party;
-		char this_feature;
 
 		if (isupper(*feature)) {
-			party = &(config->features_caller);
+			party = &config->features_caller;
 		} else {
-			party = &(config->features_callee);
+			party = &config->features_callee;
 		}
 
-		this_feature = tolower(*feature);
-
-		switch (this_feature) {
+		switch (tolower(*feature)) {
 		case 't' :
 			ast_set_flag(party, AST_FEATURE_REDIRECT);
 			break;
@@ -4277,6 +4084,7 @@ static void set_bridge_features_on_config(struct ast_bridge_config *config, cons
 			break;
 		default :
 			ast_log(LOG_WARNING, "Skipping unknown feature code '%c'\n", *feature);
+			break;
 		}
 	}
 }
@@ -4332,6 +4140,279 @@ void ast_bridge_end_dtmf(struct ast_channel *chan, char digit, struct timeval st
 		digit, ast_channel_name(chan), why, duration);
 }
 
+/*!
+ * \internal
+ * \brief Setup bridge builtin features.
+ * \since 12.0.0
+ *
+ * \param features Bridge features to setup.
+ * \param chan Get features from this channel.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int setup_bridge_features_builtin(struct ast_bridge_features *features, struct ast_channel *chan)
+{
+	struct ast_flags *flags;
+	char dtmf[FEATURE_MAX_LEN];
+	int res;
+
+	ast_channel_lock(chan);
+	flags = ast_bridge_features_ds_get(chan);
+	ast_channel_unlock(chan);
+	if (!flags) {
+		return 0;
+	}
+
+	res = 0;
+	ast_rdlock_call_features();
+	if (ast_test_flag(flags, AST_FEATURE_REDIRECT)) {
+		/* Add atxfer and blind transfer. */
+		builtin_feature_get_exten(chan, "blindxfer", dtmf, sizeof(dtmf));
+		if (!ast_strlen_zero(dtmf)) {
+/* BUGBUG need to supply a blind transfer structure and destructor to use other than defaults */
+			res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_BLINDTRANSFER, dtmf, NULL, NULL, 1);
+		}
+		builtin_feature_get_exten(chan, "atxfer", dtmf, sizeof(dtmf));
+		if (!ast_strlen_zero(dtmf)) {
+/* BUGBUG need to supply an attended transfer structure and destructor to use other than defaults */
+			res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, dtmf, NULL, NULL, 1);
+		}
+	}
+	if (ast_test_flag(flags, AST_FEATURE_DISCONNECT)) {
+		builtin_feature_get_exten(chan, "disconnect", dtmf, sizeof(dtmf));
+		if (ast_strlen_zero(dtmf)) {
+			res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_HANGUP, dtmf, NULL, NULL, 1);
+		}
+	}
+	if (ast_test_flag(flags, AST_FEATURE_PARKCALL)) {
+		builtin_feature_get_exten(chan, "parkcall", dtmf, sizeof(dtmf));
+		if (!ast_strlen_zero(dtmf)) {
+			res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_PARKCALL, dtmf, NULL, NULL, 1);
+		}
+	}
+	if (ast_test_flag(flags, AST_FEATURE_AUTOMON)) {
+		builtin_feature_get_exten(chan, "automon", dtmf, sizeof(dtmf));
+		if (!ast_strlen_zero(dtmf)) {
+			res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMON, dtmf, NULL, NULL, 1);
+		}
+	}
+	if (ast_test_flag(flags, AST_FEATURE_AUTOMIXMON)) {
+		builtin_feature_get_exten(chan, "automixmon", dtmf, sizeof(dtmf));
+		if (!ast_strlen_zero(dtmf)) {
+			res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMIXMON, dtmf, NULL, NULL, 1);
+		}
+	}
+	ast_unlock_call_features();
+
+#if 0	/* BUGBUG don't report errors untill all of the builtin features are supported. */
+	return res ? -1 : 0;
+#else
+	return 0;
+#endif
+}
+
+struct dtmf_hook_run_app {
+	/*! Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER) */
+	unsigned int flags;
+	/*! Offset into app_name[] where the MOH class name starts.  (zero if no MOH) */
+	int moh_offset;
+	/*! Offset into app_name[] where the application argument string starts. (zero if no arguments) */
+	int app_args_offset;
+	/*! Application name to run. */
+	char app_name[0];
+};
+
+/*!
+ * \internal
+ * \brief Setup bridge dynamic features.
+ * \since 12.0.0
+ *
+ * \param bridge The bridge that the channel is part of
+ * \param bridge_channel Channel executing the feature
+ * \param hook_pvt Private data passed in when the hook was created
+ *
+ * \retval 0 Keep the callback hook.
+ * \retval -1 Remove the callback hook.
+ */
+static int app_dtmf_feature_hook(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+	struct dtmf_hook_run_app *pvt = hook_pvt;
+	void (*run_it)(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class);
+
+	if (ast_test_flag(pvt, AST_FEATURE_FLAG_ONPEER)) {
+		run_it = ast_bridge_channel_write_app;
+	} else {
+		run_it = ast_bridge_channel_run_app;
+	}
+
+/*
+ * BUGBUG need to pass to run_it the triggering channel name so DYNAMIC_WHO_TRIGGERED can be set on the channel when it is run.
+ *
+ * This would replace DYNAMIC_PEERNAME which is redundant with
+ * BRIDGEPEER anyway.  The value of DYNAMIC_WHO_TRIGGERED is
+ * really useful in the case of a multi-party bridge.
+ */
+	run_it(bridge_channel, pvt->app_name,
+		pvt->app_args_offset ? &pvt->app_name[pvt->app_args_offset] : NULL,
+		pvt->moh_offset ? &pvt->app_name[pvt->moh_offset] : NULL);
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Add a dynamic DTMF feature hook to the bridge features.
+ * \since 12.0.0
+ *
+ * \param features Bridge features to setup.
+ * \param flags Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER).
+ * \param dtmf DTMF trigger sequence.
+ * \param app_name Dialplan application name to run.
+ * \param app_args Dialplan application arguments. (Empty or NULL if no arguments)
+ * \param moh_class MOH class to play to peer. (Empty or NULL if no MOH played)
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int add_dynamic_dtmf_hook(struct ast_bridge_features *features, unsigned int flags, const char *dtmf, const char *app_name, const char *app_args, const char *moh_class)
+{
+	struct dtmf_hook_run_app *app_data;
+	size_t len_name = strlen(app_name) + 1;
+	size_t len_args = ast_strlen_zero(app_args) ? 0 : strlen(app_args) + 1;
+	size_t len_moh = ast_strlen_zero(moh_class) ? 0 : strlen(moh_class) + 1;
+	size_t len_data = sizeof(*app_data) + len_name + len_args + len_moh;
+
+	/* Fill in application run hook data. */
+	app_data = ast_malloc(len_data);
+	if (!app_data) {
+		return -1;
+	}
+	app_data->flags = flags;
+	app_data->app_args_offset = len_args ? len_name : 0;
+	app_data->moh_offset = len_moh ? len_name + len_args : 0;
+	strcpy(app_data->app_name, app_name);/* Safe */
+	if (len_args) {
+		strcpy(&app_data->app_name[app_data->app_args_offset], app_args);/* Safe */
+	}
+	if (len_moh) {
+		strcpy(&app_data->app_name[app_data->moh_offset], moh_class);/* Safe */
+	}
+
+	return ast_bridge_dtmf_hook(features, dtmf, app_dtmf_feature_hook,
+		app_data, ast_free_ptr, 1);
+}
+
+/*!
+ * \internal
+ * \brief Setup bridge dynamic features.
+ * \since 12.0.0
+ *
+ * \param features Bridge features to setup.
+ * \param chan Get features from this channel.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int setup_bridge_features_dynamic(struct ast_bridge_features *features, struct ast_channel *chan)
+{
+	const char *feat;
+	char *dynamic_features = NULL;
+	char *tok;
+	int res;
+
+	ast_channel_lock(chan);
+	feat = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES");
+	if (!ast_strlen_zero(feat)) {
+		dynamic_features = ast_strdupa(feat);
+	}
+	ast_channel_unlock(chan);
+	if (!dynamic_features) {
+		return 0;
+	}
+
+/* BUGBUG need to pass to add_dynamic_dtmf_hook the applicationmap name (feature->sname) so the DYNAMIC_FEATURENAME can be set on the channel when it is run. */
+	res = 0;
+	while ((tok = strsep(&dynamic_features, "#"))) {
+		struct feature_group *fg;
+		struct ast_call_feature *feature;
+
+		AST_RWLIST_RDLOCK(&feature_groups);
+		fg = find_group(tok);
+		if (fg) {
+			struct feature_group_exten *fge;
+
+			AST_LIST_TRAVERSE(&fg->features, fge, entry) {
+				res |= add_dynamic_dtmf_hook(features, fge->feature->flags, fge->exten,
+					fge->feature->app, fge->feature->app_args, fge->feature->moh_class);
+			}
+		}
+		AST_RWLIST_UNLOCK(&feature_groups);
+
+		ast_rdlock_call_features();
+		feature = find_dynamic_feature(tok);
+		if (feature) {
+			res |= add_dynamic_dtmf_hook(features, feature->flags, feature->exten,
+				feature->app, feature->app_args, feature->moh_class);
+		}
+		ast_unlock_call_features();
+	}
+	return res;
+}
+
+/* BUGBUG struct ast_call_feature needs to be made an ao2 object so the basic bridge class can own the code setting up it's DTMF hooks. */
+/* BUGBUG this really should be made a private function of bridging_basic.c after struct ast_call_feature is made an ao2 object. */
+int ast_bridge_channel_setup_features(struct ast_bridge_channel *bridge_channel)
+{
+	int res = 0;
+
+	/* Always pass through any DTMF digits. */
+	bridge_channel->features->dtmf_passthrough = 1;
+
+	res |= setup_bridge_features_builtin(bridge_channel->features, bridge_channel->chan);
+	res |= setup_bridge_features_dynamic(bridge_channel->features, bridge_channel->chan);
+
+	return res;
+}
+
+static void bridge_config_set_limits_warning_values(struct ast_bridge_config *config, struct ast_bridge_features_limits *limits)
+{
+	if (config->end_sound) {
+		ast_string_field_set(limits, duration_sound, config->end_sound);
+	}
+
+	if (config->warning_sound) {
+		ast_string_field_set(limits, warning_sound, config->warning_sound);
+	}
+
+	if (config->start_sound) {
+		ast_string_field_set(limits, connect_sound, config->start_sound);
+	}
+
+	limits->frequency = config->warning_freq;
+	limits->warning = config->play_warning;
+}
+
+/*!
+ * \internal brief Setup limit hook structures on calls that need limits
+ *
+ * \param config ast_bridge_config which provides the limit data
+ * \param caller_limits pointer to an ast_bridge_features_limits struct which will store the caller side limits
+ * \param callee_limits pointer to an ast_bridge_features_limits struct which will store the callee side limits
+ */
+static void bridge_config_set_limits(struct ast_bridge_config *config, struct ast_bridge_features_limits *caller_limits, struct ast_bridge_features_limits *callee_limits)
+{
+	if (ast_test_flag(&config->features_caller, AST_FEATURE_PLAY_WARNING)) {
+		bridge_config_set_limits_warning_values(config, caller_limits);
+	}
+
+	if (ast_test_flag(&config->features_callee, AST_FEATURE_PLAY_WARNING)) {
+		bridge_config_set_limits_warning_values(config, callee_limits);
+	}
+
+	caller_limits->duration = config->timelimit;
+	callee_limits->duration = config->timelimit;
+}
+
 /*!
  * \internal
  * \brief Check if Monitor needs to be started on a channel.
@@ -4374,6 +4455,24 @@ static void bridge_check_monitor(struct ast_channel *chan, struct ast_channel *p
 	}
 }
 
+/*!
+ * \internal
+ * \brief Send the peer channel on its way on bridge start failure.
+ * \since 12.0.0
+ *
+ * \param chan Chan to put into autoservice.
+ * \param peer Chan to send to after bridge goto or run hangup handlers and hangup.
+ *
+ * \return Nothing
+ */
+static void bridge_failed_peer_goto(struct ast_channel *chan, struct ast_channel *peer)
+{
+	if (ast_after_bridge_goto_setup(peer)
+		|| ast_pbx_start(peer)) {
+		ast_autoservice_chan_hangup_peer(chan, peer);
+	}
+}
+
 /*!
  * \brief bridge the call and set CDR
  *
@@ -4388,33 +4487,16 @@ static void bridge_check_monitor(struct ast_channel *chan, struct ast_channel *p
  */
 int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config)
 {
-	/* Copy voice back and forth between the two channels.  Give the peer
-	   the ability to transfer calls with '#<extension' syntax. */
-	struct ast_frame *f;
-	struct ast_channel *who;
-	char chan_featurecode[FEATURE_MAX_LEN + 1]="";
-	char peer_featurecode[FEATURE_MAX_LEN + 1]="";
-	char orig_channame[AST_CHANNEL_NAME];
-	char orig_peername[AST_CHANNEL_NAME];
 	int res;
-	int diff;
-	int hasfeatures=0;
-	int hadfeatures=0;
-	int sendingdtmfdigit = 0;
-	int we_disabled_peer_cdr = 0;
-	struct ast_option_header *aoh;
-	struct ast_cdr *bridge_cdr = NULL;
-	struct ast_cdr *chan_cdr = ast_channel_cdr(chan); /* the proper chan cdr, if there are forked cdrs */
-	struct ast_cdr *peer_cdr = ast_channel_cdr(peer); /* the proper chan cdr, if there are forked cdrs */
-	struct ast_cdr *new_chan_cdr = NULL; /* the proper chan cdr, if there are forked cdrs */
-	struct ast_cdr *new_peer_cdr = NULL; /* the proper chan cdr, if there are forked cdrs */
-	struct ast_silence_generator *silgen = NULL;
-	/*! TRUE if h-exten or hangup handlers run. */
-	int hangup_run = 0;
+	struct ast_bridge *bridge;
+	struct ast_bridge_features chan_features;
+	struct ast_bridge_features *peer_features;
 
+/* BUGBUG these channel vars may need to be made dynamic so they update when transfers happen. */
 	pbx_builtin_setvar_helper(chan, "BRIDGEPEER", ast_channel_name(peer));
 	pbx_builtin_setvar_helper(peer, "BRIDGEPEER", ast_channel_name(chan));
 
+/* BUGBUG revisit how BLINDTRANSFER operates with the new bridging model. */
 	/* Clear any BLINDTRANSFER since the transfer has completed. */
 	pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", NULL);
 	pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", NULL);
@@ -4422,10 +4504,15 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a
 	set_bridge_features_on_config(config, pbx_builtin_getvar_helper(chan, "BRIDGE_FEATURES"));
 	add_features_datastores(chan, peer, config);
 
-	/* This is an interesting case.  One example is if a ringing channel gets redirected to
-	 * an extension that picks up a parked call.  This will make sure that the call taken
-	 * out of parking gets told that the channel it just got bridged to is still ringing. */
-	if (ast_channel_state(chan) == AST_STATE_RINGING && ast_channel_visible_indication(peer) != AST_CONTROL_RINGING) {
+	/*
+	 * This is an interesting case.  One example is if a ringing
+	 * channel gets redirected to an extension that picks up a
+	 * parked call.  This will make sure that the call taken out of
+	 * parking gets told that the channel it just got bridged to is
+	 * still ringing.
+	 */
+	if (ast_channel_state(chan) == AST_STATE_RINGING
+		&& ast_channel_visible_indication(peer) != AST_CONTROL_RINGING) {
 		ast_indicate(peer, AST_CONTROL_RINGING);
 	}
 
@@ -4436,6 +4523,7 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a
 	/* Answer if need be */
 	if (ast_channel_state(chan) != AST_STATE_UP) {
 		if (ast_raw_answer(chan, 1)) {
+			bridge_failed_peer_goto(chan, peer);
 			return -1;
 		}
 	}
@@ -4446,571 +4534,123 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a
 	ast_channel_log("Pre-bridge PEER Channel info", peer);
 #endif
 	/* two channels are being marked as linked here */
-	ast_channel_set_linkgroup(chan,peer);
+	ast_channel_set_linkgroup(chan, peer);
 
-	/* copy the userfield from the B-leg to A-leg if applicable */
-	if (ast_channel_cdr(chan) && ast_channel_cdr(peer) && !ast_strlen_zero(ast_channel_cdr(peer)->userfield)) {
-		char tmp[256];
-
-		ast_channel_lock(chan);
-		if (!ast_strlen_zero(ast_channel_cdr(chan)->userfield)) {
-			snprintf(tmp, sizeof(tmp), "%s;%s", ast_channel_cdr(chan)->userfield, ast_channel_cdr(peer)->userfield);
-			ast_cdr_appenduserfield(chan, tmp);
-		} else {
-			ast_cdr_setuserfield(chan, ast_channel_cdr(peer)->userfield);
-		}
-		ast_channel_unlock(chan);
-		/* Don't delete the CDR; just disable it. */
-		ast_set_flag(ast_channel_cdr(peer), AST_CDR_FLAG_POST_DISABLED);
-		we_disabled_peer_cdr = 1;
-	}
-	ast_copy_string(orig_channame,ast_channel_name(chan),sizeof(orig_channame));
-	ast_copy_string(orig_peername,ast_channel_name(peer),sizeof(orig_peername));
-
-	if (!chan_cdr || (chan_cdr && !ast_test_flag(chan_cdr, AST_CDR_FLAG_POST_DISABLED))) {
-		ast_channel_lock_both(chan, peer);
-		if (chan_cdr) {
-			ast_set_flag(chan_cdr, AST_CDR_FLAG_MAIN);
-			ast_cdr_update(chan);
-			bridge_cdr = ast_cdr_dup_unique_swap(chan_cdr);
-			/* rip any forked CDR's off of the chan_cdr and attach
-			 * them to the bridge_cdr instead */
-			bridge_cdr->next = chan_cdr->next;
-			chan_cdr->next = NULL;
-			ast_copy_string(bridge_cdr->lastapp, S_OR(ast_channel_appl(chan), ""), sizeof(bridge_cdr->lastapp));
-			ast_copy_string(bridge_cdr->lastdata, S_OR(ast_channel_data(chan), ""), sizeof(bridge_cdr->lastdata));
-			if (peer_cdr && !ast_strlen_zero(peer_cdr->userfield)) {
-				ast_copy_string(bridge_cdr->userfield, peer_cdr->userfield, sizeof(bridge_cdr->userfield));
-			}
-			ast_cdr_setaccount(peer, ast_channel_accountcode(chan));
-		} else {
-			/* better yet, in a xfer situation, find out why the chan cdr got zapped (pun unintentional) */
-			bridge_cdr = ast_cdr_alloc(); /* this should be really, really rare/impossible? */
-			ast_copy_string(bridge_cdr->channel, ast_channel_name(chan), sizeof(bridge_cdr->channel));
-			ast_copy_string(bridge_cdr->dstchannel, ast_channel_name(peer), sizeof(bridge_cdr->dstchannel));
-			ast_copy_string(bridge_cdr->uniqueid, ast_channel_uniqueid(chan), sizeof(bridge_cdr->uniqueid));
-			ast_copy_string(bridge_cdr->lastapp, S_OR(ast_channel_appl(chan), ""), sizeof(bridge_cdr->lastapp));
-			ast_copy_string(bridge_cdr->lastdata, S_OR(ast_channel_data(chan), ""), sizeof(bridge_cdr->lastdata));
-			ast_cdr_setcid(bridge_cdr, chan);
-			bridge_cdr->disposition = (ast_channel_state(chan) == AST_STATE_UP) ?  AST_CDR_ANSWERED : AST_CDR_NULL;
-			bridge_cdr->amaflags = ast_channel_amaflags(chan) ? ast_channel_amaflags(chan) :  ast_default_amaflags;
-			ast_copy_string(bridge_cdr->accountcode, ast_channel_accountcode(chan), sizeof(bridge_cdr->accountcode));
-			/* Destination information */
-			ast_copy_string(bridge_cdr->dst, ast_channel_exten(chan), sizeof(bridge_cdr->dst));
-			ast_copy_string(bridge_cdr->dcontext, ast_channel_context(chan), sizeof(bridge_cdr->dcontext));
-			if (peer_cdr) {
-				bridge_cdr->start = peer_cdr->start;
-				ast_copy_string(bridge_cdr->userfield, peer_cdr->userfield, sizeof(bridge_cdr->userfield));
-			} else {
-				ast_cdr_start(bridge_cdr);
-			}
-		}
-		ast_channel_unlock(chan);
-		ast_channel_unlock(peer);
+	/*
+	 * If we are bridging a call, stop worrying about forwarding
+	 * loops.  We presume that if a call is being bridged, that the
+	 * humans in charge know what they're doing.  If they don't,
+	 * well, what can we do about that?
+	 */
+	clear_dialed_interfaces(chan);
+	clear_dialed_interfaces(peer);
 
-		ast_debug(4, "bridge answer set, chan answer set\n");
-		/* peer_cdr->answer will be set when a macro runs on the peer;
-		   in that case, the bridge answer will be delayed while the
-		   macro plays on the peer channel. The peer answered the call
-		   before the macro started playing. To the phone system,
-		   this is billable time for the call, even tho the caller
-		   hears nothing but ringing while the macro does its thing. */
-
-		/* Another case where the peer cdr's time will be set, is when
-		   A self-parks by pickup up phone and dialing 700, then B
-		   picks up A by dialing its parking slot; there may be more
-		   practical paths that get the same result, tho... in which
-		   case you get the previous answer time from the Park... which
-		   is before the bridge's start time, so I added in the
-		   tvcmp check to the if below */
-
-		if (peer_cdr && !ast_tvzero(peer_cdr->answer) && ast_tvcmp(peer_cdr->answer, bridge_cdr->start) >= 0) {
-			ast_cdr_setanswer(bridge_cdr, peer_cdr->answer);
-			ast_cdr_setdisposition(bridge_cdr, peer_cdr->disposition);
-			if (chan_cdr) {
-				ast_cdr_setanswer(chan_cdr, peer_cdr->answer);
-				ast_cdr_setdisposition(chan_cdr, peer_cdr->disposition);
-			}
-		} else {
-			ast_cdr_answer(bridge_cdr);
-			if (chan_cdr) {
-				ast_cdr_answer(chan_cdr); /* for the sake of cli status checks */
-			}
-		}
-		if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT) && (chan_cdr || peer_cdr)) {
-			if (chan_cdr) {
-				ast_set_flag(chan_cdr, AST_CDR_FLAG_BRIDGED);
-			}
-			if (peer_cdr) {
-				ast_set_flag(peer_cdr, AST_CDR_FLAG_BRIDGED);
-			}
-		}
-		/* the DIALED flag may be set if a dialed channel is transfered
-		 * and then bridged to another channel.  In order for the
-		 * bridge CDR to be written, the DIALED flag must not be
-		 * present. */
-		ast_clear_flag(bridge_cdr, AST_CDR_FLAG_DIALED);
+	res = 0;
+	ast_channel_lock(chan);
+	res |= ast_bridge_features_ds_set(chan, &config->features_caller);
+	ast_channel_unlock(chan);
+	ast_channel_lock(peer);
+	res |= ast_bridge_features_ds_set(peer, &config->features_callee);
+	ast_channel_unlock(peer);
+	if (res) {
+		bridge_failed_peer_goto(chan, peer);
+		return -1;
 	}
-	ast_cel_report_event(chan, AST_CEL_BRIDGE_START, NULL, NULL, peer);
-
-	/* If we are bridging a call, stop worrying about forwarding loops. We presume that if
-	 * a call is being bridged, that the humans in charge know what they're doing. If they
-	 * don't, well, what can we do about that? */
-	clear_dialed_interfaces(chan);
-	clear_dialed_interfaces(peer);
 
-	for (;;) {
-		struct ast_channel *other;	/* used later */
+	/* Setup features. */
+	res = ast_bridge_features_init(&chan_features);
+	peer_features = ast_bridge_features_new();
+	if (res || !peer_features) {
+		ast_bridge_features_destroy(peer_features);
+		ast_bridge_features_cleanup(&chan_features);
+		bridge_failed_peer_goto(chan, peer);
+		return -1;
+	}
 
-		res = ast_channel_bridge(chan, peer, config, &f, &who);
+	if (config->timelimit) {
+		struct ast_bridge_features_limits call_duration_limits_chan;
+		struct ast_bridge_features_limits call_duration_limits_peer;
+		int abandon_call = 0; /* TRUE if set limits fails so we can abandon the call. */
 
-		if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE)
-			|| ast_test_flag(ast_channel_flags(peer), AST_FLAG_ZOMBIE)) {
-			/* Zombies are present time to leave! */
-			res = -1;
-			if (f) {
-				ast_frfree(f);
-			}
-			goto before_you_go;
-		}
-
-		/* When frame is not set, we are probably involved in a situation
-		   where we've timed out.
-		   When frame is set, we'll come this code twice; once for DTMF_BEGIN
-		   and also for DTMF_END. If we flow into the following 'if' for both, then
-		   our wait times are cut in half, as both will subtract from the
-		   feature_timer. Not good!
-		*/
-		if (config->feature_timer && (!f || f->frametype == AST_FRAME_DTMF_END)) {
-			/* Update feature timer for next pass */
-			diff = ast_tvdiff_ms(ast_tvnow(), config->feature_start_time);
-			if (res == AST_BRIDGE_RETRY) {
-				/* The feature fully timed out but has not been updated. Skip
-				 * the potential round error from the diff calculation and
-				 * explicitly set to expired. */
-				config->feature_timer = -1;
-			} else {
-				config->feature_timer -= diff;
-			}
+		if (ast_bridge_features_limits_construct(&call_duration_limits_chan)) {
+			ast_log(LOG_ERROR, "Could not construct caller duration limits. Bridge canceled.\n");
 
-			if (hasfeatures) {
-				if (config->feature_timer <= 0) {
-					/* Not *really* out of time, just out of time for
-					   digits to come in for features. */
-					ast_debug(1, "Timed out for feature!\n");
-					if (!ast_strlen_zero(peer_featurecode)) {
-						ast_dtmf_stream(chan, peer, peer_featurecode, 0, f ? f->len : 0);
-						memset(peer_featurecode, 0, sizeof(peer_featurecode));
-					}
-					if (!ast_strlen_zero(chan_featurecode)) {
-						ast_dtmf_stream(peer, chan, chan_featurecode, 0, f ? f->len : 0);
-						memset(chan_featurecode, 0, sizeof(chan_featurecode));
-					}
-					if (f)
-						ast_frfree(f);
-					hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
-					if (!hasfeatures) {
-						/* No more digits expected - reset the timer */
-						config->feature_timer = 0;
-					}
-					hadfeatures = hasfeatures;
-					/* Continue as we were */
-					continue;
-				} else if (!f) {
-					/* The bridge returned without a frame and there is a feature in progress.
-					 * However, we don't think the feature has quite yet timed out, so just
-					 * go back into the bridge. */
-					continue;
-				}
-			} else {
-				if (config->feature_timer <=0) {
-					/* We ran out of time */
-					config->feature_timer = 0;
-					who = chan;
-					if (f)
-						ast_frfree(f);
-					f = NULL;
-					res = 0;
-				}
-			}
-		}
-		if (res < 0) {
-			if (!ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE) && !ast_test_flag(ast_channel_flags(peer), AST_FLAG_ZOMBIE) && !ast_check_hangup(chan) && !ast_check_hangup(peer)) {
-				ast_log(LOG_WARNING, "Bridge failed on channels %s and %s\n", ast_channel_name(chan), ast_channel_name(peer));
-			}
-			goto before_you_go;
+			ast_bridge_features_destroy(peer_features);
+			ast_bridge_features_cleanup(&chan_features);
+			bridge_failed_peer_goto(chan, peer);
+			return -1;
 		}
 
-		if (!f || (f->frametype == AST_FRAME_CONTROL &&
-				(f->subclass.integer == AST_CONTROL_HANGUP || f->subclass.integer == AST_CONTROL_BUSY ||
-					f->subclass.integer == AST_CONTROL_CONGESTION))) {
-			res = -1;
-			break;
-		}
-		/* many things should be sent to the 'other' channel */
-		other = (who == chan) ? peer : chan;
-		if (f->frametype == AST_FRAME_CONTROL) {
-			switch (f->subclass.integer) {
-			case AST_CONTROL_RINGING:
-			case AST_CONTROL_FLASH:
-			case AST_CONTROL_MCID:
-			case -1:
-				ast_indicate(other, f->subclass.integer);
-				break;
-			case AST_CONTROL_CONNECTED_LINE:
-				if (ast_channel_connected_line_sub(who, other, f, 1) &&
-					ast_channel_connected_line_macro(who, other, f, who != chan, 1)) {
-					ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
-				}
-				break;
-			case AST_CONTROL_REDIRECTING:
-				if (ast_channel_redirecting_sub(who, other, f, 1) &&
-					ast_channel_redirecting_macro(who, other, f, who != chan, 1)) {
-					ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
-				}
-				break;
-			case AST_CONTROL_PVT_CAUSE_CODE:
-			case AST_CONTROL_AOC:
-			case AST_CONTROL_HOLD:
-			case AST_CONTROL_UNHOLD:
-				ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
-				break;
-			case AST_CONTROL_OPTION:
-				aoh = f->data.ptr;
-				/* Forward option Requests, but only ones we know are safe
-				 * These are ONLY sent by chan_iax2 and I'm not convinced that
-				 * they are useful. I haven't deleted them entirely because I
-				 * just am not sure of the ramifications of removing them. */
-				if (aoh && aoh->flag == AST_OPTION_FLAG_REQUEST) {
-					switch (ntohs(aoh->option)) {
-					case AST_OPTION_TONE_VERIFY:
-					case AST_OPTION_TDD:
-					case AST_OPTION_RELAXDTMF:
-					case AST_OPTION_AUDIO_MODE:
-					case AST_OPTION_DIGIT_DETECT:
-					case AST_OPTION_FAX_DETECT:
-						ast_channel_setoption(other, ntohs(aoh->option), aoh->data,
-							f->datalen - sizeof(struct ast_option_header), 0);
-					}
-				}
-				break;
-			}
-		} else if (f->frametype == AST_FRAME_DTMF_BEGIN) {
-			struct ast_flags *cfg;
-			char dtmfcode[2] = { f->subclass.integer, };
-			size_t featurelen;
-
-			if (who == chan) {
-				featurelen = strlen(chan_featurecode);
-				cfg = &(config->features_caller);
-			} else {
-				featurelen = strlen(peer_featurecode);
-				cfg = &(config->features_callee);
-			}
-			/* Take a peek if this (possibly) matches a feature. If not, just pass this
-			 * DTMF along untouched. If this is not the first digit of a multi-digit code
-			 * then we need to fall through and stream the characters if it matches */
-			if (featurelen == 0
-				&& feature_check(chan, cfg, &dtmfcode[0]) == AST_FEATURE_RETURN_PASSDIGITS) {
-				if (option_debug > 3) {
-					ast_log(LOG_DEBUG, "Passing DTMF through, since it is not a feature code\n");
-				}
-				ast_write(other, f);
-				sendingdtmfdigit = 1;
-			} else {
-				/* If ast_opt_transmit_silence is set, then we need to make sure we are
-				 * transmitting something while we hold on to the DTMF waiting for a
-				 * feature. */
-				if (!silgen && ast_opt_transmit_silence) {
-					silgen = ast_channel_start_silence_generator(other);
-				}
-				if (option_debug > 3) {
-					ast_log(LOG_DEBUG, "Not passing DTMF through, since it may be a feature code\n");
-				}
-			}
-		} else if (f->frametype == AST_FRAME_DTMF_END) {
-			char *featurecode;
-			int sense;
-			unsigned int dtmfduration = f->len;
-
-			hadfeatures = hasfeatures;
-			/* This cannot overrun because the longest feature is one shorter than our buffer */
-			if (who == chan) {
-				sense = FEATURE_SENSE_CHAN;
-				featurecode = chan_featurecode;
-			} else  {
-				sense = FEATURE_SENSE_PEER;
-				featurecode = peer_featurecode;
-			}
+		if (ast_bridge_features_limits_construct(&call_duration_limits_peer)) {
+			ast_log(LOG_ERROR, "Could not construct callee duration limits. Bridge canceled.\n");
+			ast_bridge_features_limits_destroy(&call_duration_limits_chan);
 
-			if (sendingdtmfdigit == 1) {
-				/* We let the BEGIN go through happily, so let's not bother with the END,
-				 * since we already know it's not something we bother with */
-				ast_write(other, f);
-				sendingdtmfdigit = 0;
-			} else {
-				/*! append the event to featurecode. we rely on the string being zero-filled, and
-				 * not overflowing it.
-				 * \todo XXX how do we guarantee the latter ?
-				 */
-				featurecode[strlen(featurecode)] = f->subclass.integer;
-				/* Get rid of the frame before we start doing "stuff" with the channels */
-				ast_frfree(f);
-				f = NULL;
-				if (silgen) {
-					ast_channel_stop_silence_generator(other, silgen);
-					silgen = NULL;
-				}
-				config->feature_timer = 0;
-				res = feature_interpret(chan, peer, config, featurecode, sense);
-				switch(res) {
-				case AST_FEATURE_RETURN_PASSDIGITS:
-					ast_dtmf_stream(other, who, featurecode, 0, dtmfduration);
-					/* Fall through */
-				case AST_FEATURE_RETURN_SUCCESS:
-					memset(featurecode, 0, sizeof(chan_featurecode));
-					break;
-				}
-				if (res >= AST_FEATURE_RETURN_PASSDIGITS) {
-					res = 0;
-				} else {
-					break;
-				}
-				hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
-				if (hadfeatures && !hasfeatures) {
-					/* Feature completed or timed out */
-					config->feature_timer = 0;
-				} else if (hasfeatures) {
-					if (config->timelimit) {
-						/* No warning next time - we are waiting for feature code */
-						ast_set_flag(config, AST_FEATURE_WARNING_ACTIVE);
-					}
-					config->feature_start_time = ast_tvnow();
-					config->feature_timer = featuredigittimeout;
-					ast_debug(1, "Set feature timer to %ld ms\n", config->feature_timer);
-				}
-			}
+			ast_bridge_features_destroy(peer_features);
+			ast_bridge_features_cleanup(&chan_features);
+			bridge_failed_peer_goto(chan, peer);
+			return -1;
 		}
-		if (f)
-			ast_frfree(f);
-	}
-	ast_cel_report_event(chan, AST_CEL_BRIDGE_END, NULL, NULL, peer);
 
-before_you_go:
-	if (ast_channel_sending_dtmf_digit(chan)) {
-		ast_bridge_end_dtmf(chan, ast_channel_sending_dtmf_digit(chan),
-			ast_channel_sending_dtmf_tv(chan), "bridge end");
-	}
-	if (ast_channel_sending_dtmf_digit(peer)) {
-		ast_bridge_end_dtmf(peer, ast_channel_sending_dtmf_digit(peer),
-			ast_channel_sending_dtmf_tv(peer), "bridge end");
-	}
+		bridge_config_set_limits(config, &call_duration_limits_chan, &call_duration_limits_peer);
 
-	/* Just in case something weird happened and we didn't clean up the silence generator... */
-	if (silgen) {
-		ast_channel_stop_silence_generator(who == chan ? peer : chan, silgen);
-		silgen = NULL;
-	}
+		if (ast_bridge_features_set_limits(&chan_features, &call_duration_limits_chan, 0)) {
+			abandon_call = 1;
+		}
+		if (ast_bridge_features_set_limits(peer_features, &call_duration_limits_peer, 0)) {
+			abandon_call = 1;
+		}
 
-	/* Wait for any dual redirect to complete. */
-	while (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT)) {
-		sched_yield();
-	}
+		/* At this point we are done with the limits structs since they have been copied to the individual feature sets. */
+		ast_bridge_features_limits_destroy(&call_duration_limits_chan);
+		ast_bridge_features_limits_destroy(&call_duration_limits_peer);
 
-	if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT)) {
-		ast_clear_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT); /* its job is done */
-		if (bridge_cdr) {
-			ast_cdr_discard(bridge_cdr);
-			/* QUESTION: should we copy bridge_cdr fields to the peer before we throw it away? */
+		if (abandon_call) {
+			ast_log(LOG_ERROR, "Could not set duration limits on one or more sides of the call. Bridge canceled.\n");
+			ast_bridge_features_destroy(peer_features);
+			ast_bridge_features_cleanup(&chan_features);
+			bridge_failed_peer_goto(chan, peer);
+			return -1;
 		}
-		return res; /* if we shouldn't do the h-exten, we shouldn't do the bridge cdr, either! */
 	}
 
-	if (config->end_bridge_callback) {
-		config->end_bridge_callback(config->end_bridge_callback_data);
+	/* Create bridge */
+	bridge = ast_bridge_basic_new();
+	if (!bridge) {
+		ast_bridge_features_destroy(peer_features);
+		ast_bridge_features_cleanup(&chan_features);
+		bridge_failed_peer_goto(chan, peer);
+		return -1;
 	}
 
-	/* run the hangup exten on the chan object IFF it was NOT involved in a parking situation
-	 * if it were, then chan belongs to a different thread now, and might have been hung up long
-	 * ago.
-	 */
-	if (!(ast_channel_softhangup_internal_flag(chan) & (AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE))
-			&& !ast_test_flag(&config->features_caller, AST_FEATURE_NO_H_EXTEN)) {
-		struct ast_cdr *swapper = NULL;
-		char savelastapp[AST_MAX_EXTENSION];
-		char savelastdata[AST_MAX_EXTENSION];
-		char save_context[AST_MAX_CONTEXT];
-		char save_exten[AST_MAX_EXTENSION];
-		int  save_prio;
-
-		ast_channel_lock(chan);
-		if (bridge_cdr) {
-			/*
-			 * Swap the bridge_cdr and the chan cdr for a moment, and let
-			 * the hangup dialplan code operate on it.
-			 */
-			swapper = ast_channel_cdr(chan);
-			ast_channel_cdr_set(chan, bridge_cdr);
-
-			/* protect the lastapp/lastdata against the effects of the hangup/dialplan code */
-			ast_copy_string(savelastapp, bridge_cdr->lastapp, sizeof(bridge_cdr->lastapp));
-			ast_copy_string(savelastdata, bridge_cdr->lastdata, sizeof(bridge_cdr->lastdata));
-		}
-		ast_copy_string(save_context, ast_channel_context(chan), sizeof(save_context));
-		ast_copy_string(save_exten, ast_channel_exten(chan), sizeof(save_exten));
-		save_prio = ast_channel_priority(chan);
-		ast_channel_unlock(chan);
-
-		ast_autoservice_start(peer);
-		if (ast_exists_extension(chan, ast_channel_context(chan), "h", 1,
-			S_COR(ast_channel_caller(chan)->id.number.valid,
-				ast_channel_caller(chan)->id.number.str, NULL))) {
-			ast_pbx_h_exten_run(chan, ast_channel_context(chan));
-			hangup_run = 1;
-		} else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
-			&& ast_exists_extension(chan, ast_channel_macrocontext(chan), "h", 1,
-				S_COR(ast_channel_caller(chan)->id.number.valid,
-					ast_channel_caller(chan)->id.number.str, NULL))) {
-			ast_pbx_h_exten_run(chan, ast_channel_macrocontext(chan));
-			hangup_run = 1;
-		}
-		if (ast_pbx_hangup_handler_run(chan)) {
-			/* Indicate hangup handlers were run. */
-			hangup_run = 1;
-		}
-		ast_autoservice_stop(peer);
-
-		ast_channel_lock(chan);
-
-		/* swap it back */
-		ast_channel_context_set(chan, save_context);
-		ast_channel_exten_set(chan, save_exten);
-		ast_channel_priority_set(chan, save_prio);
-		if (bridge_cdr) {
-			if (ast_channel_cdr(chan) == bridge_cdr) {
-				ast_channel_cdr_set(chan, swapper);
-
-				/* Restore the lastapp/lastdata */
-				ast_copy_string(bridge_cdr->lastapp, savelastapp, sizeof(bridge_cdr->lastapp));
-				ast_copy_string(bridge_cdr->lastdata, savelastdata, sizeof(bridge_cdr->lastdata));
-			} else {
-				bridge_cdr = NULL;
-			}
-		}
-		ast_channel_unlock(chan);
+	/* Put peer into the bridge */
+	if (ast_bridge_impart(bridge, peer, NULL, peer_features, 1)) {
+		ast_bridge_destroy(bridge);
+		ast_bridge_features_cleanup(&chan_features);
+		bridge_failed_peer_goto(chan, peer);
+		return -1;
 	}
 
-	/* obey the NoCDR() wishes. -- move the DISABLED flag to the bridge CDR if it was set on the channel during the bridge... */
-	new_chan_cdr = pick_unlocked_cdr(ast_channel_cdr(chan)); /* the proper chan cdr, if there are forked cdrs */
+	/* Join bridge */
+	ast_bridge_join(bridge, chan, NULL, &chan_features, NULL, 1);
 
 	/*
-	 * If the channel CDR has been modified during the call, record
-	 * the changes in the bridge cdr, BUT, if hangup_run, the CDR
-	 * got swapped so don't overwrite what was done in the
-	 * h-extension or hangup handlers.  What a mess.  This is why
-	 * you never touch CDR code.
+	 * If the bridge was broken for a hangup that isn't real, then
+	 * don't run the h extension, because the channel isn't really
+	 * hung up.  This should really only happen with
+	 * AST_SOFTHANGUP_ASYNCGOTO.
 	 */
-	if (new_chan_cdr && bridge_cdr && !hangup_run) {
-		ast_cdr_copy_vars(bridge_cdr, new_chan_cdr);
-		ast_copy_string(bridge_cdr->userfield, new_chan_cdr->userfield, sizeof(bridge_cdr->userfield));
-		bridge_cdr->amaflags = new_chan_cdr->amaflags;
-		ast_copy_string(bridge_cdr->accountcode, new_chan_cdr->accountcode, sizeof(bridge_cdr->accountcode));
-		if (ast_test_flag(new_chan_cdr, AST_CDR_FLAG_POST_DISABLED)) {
-			ast_set_flag(bridge_cdr, AST_CDR_FLAG_POST_DISABLED);
-		}
-	}
-
-	/* we can post the bridge CDR at this point */
-	if (bridge_cdr) {
-		ast_cdr_end(bridge_cdr);
-		ast_cdr_detach(bridge_cdr);
-	}
-
-	/* do a specialized reset on the beginning channel
-	   CDR's, if they still exist, so as not to mess up
-	   issues in future bridges;
-
-	   Here are the rules of the game:
-	   1. The chan and peer channel pointers will not change
-	      during the life of the bridge.
-	   2. But, in transfers, the channel names will change.
-	      between the time the bridge is started, and the
-	      time the channel ends.
-	      Usually, when a channel changes names, it will
-	      also change CDR pointers.
-	   3. Usually, only one of the two channels (chan or peer)
-	      will change names.
-	   4. Usually, if a channel changes names during a bridge,
-	      it is because of a transfer. Usually, in these situations,
-	      it is normal to see 2 bridges running simultaneously, and
-	      it is not unusual to see the two channels that change
-	      swapped between bridges.
-	   5. After a bridge occurs, we have 2 or 3 channels' CDRs
-	      to attend to; if the chan or peer changed names,
-	      we have the before and after attached CDR's.
-	*/
-
-	if (new_chan_cdr) {
-		struct ast_channel *chan_ptr = NULL;
-
-		if (strcasecmp(orig_channame, ast_channel_name(chan)) != 0) {
-			/* old channel */
-			if ((chan_ptr = ast_channel_get_by_name(orig_channame))) {
-				ast_channel_lock(chan_ptr);
-				if (!ast_bridged_channel(chan_ptr)) {
-					struct ast_cdr *cur;
-					for (cur = ast_channel_cdr(chan_ptr); cur; cur = cur->next) {
-						if (cur == chan_cdr) {
-							break;
-						}
-					}
-					if (cur) {
-						ast_cdr_specialized_reset(chan_cdr, 0);
-					}
-				}
-				ast_channel_unlock(chan_ptr);
-				chan_ptr = ast_channel_unref(chan_ptr);
-			}
-			/* new channel */
-			ast_cdr_specialized_reset(new_chan_cdr, 0);
-		} else {
-			ast_cdr_specialized_reset(ast_channel_cdr(chan), 0); /* nothing changed, reset the chan cdr  */
-		}
-	}
-
-	{
-		struct ast_channel *chan_ptr = NULL;
-		new_peer_cdr = pick_unlocked_cdr(ast_channel_cdr(peer)); /* the proper chan cdr, if there are forked cdrs */
-		if (new_chan_cdr && ast_test_flag(new_chan_cdr, AST_CDR_FLAG_POST_DISABLED) && new_peer_cdr && !ast_test_flag(new_peer_cdr, AST_CDR_FLAG_POST_DISABLED))
-			ast_set_flag(new_peer_cdr, AST_CDR_FLAG_POST_DISABLED); /* DISABLED is viral-- it will propagate across a bridge */
-		if (strcasecmp(orig_peername, ast_channel_name(peer)) != 0) {
-			/* old channel */
-			if ((chan_ptr = ast_channel_get_by_name(orig_peername))) {
-				ast_channel_lock(chan_ptr);
-				if (!ast_bridged_channel(chan_ptr)) {
-					struct ast_cdr *cur;
-					for (cur = ast_channel_cdr(chan_ptr); cur; cur = cur->next) {
-						if (cur == peer_cdr) {
-							break;
-						}
-					}
-					if (cur) {
-						ast_cdr_specialized_reset(peer_cdr, 0);
-					}
-				}
-				ast_channel_unlock(chan_ptr);
-				chan_ptr = ast_channel_unref(chan_ptr);
-			}
-			/* new channel */
-			if (new_peer_cdr) {
-				ast_cdr_specialized_reset(new_peer_cdr, 0);
-			}
-		} else {
-			if (we_disabled_peer_cdr) {
-				ast_clear_flag(ast_channel_cdr(peer), AST_CDR_FLAG_POST_DISABLED);
-			}
-			ast_cdr_specialized_reset(ast_channel_cdr(peer), 0); /* nothing changed, reset the peer cdr  */
-		}
+	res = -1;
+	ast_channel_lock(chan);
+	if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
+		res = 0;
+	}
+	ast_channel_unlock(chan);
+
+	ast_bridge_features_cleanup(&chan_features);
+
+/* BUGBUG this is used by Dial and FollowMe for CDR information.  By Queue for Queue stats like CDRs. */
+	if (res && config->end_bridge_callback) {
+		config->end_bridge_callback(config->end_bridge_callback_data);
 	}
 
 	return res;
@@ -5456,395 +5096,6 @@ AST_APP_OPTIONS(park_call_options, BEGIN_OPTIONS
 	AST_APP_OPTION('s', AST_PARK_OPT_SILENCE),
 END_OPTIONS );
 
-/*! \brief Park a call */
-static int park_call_exec(struct ast_channel *chan, const char *data)
-{
-	struct ast_park_call_args args = { 0, };
-	struct ast_flags flags = { 0 };
-	char orig_exten[AST_MAX_EXTENSION];
-	int orig_priority;
-	int res;
-	const char *pl_name;
-	char *parse;
-	struct park_app_args app_args;
-
-	/*
-	 * Cache the original channel name because we are going to
-	 * masquerade the channel.  Prefer the BLINDTRANSFER channel
-	 * name over this channel name.  BLINDTRANSFER could be set if
-	 * the parking access extension did not get detected and we are
-	 * executing the Park application from the dialplan.
-	 *
-	 * The orig_chan_name is used to return the call to the
-	 * originator on parking timeout.
-	 */
-	args.orig_chan_name = ast_strdupa(S_OR(
-		pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"), ast_channel_name(chan)));
-
-	/* Answer if call is not up */
-	if (ast_channel_state(chan) != AST_STATE_UP) {
-		if (ast_answer(chan)) {
-			return -1;
-		}
-
-		/* Sleep to allow VoIP streams to settle down */
-		if (ast_safe_sleep(chan, 1000)) {
-			return -1;
-		}
-	}
-
-	/* Process the dialplan application options. */
-	parse = ast_strdupa(data);
-	AST_STANDARD_APP_ARGS(app_args, parse);
-
-	if (!ast_strlen_zero(app_args.timeout)) {
-		if (sscanf(app_args.timeout, "%30d", &args.timeout) != 1) {
-			ast_log(LOG_WARNING, "Invalid timeout '%s' provided\n", app_args.timeout);
-			args.timeout = 0;
-		}
-	}
-	if (!ast_strlen_zero(app_args.return_con)) {
-		args.return_con = app_args.return_con;
-	}
-	if (!ast_strlen_zero(app_args.return_ext)) {
-		args.return_ext = app_args.return_ext;
-	}
-	if (!ast_strlen_zero(app_args.return_pri)) {
-		if (sscanf(app_args.return_pri, "%30d", &args.return_pri) != 1) {
-			ast_log(LOG_WARNING, "Invalid priority '%s' specified\n", app_args.return_pri);
-			args.return_pri = 0;
-		}
-	}
-
-	ast_app_parse_options(park_call_options, &flags, NULL, app_args.options);
-	args.flags = flags.flags;
-
-	/*
-	 * Setup the exten/priority to be s/1 since we don't know where
-	 * this call should return.
-	 */
-	ast_copy_string(orig_exten, ast_channel_exten(chan), sizeof(orig_exten));
-	orig_priority = ast_channel_priority(chan);
-	ast_channel_exten_set(chan, "s");
-	ast_channel_priority_set(chan, 1);
-
-	/* Park the call */
-	if (!ast_strlen_zero(app_args.pl_name)) {
-		pl_name = app_args.pl_name;
-	} else {
-		pl_name = findparkinglotname(chan);
-	}
-	if (ast_strlen_zero(pl_name)) {
-		/* Parking lot is not specified, so use the default parking lot. */
-		args.parkinglot = parkinglot_addref(default_parkinglot);
-	} else {
-		args.parkinglot = find_parkinglot(pl_name);
-		if (!args.parkinglot && parkeddynamic) {
-			args.parkinglot = create_dynamic_parkinglot(pl_name, chan);
-		}
-	}
-	if (args.parkinglot) {
-		res = masq_park_call(chan, chan, &args);
-		parkinglot_unref(args.parkinglot);
-	} else {
-		/* Parking failed because the parking lot does not exist. */
-		if (!ast_test_flag(&args, AST_PARK_OPT_SILENCE)) {
-			ast_stream_and_wait(chan, "pbx-parkingfailed", "");
-		}
-		res = -1;
-	}
-	if (res) {
-		/* Park failed, try to continue in the dialplan. */
-		ast_channel_exten_set(chan, orig_exten);
-		ast_channel_priority_set(chan, orig_priority);
-		res = 0;
-	} else {
-		/* Park succeeded. */
-		res = -1;
-	}
-
-	return res;
-}
-
-/*! \brief Pickup parked call */
-static int parked_call_exec(struct ast_channel *chan, const char *data)
-{
-	int res;
-	struct ast_channel *peer = NULL;
-	struct parkeduser *pu;
-	struct ast_context *con;
-	char *parse;
-	const char *pl_name;
-	int park = 0;
-	struct ast_bridge_config config;
-	struct ast_parkinglot *parkinglot;
-	AST_DECLARE_APP_ARGS(app_args,
-		AST_APP_ARG(pl_space);	/*!< Parking lot space to retrieve if present. */
-		AST_APP_ARG(pl_name);	/*!< Parking lot name to use if present. */
-		AST_APP_ARG(dummy);		/*!< Place to put any remaining args string. */
-	);
-
-	parse = ast_strdupa(data);
-	AST_STANDARD_APP_ARGS(app_args, parse);
-
-	if (!ast_strlen_zero(app_args.pl_space)) {
-		if (sscanf(app_args.pl_space, "%30u", &park) != 1) {
-			ast_log(LOG_WARNING, "Specified parking extension not a number: %s\n",
-				app_args.pl_space);
-			park = -1;
-		}
-	}
-
-	if (!ast_strlen_zero(app_args.pl_name)) {
-		pl_name = app_args.pl_name;
-	} else {
-		pl_name = findparkinglotname(chan);
-	}
-	if (ast_strlen_zero(pl_name)) {
-		/* Parking lot is not specified, so use the default parking lot. */
-		parkinglot = parkinglot_addref(default_parkinglot);
-	} else {
-		parkinglot = find_parkinglot(pl_name);
-		if (!parkinglot) {
-			/* It helps to answer the channel if not already up. :) */
-			if (ast_channel_state(chan) != AST_STATE_UP) {
-				ast_answer(chan);
-			}
-			if (ast_stream_and_wait(chan, "pbx-invalidpark", "")) {
-				ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n",
-					"pbx-invalidpark", ast_channel_name(chan));
-			}
-			ast_log(LOG_WARNING,
-				"Channel %s tried to retrieve parked call from unknown parking lot '%s'\n",
-				ast_channel_name(chan), pl_name);
-			return -1;
-		}
-	}
-
-	AST_LIST_LOCK(&parkinglot->parkings);
-	AST_LIST_TRAVERSE_SAFE_BEGIN(&parkinglot->parkings, pu, list) {
-		if ((ast_strlen_zero(app_args.pl_space) || pu->parkingnum == park)
-			&& !pu->notquiteyet && !ast_channel_pbx(pu->chan)) {
-			/* The parking space has a call and can be picked up now. */
-			AST_LIST_REMOVE_CURRENT(list);
-			break;
-		}
-	}
-	AST_LIST_TRAVERSE_SAFE_END;
-	if (pu) {
-		struct ast_callid *callid = ast_read_threadstorage_callid();
-
-		/* Found a parked call to pickup. */
-		peer = pu->chan;
-
-		/* We need to map the call id we have from this thread to the channel we found. */
-		if (callid) {
-			ast_channel_callid_set(peer, callid);
-			callid = ast_callid_unref(callid);
-		}
-
-		con = ast_context_find(parkinglot->cfg.parking_con);
-		if (con) {
-			if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL, 0)) {
-				ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
-			} else {
-				notify_metermaids(pu->parkingexten, parkinglot->cfg.parking_con, AST_DEVICE_NOT_INUSE);
-			}
-		} else {
-			ast_log(LOG_WARNING, "Whoa, no parking context?\n");
-		}
-
-		ast_cel_report_event(pu->chan, AST_CEL_PARK_END, NULL, "UnParkedCall", chan);
-		/*** DOCUMENTATION
-			<managerEventInstance>
-				<synopsis>Raised when a call has been unparked.</synopsis>
-				<syntax>
-					<xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter[@name='Exten'])" />
-					<xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter[@name='Parkinglot'])" />
-					<xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter[@name='From'])" />
-				</syntax>
-				<see-also>
-					<ref type="application">ParkedCall</ref>
-					<ref type="managerEvent">ParkedCall</ref>
-				</see-also>
-			</managerEventInstance>
-		***/
-		ast_manager_event(pu->chan, EVENT_FLAG_CALL, "UnParkedCall",
-			"Exten: %s\r\n"
-			"Channel: %s\r\n"
-			"Parkinglot: %s\r\n"
-			"From: %s\r\n"
-			"CallerIDNum: %s\r\n"
-			"CallerIDName: %s\r\n"
-			"ConnectedLineNum: %s\r\n"
-			"ConnectedLineName: %s\r\n"
-			"Uniqueid: %s\r\n",
-			pu->parkingexten, ast_channel_name(pu->chan), pu->parkinglot->name,
-			ast_channel_name(chan),
-			S_COR(ast_channel_caller(pu->chan)->id.number.valid, ast_channel_caller(pu->chan)->id.number.str, "<unknown>"),
-			S_COR(ast_channel_caller(pu->chan)->id.name.valid, ast_channel_caller(pu->chan)->id.name.str, "<unknown>"),
-			S_COR(ast_channel_connected(pu->chan)->id.number.valid, ast_channel_connected(pu->chan)->id.number.str, "<unknown>"),
-			S_COR(ast_channel_connected(pu->chan)->id.name.valid, ast_channel_connected(pu->chan)->id.name.str, "<unknown>"),
-			ast_channel_uniqueid(pu->chan)
-			);
-
-		/* Stop entertaining the caller. */
-		switch (pu->hold_method) {
-		case AST_CONTROL_HOLD:
-			ast_indicate(pu->chan, AST_CONTROL_UNHOLD);
-			break;
-		case AST_CONTROL_RINGING:
-			ast_indicate(pu->chan, -1);
-			break;
-		default:
-			break;
-		}
-		pu->hold_method = 0;
-
-		parkinglot_unref(pu->parkinglot);
-		ast_free(pu);
-	}
-	AST_LIST_UNLOCK(&parkinglot->parkings);
-
-	if (peer) {
-		/* Update connected line between retrieving call and parked call. */
-		struct ast_party_connected_line connected;
-
-		ast_party_connected_line_init(&connected);
-
-		/* Send our caller-id to peer. */
-		ast_channel_lock(chan);
-		ast_connected_line_copy_from_caller(&connected, ast_channel_caller(chan));
-		ast_channel_unlock(chan);
-		connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER;
-		if (ast_channel_connected_line_sub(chan, peer, &connected, 0) &&
-			ast_channel_connected_line_macro(chan, peer, &connected, 0, 0)) {
-			ast_channel_update_connected_line(peer, &connected, NULL);
-		}
-
-		/*
-		 * Get caller-id from peer.
-		 *
-		 * Update the retrieving call before it is answered if possible
-		 * for best results.  Some phones do not support updating the
-		 * connected line information after connection.
-		 */
-		ast_channel_lock(peer);
-		ast_connected_line_copy_from_caller(&connected, ast_channel_caller(peer));
-		ast_channel_unlock(peer);
-		connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER;
-		if (ast_channel_connected_line_sub(peer, chan, &connected, 0) &&
-			ast_channel_connected_line_macro(peer, chan, &connected, 1, 0)) {
-			ast_channel_update_connected_line(chan, &connected, NULL);
-		}
-
-		ast_party_connected_line_free(&connected);
-	}
-
-	/* JK02: it helps to answer the channel if not already up */
-	if (ast_channel_state(chan) != AST_STATE_UP) {
-		ast_answer(chan);
-	}
-
-	if (peer) {
-		struct ast_datastore *features_datastore;
-		struct ast_dial_features *dialfeatures;
-
-		/* Play a courtesy to the source(s) configured to prefix the bridge connecting */
-		if (!ast_strlen_zero(courtesytone)) {
-			static const char msg[] = "courtesy tone";
-
-			switch (parkedplay) {
-			case 0:/* Courtesy tone to pickup chan */
-				res = play_message_to_chans(chan, peer, -1, msg, courtesytone);
-				break;
-			case 1:/* Courtesy tone to parked chan */
-				res = play_message_to_chans(chan, peer, 1, msg, courtesytone);
-				break;
-			case 2:/* Courtesy tone to both chans */
-				res = play_message_to_chans(chan, peer, 0, msg, courtesytone);
-				break;
-			default:
-				res = 0;
-				break;
-			}
-			if (res) {
-				ast_autoservice_chan_hangup_peer(chan, peer);
-				parkinglot_unref(parkinglot);
-				return -1;
-			}
-		}
-
-		res = ast_channel_make_compatible(chan, peer);
-		if (res < 0) {
-			ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", ast_channel_name(chan), ast_channel_name(peer));
-			ast_autoservice_chan_hangup_peer(chan, peer);
-			parkinglot_unref(parkinglot);
-			return -1;
-		}
-		/* This runs sorta backwards, since we give the incoming channel control, as if it
-		   were the person called. */
-		ast_verb(3, "Channel %s connected to parked call %d\n", ast_channel_name(chan), park);
-
-		pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", ast_channel_name(peer));
-		ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(peer));
-		memset(&config, 0, sizeof(struct ast_bridge_config));
-
-		/* Get datastore for peer and apply it's features to the callee side of the bridge config */
-		ast_channel_lock(peer);
-		features_datastore = ast_channel_datastore_find(peer, &dial_features_info, NULL);
-		if (features_datastore && (dialfeatures = features_datastore->data)) {
-			ast_copy_flags(&config.features_callee, &dialfeatures->my_features,
-				AST_FLAGS_ALL);
-		}
-		ast_channel_unlock(peer);
-
-		if ((parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH)) {
-			ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT);
-		}
-		if ((parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH)) {
-			ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT);
-		}
-		if ((parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYBOTH)) {
-			ast_set_flag(&(config.features_callee), AST_FEATURE_PARKCALL);
-		}
-		if ((parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYBOTH)) {
-			ast_set_flag(&(config.features_caller), AST_FEATURE_PARKCALL);
-		}
-		if ((parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYBOTH)) {
-			ast_set_flag(&(config.features_callee), AST_FEATURE_DISCONNECT);
-		}
-		if ((parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYBOTH)) {
-			ast_set_flag(&(config.features_caller), AST_FEATURE_DISCONNECT);
-		}
-		if ((parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYBOTH)) {
-			ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMON);
-		}
-		if ((parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYBOTH)) {
-			ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMON);
-		}
-
-		res = ast_bridge_call(chan, peer, &config);
-
-		pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", ast_channel_name(peer));
-		ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(peer));
-
-		/* Simulate the PBX hanging up */
-		ast_autoservice_chan_hangup_peer(chan, peer);
-	} else {
-		if (ast_stream_and_wait(chan, "pbx-invalidpark", "")) {
-			ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", "pbx-invalidpark",
-				ast_channel_name(chan));
-		}
-		ast_verb(3, "Channel %s tried to retrieve nonexistent parked call %d\n",
-			ast_channel_name(chan), park);
-		res = -1;
-	}
-
-	parkinglot_unref(parkinglot);
-	return res;
-}
-
 /*!
  * \brief Unreference parkinglot object.
  */
@@ -6282,6 +5533,10 @@ static void process_applicationmap_line(struct ast_variable *var)
 		return;
 	}
 
+	/*
+	 * We will parse and require correct syntax for the ActivatedBy
+	 * option, but the ActivatedBy option is not honored anymore.
+	 */
 	if (ast_strlen_zero(args.activatedby)) {
 		ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH);
 	} else if (!strcasecmp(args.activatedby, "caller")) {
@@ -7235,8 +6490,6 @@ static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cl
 {
 	int i;
 	struct ast_call_feature *feature;
-	struct ao2_iterator iter;
-	struct ast_parkinglot *curlot;
 #define HFS_FORMAT "%-25s %-7s %-7s\n"
 
 	switch (cmd) {
@@ -7292,28 +6545,7 @@ static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cl
 	}
 	AST_RWLIST_UNLOCK(&feature_groups);
 
-	iter = ao2_iterator_init(parkinglots, 0);
-	while ((curlot = ao2_iterator_next(&iter))) {
-		ast_cli(a->fd, "\nCall parking (Parking lot: %s)\n", curlot->name);
-		ast_cli(a->fd, "------------\n");
-		ast_cli(a->fd,"%-22s:      %s\n", "Parking extension", curlot->cfg.parkext);
-		ast_cli(a->fd,"%-22s:      %s\n", "Parking context", curlot->cfg.parking_con);
-		ast_cli(a->fd,"%-22s:      %d-%d\n", "Parked call extensions",
-			curlot->cfg.parking_start, curlot->cfg.parking_stop);
-		ast_cli(a->fd,"%-22s:      %u ms\n", "Parkingtime", curlot->cfg.parkingtime);
-		ast_cli(a->fd,"%-22s:      %s\n", "Comeback to origin",
-				(curlot->cfg.comebacktoorigin ? "yes" : "no"));
-		ast_cli(a->fd,"%-22s:      %s%s\n", "Comeback context",
-				curlot->cfg.comebackcontext, (curlot->cfg.comebacktoorigin ?
-					" (comebacktoorigin=yes, not used)" : ""));
-		ast_cli(a->fd,"%-22s:      %d\n", "Comeback dial time",
-				curlot->cfg.comebackdialtime);
-		ast_cli(a->fd,"%-22s:      %s\n", "MusicOnHold class", curlot->cfg.mohclass);
-		ast_cli(a->fd,"%-22s:      %s\n", "Enabled", AST_CLI_YESNO(!curlot->disabled));
-		ast_cli(a->fd,"\n");
-		ao2_ref(curlot, -1);
-	}
-	ao2_iterator_destroy(&iter);
+	ast_cli(a->fd, "\n");
 
 	return CLI_SUCCESS;
 }
@@ -7511,8 +6743,8 @@ static int action_bridge(struct mansession *s, const struct message *m)
 		return 0;
 	}
 
-	tobj->chan = tmpchana;
-	tobj->peer = tmpchanb;
+	tobj->chan = tmpchanb;
+	tobj->peer = tmpchana;
 	tobj->return_to_pbx = 1;
 
 	if (ast_true(playtone)) {
@@ -7545,6 +6777,7 @@ static int action_bridge(struct mansession *s, const struct message *m)
 				"Channel1: %s\r\n"
 				"Channel2: %s\r\n", ast_channel_name(tmpchana), ast_channel_name(tmpchanb));
 
+/* BUGBUG there seems to be no COLP update here. */
 	bridge_call_thread_launch(tobj);
 
 	astman_send_ack(s, m, "Launched bridge thread with success");
@@ -7552,180 +6785,11 @@ static int action_bridge(struct mansession *s, const struct message *m)
 	return 0;
 }
 
-/*!
- * \brief CLI command to list parked calls
- * \param e
- * \param cmd
- * \param a
- *
- * Check right usage, lock parking lot, display parked calls, unlock parking lot list.
- * \retval CLI_SUCCESS on success.
- * \retval CLI_SHOWUSAGE on incorrect number of arguments.
- * \retval NULL when tab completion is used.
- */
-static char *handle_parkedcalls(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	struct parkeduser *cur;
-	int numparked = 0;
-	struct ao2_iterator iter;
-	struct ast_parkinglot *curlot;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "parkedcalls show";
-		e->usage =
-			"Usage: parkedcalls show\n"
-			"       List currently parked calls\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc > e->args)
-		return CLI_SHOWUSAGE;
-
-	ast_cli(a->fd, "%-10s %-25s (%-15s %-12s %4s) %s\n", "Num", "Channel",
-		"Context", "Extension", "Pri", "Timeout");
-
-	iter = ao2_iterator_init(parkinglots, 0);
-	while ((curlot = ao2_iterator_next(&iter))) {
-		int lotparked = 0;
-
-		/* subtract ref for iterator and for configured parking lot */
-		ast_cli(a->fd, "*** Parking lot: %s (%d)\n", curlot->name,
-			ao2_ref(curlot, 0) - 2 - (curlot == default_parkinglot));
-
-		AST_LIST_LOCK(&curlot->parkings);
-		AST_LIST_TRAVERSE(&curlot->parkings, cur, list) {
-			ast_cli(a->fd, "%-10.10s %-25s (%-15s %-12s %4d) %6lds\n",
-				cur->parkingexten, ast_channel_name(cur->chan), cur->context, cur->exten,
-				cur->priority,
-				(long) (cur->start.tv_sec + (cur->parkingtime / 1000) - time(NULL)));
-			++lotparked;
-		}
-		AST_LIST_UNLOCK(&curlot->parkings);
-		if (lotparked) {
-			numparked += lotparked;
-			ast_cli(a->fd, "   %d parked call%s in parking lot %s\n", lotparked,
-				ESS(lotparked), curlot->name);
-		}
-
-		ao2_ref(curlot, -1);
-	}
-	ao2_iterator_destroy(&iter);
-
-	ast_cli(a->fd, "---\n%d parked call%s in total.\n", numparked, ESS(numparked));
-
-	return CLI_SUCCESS;
-}
-
 static struct ast_cli_entry cli_features[] = {
 	AST_CLI_DEFINE(handle_feature_show, "Lists configured features"),
 	AST_CLI_DEFINE(handle_features_reload, "Reloads configured features"),
-	AST_CLI_DEFINE(handle_parkedcalls, "List currently parked calls"),
 };
 
-static int manager_parkinglot_list(struct mansession *s, const struct message *m)
-{
-	const char *id = astman_get_header(m, "ActionID");
-	char idText[256] = "";
-	struct ao2_iterator iter;
-	struct ast_parkinglot *curlot;
-
-	if (!ast_strlen_zero(id))
-		snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
-
-	astman_send_ack(s, m, "Parking lots will follow");
-
-	iter = ao2_iterator_init(parkinglots, 0);
-	while ((curlot = ao2_iterator_next(&iter))) {
-		astman_append(s, "Event: Parkinglot\r\n"
-			"Name: %s\r\n"
-			"StartExten: %d\r\n"
-			"StopExten: %d\r\n"
-			"Timeout: %d\r\n"
-			"\r\n",
-			curlot->name,
-			curlot->cfg.parking_start,
-			curlot->cfg.parking_stop,
-			curlot->cfg.parkingtime ? curlot->cfg.parkingtime / 1000 : curlot->cfg.parkingtime);
-		ao2_ref(curlot, -1);
-	}
-
-	astman_append(s,
-		"Event: ParkinglotsComplete\r\n"
-		"%s"
-		"\r\n",idText);
-
-	return RESULT_SUCCESS;
-}
-
-/*!
- * \brief Dump parking lot status
- * \param s
- * \param m
- *
- * Lock parking lot, iterate list and append parked calls status, unlock parking lot.
- * \return Always RESULT_SUCCESS
- */
-static int manager_parking_status(struct mansession *s, const struct message *m)
-{
-	struct parkeduser *cur;
-	const char *id = astman_get_header(m, "ActionID");
-	char idText[256] = "";
-	struct ao2_iterator iter;
-	struct ast_parkinglot *curlot;
-	int numparked = 0;
-	long now = time(NULL);
-
-	if (!ast_strlen_zero(id))
-		snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
-
-	astman_send_ack(s, m, "Parked calls will follow");
-
-	iter = ao2_iterator_init(parkinglots, 0);
-	while ((curlot = ao2_iterator_next(&iter))) {
-		AST_LIST_LOCK(&curlot->parkings);
-		AST_LIST_TRAVERSE(&curlot->parkings, cur, list) {
-			astman_append(s, "Event: ParkedCall\r\n"
-				"Parkinglot: %s\r\n"
-				"Exten: %d\r\n"
-				"Channel: %s\r\n"
-				"From: %s\r\n"
-				"Timeout: %ld\r\n"
-				"Duration: %ld\r\n"
-				"CallerIDNum: %s\r\n"
-				"CallerIDName: %s\r\n"
-				"ConnectedLineNum: %s\r\n"
-				"ConnectedLineName: %s\r\n"
-				"%s"
-				"\r\n",
-				curlot->name,
-				cur->parkingnum, ast_channel_name(cur->chan), cur->peername,
-				(long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - now,
-				now - (long) cur->start.tv_sec,
-				S_COR(ast_channel_caller(cur->chan)->id.number.valid, ast_channel_caller(cur->chan)->id.number.str, ""),	/* XXX in other places it is <unknown> */
-				S_COR(ast_channel_caller(cur->chan)->id.name.valid, ast_channel_caller(cur->chan)->id.name.str, ""),
-				S_COR(ast_channel_connected(cur->chan)->id.number.valid, ast_channel_connected(cur->chan)->id.number.str, ""),	/* XXX in other places it is <unknown> */
-				S_COR(ast_channel_connected(cur->chan)->id.name.valid, ast_channel_connected(cur->chan)->id.name.str, ""),
-				idText);
-			++numparked;
-		}
-		AST_LIST_UNLOCK(&curlot->parkings);
-		ao2_ref(curlot, -1);
-	}
-	ao2_iterator_destroy(&iter);
-
-	astman_append(s,
-		"Event: ParkedCallsComplete\r\n"
-		"Total: %d\r\n"
-		"%s"
-		"\r\n",
-		numparked, idText);
-
-	return RESULT_SUCCESS;
-}
-
 /*!
  * \brief Create manager event for parked calls
  * \param s
@@ -8210,8 +7274,11 @@ int ast_bridge_timelimit(struct ast_channel *chan, struct ast_bridge_config *con
 		calldurationlimit->tv_usec = (config->timelimit % 1000) * 1000;
 		ast_verb(3, "Setting call duration limit to %.3lf seconds.\n",
 			calldurationlimit->tv_sec + calldurationlimit->tv_usec / 1000000.0);
-		config->timelimit = play_to_caller = play_to_callee =
-		config->play_warning = config->warning_freq = 0;
+		play_to_caller = 0;
+		play_to_callee = 0;
+		config->timelimit = 0;
+		config->play_warning = 0;
+		config->warning_freq = 0;
 	} else {
 		ast_verb(4, "Limit Data for this call:\n");
 		ast_verb(4, "timelimit      = %ld ms (%.3lf s)\n", config->timelimit, config->timelimit / 1000.0);
@@ -8242,12 +7309,17 @@ int ast_bridge_timelimit(struct ast_channel *chan, struct ast_bridge_config *con
  */
 static int bridge_exec(struct ast_channel *chan, const char *data)
 {
-	struct ast_channel *current_dest_chan, *final_dest_chan, *chans[2];
+	struct ast_channel *current_dest_chan;
+	struct ast_channel *final_dest_chan;
+	struct ast_channel *chans[2];
 	char *tmp_data  = NULL;
 	struct ast_flags opts = { 0, };
 	struct ast_bridge_config bconfig = { { 0, }, };
 	char *opt_args[OPT_ARG_ARRAY_SIZE];
 	struct timeval calldurationlimit = { 0, };
+	const char *context;
+	const char *extension;
+	int priority;
 
 	AST_DECLARE_APP_ARGS(args,
 		AST_APP_ARG(dest_chan);
@@ -8357,8 +7429,7 @@ static int bridge_exec(struct ast_channel *chan, const char *data)
 			"Channel1: %s\r\n"
 			"Channel2: %s\r\n", ast_channel_name(chan), ast_channel_name(final_dest_chan));
 
-		/* Maybe we should return this channel to the PBX? */
-		ast_autoservice_chan_hangup_peer(chan, final_dest_chan);
+		bridge_failed_peer_goto(chan, final_dest_chan);
 
 		pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "INCOMPATIBLE");
 		current_dest_chan = ast_channel_unref(current_dest_chan);
@@ -8406,60 +7477,28 @@ static int bridge_exec(struct ast_channel *chan, const char *data)
 	if (ast_test_flag(&opts, OPT_CALLER_PARK))
 		ast_set_flag(&(bconfig.features_caller), AST_FEATURE_PARKCALL);
 
-	/*
-	 * Don't let the after-bridge code run the h-exten.  We want to
-	 * continue in the dialplan.
-	 */
-	ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT);
+	/* Setup after bridge goto location. */
+	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(final_dest_chan, context, extension, priority,
+			opt_args[OPT_ARG_CALLEE_GO_ON]);
+	} else if (!ast_test_flag(&opts, OPT_CALLEE_KILL)) {
+		ast_channel_lock(final_dest_chan);
+		context = ast_strdupa(ast_channel_context(final_dest_chan));
+		extension = ast_strdupa(ast_channel_exten(final_dest_chan));
+		priority = ast_channel_priority(final_dest_chan);
+		ast_channel_unlock(final_dest_chan);
+		ast_after_bridge_set_goto(final_dest_chan, context, extension, priority);
+	}
+
 	ast_bridge_call(chan, final_dest_chan, &bconfig);
 
 	/* The bridge has ended, set BRIDGERESULT to SUCCESS. */
 	pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "SUCCESS");
-
-	/* If the other channel has not been hung up, return it to the PBX */
-	if (!ast_check_hangup(final_dest_chan)) {
-		if (ast_test_flag(&opts, OPT_CALLEE_GO_ON)) {
-			char *caller_context;
-			char *caller_extension;
-			int caller_priority;
-			int goto_opt;
-
-			ast_channel_lock(chan);
-			caller_context = ast_strdupa(ast_channel_context(chan));
-			caller_extension = ast_strdupa(ast_channel_exten(chan));
-			caller_priority = ast_channel_priority(chan);
-			ast_channel_unlock(chan);
-
-			if (!ast_strlen_zero(opt_args[OPT_ARG_CALLEE_GO_ON])) {
-				ast_replace_subargument_delimiter(opt_args[OPT_ARG_CALLEE_GO_ON]);
-				/* Set current dialplan position to bridger dialplan position */
-				goto_opt = ast_goto_if_exists(final_dest_chan, caller_context, caller_extension, caller_priority)
-					/* Then perform the goto */
-					|| ast_parseable_goto(final_dest_chan, opt_args[OPT_ARG_CALLEE_GO_ON]);
-			} else { /* F() */
-				goto_opt = ast_goto_if_exists(final_dest_chan, caller_context, caller_extension, caller_priority + 1);
-			}
-			if (goto_opt || ast_pbx_start(final_dest_chan)) {
-				ast_autoservice_chan_hangup_peer(chan, final_dest_chan);
-			}
-		} else if (!ast_test_flag(&opts, OPT_CALLEE_KILL)) {
-			ast_debug(1, "starting new PBX in %s,%s,%d for chan %s\n",
-				ast_channel_context(final_dest_chan), ast_channel_exten(final_dest_chan),
-				ast_channel_priority(final_dest_chan), ast_channel_name(final_dest_chan));
-
-			if (ast_pbx_start(final_dest_chan)) {
-				ast_log(LOG_WARNING, "FAILED continuing PBX on dest chan %s\n", ast_channel_name(final_dest_chan));
-				ast_autoservice_chan_hangup_peer(chan, final_dest_chan);
-			} else {
-				ast_debug(1, "SUCCESS continuing PBX on chan %s\n", ast_channel_name(final_dest_chan));
-			}
-		} else {
-			ast_autoservice_chan_hangup_peer(chan, final_dest_chan);
-		}
-	} else {
-		ast_debug(1, "chan %s was hungup\n", ast_channel_name(final_dest_chan));
-		ast_autoservice_chan_hangup_peer(chan, final_dest_chan);
-	}
 done:
 	ast_free((char *) bconfig.warning_sound);
 	ast_free((char *) bconfig.end_sound);
@@ -9130,10 +8169,7 @@ static void features_shutdown(void)
 	ast_custom_function_unregister(&feature_function);
 	ast_manager_unregister("Bridge");
 	ast_manager_unregister("Park");
-	ast_manager_unregister("Parkinglots");
-	ast_manager_unregister("ParkedCalls");
-	ast_unregister_application(parkcall);
-	ast_unregister_application(parkedcall);
+
 	ast_unregister_application(app_bridge);
 
 	pthread_cancel(parking_thread);
@@ -9161,12 +8197,7 @@ int ast_features_init(void)
 		return -1;
 	}
 	ast_register_application2(app_bridge, bridge_exec, NULL, NULL, NULL);
-	res = ast_register_application2(parkedcall, parked_call_exec, NULL, NULL, NULL);
-	if (!res)
-		res = ast_register_application2(parkcall, park_call_exec, NULL, NULL, NULL);
 	if (!res) {
-		ast_manager_register_xml_core("ParkedCalls", 0, manager_parking_status);
-		ast_manager_register_xml_core("Parkinglots", 0, manager_parkinglot_list);
 		ast_manager_register_xml_core("Park", EVENT_FLAG_CALL, manager_park);
 		ast_manager_register_xml_core("Bridge", EVENT_FLAG_CALL, action_bridge);
 	}
@@ -9182,4 +8213,3 @@ int ast_features_init(void)
 
 	return res;
 }
-
diff --git a/main/frame.c b/main/frame.c
index b559d552d2255a7a863908f514a4e3ad1acd9a1a..8822261f6f7b720f4f2a1f08f654a9fa9ba66acf 100644
--- a/main/frame.c
+++ b/main/frame.c
@@ -635,6 +635,10 @@ void ast_frame_subclass2str(struct ast_frame *f, char *subclass, size_t slen, ch
 		/* Should never happen */
 		snprintf(subclass, slen, "IAX Frametype %d", f->subclass.integer);
 		break;
+	case AST_FRAME_BRIDGE_ACTION:
+		/* Should never happen */
+		snprintf(subclass, slen, "Bridge Frametype %d", f->subclass.integer);
+		break;
 	case AST_FRAME_TEXT:
 		ast_copy_string(subclass, "N/A", slen);
 		if (moreinfo) {
@@ -722,6 +726,10 @@ void ast_frame_type2str(enum ast_frame_type frame_type, char *ftype, size_t len)
 		/* Should never happen */
 		ast_copy_string(ftype, "IAX Specific", len);
 		break;
+	case AST_FRAME_BRIDGE_ACTION:
+		/* Should never happen */
+		ast_copy_string(ftype, "Bridge Specific", len);
+		break;
 	case AST_FRAME_TEXT:
 		ast_copy_string(ftype, "Text", len);
 		break;
diff --git a/main/manager.c b/main/manager.c
index c9b2fbe1e1a74c9a2190f591d7e741586a5e9bf8..c28e6169b24d3a9e49367d1ad4812af98b67eae7 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -94,6 +94,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/stasis.h"
 #include "asterisk/test.h"
 #include "asterisk/json.h"
+#include "asterisk/bridging.h"
 
 /*** DOCUMENTATION
 	<manager name="Ping" language="en_US">
@@ -966,6 +967,25 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                         manager.conf will be present upon starting a new session.</para>
 		</description>
 	</manager>
+	<manager name="BlindTransfer" language="en_US">
+		<synopsis>
+			Blind transfer channel(s) to the given destination
+		</synopsis>
+		<syntax>
+			<parameter name="Channel" required="true">
+			</parameter>
+			<parameter name="Context">
+			</parameter>
+			<parameter name="Exten">
+			</parameter>
+		</syntax>
+		<description>
+			<para>Redirect all channels currently bridged to the specified channel to the specified destination.</para>
+		</description>
+		<see-also>
+			<ref type="manager">Redirect</ref>
+		</see-also>
+	</manager>
  ***/
 
 /*! \addtogroup Group_AMI AMI functions
@@ -3538,7 +3558,8 @@ static int action_status(struct mansession *s, const struct message *m)
 	const char *cvariables = astman_get_header(m, "Variables");
 	char *variables = ast_strdupa(S_OR(cvariables, ""));
 	struct ast_channel *c;
-	char bridge[256];
+	struct ast_bridge *bridge;
+	char bridge_text[256];
 	struct timeval now = ast_tvnow();
 	long elapsed_seconds = 0;
 	int channels = 0;
@@ -3607,44 +3628,52 @@ static int action_status(struct mansession *s, const struct message *m)
 		}
 
 		channels++;
-		if (ast_channel_internal_bridged_channel(c)) {
-			snprintf(bridge, sizeof(bridge), "BridgedChannel: %s\r\nBridgedUniqueid: %s\r\n", ast_channel_name(ast_channel_internal_bridged_channel(c)), ast_channel_uniqueid(ast_channel_internal_bridged_channel(c)));
+		bridge = ast_channel_get_bridge(c);
+		if (bridge) {
+			snprintf(bridge_text, sizeof(bridge_text), "BridgeID: %s\r\n",
+				bridge->uniqueid);
+			ao2_ref(bridge, -1);
 		} else {
-			bridge[0] = '\0';
+			bridge_text[0] = '\0';
 		}
 		if (ast_channel_pbx(c)) {
 			if (ast_channel_cdr(c)) {
 				elapsed_seconds = now.tv_sec - ast_channel_cdr(c)->start.tv_sec;
 			}
 			astman_append(s,
-			"Event: Status\r\n"
-			"Privilege: Call\r\n"
-			"Channel: %s\r\n"
-			"CallerIDNum: %s\r\n"
-			"CallerIDName: %s\r\n"
-			"ConnectedLineNum: %s\r\n"
-			"ConnectedLineName: %s\r\n"
-			"Accountcode: %s\r\n"
-			"ChannelState: %d\r\n"
-			"ChannelStateDesc: %s\r\n"
-			"Context: %s\r\n"
-			"Extension: %s\r\n"
-			"Priority: %d\r\n"
-			"Seconds: %ld\r\n"
-			"%s"
-			"Uniqueid: %s\r\n"
-			"%s"
-			"%s"
-			"\r\n",
-			ast_channel_name(c),
-			S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, "<unknown>"),
-			S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, "<unknown>"),
-			S_COR(ast_channel_connected(c)->id.number.valid, ast_channel_connected(c)->id.number.str, "<unknown>"),
-			S_COR(ast_channel_connected(c)->id.name.valid, ast_channel_connected(c)->id.name.str, "<unknown>"),
-			ast_channel_accountcode(c),
-			ast_channel_state(c),
-			ast_state2str(ast_channel_state(c)), ast_channel_context(c),
-			ast_channel_exten(c), ast_channel_priority(c), (long)elapsed_seconds, bridge, ast_channel_uniqueid(c), ast_str_buffer(str), idText);
+				"Event: Status\r\n"
+				"Privilege: Call\r\n"
+				"Channel: %s\r\n"
+				"CallerIDNum: %s\r\n"
+				"CallerIDName: %s\r\n"
+				"ConnectedLineNum: %s\r\n"
+				"ConnectedLineName: %s\r\n"
+				"Accountcode: %s\r\n"
+				"ChannelState: %d\r\n"
+				"ChannelStateDesc: %s\r\n"
+				"Context: %s\r\n"
+				"Extension: %s\r\n"
+				"Priority: %d\r\n"
+				"Seconds: %ld\r\n"
+				"%s"
+				"Uniqueid: %s\r\n"
+				"%s"
+				"%s"
+				"\r\n",
+				ast_channel_name(c),
+				S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, "<unknown>"),
+				S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, "<unknown>"),
+				S_COR(ast_channel_connected(c)->id.number.valid, ast_channel_connected(c)->id.number.str, "<unknown>"),
+				S_COR(ast_channel_connected(c)->id.name.valid, ast_channel_connected(c)->id.name.str, "<unknown>"),
+				ast_channel_accountcode(c),
+				ast_channel_state(c),
+				ast_state2str(ast_channel_state(c)),
+				ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c),
+				(long) elapsed_seconds,
+				bridge_text,
+				ast_channel_uniqueid(c),
+				ast_str_buffer(str),
+				idText);
 		} else {
 			astman_append(s,
 				"Event: Status\r\n"
@@ -3667,8 +3696,11 @@ static int action_status(struct mansession *s, const struct message *m)
 				S_COR(ast_channel_connected(c)->id.number.valid, ast_channel_connected(c)->id.number.str, "<unknown>"),
 				S_COR(ast_channel_connected(c)->id.name.valid, ast_channel_connected(c)->id.name.str, "<unknown>"),
 				ast_channel_accountcode(c),
-				ast_state2str(ast_channel_state(c)), bridge, ast_channel_uniqueid(c),
-				ast_str_buffer(str), idText);
+				ast_state2str(ast_channel_state(c)),
+				bridge_text,
+				ast_channel_uniqueid(c),
+				ast_str_buffer(str),
+				idText);
 		}
 
 		ast_channel_unlock(c);
@@ -3804,12 +3836,6 @@ static int action_redirect(struct mansession *s, const struct message *m)
 
 	if (ast_strlen_zero(name2)) {
 		/* Single channel redirect in progress. */
-		if (ast_channel_pbx(chan)) {
-			ast_channel_lock(chan);
-			/* don't let the after-bridge code run the h-exten */
-			ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT);
-			ast_channel_unlock(chan);
-		}
 		res = ast_async_goto(chan, context, exten, pi);
 		if (!res) {
 			astman_send_ack(s, m, "Redirect successful");
@@ -3837,16 +3863,12 @@ static int action_redirect(struct mansession *s, const struct message *m)
 	/* Dual channel redirect in progress. */
 	if (ast_channel_pbx(chan)) {
 		ast_channel_lock(chan);
-		/* don't let the after-bridge code run the h-exten */
-		ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT
-			| AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
+		ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
 		ast_channel_unlock(chan);
 	}
 	if (ast_channel_pbx(chan2)) {
 		ast_channel_lock(chan2);
-		/* don't let the after-bridge code run the h-exten */
-		ast_set_flag(ast_channel_flags(chan2), AST_FLAG_BRIDGE_HANGUP_DONT
-			| AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
+		ast_set_flag(ast_channel_flags(chan2), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
 		ast_channel_unlock(chan2);
 	}
 	res = ast_async_goto(chan, context, exten, pi);
@@ -3882,6 +3904,51 @@ static int action_redirect(struct mansession *s, const struct message *m)
 	return 0;
 }
 
+static int action_blind_transfer(struct mansession *s, const struct message *m)
+{
+	const char *name = astman_get_header(m, "Channel");
+	const char *exten = astman_get_header(m, "Exten");
+	const char *context = astman_get_header(m, "Context");
+	RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup);
+
+	if (ast_strlen_zero(name)) {
+		astman_send_error(s, m, "No channel specified");
+		return 0;
+	}
+
+	if (ast_strlen_zero(exten)) {
+		astman_send_error(s, m, "No extension specified");
+		return 0;
+	}
+
+	chan = ast_channel_get_by_name(name);
+	if (!chan) {
+		astman_send_error(s, m, "Channel specified does not exist");
+		return 0;
+	}
+
+	if (ast_strlen_zero(context)) {
+		context = ast_channel_context(chan);
+	}
+
+	switch (ast_bridge_transfer_blind(chan, exten, context, NULL, NULL)) {
+	case AST_BRIDGE_TRANSFER_NOT_PERMITTED:
+		astman_send_error(s, m, "Transfer not permitted");
+		break;
+	case AST_BRIDGE_TRANSFER_INVALID:
+		astman_send_error(s, m, "Transfer invalid");
+		break;
+	case AST_BRIDGE_TRANSFER_FAIL:
+		astman_send_error(s, m, "Transfer failed");
+		break;
+	case AST_BRIDGE_TRANSFER_SUCCESS:
+		astman_send_ack(s, m, "Transfer succeeded");
+		break;
+	}
+
+	return 0;
+}
+
 static int action_atxfer(struct mansession *s, const struct message *m)
 {
 	const char *name = astman_get_header(m, "Channel");
@@ -5683,7 +5750,7 @@ int __ast_manager_event_multichan(int category, const char *event, int chancount
 		return -1;
 	}
 
-	cat_str = authority_to_str (category, &auth);
+	cat_str = authority_to_str(category, &auth);
 	ast_str_set(&buf, 0,
 			"Event: %s\r\nPrivilege: %s\r\n",
 			 event, cat_str);
@@ -7510,6 +7577,10 @@ static int __init_manager(int reload, int by_external_config)
 		return -1;
 	}
 
+	if (manager_bridging_init()) {
+		return -1;
+	}
+
 	if (!registered) {
 		/* Register default actions */
 		ast_manager_register_xml_core("Ping", 0, action_ping);
@@ -7547,6 +7618,7 @@ static int __init_manager(int reload, int by_external_config)
 		ast_manager_register_xml_core("ModuleCheck", EVENT_FLAG_SYSTEM, manager_modulecheck);
 		ast_manager_register_xml_core("AOCMessage", EVENT_FLAG_AOC, action_aocmessage);
 		ast_manager_register_xml_core("Filter", EVENT_FLAG_SYSTEM, action_filter);
+		ast_manager_register_xml_core("BlindTransfer", EVENT_FLAG_CALL, action_blind_transfer);
 
 #ifdef TEST_FRAMEWORK
 		stasis_subscribe(ast_test_suite_topic(), test_suite_event_cb, NULL);
@@ -8025,3 +8097,44 @@ struct ast_datastore *astman_datastore_find(struct mansession *s, const struct a
 
 	return datastore;
 }
+
+static void manager_event_blob_dtor(void *obj)
+{
+	struct ast_manager_event_blob *ev = obj;
+	ast_string_field_free_memory(ev);
+}
+
+struct ast_manager_event_blob *
+__attribute__((format(printf, 3, 4)))
+ast_manager_event_blob_create(
+	int event_flags,
+	const char *manager_event,
+	const char *extra_fields_fmt,
+	...)
+{
+	RAII_VAR(struct ast_manager_event_blob *, ev, NULL, ao2_cleanup);
+	va_list argp;
+
+	ast_assert(extra_fields_fmt != NULL);
+	ast_assert(manager_event != NULL);
+
+	ev = ao2_alloc(sizeof(*ev), manager_event_blob_dtor);
+	if (!ev) {
+		return NULL;
+	}
+
+	if (ast_string_field_init(ev, 20)) {
+		return NULL;
+	}
+
+	ev->manager_event = manager_event;
+	ev->event_flags = event_flags;
+
+	va_start(argp, extra_fields_fmt);
+	ast_string_field_ptr_build_va(ev, &ev->extra_fields, extra_fields_fmt,
+				      argp);
+	va_end(argp);
+
+	ao2_ref(ev, +1);
+	return ev;
+}
diff --git a/main/manager_bridging.c b/main/manager_bridging.c
new file mode 100644
index 0000000000000000000000000000000000000000..01ce68aaf55b850db29cef145649edca4057abc6
--- /dev/null
+++ b/main/manager_bridging.c
@@ -0,0 +1,470 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Kinsey Moore <kmoore@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 The Asterisk Management Interface - AMI (bridge event handling)
+ *
+ * \author Kinsey Moore <kmoore@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/stasis_bridging.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/manager.h"
+#include "asterisk/stasis_message_router.h"
+
+/*! \brief Message router for cached bridge state snapshot updates */
+static struct stasis_message_router *bridge_state_router;
+
+/*** DOCUMENTATION
+	<managerEvent language="en_US" name="BridgeCreate">
+		<managerEventInstance class="EVENT_FLAG_CALL">
+			<synopsis>Raised when a bridge is created.</synopsis>
+			<syntax>
+				<parameter name="BridgeUniqueid">
+				</parameter>
+				<parameter name="BridgeType">
+					<para>The type of bridge</para>
+				</parameter>
+			</syntax>
+		</managerEventInstance>
+	</managerEvent>
+	<managerEvent language="en_US" name="BridgeDestroy">
+		<managerEventInstance class="EVENT_FLAG_CALL">
+			<synopsis>Raised when a bridge is destroyed.</synopsis>
+			<syntax>
+				<xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+			</syntax>
+		</managerEventInstance>
+	</managerEvent>
+	<managerEvent language="en_US" name="BridgeEnter">
+		<managerEventInstance class="EVENT_FLAG_CALL">
+			<synopsis>Raised when a channel enters a bridge.</synopsis>
+			<syntax>
+				<xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+				<parameter name="Uniqueid">
+					<para>The uniqueid of the channel entering the bridge</para>
+				</parameter>
+			</syntax>
+		</managerEventInstance>
+	</managerEvent>
+	<managerEvent language="en_US" name="BridgeLeave">
+		<managerEventInstance class="EVENT_FLAG_CALL">
+			<synopsis>Raised when a channel leaves a bridge.</synopsis>
+			<syntax>
+				<xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+				<parameter name="Uniqueid">
+					<para>The uniqueid of the channel leaving the bridge</para>
+				</parameter>
+			</syntax>
+		</managerEventInstance>
+	</managerEvent>
+	<manager name="BridgeList" language="en_US">
+		<synopsis>
+			Get a list of bridges in the system.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+			<parameter name="BridgeType">
+				<para>Optional type for filtering the resulting list of bridges.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Returns a list of bridges, optionally filtering on a bridge type.</para>
+		</description>
+	</manager>
+	<manager name="BridgeInfo" language="en_US">
+		<synopsis>
+			Get information about a bridge.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+			<parameter name="BridgeUniqueid" required="true">
+				<para>The unique ID of the bridge about which to retreive information.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Returns detailed information about a bridge and the channels in it.</para>
+		</description>
+	</manager>
+ ***/
+
+struct ast_str *ast_manager_build_bridge_state_string(
+	const struct ast_bridge_snapshot *snapshot,
+	const char *suffix)
+{
+	struct ast_str *out = ast_str_create(128);
+	int res = 0;
+	if (!out) {
+		return NULL;
+	}
+	res = ast_str_set(&out, 0,
+		"BridgeUniqueid%s: %s\r\n"
+		"BridgeType%s: %s\r\n",
+		suffix, snapshot->uniqueid,
+		suffix, snapshot->technology);
+
+	if (!res) {
+		return NULL;
+	}
+
+	return out;
+}
+
+/*! \brief Typedef for callbacks that get called on channel snapshot updates */
+typedef struct ast_manager_event_blob *(*bridge_snapshot_monitor)(
+	struct ast_bridge_snapshot *old_snapshot,
+	struct ast_bridge_snapshot *new_snapshot);
+
+/*! \brief Handle bridge creation */
+static struct ast_manager_event_blob *bridge_create(
+	struct ast_bridge_snapshot *old_snapshot,
+	struct ast_bridge_snapshot *new_snapshot)
+{
+	if (!new_snapshot || old_snapshot) {
+		return NULL;
+	}
+
+	return ast_manager_event_blob_create(
+		EVENT_FLAG_CALL, "BridgeCreate", NO_EXTRA_FIELDS);
+}
+
+/*! \brief Handle bridge destruction */
+static struct ast_manager_event_blob *bridge_destroy(
+	struct ast_bridge_snapshot *old_snapshot,
+	struct ast_bridge_snapshot *new_snapshot)
+{
+	if (new_snapshot || !old_snapshot) {
+		return NULL;
+	}
+
+	return ast_manager_event_blob_create(
+		EVENT_FLAG_CALL, "BridgeDestroy", NO_EXTRA_FIELDS);
+}
+
+
+bridge_snapshot_monitor bridge_monitors[] = {
+	bridge_create,
+	bridge_destroy,
+};
+
+static void bridge_snapshot_update(void *data, struct stasis_subscription *sub,
+				    struct stasis_topic *topic,
+				    struct stasis_message *message)
+{
+	RAII_VAR(struct ast_str *, bridge_event_string, NULL, ast_free);
+	struct stasis_cache_update *update;
+	struct ast_bridge_snapshot *old_snapshot;
+	struct ast_bridge_snapshot *new_snapshot;
+	size_t i;
+
+	update = stasis_message_data(message);
+
+	if (ast_bridge_snapshot_type() != update->type) {
+		return;
+	}
+
+	old_snapshot = stasis_message_data(update->old_snapshot);
+	new_snapshot = stasis_message_data(update->new_snapshot);
+
+	for (i = 0; i < ARRAY_LEN(bridge_monitors); ++i) {
+		RAII_VAR(struct ast_manager_event_blob *, event, NULL, ao2_cleanup);
+
+		event = bridge_monitors[i](old_snapshot, new_snapshot);
+		if (!event) {
+			continue;
+		}
+
+		/* If we haven't already, build the channel event string */
+		if (!bridge_event_string) {
+			bridge_event_string =
+				ast_manager_build_bridge_state_string(
+					new_snapshot ? new_snapshot : old_snapshot, "");
+			if (!bridge_event_string) {
+				return;
+			}
+		}
+
+		manager_event(event->event_flags, event->manager_event, "%s%s",
+			ast_str_buffer(bridge_event_string),
+			event->extra_fields);
+	}
+}
+
+static void bridge_merge_cb(void *data, struct stasis_subscription *sub,
+				    struct stasis_topic *topic,
+				    struct stasis_message *message)
+{
+	struct ast_bridge_merge_message *merge_msg = stasis_message_data(message);
+	RAII_VAR(struct ast_str *, to_text, NULL, ast_free);
+	RAII_VAR(struct ast_str *, from_text, NULL, ast_free);
+
+	ast_assert(merge_msg->to != NULL);
+	ast_assert(merge_msg->from != NULL);
+
+	to_text = ast_manager_build_bridge_state_string(merge_msg->to, "");
+	from_text = ast_manager_build_bridge_state_string(merge_msg->from, "From");
+
+	/*** DOCUMENTATION
+		<managerEventInstance>
+			<synopsis>Raised when two bridges are merged.</synopsis>
+			<syntax>
+				<xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+				<parameter name="BridgeUniqueidFrom">
+					<para>The uniqueid of the bridge being dissolved in the merge</para>
+				</parameter>
+				<parameter name="BridgeTypeFrom">
+					<para>The type of bridge that is being dissolved in the merge</para>
+				</parameter>
+			</syntax>
+		</managerEventInstance>
+	***/
+	manager_event(EVENT_FLAG_CALL, "BridgeMerge",
+		"%s"
+		"%s",
+		ast_str_buffer(to_text),
+		ast_str_buffer(from_text));
+}
+
+static void channel_enter_cb(void *data, struct stasis_subscription *sub,
+				    struct stasis_topic *topic,
+				    struct stasis_message *message)
+{
+	struct ast_bridge_blob *blob = stasis_message_data(message);
+	RAII_VAR(struct ast_str *, bridge_text, NULL, ast_free);
+	RAII_VAR(struct ast_str *, channel_text, NULL, ast_free);
+
+	bridge_text = ast_manager_build_bridge_state_string(blob->bridge, "");
+	channel_text = ast_manager_build_channel_state_string(blob->channel);
+
+	manager_event(EVENT_FLAG_CALL, "BridgeEnter",
+		"%s"
+		"%s",
+		ast_str_buffer(bridge_text),
+		ast_str_buffer(channel_text));
+}
+
+static void channel_leave_cb(void *data, struct stasis_subscription *sub,
+				    struct stasis_topic *topic,
+				    struct stasis_message *message)
+{
+	struct ast_bridge_blob *blob = stasis_message_data(message);
+	RAII_VAR(struct ast_str *, bridge_text, NULL, ast_free);
+	RAII_VAR(struct ast_str *, channel_text, NULL, ast_free);
+
+	bridge_text = ast_manager_build_bridge_state_string(blob->bridge, "");
+	channel_text = ast_manager_build_channel_state_string(blob->channel);
+
+	manager_event(EVENT_FLAG_CALL, "BridgeLeave",
+		"%s"
+		"%s",
+		ast_str_buffer(bridge_text),
+		ast_str_buffer(channel_text));
+}
+
+static int filter_bridge_type_cb(void *obj, void *arg, int flags)
+{
+	char *bridge_type = arg;
+	struct ast_bridge_snapshot *snapshot = stasis_message_data(obj);
+	/* unlink all the snapshots that do not match the bridge type */
+	return strcmp(bridge_type, snapshot->technology) ? CMP_MATCH : 0;
+}
+
+static int send_bridge_list_item_cb(void *obj, void *arg, void *data, int flags)
+{
+	struct ast_bridge_snapshot *snapshot = stasis_message_data(obj);
+	struct mansession *s = arg;
+	char *id_text = data;
+	RAII_VAR(struct ast_str *, bridge_info, ast_manager_build_bridge_state_string(snapshot, ""), ast_free);
+
+	astman_append(s,
+		"Event: BridgeListItem\r\n"
+		"%s"
+		"%s"
+		"\r\n",
+		ast_str_buffer(bridge_info),
+		id_text);
+	return 0;
+}
+
+static int manager_bridges_list(struct mansession *s, const struct message *m)
+{
+	const char *id = astman_get_header(m, "ActionID");
+	const char *type_filter = astman_get_header(m, "BridgeType");
+	RAII_VAR(struct ast_str *, id_text, ast_str_create(128), ast_free);
+	RAII_VAR(struct ao2_container *, bridges, NULL, ao2_cleanup);
+
+	if (!id_text) {
+		astman_send_error(s, m, "Internal error");
+		return -1;
+	}
+
+	if (!ast_strlen_zero(id)) {
+		ast_str_set(&id_text, 0, "ActionID: %s\r\n", id);
+	}
+
+	bridges = stasis_cache_dump(ast_bridge_topic_all_cached(), ast_bridge_snapshot_type());
+	if (!bridges) {
+		astman_send_error(s, m, "Internal error");
+		return -1;
+	}
+
+	astman_send_ack(s, m, "Bridge listing will follow");
+
+	if (!ast_strlen_zero(type_filter)) {
+		char *type_filter_dup = ast_strdupa(type_filter);
+		ao2_callback(bridges, OBJ_MULTIPLE | OBJ_NODATA | OBJ_UNLINK, filter_bridge_type_cb, type_filter_dup);
+	}
+
+	ao2_callback_data(bridges, OBJ_NODATA, send_bridge_list_item_cb, s, ast_str_buffer(id_text));
+
+	astman_append(s,
+		"Event: BridgeListComplete\r\n"
+		"%s"
+		"\r\n",
+		ast_str_buffer(id_text));
+
+	return 0;
+}
+
+static int send_bridge_info_item_cb(void *obj, void *arg, void *data, int flags)
+{
+	char *uniqueid = obj;
+	struct mansession *s = arg;
+	char *id_text = data;
+
+	astman_append(s,
+		"Event: BridgeInfoChannel\r\n"
+		"Uniqueid: %s\r\n"
+		"%s"
+		"\r\n",
+		uniqueid,
+		id_text);
+	return 0;
+}
+
+static int manager_bridge_info(struct mansession *s, const struct message *m)
+{
+	const char *id = astman_get_header(m, "ActionID");
+	const char *bridge_uniqueid = astman_get_header(m, "BridgeUniqueid");
+	RAII_VAR(struct ast_str *, id_text, ast_str_create(128), ast_free);
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_str *, bridge_info, NULL, ast_free);
+	struct ast_bridge_snapshot *snapshot;
+
+	if (!id_text) {
+		astman_send_error(s, m, "Internal error");
+		return -1;
+	}
+
+	if (ast_strlen_zero(bridge_uniqueid)) {
+		astman_send_error(s, m, "BridgeUniqueid must be provided");
+		return -1;
+	}
+
+	if (!ast_strlen_zero(id)) {
+		ast_str_set(&id_text, 0, "ActionID: %s\r\n", id);
+	}
+
+	msg = stasis_cache_get(ast_bridge_topic_all_cached(), ast_bridge_snapshot_type(), bridge_uniqueid);
+	if (!msg) {
+		astman_send_error(s, m, "Specified BridgeUniqueid not found");
+		return -1;
+	}
+
+	astman_send_ack(s, m, "Bridge channel listing will follow");
+
+	snapshot = stasis_message_data(msg);
+	bridge_info = ast_manager_build_bridge_state_string(snapshot, "");
+
+	ao2_callback_data(snapshot->channels, OBJ_NODATA, send_bridge_info_item_cb, s, ast_str_buffer(id_text));
+
+	astman_append(s,
+		"Event: BridgeInfoComplete\r\n"
+		"%s"
+		"%s"
+		"\r\n",
+		ast_str_buffer(bridge_info),
+		ast_str_buffer(id_text));
+
+	return 0;
+}
+
+static void manager_bridging_shutdown(void)
+{
+	stasis_message_router_unsubscribe(bridge_state_router);
+	bridge_state_router = NULL;
+	ast_manager_unregister("BridgeList");
+	ast_manager_unregister("BridgeInfo");
+}
+
+int manager_bridging_init(void)
+{
+	int ret = 0;
+
+	if (bridge_state_router) {
+		/* Already initialized */
+		return 0;
+	}
+
+	ast_register_atexit(manager_bridging_shutdown);
+
+	bridge_state_router = stasis_message_router_create(
+		stasis_caching_get_topic(ast_bridge_topic_all_cached()));
+
+	if (!bridge_state_router) {
+		return -1;
+	}
+
+	ret |= stasis_message_router_add(bridge_state_router,
+					 stasis_cache_update_type(),
+					 bridge_snapshot_update,
+					 NULL);
+
+	ret |= stasis_message_router_add(bridge_state_router,
+					 ast_bridge_merge_message_type(),
+					 bridge_merge_cb,
+					 NULL);
+
+	ret |= stasis_message_router_add(bridge_state_router,
+					 ast_channel_entered_bridge_type(),
+					 channel_enter_cb,
+					 NULL);
+
+	ret |= stasis_message_router_add(bridge_state_router,
+					 ast_channel_left_bridge_type(),
+					 channel_leave_cb,
+					 NULL);
+
+	ret |= ast_manager_register_xml_core("BridgeList", 0, manager_bridges_list);
+	ret |= ast_manager_register_xml_core("BridgeInfo", 0, manager_bridge_info);
+
+	/* If somehow we failed to add any routes, just shut down the whole
+	 * thing and fail it.
+	 */
+	if (ret) {
+		manager_bridging_shutdown();
+		return -1;
+	}
+
+	return 0;
+}
diff --git a/main/manager_channels.c b/main/manager_channels.c
index 0cab365628086268b10410a20c68ac192cdd9052..fb579dd9505cca0f2200ca41683562fc1e4de681 100644
--- a/main/manager_channels.c
+++ b/main/manager_channels.c
@@ -284,83 +284,18 @@ struct ast_str *ast_manager_build_channel_state_string_suffix(
 }
 
 struct ast_str *ast_manager_build_channel_state_string(
-	const struct ast_channel_snapshot *snapshot)
+		const struct ast_channel_snapshot *snapshot)
 {
 	return ast_manager_build_channel_state_string_suffix(snapshot, "");
 }
 
-/*! \brief Struct containing info for an AMI channel event to send out. */
-struct snapshot_manager_event {
-	/*! event_flags manager_event() flags parameter. */
-	int event_flags;
-	/*!  manager_event manager_event() category. */
-	const char *manager_event;
-	AST_DECLARE_STRING_FIELDS(
-		/* extra fields to include in the event. */
-		AST_STRING_FIELD(extra_fields);
-		);
-};
-
-static void snapshot_manager_event_dtor(void *obj)
-{
-	struct snapshot_manager_event *ev = obj;
-	ast_string_field_free_memory(ev);
-}
-
-/*!
- * \brief Construct a \ref snapshot_manager_event.
- * \param event_flags manager_event() flags parameter.
- * \param manager_event manager_event() category.
- * \param extra_fields_fmt Format string for extra fields to include.
- *                         Or NO_EXTRA_FIELDS for no extra fields.
- * \return New \ref snapshot_manager_event object.
- * \return \c NULL on error.
- */
-static struct snapshot_manager_event *
-__attribute__((format(printf, 3, 4)))
-snapshot_manager_event_create(
-	int event_flags,
-	const char *manager_event,
-	const char *extra_fields_fmt,
-	...)
-{
-	RAII_VAR(struct snapshot_manager_event *, ev, NULL, ao2_cleanup);
-	va_list argp;
-
-	ast_assert(extra_fields_fmt != NULL);
-	ast_assert(manager_event != NULL);
-
-	ev = ao2_alloc(sizeof(*ev), snapshot_manager_event_dtor);
-	if (!ev) {
-		return NULL;
-	}
-
-	if (ast_string_field_init(ev, 20)) {
-		return NULL;
-	}
-
-	ev->manager_event = manager_event;
-	ev->event_flags = event_flags;
-
-	va_start(argp, extra_fields_fmt);
-	ast_string_field_ptr_build_va(ev, &ev->extra_fields, extra_fields_fmt,
-				      argp);
-	va_end(argp);
-
-	ao2_ref(ev, +1);
-	return ev;
-}
-
-/*! GCC warns about blank or NULL format strings. So, shenanigans! */
-#define NO_EXTRA_FIELDS "%s", ""
-
 /*! \brief Typedef for callbacks that get called on channel snapshot updates */
-typedef struct snapshot_manager_event *(*snapshot_monitor)(
+typedef struct ast_manager_event_blob *(*channel_snapshot_monitor)(
 	struct ast_channel_snapshot *old_snapshot,
 	struct ast_channel_snapshot *new_snapshot);
 
 /*! \brief Handle channel state changes */
-static struct snapshot_manager_event *channel_state_change(
+static struct ast_manager_event_blob *channel_state_change(
 	struct ast_channel_snapshot *old_snapshot,
 	struct ast_channel_snapshot *new_snapshot)
 {
@@ -377,7 +312,7 @@ static struct snapshot_manager_event *channel_state_change(
 	 */
 
 	if (!old_snapshot) {
-		return snapshot_manager_event_create(
+		return ast_manager_event_blob_create(
 			EVENT_FLAG_CALL, "Newchannel", NO_EXTRA_FIELDS);
 	}
 
@@ -385,7 +320,7 @@ static struct snapshot_manager_event *channel_state_change(
 	is_hungup = ast_test_flag(&new_snapshot->flags, AST_FLAG_ZOMBIE) ? 1 : 0;
 
 	if (!was_hungup && is_hungup) {
-		return snapshot_manager_event_create(
+		return ast_manager_event_blob_create(
 			EVENT_FLAG_CALL, "Hangup",
 			"Cause: %d\r\n"
 			"Cause-txt: %s\r\n",
@@ -394,7 +329,7 @@ static struct snapshot_manager_event *channel_state_change(
 	}
 
 	if (old_snapshot->state != new_snapshot->state) {
-		return snapshot_manager_event_create(
+		return ast_manager_event_blob_create(
 			EVENT_FLAG_CALL, "Newstate", NO_EXTRA_FIELDS);
 	}
 
@@ -402,7 +337,7 @@ static struct snapshot_manager_event *channel_state_change(
 	return NULL;
 }
 
-static struct snapshot_manager_event *channel_newexten(
+static struct ast_manager_event_blob *channel_newexten(
 	struct ast_channel_snapshot *old_snapshot,
 	struct ast_channel_snapshot *new_snapshot)
 {
@@ -421,7 +356,7 @@ static struct snapshot_manager_event *channel_newexten(
 	}
 
 	/* DEPRECATED: Extension field deprecated in 12; remove in 14 */
-	return snapshot_manager_event_create(
+	return ast_manager_event_blob_create(
 		EVENT_FLAG_CALL, "Newexten",
 		"Extension: %s\r\n"
 		"Application: %s\r\n"
@@ -431,7 +366,7 @@ static struct snapshot_manager_event *channel_newexten(
 		new_snapshot->data);
 }
 
-static struct snapshot_manager_event *channel_new_callerid(
+static struct ast_manager_event_blob *channel_new_callerid(
 	struct ast_channel_snapshot *old_snapshot,
 	struct ast_channel_snapshot *new_snapshot)
 {
@@ -444,14 +379,14 @@ static struct snapshot_manager_event *channel_new_callerid(
 		return NULL;
 	}
 
-	return snapshot_manager_event_create(
+	return ast_manager_event_blob_create(
 		EVENT_FLAG_CALL, "NewCallerid",
 		"CID-CallingPres: %d (%s)\r\n",
 		new_snapshot->caller_pres,
 		ast_describe_caller_presentation(new_snapshot->caller_pres));
 }
 
-snapshot_monitor monitors[] = {
+channel_snapshot_monitor channel_monitors[] = {
 	channel_state_change,
 	channel_newexten,
 	channel_new_callerid
@@ -476,9 +411,9 @@ static void channel_snapshot_update(void *data, struct stasis_subscription *sub,
 	old_snapshot = stasis_message_data(update->old_snapshot);
 	new_snapshot = stasis_message_data(update->new_snapshot);
 
-	for (i = 0; i < ARRAY_LEN(monitors); ++i) {
-		RAII_VAR(struct snapshot_manager_event *, ev, NULL, ao2_cleanup);
-		ev = monitors[i](old_snapshot, new_snapshot);
+	for (i = 0; i < ARRAY_LEN(channel_monitors); ++i) {
+		RAII_VAR(struct ast_manager_event_blob *, ev, NULL, ao2_cleanup);
+		ev = channel_monitors[i](old_snapshot, new_snapshot);
 
 		if (!ev) {
 			continue;
diff --git a/main/parking.c b/main/parking.c
new file mode 100644
index 0000000000000000000000000000000000000000..2a5b72e61fa372103791ddfbdf6941b14124cb50
--- /dev/null
+++ b/main/parking.c
@@ -0,0 +1,191 @@
+/*
+ * 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 Parking Core
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/_private.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/pbx.h"
+#include "asterisk/bridging.h"
+#include "asterisk/parking.h"
+#include "asterisk/channel.h"
+
+/*! \brief Message type for parked calls */
+static struct stasis_message_type *parked_call_type;
+
+/*! \brief Topic for parking lots */
+static struct stasis_topic *parking_topic;
+
+/*! \brief Function Callback for handling blind transfers to park applications */
+static ast_park_blind_xfer_fn ast_park_blind_xfer_func = NULL;
+
+/*! \brief Function Callback for handling a bridge channel trying to park itself */
+static ast_bridge_channel_park_fn ast_bridge_channel_park_func = NULL;
+
+void ast_parking_stasis_init(void)
+{
+	parked_call_type = stasis_message_type_create("ast_parked_call");
+	parking_topic = stasis_topic_create("ast_parking");
+}
+
+void ast_parking_stasis_disable(void)
+{
+	ao2_cleanup(parked_call_type);
+	ao2_cleanup(parking_topic);
+	parked_call_type = NULL;
+	parking_topic = NULL;
+}
+
+struct stasis_topic *ast_parking_topic(void)
+{
+	return parking_topic;
+}
+
+struct stasis_message_type *ast_parked_call_type(void)
+{
+	return parked_call_type;
+}
+
+/*! \brief Destructor for parked_call_payload objects */
+static void parked_call_payload_destructor(void *obj)
+{
+	struct ast_parked_call_payload *park_obj = obj;
+
+	ao2_cleanup(park_obj->parkee);
+	ao2_cleanup(park_obj->parker);
+	ast_string_field_free_memory(park_obj);
+}
+
+struct ast_parked_call_payload *ast_parked_call_payload_create(enum ast_parked_call_event_type event_type,
+		struct ast_channel_snapshot *parkee_snapshot, struct ast_channel_snapshot *parker_snapshot,
+		struct ast_channel_snapshot *retriever_snapshot, const char *parkinglot,
+		unsigned int parkingspace, unsigned long int timeout,
+		unsigned long int duration)
+{
+	RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+
+	payload = ao2_alloc(sizeof(*payload), parked_call_payload_destructor);
+	if (!payload) {
+		return NULL;
+	}
+
+	if (ast_string_field_init(payload, 32)) {
+		return NULL;
+	}
+
+	payload->event_type = event_type;
+
+	ao2_ref(parkee_snapshot, +1);
+	payload->parkee = parkee_snapshot;
+
+	if (parker_snapshot) {
+		ao2_ref(parker_snapshot, +1);
+		payload->parker = parker_snapshot;
+	}
+
+	if (retriever_snapshot) {
+		ao2_ref(retriever_snapshot, +1);
+		payload->retriever = retriever_snapshot;
+	}
+
+	if (parkinglot) {
+		ast_string_field_set(payload, parkinglot, parkinglot);
+	}
+
+	payload->parkingspace = parkingspace;
+	payload->timeout = timeout;
+	payload->duration = duration;
+
+	/* Bump the ref count by one since RAII_VAR is going to eat one when we leave. */
+	ao2_ref(payload, +1);
+	return payload;
+}
+
+void ast_install_park_blind_xfer_func(ast_park_blind_xfer_fn park_blind_xfer_func)
+{
+	ast_park_blind_xfer_func = park_blind_xfer_func;
+}
+
+void ast_install_bridge_channel_park_func(ast_bridge_channel_park_fn bridge_channel_park_func)
+{
+	ast_bridge_channel_park_func = bridge_channel_park_func;
+}
+
+void ast_uninstall_park_blind_xfer_func(void)
+{
+	ast_park_blind_xfer_func = NULL;
+}
+
+void ast_uninstall_bridge_channel_park_func(void)
+{
+	ast_bridge_channel_park_func = NULL;
+}
+
+int ast_park_blind_xfer(struct ast_bridge *bridge, struct ast_bridge_channel *parker,
+		struct ast_exten *park_exten)
+{
+	static int warned = 0;
+	if (ast_park_blind_xfer_func) {
+		return ast_park_blind_xfer_func(bridge, parker, park_exten);
+	}
+
+	if (warned++ % 10 == 0) {
+		ast_verb(3, "%s attempted to blind transfer to a parking extension, but no parking blind transfer function is loaded.\n",
+			ast_channel_name(parker->chan));
+	}
+
+	return -1;
+}
+
+struct ast_exten *ast_get_parking_exten(const char *exten_str, struct ast_channel *chan, const char *context)
+{
+	struct ast_exten *exten;
+	struct pbx_find_info q = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
+	const char *app_at_exten;
+
+	ast_debug(4, "Checking if %s@%s is a parking exten\n", exten_str, context);
+	exten = pbx_find_extension(chan, NULL, &q, context, exten_str, 1, NULL, NULL,
+		E_MATCH);
+	if (!exten) {
+		return NULL;
+	}
+
+	app_at_exten = ast_get_extension_app(exten);
+	if (!app_at_exten || strcasecmp(PARK_APPLICATION, app_at_exten)) {
+		return NULL;
+	}
+
+	return exten;
+}
+
+void ast_bridge_channel_park(struct ast_bridge_channel *bridge_channel, const char *parkee_uuid, const char *parker_uuid, const char *app_data)
+{
+	/* Run installable function */
+	if (ast_bridge_channel_park_func) {
+		return ast_bridge_channel_park_func(bridge_channel, parkee_uuid, parker_uuid, app_data);
+	}
+}
diff --git a/main/pbx.c b/main/pbx.c
index 6359c056c2ca924b344e59a14f3693d280e503b0..8408048f2de48cf907e12fc1c96dc37d23c39b76 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -9395,8 +9395,12 @@ int ast_async_goto(struct ast_channel *chan, const char *context, const char *ex
 	} tmpvars = { 0, };
 
 	ast_channel_lock(chan);
-	if (ast_channel_pbx(chan)) { /* This channel is currently in the PBX */
-		ast_explicit_goto(chan, context, exten, priority + 1);
+	/* Channels in a bridge or running a PBX can be sent directly to the specified destination */
+	if (ast_channel_is_bridged(chan) || ast_channel_pbx(chan)) {
+		if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP)) {
+			priority += 1;
+		}
+		ast_explicit_goto(chan, context, exten, priority);
 		ast_softhangup_nolock(chan, AST_SOFTHANGUP_ASYNCGOTO);
 		ast_channel_unlock(chan);
 		return res;
@@ -9441,8 +9445,7 @@ int ast_async_goto(struct ast_channel *chan, const char *context, const char *ex
 
 	/* Masquerade into tmp channel */
 	if (ast_channel_masquerade(tmpchan, chan)) {
-		/* Failed to set up the masquerade.  It's probably chan_local
-		 * in the middle of optimizing itself out.  Sad. :( */
+		/* Failed to set up the masquerade. */
 		ast_hangup(tmpchan);
 		tmpchan = NULL;
 		res = -1;
diff --git a/main/rtp_engine.c b/main/rtp_engine.c
index b25cb95b36d58969301497cee38be25e1eb92f80..8e58f658d1f19c0d8f4355cbd610301d2ba5865b 100644
--- a/main/rtp_engine.c
+++ b/main/rtp_engine.c
@@ -928,497 +928,6 @@ struct ast_rtp_glue *ast_rtp_instance_get_glue(const char *type)
 	return glue;
 }
 
-static enum ast_bridge_result local_bridge_loop(struct ast_channel *c0, struct ast_channel *c1, struct ast_rtp_instance *instance0, struct ast_rtp_instance *instance1, int timeoutms, int flags, struct ast_frame **fo, struct ast_channel **rc, void *pvt0, void *pvt1)
-{
-	enum ast_bridge_result res = AST_BRIDGE_FAILED;
-	struct ast_channel *who = NULL, *other = NULL, *cs[3] = { NULL, };
-	struct ast_frame *fr = NULL;
-	struct timeval start;
-
-	/* Start locally bridging both instances */
-	if (instance0->engine->local_bridge && instance0->engine->local_bridge(instance0, instance1)) {
-		ast_debug(1, "Failed to locally bridge %s to %s, backing out.\n", ast_channel_name(c0), ast_channel_name(c1));
-		ast_channel_unlock(c0);
-		ast_channel_unlock(c1);
-		return AST_BRIDGE_FAILED_NOWARN;
-	}
-	if (instance1->engine->local_bridge && instance1->engine->local_bridge(instance1, instance0)) {
-		ast_debug(1, "Failed to locally bridge %s to %s, backing out.\n", ast_channel_name(c1), ast_channel_name(c0));
-		if (instance0->engine->local_bridge) {
-			instance0->engine->local_bridge(instance0, NULL);
-		}
-		ast_channel_unlock(c0);
-		ast_channel_unlock(c1);
-		return AST_BRIDGE_FAILED_NOWARN;
-	}
-
-	ast_channel_unlock(c0);
-	ast_channel_unlock(c1);
-
-	instance0->bridged = instance1;
-	instance1->bridged = instance0;
-
-	ast_poll_channel_add(c0, c1);
-
-	/* Hop into a loop waiting for a frame from either channel */
-	cs[0] = c0;
-	cs[1] = c1;
-	cs[2] = NULL;
-	start = ast_tvnow();
-	for (;;) {
-		int ms;
-		/* If the underlying formats have changed force this bridge to break */
-		if ((ast_format_cmp(ast_channel_rawreadformat(c0), ast_channel_rawwriteformat(c1)) == AST_FORMAT_CMP_NOT_EQUAL) ||
-			(ast_format_cmp(ast_channel_rawreadformat(c1), ast_channel_rawwriteformat(c0)) == AST_FORMAT_CMP_NOT_EQUAL)) {
-			ast_debug(1, "rtp-engine-local-bridge: Oooh, formats changed, backing out\n");
-			res = AST_BRIDGE_FAILED_NOWARN;
-			break;
-		}
-		/* Check if anything changed */
-		if ((ast_channel_tech_pvt(c0) != pvt0) ||
-		    (ast_channel_tech_pvt(c1) != pvt1) ||
-		    (ast_channel_masq(c0) || ast_channel_masqr(c0) || ast_channel_masq(c1) || ast_channel_masqr(c1)) ||
-		    (ast_channel_monitor(c0) || ast_channel_audiohooks(c0) || ast_channel_monitor(c1) || ast_channel_audiohooks(c1)) ||
-		    (!ast_framehook_list_is_empty(ast_channel_framehooks(c0)) || !ast_framehook_list_is_empty(ast_channel_framehooks(c1)))) {
-			ast_debug(1, "rtp-engine-local-bridge: Oooh, something is weird, backing out\n");
-			/* If a masquerade needs to happen we have to try to read in a frame so that it actually happens. Without this we risk being called again and going into a loop */
-			if ((ast_channel_masq(c0) || ast_channel_masqr(c0)) && (fr = ast_read(c0))) {
-				ast_frfree(fr);
-			}
-			if ((ast_channel_masq(c1) || ast_channel_masqr(c1)) && (fr = ast_read(c1))) {
-				ast_frfree(fr);
-			}
-			res = AST_BRIDGE_RETRY;
-			break;
-		}
-		/* Wait on a channel to feed us a frame */
-		ms = ast_remaining_ms(start, timeoutms);
-		if (!(who = ast_waitfor_n(cs, 2, &ms))) {
-			if (!ms) {
-				res = AST_BRIDGE_RETRY;
-				break;
-			}
-			ast_debug(2, "rtp-engine-local-bridge: Ooh, empty read...\n");
-			if (ast_check_hangup(c0) || ast_check_hangup(c1)) {
-				break;
-			}
-			continue;
-		}
-		/* Read in frame from channel */
-		fr = ast_read(who);
-		other = (who == c0) ? c1 : c0;
-		/* Depending on the frame we may need to break out of our bridge */
-		if (!fr || ((fr->frametype == AST_FRAME_DTMF_BEGIN || fr->frametype == AST_FRAME_DTMF_END) &&
-			    ((who == c0) && (flags & AST_BRIDGE_DTMF_CHANNEL_0)) |
-			    ((who == c1) && (flags & AST_BRIDGE_DTMF_CHANNEL_1)))) {
-			/* Record received frame and who */
-			*fo = fr;
-			*rc = who;
-			ast_debug(1, "rtp-engine-local-bridge: Ooh, got a %s\n", fr ? "digit" : "hangup");
-			res = AST_BRIDGE_COMPLETE;
-			break;
-		} else if ((fr->frametype == AST_FRAME_CONTROL) && !(flags & AST_BRIDGE_IGNORE_SIGS)) {
-			if ((fr->subclass.integer == AST_CONTROL_HOLD) ||
-			    (fr->subclass.integer == AST_CONTROL_UNHOLD) ||
-			    (fr->subclass.integer == AST_CONTROL_VIDUPDATE) ||
-			    (fr->subclass.integer == AST_CONTROL_SRCUPDATE) ||
-			    (fr->subclass.integer == AST_CONTROL_T38_PARAMETERS) ||
-			    (fr->subclass.integer == AST_CONTROL_UPDATE_RTP_PEER)) {
-				/* If we are going on hold, then break callback mode and P2P bridging */
-				if (fr->subclass.integer == AST_CONTROL_HOLD) {
-					if (instance0->engine->local_bridge) {
-						instance0->engine->local_bridge(instance0, NULL);
-					}
-					if (instance1->engine->local_bridge) {
-						instance1->engine->local_bridge(instance1, NULL);
-					}
-					instance0->bridged = NULL;
-					instance1->bridged = NULL;
-				} else if (fr->subclass.integer == AST_CONTROL_UNHOLD) {
-					if (instance0->engine->local_bridge) {
-						instance0->engine->local_bridge(instance0, instance1);
-					}
-					if (instance1->engine->local_bridge) {
-						instance1->engine->local_bridge(instance1, instance0);
-					}
-					instance0->bridged = instance1;
-					instance1->bridged = instance0;
-				}
-				/* Since UPDATE_BRIDGE_PEER is only used by the bridging code, don't forward it */
-				if (fr->subclass.integer != AST_CONTROL_UPDATE_RTP_PEER) {
-					ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
-				}
-				ast_frfree(fr);
-			} else if (fr->subclass.integer == AST_CONTROL_CONNECTED_LINE) {
-				if (ast_channel_connected_line_sub(who, other, fr, 1) &&
-					ast_channel_connected_line_macro(who, other, fr, other == c0, 1)) {
-					ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
-				}
-				ast_frfree(fr);
-			} else if (fr->subclass.integer == AST_CONTROL_REDIRECTING) {
-				if (ast_channel_redirecting_sub(who, other, fr, 1) &&
-					ast_channel_redirecting_macro(who, other, fr, other == c0, 1)) {
-					ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
-				}
-				ast_frfree(fr);
-			} else if (fr->subclass.integer == AST_CONTROL_PVT_CAUSE_CODE) {
-				ast_channel_hangupcause_hash_set(other, fr->data.ptr, fr->datalen);
-				ast_frfree(fr);
-			} else {
-				*fo = fr;
-				*rc = who;
-				ast_debug(1, "rtp-engine-local-bridge: Got a FRAME_CONTROL (%d) frame on channel %s\n", fr->subclass.integer, ast_channel_name(who));
-				res = AST_BRIDGE_COMPLETE;
-				break;
-			}
-		} else {
-			if ((fr->frametype == AST_FRAME_DTMF_BEGIN) ||
-			    (fr->frametype == AST_FRAME_DTMF_END) ||
-			    (fr->frametype == AST_FRAME_VOICE) ||
-			    (fr->frametype == AST_FRAME_VIDEO) ||
-			    (fr->frametype == AST_FRAME_IMAGE) ||
-			    (fr->frametype == AST_FRAME_HTML) ||
-			    (fr->frametype == AST_FRAME_MODEM) ||
-			    (fr->frametype == AST_FRAME_TEXT)) {
-				ast_write(other, fr);
-			}
-
-			ast_frfree(fr);
-		}
-		/* Swap priority */
-		cs[2] = cs[0];
-		cs[0] = cs[1];
-		cs[1] = cs[2];
-	}
-
-	/* Stop locally bridging both instances */
-	if (instance0->engine->local_bridge) {
-		instance0->engine->local_bridge(instance0, NULL);
-	}
-	if (instance1->engine->local_bridge) {
-		instance1->engine->local_bridge(instance1, NULL);
-	}
-
-	instance0->bridged = NULL;
-	instance1->bridged = NULL;
-
-	ast_poll_channel_del(c0, c1);
-
-	return res;
-}
-
-static enum ast_bridge_result remote_bridge_loop(struct ast_channel *c0,
-	struct ast_channel *c1,
-	struct ast_rtp_instance *instance0,
-	struct ast_rtp_instance *instance1,
-	struct ast_rtp_instance *vinstance0,
-	struct ast_rtp_instance *vinstance1,
-	struct ast_rtp_instance *tinstance0,
-	struct ast_rtp_instance *tinstance1,
-	struct ast_rtp_glue *glue0,
-	struct ast_rtp_glue *glue1,
-	struct ast_format_cap *cap0,
-	struct ast_format_cap *cap1,
-	int timeoutms,
-	int flags,
-	struct ast_frame **fo,
-	struct ast_channel **rc,
-	void *pvt0,
-	void *pvt1)
-{
-	enum ast_bridge_result res = AST_BRIDGE_FAILED;
-	struct ast_channel *who = NULL, *other = NULL, *cs[3] = { NULL, };
-	struct ast_format_cap *oldcap0 = ast_format_cap_dup(cap0);
-	struct ast_format_cap *oldcap1 = ast_format_cap_dup(cap1);
-	struct ast_sockaddr ac1 = {{0,}}, vac1 = {{0,}}, tac1 = {{0,}}, ac0 = {{0,}}, vac0 = {{0,}}, tac0 = {{0,}};
-	struct ast_sockaddr t1 = {{0,}}, vt1 = {{0,}}, tt1 = {{0,}}, t0 = {{0,}}, vt0 = {{0,}}, tt0 = {{0,}};
-	struct ast_frame *fr = NULL;
-	struct timeval start;
-
-	if (!oldcap0 || !oldcap1) {
-		ast_channel_unlock(c0);
-		ast_channel_unlock(c1);
-		goto remote_bridge_cleanup;
-	}
-	/* Test the first channel */
-	if (!(glue0->update_peer(c0, instance1, vinstance1, tinstance1, cap1, 0))) {
-		ast_rtp_instance_get_remote_address(instance1, &ac1);
-		if (vinstance1) {
-			ast_rtp_instance_get_remote_address(vinstance1, &vac1);
-		}
-		if (tinstance1) {
-			ast_rtp_instance_get_remote_address(tinstance1, &tac1);
-		}
-	} else {
-		ast_log(LOG_WARNING, "Channel '%s' failed to talk to '%s'\n", ast_channel_name(c0), ast_channel_name(c1));
-	}
-
-	/* Test the second channel */
-	if (!(glue1->update_peer(c1, instance0, vinstance0, tinstance0, cap0, 0))) {
-		ast_rtp_instance_get_remote_address(instance0, &ac0);
-		if (vinstance0) {
-			ast_rtp_instance_get_remote_address(instance0, &vac0);
-		}
-		if (tinstance0) {
-			ast_rtp_instance_get_remote_address(instance0, &tac0);
-		}
-	} else {
-		ast_log(LOG_WARNING, "Channel '%s' failed to talk to '%s'\n", ast_channel_name(c1), ast_channel_name(c0));
-	}
-
-	ast_channel_unlock(c0);
-	ast_channel_unlock(c1);
-
-	instance0->bridged = instance1;
-	instance1->bridged = instance0;
-
-	ast_poll_channel_add(c0, c1);
-
-	/* Go into a loop handling any stray frames that may come in */
-	cs[0] = c0;
-	cs[1] = c1;
-	cs[2] = NULL;
-	start = ast_tvnow();
-	for (;;) {
-		int ms;
-		/* Check if anything changed */
-		if ((ast_channel_tech_pvt(c0) != pvt0) ||
-		    (ast_channel_tech_pvt(c1) != pvt1) ||
-		    (ast_channel_masq(c0) || ast_channel_masqr(c0) || ast_channel_masq(c1) || ast_channel_masqr(c1)) ||
-		    (ast_channel_monitor(c0) || ast_channel_audiohooks(c0) || ast_channel_monitor(c1) || ast_channel_audiohooks(c1)) ||
-		    (!ast_framehook_list_is_empty(ast_channel_framehooks(c0)) || !ast_framehook_list_is_empty(ast_channel_framehooks(c1)))) {
-			ast_debug(1, "Oooh, something is weird, backing out\n");
-			res = AST_BRIDGE_RETRY;
-			break;
-		}
-
-		/* Check if they have changed their address */
-		ast_rtp_instance_get_remote_address(instance1, &t1);
-		if (vinstance1) {
-			ast_rtp_instance_get_remote_address(vinstance1, &vt1);
-		}
-		if (tinstance1) {
-			ast_rtp_instance_get_remote_address(tinstance1, &tt1);
-		}
-		if (glue1->get_codec) {
-			ast_format_cap_remove_all(cap1);
-			glue1->get_codec(c1, cap1);
-		}
-
-		ast_rtp_instance_get_remote_address(instance0, &t0);
-		if (vinstance0) {
-			ast_rtp_instance_get_remote_address(vinstance0, &vt0);
-		}
-		if (tinstance0) {
-			ast_rtp_instance_get_remote_address(tinstance0, &tt0);
-		}
-		if (glue0->get_codec) {
-			ast_format_cap_remove_all(cap0);
-			glue0->get_codec(c0, cap0);
-		}
-
-		if ((ast_sockaddr_cmp(&t1, &ac1)) ||
-		    (vinstance1 && ast_sockaddr_cmp(&vt1, &vac1)) ||
-		    (tinstance1 && ast_sockaddr_cmp(&tt1, &tac1)) ||
-		    (!ast_format_cap_identical(cap1, oldcap1))) {
-			char tmp_buf[512] = { 0, };
-			ast_debug(1, "Oooh, '%s' changed end address to %s (format %s)\n",
-				  ast_channel_name(c1), ast_sockaddr_stringify(&t1),
-				  ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), cap1));
-			ast_debug(1, "Oooh, '%s' changed end vaddress to %s (format %s)\n",
-				  ast_channel_name(c1), ast_sockaddr_stringify(&vt1),
-				  ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), cap1));
-			ast_debug(1, "Oooh, '%s' changed end taddress to %s (format %s)\n",
-				  ast_channel_name(c1), ast_sockaddr_stringify(&tt1),
-				  ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), cap1));
-			ast_debug(1, "Oooh, '%s' was %s/(format %s)\n",
-				  ast_channel_name(c1), ast_sockaddr_stringify(&ac1),
-				  ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), oldcap1));
-			ast_debug(1, "Oooh, '%s' was %s/(format %s)\n",
-				  ast_channel_name(c1), ast_sockaddr_stringify(&vac1),
-				  ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), oldcap1));
-			ast_debug(1, "Oooh, '%s' was %s/(format %s)\n",
-				  ast_channel_name(c1), ast_sockaddr_stringify(&tac1),
-				  ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), oldcap1));
-			if (glue0->update_peer(c0,
-					       ast_sockaddr_isnull(&t1)  ? NULL : instance1,
-					       ast_sockaddr_isnull(&vt1) ? NULL : vinstance1,
-					       ast_sockaddr_isnull(&tt1) ? NULL : tinstance1,
-					       cap1, 0)) {
-				ast_log(LOG_WARNING, "Channel '%s' failed to update to '%s'\n", ast_channel_name(c0), ast_channel_name(c1));
-			}
-			ast_sockaddr_copy(&ac1, &t1);
-			ast_sockaddr_copy(&vac1, &vt1);
-			ast_sockaddr_copy(&tac1, &tt1);
-			ast_format_cap_copy(oldcap1, cap1);
-		}
-		if ((ast_sockaddr_cmp(&t0, &ac0)) ||
-		    (vinstance0 && ast_sockaddr_cmp(&vt0, &vac0)) ||
-		    (tinstance0 && ast_sockaddr_cmp(&tt0, &tac0)) ||
-		    (!ast_format_cap_identical(cap0, oldcap0))) {
-			char tmp_buf[512] = { 0, };
-			ast_debug(1, "Oooh, '%s' changed end address to %s (format %s)\n",
-				  ast_channel_name(c0), ast_sockaddr_stringify(&t0),
-				  ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), cap0));
-			ast_debug(1, "Oooh, '%s' was %s/(format %s)\n",
-				  ast_channel_name(c0), ast_sockaddr_stringify(&ac0),
-				  ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), oldcap0));
-			if (glue1->update_peer(c1, t0.len ? instance0 : NULL,
-						vt0.len ? vinstance0 : NULL,
-						tt0.len ? tinstance0 : NULL,
-						cap0, 0)) {
-				ast_log(LOG_WARNING, "Channel '%s' failed to update to '%s'\n", ast_channel_name(c1), ast_channel_name(c0));
-			}
-			ast_sockaddr_copy(&ac0, &t0);
-			ast_sockaddr_copy(&vac0, &vt0);
-			ast_sockaddr_copy(&tac0, &tt0);
-			ast_format_cap_copy(oldcap0, cap0);
-		}
-
-		ms = ast_remaining_ms(start, timeoutms);
-		/* Wait for frame to come in on the channels */
-		if (!(who = ast_waitfor_n(cs, 2, &ms))) {
-			if (!ms) {
-				res = AST_BRIDGE_RETRY;
-				break;
-			}
-			ast_debug(1, "Ooh, empty read...\n");
-			if (ast_check_hangup(c0) || ast_check_hangup(c1)) {
-				break;
-			}
-			continue;
-		}
-		fr = ast_read(who);
-		other = (who == c0) ? c1 : c0;
-		if (!fr || ((fr->frametype == AST_FRAME_DTMF_BEGIN || fr->frametype == AST_FRAME_DTMF_END) &&
-			    (((who == c0) && (flags & AST_BRIDGE_DTMF_CHANNEL_0)) ||
-			     ((who == c1) && (flags & AST_BRIDGE_DTMF_CHANNEL_1))))) {
-			/* Break out of bridge */
-			*fo = fr;
-			*rc = who;
-			ast_debug(1, "Oooh, got a %s\n", fr ? "digit" : "hangup");
-			res = AST_BRIDGE_COMPLETE;
-			break;
-		} else if ((fr->frametype == AST_FRAME_CONTROL) && !(flags & AST_BRIDGE_IGNORE_SIGS)) {
-			if ((fr->subclass.integer == AST_CONTROL_HOLD) ||
-			    (fr->subclass.integer == AST_CONTROL_UNHOLD) ||
-			    (fr->subclass.integer == AST_CONTROL_VIDUPDATE) ||
-			    (fr->subclass.integer == AST_CONTROL_SRCUPDATE) ||
-			    (fr->subclass.integer == AST_CONTROL_T38_PARAMETERS) ||
-				(fr->subclass.integer == AST_CONTROL_UPDATE_RTP_PEER)) {
-				if (fr->subclass.integer == AST_CONTROL_HOLD) {
-					/* If we someone went on hold we want the other side to reinvite back to us */
-					if (who == c0) {
-						glue1->update_peer(c1, NULL, NULL, NULL, 0, 0);
-					} else {
-						glue0->update_peer(c0, NULL, NULL, NULL, 0, 0);
-					}
-				} else if (fr->subclass.integer == AST_CONTROL_UNHOLD ||
-					fr->subclass.integer == AST_CONTROL_UPDATE_RTP_PEER) {
-					/* If they went off hold they should go back to being direct, or if we have
-					 * been told to force a peer update, go ahead and do it. */
-					if (who == c0) {
-						glue1->update_peer(c1, instance0, vinstance0, tinstance0, cap0, 0);
-					} else {
-						glue0->update_peer(c0, instance1, vinstance1, tinstance1, cap1, 0);
-					}
-				}
-				/* Update local address information */
-				ast_rtp_instance_get_remote_address(instance0, &t0);
-				ast_sockaddr_copy(&ac0, &t0);
-				ast_rtp_instance_get_remote_address(instance1, &t1);
-				ast_sockaddr_copy(&ac1, &t1);
-				/* Update codec information */
-				if (glue0->get_codec && ast_channel_tech_pvt(c0)) {
-					ast_format_cap_remove_all(cap0);
-					ast_format_cap_remove_all(oldcap0);
-					glue0->get_codec(c0, cap0);
-					ast_format_cap_append(oldcap0, cap0);
-
-				}
-				if (glue1->get_codec && ast_channel_tech_pvt(c1)) {
-					ast_format_cap_remove_all(cap1);
-					ast_format_cap_remove_all(oldcap1);
-					glue1->get_codec(c1, cap1);
-					ast_format_cap_append(oldcap1, cap1);
-				}
-				/* Since UPDATE_BRIDGE_PEER is only used by the bridging code, don't forward it */
-				if (fr->subclass.integer != AST_CONTROL_UPDATE_RTP_PEER) {
-					ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
-				}
-				ast_frfree(fr);
-			} else if (fr->subclass.integer == AST_CONTROL_CONNECTED_LINE) {
-				if (ast_channel_connected_line_sub(who, other, fr, 1) &&
-					ast_channel_connected_line_macro(who, other, fr, other == c0, 1)) {
-					ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
-				}
-				ast_frfree(fr);
-			} else if (fr->subclass.integer == AST_CONTROL_REDIRECTING) {
-				if (ast_channel_redirecting_sub(who, other, fr, 1) &&
-					ast_channel_redirecting_macro(who, other, fr, other == c0, 1)) {
-					ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
-				}
-				ast_frfree(fr);
-			} else if (fr->subclass.integer == AST_CONTROL_PVT_CAUSE_CODE) {
-				ast_channel_hangupcause_hash_set(other, fr->data.ptr, fr->datalen);
-				ast_frfree(fr);
-			} else {
-				*fo = fr;
-				*rc = who;
-				ast_debug(1, "Got a FRAME_CONTROL (%d) frame on channel %s\n", fr->subclass.integer, ast_channel_name(who));
-				res = AST_BRIDGE_COMPLETE;
-				goto remote_bridge_cleanup;
-			}
-		} else {
-			if ((fr->frametype == AST_FRAME_DTMF_BEGIN) ||
-			    (fr->frametype == AST_FRAME_DTMF_END) ||
-			    (fr->frametype == AST_FRAME_VOICE) ||
-			    (fr->frametype == AST_FRAME_VIDEO) ||
-			    (fr->frametype == AST_FRAME_IMAGE) ||
-			    (fr->frametype == AST_FRAME_HTML) ||
-			    (fr->frametype == AST_FRAME_MODEM) ||
-			    (fr->frametype == AST_FRAME_TEXT)) {
-				ast_write(other, fr);
-			}
-			ast_frfree(fr);
-		}
-		/* Swap priority */
-		cs[2] = cs[0];
-		cs[0] = cs[1];
-		cs[1] = cs[2];
-	}
-
-	if (ast_test_flag(ast_channel_flags(c0), AST_FLAG_ZOMBIE)) {
-		ast_debug(1, "Channel '%s' Zombie cleardown from bridge\n", ast_channel_name(c0));
-	} else if (ast_channel_tech_pvt(c0) != pvt0) {
-		ast_debug(1, "Channel c0->'%s' pvt changed, in bridge with c1->'%s'\n", ast_channel_name(c0), ast_channel_name(c1));
-	} else if (glue0 != ast_rtp_instance_get_glue(ast_channel_tech(c0)->type)) {
-		ast_debug(1, "Channel c0->'%s' technology changed, in bridge with c1->'%s'\n", ast_channel_name(c0), ast_channel_name(c1));
-	} else if (glue0->update_peer(c0, NULL, NULL, NULL, 0, 0)) {
-		ast_log(LOG_WARNING, "Channel '%s' failed to break RTP bridge\n", ast_channel_name(c0));
-	}
-	if (ast_test_flag(ast_channel_flags(c1), AST_FLAG_ZOMBIE)) {
-		ast_debug(1, "Channel '%s' Zombie cleardown from bridge\n", ast_channel_name(c1));
-	} else if (ast_channel_tech_pvt(c1) != pvt1) {
-		ast_debug(1, "Channel c1->'%s' pvt changed, in bridge with c0->'%s'\n", ast_channel_name(c1), ast_channel_name(c0));
-	} else if (glue1 != ast_rtp_instance_get_glue(ast_channel_tech(c1)->type)) {
-		ast_debug(1, "Channel c1->'%s' technology changed, in bridge with c0->'%s'\n", ast_channel_name(c1), ast_channel_name(c0));
-	} else if (glue1->update_peer(c1, NULL, NULL, NULL, 0, 0)) {
-		ast_log(LOG_WARNING, "Channel '%s' failed to break RTP bridge\n", ast_channel_name(c1));
-	}
-
-	instance0->bridged = NULL;
-	instance1->bridged = NULL;
-
-	ast_poll_channel_del(c0, c1);
-
-remote_bridge_cleanup:
-	ast_format_cap_destroy(oldcap0);
-	ast_format_cap_destroy(oldcap1);
-
-	return res;
-}
-
 /*!
  * \brief Conditionally unref an rtp instance
  */
@@ -1430,184 +939,14 @@ static void unref_instance_cond(struct ast_rtp_instance **instance)
 	}
 }
 
-enum ast_bridge_result ast_rtp_instance_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms)
+struct ast_rtp_instance *ast_rtp_instance_get_bridged(struct ast_rtp_instance *instance)
 {
-	struct ast_rtp_instance *instance0 = NULL, *instance1 = NULL,
-			*vinstance0 = NULL, *vinstance1 = NULL,
-			*tinstance0 = NULL, *tinstance1 = NULL;
-	struct ast_rtp_glue *glue0, *glue1;
-	struct ast_sockaddr addr1 = { {0, }, }, addr2 = { {0, }, };
-	enum ast_rtp_glue_result audio_glue0_res = AST_RTP_GLUE_RESULT_FORBID, video_glue0_res = AST_RTP_GLUE_RESULT_FORBID;
-	enum ast_rtp_glue_result audio_glue1_res = AST_RTP_GLUE_RESULT_FORBID, video_glue1_res = AST_RTP_GLUE_RESULT_FORBID;
-	enum ast_bridge_result res = AST_BRIDGE_FAILED;
-	enum ast_rtp_dtmf_mode dmode;
-	struct ast_format_cap *cap0 = ast_format_cap_alloc_nolock();
-	struct ast_format_cap *cap1 = ast_format_cap_alloc_nolock();
-	int unlock_chans = 1;
-	int read_ptime0, read_ptime1, write_ptime0, write_ptime1;
-
-	if (!cap0 || !cap1) {
-		unlock_chans = 0;
-		goto done;
-	}
-
-	/* Lock both channels so we can look for the glue that binds them together */
-	ast_channel_lock_both(c0, c1);
-
-	/* Ensure neither channel got hungup during lock avoidance */
-	if (ast_check_hangup(c0) || ast_check_hangup(c1)) {
-		ast_log(LOG_WARNING, "Got hangup while attempting to bridge '%s' and '%s'\n", ast_channel_name(c0), ast_channel_name(c1));
-		goto done;
-	}
-
-	/* Grab glue that binds each channel to something using the RTP engine */
-	if (!(glue0 = ast_rtp_instance_get_glue(ast_channel_tech(c0)->type)) || !(glue1 = ast_rtp_instance_get_glue(ast_channel_tech(c1)->type))) {
-		ast_debug(1, "Can't find native functions for channel '%s'\n", glue0 ? ast_channel_name(c1) : ast_channel_name(c0));
-		goto done;
-	}
-
-	audio_glue0_res = glue0->get_rtp_info(c0, &instance0);
-	video_glue0_res = glue0->get_vrtp_info ? glue0->get_vrtp_info(c0, &vinstance0) : AST_RTP_GLUE_RESULT_FORBID;
-
-	audio_glue1_res = glue1->get_rtp_info(c1, &instance1);
-	video_glue1_res = glue1->get_vrtp_info ? glue1->get_vrtp_info(c1, &vinstance1) : AST_RTP_GLUE_RESULT_FORBID;
-
-	/* Apply any limitations on direct media bridging that may be present */
-	if (audio_glue0_res == audio_glue1_res && audio_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {
-		if (glue0->allow_rtp_remote && !(glue0->allow_rtp_remote(c0, instance1))) {
-			/* If the allow_rtp_remote indicates that remote isn't allowed, revert to local bridge */
-			audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
-		} else if (glue1->allow_rtp_remote && !(glue1->allow_rtp_remote(c1, instance0))) {
-			audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
-		}
-	}
-	if (video_glue0_res == video_glue1_res && video_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {
-		if (glue0->allow_vrtp_remote && !(glue0->allow_vrtp_remote(c0, instance1))) {
-			/* if the allow_vrtp_remote indicates that remote isn't allowed, revert to local bridge */
-			video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
-		} else if (glue1->allow_vrtp_remote && !(glue1->allow_vrtp_remote(c1, instance0))) {
-			video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
-		}
-	}
-
-	/* If we are carrying video, and both sides are not going to remotely bridge... fail the native bridge */
-	if (video_glue0_res != AST_RTP_GLUE_RESULT_FORBID && (audio_glue0_res != AST_RTP_GLUE_RESULT_REMOTE || video_glue0_res != AST_RTP_GLUE_RESULT_REMOTE)) {
-		audio_glue0_res = AST_RTP_GLUE_RESULT_FORBID;
-	}
-	if (video_glue1_res != AST_RTP_GLUE_RESULT_FORBID && (audio_glue1_res != AST_RTP_GLUE_RESULT_REMOTE || video_glue1_res != AST_RTP_GLUE_RESULT_REMOTE)) {
-		audio_glue1_res = AST_RTP_GLUE_RESULT_FORBID;
-	}
-
-	/* If any sort of bridge is forbidden just completely bail out and go back to generic bridging */
-	if (audio_glue0_res == AST_RTP_GLUE_RESULT_FORBID || audio_glue1_res == AST_RTP_GLUE_RESULT_FORBID) {
-		res = AST_BRIDGE_FAILED_NOWARN;
-		goto done;
-	}
-
-
-	/* If address families differ, force a local bridge */
-	ast_rtp_instance_get_remote_address(instance0, &addr1);
-	ast_rtp_instance_get_remote_address(instance1, &addr2);
-
-	if (addr1.ss.ss_family != addr2.ss.ss_family ||
-	   (ast_sockaddr_is_ipv4_mapped(&addr1) != ast_sockaddr_is_ipv4_mapped(&addr2))) {
-		audio_glue0_res = AST_RTP_GLUE_RESULT_LOCAL;
-		audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
-	}
-
-	/* If we need to get DTMF see if we can do it outside of the RTP stream itself */
-	dmode = ast_rtp_instance_dtmf_mode_get(instance0);
-	if ((flags & AST_BRIDGE_DTMF_CHANNEL_0) && dmode) {
-		res = AST_BRIDGE_FAILED_NOWARN;
-		goto done;
-	}
-	dmode = ast_rtp_instance_dtmf_mode_get(instance1);
-	if ((flags & AST_BRIDGE_DTMF_CHANNEL_1) && dmode) {
-		res = AST_BRIDGE_FAILED_NOWARN;
-		goto done;
-	}
-
-	/* If we have gotten to a local bridge make sure that both sides have the same local bridge callback and that they are DTMF compatible */
-	if ((audio_glue0_res == AST_RTP_GLUE_RESULT_LOCAL || audio_glue1_res == AST_RTP_GLUE_RESULT_LOCAL)
-		&& (instance0->engine->local_bridge != instance1->engine->local_bridge
-			|| (instance0->engine->dtmf_compatible && !instance0->engine->dtmf_compatible(c0, instance0, c1, instance1)))) {
-		res = AST_BRIDGE_FAILED_NOWARN;
-		goto done;
-	}
-
-	/* Make sure that codecs match */
-	if (glue0->get_codec){
-		glue0->get_codec(c0, cap0);
-	}
-	if (glue1->get_codec) {
-		glue1->get_codec(c1, cap1);
-	}
-	if (!ast_format_cap_is_empty(cap0) && !ast_format_cap_is_empty(cap1) && !ast_format_cap_has_joint(cap0, cap1)) {
-		char tmp0[256] = { 0, };
-		char tmp1[256] = { 0, };
-		ast_debug(1, "Channel codec0 = %s is not codec1 = %s, cannot native bridge in RTP.\n",
-			ast_getformatname_multiple(tmp0, sizeof(tmp0), cap0),
-			ast_getformatname_multiple(tmp1, sizeof(tmp1), cap1));
-		res = AST_BRIDGE_FAILED_NOWARN;
-		goto done;
-	}
-
-	read_ptime0 = (ast_codec_pref_getsize(&instance0->codecs.pref, ast_channel_rawreadformat(c0))).cur_ms;
-	read_ptime1 = (ast_codec_pref_getsize(&instance1->codecs.pref, ast_channel_rawreadformat(c1))).cur_ms;
-	write_ptime0 = (ast_codec_pref_getsize(&instance0->codecs.pref, ast_channel_rawwriteformat(c0))).cur_ms;
-	write_ptime1 = (ast_codec_pref_getsize(&instance1->codecs.pref, ast_channel_rawwriteformat(c1))).cur_ms;
-
-	if (read_ptime0 != write_ptime1 || read_ptime1 != write_ptime0) {
-		ast_debug(1, "Packetization differs between RTP streams (%d != %d or %d != %d). Cannot native bridge in RTP\n",
-				read_ptime0, write_ptime1, read_ptime1, write_ptime0);
-		res = AST_BRIDGE_FAILED_NOWARN;
-		goto done;
-	}
-
-	instance0->glue = glue0;
-	instance1->glue = glue1;
-	instance0->chan = c0;
-	instance1->chan = c1;
-
-	/* Depending on the end result for bridging either do a local bridge or remote bridge */
-	if (audio_glue0_res == AST_RTP_GLUE_RESULT_LOCAL || audio_glue1_res == AST_RTP_GLUE_RESULT_LOCAL) {
-		ast_verb(3, "Locally bridging %s and %s\n", ast_channel_name(c0), ast_channel_name(c1));
-		res = local_bridge_loop(c0, c1, instance0, instance1, timeoutms, flags, fo, rc, ast_channel_tech_pvt(c0), ast_channel_tech_pvt(c1));
-	} else {
-		ast_verb(3, "Remotely bridging %s and %s\n", ast_channel_name(c0), ast_channel_name(c1));
-		res = remote_bridge_loop(c0, c1, instance0, instance1, vinstance0, vinstance1,
-				tinstance0, tinstance1, glue0, glue1, cap0, cap1, timeoutms, flags,
-				fo, rc, ast_channel_tech_pvt(c0), ast_channel_tech_pvt(c1));
-	}
-
-	instance0->glue = NULL;
-	instance1->glue = NULL;
-	instance0->chan = NULL;
-	instance1->chan = NULL;
-
-	unlock_chans = 0;
-
-done:
-	if (unlock_chans) {
-		ast_channel_unlock(c0);
-		ast_channel_unlock(c1);
-	}
-	ast_format_cap_destroy(cap1);
-	ast_format_cap_destroy(cap0);
-
-	unref_instance_cond(&instance0);
-	unref_instance_cond(&instance1);
-	unref_instance_cond(&vinstance0);
-	unref_instance_cond(&vinstance1);
-	unref_instance_cond(&tinstance0);
-	unref_instance_cond(&tinstance1);
-
-	return res;
+	return instance->bridged;
 }
 
-struct ast_rtp_instance *ast_rtp_instance_get_bridged(struct ast_rtp_instance *instance)
+void ast_rtp_instance_set_bridged(struct ast_rtp_instance *instance, struct ast_rtp_instance *bridged)
 {
-	return instance->bridged;
+	instance->bridged = bridged;
 }
 
 void ast_rtp_instance_early_bridge_make_compatible(struct ast_channel *c0, struct ast_channel *c1)
diff --git a/main/stasis_bridging.c b/main/stasis_bridging.c
new file mode 100644
index 0000000000000000000000000000000000000000..2ee4fcfc169fff5748c24538d89642072708f91f
--- /dev/null
+++ b/main/stasis_bridging.c
@@ -0,0 +1,358 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Kinsey Moore <kmoore@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 Stasis Messages and Data Types for Bridge Objects
+ *
+ * \author Kinsey Moore <kmoore@digium.com>
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/astobj2.h"
+#include "asterisk/stasis.h"
+#include "asterisk/channel.h"
+#include "asterisk/stasis_bridging.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_technology.h"
+
+#define SNAPSHOT_CHANNELS_BUCKETS 13
+
+/*!
+ * @{ \brief Define bridge message types.
+ */
+STASIS_MESSAGE_TYPE_DEFN(ast_bridge_snapshot_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_bridge_merge_message_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_entered_bridge_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_left_bridge_type);
+/*! @} */
+
+/*! \brief Aggregate topic for bridge messages */
+static struct stasis_topic *bridge_topic_all;
+
+/*! \brief Caching aggregate topic for bridge snapshots */
+static struct stasis_caching_topic *bridge_topic_all_cached;
+
+/*! \brief Topic pool for individual bridge topics */
+static struct stasis_topic_pool *bridge_topic_pool;
+
+/*! \brief Destructor for bridge snapshots */
+static void bridge_snapshot_dtor(void *obj)
+{
+	struct ast_bridge_snapshot *snapshot = obj;
+	ast_string_field_free_memory(snapshot);
+	ao2_cleanup(snapshot->channels);
+	snapshot->channels = NULL;
+}
+
+struct ast_bridge_snapshot *ast_bridge_snapshot_create(struct ast_bridge *bridge)
+{
+	RAII_VAR(struct ast_bridge_snapshot *, snapshot, NULL, ao2_cleanup);
+	struct ast_bridge_channel *bridge_channel;
+
+	snapshot = ao2_alloc(sizeof(*snapshot), bridge_snapshot_dtor);
+	if (!snapshot || ast_string_field_init(snapshot, 128)) {
+		return NULL;
+	}
+
+	snapshot->channels = ast_str_container_alloc(SNAPSHOT_CHANNELS_BUCKETS);
+	if (!snapshot->channels) {
+		return NULL;
+	}
+
+	AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+		if (ast_str_container_add(snapshot->channels,
+				ast_channel_uniqueid(bridge_channel->chan))) {
+			return NULL;
+		}
+	}
+
+	ast_string_field_set(snapshot, uniqueid, bridge->uniqueid);
+	ast_string_field_set(snapshot, technology, bridge->technology->name);
+
+	snapshot->feature_flags = bridge->feature_flags;
+	snapshot->num_channels = bridge->num_channels;
+	snapshot->num_active = bridge->num_active;
+
+	ao2_ref(snapshot, +1);
+	return snapshot;
+}
+
+struct stasis_topic *ast_bridge_topic(struct ast_bridge *bridge)
+{
+	struct stasis_topic *bridge_topic = stasis_topic_pool_get_topic(bridge_topic_pool, bridge->uniqueid);
+	if (!bridge_topic) {
+		return ast_bridge_topic_all();
+	}
+	return bridge_topic;
+}
+
+struct stasis_topic *ast_bridge_topic_all(void)
+{
+	return bridge_topic_all;
+}
+
+struct stasis_caching_topic *ast_bridge_topic_all_cached(void)
+{
+	return bridge_topic_all_cached;
+}
+
+void ast_bridge_publish_state(struct ast_bridge *bridge)
+{
+	RAII_VAR(struct ast_bridge_snapshot *, snapshot, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+	ast_assert(bridge != NULL);
+
+	snapshot = ast_bridge_snapshot_create(bridge);
+	if (!snapshot) {
+		return;
+	}
+
+	msg = stasis_message_create(ast_bridge_snapshot_type(), snapshot);
+	if (!msg) {
+		return;
+	}
+
+	stasis_publish(ast_bridge_topic(bridge), msg);
+}
+
+static void bridge_publish_state_from_blob(struct ast_bridge_blob *obj)
+{
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+	ast_assert(obj != NULL);
+
+	msg = stasis_message_create(ast_bridge_snapshot_type(), obj->bridge);
+	if (!msg) {
+		return;
+	}
+
+	stasis_publish(stasis_topic_pool_get_topic(bridge_topic_pool, obj->bridge->uniqueid), msg);
+}
+
+/*! \brief Destructor for bridge merge messages */
+static void bridge_merge_message_dtor(void *obj)
+{
+	struct ast_bridge_merge_message *msg = obj;
+
+	ao2_cleanup(msg->to);
+	msg->to = NULL;
+	ao2_cleanup(msg->from);
+	msg->from = NULL;
+}
+
+/*! \brief Bridge merge message creation helper */
+static struct ast_bridge_merge_message *bridge_merge_message_create(struct ast_bridge *to, struct ast_bridge *from)
+{
+	RAII_VAR(struct ast_bridge_merge_message *, msg, NULL, ao2_cleanup);
+
+	msg = ao2_alloc(sizeof(*msg), bridge_merge_message_dtor);
+	if (!msg) {
+		return NULL;
+	}
+
+	msg->to = ast_bridge_snapshot_create(to);
+	if (!msg->to) {
+		return NULL;
+	}
+
+	msg->from = ast_bridge_snapshot_create(from);
+	if (!msg->from) {
+		return NULL;
+	}
+
+	ao2_ref(msg, +1);
+	return msg;
+}
+
+void ast_bridge_publish_merge(struct ast_bridge *to, struct ast_bridge *from)
+{
+	RAII_VAR(struct ast_bridge_merge_message *, merge_msg, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+	ast_assert(to != NULL);
+	ast_assert(from != NULL);
+
+	merge_msg = bridge_merge_message_create(to, from);
+	if (!merge_msg) {
+		return;
+	}
+
+	msg = stasis_message_create(ast_bridge_merge_message_type(), merge_msg);
+	if (!msg) {
+		return;
+	}
+
+	stasis_publish(ast_bridge_topic_all(), msg);
+}
+
+static void bridge_blob_dtor(void *obj)
+{
+	struct ast_bridge_blob *event = obj;
+	ao2_cleanup(event->bridge);
+	event->bridge = NULL;
+	ao2_cleanup(event->channel);
+	event->channel = NULL;
+	ast_json_unref(event->blob);
+	event->blob = NULL;
+}
+
+struct stasis_message *ast_bridge_blob_create(
+	struct stasis_message_type *message_type,
+	struct ast_bridge *bridge,
+	struct ast_channel *chan,
+	struct ast_json *blob)
+{
+	RAII_VAR(struct ast_bridge_blob *, obj, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+	obj = ao2_alloc(sizeof(*obj), bridge_blob_dtor);
+	if (!obj) {
+		return NULL;
+	}
+
+	if (bridge) {
+		obj->bridge = ast_bridge_snapshot_create(bridge);
+		if (obj->bridge == NULL) {
+			return NULL;
+		}
+	}
+
+	if (chan) {
+		obj->channel = ast_channel_snapshot_create(chan);
+		if (obj->channel == NULL) {
+			return NULL;
+		}
+	}
+
+	if (blob) {
+		obj->blob = ast_json_ref(blob);
+	}
+
+	msg = stasis_message_create(message_type, obj);
+	if (!msg) {
+		return NULL;
+	}
+
+	ao2_ref(msg, +1);
+	return msg;
+}
+
+const char *ast_bridge_blob_json_type(struct ast_bridge_blob *obj)
+{
+	if (obj == NULL) {
+		return NULL;
+	}
+
+	return ast_json_string_get(ast_json_object_get(obj->blob, "type"));
+}
+
+void ast_bridge_publish_enter(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+	msg = ast_bridge_blob_create(ast_channel_entered_bridge_type(), bridge, chan, NULL);
+	if (!msg) {
+		return;
+	}
+
+	/* enter blob first, then state */
+	stasis_publish(ast_bridge_topic(bridge), msg);
+	bridge_publish_state_from_blob(stasis_message_data(msg));
+}
+
+void ast_bridge_publish_leave(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+	msg = ast_bridge_blob_create(ast_channel_left_bridge_type(), bridge, chan, NULL);
+	if (!msg) {
+		return;
+	}
+
+	/* state first, then leave blob (opposite of enter, preserves nesting of events) */
+	bridge_publish_state_from_blob(stasis_message_data(msg));
+	stasis_publish(ast_bridge_topic(bridge), msg);
+}
+
+struct ast_json *ast_bridge_snapshot_to_json(const struct ast_bridge_snapshot *snapshot)
+{
+	RAII_VAR(struct ast_json *, json_chan, NULL, ast_json_unref);
+	int r = 0;
+
+	if (snapshot == NULL) {
+		return NULL;
+	}
+
+	json_chan = ast_json_object_create();
+	if (!json_chan) { ast_log(LOG_ERROR, "Error creating channel json object\n"); return NULL; }
+
+	r = ast_json_object_set(json_chan, "bridge-uniqueid", ast_json_string_create(snapshot->uniqueid));
+	if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; }
+	r = ast_json_object_set(json_chan, "bridge-technology", ast_json_string_create(snapshot->technology));
+	if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; }
+
+	return ast_json_ref(json_chan);
+}
+
+void ast_stasis_bridging_shutdown(void)
+{
+	STASIS_MESSAGE_TYPE_CLEANUP(ast_bridge_snapshot_type);
+	STASIS_MESSAGE_TYPE_CLEANUP(ast_bridge_merge_message_type);
+	STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_entered_bridge_type);
+	STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_left_bridge_type);
+	ao2_cleanup(bridge_topic_all);
+	bridge_topic_all = NULL;
+	bridge_topic_all_cached = stasis_caching_unsubscribe(bridge_topic_all_cached);
+	ao2_cleanup(bridge_topic_pool);
+	bridge_topic_pool = NULL;
+}
+
+/*! \brief snapshot ID getter for caching topic */
+static const char *bridge_snapshot_get_id(struct stasis_message *msg)
+{
+	struct ast_bridge_snapshot *snapshot;
+	if (stasis_message_type(msg) != ast_bridge_snapshot_type()) {
+		return NULL;
+	}
+	snapshot = stasis_message_data(msg);
+	return snapshot->uniqueid;
+}
+
+int ast_stasis_bridging_init(void)
+{
+	STASIS_MESSAGE_TYPE_INIT(ast_bridge_snapshot_type);
+	STASIS_MESSAGE_TYPE_INIT(ast_bridge_merge_message_type);
+	STASIS_MESSAGE_TYPE_INIT(ast_channel_entered_bridge_type);
+	STASIS_MESSAGE_TYPE_INIT(ast_channel_left_bridge_type);
+	bridge_topic_all = stasis_topic_create("ast_bridge_topic_all");
+	bridge_topic_all_cached = stasis_caching_topic_create(bridge_topic_all, bridge_snapshot_get_id);
+	bridge_topic_pool = stasis_topic_pool_create(bridge_topic_all);
+	return !bridge_topic_all
+		|| !bridge_topic_all_cached
+		|| !bridge_topic_pool ? -1 : 0;
+}
diff --git a/main/strings.c b/main/strings.c
index 47715e63ea0f9b05abbd7935fbd33e64cb0aeeb8..d004da2278f2504284064f3f8ebba85d591f17b8 100644
--- a/main/strings.c
+++ b/main/strings.c
@@ -160,3 +160,4 @@ char *__ast_str_helper2(struct ast_str **buf, ssize_t maxlen, const char *src, s
 	return (*buf)->__AST_STR_STR;
 }
 
+
diff --git a/res/Makefile b/res/Makefile
index 2ed719093fb9b65e33eda543fdbbfb2858d1967e..667e097e8c524a30993a5a720d9cb3b9ab754f7b 100644
--- a/res/Makefile
+++ b/res/Makefile
@@ -75,6 +75,10 @@ ael/pval.o: ael/pval.c
 clean::
 	rm -f snmp/*.[oi] ael/*.[oi] ais/*.[oi] stasis_http/*.[oi]
 	rm -f res_sip/*.[oi] stasis/*.[oi]
+	rm -f parking/*.o parking/*.i
+
+$(if $(filter res_parking,$(EMBEDDED_MODS)),modules.link,res_parking.so): $(subst .c,.o,$(wildcard parking/*.c))
+$(subst .c,.o,$(wildcard parking/*.c)): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_parking)
 
 # Dependencies for res_stasis_http_*.so are generated, so they're in this file
 include stasis_http.make
diff --git a/res/parking/parking_applications.c b/res/parking/parking_applications.c
new file mode 100644
index 0000000000000000000000000000000000000000..097329b936012ca91799de96b940971faa828b32
--- /dev/null
+++ b/res/parking/parking_applications.c
@@ -0,0 +1,801 @@
+/*
+ * 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 Call Parking Applications
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "res_parking.h"
+#include "asterisk/config.h"
+#include "asterisk/config_options.h"
+#include "asterisk/event.h"
+#include "asterisk/utils.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/features.h"
+#include "asterisk/module.h"
+#include "asterisk/app.h"
+#include "asterisk/say.h"
+#include "asterisk/features.h"
+#include "asterisk/bridging_basic.h"
+
+/*** DOCUMENTATION
+	<application name="Park" language="en_US">
+		<synopsis>
+			Park yourself.
+		</synopsis>
+		<syntax>
+			<parameter name="parking_lot_name">
+				<para>Specify in which parking lot to park a call.</para>
+				<para>The parking lot used is selected in the following order:</para>
+				<para>1) parking_lot_name option to this application</para>
+				<para>2) <variable>PARKINGLOT</variable> variable</para>
+				<para>3) <literal>CHANNEL(parkinglot)</literal> function
+				(Possibly preset by the channel driver.)</para>
+				<para>4) Default parking lot.</para>
+			</parameter>
+			<parameter name="options">
+				<para>A list of options for this parked call.</para>
+				<optionlist>
+					<option name="r">
+						<para>Send ringing instead of MOH to the parked call.</para>
+					</option>
+					<option name="R">
+						<para>Randomize the selection of a parking space.</para>
+					</option>
+					<option name="s">
+						<para>Silence announcement of the parking space number.</para>
+					</option>
+					<option name="c" argsep=",">
+						<argument name="context" required="false" />
+						<argument name="extension" required="false" />
+						<argument name="priority" required="true" />
+						<para>If the parking times out, go to this place in the dialplan
+							instead of where the parking lot defines the call should go.
+						</para>
+					</option>
+					<option name="t">
+						<argument name="duration" required="true" />
+						<para>Use a timeout of <literal>duration</literal> seconds instead
+							of the timeout specified by the parking lot.</para>
+					</option>
+				</optionlist>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Used to park yourself (typically in combination with an attended
+			transfer to know the parking space).</para>
+			<para>If you set the <variable>PARKINGEXTEN</variable> variable to a
+				parking space extension in the parking lot, Park() will attempt to park the
+				call on that extension. If the extension is already in use then execution
+				will continue at the next priority.
+			</para>
+		</description>
+		<see-also>
+			<ref type="application">ParkedCall</ref>
+		</see-also>
+	</application>
+
+	<application name="ParkedCall" language="en_US">
+		<synopsis>
+			Retrieve a parked call.
+		</synopsis>
+		<syntax>
+			<parameter name="parking_lot_name">
+				<para>Specify from which parking lot to retrieve a parked call.</para>
+				<para>The parking lot used is selected in the following order:</para>
+				<para>1) parking_lot_name option</para>
+				<para>2) <variable>PARKINGLOT</variable> variable</para>
+				<para>3) <literal>CHANNEL(parkinglot)</literal> function
+				(Possibly preset by the channel driver.)</para>
+				<para>4) Default parking lot.</para>
+			</parameter>
+			<parameter name="parking_space">
+				<para>Parking space to retrieve a parked call from.
+				If not provided then the first available parked call in the
+				parking lot will be retrieved.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Used to retrieve a parked call from a parking lot.</para>
+			<note>
+				<para>If a parking lot's parkext option is set, then Parking lots
+				will automatically create and manage dialplan extensions in
+				the parking lot context. If that is the case then you will not
+				need to manage parking extensions yourself, just include the
+				parking context of the parking lot.</para>
+			</note>
+		</description>
+		<see-also>
+			<ref type="application">Park</ref>
+		</see-also>
+	</application>
+
+	<application name="ParkAndAnnounce" language="en_US">
+		<synopsis>
+			Park and Announce.
+		</synopsis>
+		<syntax>
+			<parameter name="parking_lot_name">
+				<para>Specify in which parking lot to park a call.</para>
+				<para>The parking lot used is selected in the following order:</para>
+				<para>1) parking_lot_name option to this application</para>
+				<para>2) <variable>PARKINGLOT</variable> variable</para>
+				<para>3) <literal>CHANNEL(parkinglot)</literal> function
+				(Possibly preset by the channel driver.)</para>
+				<para>4) Default parking lot.</para>
+			</parameter>
+			<parameter name="options">
+				<para>A list of options for this parked call.</para>
+				<optionlist>
+					<option name="r">
+						<para>Send ringing instead of MOH to the parked call.</para>
+					</option>
+					<option name="R">
+						<para>Randomize the selection of a parking space.</para>
+					</option>
+					<option name="c" argsep=",">
+						<argument name="context" required="false" />
+						<argument name="extension" required="false" />
+						<argument name="priority" required="true" />
+						<para>If the parking times out, go to this place in the dialplan
+							instead of where the parking lot defines the call should go.
+						</para>
+					</option>
+					<option name="t">
+						<argument name="duration" required="true" />
+						<para>Use a timeout of <literal>duration</literal> seconds instead
+							of the timeout specified by the parking lot.</para>
+					</option>
+				</optionlist>
+			</parameter>
+			<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="dial" required="true">
+				<para>The app_dial style resource to call to make the
+				announcement. Console/dsp calls the console.</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>
+ ***/
+
+/* Park a call */
+
+enum park_args {
+	OPT_ARG_COMEBACK,
+	OPT_ARG_TIMEOUT,
+	OPT_ARG_ARRAY_SIZE /* Always the last element of the enum */
+};
+
+enum park_flags {
+	MUXFLAG_RINGING = (1 << 0),
+	MUXFLAG_RANDOMIZE = (1 << 1),
+	MUXFLAG_NOANNOUNCE = (1 << 2),
+	MUXFLAG_COMEBACK_OVERRIDE = (1 << 3),
+	MUXFLAG_TIMEOUT_OVERRIDE = (1 << 4),
+};
+
+AST_APP_OPTIONS(park_opts, {
+	AST_APP_OPTION('r', MUXFLAG_RINGING),
+	AST_APP_OPTION('R', MUXFLAG_RANDOMIZE),
+	AST_APP_OPTION('s', MUXFLAG_NOANNOUNCE),
+	AST_APP_OPTION_ARG('c', MUXFLAG_COMEBACK_OVERRIDE, OPT_ARG_COMEBACK),
+	AST_APP_OPTION_ARG('t', MUXFLAG_TIMEOUT_OVERRIDE, OPT_ARG_TIMEOUT),
+});
+
+static int apply_option_timeout (int *var, char *timeout_arg)
+{
+	if (ast_strlen_zero(timeout_arg)) {
+		ast_log(LOG_ERROR, "No duration value provided for the timeout ('t') option.\n");
+		return -1;
+	}
+
+	if (sscanf(timeout_arg, "%d", var) != 1 || *var < 0) {
+		ast_log(LOG_ERROR, "Duration value provided for timeout ('t') option must be 0 or greater.\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int park_app_parse_data(const char *data, int *disable_announce, int *use_ringing, int *randomize, int *time_limit, char **comeback_override, char **lot_name)
+{
+	char *parse;
+	struct ast_flags flags = { 0 };
+
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(lot_name);
+		AST_APP_ARG(options);
+		AST_APP_ARG(other);	/* Any remaining unused arguments */
+	);
+
+	parse = ast_strdupa(data);
+	AST_STANDARD_APP_ARGS(args, parse);
+
+	if (args.options) {
+		char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
+		ast_app_parse_options(park_opts, &flags, opts, args.options);
+		if (ast_test_flag(&flags, MUXFLAG_TIMEOUT_OVERRIDE)) {
+			if (apply_option_timeout(time_limit, opts[OPT_ARG_TIMEOUT])) {
+				return -1;
+			}
+		}
+
+		if (ast_test_flag(&flags, MUXFLAG_COMEBACK_OVERRIDE)) {
+			*comeback_override = ast_strdup(opts[OPT_ARG_COMEBACK]);
+		}
+
+		if (ast_test_flag(&flags, MUXFLAG_NOANNOUNCE)) {
+			if (disable_announce) {
+				*disable_announce = 1;
+			}
+		}
+
+		if (ast_test_flag(&flags, MUXFLAG_RINGING)) {
+			*use_ringing = 1;
+		}
+
+		if (ast_test_flag(&flags, MUXFLAG_RANDOMIZE)) {
+			*randomize = 1;
+		}
+	}
+
+	if (!ast_strlen_zero(args.lot_name)) {
+		*lot_name = ast_strdup(args.lot_name);
+	}
+
+	return 0;
+}
+
+static void park_common_datastore_destroy(void *data)
+{
+	struct park_common_datastore *datastore = data;
+	ast_free(datastore->parker_uuid);
+	ast_free(datastore->comeback_override);
+	ast_free(datastore);
+}
+
+static const struct ast_datastore_info park_common_info = {
+	.type = "park entry data",
+	.destroy = park_common_datastore_destroy,
+};
+
+static void wipe_park_common_datastore(struct ast_channel *chan)
+{
+	struct ast_datastore *datastore;
+
+	ast_channel_lock(chan);
+	datastore = ast_channel_datastore_find(chan, &park_common_info, NULL);
+	if (datastore) {
+		ast_channel_datastore_remove(chan, datastore);
+		ast_datastore_free(datastore);
+	}
+	ast_channel_unlock(chan);
+}
+
+static int setup_park_common_datastore(struct ast_channel *parkee, const char *parker_uuid, const char *comeback_override, int randomize, int time_limit, int silence_announce)
+{
+	struct ast_datastore *datastore = NULL;
+	struct park_common_datastore *park_datastore;
+
+	wipe_park_common_datastore(parkee);
+
+	if (!(datastore = ast_datastore_alloc(&park_common_info, NULL))) {
+		return -1;
+	}
+
+	if (!(park_datastore = ast_calloc(1, sizeof(*park_datastore)))) {
+		ast_datastore_free(datastore);
+		return -1;
+	}
+
+	park_datastore->parker_uuid = ast_strdup(parker_uuid);
+	park_datastore->randomize = randomize;
+	park_datastore->time_limit = time_limit;
+	park_datastore->silence_announce = silence_announce;
+
+	if (comeback_override) {
+		park_datastore->comeback_override = ast_strdup(comeback_override);
+	}
+
+
+	datastore->data = park_datastore;
+	ast_channel_lock(parkee);
+	ast_channel_datastore_add(parkee, datastore);
+	ast_channel_unlock(parkee);
+
+	return 0;
+}
+
+void get_park_common_datastore_data(struct ast_channel *parkee, char **parker_uuid, char **comeback_override,
+		int *randomize, int *time_limit, int *silence_announce)
+{
+	struct ast_datastore *datastore;
+	struct park_common_datastore *data;
+
+	ast_channel_lock(parkee);
+	if (!(datastore = ast_channel_datastore_find(parkee, &park_common_info, NULL))) {
+		ast_channel_unlock(parkee);
+		return;
+	}
+
+	data = datastore->data;
+
+	if (!data) {
+		/* This data should always be populated if this datastore was appended to the channel */
+		ast_assert(0);
+	}
+
+	*parker_uuid = ast_strdup(data->parker_uuid);
+	*randomize = data->randomize;
+	*time_limit = data->time_limit;
+	*silence_announce = data->silence_announce;
+
+	if (data->comeback_override) {
+		*comeback_override = ast_strdup(data->comeback_override);
+	}
+
+	ast_channel_unlock(parkee);
+}
+
+struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker, const char *app_data,
+		int *silence_announcements)
+{
+	int use_ringing = 0;
+	int randomize = 0;
+	int time_limit = -1;
+	char *lot_name;
+
+	struct ast_bridge *parking_bridge;
+	RAII_VAR(char *, comeback_override, NULL, ast_free);
+	RAII_VAR(char *, lot_name_app_arg, NULL, ast_free);
+	RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
+
+	if (app_data) {
+		park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, &comeback_override, &lot_name_app_arg);
+	}
+
+	lot_name = lot_name_app_arg;
+
+	/* If the name of the parking lot isn't specified in the arguments, find it based on the channel. */
+	if (ast_strlen_zero(lot_name)) {
+		ast_channel_lock(parker);
+		lot_name = ast_strdupa(find_channel_parking_lot_name(parker));
+		ast_channel_unlock(parker);
+	}
+
+	lot = parking_lot_find_by_name(lot_name);
+
+	if (!lot) {
+		ast_log(LOG_ERROR, "Could not find parking lot: '%s'\n", lot_name);
+		return NULL;
+	}
+
+	ao2_lock(lot);
+	parking_bridge = parking_lot_get_bridge(lot);
+	ao2_unlock(lot);
+
+	if (parking_bridge) {
+		/* Apply relevant bridge roles and such to the parking channel */
+		parking_channel_set_roles(parkee, lot, use_ringing);
+		setup_park_common_datastore(parkee, ast_channel_uniqueid(parker), comeback_override, randomize, time_limit,
+			silence_announcements ? *silence_announcements : 0);
+		return parking_bridge;
+	}
+
+	/* Couldn't get the parking bridge. Epic failure. */
+	return NULL;
+}
+
+/* XXX BUGBUG - determining the parker when transferred to deep park priority
+ *     Currently all parking by the park application is treated as calls parking themselves.
+ *     However, it's possible for calls to be transferred here when the Park application is
+ *     set after the first priority of an extension. In that case, there used to be a variable
+ *     (BLINDTRANSFER) set indicating which channel placed that call here.
+ *
+ *     If BLINDTRANSFER is set, this channel name will need to be referenced in Park events
+ *     generated by stasis. Ideally we would get a whole channel snapshot and use that for the
+ *     parker, but that would likely require applying the channel snapshot to a channel datastore
+ *     on all transfers. Alternatively just the name of the parking channel could be applied along
+ *     with an indication that it's dead.
+ */
+int park_app_exec(struct ast_channel *chan, const char *data)
+{
+	RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
+
+	struct ast_bridge_features chan_features;
+	int res;
+	int silence_announcements = 0;
+	const char *blind_transfer;
+
+	ast_channel_lock(chan);
+	if ((blind_transfer = pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"))) {
+		blind_transfer = ast_strdupa(blind_transfer);
+	}
+	ast_channel_unlock(chan);
+
+	/* Handle the common parking setup stuff */
+	if (!(parking_bridge = park_common_setup(chan, chan, data, &silence_announcements))) {
+		if (!silence_announcements && !blind_transfer) {
+			ast_stream_and_wait(chan, "pbx-parkingfailed", "");
+		}
+		return 0;
+	}
+
+	/* Initialize bridge features for the channel. */
+	res = ast_bridge_features_init(&chan_features);
+	if (res) {
+		ast_bridge_features_cleanup(&chan_features);
+		return -1;
+	}
+
+	/* Now for the fun part... park it! */
+	ast_bridge_join(parking_bridge, chan, NULL, &chan_features, NULL, 0);
+
+	/*
+	 * If the bridge was broken for a hangup that isn't real, then
+	 * don't run the h extension, because the channel isn't really
+	 * hung up.  This should only happen with AST_SOFTHANGUP_ASYNCGOTO.
+	 */
+	res = -1;
+
+	ast_channel_lock(chan);
+	if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
+		res = 0;
+	}
+	ast_channel_unlock(chan);
+
+	ast_bridge_features_cleanup(&chan_features);
+
+	return res;
+}
+
+/* Retrieve a parked call */
+
+int parked_call_app_exec(struct ast_channel *chan, const char *data)
+{
+	RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
+	RAII_VAR(struct parked_user *, pu, NULL, ao2_cleanup); /* Parked user being retrieved */
+	RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
+	struct ast_bridge *retrieval_bridge;
+	int res;
+	int target_space = -1;
+	struct ast_bridge_features chan_features;
+	char *parse;
+	char *lot_name;
+
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(lot_name);
+		AST_APP_ARG(parking_space);
+		AST_APP_ARG(other);	/* Any remaining unused arguments */
+	);
+
+	parse = ast_strdupa(data);
+	AST_STANDARD_APP_ARGS(args, parse);
+
+	/* Answer the channel if needed */
+	if (ast_channel_state(chan) != AST_STATE_UP) {
+		ast_answer(chan);
+	}
+
+	lot_name = args.lot_name;
+
+	/* If the name of the parking lot isn't in the arguments, find it based on the channel. */
+	if (ast_strlen_zero(lot_name)) {
+		ast_channel_lock(chan);
+		lot_name = ast_strdupa(find_channel_parking_lot_name(chan));
+		ast_channel_unlock(chan);
+	}
+
+	lot = parking_lot_find_by_name(lot_name);
+
+	if (!lot) {
+		ast_log(LOG_ERROR, "Could not find the requested parking lot\n");
+		ast_stream_and_wait(chan, "pbx-invalidpark", "");
+		return -1;
+	}
+
+	if (!ast_strlen_zero(args.parking_space)) {
+		if (sscanf(args.parking_space, "%d", &target_space) != 1 || target_space < 0) {
+			ast_stream_and_wait(chan, "pbx-invalidpark", "");
+			ast_log(LOG_ERROR, "value '%s' for parking_space argument is invalid. Must be an integer greater than 0.\n", args.parking_space);
+			return -1;
+		}
+	}
+
+	/* Attempt to get the parked user from the parking lot */
+	pu = parking_lot_retrieve_parked_user(lot, target_space);
+	if (!pu) {
+		ast_stream_and_wait(chan, "pbx-invalidpark", "");
+		return -1;
+	}
+
+	/* The parked call needs to know who is retrieving it before we move it out of the parking bridge */
+	pu->retriever = ast_channel_snapshot_create(chan);
+
+	/* Create bridge */
+	retrieval_bridge = ast_bridge_basic_new();
+	if (!retrieval_bridge) {
+		return -1;
+	}
+
+	/* Move the parkee into the new bridge */
+	if (ast_bridge_move(retrieval_bridge, lot->parking_bridge, pu->chan, NULL, 0)) {
+		ast_bridge_destroy(retrieval_bridge);
+		return -1;
+	}
+
+	/* Initialize our bridge features */
+	res = ast_bridge_features_init(&chan_features);
+	if (res) {
+		ast_bridge_destroy(retrieval_bridge);
+		ast_bridge_features_cleanup(&chan_features);
+		return -1;
+	}
+
+	/* Set the features */
+	parked_call_retrieve_enable_features(chan, lot, AST_FEATURE_FLAG_BYCALLER);
+
+	/* If the parkedplay option is set for the caller to hear, play that tone now. */
+	if (lot->cfg->parkedplay & AST_FEATURE_FLAG_BYCALLER) {
+		ast_stream_and_wait(chan, lot->cfg->courtesytone, NULL);
+	}
+
+	/* Now we should try to join the new bridge ourselves... */
+	ast_bridge_join(retrieval_bridge, chan, NULL, &chan_features, NULL, 1);
+
+	ast_bridge_features_cleanup(&chan_features);
+
+	return 0;
+}
+
+struct park_announce_subscription_data {
+	char *parkee_uuid;
+	char *dial_string;
+	char *announce_string;
+};
+
+static void park_announce_subscription_data_destroy(void *data)
+{
+	struct park_announce_subscription_data *pa_data = data;
+	ast_free(pa_data->parkee_uuid);
+	ast_free(pa_data->dial_string);
+	ast_free(pa_data->announce_string);
+	ast_free(pa_data);
+}
+
+static struct park_announce_subscription_data *park_announce_subscription_data_create(const char *parkee_uuid,
+		const char *dial_string,
+		const char *announce_string)
+{
+	struct park_announce_subscription_data *pa_data;
+
+	if (!(pa_data = ast_calloc(1, sizeof(*pa_data)))) {
+		return NULL;
+	}
+
+	if (!(pa_data->parkee_uuid = ast_strdup(parkee_uuid))
+		|| !(pa_data->dial_string = ast_strdup(dial_string))
+		|| !(pa_data->announce_string = ast_strdup(announce_string))) {
+		park_announce_subscription_data_destroy(pa_data);
+		return NULL;
+	}
+
+	return pa_data;
+}
+
+static void announce_to_dial(char *dial_string, char *announce_string, int parkingspace, struct ast_channel_snapshot *parkee_snapshot)
+{
+	struct ast_channel *dchan;
+	struct outgoing_helper oh = { 0, };
+	int outstate;
+	struct ast_format_cap *cap_slin = ast_format_cap_alloc_nolock();
+	char buf[13];
+	char *dial_tech;
+	char *cur_announce;
+	struct ast_format tmpfmt;
+
+	dial_tech = strsep(&dial_string, "/");
+	ast_verb(3, "Dial Tech,String: (%s,%s)\n", dial_tech, dial_string);
+
+	if (!cap_slin) {
+		ast_log(LOG_WARNING, "PARK: Failed to announce park.\n");
+		goto announce_cleanup;
+	}
+	ast_format_cap_add(cap_slin, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0));
+
+	snprintf(buf, sizeof(buf), "%d", parkingspace);
+	oh.vars = ast_variable_new("_PARKEDAT", buf, "");
+	dchan = __ast_request_and_dial(dial_tech, cap_slin, NULL, dial_string, 30000,
+		&outstate,
+		parkee_snapshot->caller_number,
+		parkee_snapshot->caller_name,
+		&oh);
+
+	ast_variables_destroy(oh.vars);
+	if (!dchan) {
+		ast_log(LOG_WARNING, "PARK: Unable to allocate announce channel.\n");
+		goto announce_cleanup;
+	}
+
+	ast_verb(4, "Announce Template: %s\n", announce_string);
+
+	for (cur_announce = strsep(&announce_string, ":"); cur_announce; cur_announce = strsep(&announce_string, ":")) {
+		ast_verb(4, "Announce:%s\n", cur_announce);
+		if (!strcmp(cur_announce, "PARKED")) {
+			ast_say_digits(dchan, parkingspace, "", ast_channel_language(dchan));
+		} else {
+			int dres = ast_streamfile(dchan, cur_announce, ast_channel_language(dchan));
+			if (!dres) {
+				dres = ast_waitstream(dchan, "");
+			} else {
+				ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", cur_announce, ast_channel_name(dchan));
+			}
+		}
+	}
+
+	ast_stopstream(dchan);
+	ast_hangup(dchan);
+
+announce_cleanup:
+	cap_slin = ast_format_cap_destroy(cap_slin);
+}
+
+static void park_announce_update_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+{
+	struct park_announce_subscription_data *pa_data = data;
+	char *dial_string = pa_data->dial_string;
+
+	struct ast_parked_call_payload *payload = stasis_message_data(message);
+
+	if (stasis_subscription_final_message(sub, message)) {
+		park_announce_subscription_data_destroy(data);
+		return;
+	}
+
+	if (payload->event_type != PARKED_CALL) {
+		/* We are only concerned with calls parked */
+		return;
+	}
+
+	if (strcmp(payload->parkee->uniqueid, pa_data->parkee_uuid)) {
+		/* We are only concerned with the parkee we are subscribed for. */
+		return;
+	}
+
+	if (!ast_strlen_zero(dial_string)) {
+		announce_to_dial(dial_string, pa_data->announce_string, payload->parkingspace, payload->parkee);
+	}
+
+	*dial_string = '\0'; /* If we observe this dial string on a second pass, we don't want to do anything with it. */
+}
+
+int park_and_announce_app_exec(struct ast_channel *chan, const char *data)
+{
+	struct ast_bridge_features chan_features;
+	char *parse;
+	int res;
+	int silence_announcements = 1;
+
+	struct stasis_subscription *parking_subscription;
+	struct park_announce_subscription_data *pa_data;
+
+	RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
+
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(lot_name);
+		AST_APP_ARG(options);
+		AST_APP_ARG(announce_template);
+		AST_APP_ARG(dial);
+		AST_APP_ARG(others);/* Any remaining unused arguments */
+	);
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_ERROR, "ParkAndAnnounce has required arguments. No arguments were provided.\n");
+		return -1;
+	}
+
+	parse = ast_strdupa(data);
+	AST_STANDARD_APP_ARGS(args, parse);
+
+	if (ast_strlen_zero(args.announce_template)) {
+		/* improperly configured arguments for the application */
+		ast_log(LOG_ERROR, "ParkAndAnnounce requires the announce_template argument.\n");
+		return -1;
+	}
+
+	if (ast_strlen_zero(args.dial)) {
+		/* improperly configured arguments */
+		ast_log(LOG_ERROR, "ParkAndAnnounce requires the dial argument.\n");
+		return -1;
+	}
+
+	if (!strchr(args.dial, '/')) {
+		ast_log(LOG_ERROR, "ParkAndAnnounce dial string '%s' is improperly formed.\n", args.dial);
+		return -1;
+	}
+
+	/* Handle the common parking setup stuff */
+	if (!(parking_bridge = park_common_setup(chan, chan, data, &silence_announcements))) {
+		return 0;
+	}
+
+	/* Initialize bridge features for the channel. */
+	res = ast_bridge_features_init(&chan_features);
+	if (res) {
+		ast_bridge_features_cleanup(&chan_features);
+		return -1;
+	}
+
+	/* subscribe to the parking message so that we can announce once it is parked */
+	pa_data = park_announce_subscription_data_create(ast_channel_uniqueid(chan), args.dial, args.announce_template);
+	if (!pa_data) {
+		return -1;
+	}
+
+	if (!(parking_subscription = stasis_subscribe(ast_parking_topic(), park_announce_update_cb, pa_data))) {
+		/* Failed to create subscription */
+		park_announce_subscription_data_destroy(pa_data);
+		return -1;
+	}
+
+	/* Now for the fun part... park it! */
+	ast_bridge_join(parking_bridge, chan, NULL, &chan_features, NULL, 0);
+
+	/* Toss the subscription since we aren't bridged at this point. */
+	stasis_unsubscribe(parking_subscription);
+
+	/*
+	 * If the bridge was broken for a hangup that isn't real, then
+	 * don't run the h extension, because the channel isn't really
+	 * hung up.  This should only happen with AST_SOFTHANGUP_ASYNCGOTO.
+	 */
+	res = -1;
+
+	ast_channel_lock(chan);
+	if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
+		res = 0;
+	}
+	ast_channel_unlock(chan);
+
+	ast_bridge_features_cleanup(&chan_features);
+
+	return res;
+}
diff --git a/res/parking/parking_bridge.c b/res/parking/parking_bridge.c
new file mode 100644
index 0000000000000000000000000000000000000000..b9ceb5203d9577d4c746e5b1a44e40e2c19d0d8c
--- /dev/null
+++ b/res/parking/parking_bridge.c
@@ -0,0 +1,421 @@
+/*
+ * 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 Parking Bridge Class
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+#include "asterisk/logger.h"
+#include "res_parking.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/features.h"
+#include "asterisk/say.h"
+
+struct ast_bridge_parking
+{
+	struct ast_bridge base;
+
+	/* private stuff for parking */
+	struct parking_lot *lot;
+};
+
+/*!
+ * \internal
+ * \brief ast_bridge parking class destructor
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \note XXX Stub... and it might go unused.
+ *
+ * \return Nothing
+ */
+static void bridge_parking_destroy(struct ast_bridge_parking *self)
+{
+	ast_bridge_base_v_table.destroy(&self->base);
+}
+
+static void bridge_parking_dissolving(struct ast_bridge_parking *self)
+{
+	struct parking_lot *lot = self->lot;
+
+	/* Unlink the parking bridge from the parking lot that owns it */
+	lot->parking_bridge = NULL;
+	ao2_ref(lot, -1);
+
+	/* Disassociate the bridge from the parking lot as well. */
+	self->lot = NULL;
+
+	ast_bridge_base_v_table.dissolving(&self->base);
+}
+
+static void destroy_parked_user(void *obj)
+{
+	struct parked_user *pu = obj;
+
+	ao2_cleanup(pu->lot);
+	pu->lot = NULL;
+
+	ao2_cleanup(pu->parker);
+	pu->parker = NULL;
+}
+
+/*!
+ * \internal
+ * \since 12
+ * \brief Construct a parked_user struct assigned to the specified parking lot
+ *
+ * \param lot The parking lot we are assigning the user to
+ * \param parkee The channel being parked
+ * \param parker The channel performing the park operation (may be the same channel)
+ * \param use_random_space if true, prioritize using a random parking space instead
+ *        of ${PARKINGEXTEN} and/or automatic assignment from the parking lot
+ * \param time_limit If using a custom timeout, this should be supplied so that the
+ *        parked_user struct can provide this information for manager events. If <0,
+ *        use the parking lot limit instead.
+ *
+ * \retval NULL on failure
+ * \retval reference to the parked user
+ *
+ * \note ao2_cleanup this reference when you are done using it or you'll cause leaks.
+ */
+static struct parked_user *generate_parked_user(struct parking_lot *lot, struct ast_channel *chan, struct ast_channel *parker, int use_random_space, int time_limit)
+{
+	struct parked_user *new_parked_user;
+	int preferred_space = -1; /* Initialize to use parking lot defaults */
+	int parking_space;
+	const char *parkingexten;
+
+	if (lot->mode == PARKINGLOT_DISABLED) {
+		ast_log(LOG_NOTICE, "Tried to park in a parking lot that is no longer able to be parked to.\n");
+		return NULL;
+	}
+
+	new_parked_user = ao2_alloc(sizeof(*new_parked_user), destroy_parked_user);
+	if (!new_parked_user) {
+		return NULL;
+	}
+
+
+	if (use_random_space) {
+		preferred_space = ast_random() % (lot->cfg->parking_stop - lot->cfg->parking_start + 1);
+		preferred_space += lot->cfg->parking_start;
+	} else {
+		ast_channel_lock(chan);
+		if ((parkingexten = pbx_builtin_getvar_helper(chan, "PARKINGEXTEN"))) {
+			parkingexten = ast_strdupa(parkingexten);
+		}
+		ast_channel_unlock(chan);
+
+		if (!ast_strlen_zero(parkingexten)) {
+			if (sscanf(parkingexten, "%30d", &preferred_space) != 1 || preferred_space <= 0) {
+				ast_log(LOG_WARNING, "PARKINGEXTEN='%s' is not a valid parking space.\n", parkingexten);
+				ao2_ref(new_parked_user, -1);
+				return NULL;
+			}
+		}
+	}
+
+
+	/* We need to keep the lot locked between parking_lot_get_space and actually placing it in the lot. Or until we decide not to. */
+	ao2_lock(lot);
+
+	parking_space = parking_lot_get_space(lot, preferred_space);
+	if (parking_space == -1) {
+		ast_log(LOG_NOTICE, "Failed to get parking space in lot '%s'. All full.\n", lot->name);
+		ao2_ref(new_parked_user, -1);
+		ao2_unlock(lot);
+		return NULL;
+	}
+
+	lot->next_space = ((parking_space + 1) - lot->cfg->parking_start) % (lot->cfg->parking_stop - lot->cfg->parking_start + 1) + lot->cfg->parking_start;
+	new_parked_user->chan = chan;
+	new_parked_user->parking_space = parking_space;
+
+	/* Have the parked user take a reference to the parking lot. This reference should be immutable and released at destruction */
+	new_parked_user->lot = lot;
+	ao2_ref(lot, +1);
+
+	new_parked_user->start = ast_tvnow();
+	new_parked_user->time_limit = (time_limit >= 0) ? time_limit : lot->cfg->parkingtime;
+	new_parked_user->parker = ast_channel_snapshot_create(parker);
+	if (!new_parked_user->parker) {
+		ao2_ref(new_parked_user, -1);
+		ao2_unlock(lot);
+		return NULL;
+	}
+
+	/* Insert into the parking lot's parked user list. We can unlock the lot now. */
+	ao2_link(lot->parked_users, new_parked_user);
+	ao2_unlock(lot);
+
+	return new_parked_user;
+}
+
+/* TODO CEL events for parking */
+
+/*!
+ * \internal
+ * \brief ast_bridge parking push method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon
+ * \param bridge_channel Bridge channel to push
+ * \param swap Bridge channel to swap places with if not NULL
+ *
+ * \note On entry, self is already locked
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int bridge_parking_push(struct ast_bridge_parking *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
+{
+	struct parked_user *pu;
+	int randomize = 0;
+	int time_limit = -1;
+	int silence = 0;
+	const char *blind_transfer;
+	RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup);
+	RAII_VAR(char *, parker_uuid, NULL, ast_free);
+	RAII_VAR(char *, comeback_override, NULL, ast_free);
+
+	ast_bridge_base_v_table.push(&self->base, bridge_channel, swap);
+
+	/* Answer the channel if needed */
+	if (ast_channel_state(bridge_channel->chan) != AST_STATE_UP) {
+		ast_answer(bridge_channel->chan);
+	}
+
+	if (swap) {
+		ao2_lock(swap);
+		pu = swap->bridge_pvt;
+		if (!pu) {
+			/* This should be impossible since the only way a channel can enter in the first place
+			 * is if it has a parked user associated with it */
+			publish_parked_call_failure(bridge_channel->chan);
+			ao2_unlock(swap);
+			return -1;
+		}
+
+		/* Give the swap channel's parked user reference to the incoming channel */
+		pu->chan = bridge_channel->chan;
+		bridge_channel->bridge_pvt = pu;
+		swap->bridge_pvt = NULL;
+
+		/* TODO Add a parked call swap message type to relay information about parked channel swaps */
+
+		ao2_unlock(swap);
+
+		parking_set_duration(bridge_channel->features, pu);
+
+		return 0;
+	}
+
+	get_park_common_datastore_data(bridge_channel->chan, &parker_uuid, &comeback_override, &randomize, &time_limit, &silence);
+	parker = ast_channel_get_by_name(parker_uuid);
+
+	/* If the parker and the parkee are the same channel pointer, then the channel entered using
+	 * the park application. It's possible the the blindtransfer channel is still alive (particularly
+	 * when a multichannel bridge is parked), so try to get the real parker if possible. */
+	ast_channel_lock(bridge_channel->chan);
+	blind_transfer = S_OR(pbx_builtin_getvar_helper(bridge_channel->chan, "BLINDTRANSFER"),
+		ast_channel_name(bridge_channel->chan));
+	if (blind_transfer) {
+		blind_transfer = ast_strdupa(blind_transfer);
+	}
+	ast_channel_unlock(bridge_channel->chan);
+
+	if (parker == bridge_channel->chan) {
+		struct ast_channel *real_parker = ast_channel_get_by_name(blind_transfer);
+		if (real_parker) {
+			ao2_cleanup(parker);
+			parker = real_parker;
+		}
+	}
+
+	if (!parker) {
+		return -1;
+	}
+
+	pu = generate_parked_user(self->lot, bridge_channel->chan, parker, randomize, time_limit);
+	if (!pu) {
+		publish_parked_call_failure(bridge_channel->chan);
+		return -1;
+	}
+
+	/* If a comeback_override was provided, set it for the parked user's comeback string. */
+	if (comeback_override) {
+		strncpy(pu->comeback, comeback_override, sizeof(pu->comeback));
+		pu->comeback[sizeof(pu->comeback) - 1] = '\0';
+	}
+
+	/* Generate ParkedCall Stasis Message */
+	publish_parked_call(pu, PARKED_CALL);
+
+	/* If the parkee and the parker are the same and silence_announce isn't set, play the announcement to the parkee */
+	if (!strcmp(blind_transfer, ast_channel_name(bridge_channel->chan)) && !silence) {
+		char saynum_buf[16];
+		snprintf(saynum_buf, sizeof(saynum_buf), "%u %u", 0, pu->parking_space);
+		ast_bridge_channel_queue_playfile(bridge_channel, say_parking_space, saynum_buf, NULL);
+	}
+
+	/* Apply parking duration limits */
+	parking_set_duration(bridge_channel->features, pu);
+
+	/* Set this to the bridge pvt so that we don't have to refind the parked user associated with this bridge channel again. */
+	bridge_channel->bridge_pvt = pu;
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief ast_bridge parking pull method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to pull.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_parking_pull(struct ast_bridge_parking *self, struct ast_bridge_channel *bridge_channel)
+{
+	RAII_VAR(struct parked_user *, pu, NULL, ao2_cleanup);
+	ast_bridge_base_v_table.pull(&self->base, bridge_channel);
+
+	/* Take over the bridge channel's pu reference. It will be released when we are done. */
+	pu = bridge_channel->bridge_pvt;
+	bridge_channel->bridge_pvt = NULL;
+
+	/* This should only happen if the exiting channel was swapped out */
+	if (!pu) {
+		return;
+	}
+
+	/* If we got here without the resolution being set, that's because the call was hung up for some reason without
+	 * timing out or being picked up. There may be some forcible park removals later, but the resolution should be
+	 * handled in those cases */
+	ao2_lock(pu);
+	if (pu->resolution == PARK_UNSET) {
+		pu->resolution = PARK_ABANDON;
+	}
+	ao2_unlock(pu);
+
+	switch (pu->resolution) {
+	case PARK_UNSET:
+		/* This should be impossible now since the resolution is forcibly set to abandon if it was unset at this point. Resolution
+		   isn't allowed to be changed when it isn't currently PARK_UNSET. */
+		return;
+	case PARK_ABANDON:
+		/* Since the call was abandoned without additional handling, we need to issue the give up event and unpark the user. */
+		publish_parked_call(pu, PARKED_CALL_GIVEUP);
+		unpark_parked_user(pu);
+		return;
+	case PARK_FORCED:
+		/* PARK_FORCED is currently unused, but it is expected that it would be handled similar to PARK_ANSWERED.
+		 * There is currently no event related to forced parked calls either */
+		return;
+	case PARK_ANSWERED:
+		/* If answered or forced, the channel should be pulled from the bridge as part of that process and unlinked from
+		 * the parking lot afterwards. We do need to apply bridge features though and play the courtesy tone if set. */
+		publish_parked_call(pu, PARKED_CALL_UNPARKED);
+		parked_call_retrieve_enable_features(bridge_channel->chan, pu->lot, AST_FEATURE_FLAG_BYCALLEE);
+
+		if (pu->lot->cfg->parkedplay & AST_FEATURE_FLAG_BYCALLEE) {
+			ast_bridge_channel_queue_playfile(bridge_channel, NULL, pu->lot->cfg->courtesytone, NULL);
+		}
+
+		return;
+	case PARK_TIMEOUT:
+		/* Timeout is similar to abandon because it simply sets the bridge state to end and doesn't
+		 * actually pull the channel. Because of that, unpark should happen in here. */
+		publish_parked_call(pu, PARKED_CALL_TIMEOUT);
+		unpark_parked_user(pu);
+		return;
+	}
+}
+
+/*!
+ * \internal
+ * \brief ast_bridge parking notify_masquerade method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel that was masqueraded.
+ *
+ * \note On entry, self is already locked.
+ * \note XXX Stub... and it will probably go unused.
+ *
+ * \return Nothing
+ */
+static void bridge_parking_notify_masquerade(struct ast_bridge_parking *self, struct ast_bridge_channel *bridge_channel)
+{
+	ast_bridge_base_v_table.notify_masquerade(&self->base, bridge_channel);
+}
+
+static void bridge_parking_get_merge_priority(struct ast_bridge_parking *self)
+{
+	ast_bridge_base_v_table.get_merge_priority(&self->base);
+}
+
+struct ast_bridge_methods ast_bridge_parking_v_table = {
+	.name = "parking",
+	.destroy = (ast_bridge_destructor_fn) bridge_parking_destroy,
+	.dissolving = (ast_bridge_dissolving_fn) bridge_parking_dissolving,
+	.push = (ast_bridge_push_channel_fn) bridge_parking_push,
+	.pull = (ast_bridge_pull_channel_fn) bridge_parking_pull,
+	.notify_masquerade = (ast_bridge_notify_masquerade_fn) bridge_parking_notify_masquerade,
+	.get_merge_priority = (ast_bridge_merge_priority_fn) bridge_parking_get_merge_priority,
+};
+
+static struct ast_bridge *ast_bridge_parking_init(struct ast_bridge_parking *self, struct parking_lot *bridge_lot)
+{
+	if (!self) {
+		return NULL;
+	}
+
+	/* If no lot is defined for the bridge, then we aren't allowing the bridge to be initialized. */
+	if (!bridge_lot) {
+		ao2_ref(self, -1);
+		return NULL;
+	}
+
+	/* It doesn't need to be a reference since the bridge only lives as long as the parking lot lives. */
+	self->lot = bridge_lot;
+
+	return &self->base;
+}
+
+struct ast_bridge *bridge_parking_new(struct parking_lot *bridge_lot)
+{
+	void *bridge;
+
+	bridge = ast_bridge_alloc(sizeof(struct ast_bridge_parking), &ast_bridge_parking_v_table);
+	bridge = ast_bridge_base_init(bridge, AST_BRIDGE_CAPABILITY_HOLDING,
+		AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM
+		| AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM);
+	bridge = ast_bridge_parking_init(bridge, bridge_lot);
+	bridge = ast_bridge_register(bridge);
+	return bridge;
+}
diff --git a/res/parking/parking_bridge_features.c b/res/parking/parking_bridge_features.c
new file mode 100644
index 0000000000000000000000000000000000000000..ddb5e7ff2b97d9c4ebfe784685d325a3b99cb38d
--- /dev/null
+++ b/res/parking/parking_bridge_features.c
@@ -0,0 +1,479 @@
+/*
+ * 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 Parking Bridge DTMF and Interval features
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "res_parking.h"
+#include "asterisk/utils.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/logger.h"
+#include "asterisk/pbx.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_features.h"
+#include "asterisk/features.h"
+#include "asterisk/say.h"
+#include "asterisk/datastore.h"
+#include "asterisk/stasis.h"
+
+struct parked_subscription_datastore {
+	struct stasis_subscription *parked_subscription;
+};
+
+struct parked_subscription_data {
+	char *parkee_uuid;
+	char parker_uuid[0];
+};
+
+static void parked_subscription_datastore_destroy(void *data)
+{
+	struct parked_subscription_datastore *subscription_datastore = data;
+
+	stasis_unsubscribe(subscription_datastore->parked_subscription);
+	subscription_datastore->parked_subscription = NULL;
+
+	ast_free(subscription_datastore);
+}
+
+static const struct ast_datastore_info parked_subscription_info = {
+	.type = "park subscription",
+	.destroy = parked_subscription_datastore_destroy,
+};
+
+static void wipe_subscription_datastore(struct ast_channel *chan)
+{
+	struct ast_datastore *datastore;
+
+	ast_channel_lock(chan);
+
+	datastore = ast_channel_datastore_find(chan, &parked_subscription_info, NULL);
+	if (datastore) {
+		ast_channel_datastore_remove(chan, datastore);
+		ast_datastore_free(datastore);
+	}
+	ast_channel_unlock(chan);
+}
+
+static void parker_parked_call_message_response(struct ast_parked_call_payload *message, struct parked_subscription_data *data,
+	struct stasis_subscription *sub)
+{
+	const char *parkee_to_act_on = data->parkee_uuid;
+	char saynum_buf[16];
+	struct ast_channel_snapshot *parkee_snapshot = message->parkee;
+	RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup);
+
+	if (strcmp(parkee_to_act_on, parkee_snapshot->uniqueid)) {
+		return;
+	}
+
+	if (message->event_type != PARKED_CALL && message->event_type != PARKED_CALL_FAILED) {
+		/* We only care about these two event types */
+		return;
+	}
+
+	parker = ast_channel_get_by_name(data->parker_uuid);
+	if (!parker) {
+		return;
+	}
+
+	ast_channel_lock(parker);
+	bridge_channel = ast_channel_get_bridge_channel(parker);
+	ast_channel_unlock(parker);
+	if (!bridge_channel) {
+		return;
+	}
+
+	if (message->event_type == PARKED_CALL) {
+		/* queue the saynum on the bridge channel and hangup */
+		snprintf(saynum_buf, sizeof(saynum_buf), "%u %u", 1, message->parkingspace);
+		ast_bridge_channel_queue_playfile(bridge_channel, say_parking_space, saynum_buf, NULL);
+		wipe_subscription_datastore(bridge_channel->chan);
+	}
+
+	if (message->event_type == PARKED_CALL_FAILED) {
+		ast_bridge_channel_queue_playfile(bridge_channel, NULL, "pbx-parkingfailed", NULL);
+		wipe_subscription_datastore(bridge_channel->chan);
+	}
+}
+
+static void parker_update_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+{
+	if (stasis_subscription_final_message(sub, message)) {
+		ast_free(data);
+		return;
+	}
+
+	if (stasis_message_type(message) == ast_parked_call_type()) {
+		struct ast_parked_call_payload *parked_call_message = stasis_message_data(message);
+		parker_parked_call_message_response(parked_call_message, data, sub);
+	}
+}
+
+static int create_parked_subscription(struct ast_channel *chan, const char *parkee_uuid)
+{
+	struct ast_datastore *datastore;
+	struct parked_subscription_datastore *parked_datastore;
+	struct parked_subscription_data *subscription_data;
+
+	char *parker_uuid = ast_strdupa(ast_channel_uniqueid(chan));
+	size_t parker_uuid_size = strlen(parker_uuid) + 1;
+
+	/* If there is already a subscription, get rid of it. */
+	wipe_subscription_datastore(chan);
+
+	if (!(datastore = ast_datastore_alloc(&parked_subscription_info, NULL))) {
+		return -1;
+	}
+
+	if (!(parked_datastore = ast_calloc(1, sizeof(*parked_datastore)))) {
+		ast_datastore_free(datastore);
+		return -1;
+	}
+
+	if (!(subscription_data = ast_calloc(1, sizeof(*subscription_data) + parker_uuid_size +
+			strlen(parkee_uuid) + 1))) {
+		ast_datastore_free(datastore);
+		ast_free(parked_datastore);
+		return -1;
+	}
+
+	subscription_data->parkee_uuid = subscription_data->parker_uuid + parker_uuid_size;
+	strcpy(subscription_data->parkee_uuid, parkee_uuid);
+	strcpy(subscription_data->parker_uuid, parker_uuid);
+
+	if (!(parked_datastore->parked_subscription = stasis_subscribe(ast_parking_topic(), parker_update_cb, subscription_data))) {
+		return -1;
+	}
+
+	datastore->data = parked_datastore;
+
+	ast_channel_lock(chan);
+	ast_channel_datastore_add(chan, datastore);
+	ast_channel_unlock(chan);
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Helper function that creates an outgoing channel and returns it immediately. This function is nearly
+ *        identical to the dial_transfer function in bridge_builtin_features.c, however it doesn't swap the
+ *        local channel and the channel that instigated the park.
+ */
+static struct ast_channel *park_local_transfer(struct ast_channel *parker, const char *exten, const char *context)
+{
+	RAII_VAR(struct ast_channel *, parkee_side_2, NULL, ao2_cleanup);
+	char destination[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 1];
+	struct ast_channel *parkee;
+	int cause;
+
+	/* Used for side_2 hack */
+	char *parkee_name;
+	char *semi_pos;
+
+	/* Fill the variable with the extension and context we want to call */
+	snprintf(destination, sizeof(destination), "%s@%s", exten, context);
+
+	/* Now we request that chan_local prepare to call the destination */
+	parkee = ast_request("Local", ast_channel_nativeformats(parker), parker, destination,
+		&cause);
+	if (!parkee) {
+		return NULL;
+	}
+
+	/* Before we actually dial out let's inherit appropriate information. */
+	ast_channel_lock_both(parker, parkee);
+	ast_connected_line_copy_from_caller(ast_channel_connected(parkee), ast_channel_caller(parker));
+	ast_channel_inherit_variables(parker, parkee);
+	ast_channel_datastore_inherit(parker, parkee);
+	ast_channel_unlock(parkee);
+	ast_channel_unlock(parker);
+
+	/* BUGBUG Use Richard's unreal channel stuff here instead of this hack */
+	parkee_name = ast_strdupa(ast_channel_name(parkee));
+
+	semi_pos = strrchr(parkee_name, ';');
+	if (!semi_pos) {
+		/* There should always be a semicolon present in the string if is used since it's a local channel. */
+		ast_assert(0);
+		return NULL;
+	}
+
+	parkee_name[(semi_pos - parkee_name) + 1] = '2';
+	parkee_side_2 = ast_channel_get_by_name(parkee_name);
+
+	/* We need to have the parker subscribe to the new local channel before hand. */
+	create_parked_subscription(parker, ast_channel_uniqueid(parkee_side_2));
+
+	pbx_builtin_setvar_helper(parkee_side_2, "BLINDTRANSFER", ast_channel_name(parker));
+
+	/* Since the above worked fine now we actually call it and return the channel */
+	if (ast_call(parkee, destination, 0)) {
+		ast_hangup(parkee);
+		return NULL;
+	}
+
+	return parkee;
+}
+
+static int park_feature_helper(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_exten *park_exten)
+{
+	RAII_VAR(struct ast_channel *, other, NULL, ao2_cleanup);
+	RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
+	RAII_VAR(struct parked_user *, pu, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
+	RAII_VAR(struct ao2_container *, bridge_peers, NULL, ao2_cleanup);
+	struct ao2_iterator iter;
+
+	bridge_peers = ast_bridge_peers(bridge);
+
+	if (ao2_container_count(bridge_peers) < 2) {
+		/* There is nothing to do if there is no one to park. */
+		return 0;
+	}
+
+	if (ao2_container_count(bridge_peers) > 2) {
+		/* With a multiparty bridge, we need to do a regular blind transfer. We link the existing bridge to the parking lot with a
+		 * local channel rather than transferring others. */
+		struct ast_channel *transfer_chan = NULL;
+
+		if (!park_exten) {
+			/* This simply doesn't work. The user attempted to one-touch park the parking lot and we can't originate a local channel
+			 * without knowing an extension to transfer it to.
+			 * XXX However, when parking lots are changed to be able to register extensions then this will be doable. */
+			ast_log(LOG_ERROR, "Can not one-touch park a multiparty bridge.\n");
+			return 0;
+		}
+
+		transfer_chan = park_local_transfer(bridge_channel->chan,
+			ast_get_extension_name(park_exten), ast_get_context_name(ast_get_extension_context(park_exten)));
+
+		if (!transfer_chan) {
+			return 0;
+		}
+
+		if (ast_bridge_impart(bridge_channel->bridge, transfer_chan, NULL, NULL, 1)) {
+			ast_hangup(transfer_chan);
+		}
+
+		return 0;
+	}
+
+	/* Since neither of the above cases were used, we are doing a simple park with a two party bridge. */
+
+	for (iter = ao2_iterator_init(bridge_peers, 0); (other = ao2_iterator_next(&iter)); ao2_ref(other, -1)) {
+		/* We need the channel that isn't the bridge_channel's channel. */
+		if (strcmp(ast_channel_uniqueid(other), ast_channel_uniqueid(bridge_channel->chan))) {
+			break;
+		}
+	}
+	ao2_iterator_destroy(&iter);
+
+	if (!other) {
+		ast_assert(0);
+		return -1;
+	}
+
+	/* Subscribe to park messages with the other channel entering */
+	if (create_parked_subscription(bridge_channel->chan, ast_channel_uniqueid(other))) {
+		return -1;
+	}
+
+	/* Write the park frame with the intended recipient and other data out to the bridge. */
+	ast_bridge_channel_write_park(bridge_channel, ast_channel_uniqueid(other), ast_channel_uniqueid(bridge_channel->chan), ast_get_extension_app_data(park_exten));
+
+	return 0;
+}
+
+static void park_bridge_channel(struct ast_bridge_channel *bridge_channel, const char *uuid_parkee, const char *uuid_parker, const char *app_data)
+{
+	RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_bridge *, original_bridge, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup);
+
+	if (strcmp(ast_channel_uniqueid(bridge_channel->chan), uuid_parkee)) {
+		/* We aren't the parkee, so ignore this action. */
+		return;
+	}
+
+	parker = ast_channel_get_by_name(uuid_parker);
+
+	if (!parker) {
+		ast_log(LOG_NOTICE, "Channel with uuid %s left before we could start parking the call. Parking canceled.\n", uuid_parker);
+		publish_parked_call_failure(bridge_channel->chan);
+		return;
+	}
+
+	if (!(parking_bridge = park_common_setup(bridge_channel->chan, parker, app_data, NULL))) {
+		publish_parked_call_failure(bridge_channel->chan);
+		return;
+	}
+
+	pbx_builtin_setvar_helper(bridge_channel->chan, "BLINDTRANSFER", ast_channel_name(parker));
+
+	/* bridge_channel must be locked so we can get a reference to the bridge it is currently on */
+	ao2_lock(bridge_channel);
+
+	original_bridge = bridge_channel->bridge;
+	if (!original_bridge) {
+		ao2_unlock(bridge_channel);
+		publish_parked_call_failure(bridge_channel->chan);
+		return;
+	}
+
+	ao2_ref(original_bridge, +1); /* Cleaned by RAII_VAR */
+
+	ao2_unlock(bridge_channel);
+
+	if (ast_bridge_move(parking_bridge, original_bridge, bridge_channel->chan, NULL, 1)) {
+		ast_log(LOG_ERROR, "Failed to move %s into the parking bridge.\n",
+			ast_channel_name(bridge_channel->chan));
+	}
+}
+
+static int feature_park(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+	park_feature_helper(bridge, bridge_channel, NULL);
+	return 0;
+}
+
+static void parking_duration_cb_destroyer(void *hook_pvt)
+{
+	struct parked_user *user = hook_pvt;
+	ao2_ref(user, -1);
+}
+
+/*! \internal
+ * \brief Interval hook. Pulls a parked call from the parking bridge after the timeout is passed and sets the resolution to timeout.
+ *
+ * \param bridge Which bridge the channel was parked in
+ * \param bridge_channel bridge channel this interval hook is being executed on
+ * \param hook_pvt A pointer to the parked_user struct associated with the channel is stuffed in here
+ */
+static int parking_duration_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+	struct parked_user *user = hook_pvt;
+	struct ast_channel *chan = user->chan;
+	char *peername;
+	char parking_space[AST_MAX_EXTENSION];
+
+	/* We are still in the bridge, so it's possible for other stuff to mess with the parked call before we leave the bridge
+	   to deal with this, lock the parked user, check and set resolution. */
+	ao2_lock(user);
+	if (user->resolution != PARK_UNSET) {
+		/* Abandon timeout since something else has resolved the parked user before we got to it. */
+		ao2_unlock(user);
+		return -1;
+	}
+
+	user->resolution = PARK_TIMEOUT;
+	ao2_unlock(user);
+
+	ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+
+	/* Set parking timeout channel variables */
+	snprintf(parking_space, sizeof(parking_space), "%d", user->parking_space);
+	pbx_builtin_setvar_helper(chan, "PARKING_SPACE", parking_space);
+	pbx_builtin_setvar_helper(chan, "PARKINGSLOT", parking_space); /* Deprecated version of PARKING_SPACE */
+	pbx_builtin_setvar_helper(chan, "PARKEDLOT", user->lot->name);
+
+	peername = ast_strdupa(user->parker->name);
+	flatten_peername(peername);
+
+	pbx_builtin_setvar_helper(chan, "PARKER", peername);
+
+	/* TODO Dialplan generation for park-dial extensions */
+
+	/* async_goto the proper PBX destination - this should happen when we come out of the bridge */
+	if (!ast_strlen_zero(user->comeback)) {
+		ast_async_parseable_goto(chan, user->comeback);
+	} else {
+		comeback_goto(user, user->lot);
+	}
+
+	return -1;
+}
+
+void say_parking_space(struct ast_bridge_channel *bridge_channel, const char *payload)
+{
+	int numeric_value;
+	int hangup_after;
+
+	if (sscanf(payload, "%u %u", &hangup_after, &numeric_value) != 2) {
+		/* If say_parking_space is called with a non-numeric string, we have a problem. */
+		ast_assert(0);
+		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+		return;
+	}
+
+	ast_say_digits(bridge_channel->chan, numeric_value, "", ast_channel_language(bridge_channel->chan));
+
+	if (hangup_after) {
+		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+	}
+}
+
+void parking_set_duration(struct ast_bridge_features *features, struct parked_user *user)
+{
+	unsigned int time_limit;
+
+	time_limit = user->time_limit * 1000;
+
+	if (!time_limit) {
+		/* There is no duration limit that we need to apply. */
+		return;
+	}
+
+	/* If the time limit has already been passed, set a really low time limit so we can kick them out immediately. */
+	time_limit = ast_remaining_ms(user->start, time_limit);
+	if (time_limit <= 0) {
+		time_limit = 1;
+	}
+
+	/* The interval hook is going to need a reference to the parked_user */
+	ao2_ref(user, +1);
+
+	if (ast_bridge_interval_hook(features, time_limit,
+		parking_duration_callback, user, parking_duration_cb_destroyer, 1)) {
+		ast_log(LOG_ERROR, "Failed to apply duration limits to the parking call.\n");
+	}
+}
+
+void unload_parking_bridge_features(void)
+{
+	ast_bridge_features_unregister(AST_BRIDGE_BUILTIN_PARKCALL);
+	ast_uninstall_park_blind_xfer_func();
+	ast_uninstall_bridge_channel_park_func();
+}
+
+int load_parking_bridge_features(void)
+{
+	ast_bridge_features_register(AST_BRIDGE_BUILTIN_PARKCALL, feature_park, NULL);
+	ast_install_park_blind_xfer_func(park_feature_helper);
+	ast_install_bridge_channel_park_func(park_bridge_channel);
+	return 0;
+}
diff --git a/res/parking/parking_controller.c b/res/parking/parking_controller.c
new file mode 100644
index 0000000000000000000000000000000000000000..03d7b8861a198147239cf8ec339372c8b8e2d60f
--- /dev/null
+++ b/res/parking/parking_controller.c
@@ -0,0 +1,292 @@
+/*
+ * 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 Parking Entry, Exit, and other assorted controls.
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+#include "asterisk.h"
+
+#include "asterisk/logger.h"
+#include "res_parking.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/utils.h"
+#include "asterisk/manager.h"
+#include "asterisk/test.h"
+#include "asterisk/features.h"
+#include "asterisk/bridging_basic.h"
+
+struct ast_bridge *parking_lot_get_bridge(struct parking_lot *lot)
+{
+	struct ast_bridge *lot_bridge;
+
+	if (lot->parking_bridge) {
+		ao2_ref(lot->parking_bridge, +1);
+		return lot->parking_bridge;
+	}
+
+	lot_bridge = bridge_parking_new(lot);
+	if (!lot_bridge) {
+		return NULL;
+	}
+
+	/* The parking lot needs a reference to the bridge as well. */
+	lot->parking_bridge = lot_bridge;
+	ao2_ref(lot->parking_bridge, +1);
+
+	return lot_bridge;
+}
+
+void parking_channel_set_roles(struct ast_channel *chan, struct parking_lot *lot, int force_ringing)
+{
+	ast_channel_add_bridge_role(chan, "holding_participant");
+	if (force_ringing) {
+		ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "ringing");
+	} else {
+		ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "musiconhold");
+		if (!ast_strlen_zero(lot->cfg->mohclass)) {
+			ast_channel_set_bridge_role_option(chan, "holding_participant", "moh_class", lot->cfg->mohclass);
+		}
+	}
+}
+
+struct parking_limits_pvt {
+	struct parked_user *user;
+};
+
+int unpark_parked_user(struct parked_user *pu)
+{
+	if (pu->lot) {
+		ao2_unlink(pu->lot->parked_users, pu);
+		parking_lot_remove_if_unused(pu->lot);
+		return 0;
+	}
+
+	return -1;
+}
+
+int parking_lot_get_space(struct parking_lot *lot, int target_override)
+{
+	int original_target;
+	int current_target;
+	struct ao2_iterator i;
+	struct parked_user *user;
+	int wrap;
+
+	if (lot->cfg->parkfindnext) {
+		/* Use next_space if the lot already has next_space set; otherwise use lot start. */
+		original_target = lot->next_space ? lot->next_space : lot->cfg->parking_start;
+	} else {
+		original_target = lot->cfg->parking_start;
+	}
+
+	if (target_override >= lot->cfg->parking_start && target_override <= lot->cfg->parking_stop) {
+		original_target = target_override;
+	}
+
+	current_target = original_target;
+
+	wrap = lot->cfg->parking_start;
+
+	i = ao2_iterator_init(lot->parked_users, 0);
+	while ((user = ao2_iterator_next(&i))) {
+		/* Increment the wrap on each pass until we find an empty space */
+		if (wrap == user->parking_space) {
+			wrap += 1;
+		}
+
+		if (user->parking_space < current_target) {
+			/* It's lower than the anticipated target, so we haven't reached the target yet. */
+			ao2_ref(user, -1);
+			continue;
+		}
+
+		if (user->parking_space > current_target) {
+			/* The current target is usable because all items below have been read and the next target is higher than the one we want. */
+			ao2_ref(user, -1);
+			break;
+		}
+
+		/* We found one already parked here. */
+		current_target += 1;
+		ao2_ref(user, -1);
+	}
+	ao2_iterator_destroy(&i);
+
+	if (current_target <= lot->cfg->parking_stop) {
+		return current_target;
+	}
+
+	if (wrap <= lot->cfg->parking_stop) {
+		return wrap;
+	}
+
+	return -1;
+}
+
+static int retrieve_parked_user_targeted(void *obj, void *arg, int flags)
+{
+	int *target = arg;
+	struct parked_user *user = obj;
+	if (user->parking_space == *target) {
+		return CMP_MATCH;
+	}
+
+	return 0;
+}
+
+struct parked_user *parking_lot_retrieve_parked_user(struct parking_lot *lot, int target)
+{
+	RAII_VAR(struct parked_user *, user, NULL, ao2_cleanup);
+
+	if (target < 0) {
+		user = ao2_callback(lot->parked_users, 0, NULL, NULL);
+	} else {
+		user = ao2_callback(lot->parked_users, 0, retrieve_parked_user_targeted, &target);
+	}
+
+	if (!user) {
+		return NULL;
+	}
+
+	ao2_lock(user);
+	if (user->resolution != PARK_UNSET) {
+		/* Abandon. Something else has resolved the parked user before we got to it. */
+		ao2_unlock(user);
+		return NULL;
+	}
+
+	ao2_unlink(lot->parked_users, user);
+	user->resolution = PARK_ANSWERED;
+	ao2_unlock(user);
+
+	parking_lot_remove_if_unused(user->lot);
+
+	/* Bump the ref count by 1 since the RAII_VAR will eat the reference otherwise */
+	ao2_ref(user, +1);
+	return user;
+}
+
+void parked_call_retrieve_enable_features(struct ast_channel *chan, struct parking_lot *lot, int recipient_mode)
+{
+	/* Enabling features here should be additive to features that are already on the channel. */
+	struct ast_flags feature_flags = { 0 };
+	struct ast_flags *existing_features;
+
+	ast_channel_lock(chan);
+	existing_features = ast_bridge_features_ds_get(chan);
+
+	if (existing_features) {
+		feature_flags = *existing_features;
+	}
+
+	if (lot->cfg->parkedcalltransfers & recipient_mode) {
+		ast_set_flag(&feature_flags, AST_FEATURE_REDIRECT);
+		ast_set_flag(&feature_flags, AST_FEATURE_ATXFER);
+	}
+
+	if (lot->cfg->parkedcallreparking & recipient_mode) {
+		ast_set_flag(&feature_flags, AST_FEATURE_PARKCALL);
+	}
+
+	if (lot->cfg->parkedcallhangup & recipient_mode) {
+		ast_set_flag(&feature_flags, AST_FEATURE_DISCONNECT);
+	}
+
+	if (lot->cfg->parkedcallrecording & recipient_mode) {
+		ast_set_flag(&feature_flags, AST_FEATURE_AUTOMIXMON);
+	}
+
+	ast_bridge_features_ds_set(chan, &feature_flags);
+	ast_channel_unlock(chan);
+
+	return;
+}
+
+void flatten_peername(char *peername)
+{
+	int i;
+	char *dash;
+
+	/* Truncate after the dash */
+	dash = strrchr(peername, '-');
+	if (dash) {
+		*dash = '\0';
+	}
+
+	/* Replace slashes with underscores since slashes are reserved characters for extension matching */
+	for (i = 0; peername[i]; i++) {
+		if (peername[i] == '/') {
+			/* The underscore is the flattest character of all. */
+			peername[i] = '_';
+		}
+	}
+}
+
+int comeback_goto(struct parked_user *pu, struct parking_lot *lot)
+{
+	struct ast_channel *chan = pu->chan;
+	char *peername;
+	const char *blindtransfer;
+
+	ast_channel_lock(chan);
+	if ((blindtransfer = pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"))) {
+		blindtransfer = ast_strdupa(blindtransfer);
+	}
+	ast_channel_unlock(chan);
+
+	peername = blindtransfer ? ast_strdupa(blindtransfer) : ast_strdupa(pu->parker->name);
+
+	/* XXX Comeback to origin mode: Generate an extension in park-dial to Dial the peer */
+
+
+	/* Flatten the peername so that it can be used for performing the timeout PBX operations */
+	flatten_peername(peername);
+
+	if (lot->cfg->comebacktoorigin) {
+		if (ast_exists_extension(chan, PARK_DIAL_CONTEXT, peername, 1, NULL)) {
+			ast_async_goto(chan, PARK_DIAL_CONTEXT, peername, 1);
+			return 0;
+		} else {
+			ast_log(LOG_ERROR, "Can not start %s at %s,%s,1 because extension does not exist. Terminating call.\n",
+				ast_channel_name(chan), PARK_DIAL_CONTEXT, peername);
+			return -1;
+		}
+	}
+
+	if (ast_exists_extension(chan, lot->cfg->comebackcontext, peername, 1, NULL)) {
+		ast_async_goto(chan, lot->cfg->comebackcontext, peername, 1);
+		return 0;
+	}
+
+	if (ast_exists_extension(chan, lot->cfg->comebackcontext, "s", 1, NULL)) {
+		ast_verb(2, "Could not start %s at %s,%s,1. Using 's@%s' instead.\n", ast_channel_name(chan),
+			lot->cfg->comebackcontext, peername, lot->cfg->comebackcontext);
+		ast_async_goto(chan, lot->cfg->comebackcontext, "s", 1);
+		return 0;
+	}
+
+	ast_verb(2, "Can not start %s at %s,%s,1 and exten 's@%s' does not exist. Using 's@default'\n",
+		ast_channel_name(chan),
+		lot->cfg->comebackcontext, peername, lot->cfg->comebackcontext);
+	ast_async_goto(chan, "default", "s", 1);
+
+	return 0;
+}
diff --git a/res/parking/parking_manager.c b/res/parking/parking_manager.c
new file mode 100644
index 0000000000000000000000000000000000000000..d6a05573fb159f2ca2babc47be9e78d0c498b0a5
--- /dev/null
+++ b/res/parking/parking_manager.c
@@ -0,0 +1,610 @@
+/*
+ * 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 Call Parking Manager Actions and Events
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "res_parking.h"
+#include "asterisk/config.h"
+#include "asterisk/config_options.h"
+#include "asterisk/event.h"
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/features.h"
+#include "asterisk/manager.h"
+
+/*** DOCUMENTATION
+	<manager name="Parkinglots" language="en_US">
+		<synopsis>
+			Get a list of parking lots
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+		</syntax>
+		<description>
+			<para>List all parking lots as a series of AMI events</para>
+		</description>
+	</manager>
+	<manager name="ParkedCalls" language="en_US">
+		<synopsis>
+			List parked calls.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+			<parameter name="ParkingLot">
+				<para>If specified, only show parked calls from the parking lot with this name.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>List parked calls.</para>
+		</description>
+	</manager>
+	<managerEvent language="en_US" name="ParkedCall">
+		<managerEventInstance class="EVENT_FLAG_CALL">
+			<synopsis>Raised when a channel is parked.</synopsis>
+			<syntax>
+				<parameter name="ChannelParkee">
+				</parameter>
+				<parameter name="ChannelStateParkee">
+					<para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
+				</parameter>
+				<parameter name="ChannelStateDescParkee">
+					<enumlist>
+						<enum name="Down"/>
+						<enum name="Rsrvd"/>
+						<enum name="OffHook"/>
+						<enum name="Dialing"/>
+						<enum name="Ring"/>
+						<enum name="Ringing"/>
+						<enum name="Up"/>
+						<enum name="Busy"/>
+						<enum name="Dialing Offhook"/>
+						<enum name="Pre-ring"/>
+						<enum name="Unknown"/>
+					</enumlist>
+				</parameter>
+				<parameter name="CallerIDNumParkee">
+				</parameter>
+				<parameter name="CallerIDNameParkee">
+				</parameter>
+				<parameter name="ConnectedLineNumParkee">
+				</parameter>
+				<parameter name="ConnectedLineNameParkee">
+				</parameter>
+				<parameter name="AccountCodeParkee">
+				</parameter>
+				<parameter name="ContextParkee">
+				</parameter>
+				<parameter name="ExtenParkee">
+				</parameter>
+				<parameter name="PriorityParkee">
+				</parameter>
+				<parameter name="UniqueidParkee">
+				</parameter>
+				<parameter name="ChannelParker">
+				</parameter>
+				<parameter name="ChannelStateParker">
+				<para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
+				</parameter>
+				<parameter name="ChannelStateDescParker">
+					<enumlist>
+						<enum name="Down"/>
+						<enum name="Rsrvd"/>
+						<enum name="OffHook"/>
+						<enum name="Dialing"/>
+						<enum name="Ring"/>
+						<enum name="Ringing"/>
+						<enum name="Up"/>
+						<enum name="Busy"/>
+						<enum name="Dialing Offhook"/>
+						<enum name="Pre-ring"/>
+						<enum name="Unknown"/>
+					</enumlist>
+				</parameter>
+				<parameter name="CallerIDNumParker">
+				</parameter>
+				<parameter name="CallerIDNameParker">
+				</parameter>
+				<parameter name="ConnectedLineNumParker">
+				</parameter>
+				<parameter name="ConnectedLineNameParker">
+				</parameter>
+				<parameter name="AccountCodeParker">
+				</parameter>
+				<parameter name="ContextParker">
+				</parameter>
+				<parameter name="ExtenParker">
+				</parameter>
+				<parameter name="PriorityParker">
+				</parameter>
+				<parameter name="UniqueidParker">
+				</parameter>
+				<parameter name="Parkinglot">
+					<para>Name of the parking lot that the parkee is parked in</para>
+				</parameter>
+				<parameter name="ParkingSpace">
+					<para>Parking Space that the parkee is parked in</para>
+				</parameter>
+				<parameter name="ParkingTimeout">
+				<para>Time remaining until the parkee is forcefully removed from parking in seconds</para>
+				</parameter>
+				<parameter name="ParkingDuration">
+					<para>Time the parkee has been in the parking bridge (in seconds)</para>
+				</parameter>
+			</syntax>
+		</managerEventInstance>
+	</managerEvent>
+	<managerEvent language="en_US" name="ParkedCallTimeOut">
+		<managerEventInstance class="EVENT_FLAG_CALL">
+			<synopsis>Raised when a channel leaves a parking lot due to reaching the time limit of being parked.</synopsis>
+			<syntax>
+				<xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
+			</syntax>
+		</managerEventInstance>
+	</managerEvent>
+	<managerEvent language="en_US" name="ParkedCallGiveUp">
+		<managerEventInstance class="EVENT_FLAG_CALL">
+			<synopsis>Raised when a channel leaves a parking lot because it hung up without being answered.</synopsis>
+			<syntax>
+				<xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
+			</syntax>
+		</managerEventInstance>
+	</managerEvent>
+	<managerEvent language="en_US" name="UnParkedCall">
+		<managerEventInstance class="EVENT_FLAG_CALL">
+			<synopsis>Raised when a channel leaves a parking lot because it was retrieved from the parking lot and reconnected.</synopsis>
+			<syntax>
+				<xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
+				<parameter name="ChannelRetriever">
+				</parameter>
+				<parameter name="ChannelStateRetriever">
+					<para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
+				</parameter>
+				<parameter name="ChannelStateDescRetriever">
+					<enumlist>
+						<enum name="Down"/>
+						<enum name="Rsrvd"/>
+						<enum name="OffHook"/>
+						<enum name="Dialing"/>
+						<enum name="Ring"/>
+						<enum name="Ringing"/>
+						<enum name="Up"/>
+						<enum name="Busy"/>
+						<enum name="Dialing Offhook"/>
+						<enum name="Pre-ring"/>
+						<enum name="Unknown"/>
+					</enumlist>
+				</parameter>
+				<parameter name="CallerIDNumRetriever">
+				</parameter>
+				<parameter name="CallerIDNameRetriever">
+				</parameter>
+				<parameter name="ConnectedLineNumRetriever">
+				</parameter>
+				<parameter name="ConnectedLineNameRetriever">
+				</parameter>
+				<parameter name="AccountCodeRetriever">
+				</parameter>
+				<parameter name="ContextRetriever">
+				</parameter>
+				<parameter name="ExtenRetriever">
+				</parameter>
+				<parameter name="PriorityRetriever">
+				</parameter>
+				<parameter name="UniqueidRetriever">
+				</parameter>
+			</syntax>
+		</managerEventInstance>
+	</managerEvent>
+ ***/
+
+/*! \brief subscription to the parking lot topic */
+static struct stasis_subscription *parking_sub;
+
+static struct ast_parked_call_payload *parked_call_payload_from_failure(struct ast_channel *chan)
+{
+	RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_channel_snapshot *, parkee_snapshot, NULL, ao2_cleanup);
+
+	parkee_snapshot = ast_channel_snapshot_create(chan);
+	if (!parkee_snapshot) {
+		return NULL;
+	}
+
+	return ast_parked_call_payload_create(PARKED_CALL_FAILED, parkee_snapshot, NULL, NULL, NULL, 0, 0, 0);
+}
+
+static struct ast_parked_call_payload *parked_call_payload_from_parked_user(struct parked_user *pu, enum ast_parked_call_event_type event_type)
+{
+	RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_channel_snapshot *, parkee_snapshot, NULL, ao2_cleanup);
+	long int timeout;
+	long int duration;
+	struct timeval now = ast_tvnow();
+	const char *lot_name = pu->lot->name;
+
+	if (!pu->parker) {
+		return NULL;
+	}
+
+	parkee_snapshot = ast_channel_snapshot_create(pu->chan);
+
+	if (!parkee_snapshot) {
+		return NULL;
+	}
+
+	timeout = pu->start.tv_sec + (long) pu->time_limit - now.tv_sec;
+	duration = now.tv_sec - pu->start.tv_sec;
+
+	return ast_parked_call_payload_create(event_type, parkee_snapshot, pu->parker, pu->retriever, lot_name, pu->parking_space, timeout, duration);
+
+}
+
+/*! \brief Builds a manager string based on the contents of a parked call payload */
+static struct ast_str *manager_build_parked_call_string(const struct ast_parked_call_payload *payload)
+{
+	struct ast_str *out = ast_str_create(1024);
+	RAII_VAR(struct ast_str *, parkee_string, NULL, ast_free);
+	RAII_VAR(struct ast_str *, parker_string, NULL, ast_free);
+	RAII_VAR(struct ast_str *, retriever_string, NULL, ast_free);
+
+	if (!out) {
+		return NULL;
+	}
+
+	parkee_string = ast_manager_build_channel_state_string_suffix(payload->parkee, "Parkee");
+
+	if (payload->parker) {
+		parker_string = ast_manager_build_channel_state_string_suffix(payload->parker, "Parker");
+	}
+
+	if (payload->retriever) {
+		retriever_string = ast_manager_build_channel_state_string_suffix(payload->retriever, "Retriever");
+	}
+
+	ast_str_set(&out, 0,
+		"%s" /* parkee channel state */
+		"%s" /* parker channel state */
+		"%s" /* retriever channel state (when available) */
+		"Parkinglot: %s\r\n"
+		"ParkingSpace: %u\r\n"
+		"ParkingTimeout: %lu\r\n"
+		"ParkingDuration: %lu\r\n",
+
+		ast_str_buffer(parkee_string),
+		parker_string ? ast_str_buffer(parker_string) : "",
+		retriever_string ? ast_str_buffer(retriever_string) : "",
+		payload->parkinglot,
+		payload->parkingspace,
+		payload->timeout,
+		payload->duration);
+
+	return out;
+}
+
+static int manager_parking_status_single_lot(struct mansession *s, const struct message *m, const char *id_text, const char *lot_name)
+{
+	RAII_VAR(struct parking_lot *, curlot, NULL, ao2_cleanup);
+	struct parked_user *curuser;
+	struct ao2_iterator iter_users;
+	int total = 0;
+
+	curlot = parking_lot_find_by_name(lot_name);
+
+	if (!curlot) {
+		astman_send_error(s, m, "Requested parking lot could not be found.");
+		return RESULT_SUCCESS;
+	}
+
+	astman_send_ack(s, m, "Parked calls will follow");
+
+	iter_users = ao2_iterator_init(curlot->parked_users, 0);
+	while ((curuser = ao2_iterator_next(&iter_users))) {
+		RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+		RAII_VAR(struct ast_str *, parked_call_string, NULL, ast_free);
+
+		payload = parked_call_payload_from_parked_user(curuser, PARKED_CALL);
+		if (!payload) {
+			astman_send_error(s, m, "Failed to retrieve parking data about a parked user.");
+			return RESULT_FAILURE;
+		}
+
+		parked_call_string = manager_build_parked_call_string(payload);
+		if (!parked_call_string) {
+			astman_send_error(s, m, "Failed to retrieve parkingd ata about a parked user.");
+			return RESULT_FAILURE;
+		}
+
+		total++;
+
+		astman_append(s, "Event: ParkedCall\r\n"
+			"%s" /* The parked call string */
+			"%s" /* The action ID */
+			"\r\n",
+			ast_str_buffer(parked_call_string),
+			id_text);
+
+		ao2_ref(curuser, -1);
+	}
+
+	ao2_iterator_destroy(&iter_users);
+
+	astman_append(s,
+		"Event: ParkedCallsComplete\r\n"
+		"Total: %d\r\n"
+		"%s"
+		"\r\n",
+		total, id_text);
+
+	return RESULT_SUCCESS;
+}
+
+static int manager_parking_status_all_lots(struct mansession *s, const struct message *m, const char *id_text)
+{
+	struct parked_user *curuser;
+	struct ao2_container *lot_container;
+	struct ao2_iterator iter_lots;
+	struct ao2_iterator iter_users;
+	struct parking_lot *curlot;
+	int total = 0;
+
+	lot_container = get_parking_lot_container();
+
+	if (!lot_container) {
+		ast_log(LOG_ERROR, "Failed to obtain parking lot list. Action canceled.\n");
+		astman_send_error(s, m, "Could not create parking lot list");
+		return RESULT_SUCCESS;
+	}
+
+	iter_lots = ao2_iterator_init(lot_container, 0);
+
+	astman_send_ack(s, m, "Parked calls will follow");
+
+	while ((curlot = ao2_iterator_next(&iter_lots))) {
+		iter_users = ao2_iterator_init(curlot->parked_users, 0);
+		while ((curuser = ao2_iterator_next(&iter_users))) {
+			RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+			RAII_VAR(struct ast_str *, parked_call_string, NULL, ast_free);
+
+			payload = parked_call_payload_from_parked_user(curuser, PARKED_CALL);
+			if (!payload) {
+				return RESULT_FAILURE;
+			}
+
+			parked_call_string = manager_build_parked_call_string(payload);
+			if (!payload) {
+				return RESULT_FAILURE;
+			}
+
+			total++;
+
+			astman_append(s, "Event: ParkedCall\r\n"
+				"%s" /* The parked call string */
+				"%s" /* The action ID */
+				"\r\n",
+				ast_str_buffer(parked_call_string),
+				id_text);
+
+			ao2_ref(curuser, -1);
+		}
+		ao2_iterator_destroy(&iter_users);
+		ao2_ref(curlot, -1);
+	}
+
+	ao2_iterator_destroy(&iter_lots);
+
+	astman_append(s,
+		"Event: ParkedCallsComplete\r\n"
+		"Total: %d\r\n"
+		"%s"
+		"\r\n",
+		total, id_text);
+
+	return RESULT_SUCCESS;
+}
+
+static int manager_parking_status(struct mansession *s, const struct message *m)
+{
+	const char *id = astman_get_header(m, "ActionID");
+	const char *lot_name = astman_get_header(m, "ParkingLot");
+	char id_text[256] = "";
+
+	if (!ast_strlen_zero(id)) {
+		snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id);
+	}
+
+	if (!ast_strlen_zero(lot_name)) {
+		return manager_parking_status_single_lot(s, m, id_text, lot_name);
+	}
+
+	return manager_parking_status_all_lots(s, m, id_text);
+
+}
+
+static int manager_append_event_parking_lot_data_cb(void *obj, void *arg, void *data, int flags)
+{
+	struct parking_lot *curlot = obj;
+	struct mansession *s = arg;
+	char *id_text = data;
+
+	astman_append(s, "Event: Parkinglot\r\n"
+		"Name: %s\r\n"
+		"StartSpace: %d\r\n"
+		"StopSpace: %d\r\n"
+		"Timeout: %d\r\n"
+		"%s" /* The Action ID */
+		"\r\n",
+		curlot->name,
+		curlot->cfg->parking_start,
+		curlot->cfg->parking_stop,
+		curlot->cfg->parkingtime,
+		id_text);
+
+	return 0;
+}
+
+static int manager_parking_lot_list(struct mansession *s, const struct message *m)
+{
+	const char *id = astman_get_header(m, "ActionID");
+	char id_text[256] = "";
+	struct ao2_container *lot_container;
+
+	if (!ast_strlen_zero(id)) {
+		snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id);
+	}
+
+	lot_container = get_parking_lot_container();
+
+	if (!lot_container) {
+		ast_log(LOG_ERROR, "Failed to obtain parking lot list. Action canceled.\n");
+		astman_send_error(s, m, "Could not create parking lot list");
+		return -1;
+	}
+
+	astman_send_ack(s, m, "Parking lots will follow");
+
+	ao2_callback_data(lot_container, OBJ_MULTIPLE | OBJ_NODATA, manager_append_event_parking_lot_data_cb, s, id_text);
+
+	astman_append(s,
+		"Event: ParkinglotsComplete\r\n"
+		"%s"
+		"\r\n",id_text);
+
+	return RESULT_SUCCESS;
+}
+
+void publish_parked_call_failure(struct ast_channel *parkee)
+{
+	RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+	payload = parked_call_payload_from_failure(parkee);
+	if (!payload) {
+		return;
+	}
+
+	msg = stasis_message_create(ast_parked_call_type(), payload);
+	if (!msg) {
+		return;
+	}
+
+	stasis_publish(ast_parking_topic(), msg);
+}
+
+void publish_parked_call(struct parked_user *pu, enum ast_parked_call_event_type event_type)
+{
+	RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+	payload = parked_call_payload_from_parked_user(pu, event_type);
+	if (!payload) {
+		return;
+	}
+
+	msg = stasis_message_create(ast_parked_call_type(), payload);
+	if (!msg) {
+		return;
+	}
+
+	stasis_publish(ast_parking_topic(), msg);
+}
+
+static void parked_call_message_response(struct ast_parked_call_payload *parked_call)
+{
+	char *event_type = "";
+	RAII_VAR(struct ast_str *, parked_call_string, NULL, ast_free);
+
+	switch (parked_call->event_type) {
+	case PARKED_CALL:
+		event_type = "ParkedCall";
+		break;
+	case PARKED_CALL_TIMEOUT:
+		event_type = "ParkedCallTimeOut";
+		break;
+	case PARKED_CALL_GIVEUP:
+		event_type = "ParkedCallGiveUp";
+		break;
+	case PARKED_CALL_UNPARKED:
+		event_type = "UnParkedCall";
+		break;
+	case PARKED_CALL_FAILED:
+		/* PARKED_CALL_FAILED doesn't currently get a message and is used exclusively for bridging */
+		return;
+	}
+
+	parked_call_string = manager_build_parked_call_string(parked_call);
+	if (!parked_call_string) {
+		ast_log(LOG_ERROR, "Failed to issue an AMI event of '%s' in response to a stasis message.\n", event_type);
+		return;
+	}
+
+	manager_event(EVENT_FLAG_CALL, event_type,
+			"%s",
+			ast_str_buffer(parked_call_string)
+		);
+}
+
+static void parking_event_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+{
+	if (stasis_message_type(message) == ast_parked_call_type()) {
+		struct ast_parked_call_payload *parked_call_message = stasis_message_data(message);
+		parked_call_message_response(parked_call_message);
+	}
+}
+
+static void parking_manager_enable_stasis(void)
+{
+	ast_parking_stasis_init();
+	if (!parking_sub) {
+		parking_sub = stasis_subscribe(ast_parking_topic(), parking_event_cb, NULL);
+	}
+}
+
+int load_parking_manager(void)
+{
+	int res;
+
+	res = ast_manager_register_xml_core("Parkinglots", 0, manager_parking_lot_list);
+	res |= ast_manager_register_xml_core("ParkedCalls", 0, manager_parking_status);
+	/* TODO Add a 'Park' manager action */
+	parking_manager_enable_stasis();
+	return res ? -1 : 0;
+}
+
+static void parking_manager_disable_stasis(void)
+{
+	parking_sub = stasis_unsubscribe(parking_sub);
+	ast_parking_stasis_disable();
+}
+
+void unload_parking_manager(void)
+{
+	ast_manager_unregister("Parkinglots");
+	ast_manager_unregister("ParkedCalls");
+	parking_manager_disable_stasis();
+}
diff --git a/res/parking/parking_ui.c b/res/parking/parking_ui.c
new file mode 100644
index 0000000000000000000000000000000000000000..5b432b6898276d7189a2cf94503633dc5d8009f4
--- /dev/null
+++ b/res/parking/parking_ui.c
@@ -0,0 +1,199 @@
+/*
+ * 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 Call Parking CLI commands
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "res_parking.h"
+#include "asterisk/config.h"
+#include "asterisk/config_options.h"
+#include "asterisk/event.h"
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/features.h"
+#include "asterisk/manager.h"
+
+static void display_parked_call(struct parked_user *user, int fd)
+{
+	ast_cli(fd, "  Space: %d\n", user->parking_space);
+	ast_cli(fd, "  Channel: %s\n", ast_channel_name(user->chan));
+	ast_cli(fd, "  Parker: %s\n", user->parker ? user->parker->name : "<unknown>");
+	ast_cli(fd, "\n");
+}
+
+static int display_parked_users_cb(void *obj, void *arg, int flags)
+{
+	int *fd = arg;
+	struct parked_user *user = obj;
+	display_parked_call(user, *fd);
+	return 0;
+}
+
+static void display_parking_lot(struct parking_lot *lot, int fd)
+{
+	ast_cli(fd, "Parking Lot: %s\n--------------------------------------------------------------------------\n", lot->name);
+	ast_cli(fd, "Parking Extension   :  %s\n", lot->cfg->parkext);
+	ast_cli(fd, "Parking Context     :  %s\n", lot->cfg->parking_con);
+	ast_cli(fd, "Parking Spaces      :  %d-%d\n", lot->cfg->parking_start, lot->cfg->parking_stop);
+	ast_cli(fd, "Parking Time        :  %u sec\n", lot->cfg->parkingtime);
+	ast_cli(fd, "Comeback to Origin  :  %s\n", lot->cfg->comebacktoorigin ? "yes" : "no");
+	ast_cli(fd, "Comeback Context    :  %s%s\n", lot->cfg->comebackcontext, lot->cfg->comebacktoorigin ? " (comebacktoorigin=yes, not used)" : "");
+	ast_cli(fd, "Comeback Dial Time  :  %u sec\n", lot->cfg->comebackdialtime);
+	ast_cli(fd, "MusicOnHold Class   :  %s\n", lot->cfg->mohclass);
+	ast_cli(fd, "Enabled             :  %s\n", (lot->mode == PARKINGLOT_DISABLED) ? "no" : "yes");
+	ast_cli(fd, "\n");
+}
+
+static int display_parking_lot_cb(void *obj, void *arg, int flags)
+{
+	int *fd = arg;
+	struct parking_lot *lot = obj;
+	display_parking_lot(lot, *fd);
+	return 0;
+}
+
+static void cli_display_parking_lot(int fd, const char *name)
+{
+	RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
+	lot = parking_lot_find_by_name(name);
+
+	/* If the parking lot couldn't be found with the search, also abort. */
+	if (!lot) {
+		ast_cli(fd, "Could not find parking lot '%s'\n\n", name);
+		return;
+	}
+
+	display_parking_lot(lot, fd);
+
+	ast_cli(fd, "Parked Calls\n------------\n");
+
+	if (!ao2_container_count(lot->parked_users)) {
+		ast_cli(fd, "  (none)\n");
+		ast_cli(fd, "\n\n");
+		return;
+	}
+
+	ao2_callback(lot->parked_users, OBJ_MULTIPLE | OBJ_NODATA, display_parked_users_cb, &fd);
+	ast_cli(fd, "\n");
+}
+
+static void cli_display_parking_lot_list(int fd)
+{
+	struct ao2_container *lot_container;
+
+	lot_container = get_parking_lot_container();
+
+	if (!lot_container) {
+		ast_cli(fd, "Failed to obtain parking lot list.\n\n");
+		return;
+	}
+
+	ao2_callback(lot_container, OBJ_MULTIPLE | OBJ_NODATA, display_parking_lot_cb, &fd);
+	ast_cli(fd, "\n");
+}
+
+struct parking_lot_complete {
+	int seeking;    /*! Nth match to return. */
+	int which;      /*! Which match currently on. */
+};
+
+static int complete_parking_lot_search(void *obj, void *arg, void *data, int flags)
+{
+	struct parking_lot_complete *search = data;
+	if (++search->which > search->seeking) {
+		return CMP_MATCH;
+	}
+	return 0;
+}
+
+static char *complete_parking_lot(const char *word, int seeking)
+{
+	char *ret = NULL;
+	struct parking_lot *lot;
+	struct ao2_container *global_lots = get_parking_lot_container();
+	struct parking_lot_complete search = {
+		.seeking = seeking,
+		};
+
+	lot = ao2_callback_data(global_lots, ast_strlen_zero(word) ? 0 : OBJ_PARTIAL_KEY,
+		complete_parking_lot_search, (char *) word, &search);
+
+	if (!lot) {
+		return NULL;
+	}
+
+	ret = ast_strdup(lot->name);
+	ao2_ref(lot, -1);
+	return ret;
+}
+
+/* \brief command parking show <name> */
+static char *handle_show_parking_lot_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "parking show";
+		e->usage =
+			"Usage: parking show [name]\n"
+			"	Shows a list of parking lots or details of a specific parking lot.";
+		return NULL;
+	case CLI_GENERATE:
+		if (a->pos == 2) {
+			return complete_parking_lot(a->word, a->n);
+		}
+		return NULL;
+	}
+
+	ast_cli(a->fd, "\n");
+
+	if (a->argc == 2) {
+		cli_display_parking_lot_list(a->fd);
+		return CLI_SUCCESS;
+	}
+
+	if (a->argc == 3) {
+		cli_display_parking_lot(a->fd, a->argv[2]);
+		return CLI_SUCCESS;
+	}
+
+	return CLI_SHOWUSAGE;
+}
+
+static struct ast_cli_entry cli_parking_lot[] = {
+	AST_CLI_DEFINE(handle_show_parking_lot_cmd, "Show a parking lot or a list of all parking lots."),
+};
+
+int load_parking_ui(void)
+{
+	return ast_cli_register_multiple(cli_parking_lot, ARRAY_LEN(cli_parking_lot));
+}
+
+void unload_parking_ui(void)
+{
+	ast_cli_unregister_multiple(cli_parking_lot, ARRAY_LEN(cli_parking_lot));
+}
diff --git a/res/parking/res_parking.h b/res/parking/res_parking.h
new file mode 100644
index 0000000000000000000000000000000000000000..7f66d6e8f04bd1cf77d9d2e20b203cb42b07199c
--- /dev/null
+++ b/res/parking/res_parking.h
@@ -0,0 +1,436 @@
+/*
+ * 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 Call Parking Resource Internal API
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk/pbx.h"
+#include "asterisk/bridging.h"
+#include "asterisk/parking.h"
+#include "asterisk/stasis_channels.h"
+
+#define DEFAULT_PARKING_LOT "default"
+#define DEFAULT_PARKING_EXTEN "700"
+#define PARK_DIAL_CONTEXT "park-dial"
+
+enum park_call_resolution {
+	PARK_UNSET = 0,		/*! Nothing set a resolution. This should never be observed in practice. */
+	PARK_ABANDON,		/*! The channel for the parked call hung up */
+	PARK_TIMEOUT,		/*! The parked call stayed parked until the parking lot timeout was reached and was removed */
+	PARK_FORCED,		/*! The parked call was forcibly terminated by an unusual means in Asterisk */
+	PARK_ANSWERED,		/*! The parked call was retrieved successfully */
+};
+
+enum parked_call_feature_options {
+	OPT_PARKEDPLAY = 0,
+	OPT_PARKEDTRANSFERS,
+	OPT_PARKEDREPARKING,
+	OPT_PARKEDHANGUP,
+	OPT_PARKEDRECORDING,
+};
+
+enum parking_lot_modes {
+	PARKINGLOT_NORMAL = 0,          /*! The parking lot is configured normally and can accept new calls. Disable on reload if the config isn't replaced.
+	                                 *  valid transitions: PARKINGLOT_DISABLED */
+	PARKINGLOT_DYNAMIC,             /*! The parking lot is a dynamically created parking lot. It can be parked to at any time. Disabled on last parked call leaving.
+	                                 *  valid transitions: PARKINGLOT_DISABLED */
+	PARKINGLOT_DISABLED,            /*! The parking lot is no longer linked to a parking lot in configuration. It can no longer be parked to.
+	                                 *  and it can not be parked to. This mode has no transitions. */
+};
+
+struct parking_lot_cfg {
+	int parking_start;                        /*!< First space in the parking lot */
+	int parking_stop;                         /*!< Last space in the parking lot */
+
+	unsigned int parkingtime;                 /*!< Analogous to parkingtime config option */
+	unsigned int comebackdialtime;            /*!< Analogous to comebackdialtime config option */
+	unsigned int parkfindnext;                /*!< Analogous to parkfindnext config option */
+	unsigned int parkext_exclusive;           /*!< Analogous to parkext_exclusive config option */
+	unsigned int parkaddhints;                /*!< Analogous to parkaddhints config option */
+	unsigned int comebacktoorigin;            /*!< Analogous to comebacktoorigin config option */
+	int parkedplay;                           /*!< Analogous to parkedplay config option */
+	int parkedcalltransfers;                  /*!< Analogous to parkedcalltransfers config option */
+	int parkedcallreparking;                  /*!< Analogous to parkedcallreparking config option */
+	int parkedcallhangup;                     /*!< Analogous to parkedcallhangup config option */
+	int parkedcallrecording;                  /*!< Analogous to parkedcallrecording config option */
+
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(name);               /*!< Name of the parking lot configuration object */
+		AST_STRING_FIELD(mohclass);           /*!< Analogous to mohclass config option */
+		AST_STRING_FIELD(parkext);            /*!< Analogous to parkext config option */
+		AST_STRING_FIELD(parking_con);        /*!< Analogous to context config option */
+		AST_STRING_FIELD(comebackcontext);    /*!< Analogous to comebackcontext config option */
+		AST_STRING_FIELD(courtesytone);       /*!< Analogous to courtesytone config option */
+	);
+};
+
+struct parking_lot {
+	int next_space;                           /*!< When using parkfindnext, which space we should start searching from next time we park */
+	struct ast_bridge *parking_bridge;        /*!< Bridged where parked calls will rest until they are answered or otherwise leave */
+	struct ao2_container *parked_users;       /*!< List of parked users rigidly ordered by their parking space */
+	struct parking_lot_cfg *cfg;              /*!< Reference to configuration object for the parking lot */
+	enum parking_lot_modes mode;              /*!< Whether a parking lot is operational, being reconfigured, primed for deletion, or dynamically created. */
+	int disable_mark;                         /*!< On reload, disable this parking lot if it doesn't receive a new configuration. */
+
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(name);               /*!< Name of the parking lot object */
+	);
+};
+
+struct parked_user {
+	struct ast_channel *chan;                 /*!< Parked channel */
+	struct ast_channel_snapshot *parker;      /*!< Snapshot of the channel that parked the call at the time of parking */
+	struct ast_channel_snapshot *retriever;   /*!< Snapshot of the channel that retrieves a parked call */
+	struct timeval start;                     /*!< When the call was parked */
+	int parking_space;                        /*!< Which parking space is used */
+	char comeback[AST_MAX_CONTEXT];           /*!< Where to go on parking timeout */
+	unsigned int time_limit;                  /*!< How long this specific channel may remain in the parking lot before timing out */
+	struct parking_lot *lot;      /*!< Which parking lot the user is parked to */
+	enum park_call_resolution resolution;     /*!< How did the parking session end? If the call is in a bridge, lock parked_user before checking/setting */
+};
+
+/*!
+ * \since 12
+ * \brief If a parking lot exists in the parking lot list already, update its status to match the provided
+ *        configuration and return a reference return a reference to it. Otherwise, create a parking lot
+ *        struct based on a parking lot configuration and return a reference to the new one.
+ *
+ * \param cfg The configuration being used as a reference to build the parking lot from.
+ *
+ * \retval A reference to the new parking lot
+ * \retval NULL if it was not found and could not be be allocated
+ *
+ * \note The parking lot will need to be unreffed if it ever falls out of scope
+ * \note The parking lot will automatically be added to the parking lot container if needed as part of this process
+ */
+struct parking_lot *parking_lot_build_or_update(struct parking_lot_cfg *cfg);
+
+/*!
+ * \since 12
+ * \brief Remove a parking lot from the usable lists if it is no longer involved in any calls and no configuration currently claims it
+ *
+ * \param lot Which parking lot is being checked for elimination
+ *
+ * \note This should generally be called when something is happening that could cause a parking lot to die such as a call being unparked or
+ *       a parking lot no longer existing in configurations.
+ */
+void parking_lot_remove_if_unused(struct parking_lot *lot);
+
+/*!
+ * \since 12
+ * \brief Create a new parking bridge
+ *
+ * \param bridge_lot Parking lot which the new bridge should be based on
+ *
+ * \retval NULL if the bridge can not be created
+ * \retval Newly created parking bridge
+ */
+struct ast_bridge *bridge_parking_new(struct parking_lot *bridge_lot);
+
+/*!
+ * \since 12
+ * \brief Get a reference to a parking lot's bridge. If it doesn't exist, create it and get a reference.
+ *
+ * \param lot Which parking lot we need the bridge from. This parking lot must be locked before calling this function.
+ *
+ * \retval A reference to the ast_bridge associated with the parking lot
+ * \retval NULL if it didn't already have a bridge and one couldn't be created
+ *
+ * \note This bridge will need to be unreffed if it ever falls out of scope.
+ */
+struct ast_bridge *parking_lot_get_bridge(struct parking_lot *lot);
+
+/*!
+ * \since 12
+ * \brief Get an available parking space within a parking lot.
+ *
+ * \param lot Which parking lot we are getting a space from
+ * \param target_override If there is a specific slot we want, provide it here and we'll start from that position
+ *
+ * \retval -1 if No slot can be found
+ * \retval integer value of parking space selected
+ *
+ * \note lot should be locked before this is called and unlocked only after a parked_user with the space
+ *       returned has been added to the parking lot.
+ */
+int parking_lot_get_space(struct parking_lot *lot, int target_override);
+
+/*!
+ * \since 12
+ * \brief Determine if there is a parked user in a parking space and pull it from the parking lot if there is.
+ *
+ * \param lot Parking lot being pulled from
+ * \param target If < 0   search for the first occupied space in the parking lot
+ *               If >= 0  Only pull from the indicated target
+ *
+ * \retval NULL if no parked user could be pulled from the requested parking lot at the requested parking space
+ * \retval reference to the requested parked user
+ *
+ * \note The parked user will be removed from parking lot as part of this process
+ * \note Remove this reference with ao2_cleanup once it falls out of scope.
+ */
+struct parked_user *parking_lot_retrieve_parked_user(struct parking_lot *lot, int target);
+
+/*!
+ * \since 12
+ * \brief Apply features based on the parking lot feature options
+ *
+ * \param chan Which channel's feature set is being modified
+ * \param lot parking lot which establishes the features used
+ * \param recipient_mode AST_FEATURE_FLAG_BYCALLER if the user is the retriever
+ *                       AST_FEATURE_FLAG_BYCALLEE if the user is the parkee
+ */
+void parked_call_retrieve_enable_features(struct ast_channel *chan, struct parking_lot *lot, int recipient_mode);
+
+/*!
+ * \since 12
+ * \brief Set necessary bridge roles on a channel that is about to enter a parking lot
+ *
+ * \param chan Entering channel
+ * \param lot The parking lot the channel will be entering
+ * \param force_ringing Use ringing instead of music on hold
+ */
+void parking_channel_set_roles(struct ast_channel *chan, struct parking_lot *lot, int force_ringing);
+
+/*!
+ * \since 12
+ * \brief custom callback function for ast_bridge_channel_queue_playfile which plays a parking space
+ *        and optionally hangs up the call afterwards based on the payload in playfile.
+ */
+void say_parking_space(struct ast_bridge_channel *bridge_channel, const char *payload);
+
+/*!
+ * \since 12
+ * \brief Setup timeout interval feature on an ast_bridge_features for parking
+ *
+ * \param features The ast_bridge_features we are establishing the interval hook on
+ * \param user The parked_user receiving the timeout duration limits
+ */
+void parking_set_duration(struct ast_bridge_features *features, struct parked_user *user);
+
+/*!
+ * \since 12
+ * \brief Get a pointer to the parking lot container for purposes such as iteration
+ *
+ * \retval pointer to the parking lot container.
+ */
+struct ao2_container *get_parking_lot_container(void);
+
+/*!
+ * \since 12
+ * \brief Find a parking lot based on its name
+ *
+ * \param lot_name Name of the parking lot sought
+ *
+ * \retval The parking lot if found
+ * \retval NULL if no parking lot with the name specified exists
+ *
+ * \note ao2_cleanup this reference when you are done using it or you'll cause leaks.
+ */
+struct parking_lot *parking_lot_find_by_name(const char *lot_name);
+
+/*!
+ * \since 12
+ * \brief Find parking lot name from channel
+ *
+ * \param chan The channel we want the parking lot name for
+ *
+ * \retval name of the channel's assigned parking lot if it is defined by the channel in some way
+ * \retval name of the default parking lot if it is not
+ *
+ * \note Channel needs to be locked while the returned string is in use.
+ */
+const char *find_channel_parking_lot_name(struct ast_channel *chan);
+
+/*!
+ * \since 12
+ * \brief Flattens a peer name so that it can be written to/found from PBX extensions
+ *
+ * \param peername unflattened peer name. This will be flattened in place, so expect it to change.
+ */
+void flatten_peername(char *peername);
+
+/*!
+ * \since 12
+ * \brief Set a channel's position in the PBX after timeout using the parking lot settings
+ *
+ * \param pu Parked user who is entering/reentering the PBX
+ * \param lot Parking lot the user was removed from.
+ *
+ * \retval 0 Position set successfully
+ * \retval -1 Failed to set the position
+ */
+int comeback_goto(struct parked_user *pu, struct parking_lot *lot);
+
+/*!
+ * \since 12
+ * \brief Pull a parked user out of its parking lot. Use this when you don't want to use the parked user afterwards.
+ * \param user The parked user being pulled.
+ *
+ * \retval 0 on success
+ * \retval -1 if the user didn't have its parking lot set
+ */
+int unpark_parked_user(struct parked_user *user);
+
+/*!
+ * \since 12
+ * \brief Publish a stasis parked call message for the channel indicating failure to park.
+ *
+ * \param parkee channel belonging to the failed parkee
+ */
+void publish_parked_call_failure(struct ast_channel *parkee);
+
+/*!
+ * \since 12
+ * \brief Publish a stasis parked call message for a given parked user
+ *
+ * \param pu pointer to a parked_user that we are generating the message for
+ * \param event_type What parked call event type is provoking this message
+ */
+void publish_parked_call(struct parked_user *pu, enum ast_parked_call_event_type event_type);
+
+/*!
+ * \since 12
+ * \brief Function to prepare a channel for parking by determining which parking bridge should
+ *        be used, setting up a park common datastore so that the parking bridge will have access
+ *        to necessary parking information when joining, and applying various bridge roles to the
+ *        channel.
+ *
+ * \param parkee The channel being preparred for parking
+ * \param parker The channel initiating the park; may be the parkee as well
+ * \param app_data arguments supplied to the Park application. May be NULL.
+ * \param silence_announcements optional pointer to an integer where we want to store the silence option flag
+ *        this value should be initialized to 0 prior to calling park_common_setup.
+ *
+ * \retval reference to a parking bridge if successful
+ * \retval NULL on failure
+ *
+ * \note ao2_cleanup this reference when you are done using it or you'll cause leaks.
+ */
+struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker,
+	const char *app_data, int *silence_announcements);
+
+struct park_common_datastore {
+	char *parker_uuid;           /*!< Unique ID of the channel parking the call. */
+	char *comeback_override;     /*!< Optional goto string for where to send the call after we are done */
+	int randomize;               /*!< Pick a parking space to enter on at random */
+	int time_limit;              /*!< time limit override. -1 values don't override, 0 for unlimited time, >0 for custom time limit in seconds */
+	int silence_announce;        /*!< Used when a call parks itself to keep it from hearing the parked call announcement */
+};
+
+/*!
+ * \since 12
+ * \brief Function that pulls data from the park common datastore on a channel in order to apply it to
+ *        the parked user struct upon bridging.
+ *
+ * \param parkee The channel entering parking with the datastore we are checking
+ * \param parker_uuid pointer to a string pointer for placing the name of the channel that parked parkee
+ * \param comeback_override pointer to a string pointer for placing the comeback_override option
+ * \param randomize integer pointer to an integer for placing the randomize option
+ * \param time_limit integer pointer to an integer for placing the time limit option
+ * \param silence_announce pointer to an integer for placing the silence_announcements option
+ */
+void get_park_common_datastore_data(struct ast_channel *parkee,
+		char **parker_uuid, char **comeback_override,
+		int *randomize, int *time_limit, int *silence_announce);
+
+/*!
+ * \since 12
+ * \brief Execution function for the parking application
+ *
+ * \param chan ast_channel entering the application
+ * \param data arguments to the application
+ *
+ * \retval 0 the application executed in such a way that the channel should proceed in the dial plan
+ * \retval -1 the channel should no longer proceed through the dial plan
+ *
+ * \note this function should only be used to register the parking application and not generally to park calls.
+ */
+int park_app_exec(struct ast_channel *chan, const char *data);
+
+/*!
+ * \since 12
+ * \brief Execution function for the parked call application
+ *
+ * \param chan ast_channel entering the application
+ * \param data arguments to the application
+ *
+ * \retval 0 the application executed in such a way that the channel should proceed in the dial plan
+ * \retval -1 the channel should no longer proceed through the dial plan
+ */
+int parked_call_app_exec(struct ast_channel *chan, const char *data);
+
+/*!
+ * \since 12
+ * \brief Execution function for the park and retrieve application
+ *
+ * \param chan ast_channel entering the application
+ * \param data arguments to the application
+ *
+ * \retval 0 the application executed in such a way that the channel should proceed in the dial plan
+ * \retval -1 the channel should no longer proceed through the dial plan
+ *
+ * \note this function should only be used to register the park and announce application and not generally to park and announce.
+ */
+int park_and_announce_app_exec(struct ast_channel *chan, const char *data);
+
+/*!
+ * \since 12
+ * \brief Register CLI commands
+ *
+ * \retval 0 if successful
+ * \retval -1 on failure
+ */
+int load_parking_ui(void);
+
+/*!
+ * \since 12
+ * \brief Unregister CLI commands
+ */
+void unload_parking_ui(void);
+
+/*!
+ * \since 12
+ * \brief Register manager actions and setup subscriptions for stasis events
+ */
+int load_parking_manager(void);
+
+/*!
+ * \since 12
+ * \brief Unregister manager actions and remove subscriptions for stasis events
+ */
+void unload_parking_manager(void);
+
+/*!
+ * \since 12
+ * \brief Register bridge features for parking
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int load_parking_bridge_features(void);
+
+/*!
+ * \since 12
+ * \brief Unregister features registered by load_parking_bridge_features
+ */
+void unload_parking_bridge_features(void);
diff --git a/res/res_parking.c b/res/res_parking.c
new file mode 100644
index 0000000000000000000000000000000000000000..72627f16461ffa06cb05f3eaa6b1f5cea297c0fc
--- /dev/null
+++ b/res/res_parking.c
@@ -0,0 +1,831 @@
+/*
+ * 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 Call Parking Resource
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+/*** MODULEINFO
+	<depend>bridge_holding</depend>
+	<support_level>core</support_level>
+ ***/
+
+/*** DOCUMENTATION
+	<configInfo name="res_parking" language="en_US">
+		<configFile name="res_parking.conf">
+			<configObject name="globals">
+				<synopsis>Options that apply to every parking lot</synopsis>
+			</configObject>
+			<configObject name="parking_lot">
+				<synopsis>Defined parking lots for res_parking to use to park calls on</synopsis>
+				<configOption name="context" default="parkedcalls">
+					<synopsis>The name of the context where calls are parked and picked up from.</synopsis>
+					<description><para>This option is only used if parkext is set.</para></description>
+				</configOption>
+				<configOption name="parkext">
+					<synopsis>Extension to park calls to this parking lot.</synopsis>
+					<description><para>If this option is used, this extension will automatically be created to place calls into
+                        parking lots. In addition, if parkext_exclusive is set for this parking lot, the name of the parking lot
+                        will be included in the application's arguments so that it only parks to this parking lot. The extension
+                        will be created in <literal>context</literal>. Using this option also creates extensions for retrieving
+                        parked calls from the parking spaces in the same context.</para></description>
+				</configOption>
+				<configOption name="parkext_exclusive" default="no">
+					<synopsis>If yes, the extension registered as parkext will park exclusively to this parking lot.</synopsis>
+				</configOption>
+				<configOption name="parkpos" default="701-750">
+					<synopsis>Numerical range of parking spaces which can be used to retrieve parked calls.</synopsis>
+					<description><para>If parkext is set, these extensions will automatically be mapped in <literal>context</literal>
+						in order to pick up calls parked to these parking spaces.</para></description>
+				</configOption>
+				<configOption name="parkinghints" default="no">
+					<synopsis>If yes, this parking lot will add hints automatically for parking spaces.</synopsis>
+				</configOption>
+				<configOption name="parkingtime" default="45">
+					<synopsis>Amount of time a call will remain parked before giving up (in seconds).</synopsis>
+				</configOption>
+				<configOption name="parkedmusicclass">
+					<synopsis>Which music class to use for parked calls. They will use the default if unspecified.</synopsis>
+				</configOption>
+				<configOption name="comebacktoorigin" default="yes">
+					<synopsis>Determines what should be done with the parked channel if no one picks it up before it times out.</synopsis>
+					<description><para>Valid Options:</para>
+						<enumlist>
+							<enum name="yes">
+								<para>Automatically have the parked channel dial the device that parked the call with dial
+									timeout set by the <literal>parkingtime</literal> option. When the call times out an extension
+									to dial the PARKER will automatically be created in the <literal>park-dial</literal> context with
+									an extension of the flattened parker device name. If the call is not answered, the parked channel
+									that is timing out will continue in the dial plan at that point if there are more priorities in
+									the extension (which won't be the case unless the dialplan deliberately includes such priorities
+									in the <literal>park-dial</literal> context through pattern matching or deliberately written
+									flattened peer extensions).</para>
+							</enum>
+							<enum name="no">
+								<para>Place the call into the PBX at <literal>comebackcontext</literal> instead. The extension will
+									still be set as the flattened peer name. If an extension the flattened peer name isn't available
+									then it will fall back to the <literal>s</literal> extension. If that also is unavailable it will
+									attempt to fall back to <literal>s@default</literal>. The normal dial extension will still be
+									created in the <literal>park-dial</literal> context with the extension also being the flattened
+									peer name.</para>
+							</enum>
+						</enumlist>
+						<note><para>Flattened Peer Names - Extensions can not include slash characters since those are used for pattern
+							matching. When a peer name is flattened, slashes become underscores. For example if the parker of a call
+							is called <literal>SIP/0004F2040001</literal> then flattened peer name and therefor the extensions created
+							and used on timeouts will be <literal>SIP_0004F204001</literal>.</para></note>
+						<note><para>When parking times out and the channel returns to the dial plan, the following variables are set:
+						</para></note>
+						<variablelist>
+							<variable name="PARKINGSLOT">
+								<para>extension that the call was parked in prior to timing out.</para>
+							</variable>
+							<variable name="PARKEDLOT">
+								<para>name of the lot that the call was parked in prior to timing out.</para>
+							</variable>
+							<variable name="PARKER">
+								<para>The device that parked the call</para>
+							</variable>
+						</variablelist>
+					</description>
+				</configOption>
+				<configOption name="comebackdialtime" default="30">
+					<synopsis>Timeout for the Dial extension created to call back the parker when a parked call times out.</synopsis>
+				</configOption>
+				<configOption name="comebackcontext" default="parkedcallstimeout">
+					<synopsis>Context where parked calls will enter the PBX on timeout when comebacktoorigin=no</synopsis>
+					<description><para>The extension the call enters will prioritize the flattened peer name in this context.
+						If the flattened peer name extension is unavailable, then the 's' extension in this context will be
+						used. If that also is unavailable, the 's' extension in the 'default' context will be used.</para>
+					</description>
+				</configOption>
+				<configOption name="courtesytone">
+					<synopsis>If the name of a sound file is provided, use this as the courtesy tone</synopsis>
+					<description><para>By default, this tone is only played to the caller of a parked call. Who receives the tone
+						can be changed using the <literal>parkedplay</literal> option.</para>
+					</description>
+				</configOption>
+				<configOption name="parkedplay" default="caller">
+					<synopsis>Who we should play the courtesytone to on the pickup of a parked call from this lot</synopsis>
+					<description>
+						<enumlist>
+							<enum name="no"><para>Apply to neither side.</para></enum>
+							<enum name="caller"><para>Apply to only to the caller picking up the parked call.</para></enum>
+							<enum name="callee"><para>Apply to only to the parked call being picked up.</para></enum>
+							<enum name="both"><para>Apply to both the caller and the callee.</para></enum>
+						</enumlist>
+						<note><para>If courtesy tone is not specified then this option will be ignored.</para></note>
+					</description>
+				</configOption>
+				<configOption name="parkedcalltransfers" default="no">
+					<synopsis>Apply the DTMF transfer features to the caller and/or callee when parked calls are picked up.</synopsis>
+					<description>
+						<xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
+					</description>
+				</configOption>
+				<configOption name="parkedcallreparking" default="no">
+					<synopsis>Apply the DTMF parking feature to the caller and/or callee when parked calls are picked up.</synopsis>
+					<description>
+						<xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
+					</description>
+				</configOption>
+				<configOption name="parkedcallhangup" default="no">
+					<synopsis>Apply the DTMF Hangup feature to the caller and/or callee when parked calls are picked up.</synopsis>
+					<description>
+						<xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
+					</description>
+				</configOption>
+				<configOption name="parkedcallrecording" default="no">
+					<synopsis>Apply the DTMF recording features to the caller and/or callee when parked calls are picked up</synopsis>
+					<description>
+						<xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
+					</description>
+				</configOption>
+				<configOption name="findslot" default="first">
+					<synopsis>Rule to use when trying to figure out which parking space a call should be parked with.</synopsis>
+					<description>
+						<enumlist>
+							<enum name="first"><para>Always try to place in the lowest available space in the parking lot</para></enum>
+							<enum name="next"><para>Track the last parking space used and always attempt to use the one immediately after.
+							</para></enum>
+						</enumlist>
+					</description>
+				</configOption>
+				<configOption name="courtesytone">
+					<synopsis>If set, the sound set will be played to whomever is set by parkedplay</synopsis>
+				</configOption>
+			</configObject>
+		</configFile>
+	</configInfo>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "parking/res_parking.h"
+#include "asterisk/config.h"
+#include "asterisk/config_options.h"
+#include "asterisk/event.h"
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/features.h"
+#include "asterisk/manager.h"
+
+#define PARKED_CALL_APPLICATION "ParkedCall"
+#define PARK_AND_ANNOUNCE_APPLICATION "ParkAndAnnounce"
+
+/* TODO Add unit tests for parking */
+
+static int parking_lot_sort_fn(const void *obj_left, const void *obj_right, int flags)
+{
+	const struct parking_lot *left = obj_left;
+	const struct parking_lot *right = obj_right;
+	const char *right_key = obj_right;
+	int cmp;
+
+	switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+	default:
+	case OBJ_POINTER:
+		right_key = right->name;
+		/* Fall through */
+	case OBJ_KEY:
+		cmp = strcmp(left->name, right_key);
+		break;
+	case OBJ_PARTIAL_KEY:
+		cmp = strncmp(left->name, right_key, strlen(right_key));
+	}
+	return cmp;
+}
+
+/*! All parking lots that are currently alive in some fashion can be obtained from here */
+static struct ao2_container *parking_lot_container;
+
+static void *parking_config_alloc(void);
+
+static void *parking_lot_cfg_alloc(const char *cat);
+static void *named_item_find(struct ao2_container *container, const char *name); /* XXX This is really just a generic string find. Move to astobj2.c? */
+
+static int config_parking_preapply(void);
+static void link_configured_disable_marked_lots(void);
+
+struct parking_global_config {
+	/* TODO Implement dynamic parking lots. Entirely. */
+	int parkeddynamic;
+};
+
+struct parking_config {
+	struct parking_global_config *global;
+	struct ao2_container *parking_lots;
+};
+
+static struct aco_type global_option = {
+	.type = ACO_GLOBAL,
+	.name = "globals",
+	.item_offset = offsetof(struct parking_config, global),
+	.category_match = ACO_WHITELIST,
+	.category = "^general$",
+};
+
+struct aco_type *global_options[] = ACO_TYPES(&global_option);
+
+static struct aco_type parking_lot_type = {
+	.type = ACO_ITEM,
+	.name = "parking_lot",
+	.category_match = ACO_BLACKLIST,
+	.category = "^(general)$",
+	.item_alloc = parking_lot_cfg_alloc,
+	.item_find = named_item_find,
+	.item_offset = offsetof(struct parking_config, parking_lots),
+};
+
+struct aco_type *parking_lot_types[] = ACO_TYPES(&parking_lot_type);
+
+struct aco_file parking_lot_conf = {
+	.filename = "res_parking.conf",
+	.types = ACO_TYPES(&global_option, &parking_lot_type),
+};
+
+static AO2_GLOBAL_OBJ_STATIC(globals);
+
+CONFIG_INFO_STANDARD(cfg_info, globals, parking_config_alloc,
+	.files = ACO_FILES(&parking_lot_conf),
+	.pre_apply_config = config_parking_preapply,
+	.post_apply_config = link_configured_disable_marked_lots,
+);
+
+static int parking_lot_cfg_hash_fn(const void *obj, const int flags)
+{
+	const struct parking_lot_cfg *entry;
+	const char *key;
+
+	switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+	case OBJ_KEY:
+		key = obj;
+		return ast_str_hash(key);
+	case OBJ_PARTIAL_KEY:
+		ast_assert(0);
+		return 0;
+	default:
+		entry = obj;
+		return ast_str_hash(entry->name);
+	}
+}
+
+static int parking_lot_cfg_cmp_fn(void *obj, void *arg, const int flags)
+{
+	struct parking_lot_cfg *entry1 = obj;
+
+	char *key;
+	size_t key_size;
+	struct parking_lot_cfg *entry2;
+
+	switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+	case OBJ_KEY:
+		key = arg;
+		return (!strcmp(entry1->name, key)) ? CMP_MATCH : 0;
+	case OBJ_PARTIAL_KEY:
+		key = arg;
+		key_size = strlen(key);
+		return (!strncmp(entry1->name, key, key_size)) ? CMP_MATCH : 0;
+	case OBJ_POINTER:
+		entry2 = arg;
+		return (!strcmp(entry1->name, entry2->name)) ? CMP_MATCH : 0;
+	default:
+		return CMP_STOP;
+	}
+}
+
+/*! \brief destructor for parking_config */
+static void parking_config_destructor(void *obj)
+{
+	struct parking_config *cfg = obj;
+	ao2_cleanup(cfg->parking_lots);
+	ao2_cleanup(cfg->global);
+}
+
+/*! \brief destructor for parking_global_config */
+static void parking_global_config_destructor(void *obj)
+{
+	/* For now, do nothing. */
+}
+
+/*! \brief allocator callback for parking_config. Notice it returns void * since it is only used by the backend config code */
+static void *parking_config_alloc(void)
+{
+	RAII_VAR(struct parking_config *, cfg, NULL, ao2_cleanup);
+
+	if (!(cfg = ao2_alloc(sizeof(*cfg), parking_config_destructor))) {
+		return NULL;
+	}
+
+	if (!(cfg->parking_lots = ao2_container_alloc(37, parking_lot_cfg_hash_fn, parking_lot_cfg_cmp_fn))) {
+		return NULL;
+	}
+
+	if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), parking_global_config_destructor))) {
+		return NULL;
+	}
+
+	/* Bump the ref count since RAII_VAR is going to eat one */
+	ao2_ref(cfg, +1);
+	return cfg;
+}
+
+void parking_lot_remove_if_unused(struct parking_lot *lot)
+{
+
+	if (lot->mode != PARKINGLOT_DISABLED) {
+		return;
+	}
+
+
+	if (!ao2_container_count(lot->parked_users)) {
+		ao2_unlink(parking_lot_container, lot);
+	}
+}
+
+static void parking_lot_disable(struct parking_lot *lot)
+{
+	lot->mode = PARKINGLOT_DISABLED;
+	parking_lot_remove_if_unused(lot);
+}
+
+/*! \brief Destroy a parking lot cfg object */
+static void parking_lot_cfg_destructor(void *obj)
+{
+	struct parking_lot_cfg *lot_cfg = obj;
+
+	ast_string_field_free_memory(lot_cfg);
+}
+
+/* The arg just needs to have the parking space with it */
+static int parked_user_cmp_fn(void *obj, void *arg, int flags)
+{
+	int *search_space = arg;
+	struct parked_user *user = obj;
+	int object_space = user->parking_space;
+
+	if (*search_space == object_space) {
+		return CMP_MATCH;
+	}
+	return 0;
+}
+
+static int parked_user_sort_fn(const void *obj_left, const void *obj_right, int flags)
+{
+	const struct parked_user *left = obj_left;
+	const struct parked_user *right = obj_right;
+
+	return left->parking_space - right->parking_space;
+}
+
+/*!
+ * \brief create a parking lot structure
+ * \param cat name given to the parking lot
+ * \retval NULL failure
+ * \retval non-NULL successfully allocated parking lot
+ */
+static void *parking_lot_cfg_alloc(const char *cat)
+{
+	struct parking_lot_cfg *lot_cfg;
+
+	lot_cfg = ao2_alloc(sizeof(*lot_cfg), parking_lot_cfg_destructor);
+	if (!lot_cfg) {
+		return NULL;
+	}
+
+	if (ast_string_field_init(lot_cfg, 32)) {
+		ao2_cleanup(lot_cfg);
+		return NULL;
+	}
+
+	ast_string_field_set(lot_cfg, name, cat);
+
+	return lot_cfg;
+}
+
+/*!
+ * XXX This is actually incredibly generic and might be better placed in something like astobj2 if there isn't already an equivalent
+ * \brief find an item in a container by its name
+ *
+ * \param container ao2container where we want the item from
+ * \param key name of the item wanted to be found
+ *
+ * \retval pointer to the parking lot if available. NULL if not found.
+ */
+static void *named_item_find(struct ao2_container *container, const char *name)
+{
+	return ao2_find(container, name, OBJ_KEY);
+}
+
+/*!
+ * \brief Custom field handler for parking positions
+ */
+static int option_handler_parkpos(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct parking_lot_cfg *lot_cfg = obj;
+	int low;
+	int high;
+
+	if (sscanf(var->value, "%30d-%30d", &low, &high) != 2) {
+		ast_log(LOG_WARNING, "Format for parking positions is a-b, where a and b are numbers\n");
+	} else if (high < low || low <= 0 || high <= 0) {
+		ast_log(LOG_WARNING, "Format for parking positions is a-b, where a <= b\n");
+	} else {
+		lot_cfg->parking_start = low;
+		lot_cfg->parking_stop = high;
+		return 0;
+	}
+	return -1;
+}
+
+/*!
+ * \brief Custom field handler for the findslot option
+ */
+static int option_handler_findslot(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct parking_lot_cfg *lot_cfg = obj;
+
+	if (!strcmp(var->value, "first")) {
+		lot_cfg->parkfindnext = 0;
+	} else if (!strcmp(var->value, "next")) {
+		lot_cfg->parkfindnext = 1;
+	} else {
+		ast_log(LOG_WARNING, "value '%s' is not valid for findslot option.\n", var->value);
+		return -1;
+	}
+
+	return 0;
+}
+
+/*!
+ * \brief Maps string values for option_handler_parkedfeature to their ENUM values
+ */
+static int parking_feature_flag_cfg(int *param, const char *var)
+{
+	if (ast_false(var)) {
+		*param = 0;
+	} else if (!strcasecmp(var, "both")) {
+		*param = AST_FEATURE_FLAG_BYBOTH;
+	} else if (!strcasecmp(var, "caller")) {
+		*param = AST_FEATURE_FLAG_BYCALLER;
+	} else if (!strcasecmp(var, "callee")) {
+		*param = AST_FEATURE_FLAG_BYCALLEE;
+	} else {
+		return -1;
+	}
+
+	return 0;
+}
+
+/*!
+ * \brief Custom field handler for feature mapping on parked call pickup options
+ */
+static int option_handler_parkedfeature(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct parking_lot_cfg *cfg = obj;
+	enum parked_call_feature_options option = aco_option_get_flags(opt);
+	int *parameter = NULL;
+
+	switch (option) {
+	case OPT_PARKEDPLAY:
+		parameter = &cfg->parkedplay;
+		break;
+	case OPT_PARKEDTRANSFERS:
+		parameter = &cfg->parkedcalltransfers;
+		break;
+	case OPT_PARKEDREPARKING:
+		parameter = &cfg->parkedcallreparking;
+		break;
+	case OPT_PARKEDHANGUP:
+		parameter = &cfg->parkedcallhangup;
+		break;
+	case OPT_PARKEDRECORDING:
+		parameter = &cfg->parkedcallrecording;
+		break;
+	}
+
+	if (!parameter) {
+		ast_log(LOG_ERROR, "Unable to handle option '%s'\n", var->name);
+		return -1;
+	}
+
+	if (parking_feature_flag_cfg(parameter, var->value)) {
+		ast_log(LOG_ERROR, "'%s' is not a valid value for parking lot option '%s'\n", var->value, var->name);
+		return -1;
+	}
+
+	return 0;
+}
+
+struct ao2_container *get_parking_lot_container(void)
+{
+	return parking_lot_container;
+}
+
+struct parking_lot *parking_lot_find_by_name(const char *lot_name)
+{
+	struct parking_lot *lot = named_item_find(parking_lot_container, lot_name);
+	return lot;
+}
+
+const char *find_channel_parking_lot_name(struct ast_channel *chan)
+{
+	const char *name;
+
+	/* The channel variable overrides everything */
+	name = pbx_builtin_getvar_helper(chan, "PARKINGLOT");
+	if (ast_strlen_zero(name) && !ast_strlen_zero(ast_channel_parkinglot(chan))) {
+		/* Use the channel's parking lot. */
+		name = ast_channel_parkinglot(chan);
+	}
+
+	/* If the name couldn't be pulled from that either, use the default parking lot name. */
+	if (ast_strlen_zero(name)) {
+		name = DEFAULT_PARKING_LOT;
+	}
+
+	return name;
+}
+
+static void parking_lot_destructor(void *obj)
+{
+	struct parking_lot *lot = obj;
+
+	if (lot->parking_bridge) {
+		ast_bridge_destroy(lot->parking_bridge);
+	}
+	ao2_cleanup(lot->parked_users);
+	ao2_cleanup(lot->cfg);
+	ast_string_field_free_memory(lot);
+}
+
+static struct parking_lot *alloc_new_parking_lot(struct parking_lot_cfg *lot_cfg)
+{
+	struct parking_lot *lot;
+	if (!(lot = ao2_alloc(sizeof(*lot), parking_lot_destructor))) {
+		return NULL;
+	}
+
+	if (ast_string_field_init(lot, 32)) {
+		return NULL;
+	}
+
+	/* Create parked user ordered list */
+	lot->parked_users = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_RWLOCK,
+		AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
+		parked_user_sort_fn,
+		parked_user_cmp_fn);
+
+	if (!lot->parked_users) {
+		ao2_cleanup(lot);
+		return NULL;
+	}
+
+	ast_string_field_set(lot, name, lot_cfg->name);
+	return lot;
+}
+
+struct parking_lot *parking_lot_build_or_update(struct parking_lot_cfg *lot_cfg)
+{
+	struct parking_lot *lot;
+	struct parking_lot_cfg *replaced_cfg = NULL;
+	int found = 0;
+
+	/* Start by trying to find it. If that works we can skip the rest. */
+	lot = named_item_find(parking_lot_container, lot_cfg->name);
+	if (!lot) {
+		lot = alloc_new_parking_lot(lot_cfg);
+
+		/* If we still don't have a lot, we failed to alloc one. */
+		if (!lot) {
+			return NULL;
+		}
+	} else {
+		found = 1;
+	}
+
+	/* Set the configuration reference. Unref the one currently in the lot if it's there. */
+	if (lot->cfg) {
+		replaced_cfg = lot->cfg;
+	}
+
+	ao2_ref(lot_cfg, +1);
+	lot->cfg = lot_cfg;
+
+	ao2_cleanup(replaced_cfg);
+
+	/* Set the operating mode to normal since the parking lot has a configuration. */
+	lot->disable_mark = 0;
+	lot->mode = PARKINGLOT_NORMAL;
+
+	if (!found) {
+		/* Link after configuration is set since a lot without configuration will cause all kinds of trouble. */
+		ao2_link(parking_lot_container, lot);
+	};
+
+	return lot;
+}
+
+static void generate_or_link_lots_to_configs(void)
+{
+	RAII_VAR(struct parking_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
+	struct parking_lot_cfg *lot_cfg;
+	struct ao2_iterator iter;
+
+	for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
+		RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
+		lot = parking_lot_build_or_update(lot_cfg);
+	}
+
+	ao2_iterator_destroy(&iter);
+}
+
+/* Preapply */
+
+static int verify_default_parking_lot(void)
+{
+	struct parking_config *cfg = aco_pending_config(&cfg_info);
+	RAII_VAR(struct parking_lot_cfg *, lot_cfg, NULL, ao2_cleanup);
+
+	if (!cfg) {
+		return 0;
+	}
+
+	lot_cfg = ao2_find(cfg->parking_lots, DEFAULT_PARKING_LOT, OBJ_KEY);
+	if (!lot_cfg) {
+		lot_cfg = parking_lot_cfg_alloc(DEFAULT_PARKING_LOT);
+		if (!lot_cfg) {
+			return -1;
+		}
+		ast_log(AST_LOG_NOTICE, "Adding %s profile to res_parking\n", DEFAULT_PARKING_LOT);
+		aco_set_defaults(&parking_lot_type, DEFAULT_PARKING_LOT, lot_cfg);
+		ast_string_field_set(lot_cfg, parkext, DEFAULT_PARKING_EXTEN);
+		ao2_link(cfg->parking_lots, lot_cfg);
+	}
+
+	return 0;
+}
+
+static void mark_lots_as_disabled(void)
+{
+	struct ao2_iterator iter;
+	struct parking_lot *lot;
+
+	for (iter = ao2_iterator_init(parking_lot_container, 0); (lot = ao2_iterator_next(&iter)); ao2_ref(lot, -1)) {
+		/* We aren't concerned with dynamic lots */
+		if (lot->mode == PARKINGLOT_DYNAMIC) {
+			continue;
+		}
+
+		lot->disable_mark = 1;
+	}
+
+	ao2_iterator_destroy(&iter);
+}
+
+static int config_parking_preapply(void)
+{
+	mark_lots_as_disabled();
+	return verify_default_parking_lot();
+}
+
+static void disable_marked_lots(void)
+{
+	struct ao2_iterator iter;
+	struct parking_lot *lot;
+
+	for (iter = ao2_iterator_init(parking_lot_container, 0); (lot = ao2_iterator_next(&iter)); ao2_ref(lot, -1)) {
+		if (lot->disable_mark) {
+			parking_lot_disable(lot);
+		}
+	}
+
+	ao2_iterator_destroy(&iter);
+}
+
+static void link_configured_disable_marked_lots(void)
+{
+	generate_or_link_lots_to_configs();
+	disable_marked_lots();
+}
+
+static int load_module(void)
+{
+	if (aco_info_init(&cfg_info)) {
+		goto error;
+	}
+
+	parking_lot_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_RWLOCK,
+		AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
+		parking_lot_sort_fn,
+		NULL);
+
+	if (!parking_lot_container) {
+		goto error;
+	}
+
+	/* Global options */
+
+	/* Register the per parking lot options. */
+	aco_option_register(&cfg_info, "parkext", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parkext));
+	aco_option_register(&cfg_info, "context", ACO_EXACT, parking_lot_types, "parkedcalls", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parking_con));
+	aco_option_register(&cfg_info, "parkingtime", ACO_EXACT, parking_lot_types, "45", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, parkingtime));
+	aco_option_register(&cfg_info, "comebacktoorigin", ACO_EXACT, parking_lot_types, "yes", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, comebacktoorigin));
+	aco_option_register(&cfg_info, "comebackcontext", ACO_EXACT, parking_lot_types, "parkedcallstimeout", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, comebackcontext));
+	aco_option_register(&cfg_info, "comebackdialtime", ACO_EXACT, parking_lot_types, "30", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, comebackdialtime));
+	aco_option_register(&cfg_info, "parkedmusicclass", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, mohclass));
+	aco_option_register(&cfg_info, "parkext_exclusive", ACO_EXACT, parking_lot_types, "no", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, parkext_exclusive));
+	aco_option_register(&cfg_info, "parkinghints", ACO_EXACT, parking_lot_types, "no", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, parkaddhints));
+	aco_option_register(&cfg_info, "courtesytone", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, courtesytone));
+
+	/* More complicated parking lot options that require special handling */
+	aco_option_register_custom(&cfg_info, "parkpos", ACO_EXACT, parking_lot_types, "701-750", option_handler_parkpos, 0);
+	aco_option_register_custom(&cfg_info, "findslot", ACO_EXACT, parking_lot_types, "first", option_handler_findslot, 0);
+	aco_option_register_custom(&cfg_info, "parkedplay", ACO_EXACT, parking_lot_types, "caller", option_handler_parkedfeature, OPT_PARKEDPLAY);
+	aco_option_register_custom(&cfg_info, "parkedcalltransfers", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDTRANSFERS);
+	aco_option_register_custom(&cfg_info, "parkedcallreparking", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDREPARKING);
+	aco_option_register_custom(&cfg_info, "parkedcallhangup", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDHANGUP);
+	aco_option_register_custom(&cfg_info, "parkedcallrecording", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDRECORDING);
+
+	if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
+		goto error;
+	}
+
+	if (ast_register_application_xml(PARK_APPLICATION, park_app_exec)) {
+		goto error;
+	}
+
+	if (ast_register_application_xml(PARKED_CALL_APPLICATION, parked_call_app_exec)) {
+		goto error;
+	}
+
+	if (ast_register_application_xml(PARK_AND_ANNOUNCE_APPLICATION, park_and_announce_app_exec)) {
+		goto error;
+	}
+
+	if (load_parking_ui()) {
+		goto error;
+	}
+
+	if (load_parking_manager()) {
+		goto error;
+	}
+
+	if (load_parking_bridge_features()) {
+		goto error;
+	}
+
+	/* TODO Dialplan generation for parking lots that set parkext */
+	/* TODO Generate hints for parking lots that set parkext and have hints enabled */
+
+	return AST_MODULE_LOAD_SUCCESS;
+
+error:
+	aco_info_destroy(&cfg_info);
+	return AST_MODULE_LOAD_DECLINE;
+}
+
+static int reload_module(void)
+{
+	if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	return 0;
+}
+
+static int unload_module(void)
+{
+	/* XXX Parking is currently unloadable due to the fact that it loads features which could cause
+	 *     significant problems if they disappeared while a channel still had access to them.
+	 */
+	return -1;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Call Parking Resource",
+	.load = load_module,
+	.unload = unload_module,
+	.reload = reload_module,
+);
diff --git a/res/res_stasis_json_events.c b/res/res_stasis_json_events.c
index f843310b62eb38b469b6f9ee0a276d8e3644b94d..10d36be424c49ba4a47fb902aee9b87135d69b71 100644
--- a/res/res_stasis_json_events.c
+++ b/res/res_stasis_json_events.c
@@ -43,18 +43,32 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/json.h"
 #include "stasis_json/resource_events.h"
 #include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_bridging.h"
 
-struct ast_json *stasis_json_event_channel_snapshot_create(
-	struct ast_channel_snapshot *channel_snapshot
+struct ast_json *stasis_json_event_channel_userevent_create(
+	struct ast_channel_snapshot *channel_snapshot,
+	struct ast_json *blob
 	)
 {
 	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
 	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
+	struct ast_json *validator;
 	int ret;
 
 	ast_assert(channel_snapshot != NULL);
+	ast_assert(blob != NULL);
+	ast_assert(ast_json_object_get(blob, "channel") == NULL);
+	ast_assert(ast_json_object_get(blob, "type") == NULL);
 
-	event = ast_json_object_create();
+	validator = ast_json_object_get(blob, "eventname");
+	if (validator) {
+		/* do validation? XXX */
+	} else {
+		/* fail message generation if the required parameter doesn't exist */
+		return NULL;
+	}
+
+	event = ast_json_deep_copy(blob);
 	if (!event) {
 		return NULL;
 	}
@@ -65,7 +79,36 @@ struct ast_json *stasis_json_event_channel_snapshot_create(
 		return NULL;
 	}
 
-	message = ast_json_pack("{s: o}", "channel_snapshot", ast_json_ref(event));
+	message = ast_json_pack("{s: o}", "channel_userevent", ast_json_ref(event));
+	if (!message) {
+		return NULL;
+	}
+
+	return ast_json_ref(message);
+}
+
+struct ast_json *stasis_json_event_bridge_created_create(
+	struct ast_bridge_snapshot *bridge_snapshot
+	)
+{
+	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
+	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
+	int ret;
+
+	ast_assert(bridge_snapshot != NULL);
+
+	event = ast_json_object_create();
+	if (!event) {
+		return NULL;
+	}
+
+	ret = ast_json_object_set(event,
+		"bridge", ast_bridge_snapshot_to_json(bridge_snapshot));
+	if (ret) {
+		return NULL;
+	}
+
+	message = ast_json_pack("{s: o}", "bridge_created", ast_json_ref(event));
 	if (!message) {
 		return NULL;
 	}
@@ -123,6 +166,35 @@ struct ast_json *stasis_json_event_channel_destroyed_create(
 	return ast_json_ref(message);
 }
 
+struct ast_json *stasis_json_event_channel_snapshot_create(
+	struct ast_channel_snapshot *channel_snapshot
+	)
+{
+	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
+	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
+	int ret;
+
+	ast_assert(channel_snapshot != NULL);
+
+	event = ast_json_object_create();
+	if (!event) {
+		return NULL;
+	}
+
+	ret = ast_json_object_set(event,
+		"channel", ast_channel_snapshot_to_json(channel_snapshot));
+	if (ret) {
+		return NULL;
+	}
+
+	message = ast_json_pack("{s: o}", "channel_snapshot", ast_json_ref(event));
+	if (!message) {
+		return NULL;
+	}
+
+	return ast_json_ref(message);
+}
+
 struct ast_json *stasis_json_event_channel_caller_id_create(
 	struct ast_channel_snapshot *channel_snapshot,
 	struct ast_json *blob
@@ -217,6 +289,35 @@ struct ast_json *stasis_json_event_channel_hangup_request_create(
 	return ast_json_ref(message);
 }
 
+struct ast_json *stasis_json_event_bridge_destroyed_create(
+	struct ast_bridge_snapshot *bridge_snapshot
+	)
+{
+	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
+	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
+	int ret;
+
+	ast_assert(bridge_snapshot != NULL);
+
+	event = ast_json_object_create();
+	if (!event) {
+		return NULL;
+	}
+
+	ret = ast_json_object_set(event,
+		"bridge", ast_bridge_snapshot_to_json(bridge_snapshot));
+	if (ret) {
+		return NULL;
+	}
+
+	message = ast_json_pack("{s: o}", "bridge_destroyed", ast_json_ref(event));
+	if (!message) {
+		return NULL;
+	}
+
+	return ast_json_ref(message);
+}
+
 struct ast_json *stasis_json_event_application_replaced_create(
 	struct ast_json *blob
 	)
@@ -299,41 +400,36 @@ struct ast_json *stasis_json_event_channel_varset_create(
 	return ast_json_ref(message);
 }
 
-struct ast_json *stasis_json_event_channel_userevent_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
+struct ast_json *stasis_json_event_channel_left_bridge_create(
+	struct ast_bridge_snapshot *bridge_snapshot,
+	struct ast_channel_snapshot *channel_snapshot
 	)
 {
 	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
 	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	struct ast_json *validator;
 	int ret;
 
 	ast_assert(channel_snapshot != NULL);
-	ast_assert(blob != NULL);
-	ast_assert(ast_json_object_get(blob, "channel") == NULL);
-	ast_assert(ast_json_object_get(blob, "type") == NULL);
+	ast_assert(bridge_snapshot != NULL);
 
-	validator = ast_json_object_get(blob, "eventname");
-	if (validator) {
-		/* do validation? XXX */
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
+	event = ast_json_object_create();
+	if (!event) {
 		return NULL;
 	}
 
-	event = ast_json_deep_copy(blob);
-	if (!event) {
+	ret = ast_json_object_set(event,
+		"channel", ast_channel_snapshot_to_json(channel_snapshot));
+	if (ret) {
 		return NULL;
 	}
 
 	ret = ast_json_object_set(event,
-		"channel", ast_channel_snapshot_to_json(channel_snapshot));
+		"bridge", ast_bridge_snapshot_to_json(bridge_snapshot));
 	if (ret) {
 		return NULL;
 	}
 
-	message = ast_json_pack("{s: o}", "channel_userevent", ast_json_ref(event));
+	message = ast_json_pack("{s: o}", "channel_left_bridge", ast_json_ref(event));
 	if (!message) {
 		return NULL;
 	}
@@ -491,6 +587,43 @@ struct ast_json *stasis_json_event_channel_state_change_create(
 	return ast_json_ref(message);
 }
 
+struct ast_json *stasis_json_event_channel_entered_bridge_create(
+	struct ast_bridge_snapshot *bridge_snapshot,
+	struct ast_channel_snapshot *channel_snapshot
+	)
+{
+	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
+	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
+	int ret;
+
+	ast_assert(channel_snapshot != NULL);
+	ast_assert(bridge_snapshot != NULL);
+
+	event = ast_json_object_create();
+	if (!event) {
+		return NULL;
+	}
+
+	ret = ast_json_object_set(event,
+		"channel", ast_channel_snapshot_to_json(channel_snapshot));
+	if (ret) {
+		return NULL;
+	}
+
+	ret = ast_json_object_set(event,
+		"bridge", ast_bridge_snapshot_to_json(bridge_snapshot));
+	if (ret) {
+		return NULL;
+	}
+
+	message = ast_json_pack("{s: o}", "channel_entered_bridge", ast_json_ref(event));
+	if (!message) {
+		return NULL;
+	}
+
+	return ast_json_ref(message);
+}
+
 struct ast_json *stasis_json_event_channel_dtmf_received_create(
 	struct ast_channel_snapshot *channel_snapshot,
 	struct ast_json *blob
diff --git a/res/res_stasis_json_events.exports.in b/res/res_stasis_json_events.exports.in
index 5a260a5f3a24c717c6b0b2aec75c85c214dc1736..e3f59ca763f27111fe6cac1ceca0aa83d7cfe7a2 100644
--- a/res/res_stasis_json_events.exports.in
+++ b/res/res_stasis_json_events.exports.in
@@ -1,16 +1,20 @@
 {
 	global:
-		LINKER_SYMBOL_PREFIXstasis_json_event_channel_snapshot_create;
+		LINKER_SYMBOL_PREFIXstasis_json_event_channel_userevent_create;
+		LINKER_SYMBOL_PREFIXstasis_json_event_bridge_created_create;
 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_destroyed_create;
+		LINKER_SYMBOL_PREFIXstasis_json_event_channel_snapshot_create;
 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_caller_id_create;
 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_hangup_request_create;
+		LINKER_SYMBOL_PREFIXstasis_json_event_bridge_destroyed_create;
 		LINKER_SYMBOL_PREFIXstasis_json_event_application_replaced_create;
 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_varset_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_channel_userevent_create;
+		LINKER_SYMBOL_PREFIXstasis_json_event_channel_left_bridge_create;
 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_created_create;
 		LINKER_SYMBOL_PREFIXstasis_json_event_stasis_start_create;
 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_dialplan_create;
 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_state_change_create;
+		LINKER_SYMBOL_PREFIXstasis_json_event_channel_entered_bridge_create;
 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_dtmf_received_create;
 		LINKER_SYMBOL_PREFIXstasis_json_event_stasis_end_create;
 	local:
diff --git a/res/stasis_json/resource_events.h b/res/stasis_json/resource_events.h
index bd1c3263b369b465cdea7f3f3d7e16793b4af727..63abe0f85d8d61a22d982458b27f2cb3c181f475 100644
--- a/res/stasis_json/resource_events.h
+++ b/res/stasis_json/resource_events.h
@@ -38,17 +38,33 @@
 #define _ASTERISK_RESOURCE_EVENTS_H
 
 struct ast_channel_snapshot;
+struct ast_bridge_snapshot;
 
 /*!
- * \brief Some part of channel state changed.
+ * \brief User-generated event with additional user-defined fields in the object.
  *
- * \param channel The channel to be used to generate this event
+ * \param channel The channel that signaled the user event.
+ * \param blob JSON blob containing the following parameters:
+ * - eventname: string - The name of the user event. (required)
  *
  * \retval NULL on error
  * \retval JSON (ast_json) describing the event
  */
-struct ast_json *stasis_json_event_channel_snapshot_create(
-	struct ast_channel_snapshot *channel_snapshot
+struct ast_json *stasis_json_event_channel_userevent_create(
+	struct ast_channel_snapshot *channel_snapshot,
+	struct ast_json *blob
+	);
+
+/*!
+ * \brief Notification that a bridge has been created.
+ *
+ * \param bridge The bridge to be used to generate this event
+ *
+ * \retval NULL on error
+ * \retval JSON (ast_json) describing the event
+ */
+struct ast_json *stasis_json_event_bridge_created_create(
+	struct ast_bridge_snapshot *bridge_snapshot
 	);
 
 /*!
@@ -67,6 +83,18 @@ struct ast_json *stasis_json_event_channel_destroyed_create(
 	struct ast_json *blob
 	);
 
+/*!
+ * \brief Some part of channel state changed.
+ *
+ * \param channel The channel to be used to generate this event
+ *
+ * \retval NULL on error
+ * \retval JSON (ast_json) describing the event
+ */
+struct ast_json *stasis_json_event_channel_snapshot_create(
+	struct ast_channel_snapshot *channel_snapshot
+	);
+
 /*!
  * \brief Channel changed Caller ID.
  *
@@ -99,6 +127,18 @@ struct ast_json *stasis_json_event_channel_hangup_request_create(
 	struct ast_json *blob
 	);
 
+/*!
+ * \brief Notification that a bridge has been destroyed.
+ *
+ * \param bridge The bridge to be used to generate this event
+ *
+ * \retval NULL on error
+ * \retval JSON (ast_json) describing the event
+ */
+struct ast_json *stasis_json_event_bridge_destroyed_create(
+	struct ast_bridge_snapshot *bridge_snapshot
+	);
+
 /*!
  * \brief Notification that another WebSocket has taken over for an application.
  *
@@ -129,18 +169,17 @@ struct ast_json *stasis_json_event_channel_varset_create(
 	);
 
 /*!
- * \brief User-generated event with additional user-defined fields in the object.
+ * \brief Notification that a channel has left a bridge.
  *
- * \param channel The channel that signaled the user event.
- * \param blob JSON blob containing the following parameters:
- * - eventname: string - The name of the user event. (required)
+ * \param channel The channel to be used to generate this event
+ * \param bridge The bridge to be used to generate this event
  *
  * \retval NULL on error
  * \retval JSON (ast_json) describing the event
  */
-struct ast_json *stasis_json_event_channel_userevent_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
+struct ast_json *stasis_json_event_channel_left_bridge_create(
+	struct ast_bridge_snapshot *bridge_snapshot,
+	struct ast_channel_snapshot *channel_snapshot
 	);
 
 /*!
@@ -198,6 +237,20 @@ struct ast_json *stasis_json_event_channel_state_change_create(
 	struct ast_channel_snapshot *channel_snapshot
 	);
 
+/*!
+ * \brief Notification that a channel has entered a bridge.
+ *
+ * \param channel The channel to be used to generate this event
+ * \param bridge The bridge to be used to generate this event
+ *
+ * \retval NULL on error
+ * \retval JSON (ast_json) describing the event
+ */
+struct ast_json *stasis_json_event_channel_entered_bridge_create(
+	struct ast_bridge_snapshot *bridge_snapshot,
+	struct ast_channel_snapshot *channel_snapshot
+	);
+
 /*!
  * \brief DTMF received on a channel.
  *
@@ -228,23 +281,26 @@ struct ast_json *stasis_json_event_stasis_end_create(
 /*
  * JSON models
  *
- * ChannelSnapshot
+ * ChannelUserevent
+ * - eventname: string (required)
+ * BridgeCreated
  * ChannelDestroyed
  * - cause: integer (required)
  * - cause_txt: string (required)
+ * ChannelSnapshot
  * ChannelCallerId
  * - caller_presentation_txt: string (required)
  * - caller_presentation: integer (required)
  * ChannelHangupRequest
  * - soft: boolean
  * - cause: integer
+ * BridgeDestroyed
  * ApplicationReplaced
  * - application: string (required)
  * ChannelVarset
  * - variable: string (required)
  * - value: string (required)
- * ChannelUserevent
- * - eventname: string (required)
+ * ChannelLeftBridge
  * ChannelCreated
  * StasisStart
  * - args: List[string] (required)
@@ -252,22 +308,27 @@ struct ast_json *stasis_json_event_stasis_end_create(
  * - application: string (required)
  * - application_data: string (required)
  * ChannelStateChange
+ * ChannelEnteredBridge
  * ChannelDtmfReceived
  * - digit: string (required)
  * Event
+ * - stasis_start: StasisStart
  * - channel_created: ChannelCreated
  * - channel_destroyed: ChannelDestroyed
+ * - channel_entered_bridge: ChannelEnteredBridge
+ * - channel_left_bridge: ChannelLeftBridge
  * - channel_dialplan: ChannelDialplan
  * - channel_varset: ChannelVarset
  * - application_replaced: ApplicationReplaced
  * - channel_state_change: ChannelStateChange
- * - stasis_start: StasisStart
+ * - bridge_created: BridgeCreated
  * - application: string (required)
  * - channel_hangup_request: ChannelHangupRequest
  * - channel_userevent: ChannelUserevent
  * - channel_snapshot: ChannelSnapshot
  * - channel_dtmf_received: ChannelDtmfReceived
  * - channel_caller_id: ChannelCallerId
+ * - bridge_destroyed: BridgeDestroyed
  * - stasis_end: StasisEnd
  * StasisEnd
  */
diff --git a/rest-api-templates/res_stasis_json_resource.c.mustache b/rest-api-templates/res_stasis_json_resource.c.mustache
index 0398302702371d400237e835f230fdf65be3fe3c..a55389c070cf7cb106ffe1de2ee3f49e4db54fbc 100644
--- a/rest-api-templates/res_stasis_json_resource.c.mustache
+++ b/rest-api-templates/res_stasis_json_resource.c.mustache
@@ -49,6 +49,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "stasis_json/resource_{{name}}.h"
 {{#has_events}}
 #include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_bridging.h"
 
 {{#events}}
 {{> event_function_decl}}
diff --git a/rest-api-templates/stasis_json_resource.h.mustache b/rest-api-templates/stasis_json_resource.h.mustache
index f55e830bda0c1a416ae037f3b6ce66b08a0b9eb8..8cfd2c1f7e185b0a635d39ecaa99bd08dd06aff8 100644
--- a/rest-api-templates/stasis_json_resource.h.mustache
+++ b/rest-api-templates/stasis_json_resource.h.mustache
@@ -38,6 +38,7 @@
 
 {{#has_events}}
 struct ast_channel_snapshot;
+struct ast_bridge_snapshot;
 
 {{#events}}
 /*!
diff --git a/rest-api/api-docs/events.json b/rest-api/api-docs/events.json
index 5b6e0549dc58e7a6981aed00dfda910df8f611ec..4a36da3b8dcec03ec67af0199865c42222ea781f 100644
--- a/rest-api/api-docs/events.json
+++ b/rest-api/api-docs/events.json
@@ -54,9 +54,13 @@
 					"required": true
 				},
 				"application_replaced": { "type": "ApplicationReplaced" },
+				"bridge_created": { "type": "BridgeCreated" },
+				"bridge_destroyed": { "type": "BridgeDestroyed" },
 				"channel_created": { "type": "ChannelCreated" },
 				"channel_destroyed": { "type": "ChannelDestroyed" },
 				"channel_snapshot": { "type": "ChannelSnapshot" },
+				"channel_entered_bridge": { "type": "ChannelEnteredBridge" },
+				"channel_left_bridge": { "type": "ChannelLeftBridge" },
 				"channel_state_change": { "type": "ChannelStateChange" },
 				"channel_dtmf_received": { "type": "ChannelDtmfReceived" },
 				"channel_dialplan": { "type": "ChannelDialplan" },
@@ -79,6 +83,26 @@
 				}
 			}
 		},
+		"BridgeCreated": {
+			"id": "BridgeCreated",
+			"description": "Notification that a bridge has been created.",
+			"properties": {
+				"bridge": {
+					"required": true,
+					"type": "Bridge"
+				}
+			}
+		},
+		"BridgeDestroyed": {
+			"id": "BridgeDestroyed",
+			"description": "Notification that a bridge has been destroyed.",
+			"properties": {
+				"bridge": {
+					"required": true,
+					"type": "Bridge"
+				}
+			}
+		},
 		"ChannelCreated": {
 			"id": "ChannelCreated",
 			"description": "Notification that a channel has been created.",
@@ -119,6 +143,33 @@
 				}
 			}
 		},
+		"ChannelEnteredBridge": {
+			"id": "ChannelEnteredBridge",
+			"description": "Notification that a channel has entered a bridge.",
+			"properties": {
+				"bridge": {
+					"required": true,
+					"type": "Bridge"
+				},
+				"channel": {
+					"type": "Channel"
+				}
+			}
+		},
+		"ChannelLeftBridge": {
+			"id": "ChannelLeftBridge",
+			"description": "Notification that a channel has left a bridge.",
+			"properties": {
+				"bridge": {
+					"required": true,
+					"type": "Bridge"
+				},
+				"channel": {
+					"required": true,
+					"type": "Channel"
+				}
+			}
+		},
 		"ChannelStateChange": {
 			"id": "ChannelStateChange",
 			"description": "Notification of a channel's state change.",