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