Merge in the bridge_construction branch to make the system use the Bridging API.
authorRichard Mudgett <rmudgett@digium.com>
Tue, 21 May 2013 18:00:22 +0000 (18:00 +0000)
committerRichard Mudgett <rmudgett@digium.com>
Tue, 21 May 2013 18:00:22 +0000 (18:00 +0000)
Breaks many things until they can be reworked.  A partial list:
chan_agent
chan_dahdi, chan_misdn, chan_iax2 native bridging
app_queue
COLP updates
DTMF attended transfers
Protocol attended transfers

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@389378 65c4cc65-6c06-0410-ace0-fbb531ad65f3

99 files changed:
CHANGES
UPGRADE.txt
addons/chan_ooh323.c
apps/app_bridgewait.c [new file with mode: 0644]
apps/app_channelredirect.c
apps/app_chanspy.c
apps/app_confbridge.c
apps/app_dial.c
apps/app_dumpchan.c
apps/app_followme.c
apps/app_mixmonitor.c
apps/app_parkandannounce.c [deleted file]
apps/app_queue.c
apps/confbridge/conf_chan_announce.c [new file with mode: 0644]
apps/confbridge/conf_chan_record.c [new file with mode: 0644]
apps/confbridge/conf_config_parser.c
apps/confbridge/confbridge_manager.c [new file with mode: 0644]
apps/confbridge/include/confbridge.h
bridges/bridge_builtin_features.c
bridges/bridge_builtin_interval_features.c [new file with mode: 0644]
bridges/bridge_holding.c [new file with mode: 0644]
bridges/bridge_multiplexed.c [deleted file]
bridges/bridge_native_rtp.c [new file with mode: 0644]
bridges/bridge_simple.c
bridges/bridge_softmix.c
channels/chan_agent.c
channels/chan_bridge.c [deleted file]
channels/chan_dahdi.c
channels/chan_gulp.c
channels/chan_h323.c
channels/chan_iax2.c
channels/chan_jingle.c
channels/chan_local.c [deleted file]
channels/chan_mgcp.c
channels/chan_misdn.c
channels/chan_motif.c
channels/chan_sip.c
channels/chan_skinny.c
channels/chan_unistim.c
channels/chan_vpb.cc
configs/features.conf.sample
configs/res_parking.conf.sample [new file with mode: 0644]
funcs/func_channel.c
funcs/func_frame_trace.c
funcs/func_jitterbuffer.c
include/asterisk/_private.h
include/asterisk/abstract_jb.h
include/asterisk/bridging.h
include/asterisk/bridging_basic.h [new file with mode: 0644]
include/asterisk/bridging_features.h
include/asterisk/bridging_roles.h [new file with mode: 0644]
include/asterisk/bridging_technology.h
include/asterisk/ccss.h
include/asterisk/channel.h
include/asterisk/config_options.h
include/asterisk/core_local.h [new file with mode: 0644]
include/asterisk/core_unreal.h [new file with mode: 0644]
include/asterisk/frame.h
include/asterisk/framehook.h
include/asterisk/manager.h
include/asterisk/parking.h [new file with mode: 0644]
include/asterisk/rtp_engine.h
include/asterisk/stasis_bridging.h [new file with mode: 0644]
main/abstract_jb.c
main/asterisk.c
main/bridging.c
main/bridging_basic.c [new file with mode: 0644]
main/bridging_roles.c [new file with mode: 0644]
main/channel.c
main/channel_internal_api.c
main/cli.c
main/config_options.c
main/core_local.c [new file with mode: 0644]
main/core_unreal.c [new file with mode: 0644]
main/features.c
main/frame.c
main/manager.c
main/manager_bridging.c [new file with mode: 0644]
main/manager_channels.c
main/parking.c [new file with mode: 0644]
main/pbx.c
main/rtp_engine.c
main/stasis_bridging.c [new file with mode: 0644]
main/strings.c
res/Makefile
res/parking/parking_applications.c [new file with mode: 0644]
res/parking/parking_bridge.c [new file with mode: 0644]
res/parking/parking_bridge_features.c [new file with mode: 0644]
res/parking/parking_controller.c [new file with mode: 0644]
res/parking/parking_manager.c [new file with mode: 0644]
res/parking/parking_ui.c [new file with mode: 0644]
res/parking/res_parking.h [new file with mode: 0644]
res/res_parking.c [new file with mode: 0644]
res/res_stasis_json_events.c
res/res_stasis_json_events.exports.in
res/stasis_json/resource_events.h
rest-api-templates/res_stasis_json_resource.c.mustache
rest-api-templates/stasis_json_resource.h.mustache
rest-api/api-docs/events.json

