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
This commit is contained in:
Richard Mudgett
2013-05-21 18:00:22 +00:00
parent e1e1cc2dee
commit 3d63833bd6
99 changed files with 19717 additions and 7682 deletions

View File

@@ -47,8 +47,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/file.h"
#include "asterisk/app.h"
#include "asterisk/astobj2.h"
#include "asterisk/pbx.h"
#include "asterisk/parking.h"
/*! \brief Helper function that presents dialtone and grabs extension */
/*!
* \brief Helper function that presents dialtone and grabs extension
*
* \retval 0 on success
* \retval -1 on failure
*/
static int grab_transfer(struct ast_channel *chan, char *exten, size_t exten_len, const char *context)
{
int res;
@@ -56,15 +63,35 @@ static int grab_transfer(struct ast_channel *chan, char *exten, size_t exten_len
/* Play the simple "transfer" prompt out and wait */
res = ast_stream_and_wait(chan, "pbx-transfer", AST_DIGIT_ANY);
ast_stopstream(chan);
/* If the person hit a DTMF digit while the above played back stick it into the buffer */
if (res < 0) {
/* Hangup or error */
return -1;
}
if (res) {
exten[0] = (char)res;
/* Store the DTMF digit that interrupted playback of the file. */
exten[0] = res;
}
/* Drop to dialtone so they can enter the extension they want to transfer to */
res = ast_app_dtget(chan, context, exten, exten_len, 100, 1000);
/* BUGBUG the timeout needs to be configurable from features.conf. */
res = ast_app_dtget(chan, context, exten, exten_len, exten_len - 1, 3000);
if (res < 0) {
/* Hangup or error */
res = -1;
} else if (!res) {
/* 0 for invalid extension dialed. */
if (ast_strlen_zero(exten)) {
ast_debug(1, "%s dialed no digits.\n", ast_channel_name(chan));
} else {
ast_debug(1, "%s dialed '%s@%s' does not exist.\n",
ast_channel_name(chan), exten, context);
}
ast_stream_and_wait(chan, "pbx-invalid", AST_DIGIT_NONE);
res = -1;
} else {
/* Dialed extension is valid. */
res = 0;
}
return res;
}
@@ -78,8 +105,10 @@ static struct ast_channel *dial_transfer(struct ast_channel *caller, const char
/* Fill the variable with the extension and context we want to call */
snprintf(destination, sizeof(destination), "%s@%s", exten, context);
/* Now we request that chan_local prepare to call the destination */
if (!(chan = ast_request("Local", ast_channel_nativeformats(caller), caller, destination, &cause))) {
/* Now we request a local channel to prepare to call the destination */
chan = ast_request("Local", ast_channel_nativeformats(caller), caller, destination,
&cause);
if (!chan) {
return NULL;
}
@@ -100,67 +129,124 @@ static struct ast_channel *dial_transfer(struct ast_channel *caller, const char
return chan;
}
/*!
* \internal
* \brief Determine the transfer context to use.
* \since 12.0.0
*
* \param transferer Channel initiating the transfer.
* \param context User supplied context if available. May be NULL.
*
* \return The context to use for the transfer.
*/
static const char *get_transfer_context(struct ast_channel *transferer, const char *context)
{
if (!ast_strlen_zero(context)) {
return context;
}
context = pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT");
if (!ast_strlen_zero(context)) {
return context;
}
context = ast_channel_macrocontext(transferer);
if (!ast_strlen_zero(context)) {
return context;
}
context = ast_channel_context(transferer);
if (!ast_strlen_zero(context)) {
return context;
}
return "default";
}
/*! \brief Internal built in feature for blind transfers */
static int feature_blind_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
{
char exten[AST_MAX_EXTENSION] = "";
struct ast_channel *chan = NULL;
struct ast_bridge_features_blind_transfer *blind_transfer = hook_pvt;
const char *context = (blind_transfer && !ast_strlen_zero(blind_transfer->context) ? blind_transfer->context : ast_channel_context(bridge_channel->chan));
const char *context;
struct ast_exten *park_exten;
/* BUGBUG the peer needs to be put on hold for the transfer. */
ast_channel_lock(bridge_channel->chan);
context = ast_strdupa(get_transfer_context(bridge_channel->chan,
blind_transfer ? blind_transfer->context : NULL));
ast_channel_unlock(bridge_channel->chan);
/* Grab the extension to transfer to */
if (!grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
ast_stream_and_wait(bridge_channel->chan, "pbx-invalid", AST_DIGIT_ANY);
if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
return 0;
}
/* Parking blind transfer override - phase this out for something more general purpose in the future. */
park_exten = ast_get_parking_exten(exten, bridge_channel->chan, context);
if (park_exten) {
/* We are transfering the transferee to a parking lot. */
if (ast_park_blind_xfer(bridge, bridge_channel, park_exten)) {
ast_log(LOG_ERROR, "%s attempted to transfer to park application and failed.\n", ast_channel_name(bridge_channel->chan));
};
return 0;
}
/* BUGBUG just need to ast_async_goto the peer so this bridge will go away and not accumulate local channels and bridges if the destination is to an application. */
/* ast_async_goto actually is a blind transfer. */
/* BUGBUG Use the bridge count to determine if can do DTMF transfer features. If count is not 2 then don't allow it. */
/* Get a channel that is the destination we wish to call */
if (!(chan = dial_transfer(bridge_channel->chan, exten, context))) {
ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_ANY);
chan = dial_transfer(bridge_channel->chan, exten, context);
if (!chan) {
return 0;
}
/* This is sort of the fun part. We impart the above channel onto the bridge, and have it take our place. */
ast_bridge_impart(bridge, chan, bridge_channel->chan, NULL, 1);
/* Impart the new channel onto the bridge, and have it take our place. */
if (ast_bridge_impart(bridge_channel->bridge, chan, bridge_channel->chan, NULL, 1)) {
ast_hangup(chan);
return 0;
}
return 0;
}
/*! 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)
{
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 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)
static int attended_transfer_threeway(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_THREEWAY;
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 abort transfer */
static int attended_transfer_abort(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);
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);
ao2_unlock(bridge);
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,70 +254,158 @@ 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;
}
/* 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;
}
/* 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);
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 */
/* 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;
}
/* 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);
/*
* 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);
/* 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 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);
/* 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);
/* 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);
/* Since the above returned the caller features structure is of no more use */
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);
}
/* 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_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_ANY);
ast_bridge_depart(peer);
}
/* Now that all channels are out of it we can destroy the bridge and the called features structure */
ast_bridge_features_cleanup(&called_features);
/* 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);
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);
}
}
return 0;
}

View File

@@ -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");

311
bridges/bridge_holding.c Normal file
View File

@@ -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");

View File

@@ -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");

414
bridges/bridge_native_rtp.c Normal file
View File

@@ -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");

View File

@@ -66,32 +66,26 @@ static int simple_bridge_join(struct ast_bridge *bridge, struct ast_bridge_chann
return ast_channel_make_compatible(c0, c1);
}
static enum ast_bridge_write_result simple_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
static int simple_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
struct ast_bridge_channel *other;
/* If this is the only channel in this bridge then immediately exit */
if (AST_LIST_FIRST(&bridge->channels) == AST_LIST_LAST(&bridge->channels)) {
return AST_BRIDGE_WRITE_FAILED;
}
/* Find the channel we actually want to write to */
if (!(other = (AST_LIST_FIRST(&bridge->channels) == bridge_channel ? AST_LIST_LAST(&bridge->channels) : AST_LIST_FIRST(&bridge->channels)))) {
return AST_BRIDGE_WRITE_FAILED;
other = ast_bridge_channel_peer(bridge_channel);
if (!other) {
return -1;
}
/* Write the frame out if they are in the waiting state... don't worry about freeing it, the bridging core will take care of it */
if (other->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
ast_write(other->chan, frame);
}
/* The bridging core takes care of freeing the passed in frame. */
ast_bridge_channel_queue_frame(other, frame);
return AST_BRIDGE_WRITE_SUCCESS;
return 0;
}
static struct ast_bridge_technology simple_bridge = {
.name = "simple_bridge",
.capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX | AST_BRIDGE_CAPABILITY_THREAD,
.preference = AST_BRIDGE_PREFERENCE_MEDIUM,
.capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX,
.preference = AST_BRIDGE_PREFERENCE_BASE_1TO1MIX,
.join = simple_bridge_join,
.write = simple_bridge_write,
};

