mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-03 11:25:35 +00:00
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:
@@ -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;
|
||||
}
|
||||
|
215
bridges/bridge_builtin_interval_features.c
Normal file
215
bridges/bridge_builtin_interval_features.c
Normal 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
311
bridges/bridge_holding.c
Normal 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");
|
||||
|
@@ -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
414
bridges/bridge_native_rtp.c
Normal 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");
|
@@ -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,
|
||||
};
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user