diff --git a/CHANGES b/CHANGES
index f5c705e..ec85f5d 100644 (file)
--- 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
index 39f0804..e2e7090 100644 (file)
@@ -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:
index a393e8e..61d9245 100644 (file)
@@ -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 (file)
index 0000000..ca12f0d
--- /dev/null
@@ -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");
index 8c98ed7..f636e02 100644 (file)
@@ -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);
index c5adb78..8e45907 100644 (file)
@@ -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;
 }
index d4cce4b..5107bcd 100644 (file)
@@ -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);
index d3d3721..35c9ad8 100644 (file)
@@ -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) {
index 4a80a3d..722f155 100644 (file)
@@ -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;
 }
 
index 83f583b..43f1967 100644 (file)
@@ -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:
index 6e7976e..e8d4903 100644 (file)
@@ -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 (file)
index 6d6ccae..0000000
+++ /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");
index c63cd07..8a96b64 100644 (file)
@@ -106,6 +106,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/cel.h"
 #include "asterisk/data.h"
 #include "asterisk/term.h"
+#include "asterisk/bridging.h"
 
 /* Define, to debug reference counts on queues, without debugging reference counts on queue members */
 /* #define REF_DEBUG_ONLY_QUEUES */
@@ -4912,6 +4913,7 @@ enum agent_complete_reason {
        TRANSFER
 };
 
+#if 0  // BUGBUG
 /*! \brief Send out AMI message with member call completion status information */
 static void send_agent_complete(const struct queue_ent *qe, const char *queuename,
        const struct ast_channel *peer, const struct member *member, time_t callstart,
@@ -4975,6 +4977,7 @@ static void send_agent_complete(const struct queue_ent *qe, const char *queuenam
                (long)(callstart - qe->start), (long)(time(NULL) - callstart), reason,
                qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, vars_len) : "");
 }
+#endif // BUGBUG
 
 struct queue_transfer_ds {
        struct queue_ent *qe;
@@ -5029,6 +5032,7 @@ static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struc
        }
 }
 
+#if 0  // BUGBUG
 /*! \brief mechanism to tell if a queue caller was atxferred by a queue member.
  *
  * When a caller is atxferred, then the queue_transfer_info datastore
@@ -5041,6 +5045,7 @@ static int attended_transfer_occurred(struct ast_channel *chan)
 {
        return ast_channel_datastore_find(chan, &queue_transfer_info, NULL) ? 0 : 1;
 }
+#endif // BUGBUG
 
 /*! \brief create a datastore for storing relevant info to log attended transfers in the queue_log
  */
@@ -5100,6 +5105,35 @@ static void end_bridge_callback(void *data)
 
 /*!
  * \internal
+ * \brief Setup the after bridge goto location on the peer.
+ * \since 12.0.0
+ *
+ * \param chan Calling channel for bridge.
+ * \param peer Peer channel for bridge.
+ * \param opts Dialing option flags.
+ * \param opt_args Dialing option argument strings.
+ *
+ * \return Nothing
+ */
+static void setup_peer_after_bridge_goto(struct ast_channel *chan, struct ast_channel *peer, struct ast_flags *opts, char *opt_args[])
+{
+       const char *context;
+       const char *extension;
+       int priority;
+
+       if (ast_test_flag(opts, OPT_CALLEE_GO_ON)) {
+               ast_channel_lock(chan);
+               context = ast_strdupa(ast_channel_context(chan));
+               extension = ast_strdupa(ast_channel_exten(chan));
+               priority = ast_channel_priority(chan);
+               ast_channel_unlock(chan);
+               ast_after_bridge_set_go_on(peer, context, extension, priority,
+                       opt_args[OPT_ARG_CALLEE_GO_ON]);
+       }
+}
+
+/*!
+ * \internal
  * \brief A large function which calls members, updates statistics, and bridges the caller and a member
  *
  * Here is the process of this function
@@ -5128,7 +5162,7 @@ static void end_bridge_callback(void *data)
  * \param[in] gosub the gosub passed as the seventh parameter to the Queue() application
  * \param[in] ringing 1 if the 'r' option is set, otherwise 0
  */
-static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char **opt_args, char *announceoverride, const char *url, int *tries, int *noption, const char *agi, const char *macro, const char *gosub, int ringing)
+static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_args, char *announceoverride, const char *url, int *tries, int *noption, const char *agi, const char *macro, const char *gosub, int ringing)
 {
        struct member *cur;
        struct callattempt *outgoing = NULL; /* the list of calls we are building */
@@ -5200,9 +5234,6 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char *
        if (ast_test_flag(&opts, OPT_CALLER_AUTOMON)) {
                ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_AUTOMON);
        }
-       if (ast_test_flag(&opts, OPT_GO_ON)) {
-               ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_NO_H_EXTEN);
-       }
        if (ast_test_flag(&opts, OPT_DATA_QUALITY)) {
                nondataquality = 0;
        }