View File

@@ -100,13 +100,15 @@ struct softmix_channel {
struct ast_frame read_frame;
/*! DSP for detecting silence */
struct ast_dsp *dsp;
/*! Bit used to indicate if a channel is talking or not. This affects how
* the channel's audio is mixed back to it. */
int talking:1;
/*! Bit used to indicate that the channel provided audio for this mixing interval */
int have_audio:1;
/*! Bit used to indicate that a frame is available to be written out to the channel */
int have_frame:1;
/*!
* \brief TRUE if a channel is talking.
*
* \note This affects how the channel's audio is mixed back to
* it.
*/
unsigned int talking:1;
/*! TRUE if the channel provided audio for this mixing interval */
unsigned int have_audio:1;
/*! Buffer containing final mixed audio from all sources */
short final_buf[MAX_DATALEN];
/*! Buffer containing only the audio from the channel */
@@ -117,28 +119,36 @@ struct softmix_channel {
struct softmix_bridge_data {
struct ast_timer *timer;
/*! Lock for signaling the mixing thread. */
ast_mutex_t lock;
/*! Condition, used if we need to wake up the mixing thread. */
ast_cond_t cond;
/*! Thread handling the mixing */
pthread_t thread;
unsigned int internal_rate;
unsigned int internal_mixing_interval;
/*! TRUE if the mixing thread should stop */
unsigned int stop:1;
};
struct softmix_stats {
/*! Each index represents a sample rate used above the internal rate. */
unsigned int sample_rates[16];
/*! Each index represents the number of channels using the same index in the sample_rates array. */
unsigned int num_channels[16];
/*! the number of channels above the internal sample rate */
unsigned int num_above_internal_rate;
/*! the number of channels at the internal sample rate */
unsigned int num_at_internal_rate;
/*! the absolute highest sample rate supported by any channel in the bridge */
unsigned int highest_supported_rate;
/*! Is the sample rate locked by the bridge, if so what is that rate.*/
unsigned int locked_rate;
/*! Each index represents a sample rate used above the internal rate. */
unsigned int sample_rates[16];
/*! Each index represents the number of channels using the same index in the sample_rates array. */
unsigned int num_channels[16];
/*! the number of channels above the internal sample rate */
unsigned int num_above_internal_rate;
/*! the number of channels at the internal sample rate */
unsigned int num_at_internal_rate;
/*! the absolute highest sample rate supported by any channel in the bridge */
unsigned int highest_supported_rate;
/*! Is the sample rate locked by the bridge, if so what is that rate.*/
unsigned int locked_rate;
};
struct softmix_mixing_array {
int max_num_entries;
int used_entries;
unsigned int max_num_entries;
unsigned int used_entries;
int16_t **buffers;
};
@@ -213,7 +223,7 @@ static void softmix_translate_helper_change_rate(struct softmix_translate_helper
/*!
* \internal
* \brief Get the next available audio on the softmix channel's read stream
* and determine if it should be mixed out or not on the write stream.
* and determine if it should be mixed out or not on the write stream.
*
* \retval pointer to buffer containing the exact number of samples requested on success.
* \retval NULL if no samples are present
@@ -295,54 +305,9 @@ static void softmix_translate_helper_cleanup(struct softmix_translate_helper *tr
}
}
static void softmix_bridge_data_destroy(void *obj)
{
struct softmix_bridge_data *softmix_data = obj;
if (softmix_data->timer) {
ast_timer_close(softmix_data->timer);
softmix_data->timer = NULL;
}
}
/*! \brief Function called when a bridge is created */
static int softmix_bridge_create(struct ast_bridge *bridge)
{
struct softmix_bridge_data *softmix_data;
if (!(softmix_data = ao2_alloc(sizeof(*softmix_data), softmix_bridge_data_destroy))) {
return -1;
}
if (!(softmix_data->timer = ast_timer_open())) {
ao2_ref(softmix_data, -1);
return -1;
}
/* start at 8khz, let it grow from there */
softmix_data->internal_rate = 8000;
softmix_data->internal_mixing_interval = DEFAULT_SOFTMIX_INTERVAL;
bridge->bridge_pvt = softmix_data;
return 0;
}
/*! \brief Function called when a bridge is destroyed */
static int softmix_bridge_destroy(struct ast_bridge *bridge)
{
struct softmix_bridge_data *softmix_data;
softmix_data = bridge->bridge_pvt;
if (!softmix_data) {
return -1;
}
ao2_ref(softmix_data, -1);
bridge->bridge_pvt = NULL;
return 0;
}
static void set_softmix_bridge_data(int rate, int interval, struct ast_bridge_channel *bridge_channel, int reset)
{
struct softmix_channel *sc = bridge_channel->bridge_pvt;
struct softmix_channel *sc = bridge_channel->tech_pvt;
unsigned int channel_read_rate = ast_format_rate(ast_channel_rawreadformat(bridge_channel->chan));
ast_mutex_lock(&sc->lock);
@@ -382,39 +347,89 @@ static void set_softmix_bridge_data(int rate, int interval, struct ast_bridge_ch
ast_mutex_unlock(&sc->lock);
}
/*!
* \internal
* \brief Poke the mixing thread in case it is waiting for an active channel.
* \since 12.0.0
*
* \param softmix_data Bridge mixing data.
*
* \return Nothing
*/
static void softmix_poke_thread(struct softmix_bridge_data *softmix_data)
{
ast_mutex_lock(&softmix_data->lock);
ast_cond_signal(&softmix_data->cond);
ast_mutex_unlock(&softmix_data->lock);
}
/*! \brief Function called when a channel is unsuspended from the bridge */
static void softmix_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
if (bridge->tech_pvt) {
softmix_poke_thread(bridge->tech_pvt);
}
}
/*!
* \internal
* \brief Indicate a source change to the channel.
* \since 12.0.0
*
* \param bridge_channel Which channel source is changing.
*
* \return Nothing
*/
static void softmix_src_change(struct ast_bridge_channel *bridge_channel)
{
ast_bridge_channel_queue_control_data(bridge_channel, AST_CONTROL_SRCCHANGE, NULL, 0);
}
/*! \brief Function called when a channel is joined into the bridge */
static int softmix_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
struct softmix_channel *sc;
struct softmix_bridge_data *softmix_data = bridge->bridge_pvt;
struct softmix_bridge_data *softmix_data;
softmix_data = bridge->tech_pvt;
if (!softmix_data) {
return -1;
}
/* Create a new softmix_channel structure and allocate various things on it */
if (!(sc = ast_calloc(1, sizeof(*sc)))) {
return -1;
}
softmix_src_change(bridge_channel);
/* Can't forget the lock */
ast_mutex_init(&sc->lock);
/* Can't forget to record our pvt structure within the bridged channel structure */
bridge_channel->bridge_pvt = sc;
bridge_channel->tech_pvt = sc;
set_softmix_bridge_data(softmix_data->internal_rate,
softmix_data->internal_mixing_interval ? softmix_data->internal_mixing_interval : DEFAULT_SOFTMIX_INTERVAL,
softmix_data->internal_mixing_interval
? softmix_data->internal_mixing_interval
: DEFAULT_SOFTMIX_INTERVAL,
bridge_channel, 0);
softmix_poke_thread(softmix_data);
return 0;
}
/*! \brief Function called when a channel leaves the bridge */
static int softmix_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
static void softmix_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
struct softmix_channel *sc = bridge_channel->bridge_pvt;
struct softmix_channel *sc = bridge_channel->tech_pvt;
if (!(bridge_channel->bridge_pvt)) {
return 0;
if (!sc) {
return;
}
bridge_channel->bridge_pvt = NULL;
bridge_channel->tech_pvt = NULL;
softmix_src_change(bridge_channel);
/* Drop mutex lock */
ast_mutex_destroy(&sc->lock);
@@ -427,111 +442,122 @@ static int softmix_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_cha
/* Eep! drop ourselves */
ast_free(sc);
return 0;
}
/*!
* \internal
* \brief If the bridging core passes DTMF to us, then they want it to be distributed out to all memebers. Do that here.
* \brief Pass the given frame to everyone else.
* \since 12.0.0
*
* \param bridge What bridge to distribute frame.
* \param bridge_channel Channel to optionally not pass frame to. (NULL to pass to everyone)
* \param frame Frame to pass.
*
* \return Nothing
*/
static void softmix_pass_dtmf(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
static void softmix_pass_everyone_else(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
struct ast_bridge_channel *tmp;
AST_LIST_TRAVERSE(&bridge->channels, tmp, entry) {
if (tmp == bridge_channel) {
struct ast_bridge_channel *cur;
AST_LIST_TRAVERSE(&bridge->channels, cur, entry) {
if (cur == bridge_channel) {
continue;
}
ast_write(tmp->chan, frame);
ast_bridge_channel_queue_frame(cur, frame);
}
}
static void softmix_pass_video_top_priority(struct ast_bridge *bridge, struct ast_frame *frame)
{
struct ast_bridge_channel *tmp;
AST_LIST_TRAVERSE(&bridge->channels, tmp, entry) {
if (tmp->suspended) {
struct ast_bridge_channel *cur;
AST_LIST_TRAVERSE(&bridge->channels, cur, entry) {
if (cur->suspended) {
continue;
}
if (ast_bridge_is_video_src(bridge, tmp->chan) == 1) {
ast_write(tmp->chan, frame);
if (ast_bridge_is_video_src(bridge, cur->chan) == 1) {
ast_bridge_channel_queue_frame(cur, frame);
break;
}
}
}
static void softmix_pass_video_all(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame, int echo)
/*!
* \internal
* \brief Determine what to do with a video frame.
* \since 12.0.0
*
* \param bridge Which bridge is getting the frame
* \param bridge_channel Which channel is writing the frame.
* \param frame What is being written.
*
* \return Nothing
*/
static void softmix_bridge_write_video(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
struct ast_bridge_channel *tmp;
AST_LIST_TRAVERSE(&bridge->channels, tmp, entry) {
if (tmp->suspended) {
continue;
struct softmix_channel *sc;
int video_src_priority;
/* Determine if the video frame should be distributed or not */
switch (bridge->video_mode.mode) {
case AST_BRIDGE_VIDEO_MODE_NONE:
break;
case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
video_src_priority = ast_bridge_is_video_src(bridge, bridge_channel->chan);
if (video_src_priority == 1) {
/* Pass to me and everyone else. */
softmix_pass_everyone_else(bridge, NULL, frame);
}
if ((tmp->chan == bridge_channel->chan) && !echo) {
continue;
break;
case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
sc = bridge_channel->tech_pvt;
ast_mutex_lock(&sc->lock);
ast_bridge_update_talker_src_video_mode(bridge, bridge_channel->chan,
sc->video_talker.energy_average,
ast_format_get_video_mark(&frame->subclass.format));
ast_mutex_unlock(&sc->lock);
video_src_priority = ast_bridge_is_video_src(bridge, bridge_channel->chan);
if (video_src_priority == 1) {
int num_src = ast_bridge_number_video_src(bridge);
int echo = num_src > 1 ? 0 : 1;
softmix_pass_everyone_else(bridge, echo ? NULL : bridge_channel, frame);
} else if (video_src_priority == 2) {
softmix_pass_video_top_priority(bridge, frame);
}
ast_write(tmp->chan, frame);
break;
}
}
/*! \brief Function called when a channel writes a frame into the bridge */
static enum ast_bridge_write_result softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
/*!
* \internal
* \brief Determine what to do with a voice frame.
* \since 12.0.0
*
* \param bridge Which bridge is getting the frame
* \param bridge_channel Which channel is writing the frame.
* \param frame What is being written.
*
* \return Nothing
*/
static void softmix_bridge_write_voice(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
struct softmix_channel *sc = bridge_channel->bridge_pvt;
struct softmix_bridge_data *softmix_data = bridge->bridge_pvt;
struct softmix_channel *sc = bridge_channel->tech_pvt;
struct softmix_bridge_data *softmix_data = bridge->tech_pvt;
int totalsilence = 0;
int cur_energy = 0;
int silence_threshold = bridge_channel->tech_args.silence_threshold ?
bridge_channel->tech_args.silence_threshold :
DEFAULT_SOFTMIX_SILENCE_THRESHOLD;
char update_talking = -1; /* if this is set to 0 or 1, tell the bridge that the channel has started or stopped talking. */
int res = AST_BRIDGE_WRITE_SUCCESS;
/* Only accept audio frames, all others are unsupported */
if (frame->frametype == AST_FRAME_DTMF_END || frame->frametype == AST_FRAME_DTMF_BEGIN) {
softmix_pass_dtmf(bridge, bridge_channel, frame);
goto bridge_write_cleanup;
} else if (frame->frametype != AST_FRAME_VOICE && frame->frametype != AST_FRAME_VIDEO) {
res = AST_BRIDGE_WRITE_UNSUPPORTED;
goto bridge_write_cleanup;
} else if (frame->datalen == 0) {
goto bridge_write_cleanup;
}
/* Determine if this video frame should be distributed or not */
if (frame->frametype == AST_FRAME_VIDEO) {
int num_src = ast_bridge_number_video_src(bridge);
int video_src_priority = ast_bridge_is_video_src(bridge, bridge_channel->chan);
switch (bridge->video_mode.mode) {
case AST_BRIDGE_VIDEO_MODE_NONE:
break;
case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
if (video_src_priority == 1) {
softmix_pass_video_all(bridge, bridge_channel, frame, 1);
}
break;
case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
ast_mutex_lock(&sc->lock);
ast_bridge_update_talker_src_video_mode(bridge, bridge_channel->chan, sc->video_talker.energy_average, ast_format_get_video_mark(&frame->subclass.format));
ast_mutex_unlock(&sc->lock);
if (video_src_priority == 1) {
int echo = num_src > 1 ? 0 : 1;
softmix_pass_video_all(bridge, bridge_channel, frame, echo);
} else if (video_src_priority == 2) {
softmix_pass_video_top_priority(bridge, frame);
}
break;
}
goto bridge_write_cleanup;
}
/* If we made it here, we are going to write the frame into the conference */
/* Write the frame into the conference */
ast_mutex_lock(&sc->lock);
ast_dsp_silence_with_energy(sc->dsp, frame, &totalsilence, &cur_energy);
if (bridge->video_mode.mode == AST_BRIDGE_VIDEO_MODE_TALKER_SRC) {
int cur_slot = sc->video_talker.energy_history_cur_slot;
sc->video_talker.energy_accum -= sc->video_talker.energy_history[cur_slot];
sc->video_talker.energy_accum += cur_energy;
sc->video_talker.energy_history[cur_slot] = cur_energy;
@@ -568,50 +594,77 @@ static enum ast_bridge_write_result softmix_bridge_write(struct ast_bridge *brid
ast_slinfactory_feed(&sc->factory, frame);
}
/* If a frame is ready to be written out, do so */
if (sc->have_frame) {
ast_write(bridge_channel->chan, &sc->write_frame);
sc->have_frame = 0;
}
/* Alllll done */
ast_mutex_unlock(&sc->lock);
if (update_talking != -1) {
ast_bridge_notify_talking(bridge, bridge_channel, update_talking);
ast_bridge_notify_talking(bridge_channel, update_talking);
}
return res;
bridge_write_cleanup:
/* Even though the frame is not being written into the conference because it is not audio,
* we should use this opportunity to check to see if a frame is ready to be written out from
* the conference to the channel. */
ast_mutex_lock(&sc->lock);
if (sc->have_frame) {
ast_write(bridge_channel->chan, &sc->write_frame);
sc->have_frame = 0;
}
ast_mutex_unlock(&sc->lock);
return res;
}
/*! \brief Function called when the channel's thread is poked */
static int softmix_bridge_poke(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
/*!
* \internal
* \brief Determine what to do with a control frame.
* \since 12.0.0
*
* \param bridge Which bridge is getting the frame
* \param bridge_channel Which channel is writing the frame.
* \param frame What is being written.
*
* \return Nothing
*/
static void softmix_bridge_write_control(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
struct softmix_channel *sc = bridge_channel->bridge_pvt;
/* BUGBUG need to look at channel roles to determine what to do with control frame. */
/*! \todo BUGBUG softmix_bridge_write_control() not written */
}
ast_mutex_lock(&sc->lock);
/*!
* \internal
* \brief Determine what to do with a frame written into the bridge.
* \since 12.0.0
*
* \param bridge Which bridge is getting the frame
* \param bridge_channel Which channel is writing the frame.
* \param frame What is being written.
*
* \retval 0 on success
* \retval -1 on failure
*
* \note On entry, bridge is already locked.
*/
static int softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
int res = 0;
if (sc->have_frame) {
ast_write(bridge_channel->chan, &sc->write_frame);
sc->have_frame = 0;
if (!bridge->tech_pvt || !bridge_channel->tech_pvt) {
return -1;
}
ast_mutex_unlock(&sc->lock);
switch (frame->frametype) {
case AST_FRAME_DTMF_BEGIN:
case AST_FRAME_DTMF_END:
softmix_pass_everyone_else(bridge, bridge_channel, frame);
break;
case AST_FRAME_VOICE:
softmix_bridge_write_voice(bridge, bridge_channel, frame);
break;
case AST_FRAME_VIDEO:
softmix_bridge_write_video(bridge, bridge_channel, frame);
break;
case AST_FRAME_CONTROL:
softmix_bridge_write_control(bridge, bridge_channel, frame);
break;
case AST_FRAME_BRIDGE_ACTION:
softmix_pass_everyone_else(bridge, bridge_channel, frame);
break;
default:
ast_debug(3, "Frame type %d unsupported\n", frame->frametype);
res = -1;
break;
}
return 0;
return res;
}
static void gather_softmix_stats(struct softmix_stats *stats,
@@ -648,7 +701,7 @@ static void gather_softmix_stats(struct softmix_stats *stats,
* \brief Analyse mixing statistics and change bridges internal rate
* if necessary.
*
* \retval 0, no changes to internal rate
* \retval 0, no changes to internal rate
* \ratval 1, internal rate was changed, update all the channels on the next mixing iteration.
*/
static unsigned int analyse_softmix_stats(struct softmix_stats *stats, struct softmix_bridge_data *softmix_data)
@@ -665,7 +718,8 @@ static unsigned int analyse_softmix_stats(struct softmix_stats *stats, struct so
* from the current rate we are using. */
if (softmix_data->internal_rate != stats->locked_rate) {
softmix_data->internal_rate = stats->locked_rate;
ast_debug(1, " Bridge is locked in at sample rate %d\n", softmix_data->internal_rate);
ast_debug(1, "Bridge is locked in at sample rate %d\n",
softmix_data->internal_rate);
return 1;
}
} else if (stats->num_above_internal_rate >= 2) {
@@ -704,13 +758,15 @@ static unsigned int analyse_softmix_stats(struct softmix_stats *stats, struct so
}
}
ast_debug(1, " Bridge changed from %d To %d\n", softmix_data->internal_rate, best_rate);
ast_debug(1, "Bridge changed from %d To %d\n",
softmix_data->internal_rate, best_rate);
softmix_data->internal_rate = best_rate;
return 1;
} else if (!stats->num_at_internal_rate && !stats->num_above_internal_rate) {
/* In this case, the highest supported rate is actually lower than the internal rate */
softmix_data->internal_rate = stats->highest_supported_rate;
ast_debug(1, " Bridge changed from %d to %d\n", softmix_data->internal_rate, stats->highest_supported_rate);
ast_debug(1, "Bridge changed from %d to %d\n",
softmix_data->internal_rate, stats->highest_supported_rate);
return 1;
}
return 0;
@@ -745,38 +801,38 @@ static int softmix_mixing_array_grow(struct softmix_mixing_array *mixing_array,
return 0;
}
/*! \brief Function which acts as the mixing thread */
static int softmix_bridge_thread(struct ast_bridge *bridge)
/*!
* \brief Mixing loop.
*
* \retval 0 on success
* \retval -1 on failure
*/
static int softmix_mixing_loop(struct ast_bridge *bridge)
{
struct softmix_stats stats = { { 0 }, };
struct softmix_mixing_array mixing_array;
struct softmix_bridge_data *softmix_data;
struct softmix_bridge_data *softmix_data = bridge->tech_pvt;
struct ast_timer *timer;
struct softmix_translate_helper trans_helper;
int16_t buf[MAX_DATALEN];
unsigned int stat_iteration_counter = 0; /* counts down, gather stats at zero and reset. */
int timingfd;
int update_all_rates = 0; /* set this when the internal sample rate has changed */
int i, x;
unsigned int idx;
unsigned int x;
int res = -1;
softmix_data = bridge->bridge_pvt;
if (!softmix_data) {
goto softmix_cleanup;
}
ao2_ref(softmix_data, 1);
timer = softmix_data->timer;
timingfd = ast_timer_fd(timer);
softmix_translate_helper_init(&trans_helper, softmix_data->internal_rate);
ast_timer_set_rate(timer, (1000 / softmix_data->internal_mixing_interval));
/* Give the mixing array room to grow, memory is cheap but allocations are expensive. */
if (softmix_mixing_array_init(&mixing_array, bridge->num + 10)) {
if (softmix_mixing_array_init(&mixing_array, bridge->num_channels + 10)) {
goto softmix_cleanup;
}
while (!bridge->stop && !bridge->refresh && bridge->array_num) {
while (!softmix_data->stop && bridge->num_active) {
struct ast_bridge_channel *bridge_channel;
int timeout = -1;
enum ast_format_id cur_slin_id = ast_format_slin_by_rate(softmix_data->internal_rate);
@@ -793,8 +849,8 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
}
/* Grow the mixing array buffer as participants are added. */
if (mixing_array.max_num_entries < bridge->num
&& softmix_mixing_array_grow(&mixing_array, bridge->num + 5)) {
if (mixing_array.max_num_entries < bridge->num_channels
&& softmix_mixing_array_grow(&mixing_array, bridge->num_channels + 5)) {
goto softmix_cleanup;
}
@@ -815,7 +871,7 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
/* Go through pulling audio from each factory that has it available */
AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
struct softmix_channel *sc = bridge_channel->bridge_pvt;
struct softmix_channel *sc = bridge_channel->tech_pvt;
/* Update the sample rate to match the bridge's native sample rate if necessary. */
if (update_all_rates) {
@@ -842,15 +898,15 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
/* mix it like crazy */
memset(buf, 0, softmix_datalen);
for (i = 0; i < mixing_array.used_entries; i++) {
for (x = 0; x < softmix_samples; x++) {
ast_slinear_saturated_add(buf + x, mixing_array.buffers[i] + x);
for (idx = 0; idx < mixing_array.used_entries; ++idx) {
for (x = 0; x < softmix_samples; ++x) {
ast_slinear_saturated_add(buf + x, mixing_array.buffers[idx] + x);
}
}
/* Next step go through removing the channel's own audio and creating a good frame... */
AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
struct softmix_channel *sc = bridge_channel->bridge_pvt;
struct softmix_channel *sc = bridge_channel->tech_pvt;
if (bridge_channel->suspended) {
continue;
@@ -869,13 +925,10 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
/* process the softmix channel's new write audio */
softmix_process_write_audio(&trans_helper, ast_channel_rawwriteformat(bridge_channel->chan), sc);
/* The frame is now ready for use... */
sc->have_frame = 1;
ast_mutex_unlock(&sc->lock);
/* Poke bridged channel thread just in case */
pthread_kill(bridge_channel->thread, SIGURG);
/* A frame is now ready for the channel. */
ast_bridge_channel_queue_frame(bridge_channel, &sc->write_frame);
}
update_all_rates = 0;
@@ -885,17 +938,17 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
}
stat_iteration_counter--;
ao2_unlock(bridge);
ast_bridge_unlock(bridge);
/* cleanup any translation frame data from the previous mixing iteration. */
softmix_translate_helper_cleanup(&trans_helper);
/* Wait for the timing source to tell us to wake up and get things done */
ast_waitfor_n_fd(&timingfd, 1, &timeout, NULL);
if (ast_timer_ack(timer, 1) < 0) {
ast_log(LOG_ERROR, "Failed to acknowledge timer in softmix bridge.\n");
ao2_lock(bridge);
ast_bridge_lock(bridge);
goto softmix_cleanup;
}
ao2_lock(bridge);
ast_bridge_lock(bridge);
/* make sure to detect mixing interval changes if they occur. */
if (bridge->internal_mixing_interval && (bridge->internal_mixing_interval != softmix_data->internal_mixing_interval)) {
@@ -910,23 +963,141 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
softmix_cleanup:
softmix_translate_helper_destroy(&trans_helper);
softmix_mixing_array_destroy(&mixing_array);
if (softmix_data) {
ao2_ref(softmix_data, -1);
}
return res;
}
/*!
* \internal
* \brief Mixing thread.
* \since 12.0.0
*
* \note The thread does not have its own reference to the
* bridge. The lifetime of the thread is tied to the lifetime
* of the mixing technology association with the bridge.
*/
static void *softmix_mixing_thread(void *data)
{
struct ast_bridge *bridge = data;
struct softmix_bridge_data *softmix_data;
ast_bridge_lock(bridge);
if (bridge->callid) {
ast_callid_threadassoc_add(bridge->callid);
}
ast_debug(1, "Bridge %s: starting mixing thread\n", bridge->uniqueid);
softmix_data = bridge->tech_pvt;
while (!softmix_data->stop) {
if (!bridge->num_active) {
/* Wait for something to happen to the bridge. */
ast_bridge_unlock(bridge);
ast_mutex_lock(&softmix_data->lock);
if (!softmix_data->stop) {
ast_cond_wait(&softmix_data->cond, &softmix_data->lock);
}
ast_mutex_unlock(&softmix_data->lock);
ast_bridge_lock(bridge);
continue;
}
if (softmix_mixing_loop(bridge)) {
/*
* A mixing error occurred. Sleep and try again later so we
* won't flood the logs.
*/
ast_bridge_unlock(bridge);
sleep(1);
ast_bridge_lock(bridge);
}
}
ast_bridge_unlock(bridge);
ast_debug(1, "Bridge %s: stopping mixing thread\n", bridge->uniqueid);
return NULL;
}
static void softmix_bridge_data_destroy(struct softmix_bridge_data *softmix_data)
{
if (softmix_data->timer) {
ast_timer_close(softmix_data->timer);
softmix_data->timer = NULL;
}
ast_mutex_destroy(&softmix_data->lock);
ast_free(softmix_data);
}
/*! \brief Function called when a bridge is created */
static int softmix_bridge_create(struct ast_bridge *bridge)
{
struct softmix_bridge_data *softmix_data;
softmix_data = ast_calloc(1, sizeof(*softmix_data));
if (!softmix_data) {
return -1;
}
ast_mutex_init(&softmix_data->lock);
softmix_data->timer = ast_timer_open();
if (!softmix_data->timer) {
softmix_bridge_data_destroy(softmix_data);
return -1;
}
/* start at 8khz, let it grow from there */
softmix_data->internal_rate = 8000;
softmix_data->internal_mixing_interval = DEFAULT_SOFTMIX_INTERVAL;
bridge->tech_pvt = softmix_data;
/* Start the mixing thread. */
if (ast_pthread_create(&softmix_data->thread, NULL, softmix_mixing_thread, bridge)) {
softmix_data->thread = AST_PTHREADT_NULL;
softmix_bridge_data_destroy(softmix_data);
bridge->tech_pvt = NULL;
return -1;
}
return 0;
}
/*! \brief Function called when a bridge is destroyed */
static void softmix_bridge_destroy(struct ast_bridge *bridge)
{
struct softmix_bridge_data *softmix_data;
pthread_t thread;
softmix_data = bridge->tech_pvt;
if (!softmix_data) {
return;
}
/* Stop the mixing thread. */
ast_mutex_lock(&softmix_data->lock);
softmix_data->stop = 1;
ast_cond_signal(&softmix_data->cond);
thread = softmix_data->thread;
softmix_data->thread = AST_PTHREADT_NULL;
ast_mutex_unlock(&softmix_data->lock);
if (thread != AST_PTHREADT_NULL) {
ast_debug(1, "Waiting for mixing thread to die.\n");
pthread_join(thread, NULL);
}
softmix_bridge_data_destroy(softmix_data);
bridge->tech_pvt = NULL;
}
static struct ast_bridge_technology softmix_bridge = {
.name = "softmix",
.capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX | AST_BRIDGE_CAPABILITY_THREAD | AST_BRIDGE_CAPABILITY_MULTITHREADED | AST_BRIDGE_CAPABILITY_OPTIMIZE | AST_BRIDGE_CAPABILITY_VIDEO,
.preference = AST_BRIDGE_PREFERENCE_LOW,
.capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX,
.preference = AST_BRIDGE_PREFERENCE_BASE_MULTIMIX,
.create = softmix_bridge_create,
.destroy = softmix_bridge_destroy,
.join = softmix_bridge_join,
.leave = softmix_bridge_leave,
.unsuspend = softmix_bridge_unsuspend,
.write = softmix_bridge_write,
.thread = softmix_bridge_thread,
.poke = softmix_bridge_poke,
};
static int unload_module(void)