* 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.
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
------------------
* 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
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
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
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:
.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,
--- /dev/null
+/*
+ * 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");
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);
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;
}
#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">
};
/*! \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);
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)
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) {
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);
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;
}
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;
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;
}
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;
}
{
/* 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);
}
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);
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);
}
/*!
* \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);
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)
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 */
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()) {
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 ?
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)
{
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;
}
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;
}
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);
*/
/*** MODULEINFO
- <depend>chan_local</depend>
<support_level>core</support_level>
***/
#include "asterisk/ccss.h"
#include "asterisk/indications.h"
#include "asterisk/framehook.h"
+#include "asterisk/bridging.h"
#include "asterisk/stasis_channels.h"
/*** DOCUMENTATION
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 */
}
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))
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;
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) {
#include "asterisk/channel.h"
#include "asterisk/app.h"
#include "asterisk/translate.h"
+#include "asterisk/bridging.h"
/*** DOCUMENTATION
<application name="DumpChan" language="en_US">
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);
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"
"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"
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),
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;
}
*/
/*** MODULEINFO
- <depend>chan_local</depend>
<support_level>core</support_level>
***/
}
res = ast_bridge_call(caller, outbound, &config);
- ast_autoservice_chan_hangup_peer(caller, outbound);
}
outrun:
static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
{
- struct ast_channel *peer = NULL;
int res = 0;
if (!chan)
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;
}
+++ /dev/null
-/*
- * 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");
#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 */
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,
(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;
}
}
+#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
{
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
*/
/*!
* \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
* \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 */
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;
}
}
/* 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;
}
}
} 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. */
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);
}
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
/* 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);
}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
/* 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);
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);
--- /dev/null
+/*
+ * 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;
+}
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;
* \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
#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;
/* 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;
}
/* 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;
}
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;
}
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;
}
--- /dev/null
+/*
+ * 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");
--- /dev/null
+/*
+ * 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");
+
+++ /dev/null
-/*
- * 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");
--- /dev/null
+/*
+ * 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");
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,
};
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 */
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;
};
/*!
* \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
}
}
-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);
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);
/* 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;
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,
* \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)
* 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) {
}
}
- 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;
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);
}
/* 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;
}
/* 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) {
/* 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;
/* 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;
}
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)) {
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)
* \ingroup channel_drivers
*/
/*** MODULEINFO
- <depend>chan_local</depend>
<depend>res_monitor</depend>
<support_level>core</support_level>
***/
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",
.unload = unload_module,
.reload = reload,
.load_pri = AST_MODPRI_CHANNEL_DRIVER,
- .nonoptreq = "res_monitor,chan_local",
+ .nonoptreq = "res_monitor",
);
+++ /dev/null
-/*
- * 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,
-);
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,
.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,
.write = oh323_write,
.indicate = oh323_indicate,
.fixup = oh323_fixup,
- .bridge = ast_rtp_instance_bridge,
};
static const char* redirectingreason2str(int redirectingreason)
#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"
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,
}
}
-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;
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;
.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,
+++ /dev/null
-/*
- * 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