@@ -5244,7 +5275,7 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char *
        }
 
        /* if the calling channel has AST_CAUSE_ANSWERED_ELSEWHERE set, make sure this is inherited.
-               (this is mainly to support chan_local)
+               (this is mainly to support unreal/local channels)
        */
        if (ast_channel_hangupcause(qe->chan) == AST_CAUSE_ANSWERED_ELSEWHERE) {
                qe->cancel_answered_elsewhere = 1;
@@ -5437,11 +5468,6 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char *
                        }
                }
        } else { /* peer is valid */
-               /* These variables are used with the F option without arguments (callee jumps to next priority after queue) */
-               char *caller_context;
-               char *caller_extension;
-               int caller_priority;
-
                /* Ah ha!  Someone answered within the desired timeframe.  Of course after this
                   we will always return with -1 so that it is hung up properly after the
                   conversation.  */
@@ -5595,11 +5621,8 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char *
                set_queue_variables(qe->parent, qe->chan);
                set_queue_variables(qe->parent, peer);
 
+               setup_peer_after_bridge_goto(qe->chan, peer, &opts, opt_args);
                ast_channel_lock(qe->chan);
-               /* Copy next destination data for 'F' option (no args) */
-               caller_context = ast_strdupa(ast_channel_context(qe->chan));
-               caller_extension = ast_strdupa(ast_channel_exten(qe->chan));
-               caller_priority = ast_channel_priority(qe->chan);
                if ((monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME"))) {
                                monitorfilename = ast_strdupa(monitorfilename);
                }
@@ -5883,6 +5906,8 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char *
                transfer_ds = setup_transfer_datastore(qe, member, callstart, callcompletedinsl);
                bridge = ast_bridge_call(qe->chan, peer, &bridge_config);
 
+/* BUGBUG need to do this queue logging a different way because we cannot reference peer anymore.  Likely needs to be made a subscriber of stasis transfer events. */
+#if 0  // BUGBUG
                /* If the queue member did an attended transfer, then the TRANSFER already was logged in the queue_log
                 * when the masquerade occurred. These other "ending" queue_log messages are unnecessary, except for
                 * the AgentComplete manager event
@@ -5917,28 +5942,12 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char *
                        /* We already logged the TRANSFER on the queue_log, but we still need to send the AgentComplete event */
                        send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), TRANSFER);
                }
+#endif // BUGBUG
 
                if (transfer_ds) {
                        ast_datastore_free(transfer_ds);
                }
 
-               if (!ast_check_hangup(peer) && ast_test_flag(&opts, OPT_CALLEE_GO_ON)) {
-                       int goto_res;
-
-                       if (!ast_strlen_zero(opt_args[OPT_ARG_CALLEE_GO_ON])) {
-                               ast_replace_subargument_delimiter(opt_args[OPT_ARG_CALLEE_GO_ON]);
-                               goto_res = ast_parseable_goto(peer, opt_args[OPT_ARG_CALLEE_GO_ON]);
-                       } else { /* F() */
-                               goto_res = ast_goto_if_exists(peer, caller_context, caller_extension,
-                                       caller_priority + 1);
-                       }
-                       if (goto_res || ast_pbx_start(peer)) {
-                               ast_autoservice_chan_hangup_peer(qe->chan, peer);
-                       }
-               } else {
-                       ast_autoservice_chan_hangup_peer(qe->chan, peer);
-               }
-
                res = bridge ? bridge : 1;
                ao2_ref(member, -1);
        }
diff --git a/apps/confbridge/conf_chan_announce.c b/apps/confbridge/conf_chan_announce.c
new file mode 100644 (file)
index 0000000..46e074b
--- /dev/null
@@ -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 (file)
index 0000000..18f971f
--- /dev/null
@@ -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;
+}
index 1bca2d5..6cec255 100644 (file)
@@ -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 (file)
index 0000000..56fedb9
--- /dev/null
@@ -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;
+}
index 2455613..f062014 100644 (file)
@@ -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
index 428d6de..493b5c8 100644 (file)
@@ -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 (file)
index 0000000..a0e767e
--- /dev/null
@@ -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 (file)
index 0000000..fe0a730
--- /dev/null
@@ -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 (file)
index 309ad47..0000000
+++ /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 (file)
index 0000000..1117e5a
--- /dev/null
@@ -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");
index 947983b..3e53b31 100644 (file)
@@ -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,
 };
index 613601a..4583435 100644 (file)
@@ -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)
index 3fb6891..d72254e 100644 (file)
@@ -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 (file)
index 8eac76a..0000000
+++ /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,
-);
index 44fe3f4..1f9ee5a 100644 (file)
@@ -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,
index 7485953..4af137f 100644 (file)
@@ -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,
index f6364a1..2476f50 100644 (file)
@@ -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)
index 112a993..49ce66c 100644 (file)
@@ -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;
index 717afae..42dccf6 100644 (file)
@@ -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 (file)
index be4586f..0000000
+++ /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_n