summaryrefslogtreecommitdiff
path: root/bridges
diff options
context:
space:
mode:
authorRichard Mudgett <rmudgett@digium.com>2013-05-21 18:00:22 +0000
committerRichard Mudgett <rmudgett@digium.com>2013-05-21 18:00:22 +0000
commit3d63833bd6c869b7efa383e8dea14be1a6eff998 (patch)
tree34957dd051b8f67c7cc58a510e24ee3873a61ad4 /bridges
parente1e1cc2deefb92f8b43825f1f34e619354737842 (diff)
Merge in the bridge_construction branch to make the system use the Bridging API.
Breaks many things until they can be reworked. A partial list: chan_agent chan_dahdi, chan_misdn, chan_iax2 native bridging app_queue COLP updates DTMF attended transfers Protocol attended transfers git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@389378 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'bridges')
-rw-r--r--bridges/bridge_builtin_features.c342
-rw-r--r--bridges/bridge_builtin_interval_features.c215
-rw-r--r--bridges/bridge_holding.c311
-rw-r--r--bridges/bridge_multiplexed.c513
-rw-r--r--bridges/bridge_native_rtp.c414
-rw-r--r--bridges/bridge_simple.c24
-rw-r--r--bridges/bridge_softmix.c593
7 files changed, 1589 insertions, 823 deletions
diff --git a/bridges/bridge_builtin_features.c b/bridges/bridge_builtin_features.c
index 428d6deda..493b5c891 100644
--- a/bridges/bridge_builtin_features.c
+++ b/bridges/bridge_builtin_features.c
@@ -47,8 +47,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/file.h"
#include "asterisk/app.h"
#include "asterisk/astobj2.h"
+#include "asterisk/pbx.h"
+#include "asterisk/parking.h"
-/*! \brief Helper function that presents dialtone and grabs extension */
+/*!
+ * \brief Helper function that presents dialtone and grabs extension
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
static int grab_transfer(struct ast_channel *chan, char *exten, size_t exten_len, const char *context)
{
int res;
@@ -56,15 +63,35 @@ static int grab_transfer(struct ast_channel *chan, char *exten, size_t exten_len
/* Play the simple "transfer" prompt out and wait */
res = ast_stream_and_wait(chan, "pbx-transfer", AST_DIGIT_ANY);
ast_stopstream(chan);
-
- /* If the person hit a DTMF digit while the above played back stick it into the buffer */
+ if (res < 0) {
+ /* Hangup or error */
+ return -1;
+ }
if (res) {
- exten[0] = (char)res;
+ /* Store the DTMF digit that interrupted playback of the file. */
+ exten[0] = res;
}
/* Drop to dialtone so they can enter the extension they want to transfer to */
- res = ast_app_dtget(chan, context, exten, exten_len, 100, 1000);
-
+/* BUGBUG the timeout needs to be configurable from features.conf. */
+ res = ast_app_dtget(chan, context, exten, exten_len, exten_len - 1, 3000);
+ if (res < 0) {
+ /* Hangup or error */
+ res = -1;
+ } else if (!res) {
+ /* 0 for invalid extension dialed. */
+ if (ast_strlen_zero(exten)) {
+ ast_debug(1, "%s dialed no digits.\n", ast_channel_name(chan));
+ } else {
+ ast_debug(1, "%s dialed '%s@%s' does not exist.\n",
+ ast_channel_name(chan), exten, context);
+ }
+ ast_stream_and_wait(chan, "pbx-invalid", AST_DIGIT_NONE);
+ res = -1;
+ } else {
+ /* Dialed extension is valid. */
+ res = 0;
+ }
return res;
}
@@ -78,8 +105,10 @@ static struct ast_channel *dial_transfer(struct ast_channel *caller, const char
/* Fill the variable with the extension and context we want to call */
snprintf(destination, sizeof(destination), "%s@%s", exten, context);
- /* Now we request that chan_local prepare to call the destination */
- if (!(chan = ast_request("Local", ast_channel_nativeformats(caller), caller, destination, &cause))) {
+ /* Now we request a local channel to prepare to call the destination */
+ chan = ast_request("Local", ast_channel_nativeformats(caller), caller, destination,
+ &cause);
+ if (!chan) {
return NULL;
}
@@ -100,67 +129,124 @@ static struct ast_channel *dial_transfer(struct ast_channel *caller, const char
return chan;
}
+/*!
+ * \internal
+ * \brief Determine the transfer context to use.
+ * \since 12.0.0
+ *
+ * \param transferer Channel initiating the transfer.
+ * \param context User supplied context if available. May be NULL.
+ *
+ * \return The context to use for the transfer.
+ */
+static const char *get_transfer_context(struct ast_channel *transferer, const char *context)
+{
+ if (!ast_strlen_zero(context)) {
+ return context;
+ }
+ context = pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT");
+ if (!ast_strlen_zero(context)) {
+ return context;
+ }
+ context = ast_channel_macrocontext(transferer);
+ if (!ast_strlen_zero(context)) {
+ return context;
+ }
+ context = ast_channel_context(transferer);
+ if (!ast_strlen_zero(context)) {
+ return context;
+ }
+ return "default";
+}
+
/*! \brief Internal built in feature for blind transfers */
static int feature_blind_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
{
char exten[AST_MAX_EXTENSION] = "";
struct ast_channel *chan = NULL;
struct ast_bridge_features_blind_transfer *blind_transfer = hook_pvt;
- const char *context = (blind_transfer && !ast_strlen_zero(blind_transfer->context) ? blind_transfer->context : ast_channel_context(bridge_channel->chan));
+ const char *context;
+ struct ast_exten *park_exten;
+
+/* BUGBUG the peer needs to be put on hold for the transfer. */
+ ast_channel_lock(bridge_channel->chan);
+ context = ast_strdupa(get_transfer_context(bridge_channel->chan,
+ blind_transfer ? blind_transfer->context : NULL));
+ ast_channel_unlock(bridge_channel->chan);
/* Grab the extension to transfer to */
- if (!grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
- ast_stream_and_wait(bridge_channel->chan, "pbx-invalid", AST_DIGIT_ANY);
+ if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
+ return 0;
+ }
+
+ /* Parking blind transfer override - phase this out for something more general purpose in the future. */
+ park_exten = ast_get_parking_exten(exten, bridge_channel->chan, context);
+ if (park_exten) {
+ /* We are transfering the transferee to a parking lot. */
+ if (ast_park_blind_xfer(bridge, bridge_channel, park_exten)) {
+ ast_log(LOG_ERROR, "%s attempted to transfer to park application and failed.\n", ast_channel_name(bridge_channel->chan));
+ };
return 0;
}
+/* BUGBUG just need to ast_async_goto the peer so this bridge will go away and not accumulate local channels and bridges if the destination is to an application. */
+/* ast_async_goto actually is a blind transfer. */
+/* BUGBUG Use the bridge count to determine if can do DTMF transfer features. If count is not 2 then don't allow it. */
+
/* Get a channel that is the destination we wish to call */
- if (!(chan = dial_transfer(bridge_channel->chan, exten, context))) {
- ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_ANY);
+ chan = dial_transfer(bridge_channel->chan, exten, context);
+ if (!chan) {
return 0;
}
- /* This is sort of the fun part. We impart the above channel onto the bridge, and have it take our place. */
- ast_bridge_impart(bridge, chan, bridge_channel->chan, NULL, 1);
+ /* Impart the new channel onto the bridge, and have it take our place. */
+ if (ast_bridge_impart(bridge_channel->bridge, chan, bridge_channel->chan, NULL, 1)) {
+ ast_hangup(chan);
+ return 0;
+ }
return 0;
}
-/*! \brief Attended transfer feature to turn it into a threeway call */
-static int attended_threeway_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+/*! Attended transfer code */
+enum atxfer_code {
+ /*! Party C hungup or other reason to abandon the transfer. */
+ ATXFER_INCOMPLETE,
+ /*! Transfer party C to party A. */
+ ATXFER_COMPLETE,
+ /*! Turn the transfer into a threeway call. */
+ ATXFER_THREEWAY,
+ /*! Hangup party C and return party B to the bridge. */
+ ATXFER_ABORT,
+};
+
+/*! \brief Attended transfer feature to complete transfer */
+static int attended_transfer_complete(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
{
- /*
- * This is sort of abusing the depart state but in this instance
- * it is only going to be handled by feature_attended_transfer()
- * so it is okay.
- */
- ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_DEPART);
+ enum atxfer_code *transfer_code = hook_pvt;
+
+ *transfer_code = ATXFER_COMPLETE;
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
return 0;
}
-/*! \brief Attended transfer abort feature */
-static int attended_abort_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+/*! \brief Attended transfer feature to turn it into a threeway call */
+static int attended_transfer_threeway(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
{
- struct ast_bridge_channel *called_bridge_channel = NULL;
-
- /* It is possible (albeit unlikely) that the bridge channels list may change, so we have to ensure we do all of our magic while locked */
- ao2_lock(bridge);
+ enum atxfer_code *transfer_code = hook_pvt;
- if (AST_LIST_FIRST(&bridge->channels) != bridge_channel) {
- called_bridge_channel = AST_LIST_FIRST(&bridge->channels);
- } else {
- called_bridge_channel = AST_LIST_LAST(&bridge->channels);
- }
-
- /* Now we basically eject the other channel from the bridge. This will cause their thread to hang them up, and our own code to consider the transfer failed. */
- if (called_bridge_channel) {
- ast_bridge_change_state(called_bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
- }
-
- ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+ *transfer_code = ATXFER_THREEWAY;
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+ return 0;
+}
- ao2_unlock(bridge);
+/*! \brief Attended transfer feature to abort transfer */
+static int attended_transfer_abort(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+ enum atxfer_code *transfer_code = hook_pvt;
+ *transfer_code = ATXFER_ABORT;
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
return 0;
}
@@ -168,71 +254,159 @@ static int attended_abort_transfer(struct ast_bridge *bridge, struct ast_bridge_
static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
{
char exten[AST_MAX_EXTENSION] = "";
- struct ast_channel *chan = NULL;
- struct ast_bridge *attended_bridge = NULL;
- struct ast_bridge_features caller_features, called_features;
- enum ast_bridge_channel_state attended_bridge_result;
+ struct ast_channel *peer;
+ struct ast_bridge *attended_bridge;
+ struct ast_bridge_features caller_features;
+ int xfer_failed;
struct ast_bridge_features_attended_transfer *attended_transfer = hook_pvt;
- const char *context = (attended_transfer && !ast_strlen_zero(attended_transfer->context) ? attended_transfer->context : ast_channel_context(bridge_channel->chan));
+ const char *context;
+ enum atxfer_code transfer_code = ATXFER_INCOMPLETE;
+
+ bridge = ast_bridge_channel_merge_inhibit(bridge_channel, +1);
+
+/* BUGBUG the peer needs to be put on hold for the transfer. */
+ ast_channel_lock(bridge_channel->chan);
+ context = ast_strdupa(get_transfer_context(bridge_channel->chan,
+ attended_transfer ? attended_transfer->context : NULL));
+ ast_channel_unlock(bridge_channel->chan);
/* Grab the extension to transfer to */
- if (!grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
- ast_stream_and_wait(bridge_channel->chan, "pbx-invalid", AST_DIGIT_ANY);
+ if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
+ ast_bridge_merge_inhibit(bridge, -1);
+ ao2_ref(bridge, -1);
return 0;
}
/* Get a channel that is the destination we wish to call */
- if (!(chan = dial_transfer(bridge_channel->chan, exten, context))) {
- ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_ANY);
+ peer = dial_transfer(bridge_channel->chan, exten, context);
+ if (!peer) {
+ ast_bridge_merge_inhibit(bridge, -1);
+ ao2_ref(bridge, -1);
+/* BUGBUG beeperr needs to be configurable from features.conf */
+ ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
return 0;
}
- /* Create a bridge to use to talk to the person we are calling */
- if (!(attended_bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_1TO1MIX, 0))) {
- ast_hangup(chan);
- ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_ANY);
+/* BUGBUG bridging API features does not support features.conf featuremap */
+/* BUGBUG bridging API features does not support the features.conf atxfer bounce between C & B channels */
+ /* Setup a DTMF menu to control the transfer. */
+ if (ast_bridge_features_init(&caller_features)
+ || ast_bridge_hangup_hook(&caller_features,
+ attended_transfer_complete, &transfer_code, NULL, 0)
+ || ast_bridge_dtmf_hook(&caller_features,
+ attended_transfer && !ast_strlen_zero(attended_transfer->abort)
+ ? attended_transfer->abort : "*1",
+ attended_transfer_abort, &transfer_code, NULL, 0)
+ || ast_bridge_dtmf_hook(&caller_features,
+ attended_transfer && !ast_strlen_zero(attended_transfer->complete)
+ ? attended_transfer->complete : "*2",
+ attended_transfer_complete, &transfer_code, NULL, 0)
+ || ast_bridge_dtmf_hook(&caller_features,
+ attended_transfer && !ast_strlen_zero(attended_transfer->threeway)
+ ? attended_transfer->threeway : "*3",
+ attended_transfer_threeway, &transfer_code, NULL, 0)) {
+ ast_bridge_features_cleanup(&caller_features);
+ ast_hangup(peer);
+ ast_bridge_merge_inhibit(bridge, -1);
+ ao2_ref(bridge, -1);
+/* BUGBUG beeperr needs to be configurable from features.conf */
+ ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
return 0;
}
- /* Setup our called features structure so that if they hang up we immediately get thrown out of the bridge */
- ast_bridge_features_init(&called_features);
- ast_bridge_features_set_flag(&called_features, AST_BRIDGE_FLAG_DISSOLVE);
+ /* Create a bridge to use to talk to the person we are calling */
+ attended_bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_1TO1MIX,
+ AST_BRIDGE_FLAG_DISSOLVE_HANGUP);
+ if (!attended_bridge) {
+ ast_bridge_features_cleanup(&caller_features);
+ ast_hangup(peer);
+ ast_bridge_merge_inhibit(bridge, -1);
+ ao2_ref(bridge, -1);
+/* BUGBUG beeperr needs to be configurable from features.conf */
+ ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
+ return 0;
+ }
+ ast_bridge_merge_inhibit(attended_bridge, +1);
/* This is how this is going down, we are imparting the channel we called above into this bridge first */
- ast_bridge_impart(attended_bridge, chan, NULL, &called_features, 1);
+/* BUGBUG we should impart the peer as an independent and move it to the original bridge. */
+ if (ast_bridge_impart(attended_bridge, peer, NULL, NULL, 0)) {
+ ast_bridge_destroy(attended_bridge);
+ ast_bridge_features_cleanup(&caller_features);
+ ast_hangup(peer);
+ ast_bridge_merge_inhibit(bridge, -1);
+ ao2_ref(bridge, -1);
+/* BUGBUG beeperr needs to be configurable from features.conf */
+ ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
+ return 0;
+ }
- /* Before we join setup a features structure with the hangup option, just in case they want to use DTMF */
- ast_bridge_features_init(&caller_features);
- ast_bridge_features_enable(&caller_features, AST_BRIDGE_BUILTIN_HANGUP,
- (attended_transfer && !ast_strlen_zero(attended_transfer->complete) ? attended_transfer->complete : "*1"), NULL);
- ast_bridge_features_hook(&caller_features, (attended_transfer && !ast_strlen_zero(attended_transfer->threeway) ? attended_transfer->threeway : "*2"),
- attended_threeway_transfer, NULL, NULL);
- ast_bridge_features_hook(&caller_features, (attended_transfer && !ast_strlen_zero(attended_transfer->abort) ? attended_transfer->abort : "*3"),
- attended_abort_transfer, NULL, NULL);
+ /*
+ * For the caller we want to join the bridge in a blocking
+ * fashion so we don't spin around in this function doing
+ * nothing while waiting.
+ */
+ ast_bridge_join(attended_bridge, bridge_channel->chan, NULL, &caller_features, NULL, 0);
- /* But for the caller we want to join the bridge in a blocking fashion so we don't spin around in this function doing nothing while waiting */
- attended_bridge_result = ast_bridge_join(attended_bridge, bridge_channel->chan, NULL, &caller_features, NULL);
+/*
+ * BUGBUG there is a small window where the channel does not point to the bridge_channel.
+ *
+ * This window is expected to go away when atxfer is redesigned
+ * to fully support existing functionality. There will be one
+ * and only one ast_bridge_channel structure per channel.
+ */
+ /* Point the channel back to the original bridge and bridge_channel. */
+ ast_bridge_channel_lock(bridge_channel);
+ ast_channel_lock(bridge_channel->chan);
+ ast_channel_internal_bridge_channel_set(bridge_channel->chan, bridge_channel);
+ ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge);
+ ast_channel_unlock(bridge_channel->chan);
+ ast_bridge_channel_unlock(bridge_channel);
+
+ /* Wait for peer thread to exit bridge and die. */
+ if (!ast_autoservice_start(bridge_channel->chan)) {
+ ast_bridge_depart(peer);
+ ast_autoservice_stop(bridge_channel->chan);
+ } else {
+ ast_bridge_depart(peer);
+ }
- /* Since the above returned the caller features structure is of no more use */
+ /* Now that all channels are out of it we can destroy the bridge and the feature structures */
+ ast_bridge_destroy(attended_bridge);
ast_bridge_features_cleanup(&caller_features);
- /* Drop the channel we are transferring to out of the above bridge since it has ended */
- if ((attended_bridge_result != AST_BRIDGE_CHANNEL_STATE_HANGUP) && !ast_bridge_depart(attended_bridge, chan)) {
- /* If the user wants to turn this into a threeway transfer then do so, otherwise they take our place */
- if (attended_bridge_result == AST_BRIDGE_CHANNEL_STATE_DEPART) {
- /* We want to impart them upon the bridge and just have us return to it as normal */
- ast_bridge_impart(bridge, chan, NULL, NULL, 1);
- } else {
- ast_bridge_impart(bridge, chan, bridge_channel->chan, NULL, 1);
+ xfer_failed = -1;
+ switch (transfer_code) {
+ case ATXFER_INCOMPLETE:
+ /* Peer hungup */
+ break;
+ case ATXFER_COMPLETE:
+ /* The peer takes our place in the bridge. */
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+ xfer_failed = ast_bridge_impart(bridge_channel->bridge, peer, bridge_channel->chan, NULL, 1);
+ break;
+ case ATXFER_THREEWAY:
+ /*
+ * Transferer wants to convert to a threeway call.
+ *
+ * Just impart the peer onto the bridge and have us return to it
+ * as normal.
+ */
+ xfer_failed = ast_bridge_impart(bridge_channel->bridge, peer, NULL, NULL, 1);
+ break;
+ case ATXFER_ABORT:
+ /* Transferer decided not to transfer the call after all. */
+ break;
+ }
+ ast_bridge_merge_inhibit(bridge, -1);
+ ao2_ref(bridge, -1);
+ if (xfer_failed) {
+ ast_hangup(peer);
+ if (!ast_check_hangup_locked(bridge_channel->chan)) {
+ ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
}
- } else {
- ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_ANY);
}
- /* Now that all channels are out of it we can destroy the bridge and the called features structure */
- ast_bridge_features_cleanup(&called_features);
- ast_bridge_destroy(attended_bridge);
-
return 0;
}
diff --git a/bridges/bridge_builtin_interval_features.c b/bridges/bridge_builtin_interval_features.c
new file mode 100644
index 000000000..a0e767ed3
--- /dev/null
+++ b/bridges/bridge_builtin_interval_features.c
@@ -0,0 +1,215 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Built in bridging interval features
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ *
+ * \ingroup bridges
+ */
+
+/*** MODULEINFO
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$REVISION: 381278 $")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/file.h"
+#include "asterisk/app.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/test.h"
+
+#include "asterisk/say.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/musiconhold.h"
+
+static int bridge_features_duration_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+ struct ast_bridge_features_limits *limits = hook_pvt;
+
+ if (!ast_strlen_zero(limits->duration_sound)) {
+ ast_stream_and_wait(bridge_channel->chan, limits->duration_sound, AST_DIGIT_NONE);
+ }
+
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+
+ ast_test_suite_event_notify("BRIDGE_TIMELIMIT", "Channel1: %s", ast_channel_name(bridge_channel->chan));
+ return -1;
+}
+
+static void limits_interval_playback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_bridge_features_limits *limits, const char *file)
+{
+ if (!strcasecmp(file, "timeleft")) {
+ unsigned int remaining = ast_tvdiff_ms(limits->quitting_time, ast_tvnow()) / 1000;
+ unsigned int min;
+ unsigned int sec;
+
+ if (remaining <= 0) {
+ return;
+ }
+
+ if ((remaining / 60) > 1) {
+ min = remaining / 60;
+ sec = remaining % 60;
+ } else {
+ min = 0;
+ sec = remaining;
+ }
+
+ ast_stream_and_wait(bridge_channel->chan, "vm-youhave", AST_DIGIT_NONE);
+ if (min) {
+ ast_say_number(bridge_channel->chan, min, AST_DIGIT_NONE,
+ ast_channel_language(bridge_channel->chan), NULL);
+ ast_stream_and_wait(bridge_channel->chan, "queue-minutes", AST_DIGIT_NONE);
+ }
+ if (sec) {
+ ast_say_number(bridge_channel->chan, sec, AST_DIGIT_NONE,
+ ast_channel_language(bridge_channel->chan), NULL);
+ ast_stream_and_wait(bridge_channel->chan, "queue-seconds", AST_DIGIT_NONE);
+ }
+ } else {
+ ast_stream_and_wait(bridge_channel->chan, file, AST_DIGIT_NONE);
+ }
+
+ /*
+ * It may be necessary to resume music on hold after we finish
+ * playing the announcment.
+ *
+ * XXX We have no idea what MOH class was in use before playing
+ * the file.
+ */
+ if (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_MOH)) {
+ ast_moh_start(bridge_channel->chan, NULL, NULL);
+ }
+}
+
+static int bridge_features_connect_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+ struct ast_bridge_features_limits *limits = hook_pvt;
+
+ if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+ return -1;
+ }
+
+ limits_interval_playback(bridge, bridge_channel, limits, limits->connect_sound);
+ return -1;
+}
+
+static int bridge_features_warning_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+ struct ast_bridge_features_limits *limits = hook_pvt;
+
+ if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+ /* If we aren't in the wait state, something more important than this warning is happening and we should skip it. */
+ limits_interval_playback(bridge, bridge_channel, limits, limits->warning_sound);
+ }
+
+ return !limits->frequency ? -1 : limits->frequency;
+}
+
+static void copy_bridge_features_limits(struct ast_bridge_features_limits *dst, struct ast_bridge_features_limits *src)
+{
+ dst->duration = src->duration;
+ dst->warning = src->warning;
+ dst->frequency = src->frequency;
+ dst->quitting_time = src->quitting_time;
+
+ ast_string_field_set(dst, duration_sound, src->duration_sound);
+ ast_string_field_set(dst, warning_sound, src->warning_sound);
+ ast_string_field_set(dst, connect_sound, src->connect_sound);
+}
+
+static int bridge_builtin_set_limits(struct ast_bridge_features *features, struct ast_bridge_features_limits *limits, int remove_on_pull)
+{
+ struct ast_bridge_features_limits *feature_limits;
+
+ if (!limits->duration) {
+ return -1;
+ }
+
+ if (features->limits) {
+ ast_log(LOG_ERROR, "Tried to apply limits to a feature set that already has limits.\n");
+ return -1;
+ }
+
+ feature_limits = ast_malloc(sizeof(*feature_limits));
+ if (!feature_limits) {
+ return -1;
+ }
+
+ if (ast_bridge_features_limits_construct(feature_limits)) {
+ return -1;
+ }
+
+ copy_bridge_features_limits(feature_limits, limits);
+ features->limits = feature_limits;
+
+/* BUGBUG feature interval hooks need to be reimplemented to be more stand alone. */
+ if (ast_bridge_interval_hook(features, feature_limits->duration,
+ bridge_features_duration_callback, feature_limits, NULL, remove_on_pull)) {
+ ast_log(LOG_ERROR, "Failed to schedule the duration limiter to the bridge channel.\n");
+ return -1;
+ }
+
+ feature_limits->quitting_time = ast_tvadd(ast_tvnow(), ast_samp2tv(feature_limits->duration, 1000));
+
+ if (!ast_strlen_zero(feature_limits->connect_sound)) {
+ if (ast_bridge_interval_hook(features, 1,
+ bridge_features_connect_callback, feature_limits, NULL, remove_on_pull)) {
+ ast_log(LOG_WARNING, "Failed to schedule connect sound to the bridge channel.\n");
+ }
+ }
+
+ if (feature_limits->warning && feature_limits->warning < feature_limits->duration) {
+ if (ast_bridge_interval_hook(features, feature_limits->duration - feature_limits->warning,
+ bridge_features_warning_callback, feature_limits, NULL, remove_on_pull)) {
+ ast_log(LOG_WARNING, "Failed to schedule warning sound playback to the bridge channel.\n");
+ }
+ }
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ return 0;
+}
+
+static int load_module(void)
+{
+ ast_bridge_interval_register(AST_BRIDGE_BUILTIN_INTERVAL_LIMITS, bridge_builtin_set_limits);
+
+ /* Bump up our reference count so we can't be unloaded. */
+ ast_module_ref(ast_module_info->self);
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Built in bridging interval features");
diff --git a/bridges/bridge_holding.c b/bridges/bridge_holding.c
new file mode 100644
index 000000000..fe0a7303f
--- /dev/null
+++ b/bridges/bridge_holding.c
@@ -0,0 +1,311 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Bridging technology for storing channels in a bridge for
+ * the purpose of holding, parking, queues, and other such
+ * states where a channel may need to be in a bridge but not
+ * actually communicating with anything.
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ *
+ * \ingroup bridges
+ */
+
+/*** MODULEINFO
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_technology.h"
+#include "asterisk/frame.h"
+#include "asterisk/musiconhold.h"
+
+enum role_flags {
+ HOLDING_ROLE_PARTICIPANT = (1 << 0),
+ HOLDING_ROLE_ANNOUNCER = (1 << 1),
+};
+
+/* BUGBUG Add IDLE_MODE_HOLD option to put channel on hold using AST_CONTROL_HOLD/AST_CONTROL_UNHOLD while in bridge */
+/* BUGBUG Add IDLE_MODE_SILENCE to send silence media frames to channel while in bridge (uses a silence generator) */
+/* BUGBUG A channel without the holding_participant role will assume IDLE_MODE_MOH with the default music class. */
+enum idle_modes {
+ IDLE_MODE_NONE = 0,
+ IDLE_MODE_MOH,
+ IDLE_MODE_RINGING,
+};
+
+/*! \brief Structure which contains per-channel role information */
+struct holding_channel {
+ struct ast_flags holding_roles;
+ enum idle_modes idle_mode;
+};
+
+static void participant_stop_hold_audio(struct ast_bridge_channel *bridge_channel)
+{
+ struct holding_channel *hc = bridge_channel->tech_pvt;
+ if (!hc) {
+ return;
+ }
+
+ switch (hc->idle_mode) {
+ case IDLE_MODE_MOH:
+ ast_moh_stop(bridge_channel->chan);
+ break;
+ case IDLE_MODE_RINGING:
+ ast_indicate(bridge_channel->chan, -1);
+ break;
+ case IDLE_MODE_NONE:
+ break;
+ }
+}
+
+static void participant_reaction_announcer_join(struct ast_bridge_channel *bridge_channel)
+{
+ struct ast_channel *chan;
+ chan = bridge_channel->chan;
+ participant_stop_hold_audio(bridge_channel);
+ if (ast_set_write_format_by_id(chan, AST_FORMAT_SLINEAR)) {
+ ast_log(LOG_WARNING, "Could not make participant %s compatible.\n", ast_channel_name(chan));
+ }
+}
+
+/* This should only be called on verified holding_participants. */
+static void participant_start_hold_audio(struct ast_bridge_channel *bridge_channel)
+{
+ struct holding_channel *hc = bridge_channel->tech_pvt;
+ const char *moh_class;
+
+ if (!hc) {
+ return;
+ }
+
+ switch(hc->idle_mode) {
+ case IDLE_MODE_MOH:
+ moh_class = ast_bridge_channel_get_role_option(bridge_channel, "holding_participant", "moh_class");
+ ast_moh_start(bridge_channel->chan, ast_strlen_zero(moh_class) ? NULL : moh_class, NULL);
+ break;
+ case IDLE_MODE_RINGING:
+ ast_indicate(bridge_channel->chan, AST_CONTROL_RINGING);
+ break;
+ case IDLE_MODE_NONE:
+ break;
+ }
+}
+
+static void handle_participant_join(struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *announcer_channel)
+{
+ struct ast_channel *us = bridge_channel->chan;
+ struct holding_channel *hc = bridge_channel->tech_pvt;
+ const char *idle_mode = ast_bridge_channel_get_role_option(bridge_channel, "holding_participant", "idle_mode");
+
+
+ if (!hc) {
+ return;
+ }
+
+ if (ast_strlen_zero(idle_mode)) {
+ hc->idle_mode = IDLE_MODE_NONE;
+ } else if (!strcmp(idle_mode, "musiconhold")) {
+ hc->idle_mode = IDLE_MODE_MOH;
+ } else if (!strcmp(idle_mode, "ringing")) {
+ hc->idle_mode = IDLE_MODE_RINGING;
+ } else {
+ ast_debug(2, "channel %s idle mode '%s' doesn't match any expected idle mode\n", ast_channel_name(us), idle_mode);
+ }
+
+ /* If the announcer channel isn't present, we need to set up ringing, music on hold, or whatever. */
+ if (!announcer_channel) {
+ participant_start_hold_audio(bridge_channel);
+ return;
+ }
+
+ /* If it is present though, we need to establish compatability. */
+ if (ast_set_write_format_by_id(us, AST_FORMAT_SLINEAR)) {
+ ast_log(LOG_WARNING, "Could not make participant %s compatible.\n", ast_channel_name(us));
+ }
+}
+
+static int holding_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+ struct ast_bridge_channel *other_channel;
+ struct ast_bridge_channel *announcer_channel;
+ struct holding_channel *hc;
+ struct ast_channel *us = bridge_channel->chan; /* The joining channel */
+
+ if (!(hc = ast_calloc(1, sizeof(*hc)))) {
+ return -1;
+ }
+
+ bridge_channel->tech_pvt = hc;
+
+ /* The bridge pvt holds the announcer channel if we have one. */
+ announcer_channel = bridge->tech_pvt;
+
+ if (ast_bridge_channel_has_role(bridge_channel, "announcer")) {
+ /* If another announcer already exists, scrap the holding channel struct so we know to ignore it in the future */
+ if (announcer_channel) {
+ bridge_channel->tech_pvt = NULL;
+ ast_free(hc);
+ ast_log(LOG_WARNING, "A second announcer channel %s attempted to enter a holding bridge.\n",
+ ast_channel_name(announcer_channel->chan));
+ return -1;
+ }
+
+ bridge->tech_pvt = bridge_channel;
+ ast_set_flag(&hc->holding_roles, HOLDING_ROLE_ANNOUNCER);
+
+ /* The announcer should always be made compatible with signed linear */
+ if (ast_set_read_format_by_id(us, AST_FORMAT_SLINEAR)) {
+ ast_log(LOG_ERROR, "Could not make announcer %s compatible.\n", ast_channel_name(us));
+ }
+
+ /* Make everyone compatible. While we are at it we should stop music on hold and ringing. */
+ AST_LIST_TRAVERSE(&bridge->channels, other_channel, entry) {
+ /* Skip the reaction if we are the channel in question */
+ if (bridge_channel == other_channel) {
+ continue;
+ }
+ participant_reaction_announcer_join(other_channel);
+ }
+
+ return 0;
+ }
+
+ /* If the entering channel isn't an announcer then we need to setup it's properties and put it in its holding state if necessary */
+ ast_set_flag(&hc->holding_roles, HOLDING_ROLE_PARTICIPANT);
+ handle_participant_join(bridge_channel, announcer_channel);
+ return 0;
+}
+
+static void participant_reaction_announcer_leave(struct ast_bridge_channel *bridge_channel)
+{
+ struct holding_channel *hc = bridge_channel->tech_pvt;
+
+ if (!hc) {
+ /* We are dealing with a channel that failed to join properly. Skip it. */
+ return;
+ }
+
+ ast_bridge_channel_restore_formats(bridge_channel);
+ if (ast_test_flag(&hc->holding_roles, HOLDING_ROLE_PARTICIPANT)) {
+ participant_start_hold_audio(bridge_channel);
+ }
+}
+
+static void holding_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+ struct ast_bridge_channel *other_channel;
+ struct holding_channel *hc = bridge_channel->tech_pvt;
+
+ if (!hc) {
+ return;
+ }
+
+ if (!ast_test_flag(&hc->holding_roles, HOLDING_ROLE_ANNOUNCER)) {
+ /* It's not an announcer so nothing needs to react to its departure. Just free the tech_pvt. */
+ if (!bridge->tech_pvt) {
+ /* Since no announcer is in the channel, we may be playing MOH/ringing. Stop that. */
+ participant_stop_hold_audio(bridge_channel);
+ }
+ ast_free(hc);
+ bridge_channel->tech_pvt = NULL;
+ return;
+ }
+
+ /* When the announcer leaves, the other channels should reset their formats and go back to moh/ringing */
+ AST_LIST_TRAVERSE(&bridge->channels, other_channel, entry) {
+ participant_reaction_announcer_leave(other_channel);
+ }
+
+ /* Since the announcer is leaving, we should clear the tech_pvt pointing to it */
+ bridge->tech_pvt = NULL;
+
+ ast_free(hc);
+ bridge_channel->tech_pvt = NULL;
+}
+
+static int holding_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+{
+ struct ast_bridge_channel *cur;
+ struct holding_channel *hc = bridge_channel->tech_pvt;
+
+ /* If there is no tech_pvt, then the channel failed to allocate one when it joined and is borked. Don't listen to him. */
+ if (!hc) {
+ return -1;
+ }
+
+ /* If we aren't an announcer, we never have any business writing anything. */
+ if (!ast_test_flag(&hc->holding_roles, HOLDING_ROLE_ANNOUNCER)) {
+ return -1;
+ }
+
+ /* Ok, so we are the announcer and there are one or more people available to receive our writes. Let's do it. */
+ AST_LIST_TRAVERSE(&bridge->channels, cur, entry) {
+ if (bridge_channel == cur || !cur->tech_pvt) {
+ continue;
+ }
+
+ ast_bridge_channel_queue_frame(cur, frame);
+ }
+
+ return 0;
+}
+
+static struct ast_bridge_technology holding_bridge = {
+ .name = "holding_bridge",
+ .capabilities = AST_BRIDGE_CAPABILITY_HOLDING,
+ .preference = AST_BRIDGE_PREFERENCE_BASE_HOLDING,
+ .write = holding_bridge_write,
+ .join = holding_bridge_join,
+ .leave = holding_bridge_leave,
+};
+
+static int unload_module(void)
+{
+ ast_format_cap_destroy(holding_bridge.format_capabilities);
+ return ast_bridge_technology_unregister(&holding_bridge);
+}
+
+static int load_module(void)
+{
+ if (!(holding_bridge.format_capabilities = ast_format_cap_alloc())) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ ast_format_cap_add_all_by_type(holding_bridge.format_capabilities, AST_FORMAT_TYPE_AUDIO);
+ ast_format_cap_add_all_by_type(holding_bridge.format_capabilities, AST_FORMAT_TYPE_VIDEO);
+ ast_format_cap_add_all_by_type(holding_bridge.format_capabilities, AST_FORMAT_TYPE_TEXT);
+
+ return ast_bridge_technology_register(&holding_bridge);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Holding bridge module");
+
diff --git a/bridges/bridge_multiplexed.c b/bridges/bridge_multiplexed.c
deleted file mode 100644
index 309ad47e3..000000000
--- a/bridges/bridge_multiplexed.c
+++ /dev/null
@@ -1,513 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2008, Digium, Inc.
- *
- * Joshua Colp <jcolp@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Two channel bridging module which groups bridges into batches of threads
- *
- * \author Joshua Colp <jcolp@digium.com>
- *
- * \ingroup bridges
- */
-
-/*** MODULEINFO
- <support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-
-#include "asterisk/module.h"
-#include "asterisk/channel.h"
-#include "asterisk/bridging.h"
-#include "asterisk/bridging_technology.h"
-#include "asterisk/frame.h"
-#include "asterisk/astobj2.h"
-
-/*! \brief Number of buckets our multiplexed thread container can have */
-#define MULTIPLEXED_BUCKETS 53
-
-/*! \brief Number of bridges we handle in a single thread */
-#define MULTIPLEXED_MAX_BRIDGES 4
-
-/*! \brief Structure which represents a single thread handling multiple 2 channel bridges */
-struct multiplexed_thread {
- /*! Thread itself */
- pthread_t thread;
- /*! Channels serviced by this thread */
- struct ast_channel *chans[2 * MULTIPLEXED_MAX_BRIDGES];
- /*! Pipe used to wake up the multiplexed thread */
- int pipe[2];
- /*! Number of channels actually being serviced by this thread */
- unsigned int service_count;
- /*! Number of bridges in this thread */
- unsigned int bridges;
- /*! TRUE if the thread is waiting on channels */
- unsigned int waiting:1;
-};
-
-/*! \brief Container of all operating multiplexed threads */
-static struct ao2_container *muxed_threads;
-
-/*! \brief Callback function for finding a free multiplexed thread */
-static int find_multiplexed_thread(void *obj, void *arg, int flags)
-{
- struct multiplexed_thread *muxed_thread = obj;
-
- return (muxed_thread->bridges < MULTIPLEXED_MAX_BRIDGES) ? CMP_MATCH | CMP_STOP : 0;
-}
-
-/*! \brief Destroy callback for a multiplexed thread structure */
-static void destroy_multiplexed_thread(void *obj)
-{
- struct multiplexed_thread *muxed_thread = obj;
-
- if (muxed_thread->pipe[0] > -1) {
- close(muxed_thread->pipe[0]);
- }
- if (muxed_thread->pipe[1] > -1) {
- close(muxed_thread->pipe[1]);
- }
-}
-
-/*! \brief Create function which finds/reserves/references a multiplexed thread structure */
-static int multiplexed_bridge_create(struct ast_bridge *bridge)
-{
- struct multiplexed_thread *muxed_thread;
-
- ao2_lock(muxed_threads);
-
- /* Try to find an existing thread to handle our additional channels */
- muxed_thread = ao2_callback(muxed_threads, 0, find_multiplexed_thread, NULL);
- if (!muxed_thread) {
- int flags;
-
- /* If we failed we will have to create a new one from scratch */
- muxed_thread = ao2_alloc(sizeof(*muxed_thread), destroy_multiplexed_thread);
- if (!muxed_thread) {
- ast_debug(1, "Failed to find or create a new multiplexed thread for bridge '%p'\n", bridge);
- ao2_unlock(muxed_threads);
- return -1;
- }
-
- muxed_thread->pipe[0] = muxed_thread->pipe[1] = -1;
- /* Setup a pipe so we can poke the thread itself when needed */
- if (pipe(muxed_thread->pipe)) {
- ast_debug(1, "Failed to create a pipe for poking a multiplexed thread for bridge '%p'\n", bridge);
- ao2_ref(muxed_thread, -1);
- ao2_unlock(muxed_threads);
- return -1;
- }
-
- /* Setup each pipe for non-blocking operation */
- flags = fcntl(muxed_thread->pipe[0], F_GETFL);
- if (fcntl(muxed_thread->pipe[0], F_SETFL, flags | O_NONBLOCK) < 0) {
- ast_log(LOG_WARNING, "Failed to setup first nudge pipe for non-blocking operation on %p (%d: %s)\n", bridge, errno, strerror(errno));
- ao2_ref(muxed_thread, -1);
- ao2_unlock(muxed_threads);
- return -1;
- }
- flags = fcntl(muxed_thread->pipe[1], F_GETFL);
- if (fcntl(muxed_thread->pipe[1], F_SETFL, flags | O_NONBLOCK) < 0) {
- ast_log(LOG_WARNING, "Failed to setup second nudge pipe for non-blocking operation on %p (%d: %s)\n", bridge, errno, strerror(errno));
- ao2_ref(muxed_thread, -1);
- ao2_unlock(muxed_threads);
- return -1;
- }
-
- /* Set up default parameters */
- muxed_thread->thread = AST_PTHREADT_NULL;
-
- /* Finally link us into the container so others may find us */
- ao2_link(muxed_threads, muxed_thread);
- ast_debug(1, "Created multiplexed thread '%p' for bridge '%p'\n", muxed_thread, bridge);
- } else {
- ast_debug(1, "Found multiplexed thread '%p' for bridge '%p'\n", muxed_thread, bridge);
- }
-
- /* Increase the number of bridges using this multiplexed bridge */
- ++muxed_thread->bridges;
-
- ao2_unlock(muxed_threads);
-
- bridge->bridge_pvt = muxed_thread;
-
- return 0;
-}
-
-/*!
- * \internal
- * \brief Nudges the multiplex thread.
- * \since 12.0.0
- *
- * \param muxed_thread Controller to poke the thread.
- *
- * \note This function assumes the muxed_thread is locked.
- *
- * \return Nothing
- */
-static void multiplexed_nudge(struct multiplexed_thread *muxed_thread)
-{
- int nudge = 0;
-
- if (muxed_thread->thread == AST_PTHREADT_NULL) {
- return;
- }
-
- if (write(muxed_thread->pipe[1], &nudge, sizeof(nudge)) != sizeof(nudge)) {
- ast_log(LOG_ERROR, "We couldn't poke multiplexed thread '%p'... something is VERY wrong\n", muxed_thread);
- }
-
- while (muxed_thread->waiting) {
- sched_yield();
- }
-}
-
-/*! \brief Destroy function which unreserves/unreferences/removes a multiplexed thread structure */
-static int multiplexed_bridge_destroy(struct ast_bridge *bridge)
-{
- struct multiplexed_thread *muxed_thread;
- pthread_t thread;
-
- muxed_thread = bridge->bridge_pvt;
- if (!muxed_thread) {
- return -1;
- }
- bridge->bridge_pvt = NULL;
-
- ao2_lock(muxed_threads);
-
- if (--muxed_thread->bridges) {
- /* Other bridges are still using the multiplexed thread. */
- ao2_unlock(muxed_threads);
- } else {
- ast_debug(1, "Unlinking multiplexed thread '%p' since nobody is using it anymore\n",
- muxed_thread);
- ao2_unlink(muxed_threads, muxed_thread);
- ao2_unlock(muxed_threads);
-
- /* Stop the multiplexed bridge thread. */
- ao2_lock(muxed_thread);
- multiplexed_nudge(muxed_thread);
- thread = muxed_thread->thread;
- muxed_thread->thread = AST_PTHREADT_STOP;
- ao2_unlock(muxed_thread);
-
- if (thread != AST_PTHREADT_NULL) {
- /* Wait for multiplexed bridge thread to die. */
- pthread_join(thread, NULL);
- }
- }
-
- ao2_ref(muxed_thread, -1);
- return 0;
-}
-
-/*! \brief Thread function that executes for multiplexed threads */
-static void *multiplexed_thread_function(void *data)
-{
- struct multiplexed_thread *muxed_thread = data;
- int fds = muxed_thread->pipe[0];
-
- ast_debug(1, "Starting actual thread for multiplexed thread '%p'\n", muxed_thread);
-
- ao2_lock(muxed_thread);
-
- while (muxed_thread->thread != AST_PTHREADT_STOP) {
- struct ast_channel *winner;
- int to = -1;
- int outfd = -1;
-
- if (1 < muxed_thread->service_count) {
- struct ast_channel *first;
-
- /* Move channels around so not just the first one gets priority */
- first = muxed_thread->chans[0];
- memmove(muxed_thread->chans, muxed_thread->chans + 1,
- sizeof(struct ast_channel *) * (muxed_thread->service_count - 1));
- muxed_thread->chans[muxed_thread->service_count - 1] = first;
- }
-
- muxed_thread->waiting = 1;
- ao2_unlock(muxed_thread);
- winner = ast_waitfor_nandfds(muxed_thread->chans, muxed_thread->service_count, &fds, 1, NULL, &outfd, &to);
- muxed_thread->waiting = 0;
- ao2_lock(muxed_thread);
- if (muxed_thread->thread == AST_PTHREADT_STOP) {
- break;
- }
-
- if (outfd > -1) {
- int nudge;
-
- if (read(muxed_thread->pipe[0], &nudge, sizeof(nudge)) < 0) {
- if (errno != EINTR && errno != EAGAIN) {
- ast_log(LOG_WARNING, "read() failed for pipe on multiplexed thread '%p': %s\n", muxed_thread, strerror(errno));
- }
- }
- }
- if (winner && ast_channel_internal_bridge(winner)) {
- struct ast_bridge *bridge;
- int stop = 0;
-
- ao2_unlock(muxed_thread);
- while ((bridge = ast_channel_internal_bridge(winner)) && ao2_trylock(bridge)) {
- sched_yield();
- if (muxed_thread->thread == AST_PTHREADT_STOP) {
- stop = 1;
- break;
- }
- }
- if (!stop && bridge) {
- ast_bridge_handle_trip(bridge, NULL, winner, -1);
- ao2_unlock(bridge);
- }
- ao2_lock(muxed_thread);
- }
- }
-
- ao2_unlock(muxed_thread);
-
- ast_debug(1, "Stopping actual thread for multiplexed thread '%p'\n", muxed_thread);
- ao2_ref(muxed_thread, -1);
-
- return NULL;
-}
-
-/*!
- * \internal
- * \brief Check to see if the multiplexed bridge thread needs to be started.
- * \since 12.0.0
- *
- * \param muxed_thread Controller to check if need to start thread.
- *
- * \note This function assumes the muxed_thread is locked.
- *
- * \return Nothing
- */
-static void multiplexed_thread_start(struct multiplexed_thread *muxed_thread)
-{
- if (muxed_thread->service_count && muxed_thread->thread == AST_PTHREADT_NULL) {
- ao2_ref(muxed_thread, +1);
- if (ast_pthread_create(&muxed_thread->thread, NULL, multiplexed_thread_function, muxed_thread)) {
- muxed_thread->thread = AST_PTHREADT_NULL;/* For paranoia's sake. */
- ao2_ref(muxed_thread, -1);
- ast_log(LOG_WARNING, "Failed to create the common thread for multiplexed thread '%p', trying next time\n",
- muxed_thread);
- }
- }
-}
-
-/*!
- * \internal
- * \brief Add a channel to the multiplexed bridge.
- * \since 12.0.0
- *
- * \param muxed_thread Controller to add a channel.
- * \param chan Channel to add to the channel service array.
- *
- * \return Nothing
- */
-static void multiplexed_chan_add(struct multiplexed_thread *muxed_thread, struct ast_channel *chan)
-{
- int idx;
-
- ao2_lock(muxed_thread);
-
- multiplexed_nudge(muxed_thread);
-
- /* Check if already in the channel service array for safety. */
- for (idx = 0; idx < muxed_thread->service_count; ++idx) {
- if (muxed_thread->chans[idx] == chan) {
- break;
- }
- }
- if (idx == muxed_thread->service_count) {
- /* Channel to add was not already in the array. */
- if (muxed_thread->service_count < ARRAY_LEN(muxed_thread->chans)) {
- muxed_thread->chans[muxed_thread->service_count++] = chan;
- } else {
- ast_log(LOG_ERROR, "Could not add channel %s to multiplexed thread %p. Array not large enough.\n",
- ast_channel_name(chan), muxed_thread);
- ast_assert(0);
- }
- }
-
- multiplexed_thread_start(muxed_thread);
-
- ao2_unlock(muxed_thread);
-}
-
-/*!
- * \internal
- * \brief Remove a channel from the multiplexed bridge.
- * \since 12.0.0
- *
- * \param muxed_thread Controller to remove a channel.
- * \param chan Channel to remove from the channel service array.
- *
- * \return Nothing
- */
-static void multiplexed_chan_remove(struct multiplexed_thread *muxed_thread, struct ast_channel *chan)
-{
- int idx;
-
- ao2_lock(muxed_thread);
-
- multiplexed_nudge(muxed_thread);
-
- /* Remove channel from service array. */
- for (idx = 0; idx < muxed_thread->service_count; ++idx) {
- if (muxed_thread->chans[idx] != chan) {
- continue;
- }
- muxed_thread->chans[idx] = muxed_thread->chans[--muxed_thread->service_count];
- break;
- }
-
- multiplexed_thread_start(muxed_thread);
-
- ao2_unlock(muxed_thread);
-}
-
-/*! \brief Join function which actually adds the channel into the array to be monitored */
-static int multiplexed_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
- struct ast_channel *c0 = AST_LIST_FIRST(&bridge->channels)->chan;
- struct ast_channel *c1 = AST_LIST_LAST(&bridge->channels)->chan;
- struct multiplexed_thread *muxed_thread = bridge->bridge_pvt;
-
- ast_debug(1, "Adding channel '%s' to multiplexed thread '%p' for monitoring\n", ast_channel_name(bridge_channel->chan), muxed_thread);
-
- multiplexed_chan_add(muxed_thread, bridge_channel->chan);
-
- /* If the second channel has not yet joined do not make things compatible */
- if (c0 == c1) {
- return 0;
- }
-
- if ((ast_format_cmp(ast_channel_writeformat(c0), ast_channel_readformat(c1)) == AST_FORMAT_CMP_EQUAL) &&
- (ast_format_cmp(ast_channel_readformat(c0), ast_channel_writeformat(c1)) == AST_FORMAT_CMP_EQUAL) &&
- (ast_format_cap_identical(ast_channel_nativeformats(c0), ast_channel_nativeformats(c1)))) {
- return 0;
- }
-
- return ast_channel_make_compatible(c0, c1);
-}
-
-/*! \brief Leave function which actually removes the channel from the array */
-static int multiplexed_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
- struct multiplexed_thread *muxed_thread = bridge->bridge_pvt;
-
- ast_debug(1, "Removing channel '%s' from multiplexed thread '%p'\n", ast_channel_name(bridge_channel->chan), muxed_thread);
-
- multiplexed_chan_remove(muxed_thread, bridge_channel->chan);
-
- return 0;
-}
-
-/*! \brief Suspend function which means control of the channel is going elsewhere */
-static void multiplexed_bridge_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
- struct multiplexed_thread *muxed_thread = bridge->bridge_pvt;
-
- ast_debug(1, "Suspending channel '%s' from multiplexed thread '%p'\n", ast_channel_name(bridge_channel->chan), muxed_thread);
-
- multiplexed_chan_remove(muxed_thread, bridge_channel->chan);
-}
-
-/*! \brief Unsuspend function which means control of the channel is coming back to us */
-static void multiplexed_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
- struct multiplexed_thread *muxed_thread = bridge->bridge_pvt;
-
- ast_debug(1, "Unsuspending channel '%s' from multiplexed thread '%p'\n", ast_channel_name(bridge_channel->chan), muxed_thread);
-
- multiplexed_chan_add(muxed_thread, bridge_channel->chan);
-}
-
-/*! \brief Write function for writing frames into the bridge */
-static enum ast_bridge_write_result multiplexed_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
-{
- struct ast_bridge_channel *other;
-
- /* If this is the only channel in this bridge then immediately exit */
- if (AST_LIST_FIRST(&bridge->channels) == AST_LIST_LAST(&bridge->channels)) {
- return AST_BRIDGE_WRITE_FAILED;
- }
-
- /* Find the channel we actually want to write to */
- if (!(other = (AST_LIST_FIRST(&bridge->channels) == bridge_channel ? AST_LIST_LAST(&bridge->channels) : AST_LIST_FIRST(&bridge->channels)))) {
- return AST_BRIDGE_WRITE_FAILED;
- }
-
- /* Write the frame out if they are in the waiting state... don't worry about freeing it, the bridging core will take care of it */
- if (other->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
- ast_write(other->chan, frame);
- }
-
- return AST_BRIDGE_WRITE_SUCCESS;
-}
-
-static struct ast_bridge_technology multiplexed_bridge = {
- .name = "multiplexed_bridge",
- .capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX,
- .preference = AST_BRIDGE_PREFERENCE_HIGH,
- .create = multiplexed_bridge_create,
- .destroy = multiplexed_bridge_destroy,
- .join = multiplexed_bridge_join,
- .leave = multiplexed_bridge_leave,
- .suspend = multiplexed_bridge_suspend,
- .unsuspend = multiplexed_bridge_unsuspend,
- .write = multiplexed_bridge_write,
-};
-
-static int unload_module(void)
-{
- int res = ast_bridge_technology_unregister(&multiplexed_bridge);
-
- ao2_ref(muxed_threads, -1);
- multiplexed_bridge.format_capabilities = ast_format_cap_destroy(multiplexed_bridge.format_capabilities);
-
- return res;
-}
-
-static int load_module(void)
-{
- if (!(muxed_threads = ao2_container_alloc(MULTIPLEXED_BUCKETS, NULL, NULL))) {
- return AST_MODULE_LOAD_DECLINE;
- }
- if (!(multiplexed_bridge.format_capabilities = ast_format_cap_alloc())) {
- return AST_MODULE_LOAD_DECLINE;
- }
- ast_format_cap_add_all_by_type(multiplexed_bridge.format_capabilities, AST_FORMAT_TYPE_AUDIO);
- ast_format_cap_add_all_by_type(multiplexed_bridge.format_capabilities, AST_FORMAT_TYPE_VIDEO);
- ast_format_cap_add_all_by_type(multiplexed_bridge.format_capabilities, AST_FORMAT_TYPE_TEXT);
- return ast_bridge_technology_register(&multiplexed_bridge);
-}
-
-AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Multiplexed two channel bridging module");
diff --git a/bridges/bridge_native_rtp.c b/bridges/bridge_native_rtp.c
new file mode 100644
index 000000000..1117e5aed
--- /dev/null
+++ b/bridges/bridge_native_rtp.c
@@ -0,0 +1,414 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Joshua Colp <jcolp@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Native RTP bridging module
+ *
+ * \author Joshua Colp <jcolp@digium.com>
+ *
+ * \ingroup bridges
+ */
+
+/*** MODULEINFO
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_technology.h"
+#include "asterisk/frame.h"
+#include "asterisk/rtp_engine.h"
+#include "asterisk/audiohook.h"
+
+/*! \brief Forward declarations for frame hook usage */
+static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+static void native_rtp_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+
+/*! \brief Internal structure which contains information about bridged RTP channels */
+struct native_rtp_bridge_data {
+ /*! \brief Framehook used to intercept certain control frames */
+ int id;
+};
+
+/*! \brief Frame hook that is called to intercept hold/unhold */
+static struct ast_frame *native_rtp_framehook(struct ast_channel *chan, struct ast_frame *f, enum ast_framehook_event event, void *data)
+{
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+
+ if (!f || (event != AST_FRAMEHOOK_EVENT_WRITE)) {
+ return f;
+ }
+
+ ast_channel_lock(chan);
+ bridge = ast_channel_get_bridge(chan);
+ ast_channel_unlock(chan);
+
+ /* It's safe for NULL to be passed to both of these, bridge_channel isn't used at all */
+ if (bridge) {
+ if (f->subclass.integer == AST_CONTROL_HOLD) {
+ native_rtp_bridge_leave(ast_channel_internal_bridge(chan), NULL);
+ } else if ((f->subclass.integer == AST_CONTROL_UNHOLD) || (f->subclass.integer == AST_CONTROL_UPDATE_RTP_PEER)) {
+ native_rtp_bridge_join(ast_channel_internal_bridge(chan), NULL);
+ }
+ }
+
+ return f;
+}
+
+/*! \brief Internal helper function which checks whether the channels are compatible with our native bridging */
+static int native_rtp_bridge_capable(struct ast_channel *chan)
+{
+ if (ast_channel_monitor(chan) || (ast_channel_audiohooks(chan) &&
+ !ast_audiohook_write_list_empty(ast_channel_audiohooks(chan))) ||
+ !ast_framehook_list_is_empty(ast_channel_framehooks(chan))) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+/*! \brief Internal helper function which gets all RTP information (glue and instances) relating to the given channels */
+static enum ast_rtp_glue_result native_rtp_bridge_get(struct ast_channel *c0, struct ast_channel *c1, struct ast_rtp_glue **glue0,
+ struct ast_rtp_glue **glue1, struct ast_rtp_instance **instance0, struct ast_rtp_instance **instance1,
+ struct ast_rtp_instance **vinstance0, struct ast_rtp_instance **vinstance1)
+{
+ enum ast_rtp_glue_result audio_glue0_res = AST_RTP_GLUE_RESULT_FORBID, video_glue0_res = AST_RTP_GLUE_RESULT_FORBID;
+ enum ast_rtp_glue_result audio_glue1_res = AST_RTP_GLUE_RESULT_FORBID, video_glue1_res = AST_RTP_GLUE_RESULT_FORBID;
+
+ if (!(*glue0 = ast_rtp_instance_get_glue(ast_channel_tech(c0)->type)) ||
+ (c1 && !(*glue1 = ast_rtp_instance_get_glue(ast_channel_tech(c1)->type)))) {
+ return AST_RTP_GLUE_RESULT_FORBID;
+ }
+
+ audio_glue0_res = (*glue0)->get_rtp_info(c0, instance0);
+ video_glue0_res = (*glue0)->get_vrtp_info ? (*glue0)->get_vrtp_info(c0, vinstance0) : AST_RTP_GLUE_RESULT_FORBID;
+
+ if (c1) {
+ audio_glue1_res = (*glue1)->get_rtp_info(c1, instance1);
+ video_glue1_res = (*glue1)->get_vrtp_info ? (*glue1)->get_vrtp_info(c1, vinstance1) : AST_RTP_GLUE_RESULT_FORBID;
+ }
+
+ /* Apply any limitations on direct media bridging that may be present */
+ if (audio_glue0_res == audio_glue1_res && audio_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {
+ if ((*glue0)->allow_rtp_remote && !((*glue0)->allow_rtp_remote(c0, *instance1))) {
+ /* If the allow_rtp_remote indicates that remote isn't allowed, revert to local bridge */
+ audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
+ } else if ((*glue1)->allow_rtp_remote && !((*glue1)->allow_rtp_remote(c1, *instance0))) {
+ audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
+ }
+ }
+ if (c1 && video_glue0_res == video_glue1_res && video_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {
+ if ((*glue0)->allow_vrtp_remote && !((*glue0)->allow_vrtp_remote(c0, *instance1))) {
+ /* if the allow_vrtp_remote indicates that remote isn't allowed, revert to local bridge */
+ video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
+ } else if ((*glue1)->allow_vrtp_remote && !((*glue1)->allow_vrtp_remote(c1, *instance0))) {
+ video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
+ }
+ }
+
+ /* If we are carrying video, and both sides are not going to remotely bridge... fail the native bridge */
+ if (video_glue0_res != AST_RTP_GLUE_RESULT_FORBID && (audio_glue0_res != AST_RTP_GLUE_RESULT_REMOTE || video_glue0_res != AST_RTP_GLUE_RESULT_REMOTE)) {
+ audio_glue0_res = AST_RTP_GLUE_RESULT_FORBID;
+ }
+ if (c1 && video_glue1_res != AST_RTP_GLUE_RESULT_FORBID && (audio_glue1_res != AST_RTP_GLUE_RESULT_REMOTE || video_glue1_res != AST_RTP_GLUE_RESULT_REMOTE)) {
+ audio_glue1_res = AST_RTP_GLUE_RESULT_FORBID;
+ }
+
+ /* If any sort of bridge is forbidden just completely bail out and go back to generic bridging */
+ if (audio_glue0_res == AST_RTP_GLUE_RESULT_FORBID || (c1 && audio_glue1_res == AST_RTP_GLUE_RESULT_FORBID)) {
+ return AST_RTP_GLUE_RESULT_FORBID;
+ }
+
+ return audio_glue0_res;
+}
+
+static int native_rtp_bridge_compatible(struct ast_bridge *bridge)
+{
+ struct ast_bridge_channel *c0 = AST_LIST_FIRST(&bridge->channels);
+ struct ast_bridge_channel *c1 = AST_LIST_LAST(&bridge->channels);
+ enum ast_rtp_glue_result native_type;
+ struct ast_rtp_glue *glue0, *glue1;
+ struct ast_rtp_instance *instance0 = NULL, *instance1 = NULL, *vinstance0 = NULL, *vinstance1 = NULL;
+ RAII_VAR(struct ast_format_cap *, cap0, ast_format_cap_alloc_nolock(), ast_format_cap_destroy);
+ RAII_VAR(struct ast_format_cap *, cap1, ast_format_cap_alloc_nolock(), ast_format_cap_destroy);
+ int read_ptime0, read_ptime1, write_ptime0, write_ptime1;
+
+ /* We require two channels before even considering native bridging */
+ if (bridge->num_channels != 2) {
+ ast_debug(1, "Bridge '%s' can not use native RTP bridge as two channels are required\n",
+ bridge->uniqueid);
+ return 0;
+ }
+
+ if (!native_rtp_bridge_capable(c0->chan)) {
+ ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has features which prevent it\n",
+ bridge->uniqueid, ast_channel_name(c0->chan));
+ return 0;
+ }
+
+ if (!native_rtp_bridge_capable(c1->chan)) {
+ ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has features which prevent it\n",
+ bridge->uniqueid, ast_channel_name(c1->chan));
+ return 0;
+ }
+
+ if ((native_type = native_rtp_bridge_get(c0->chan, c1->chan, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1))
+ == AST_RTP_GLUE_RESULT_FORBID) {
+ ast_debug(1, "Bridge '%s' can not use native RTP bridge as it was forbidden while getting details\n",
+ bridge->uniqueid);
+ return 0;
+ }
+
+ if (ao2_container_count(c0->features->dtmf_hooks) && ast_rtp_instance_dtmf_mode_get(instance0)) {
+ ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has DTMF hooks\n",
+ bridge->uniqueid, ast_channel_name(c0->chan));
+ return 0;
+ }
+
+ if (ao2_container_count(c1->features->dtmf_hooks) && ast_rtp_instance_dtmf_mode_get(instance1)) {
+ ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has DTMF hooks\n",
+ bridge->uniqueid, ast_channel_name(c1->chan));
+ return 0;
+ }
+
+ if ((native_type == AST_RTP_GLUE_RESULT_LOCAL) && ((ast_rtp_instance_get_engine(instance0)->local_bridge !=
+ ast_rtp_instance_get_engine(instance1)->local_bridge) ||
+ (ast_rtp_instance_get_engine(instance0)->dtmf_compatible &&
+ !ast_rtp_instance_get_engine(instance0)->dtmf_compatible(c0->chan, instance0, c1->chan, instance1)))) {
+ ast_debug(1, "Bridge '%s' can not use local native RTP bridge as local bridge or DTMF is not compatible\n",
+ bridge->uniqueid);
+ return 0;
+ }
+
+ /* Make sure that codecs match */
+ if (glue0->get_codec) {
+ glue0->get_codec(c0->chan, cap0);
+ }
+ if (glue1->get_codec) {
+ glue1->get_codec(c1->chan, cap1);
+ }
+ if (!ast_format_cap_is_empty(cap0) && !ast_format_cap_is_empty(cap1) && !ast_format_cap_has_joint(cap0, cap1)) {
+ char tmp0[256] = { 0, }, tmp1[256] = { 0, };
+
+ ast_debug(1, "Channel codec0 = %s is not codec1 = %s, cannot native bridge in RTP.\n",
+ ast_getformatname_multiple(tmp0, sizeof(tmp0), cap0),
+ ast_getformatname_multiple(tmp1, sizeof(tmp1), cap1));
+ return 0;
+ }
+
+ read_ptime0 = (ast_codec_pref_getsize(&ast_rtp_instance_get_codecs(instance0)->pref, ast_channel_rawreadformat(c0->chan))).cur_ms;
+ read_ptime1 = (ast_codec_pref_getsize(&ast_rtp_instance_get_codecs(instance1)->pref, ast_channel_rawreadformat(c1->chan))).cur_ms;
+ write_ptime0 = (ast_codec_pref_getsize(&ast_rtp_instance_get_codecs(instance0)->pref, ast_channel_rawwriteformat(c0->chan))).cur_ms;
+ write_ptime1 = (ast_codec_pref_getsize(&ast_rtp_instance_get_codecs(instance1)->pref, ast_channel_rawwriteformat(c1->chan))).cur_ms;
+
+ if (read_ptime0 != write_ptime1 || read_ptime1 != write_ptime0) {
+ ast_debug(1, "Packetization differs between RTP streams (%d != %d or %d != %d). Cannot native bridge in RTP\n",
+ read_ptime0, write_ptime1, read_ptime1, write_ptime0);
+ return 0;
+ }
+
+ return 1;
+}
+
+/*! \brief Helper function which adds frame hook to bridge channel */
+static int native_rtp_bridge_framehook_attach(struct ast_bridge_channel *bridge_channel)
+{
+ struct native_rtp_bridge_data *data = ao2_alloc(sizeof(*data), NULL);
+ static struct ast_framehook_interface hook = {
+ .version = AST_FRAMEHOOK_INTERFACE_VERSION,
+ .event_cb = native_rtp_framehook,
+ };
+
+ if (!data) {
+ return -1;
+ }
+
+ ast_channel_lock(bridge_channel->chan);
+
+ if (!(data->id = ast_framehook_attach(bridge_channel->chan, &hook)) < 0) {
+ ast_channel_unlock(bridge_channel->chan);
+ ao2_cleanup(data);
+ return -1;
+ }
+
+ ast_channel_unlock(bridge_channel->chan);
+
+ bridge_channel->bridge_pvt = data;
+
+ return 0;
+}
+
+/*! \brief Helper function which removes frame hook from bridge channel */
+static void native_rtp_bridge_framehook_detach(struct ast_bridge_channel *bridge_channel)
+{
+ RAII_VAR(struct native_rtp_bridge_data *, data, bridge_channel->bridge_pvt, ao2_cleanup);
+
+ if (!data) {
+ return;
+ }
+
+ ast_channel_lock(bridge_channel->chan);
+ ast_framehook_detach(bridge_channel->chan, data->id);
+ ast_channel_unlock(bridge_channel->chan);
+ bridge_channel->bridge_pvt = NULL;
+}
+
+static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+ struct ast_bridge_channel *c0 = AST_LIST_FIRST(&bridge->channels);
+ struct ast_bridge_channel *c1 = AST_LIST_LAST(&bridge->channels);
+ enum ast_rtp_glue_result native_type;
+ struct ast_rtp_glue *glue0, *glue1;
+ struct ast_rtp_instance *instance0 = NULL, *instance1 = NULL, *vinstance0 = NULL;
+ struct ast_rtp_instance *vinstance1 = NULL, *tinstance0 = NULL, *tinstance1 = NULL;
+ RAII_VAR(struct ast_format_cap *, cap0, ast_format_cap_alloc_nolock(), ast_format_cap_destroy);
+ RAII_VAR(struct ast_format_cap *, cap1, ast_format_cap_alloc_nolock(), ast_format_cap_destroy);
+
+ native_rtp_bridge_framehook_detach(c0);
+ if (native_rtp_bridge_framehook_attach(c0)) {
+ return -1;
+ }
+
+ native_rtp_bridge_framehook_detach(c1);
+ if (native_rtp_bridge_framehook_attach(c1)) {
+ native_rtp_bridge_framehook_detach(c0);
+ return -1;
+ }
+
+ native_type = native_rtp_bridge_get(c0->chan, c1->chan, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1);
+
+ if (glue0->get_codec) {
+ glue0->get_codec(c0->chan, cap0);
+ }
+ if (glue1->get_codec) {
+ glue1->get_codec(c1->chan, cap1);
+ }
+
+ if (native_type == AST_RTP_GLUE_RESULT_LOCAL) {
+ if (ast_rtp_instance_get_engine(instance0)->local_bridge) {
+ ast_rtp_instance_get_engine(instance0)->local_bridge(instance0, instance1);
+ }
+ if (ast_rtp_instance_get_engine(instance1)->local_bridge) {
+ ast_rtp_instance_get_engine(instance1)->local_bridge(instance1, instance0);
+ }
+ ast_rtp_instance_set_bridged(instance0, instance1);
+ ast_rtp_instance_set_bridged(instance1, instance0);
+ } else {
+ glue0->update_peer(c0->chan, instance1, vinstance1, tinstance1, cap1, 0);
+ glue1->update_peer(c1->chan, instance0, vinstance0, tinstance0, cap0, 0);
+ }
+
+ return 0;
+}
+
+static void native_rtp_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+ native_rtp_bridge_join(bridge, bridge_channel);
+}
+
+static void native_rtp_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+ struct ast_bridge_channel *c0 = AST_LIST_FIRST(&bridge->channels) ? AST_LIST_FIRST(&bridge->channels) : bridge_channel;
+ struct ast_bridge_channel *c1 = AST_LIST_LAST(&bridge->channels);
+ enum ast_rtp_glue_result native_type;
+ struct ast_rtp_glue *glue0, *glue1 = NULL;
+ struct ast_rtp_instance *instance0 = NULL, *instance1 = NULL, *vinstance0 = NULL, *vinstance1 = NULL;
+
+ native_rtp_bridge_framehook_detach(c0);
+ if (c1) {
+ native_rtp_bridge_framehook_detach(c1);
+ }
+
+ native_type = native_rtp_bridge_get(c0->chan, c1 ? c1->chan : NULL, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1);
+
+ if (native_type == AST_RTP_GLUE_RESULT_LOCAL) {
+ if (ast_rtp_instance_get_engine(instance0)->local_bridge) {
+ ast_rtp_instance_get_engine(instance0)->local_bridge(instance0, NULL);
+ }
+ if (instance1 && ast_rtp_instance_get_engine(instance1)->local_bridge) {
+ ast_rtp_instance_get_engine(instance1)->local_bridge(instance1, NULL);
+ }
+ ast_rtp_instance_set_bridged(instance0, instance1);
+ if (instance1) {
+ ast_rtp_instance_set_bridged(instance1, instance0);
+ }
+ } else {
+ glue0->update_peer(c0->chan, NULL, NULL, NULL, NULL, 0);
+ if (glue1) {
+ glue1->update_peer(c1->chan, NULL, NULL, NULL, NULL, 0);
+ }
+ }
+}
+
+static int native_rtp_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+{
+ struct ast_bridge_channel *other = ast_bridge_channel_peer(bridge_channel);
+
+ if (!other) {
+ return -1;
+ }
+
+ /* The bridging core takes care of freeing the passed in frame. */
+ ast_bridge_channel_queue_frame(other, frame);
+
+ return 0;
+}
+
+static struct ast_bridge_technology native_rtp_bridge = {
+ .name = "native_rtp",
+ .capabilities = AST_BRIDGE_CAPABILITY_NATIVE,
+ .preference = AST_BRIDGE_PREFERENCE_BASE_NATIVE,
+ .join = native_rtp_bridge_join,
+ .unsuspend = native_rtp_bridge_unsuspend,
+ .leave = native_rtp_bridge_leave,
+ .suspend = native_rtp_bridge_leave,
+ .write = native_rtp_bridge_write,
+ .compatible = native_rtp_bridge_compatible,
+};
+
+static int unload_module(void)
+{
+ ast_format_cap_destroy(native_rtp_bridge.format_capabilities);
+ return ast_bridge_technology_unregister(&native_rtp_bridge);
+}
+
+static int load_module(void)
+{
+ if (!(native_rtp_bridge.format_capabilities = ast_format_cap_alloc())) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ ast_format_cap_add_all_by_type(native_rtp_bridge.format_capabilities, AST_FORMAT_TYPE_AUDIO);
+ ast_format_cap_add_all_by_type(native_rtp_bridge.format_capabilities, AST_FORMAT_TYPE_VIDEO);
+ ast_format_cap_add_all_by_type(native_rtp_bridge.format_capabilities, AST_FORMAT_TYPE_TEXT);
+
+ return ast_bridge_technology_register(&native_rtp_bridge);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Native RTP bridging module");
diff --git a/bridges/bridge_simple.c b/bridges/bridge_simple.c
index 947983bae..3e53b31c0 100644
--- a/bridges/bridge_simple.c
+++ b/bridges/bridge_simple.c
@@ -66,32 +66,26 @@ static int simple_bridge_join(struct ast_bridge *bridge, struct ast_bridge_chann
return ast_channel_make_compatible(c0, c1);
}
-static enum ast_bridge_write_result simple_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+static int simple_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
struct ast_bridge_channel *other;
- /* If this is the only channel in this bridge then immediately exit */
- if (AST_LIST_FIRST(&bridge->channels) == AST_LIST_LAST(&bridge->channels)) {
- return AST_BRIDGE_WRITE_FAILED;
- }
-
/* Find the channel we actually want to write to */
- if (!(other = (AST_LIST_FIRST(&bridge->channels) == bridge_channel ? AST_LIST_LAST(&bridge->channels) : AST_LIST_FIRST(&bridge->channels)))) {
- return AST_BRIDGE_WRITE_FAILED;
+ other = ast_bridge_channel_peer(bridge_channel);
+ if (!other) {
+ return -1;
}
- /* Write the frame out if they are in the waiting state... don't worry about freeing it, the bridging core will take care of it */
- if (other->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
- ast_write(other->chan, frame);
- }
+ /* The bridging core takes care of freeing the passed in frame. */
+ ast_bridge_channel_queue_frame(other, frame);
- return AST_BRIDGE_WRITE_SUCCESS;
+ return 0;
}
static struct ast_bridge_technology simple_bridge = {
.name = "simple_bridge",
- .capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX | AST_BRIDGE_CAPABILITY_THREAD,
- .preference = AST_BRIDGE_PREFERENCE_MEDIUM,
+ .capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX,
+ .preference = AST_BRIDGE_PREFERENCE_BASE_1TO1MIX,
.join = simple_bridge_join,
.write = simple_bridge_write,
};
diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c
index 613601a1f..4583435a0 100644
--- a/bridges/bridge_softmix.c
+++ b/bridges/bridge_softmix.c
@@ -100,13 +100,15 @@ struct softmix_channel {
struct ast_frame read_frame;
/*! DSP for detecting silence */
struct ast_dsp *dsp;
- /*! Bit used to indicate if a channel is talking or not. This affects how
- * the channel's audio is mixed back to it. */
- int talking:1;
- /*! Bit used to indicate that the channel provided audio for this mixing interval */
- int have_audio:1;
- /*! Bit used to indicate that a frame is available to be written out to the channel */
- int have_frame:1;
+ /*!
+ * \brief TRUE if a channel is talking.
+ *
+ * \note This affects how the channel's audio is mixed back to
+ * it.
+ */
+ unsigned int talking:1;
+ /*! TRUE if the channel provided audio for this mixing interval */
+ unsigned int have_audio:1;
/*! Buffer containing final mixed audio from all sources */
short final_buf[MAX_DATALEN];
/*! Buffer containing only the audio from the channel */
@@ -117,28 +119,36 @@ struct softmix_channel {
struct softmix_bridge_data {
struct ast_timer *timer;
+ /*! Lock for signaling the mixing thread. */
+ ast_mutex_t lock;
+ /*! Condition, used if we need to wake up the mixing thread. */
+ ast_cond_t cond;
+ /*! Thread handling the mixing */
+ pthread_t thread;
unsigned int internal_rate;
unsigned int internal_mixing_interval;
+ /*! TRUE if the mixing thread should stop */
+ unsigned int stop:1;
};
struct softmix_stats {
- /*! Each index represents a sample rate used above the internal rate. */
- unsigned int sample_rates[16];
- /*! Each index represents the number of channels using the same index in the sample_rates array. */
- unsigned int num_channels[16];
- /*! the number of channels above the internal sample rate */
- unsigned int num_above_internal_rate;
- /*! the number of channels at the internal sample rate */
- unsigned int num_at_internal_rate;
- /*! the absolute highest sample rate supported by any channel in the bridge */
- unsigned int highest_supported_rate;
- /*! Is the sample rate locked by the bridge, if so what is that rate.*/
- unsigned int locked_rate;
+ /*! Each index represents a sample rate used above the internal rate. */
+ unsigned int sample_rates[16];
+ /*! Each index represents the number of channels using the same index in the sample_rates array. */
+ unsigned int num_channels[16];
+ /*! the number of channels above the internal sample rate */
+ unsigned int num_above_internal_rate;
+ /*! the number of channels at the internal sample rate */
+ unsigned int num_at_internal_rate;
+ /*! the absolute highest sample rate supported by any channel in the bridge */
+ unsigned int highest_supported_rate;
+ /*! Is the sample rate locked by the bridge, if so what is that rate.*/
+ unsigned int locked_rate;
};
struct softmix_mixing_array {
- int max_num_entries;
- int used_entries;
+ unsigned int max_num_entries;
+ unsigned int used_entries;
int16_t **buffers;
};
@@ -213,7 +223,7 @@ static void softmix_translate_helper_change_rate(struct softmix_translate_helper
/*!
* \internal
* \brief Get the next available audio on the softmix channel's read stream
- * and determine if it should be mixed out or not on the write stream.
+ * and determine if it should be mixed out or not on the write stream.
*
* \retval pointer to buffer containing the exact number of samples requested on success.
* \retval NULL if no samples are present
@@ -295,54 +305,9 @@ static void softmix_translate_helper_cleanup(struct softmix_translate_helper *tr
}
}
-static void softmix_bridge_data_destroy(void *obj)
-{
- struct softmix_bridge_data *softmix_data = obj;
-
- if (softmix_data->timer) {
- ast_timer_close(softmix_data->timer);
- softmix_data->timer = NULL;
- }
-}
-
-/*! \brief Function called when a bridge is created */
-static int softmix_bridge_create(struct ast_bridge *bridge)
-{
- struct softmix_bridge_data *softmix_data;
-
- if (!(softmix_data = ao2_alloc(sizeof(*softmix_data), softmix_bridge_data_destroy))) {
- return -1;
- }
- if (!(softmix_data->timer = ast_timer_open())) {
- ao2_ref(softmix_data, -1);
- return -1;
- }
-
- /* start at 8khz, let it grow from there */
- softmix_data->internal_rate = 8000;
- softmix_data->internal_mixing_interval = DEFAULT_SOFTMIX_INTERVAL;
-
- bridge->bridge_pvt = softmix_data;
- return 0;
-}
-
-/*! \brief Function called when a bridge is destroyed */
-static int softmix_bridge_destroy(struct ast_bridge *bridge)
-{
- struct softmix_bridge_data *softmix_data;
-
- softmix_data = bridge->bridge_pvt;
- if (!softmix_data) {
- return -1;
- }
- ao2_ref(softmix_data, -1);
- bridge->bridge_pvt = NULL;
- return 0;
-}
-
static void set_softmix_bridge_data(int rate, int interval, struct ast_bridge_channel *bridge_channel, int reset)
{
- struct softmix_channel *sc = bridge_channel->bridge_pvt;
+ struct softmix_channel *sc = bridge_channel->tech_pvt;
unsigned int channel_read_rate = ast_format_rate(ast_channel_rawreadformat(bridge_channel->chan));
ast_mutex_lock(&sc->lock);
@@ -382,39 +347,89 @@ static void set_softmix_bridge_data(int rate, int interval, struct ast_bridge_ch
ast_mutex_unlock(&sc->lock);
}
+/*!
+ * \internal
+ * \brief Poke the mixing thread in case it is waiting for an active channel.
+ * \since 12.0.0
+ *
+ * \param softmix_data Bridge mixing data.
+ *
+ * \return Nothing
+ */
+static void softmix_poke_thread(struct softmix_bridge_data *softmix_data)
+{
+ ast_mutex_lock(&softmix_data->lock);
+ ast_cond_signal(&softmix_data->cond);
+ ast_mutex_unlock(&softmix_data->lock);
+}
+
+/*! \brief Function called when a channel is unsuspended from the bridge */
+static void softmix_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+ if (bridge->tech_pvt) {
+ softmix_poke_thread(bridge->tech_pvt);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Indicate a source change to the channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel source is changing.
+ *
+ * \return Nothing
+ */
+static void softmix_src_change(struct ast_bridge_channel *bridge_channel)
+{
+ ast_bridge_channel_queue_control_data(bridge_channel, AST_CONTROL_SRCCHANGE, NULL, 0);
+}
+
/*! \brief Function called when a channel is joined into the bridge */
static int softmix_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
struct softmix_channel *sc;
- struct softmix_bridge_data *softmix_data = bridge->bridge_pvt;
+ struct softmix_bridge_data *softmix_data;
+
+ softmix_data = bridge->tech_pvt;
+ if (!softmix_data) {
+ return -1;
+ }
/* Create a new softmix_channel structure and allocate various things on it */
if (!(sc = ast_calloc(1, sizeof(*sc)))) {
return -1;
}
+ softmix_src_change(bridge_channel);
+
/* Can't forget the lock */
ast_mutex_init(&sc->lock);
/* Can't forget to record our pvt structure within the bridged channel structure */
- bridge_channel->bridge_pvt = sc;
+ bridge_channel->tech_pvt = sc;
set_softmix_bridge_data(softmix_data->internal_rate,
- softmix_data->internal_mixing_interval ? softmix_data->internal_mixing_interval : DEFAULT_SOFTMIX_INTERVAL,
+ softmix_data->internal_mixing_interval
+ ? softmix_data->internal_mixing_interval
+ : DEFAULT_SOFTMIX_INTERVAL,
bridge_channel, 0);
+ softmix_poke_thread(softmix_data);
return 0;
}
/*! \brief Function called when a channel leaves the bridge */
-static int softmix_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+static void softmix_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
- struct softmix_channel *sc = bridge_channel->bridge_pvt;
+ struct softmix_channel *sc = bridge_channel->tech_pvt;
- if (!(bridge_channel->bridge_pvt)) {
- return 0;
+ if (!sc) {
+ return;
}
- bridge_channel->bridge_pvt = NULL;
+ bridge_channel->tech_pvt = NULL;
+
+ softmix_src_change(bridge_channel);
/* Drop mutex lock */
ast_mutex_destroy(&sc->lock);
@@ -427,111 +442,122 @@ static int softmix_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_cha
/* Eep! drop ourselves */
ast_free(sc);
-
- return 0;
}
/*!
* \internal
- * \brief If the bridging core passes DTMF to us, then they want it to be distributed out to all memebers. Do that here.
+ * \brief Pass the given frame to everyone else.
+ * \since 12.0.0
+ *
+ * \param bridge What bridge to distribute frame.
+ * \param bridge_channel Channel to optionally not pass frame to. (NULL to pass to everyone)
+ * \param frame Frame to pass.
+ *
+ * \return Nothing
*/
-static void softmix_pass_dtmf(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+static void softmix_pass_everyone_else(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
- struct ast_bridge_channel *tmp;
- AST_LIST_TRAVERSE(&bridge->channels, tmp, entry) {
- if (tmp == bridge_channel) {
+ struct ast_bridge_channel *cur;
+
+ AST_LIST_TRAVERSE(&bridge->channels, cur, entry) {
+ if (cur == bridge_channel) {
continue;
}
- ast_write(tmp->chan, frame);
+ ast_bridge_channel_queue_frame(cur, frame);
}
}
static void softmix_pass_video_top_priority(struct ast_bridge *bridge, struct ast_frame *frame)
{
- struct ast_bridge_channel *tmp;
- AST_LIST_TRAVERSE(&bridge->channels, tmp, entry) {
- if (tmp->suspended) {
+ struct ast_bridge_channel *cur;
+
+ AST_LIST_TRAVERSE(&bridge->channels, cur, entry) {
+ if (cur->suspended) {
continue;
}
- if (ast_bridge_is_video_src(bridge, tmp->chan) == 1) {
- ast_write(tmp->chan, frame);
+ if (ast_bridge_is_video_src(bridge, cur->chan) == 1) {
+ ast_bridge_channel_queue_frame(cur, frame);
break;
}
}
}
-static void softmix_pass_video_all(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame, int echo)
+/*!
+ * \internal
+ * \brief Determine what to do with a video frame.
+ * \since 12.0.0
+ *
+ * \param bridge Which bridge is getting the frame
+ * \param bridge_channel Which channel is writing the frame.
+ * \param frame What is being written.
+ *
+ * \return Nothing
+ */
+static void softmix_bridge_write_video(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
- struct ast_bridge_channel *tmp;
- AST_LIST_TRAVERSE(&bridge->channels, tmp, entry) {
- if (tmp->suspended) {
- continue;
+ struct softmix_channel *sc;
+ int video_src_priority;
+
+ /* Determine if the video frame should be distributed or not */
+ switch (bridge->video_mode.mode) {
+ case AST_BRIDGE_VIDEO_MODE_NONE:
+ break;
+ case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
+ video_src_priority = ast_bridge_is_video_src(bridge, bridge_channel->chan);
+ if (video_src_priority == 1) {
+ /* Pass to me and everyone else. */
+ softmix_pass_everyone_else(bridge, NULL, frame);
}
- if ((tmp->chan == bridge_channel->chan) && !echo) {
- continue;
+ break;
+ case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
+ sc = bridge_channel->tech_pvt;
+ ast_mutex_lock(&sc->lock);
+ ast_bridge_update_talker_src_video_mode(bridge, bridge_channel->chan,
+ sc->video_talker.energy_average,
+ ast_format_get_video_mark(&frame->subclass.format));
+ ast_mutex_unlock(&sc->lock);
+ video_src_priority = ast_bridge_is_video_src(bridge, bridge_channel->chan);
+ if (video_src_priority == 1) {
+ int num_src = ast_bridge_number_video_src(bridge);
+ int echo = num_src > 1 ? 0 : 1;
+
+ softmix_pass_everyone_else(bridge, echo ? NULL : bridge_channel, frame);
+ } else if (video_src_priority == 2) {
+ softmix_pass_video_top_priority(bridge, frame);
}
- ast_write(tmp->chan, frame);
+ break;
}
}
-/*! \brief Function called when a channel writes a frame into the bridge */
-static enum ast_bridge_write_result softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+/*!
+ * \internal
+ * \brief Determine what to do with a voice frame.
+ * \since 12.0.0
+ *
+ * \param bridge Which bridge is getting the frame
+ * \param bridge_channel Which channel is writing the frame.
+ * \param frame What is being written.
+ *
+ * \return Nothing
+ */
+static void softmix_bridge_write_voice(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
- struct softmix_channel *sc = bridge_channel->bridge_pvt;
- struct softmix_bridge_data *softmix_data = bridge->bridge_pvt;
+ struct softmix_channel *sc = bridge_channel->tech_pvt;
+ struct softmix_bridge_data *softmix_data = bridge->tech_pvt;
int totalsilence = 0;
int cur_energy = 0;
int silence_threshold = bridge_channel->tech_args.silence_threshold ?
bridge_channel->tech_args.silence_threshold :
DEFAULT_SOFTMIX_SILENCE_THRESHOLD;
char update_talking = -1; /* if this is set to 0 or 1, tell the bridge that the channel has started or stopped talking. */
- int res = AST_BRIDGE_WRITE_SUCCESS;
-
- /* Only accept audio frames, all others are unsupported */
- if (frame->frametype == AST_FRAME_DTMF_END || frame->frametype == AST_FRAME_DTMF_BEGIN) {
- softmix_pass_dtmf(bridge, bridge_channel, frame);
- goto bridge_write_cleanup;
- } else if (frame->frametype != AST_FRAME_VOICE && frame->frametype != AST_FRAME_VIDEO) {
- res = AST_BRIDGE_WRITE_UNSUPPORTED;
- goto bridge_write_cleanup;
- } else if (frame->datalen == 0) {
- goto bridge_write_cleanup;
- }
-
- /* Determine if this video frame should be distributed or not */
- if (frame->frametype == AST_FRAME_VIDEO) {
- int num_src = ast_bridge_number_video_src(bridge);
- int video_src_priority = ast_bridge_is_video_src(bridge, bridge_channel->chan);
-
- switch (bridge->video_mode.mode) {
- case AST_BRIDGE_VIDEO_MODE_NONE:
- break;
- case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
- if (video_src_priority == 1) {
- softmix_pass_video_all(bridge, bridge_channel, frame, 1);
- }
- break;
- case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
- ast_mutex_lock(&sc->lock);
- ast_bridge_update_talker_src_video_mode(bridge, bridge_channel->chan, sc->video_talker.energy_average, ast_format_get_video_mark(&frame->subclass.format));
- ast_mutex_unlock(&sc->lock);
- if (video_src_priority == 1) {
- int echo = num_src > 1 ? 0 : 1;
- softmix_pass_video_all(bridge, bridge_channel, frame, echo);
- } else if (video_src_priority == 2) {
- softmix_pass_video_top_priority(bridge, frame);
- }
- break;
- }
- goto bridge_write_cleanup;
- }
- /* If we made it here, we are going to write the frame into the conference */
+ /* Write the frame into the conference */
ast_mutex_lock(&sc->lock);
ast_dsp_silence_with_energy(sc->dsp, frame, &totalsilence, &cur_energy);
if (bridge->video_mode.mode == AST_BRIDGE_VIDEO_MODE_TALKER_SRC) {
int cur_slot = sc->video_talker.energy_history_cur_slot;
+
sc->video_talker.energy_accum -= sc->video_talker.energy_history[cur_slot];
sc->video_talker.energy_accum += cur_energy;
sc->video_talker.energy_history[cur_slot] = cur_energy;
@@ -568,50 +594,77 @@ static enum ast_bridge_write_result softmix_bridge_write(struct ast_bridge *brid
ast_slinfactory_feed(&sc->factory, frame);
}
- /* If a frame is ready to be written out, do so */
- if (sc->have_frame) {
- ast_write(bridge_channel->chan, &sc->write_frame);
- sc->have_frame = 0;
- }
-
/* Alllll done */
ast_mutex_unlock(&sc->lock);
if (update_talking != -1) {
- ast_bridge_notify_talking(bridge, bridge_channel, update_talking);
+ ast_bridge_notify_talking(bridge_channel, update_talking);
}
-
- return res;
-
-bridge_write_cleanup:
- /* Even though the frame is not being written into the conference because it is not audio,
- * we should use this opportunity to check to see if a frame is ready to be written out from
- * the conference to the channel. */
- ast_mutex_lock(&sc->lock);
- if (sc->have_frame) {
- ast_write(bridge_channel->chan, &sc->write_frame);
- sc->have_frame = 0;
- }
- ast_mutex_unlock(&sc->lock);
-
- return res;
}
-/*! \brief Function called when the channel's thread is poked */
-static int softmix_bridge_poke(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+/*!
+ * \internal
+ * \brief Determine what to do with a control frame.
+ * \since 12.0.0
+ *
+ * \param bridge Which bridge is getting the frame
+ * \param bridge_channel Which channel is writing the frame.
+ * \param frame What is being written.
+ *
+ * \return Nothing
+ */
+static void softmix_bridge_write_control(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
- struct softmix_channel *sc = bridge_channel->bridge_pvt;
+/* BUGBUG need to look at channel roles to determine what to do with control frame. */
+ /*! \todo BUGBUG softmix_bridge_write_control() not written */
+}
- ast_mutex_lock(&sc->lock);
+/*!
+ * \internal
+ * \brief Determine what to do with a frame written into the bridge.
+ * \since 12.0.0
+ *
+ * \param bridge Which bridge is getting the frame
+ * \param bridge_channel Which channel is writing the frame.
+ * \param frame What is being written.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * \note On entry, bridge is already locked.
+ */
+static int softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+{
+ int res = 0;
- if (sc->have_frame) {
- ast_write(bridge_channel->chan, &sc->write_frame);
- sc->have_frame = 0;
+ if (!bridge->tech_pvt || !bridge_channel->tech_pvt) {
+ return -1;
}
- ast_mutex_unlock(&sc->lock);
+ switch (frame->frametype) {
+ case AST_FRAME_DTMF_BEGIN:
+ case AST_FRAME_DTMF_END:
+ softmix_pass_everyone_else(bridge, bridge_channel, frame);
+ break;
+ case AST_FRAME_VOICE:
+ softmix_bridge_write_voice(bridge, bridge_channel, frame);
+ break;
+ case AST_FRAME_VIDEO:
+ softmix_bridge_write_video(bridge, bridge_channel, frame);
+ break;
+ case AST_FRAME_CONTROL:
+ softmix_bridge_write_control(bridge, bridge_channel, frame);
+ break;
+ case AST_FRAME_BRIDGE_ACTION:
+ softmix_pass_everyone_else(bridge, bridge_channel, frame);
+ break;
+ default:
+ ast_debug(3, "Frame type %d unsupported\n", frame->frametype);
+ res = -1;
+ break;
+ }
- return 0;
+ return res;
}
static void gather_softmix_stats(struct softmix_stats *stats,
@@ -648,7 +701,7 @@ static void gather_softmix_stats(struct softmix_stats *stats,
* \brief Analyse mixing statistics and change bridges internal rate
* if necessary.
*
- * \retval 0, no changes to internal rate
+ * \retval 0, no changes to internal rate
* \ratval 1, internal rate was changed, update all the channels on the next mixing iteration.
*/
static unsigned int analyse_softmix_stats(struct softmix_stats *stats, struct softmix_bridge_data *softmix_data)
@@ -665,7 +718,8 @@ static unsigned int analyse_softmix_stats(struct softmix_stats *stats, struct so
* from the current rate we are using. */
if (softmix_data->internal_rate != stats->locked_rate) {
softmix_data->internal_rate = stats->locked_rate;
- ast_debug(1, " Bridge is locked in at sample rate %d\n", softmix_data->internal_rate);
+ ast_debug(1, "Bridge is locked in at sample rate %d\n",
+ softmix_data->internal_rate);
return 1;
}
} else if (stats->num_above_internal_rate >= 2) {
@@ -704,13 +758,15 @@ static unsigned int analyse_softmix_stats(struct softmix_stats *stats, struct so
}
}
- ast_debug(1, " Bridge changed from %d To %d\n", softmix_data->internal_rate, best_rate);
+ ast_debug(1, "Bridge changed from %d To %d\n",
+ softmix_data->internal_rate, best_rate);
softmix_data->internal_rate = best_rate;
return 1;
} else if (!stats->num_at_internal_rate && !stats->num_above_internal_rate) {
/* In this case, the highest supported rate is actually lower than the internal rate */
softmix_data->internal_rate = stats->highest_supported_rate;
- ast_debug(1, " Bridge changed from %d to %d\n", softmix_data->internal_rate, stats->highest_supported_rate);
+ ast_debug(1, "Bridge changed from %d to %d\n",
+ softmix_data->internal_rate, stats->highest_supported_rate);
return 1;
}
return 0;
@@ -745,38 +801,38 @@ static int softmix_mixing_array_grow(struct softmix_mixing_array *mixing_array,
return 0;
}
-/*! \brief Function which acts as the mixing thread */
-static int softmix_bridge_thread(struct ast_bridge *bridge)
+/*!
+ * \brief Mixing loop.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int softmix_mixing_loop(struct ast_bridge *bridge)
{
struct softmix_stats stats = { { 0 }, };
struct softmix_mixing_array mixing_array;
- struct softmix_bridge_data *softmix_data;
+ struct softmix_bridge_data *softmix_data = bridge->tech_pvt;
struct ast_timer *timer;
struct softmix_translate_helper trans_helper;
int16_t buf[MAX_DATALEN];
unsigned int stat_iteration_counter = 0; /* counts down, gather stats at zero and reset. */
int timingfd;
int update_all_rates = 0; /* set this when the internal sample rate has changed */
- int i, x;
+ unsigned int idx;
+ unsigned int x;
int res = -1;
- softmix_data = bridge->bridge_pvt;
- if (!softmix_data) {
- goto softmix_cleanup;
- }
-
- ao2_ref(softmix_data, 1);
timer = softmix_data->timer;
timingfd = ast_timer_fd(timer);
softmix_translate_helper_init(&trans_helper, softmix_data->internal_rate);
ast_timer_set_rate(timer, (1000 / softmix_data->internal_mixing_interval));
/* Give the mixing array room to grow, memory is cheap but allocations are expensive. */
- if (softmix_mixing_array_init(&mixing_array, bridge->num + 10)) {
+ if (softmix_mixing_array_init(&mixing_array, bridge->num_channels + 10)) {
goto softmix_cleanup;
}
- while (!bridge->stop && !bridge->refresh && bridge->array_num) {
+ while (!softmix_data->stop && bridge->num_active) {
struct ast_bridge_channel *bridge_channel;
int timeout = -1;
enum ast_format_id cur_slin_id = ast_format_slin_by_rate(softmix_data->internal_rate);
@@ -793,8 +849,8 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
}
/* Grow the mixing array buffer as participants are added. */
- if (mixing_array.max_num_entries < bridge->num
- && softmix_mixing_array_grow(&mixing_array, bridge->num + 5)) {
+ if (mixing_array.max_num_entries < bridge->num_channels
+ && softmix_mixing_array_grow(&mixing_array, bridge->num_channels + 5)) {
goto softmix_cleanup;
}
@@ -815,7 +871,7 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
/* Go through pulling audio from each factory that has it available */
AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
- struct softmix_channel *sc = bridge_channel->bridge_pvt;
+ struct softmix_channel *sc = bridge_channel->tech_pvt;
/* Update the sample rate to match the bridge's native sample rate if necessary. */
if (update_all_rates) {
@@ -842,15 +898,15 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
/* mix it like crazy */
memset(buf, 0, softmix_datalen);
- for (i = 0; i < mixing_array.used_entries; i++) {
- for (x = 0; x < softmix_samples; x++) {
- ast_slinear_saturated_add(buf + x, mixing_array.buffers[i] + x);
+ for (idx = 0; idx < mixing_array.used_entries; ++idx) {
+ for (x = 0; x < softmix_samples; ++x) {
+ ast_slinear_saturated_add(buf + x, mixing_array.buffers[idx] + x);
}
}
/* Next step go through removing the channel's own audio and creating a good frame... */
AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
- struct softmix_channel *sc = bridge_channel->bridge_pvt;
+ struct softmix_channel *sc = bridge_channel->tech_pvt;
if (bridge_channel->suspended) {
continue;
@@ -869,13 +925,10 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
/* process the softmix channel's new write audio */
softmix_process_write_audio(&trans_helper, ast_channel_rawwriteformat(bridge_channel->chan), sc);
- /* The frame is now ready for use... */
- sc->have_frame = 1;
-
ast_mutex_unlock(&sc->lock);
- /* Poke bridged channel thread just in case */
- pthread_kill(bridge_channel->thread, SIGURG);
+ /* A frame is now ready for the channel. */
+ ast_bridge_channel_queue_frame(bridge_channel, &sc->write_frame);
}
update_all_rates = 0;
@@ -885,17 +938,17 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
}
stat_iteration_counter--;
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
/* cleanup any translation frame data from the previous mixing iteration. */
softmix_translate_helper_cleanup(&trans_helper);
/* Wait for the timing source to tell us to wake up and get things done */
ast_waitfor_n_fd(&timingfd, 1, &timeout, NULL);
if (ast_timer_ack(timer, 1) < 0) {
ast_log(LOG_ERROR, "Failed to acknowledge timer in softmix bridge.\n");
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
goto softmix_cleanup;
}
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
/* make sure to detect mixing interval changes if they occur. */
if (bridge->internal_mixing_interval && (bridge->internal_mixing_interval != softmix_data->internal_mixing_interval)) {
@@ -910,23 +963,141 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
softmix_cleanup:
softmix_translate_helper_destroy(&trans_helper);
softmix_mixing_array_destroy(&mixing_array);
- if (softmix_data) {
- ao2_ref(softmix_data, -1);
- }
return res;
}
+/*!
+ * \internal
+ * \brief Mixing thread.
+ * \since 12.0.0
+ *
+ * \note The thread does not have its own reference to the
+ * bridge. The lifetime of the thread is tied to the lifetime
+ * of the mixing technology association with the bridge.
+ */
+static void *softmix_mixing_thread(void *data)
+{
+ struct ast_bridge *bridge = data;
+ struct softmix_bridge_data *softmix_data;
+
+ ast_bridge_lock(bridge);
+ if (bridge->callid) {
+ ast_callid_threadassoc_add(bridge->callid);
+ }
+
+ ast_debug(1, "Bridge %s: starting mixing thread\n", bridge->uniqueid);
+
+ softmix_data = bridge->tech_pvt;
+ while (!softmix_data->stop) {
+ if (!bridge->num_active) {
+ /* Wait for something to happen to the bridge. */
+ ast_bridge_unlock(bridge);
+ ast_mutex_lock(&softmix_data->lock);
+ if (!softmix_data->stop) {
+ ast_cond_wait(&softmix_data->cond, &softmix_data->lock);
+ }
+ ast_mutex_unlock(&softmix_data->lock);
+ ast_bridge_lock(bridge);
+ continue;
+ }
+
+ if (softmix_mixing_loop(bridge)) {
+ /*
+ * A mixing error occurred. Sleep and try again later so we
+ * won't flood the logs.
+ */
+ ast_bridge_unlock(bridge);
+ sleep(1);
+ ast_bridge_lock(bridge);
+ }
+ }
+
+ ast_bridge_unlock(bridge);
+
+ ast_debug(1, "Bridge %s: stopping mixing thread\n", bridge->uniqueid);
+
+ return NULL;
+}
+
+static void softmix_bridge_data_destroy(struct softmix_bridge_data *softmix_data)
+{
+ if (softmix_data->timer) {
+ ast_timer_close(softmix_data->timer);
+ softmix_data->timer = NULL;
+ }
+ ast_mutex_destroy(&softmix_data->lock);
+ ast_free(softmix_data);
+}
+
+/*! \brief Function called when a bridge is created */
+static int softmix_bridge_create(struct ast_bridge *bridge)
+{
+ struct softmix_bridge_data *softmix_data;
+
+ softmix_data = ast_calloc(1, sizeof(*softmix_data));
+ if (!softmix_data) {
+ return -1;
+ }
+ ast_mutex_init(&softmix_data->lock);
+ softmix_data->timer = ast_timer_open();
+ if (!softmix_data->timer) {
+ softmix_bridge_data_destroy(softmix_data);
+ return -1;
+ }
+ /* start at 8khz, let it grow from there */
+ softmix_data->internal_rate = 8000;
+ softmix_data->internal_mixing_interval = DEFAULT_SOFTMIX_INTERVAL;
+
+ bridge->tech_pvt = softmix_data;
+
+ /* Start the mixing thread. */
+ if (ast_pthread_create(&softmix_data->thread, NULL, softmix_mixing_thread, bridge)) {
+ softmix_data->thread = AST_PTHREADT_NULL;
+ softmix_bridge_data_destroy(softmix_data);
+ bridge->tech_pvt = NULL;
+ return -1;
+ }
+
+ return 0;
+}
+
+/*! \brief Function called when a bridge is destroyed */
+static void softmix_bridge_destroy(struct ast_bridge *bridge)
+{
+ struct softmix_bridge_data *softmix_data;
+ pthread_t thread;
+
+ softmix_data = bridge->tech_pvt;
+ if (!softmix_data) {
+ return;
+ }
+
+ /* Stop the mixing thread. */
+ ast_mutex_lock(&softmix_data->lock);
+ softmix_data->stop = 1;
+ ast_cond_signal(&softmix_data->cond);
+ thread = softmix_data->thread;
+ softmix_data->thread = AST_PTHREADT_NULL;
+ ast_mutex_unlock(&softmix_data->lock);
+ if (thread != AST_PTHREADT_NULL) {
+ ast_debug(1, "Waiting for mixing thread to die.\n");
+ pthread_join(thread, NULL);
+ }
+
+ softmix_bridge_data_destroy(softmix_data);
+ bridge->tech_pvt = NULL;
+}
+
static struct ast_bridge_technology softmix_bridge = {
.name = "softmix",
- .capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX | AST_BRIDGE_CAPABILITY_THREAD | AST_BRIDGE_CAPABILITY_MULTITHREADED | AST_BRIDGE_CAPABILITY_OPTIMIZE | AST_BRIDGE_CAPABILITY_VIDEO,
- .preference = AST_BRIDGE_PREFERENCE_LOW,
+ .capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX,
+ .preference = AST_BRIDGE_PREFERENCE_BASE_MULTIMIX,
.create = softmix_bridge_create,
.destroy = softmix_bridge_destroy,
.join = softmix_bridge_join,
.leave = softmix_bridge_leave,
+ .unsuspend = softmix_bridge_unsuspend,
.write = softmix_bridge_write,
- .thread = softmix_bridge_thread,
- .poke = softmix_bridge_poke,
};
static int unload_module(void)