Merge "bridging: Add better support for adding/removing streams." into 16

This commit is contained in:
George Joseph 2020-02-20 13:43:07 -06:00 committed by Gerrit Code Review
commit 0cf988e72b
8 changed files with 566 additions and 149 deletions

View File

@ -43,6 +43,7 @@
#include "asterisk/bridge_technology.h"
#include "asterisk/frame.h"
#include "asterisk/rtp_engine.h"
#include "asterisk/stream.h"
/*! \brief Internal structure which contains bridged RTP channel hook data */
struct native_rtp_framehook_data {
@ -85,6 +86,28 @@ struct native_rtp_bridge_channel_data {
struct rtp_glue_data glue;
};
/*! \brief Forward declarations */
static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
static void native_rtp_bridge_unsuspend(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);
static void native_rtp_bridge_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
static int native_rtp_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame);
static int native_rtp_bridge_compatible(struct ast_bridge *bridge);
static void native_rtp_stream_topology_changed(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
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_suspend,
.write = native_rtp_bridge_write,
.compatible = native_rtp_bridge_compatible,
.stream_topology_changed = native_rtp_stream_topology_changed,
};
static void rtp_glue_data_init(struct rtp_glue_data *glue)
{
glue->cb = NULL;
@ -831,12 +854,124 @@ static void native_rtp_bridge_framehook_detach(struct ast_bridge_channel *bridge
data->hook_data = NULL;
}
static struct ast_stream_topology *native_rtp_request_stream_topology_update(
struct ast_stream_topology *existing_topology,
struct ast_stream_topology *requested_topology)
{
struct ast_stream *stream;
struct ast_format_cap *audio_formats = NULL;
struct ast_stream_topology *new_topology;
int i;
new_topology = ast_stream_topology_clone(requested_topology);
if (!new_topology) {
return NULL;
}
/* We find an existing stream with negotiated audio formats that we can place into
* any audio streams in the new topology to ensure that negotiation succeeds. Some
* endpoints incorrectly terminate the call if SDP negotiation fails.
*/
for (i = 0; i < ast_stream_topology_get_count(existing_topology); ++i) {
stream = ast_stream_topology_get_stream(existing_topology, i);
if (ast_stream_get_type(stream) != AST_MEDIA_TYPE_AUDIO ||
ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {
continue;
}
audio_formats = ast_stream_get_formats(stream);
break;
}
if (audio_formats) {
for (i = 0; i < ast_stream_topology_get_count(new_topology); ++i) {
stream = ast_stream_topology_get_stream(new_topology, i);
if (ast_stream_get_type(stream) != AST_MEDIA_TYPE_AUDIO ||
ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {
continue;
}
ast_format_cap_append_from_cap(ast_stream_get_formats(stream), audio_formats,
AST_MEDIA_TYPE_AUDIO);
}
}
for (i = 0; i < ast_stream_topology_get_count(new_topology); ++i) {
stream = ast_stream_topology_get_stream(new_topology, i);
/* For both recvonly and sendonly the stream state reflects our state, that is we
* are receiving only and we are sending only. Since we are renegotiating a remote
* party we need to swap this to reflect what we will be doing. That is, if we are
* receiving from Alice then we want to be sending to Bob, so swap recvonly to
* sendonly.
*/
if (ast_stream_get_state(stream) == AST_STREAM_STATE_RECVONLY) {
ast_stream_set_state(stream, AST_STREAM_STATE_SENDONLY);
} else if (ast_stream_get_state(stream) == AST_STREAM_STATE_SENDONLY) {
ast_stream_set_state(stream, AST_STREAM_STATE_RECVONLY);
}
}
return new_topology;
}
static void native_rtp_stream_topology_changed(struct ast_bridge *bridge,
struct ast_bridge_channel *bridge_channel)
{
struct ast_channel *c0 = bridge_channel->chan;
struct ast_channel *c1 = AST_LIST_FIRST(&bridge->channels)->chan;
struct ast_stream_topology *req_top;
struct ast_stream_topology *existing_top;
struct ast_stream_topology *new_top;
ast_bridge_channel_stream_map(bridge_channel);
if (ast_channel_get_stream_topology_change_source(bridge_channel->chan)
== &native_rtp_bridge) {
return;
}
if (c0 == c1) {
c1 = AST_LIST_LAST(&bridge->channels)->chan;
}
if (c0 == c1) {
return;
}
/* If a party renegotiates we want to renegotiate their counterpart to a matching
* topology.
*/
ast_channel_lock_both(c0, c1);
req_top = ast_channel_get_stream_topology(c0);
existing_top = ast_channel_get_stream_topology(c1);
new_top = native_rtp_request_stream_topology_update(existing_top, req_top);
ast_channel_unlock(c0);
ast_channel_unlock(c1);
if (!new_top) {
/* Failure. We'll just have to live with the current topology. */
return;
}
ast_channel_request_stream_topology_change(c1, new_top, &native_rtp_bridge);
ast_stream_topology_free(new_top);
}
/*!
* \internal
* \brief Called by the bridge core 'join' callback for each channel joining he bridge
*/
static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
struct ast_stream_topology *req_top;
struct ast_stream_topology *existing_top;
struct ast_stream_topology *new_top;
struct ast_channel *c0 = AST_LIST_FIRST(&bridge->channels)->chan;
struct ast_channel *c1 = AST_LIST_LAST(&bridge->channels)->chan;
ast_debug(2, "Bridge '%s'. Channel '%s' is joining bridge tech\n",
bridge->uniqueid, ast_channel_name(bridge_channel->chan));
@ -858,6 +993,27 @@ static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_c
return -1;
}
if (c0 != c1) {
/* When both channels are joined we want to try to improve the experience by
* raising the number of streams so they match.
*/
ast_channel_lock_both(c0, c1);
req_top = ast_channel_get_stream_topology(c0);
existing_top = ast_channel_get_stream_topology(c1);
if (ast_stream_topology_get_count(req_top) < ast_stream_topology_get_count(existing_top)) {
SWAP(req_top, existing_top);
SWAP(c0, c1);
}
new_top = native_rtp_request_stream_topology_update(existing_top, req_top);
ast_channel_unlock(c0);
ast_channel_unlock(c1);
if (new_top) {
ast_channel_request_stream_topology_change(c1, new_top, &native_rtp_bridge);
ast_stream_topology_free(new_top);
}
}
native_rtp_bridge_start(bridge, NULL);
return 0;
}
@ -939,18 +1095,6 @@ static int native_rtp_bridge_write(struct ast_bridge *bridge, struct ast_bridge_
return defer;
}
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_suspend,
.write = native_rtp_bridge_write,
.compatible = native_rtp_bridge_compatible,
};
static int unload_module(void)
{
ast_bridge_technology_unregister(&native_rtp_bridge);

View File

@ -46,63 +46,8 @@
static void simple_bridge_stream_topology_changed(struct ast_bridge *bridge,
struct ast_bridge_channel *bridge_channel);
static int simple_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;
/*
* If this is the first channel we can't make it compatible...
* unless we make it compatible with itself. O.o
*/
if (c0 == c1) {
return 0;
}
if (ast_channel_make_compatible(c0, c1)) {
return -1;
}
/* Align stream topologies */
simple_bridge_stream_topology_changed(bridge, NULL);
return 0;
}
static int simple_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
const struct ast_control_t38_parameters *t38_parameters;
int defer = 0;
if (!ast_bridge_queue_everyone_else(bridge, bridge_channel, frame)) {
/* This frame was successfully queued so no need to defer */
return 0;
}
/* Depending on the frame defer it so when the next channel joins it receives it */
switch (frame->frametype) {
case AST_FRAME_CONTROL:
switch (frame->subclass.integer) {
case AST_CONTROL_T38_PARAMETERS:
t38_parameters = frame->data.ptr;
switch (t38_parameters->request_response) {
case AST_T38_REQUEST_NEGOTIATE:
defer = -1;
break;
default:
break;
}
break;
default:
break;
}
break;
default:
break;
}
return defer;
}
static int simple_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
static int simple_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame);
static struct ast_bridge_technology simple_bridge = {
.name = "simple_bridge",
@ -157,52 +102,145 @@ static struct ast_stream_topology *simple_bridge_request_stream_topology_update(
}
}
for (i = 0; i < ast_stream_topology_get_count(new_topology); ++i) {
stream = ast_stream_topology_get_stream(new_topology, i);
/* For both recvonly and sendonly the stream state reflects our state, that is we
* are receiving only and we are sending only. Since we are renegotiating a remote
* party we need to swap this to reflect what we will be doing. That is, if we are
* receiving from Alice then we want to be sending to Bob, so swap recvonly to
* sendonly.
*/
if (ast_stream_get_state(stream) == AST_STREAM_STATE_RECVONLY) {
ast_stream_set_state(stream, AST_STREAM_STATE_SENDONLY);
} else if (ast_stream_get_state(stream) == AST_STREAM_STATE_SENDONLY) {
ast_stream_set_state(stream, AST_STREAM_STATE_RECVONLY);
}
}
return new_topology;
}
static int simple_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
struct ast_stream_topology *req_top;
struct ast_stream_topology *existing_top;
struct ast_stream_topology *new_top;
struct ast_channel *c0 = AST_LIST_FIRST(&bridge->channels)->chan;
struct ast_channel *c1 = AST_LIST_LAST(&bridge->channels)->chan;
/*
* If this is the first channel we can't make it compatible...
* unless we make it compatible with itself. O.o
*/
if (c0 == c1) {
return 0;
}
if (ast_channel_make_compatible(c0, c1)) {
return -1;
}
/* When both channels are joined we want to try to improve the experience by
* raising the number of streams so they match.
*/
ast_channel_lock_both(c0, c1);
req_top = ast_channel_get_stream_topology(c0);
existing_top = ast_channel_get_stream_topology(c1);
if (ast_stream_topology_get_count(req_top) < ast_stream_topology_get_count(existing_top)) {
SWAP(req_top, existing_top);
SWAP(c0, c1);
}
new_top = simple_bridge_request_stream_topology_update(existing_top, req_top);
ast_channel_unlock(c0);
ast_channel_unlock(c1);
if (!new_top) {
/* Failure. We'll just have to live with the current topology. */
return 0;
}
ast_channel_request_stream_topology_change(c1, new_top, &simple_bridge);
ast_stream_topology_free(new_top);
return 0;
}
static int simple_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
const struct ast_control_t38_parameters *t38_parameters;
int defer = 0;
if (!ast_bridge_queue_everyone_else(bridge, bridge_channel, frame)) {
/* This frame was successfully queued so no need to defer */
return 0;
}
/* Depending on the frame defer it so when the next channel joins it receives it */
switch (frame->frametype) {
case AST_FRAME_CONTROL:
switch (frame->subclass.integer) {
case AST_CONTROL_T38_PARAMETERS:
t38_parameters = frame->data.ptr;
switch (t38_parameters->request_response) {
case AST_T38_REQUEST_NEGOTIATE:
defer = -1;
break;
default:
break;
}
break;
default:
break;
}
break;
default:
break;
}
return defer;
}
static void simple_bridge_stream_topology_changed(struct ast_bridge *bridge,
struct ast_bridge_channel *bridge_channel)
{
struct ast_channel *req_chan;
struct ast_channel *existing_chan;
struct ast_channel *c0 = bridge_channel->chan;
struct ast_channel *c1 = AST_LIST_FIRST(&bridge->channels)->chan;
struct ast_stream_topology *req_top;
struct ast_stream_topology *existing_top;
struct ast_stream_topology *new_top;
if (bridge_channel) {
ast_bridge_channel_stream_map(bridge_channel);
ast_bridge_channel_stream_map(bridge_channel);
if (ast_channel_get_stream_topology_change_source(bridge_channel->chan)
== &simple_bridge) {
return;
}
}
req_chan = AST_LIST_FIRST(&bridge->channels)->chan;
existing_chan = AST_LIST_LAST(&bridge->channels)->chan;
if (req_chan == existing_chan) {
/* Wait until both channels are in the bridge to align topologies. */
if (ast_channel_get_stream_topology_change_source(bridge_channel->chan)
== &simple_bridge) {
return;
}
/* Align topologies according to size or first channel to join */
ast_channel_lock_both(req_chan, existing_chan);
req_top = ast_channel_get_stream_topology(req_chan);
existing_top = ast_channel_get_stream_topology(existing_chan);
if (ast_stream_topology_get_count(req_top) < ast_stream_topology_get_count(existing_top)) {
SWAP(req_top, existing_top);
SWAP(req_chan, existing_chan);
if (c0 == c1) {
c1 = AST_LIST_LAST(&bridge->channels)->chan;
}
if (c0 == c1) {
return;
}
/* If a party renegotiates we want to renegotiate their counterpart to a matching
* topology.
*/
ast_channel_lock_both(c0, c1);
req_top = ast_channel_get_stream_topology(c0);
existing_top = ast_channel_get_stream_topology(c1);
new_top = simple_bridge_request_stream_topology_update(existing_top, req_top);
ast_channel_unlock(req_chan);
ast_channel_unlock(existing_chan);
ast_channel_unlock(c0);
ast_channel_unlock(c1);
if (!new_top) {
/* Failure. We'll just have to live with the current topology. */
return;
}
ast_channel_request_stream_topology_change(existing_chan, new_top, &simple_bridge);
ast_channel_request_stream_topology_change(c1, new_top, &simple_bridge);
ast_stream_topology_free(new_top);
}

View File

@ -462,12 +462,12 @@ static int is_video_source(const struct ast_stream *stream)
*
* \param stream The stream to test
* \param source_channel_name The name of a source video channel to match
* \param source_stream_name The name of the source video stream to match
* \param source_channel_stream_position The position of the video on the source channel
* \retval 1 The stream is a video destination stream
* \retval 0 The stream is not a video destination stream
*/
static int is_video_dest(const struct ast_stream *stream, const char *source_channel_name,
const char *source_stream_name)
int source_channel_stream_position)
{
char *dest_video_name;
size_t dest_video_name_len;
@ -480,17 +480,17 @@ static int is_video_dest(const struct ast_stream *stream, const char *source_cha
dest_video_name_len = SOFTBRIDGE_VIDEO_DEST_LEN + 1;
if (!ast_strlen_zero(source_channel_name)) {
dest_video_name_len += strlen(source_channel_name) + 1;
if (!ast_strlen_zero(source_stream_name)) {
dest_video_name_len += strlen(source_stream_name) + 1;
if (source_channel_stream_position != -1) {
dest_video_name_len += 11;
}
dest_video_name = ast_alloca(dest_video_name_len);
if (!ast_strlen_zero(source_stream_name)) {
/* We are looking for an exact stream name */
snprintf(dest_video_name, dest_video_name_len, "%s%c%s%c%s",
if (source_channel_stream_position != -1) {
/* We are looking for an exact stream position */
snprintf(dest_video_name, dest_video_name_len, "%s%c%s%c%d",
SOFTBRIDGE_VIDEO_DEST_PREFIX, SOFTBRIDGE_VIDEO_DEST_SEPARATOR,
source_channel_name, SOFTBRIDGE_VIDEO_DEST_SEPARATOR,
source_stream_name);
source_channel_stream_position);
return !strcmp(ast_stream_get_name(stream), dest_video_name);
}
snprintf(dest_video_name, dest_video_name_len, "%s%c%s",
@ -503,46 +503,62 @@ static int is_video_dest(const struct ast_stream *stream, const char *source_cha
return !strncmp(ast_stream_get_name(stream), dest_video_name, dest_video_name_len - 1);
}
static int append_source_stream(struct ast_stream_topology *dest,
const char *channel_name, const char *sdp_label,
struct ast_stream *stream, int index)
{
char *stream_clone_name = NULL;
struct ast_stream *stream_clone;
/* We use the stream topology index for the stream to uniquely identify and recognize it.
* This is guaranteed to remain the same across renegotiation of the source channel and
* ensures that the stream name is unique.
*/
if (ast_asprintf(&stream_clone_name, "%s%c%s%c%d", SOFTBRIDGE_VIDEO_DEST_PREFIX,
SOFTBRIDGE_VIDEO_DEST_SEPARATOR, channel_name, SOFTBRIDGE_VIDEO_DEST_SEPARATOR,
index) < 0) {
return -1;
}
stream_clone = ast_stream_clone(stream, stream_clone_name);
ast_free(stream_clone_name);
if (!stream_clone) {
return -1;
}
/* Sends an "a:label" attribute in the SDP for participant event correlation */
if (!ast_strlen_zero(sdp_label)) {
ast_stream_set_metadata(stream_clone, "SDP:LABEL", sdp_label);
}
/* We will be sending them a stream and not expecting anything in return */
ast_stream_set_state(stream_clone, AST_STREAM_STATE_SENDONLY);
if (ast_stream_topology_append_stream(dest, stream_clone) < 0) {
ast_stream_free(stream_clone);
return -1;
}
return 0;
}
static int append_source_streams(struct ast_stream_topology *dest,
const char *channel_name, const char *sdp_label,
const struct ast_stream_topology *source)
{
int i;
const char *stream_identify;
for (i = 0; i < ast_stream_topology_get_count(source); ++i) {
struct ast_stream *stream;
struct ast_stream *stream_clone;
char *stream_clone_name = NULL;
stream = ast_stream_topology_get_stream(source, i);
if (!is_video_source(stream)) {
continue;
}
stream_identify = ast_stream_get_metadata(stream, "MSID:LABEL");
if (!stream_identify) {
stream_identify = ast_stream_get_name(stream);
}
if (ast_asprintf(&stream_clone_name, "%s_%s_%s", SOFTBRIDGE_VIDEO_DEST_PREFIX,
channel_name, stream_identify) < 0) {
return -1;
}
stream_clone = ast_stream_clone(stream, stream_clone_name);
ast_free(stream_clone_name);
if (!stream_clone) {
return -1;
}
/* Sends an "a:label" attribute in the SDP for participant event correlation */
if (!ast_strlen_zero(sdp_label)) {
ast_stream_set_metadata(stream_clone, "SDP:LABEL", sdp_label);
}
if (ast_stream_topology_append_stream(dest, stream_clone) < 0) {
ast_stream_free(stream_clone);
if (append_source_stream(dest, channel_name, sdp_label, stream, i)) {
return -1;
}
}
@ -752,7 +768,7 @@ static int remove_destination_streams(struct ast_stream_topology *topology,
stream = ast_stream_topology_get_stream(topology, i);
if (is_video_dest(stream, channel_name, NULL)) {
if (is_video_dest(stream, channel_name, -1)) {
ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
stream_removed = 1;
}
@ -2135,13 +2151,13 @@ static void softmix_bridge_destroy(struct ast_bridge *bridge)
/*!
* \brief Map a source stream to all of its destination streams.
*
* \param source_stream_name Name of the source stream
* \param source_channel_name Name of channel where the source stream originates
* \param bridge_stream_position The slot in the bridge where source video will come from
* \param participants The bridge_channels in the bridge
* \param source_channel_stream_position The position of the stream on the source channel
*/
static void map_source_to_destinations(const char *source_stream_name, const char *source_channel_name,
size_t bridge_stream_position, struct ast_bridge_channels_list *participants)
static void map_source_to_destinations(const char *source_channel_name,
size_t bridge_stream_position, struct ast_bridge_channels_list *participants, int source_channel_stream_position)
{
struct ast_bridge_channel *participant;
@ -2161,7 +2177,7 @@ static void map_source_to_destinations(const char *source_stream_name, const cha
struct ast_stream *stream;
stream = ast_stream_topology_get_stream(topology, i);
if (is_video_dest(stream, source_channel_name, source_stream_name)) {
if (is_video_dest(stream, source_channel_name, source_channel_stream_position)) {
struct softmix_channel *sc = participant->tech_pvt;
AST_VECTOR_REPLACE(&participant->stream_map.to_channel, bridge_stream_position, i);
@ -2228,6 +2244,137 @@ static void remb_enable_collection(struct ast_bridge *bridge, struct ast_bridge_
}
}
static void softmix_bridge_stream_sources_update(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel,
struct softmix_channel *sc)
{
int index;
struct ast_stream_topology *old_topology = sc->topology;
struct ast_stream_topology *new_topology = ast_channel_get_stream_topology(bridge_channel->chan);
int removed_streams[MAX(ast_stream_topology_get_count(sc->topology), ast_stream_topology_get_count(new_topology))];
size_t removed_streams_count = 0;
struct ast_stream_topology *added_streams;
struct ast_bridge_channels_list *participants = &bridge->channels;
struct ast_bridge_channel *participant;
added_streams = ast_stream_topology_alloc();
if (!added_streams) {
return;
}
/* We go through the old topology comparing it to the new topology to determine what streams
* changed state. A state transition can result in the stream being considered a new source
* (for example it was removed and is now present) or being removed (a stream became inactive).
* Added streams are copied into a topology and added to each other participant while for
* removed streams we merely store their position and mark them as removed later.
*/
for (index = 0; index < ast_stream_topology_get_count(sc->topology) && index < ast_stream_topology_get_count(new_topology); ++index) {
struct ast_stream *old_stream = ast_stream_topology_get_stream(sc->topology, index);
struct ast_stream *new_stream = ast_stream_topology_get_stream(new_topology, index);
/* Ignore all streams that don't carry video and streams that are strictly outgoing destination streams */
if ((ast_stream_get_type(old_stream) != AST_MEDIA_TYPE_VIDEO && ast_stream_get_type(new_stream) != AST_MEDIA_TYPE_VIDEO) ||
!strncmp(ast_stream_get_name(old_stream), SOFTBRIDGE_VIDEO_DEST_PREFIX,
SOFTBRIDGE_VIDEO_DEST_LEN)) {
continue;
}
if (ast_stream_get_type(old_stream) == AST_MEDIA_TYPE_VIDEO && ast_stream_get_type(new_stream) != AST_MEDIA_TYPE_VIDEO) {
/* If a stream renegotiates from video to non-video then we need to remove it as a source */
removed_streams[removed_streams_count++] = index;
} else if (ast_stream_get_type(old_stream) != AST_MEDIA_TYPE_VIDEO && ast_stream_get_type(new_stream) == AST_MEDIA_TYPE_VIDEO) {
if (ast_stream_get_state(new_stream) != AST_STREAM_STATE_REMOVED) {
/* If a stream renegotiates from non-video to video in a non-removed state we need to add it as a source */
if (append_source_stream(added_streams, ast_channel_name(bridge_channel->chan),
bridge->softmix.send_sdp_label ? ast_channel_uniqueid(bridge_channel->chan) : NULL,
new_stream, index)) {
goto cleanup;
}
}
} else if (ast_stream_get_state(old_stream) != AST_STREAM_STATE_REMOVED &&
ast_stream_get_state(new_stream) != AST_STREAM_STATE_SENDRECV && ast_stream_get_state(new_stream) != AST_STREAM_STATE_RECVONLY) {
/* If a stream renegotiates and is removed then we remove it */
removed_streams[removed_streams_count++] = index;
} else if (ast_stream_get_state(old_stream) == AST_STREAM_STATE_REMOVED &&
ast_stream_get_state(new_stream) != AST_STREAM_STATE_INACTIVE && ast_stream_get_state(new_stream) != AST_STREAM_STATE_SENDONLY &&
ast_stream_get_state(new_stream) != AST_STREAM_STATE_REMOVED) {
/* If a stream renegotiates and is added then we add it */
if (append_source_stream(added_streams, ast_channel_name(bridge_channel->chan),
bridge->softmix.send_sdp_label ? ast_channel_uniqueid(bridge_channel->chan) : NULL,
new_stream, index)) {
goto cleanup;
}
}
}
/* Any newly added streams that did not take the position of a removed stream
* will be present at the end of the new topology. Since streams are never
* removed from the topology but merely marked as removed we can pick up where we
* left off when comparing the old and new topologies.
*/
for (; index < ast_stream_topology_get_count(new_topology); ++index) {
struct ast_stream *stream = ast_stream_topology_get_stream(new_topology, index);
if (!is_video_source(stream)) {
continue;
}
if (append_source_stream(added_streams, ast_channel_name(bridge_channel->chan),
bridge->softmix.send_sdp_label ? ast_channel_uniqueid(bridge_channel->chan) : NULL,
stream, index)) {
goto cleanup;
}
}
/* We always update the stored topology if we can to reflect what is currently negotiated */
sc->topology = ast_stream_topology_clone(new_topology);
if (!sc->topology) {
sc->topology = old_topology;
} else {
ast_stream_topology_free(old_topology);
}
/* If there are no removed sources and no added sources we don't need to renegotiate the
* other participants.
*/
if (!removed_streams_count && !ast_stream_topology_get_count(added_streams)) {
goto cleanup;
}
/* Go through each participant adding in the new streams and removing the old ones */
AST_LIST_TRAVERSE(participants, participant, entry) {
if (participant == bridge_channel) {
continue;
}
sc = participant->tech_pvt;
/* We add in all the new streams first so that they do not take the place
* of any of our removed streams, allowing the remote side to reset the state
* for each removed stream. */
if (append_all_streams(sc->topology, added_streams)) {
goto cleanup;
}
/* Then we go through and remove any ones that were removed */
for (index = 0; removed_streams_count && index < ast_stream_topology_get_count(sc->topology); ++index) {
struct ast_stream *stream = ast_stream_topology_get_stream(sc->topology, index);
int removed_stream;
for (removed_stream = 0; removed_stream < removed_streams_count; ++removed_stream) {
if (is_video_dest(stream, ast_channel_name(bridge_channel->chan), removed_streams[removed_stream])) {
ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
}
}
}
ast_channel_request_stream_topology_change(participant->chan, sc->topology, NULL);
}
cleanup:
ast_stream_topology_free(added_streams);
}
/*!
* \brief stream_topology_changed callback
*
@ -2241,7 +2388,7 @@ static void remb_enable_collection(struct ast_bridge *bridge, struct ast_bridge_
static void softmix_bridge_stream_topology_changed(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
struct softmix_bridge_data *softmix_data = bridge->tech_pvt;
struct softmix_channel *sc;
struct softmix_channel *sc = bridge_channel->tech_pvt;
struct ast_bridge_channel *participant;
struct ast_vector_int media_types;
int nths[AST_MEDIA_TYPE_END] = {0};
@ -2258,6 +2405,10 @@ static void softmix_bridge_stream_topology_changed(struct ast_bridge *bridge, st
break;
}
ast_channel_lock(bridge_channel->chan);
softmix_bridge_stream_sources_update(bridge, bridge_channel, sc);
ast_channel_unlock(bridge_channel->chan);
AST_VECTOR_INIT(&media_types, AST_MEDIA_TYPE_END);
/* The bridge stream identifiers may change, so reset the mapping for them.
@ -2307,7 +2458,6 @@ static void softmix_bridge_stream_topology_changed(struct ast_bridge *bridge, st
for (i = 0; i < ast_stream_topology_get_count(topology); ++i) {
struct ast_stream *stream = ast_stream_topology_get_stream(topology, i);
const char *stream_identify;
if (is_video_source(stream)) {
AST_VECTOR_APPEND(&media_types, AST_MEDIA_TYPE_VIDEO);
@ -2325,12 +2475,8 @@ static void softmix_bridge_stream_topology_changed(struct ast_bridge *bridge, st
ast_channel_unlock(participant->chan);
ast_bridge_channel_unlock(participant);
stream_identify = ast_stream_get_metadata(stream, "MSID:LABEL");
if (!stream_identify) {
stream_identify = ast_stream_get_name(stream);
}
map_source_to_destinations(stream_identify, ast_channel_name(participant->chan),
AST_VECTOR_SIZE(&media_types) - 1, &bridge->channels);
map_source_to_destinations(ast_channel_name(participant->chan),
AST_VECTOR_SIZE(&media_types) - 1, &bridge->channels, i);
ast_bridge_channel_lock(participant);
ast_channel_lock(participant->chan);
} else if (ast_stream_get_type(stream) == AST_MEDIA_TYPE_VIDEO) {
@ -2495,10 +2641,10 @@ AST_TEST_DEFINE(sfu_append_source_streams)
{ "alice_video", "vp8", AST_MEDIA_TYPE_VIDEO, },
};
static const struct stream_parameters alice_dest_stream = {
"softbridge_dest_PJSIP/Bob-00000001_bob_video", "h264,vp8", AST_MEDIA_TYPE_VIDEO,
"softbridge_dest_PJSIP/Bob-00000001_1", "h264,vp8", AST_MEDIA_TYPE_VIDEO,
};
static const struct stream_parameters bob_dest_stream = {
"softbridge_dest_PJSIP/Alice-00000000_alice_video", "vp8", AST_MEDIA_TYPE_VIDEO,
"softbridge_dest_PJSIP/Alice-00000000_1", "vp8", AST_MEDIA_TYPE_VIDEO,
};
struct ast_stream_topology *topology_alice = NULL;
struct ast_stream_topology *topology_bob = NULL;
@ -2645,7 +2791,7 @@ AST_TEST_DEFINE(sfu_remove_destination_streams)
goto end;
}
if (is_video_dest(actual, removal_results[i].channel_name, NULL) &&
if (is_video_dest(actual, removal_results[i].channel_name, -1) &&
ast_stream_get_state(actual) != AST_STREAM_STATE_REMOVED) {
ast_test_status_update(test, "Removed stream %s does not have a state of removed\n", ast_stream_get_name(actual));
goto end;

View File

@ -206,6 +206,12 @@ typedef unsigned long long ast_group_t;
struct ast_stream_topology;
/*!
* \brief Set as the change source reason when a channel stream topology has
* been changed externally as a result of the remote side renegotiating.
*/
static const char ast_stream_topology_changed_external[] = "external";
/*! \todo Add an explanation of an Asterisk generator
*/
struct ast_generator {
@ -5017,6 +5023,20 @@ int ast_channel_request_stream_topology_change(struct ast_channel *chan,
*/
int ast_channel_stream_topology_changed(struct ast_channel *chan, struct ast_stream_topology *topology);
/*!
* \brief Provide notice from a channel that the topology has changed on it as a result
* of the remote party renegotiating.
*
* \param chan The channel to provide notice from
*
* \retval 0 success
* \retval -1 failure
*
* \note This interface is provided for channels to provide notice that a topology change
* has occurred as a result of a remote party renegotiating the stream topology.
*/
int ast_channel_stream_topology_changed_externally(struct ast_channel *chan);
/*!
* \brief Retrieve the source that initiated the last stream topology change
*

View File

@ -11051,6 +11051,25 @@ int ast_channel_stream_topology_changed(struct ast_channel *chan, struct ast_str
return ast_channel_tech(chan)->indicate(chan, AST_CONTROL_STREAM_TOPOLOGY_CHANGED, topology, sizeof(topology));
}
int ast_channel_stream_topology_changed_externally(struct ast_channel *chan)
{
int res;
struct ast_frame f = { AST_FRAME_CONTROL, .subclass.integer = AST_CONTROL_STREAM_TOPOLOGY_CHANGED };
ast_assert(chan != NULL);
if (!ast_channel_is_multistream(chan)) {
return -1;
}
ast_channel_lock(chan);
ast_channel_internal_set_stream_topology_change_source(chan, (void *)&ast_stream_topology_changed_external);
res = ast_queue_frame(chan, &f);
ast_channel_unlock(chan);
return res;
}
void ast_channel_set_flag(struct ast_channel *chan, unsigned int flag)
{
ast_channel_lock(chan);

View File

@ -96,8 +96,9 @@ struct ast_stream_topology {
struct ast_stream *ast_stream_alloc(const char *name, enum ast_media_type type)
{
struct ast_stream *stream;
size_t name_len = MAX(strlen(S_OR(name, "")), 7); /* Ensure there is enough room for 'removed' */
stream = ast_calloc(1, sizeof(*stream) + strlen(S_OR(name, "")) + 1);
stream = ast_calloc(1, sizeof(*stream) + name_len + 1);
if (!stream) {
return NULL;
}
@ -113,16 +114,16 @@ struct ast_stream *ast_stream_alloc(const char *name, enum ast_media_type type)
struct ast_stream *ast_stream_clone(const struct ast_stream *stream, const char *name)
{
struct ast_stream *new_stream;
size_t stream_size;
const char *stream_name;
size_t name_len;
if (!stream) {
return NULL;
}
stream_name = name ?: stream->name;
stream_size = sizeof(*stream) + strlen(stream_name) + 1;
new_stream = ast_calloc(1, stream_size);
name_len = MAX(strlen(stream_name), 7); /* Ensure there is enough room for 'removed' */
new_stream = ast_calloc(1, sizeof(*stream) + name_len + 1);
if (!new_stream) {
return NULL;
}
@ -205,6 +206,19 @@ void ast_stream_set_state(struct ast_stream *stream, enum ast_stream_state state
ast_assert(stream != NULL);
stream->state = state;
/* When a stream is set to removed that means that any previous data for it
* is no longer valid. We therefore change its name to removed and remove
* any old metadata associated with it.
*/
if (state == AST_STREAM_STATE_REMOVED) {
strcpy(stream->name, "removed");
ast_variables_destroy(stream->metadata);
stream->metadata = NULL;
if (stream->formats) {
ast_format_cap_remove_by_type(stream->formats, AST_MEDIA_TYPE_UNKNOWN);
}
}
}
const char *ast_stream_state2str(enum ast_stream_state state)

View File

@ -1823,6 +1823,12 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
} else {
if (session_media->remotely_held) {
attr->name = STR_RECVONLY; /* Remote has sent sendonly, reply recvonly */
} else if (ast_stream_get_state(stream) == AST_STREAM_STATE_SENDONLY) {
attr->name = STR_SENDONLY; /* Stream has requested sendonly */
} else if (ast_stream_get_state(stream) == AST_STREAM_STATE_RECVONLY) {
attr->name = STR_RECVONLY; /* Stream has requested recvonly */
} else if (ast_stream_get_state(stream) == AST_STREAM_STATE_INACTIVE) {
attr->name = STR_INACTIVE; /* Stream has requested inactive */
} else {
attr->name = STR_SENDRECV; /* No hold in either direction */
}

View File

@ -952,7 +952,7 @@ static int handle_negotiated_sdp(struct ast_sip_session *session, const pjmedia_
{
int i;
struct ast_stream_topology *topology;
unsigned int changed = 0;
unsigned int changed = 0; /* 0 = unchanged, 1 = new source, 2 = new topology */
if (!session->pending_media_state->topology) {
if (session->active_media_state->topology) {
@ -1064,6 +1064,14 @@ static int handle_negotiated_sdp(struct ast_sip_session *session, const pjmedia_
topology = ast_stream_topology_clone(session->pending_media_state->topology);
if (topology) {
ast_channel_set_stream_topology(session->channel, topology);
/* If this is a remotely done renegotiation that has changed the stream topology notify what is
* currently handling this channel.
*/
if (pjmedia_sdp_neg_was_answer_remote(session->inv_session->neg) == PJ_FALSE &&
session->active_media_state && session->active_media_state->topology &&
!ast_stream_topology_equal(session->active_media_state->topology, topology)) {
changed = 2;
}
}
/* Remove all current file descriptors from the channel */
@ -1086,10 +1094,12 @@ static int handle_negotiated_sdp(struct ast_sip_session *session, const pjmedia_
ast_channel_unlock(session->channel);
if (changed) {
if (changed == 1) {
struct ast_frame f = { AST_FRAME_CONTROL, .subclass.integer = AST_CONTROL_STREAM_TOPOLOGY_SOURCE_CHANGED };
ast_queue_frame(session->channel, &f);
} else if (changed == 2) {
ast_channel_stream_topology_changed_externally(session->channel);
} else {
ast_queue_frame(session->channel, &ast_null_frame);
}
@ -1926,6 +1936,7 @@ static int sdp_requires_deferral(struct ast_sip_session *session, const pjmedia_
enum ast_media_type type;
struct ast_sip_session_media *session_media = NULL;
enum ast_sip_session_sdp_stream_defer res;
pjmedia_sdp_media *remote_stream = sdp->media[i];
/* We need a null-terminated version of the media string */
ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media));
@ -1954,6 +1965,25 @@ static int sdp_requires_deferral(struct ast_sip_session *session, const pjmedia_
return -1;
}
/* For backwards compatibility with the core default streams are always sendrecv */
if (!ast_sip_session_is_pending_stream_default(session, stream)) {
if (pjmedia_sdp_media_find_attr2(remote_stream, "sendonly", NULL)) {
/* Stream state reflects our state of a stream, so in the case of
* sendonly and recvonly we store the opposite since that is what ours
* is.
*/
ast_stream_set_state(stream, AST_STREAM_STATE_RECVONLY);
} else if (pjmedia_sdp_media_find_attr2(remote_stream, "recvonly", NULL)) {
ast_stream_set_state(stream, AST_STREAM_STATE_SENDONLY);
} else if (pjmedia_sdp_media_find_attr2(remote_stream, "inactive", NULL)) {
ast_stream_set_state(stream, AST_STREAM_STATE_INACTIVE);
} else {
ast_stream_set_state(stream, AST_STREAM_STATE_SENDRECV);
}
} else {
ast_stream_set_state(stream, AST_STREAM_STATE_SENDRECV);
}
if (session_media->handler) {
handler = session_media->handler;
if (handler->defer_incoming_sdp_stream) {