[Asterisk-code-review] Add primitive SFU support to bridge softmix. (asterisk[master])

Mark Michelson asteriskteam at digium.com
Thu May 18 14:40:10 CDT 2017


Mark Michelson has uploaded a new change for review. ( https://gerrit.asterisk.org/5643 )

Change subject: Add primitive SFU support to bridge_softmix.
......................................................................

Add primitive SFU support to bridge_softmix.

This sets up the "plumbing" in bridge_softmix to
be able to accommodate Asterisk asking as an SFU
(selective forwarding unit) for conferences.

The way this works is that whenever a channel enters or leaves a
conference, all participants in the bridge get sent a stream topology
change request. The topologies consist of the channels' original
topology, along with video destination streams corresponding to each
participants' source video streams. So for instance, if Alice, Bob, and
Carol are in the conference, and each supplies one video stream, then
the topologies for each would look like so:

Alice:
Audio,
Source video(Alice),
Destination Video(Bob),
Destination video (Carol)

Bob:
Audio,
Source video(Bob)
Destination Video(Alice),
Destination video (Carol)

Carol:
Audio,
Source video(Carol)
Destination Video(Alice),
Destination video (Bob)

This way, video that arrives from a source video stream can then be
copied out to the destination video streams on the other participants'
channels.

Once the bridge gets told that a topology on a channel has changed, the
bridge constructs a map in order to get the video frames routed to the
proper destination streams. This is done using the bridge channel's
stream_map.

This change is bare-bones with regards to SFU support. Some key features
are missing at this point:

* Stream limits. This commit makes no effort to limit the number of
  streams on a specific channel. This means that if there were 50 video
  callers in a conference, bridge_softmix will happily send out topology
  change requests to every channel in the bridge, requesting 50+
  streams.

* Configuration. The plumbing has been added to bridge_softmix, but
  there has been nothing added as of yet to app_confbridge to enable SFU
  video mode.

* Testing. Some functions included here have unit tests.
  However, the functionality as a whole has only been verified by
  hand-tracing the code.

* Selectivenss. For a "selective" forwarding unit, this does not
  currently have any means of being selective.

* Features. Presumably, someone might wish to only receive video from
  specific sources. There are no external-facing functions at the moment
  that allow for users to select who they receive video from.

* Efficiency. The current scheme treats all video streams as being
  unidirectional. We could be re-using a source video stream as a
  desetnation, too. But to simplify things on this first round, I did it
  this way.

Change-Id: I7c44a829cc63acf8b596a337b2dc3c13898a6c4d
---
M bridges/bridge_simple.c
M bridges/bridge_softmix.c
M include/asterisk/bridge.h
M include/asterisk/stream.h
M main/bridge.c
M main/bridge_channel.c
M main/sdp_state.c
M main/stream.c
8 files changed, 727 insertions(+), 12 deletions(-)


  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/43/5643/3

diff --git a/bridges/bridge_simple.c b/bridges/bridge_simple.c
index 47f41cb..00c7997 100644
--- a/bridges/bridge_simple.c
+++ b/bridges/bridge_simple.c
@@ -91,6 +91,7 @@
 	struct ast_stream_topology *t0 = ast_channel_get_stream_topology(c0);
 	struct ast_stream_topology *t1 = ast_channel_get_stream_topology(c1);
 
+	ast_bridge_channel_stream_map(bridge_channel);
 	/*
 	 * The bridge_channel should only be NULL after both channels join
 	 * the bridge and their topologies are being aligned.
diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c
index 94dfc57..5ef8f29 100644
--- a/bridges/bridge_softmix.c
+++ b/bridges/bridge_softmix.c
@@ -31,7 +31,11 @@
 	<support_level>core</support_level>
  ***/
 
+#include "asterisk.h"
 
+#include "asterisk/stream.h"
+#include "asterisk/test.h"
+#include "asterisk/vector.h"
 #include "bridge_softmix/include/bridge_softmix_internal.h"
 
 /*! The minimum sample rate of the bridge. */
@@ -53,6 +57,10 @@
  * mixed out its own write audio stream because it is not talking. */
 #define DEFAULT_SOFTMIX_SILENCE_THRESHOLD 2500
 #define DEFAULT_SOFTMIX_TALKING_THRESHOLD 160
+
+#define SOFTBRIDGE_VIDEO_DEST_PREFIX "softbridge_dest"
+#define SOFTBRIDGE_VIDEO_DEST_LEN strlen(SOFTBRIDGE_VIDEO_DEST_PREFIX)
+#define SOFTBRIDGE_VIDEO_DEST_SEPARATOR '_'
 
 struct softmix_stats {
 	/*! Each index represents a sample rate used above the internal rate. */
@@ -401,6 +409,205 @@
 	}
 }
 
+/*!
+ * \brief Determine if a stream is a video destination stream.
+ *
+ * \param stream The stream to test
+ * \retval 1 The stream is a video source
+ * \retval 0 The stream is not a video source
+ */
+static int is_video_source(const struct ast_stream *stream)
+{
+	if (ast_stream_get_type(stream) == AST_MEDIA_TYPE_VIDEO &&
+		strncmp(ast_stream_get_name(stream), SOFTBRIDGE_VIDEO_DEST_PREFIX,
+			SOFTBRIDGE_VIDEO_DEST_LEN)) {
+		return 1;
+	}
+
+	return 0;
+}
+
+/*!
+ * \brief Determine if a stream is a video destination stream.
+ *
+ * A source channel name can be provided to narrow this to a destination stream
+ * for a particular source channel. Further, a source stream name can be provided
+ * to narrow this to a particular source stream's destination. However, empty strings
+ * can be provided to match any destination video stream, regardless of source channel
+ * or source 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
+ * \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)
+{
+	char *dest_video_name;
+	size_t dest_video_name_len;
+
+	if (ast_stream_get_type(stream) != AST_MEDIA_TYPE_VIDEO) {
+		return 0;
+	}
+
+	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;
+		}
+	}
+	dest_video_name = ast_alloca(dest_video_name_len);
+
+	if (!ast_strlen_zero(source_channel_name)) {
+		if (!ast_strlen_zero(source_stream_name)) {
+			snprintf(dest_video_name, dest_video_name_len, "%s%c%s%c%s",
+				SOFTBRIDGE_VIDEO_DEST_PREFIX, SOFTBRIDGE_VIDEO_DEST_SEPARATOR,
+				source_channel_name, SOFTBRIDGE_VIDEO_DEST_SEPARATOR,
+				source_stream_name);
+			return !strcmp(ast_stream_get_name(stream), dest_video_name);
+		} else {
+			snprintf(dest_video_name, dest_video_name_len, "%s%c%s",
+				SOFTBRIDGE_VIDEO_DEST_PREFIX, SOFTBRIDGE_VIDEO_DEST_SEPARATOR,
+				source_channel_name);
+			return !strncmp(ast_stream_get_name(stream), dest_video_name, dest_video_name_len - 1);
+		}
+	} else {
+		snprintf(dest_video_name, dest_video_name_len, "%s",
+			SOFTBRIDGE_VIDEO_DEST_PREFIX);
+		return !strncmp(ast_stream_get_name(stream), dest_video_name, dest_video_name_len - 1);
+	}
+
+	return 0;
+}
+
+static int append_source_streams(struct ast_stream_topology *dest,
+	const char *channel_name,
+	const struct ast_stream_topology *source)
+{
+	int i;
+
+	for (i = 0; i < ast_stream_topology_get_count(source); ++i) {
+		struct ast_stream *stream;
+		struct ast_stream *stream_clone;
+		char *stream_clone_name;
+		size_t stream_clone_name_len;
+
+		stream = ast_stream_topology_get_stream(source, i);
+		if (!is_video_source(stream)) {
+			continue;
+		}
+
+		/* The +3 is for the two underscore separators and null terminator */
+		stream_clone_name_len = SOFTBRIDGE_VIDEO_DEST_LEN + strlen(channel_name) + strlen(ast_stream_get_name(stream)) + 4;
+		stream_clone_name = ast_alloca(stream_clone_name_len);
+		snprintf(stream_clone_name, stream_clone_name_len, "%s_%s_%s", SOFTBRIDGE_VIDEO_DEST_PREFIX,
+			channel_name, ast_stream_get_name(stream));
+
+		stream_clone = ast_stream_clone(stream, stream_clone_name);
+		if (!stream_clone) {
+			return -1;
+		}
+		ast_stream_topology_append_stream(dest, stream_clone);
+	}
+
+	return 0;
+}
+
+static int append_all_streams(struct ast_stream_topology *dest,
+	const struct ast_stream_topology *source)
+{
+	int i;
+
+	for (i = 0; i < ast_stream_topology_get_count(source); ++i) {
+		struct ast_stream *clone;
+
+		clone = ast_stream_clone(ast_stream_topology_get_stream(source, i), NULL);
+		if (!clone) {
+			return -1;
+		}
+		ast_stream_topology_append_stream(dest, clone);
+	}
+
+	return 0;
+}
+
+/*!
+ * \brief Issue channel stream topology change requests.
+ *
+ * When in SFU mode, each participant needs to be able to
+ * send video directly to other participants in the bridge.
+ * This means that all participants need to have their topologies
+ * updated. The joiner needs to have destination streams for
+ * all current participants, and the current participants need
+ * to have destinations streams added for the joiner's sources.
+ *
+ * \param joiner The channel that is joining the softmix bridge
+ * \param participants The current participants in the softmix bridge
+ */
+static void sfu_topologies_on_join(struct ast_bridge_channel *joiner, struct ast_bridge_channels_list *participants)
+{
+	struct ast_stream_topology *joiner_topology;
+	struct ast_stream_topology *joiner_video;
+	struct ast_stream_topology *existing_video;
+	struct ast_bridge_channel *participant;
+
+	joiner_video = ast_stream_topology_alloc();
+	if (!joiner_video) {
+		return;
+	}
+
+	if (append_source_streams(joiner_video, ast_channel_name(joiner->chan), ast_channel_get_stream_topology(joiner->chan))) {
+		ast_stream_topology_free(joiner_video);
+		return;
+	}
+
+	existing_video = ast_stream_topology_alloc();
+	if (!existing_video) {
+		ast_stream_topology_free(joiner_video);
+		return;
+	}
+
+	AST_LIST_TRAVERSE(participants, participant, entry) {
+		if (participant == joiner) {
+			continue;
+		}
+		if (append_source_streams(existing_video, ast_channel_name(participant->chan),
+				ast_channel_get_stream_topology(participant->chan))) {
+			ast_stream_topology_free(joiner_video);
+			ast_stream_topology_free(existing_video);
+			return;
+		}
+	}
+
+	joiner_topology = ast_stream_topology_clone(ast_channel_get_stream_topology(joiner->chan));
+	append_all_streams(joiner_topology, existing_video);
+	if (append_all_streams(joiner_topology, existing_video)) {
+		ast_stream_topology_free(joiner_video);
+		ast_stream_topology_free(existing_video);
+		return;
+	}
+	ast_channel_request_stream_topology_change(joiner->chan, joiner_topology, NULL);
+
+	AST_LIST_TRAVERSE(participants, participant, entry) {
+		struct ast_stream_topology *participant_topology;
+
+		if (participant == joiner) {
+			continue;
+		}
+		participant_topology = ast_stream_topology_clone(ast_channel_get_stream_topology(joiner->chan));
+		if (append_all_streams(participant_topology, joiner_video)) {
+			ast_stream_topology_free(joiner_video);
+			ast_stream_topology_free(existing_video);
+			return;
+		}
+		ast_channel_request_stream_topology_change(participant->chan, participant_topology, NULL);
+	}
+}
+
 /*! \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)
 {
@@ -464,18 +671,79 @@
 			: DEFAULT_SOFTMIX_INTERVAL,
 		bridge_channel, 0, set_binaural, pos_id, is_announcement);
 
+	if (bridge->softmix.video_mode.mode == AST_BRIDGE_VIDEO_MODE_SFU) {
+		sfu_topologies_on_join(bridge_channel, &bridge->channels);
+	}
+
 	softmix_poke_thread(softmix_data);
+	return 0;
+}
+
+static int remove_destination_streams(struct ast_stream_topology *dest,
+	const char *channel_name,
+	const struct ast_stream_topology *source)
+{
+	int i;
+
+	for (i = 0; i < ast_stream_topology_get_count(source); ++i) {
+		struct ast_stream *stream;
+		struct ast_stream *stream_clone;
+
+		stream = ast_stream_topology_get_stream(source, i);
+
+		if (is_video_dest(stream, channel_name, NULL)) {
+			continue;
+		}
+
+		stream_clone = ast_stream_clone(stream, NULL);
+		if (!stream_clone) {
+			continue;
+		}
+		ast_stream_topology_append_stream(dest, stream_clone);
+	}
+
+	return 0;
+}
+
+static int sfu_topologies_on_leave(struct ast_bridge_channel *leaver, struct ast_bridge_channels_list *participants)
+{
+	struct ast_stream_topology *leaver_topology;
+	struct ast_bridge_channel *participant;
+
+	leaver_topology = ast_stream_topology_alloc();
+	if (!leaver_topology) {
+		return -1;
+	}
+
+	AST_LIST_TRAVERSE(participants, participant, entry) {
+		struct ast_stream_topology *participant_topology;
+
+		participant_topology = ast_stream_topology_alloc();
+		if (!participant_topology) {
+			continue;
+		}
+
+		remove_destination_streams(participant_topology, ast_channel_name(leaver->chan), ast_channel_get_stream_topology(participant->chan));
+		ast_channel_request_stream_topology_change(participant->chan, participant_topology, NULL);
+	}
+
+	remove_destination_streams(leaver_topology, "", ast_channel_get_stream_topology(leaver->chan));
+	ast_channel_request_stream_topology_change(leaver->chan, leaver_topology, NULL);
+
 	return 0;
 }
 
 /*! \brief Function called when a channel leaves the bridge */
 static void softmix_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
 {
-
 	struct softmix_channel *sc;
 	struct softmix_bridge_data *softmix_data;
 	softmix_data = bridge->tech_pvt;
 	sc = bridge_channel->tech_pvt;
+
+	if (bridge->softmix.video_mode.mode == AST_BRIDGE_VIDEO_MODE_SFU) {
+		sfu_topologies_on_leave(bridge_channel, &bridge->channels);
+	}
 
 	if (!sc) {
 		return;
@@ -564,6 +832,12 @@
 		} else if (video_src_priority == 2) {
 			softmix_pass_video_top_priority(bridge, frame);
 		}
+		break;
+	case AST_BRIDGE_VIDEO_MODE_SFU:
+		/* Nothing special to do here, the bridge channel stream map will ensure the
+		 * video goes everywhere it needs to
+		 */
+		ast_bridge_queue_everyone_else(bridge, bridge_channel, frame);
 		break;
 	}
 }
@@ -1323,6 +1597,140 @@
 	bridge->tech_pvt = NULL;
 }
 
+/*!
+ * \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
+ */
+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)
+{
+	struct ast_bridge_channel *participant;
+
+	AST_LIST_TRAVERSE(participants, participant, entry) {
+		int i;
+		struct ast_stream_topology *topology;
+
+		if (!strcmp(source_channel_name, ast_channel_name(participant->chan))) {
+			continue;
+		}
+
+		ast_bridge_channel_lock(participant);
+		topology = ast_channel_get_stream_topology(participant->chan);
+
+		for (i = 0; i < ast_stream_topology_get_count(topology); ++i) {
+			struct ast_stream *stream;
+
+			stream = ast_stream_topology_get_stream(topology, i);
+			if (is_video_dest(stream, source_channel_name, source_stream_name)) {
+				AST_VECTOR_REPLACE(&participant->stream_map.to_channel, bridge_stream_position, i);
+				break;
+			}
+		}
+		ast_bridge_channel_unlock(participant);
+	}
+}
+
+/*\brief stream_topology_changed callback
+ *
+ * For most video modes, nothing beyond the ordinary is required.
+ * For the SFU case, though, we need to completely remap the streams
+ * in order to ensure video gets directed where it is expected to go.
+ *
+ * \param bridge The bridge
+ * \param bridge_channel Channel whose topology has changed
+ */
+static void softmix_bridge_stream_topology_changed(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_bridge_channel *participant;
+	struct ast_vector_int media_types;
+	int nths[AST_MEDIA_TYPE_END] = {0};
+
+	switch (bridge->softmix.video_mode.mode) {
+	case AST_BRIDGE_VIDEO_MODE_NONE:
+	case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
+	case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
+	default:
+		ast_bridge_channel_stream_map(bridge_channel);
+		return;
+	case AST_BRIDGE_VIDEO_MODE_SFU:
+		break;
+	}
+
+	AST_VECTOR_INIT(&media_types, AST_MEDIA_TYPE_END);
+
+	/* First traversal: re-initialize all of the participants' stream maps */
+	AST_LIST_TRAVERSE(&bridge->channels, participant, entry) {
+		int size;
+
+		ast_bridge_channel_lock(participant);
+		size = ast_stream_topology_get_count(ast_channel_get_stream_topology(participant->chan));
+
+		AST_VECTOR_FREE(&participant->stream_map.to_channel);
+		AST_VECTOR_FREE(&participant->stream_map.to_bridge);
+
+		AST_VECTOR_INIT(&participant->stream_map.to_channel, size);
+		AST_VECTOR_INIT(&participant->stream_map.to_bridge, size);
+		ast_bridge_channel_unlock(participant);
+	}
+
+	/* Second traversal: Map specific video channels from their source to their destinations.
+	 *
+	 * This is similar to what is done in ast_stream_topology_map(), except that
+	 * video channels are handled differently. Each video source has it's own
+	 * unique index on the bridge. this way, a particular channel's source video
+	 * can be distributed to the appropriate destination streams on the other
+	 * channels
+	 */
+	AST_LIST_TRAVERSE(&bridge->channels, participant, entry) {
+		int i;
+		struct ast_stream_topology *topology;
+
+		topology = ast_channel_get_stream_topology(participant->chan);
+
+		for (i = 0; i < ast_stream_topology_get_count(topology); ++i) {
+			struct ast_stream *stream = ast_stream_topology_get_stream(topology, i);
+			ast_bridge_channel_lock(participant);
+			if (is_video_source(stream)) {
+				AST_VECTOR_APPEND(&media_types, AST_MEDIA_TYPE_VIDEO);
+				AST_VECTOR_REPLACE(&participant->stream_map.to_bridge, i, AST_VECTOR_SIZE(&media_types) - 1);
+				AST_VECTOR_REPLACE(&participant->stream_map.to_channel, AST_VECTOR_SIZE(&media_types) - 1, -1);
+				/* Unlock the participant to prevent potential deadlock
+				 * in map_source_to_destinations
+				 */
+				ast_bridge_channel_unlock(participant);
+				map_source_to_destinations(ast_stream_get_name(stream), ast_channel_name(participant->chan),
+					AST_VECTOR_SIZE(&media_types) - 1, &bridge->channels);
+				ast_bridge_channel_lock(participant);
+			} else if (is_video_dest(stream, NULL, NULL)) {
+				/* We expect to never read media from video destination channels, but just
+				 * in case, we should set their to_bridge value to -1.
+				 */
+				AST_VECTOR_REPLACE(&participant->stream_map.to_bridge, i, -1);
+			} else {
+				/* XXX This is copied from ast_stream_topology_map(). This likely could
+				 * be factored out in some way
+				 */
+				enum ast_media_type type = ast_stream_get_type(stream);
+				int index = AST_VECTOR_GET_INDEX_NTH(&media_types, ++nths[type],
+					type, AST_VECTOR_ELEM_DEFAULT_CMP);
+
+				if (index == -1) {
+					AST_VECTOR_APPEND(&media_types, type);
+					index = AST_VECTOR_SIZE(&media_types) - 1;
+				}
+
+				AST_VECTOR_REPLACE(&participant->stream_map.to_bridge, i, index);
+				AST_VECTOR_REPLACE(&participant->stream_map.to_channel, index, i);
+			}
+			ast_bridge_channel_unlock(participant);
+		}
+	}
+}
+
 static struct ast_bridge_technology softmix_bridge = {
 	.name = "softmix",
 	.capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX,
@@ -1334,11 +1742,294 @@
 	.leave = softmix_bridge_leave,
 	.unsuspend = softmix_bridge_unsuspend,
 	.write = softmix_bridge_write,
+	.stream_topology_changed = softmix_bridge_stream_topology_changed,
 };
+
+#ifdef TEST_FRAMEWORK
+struct stream_parameters {
+	const char *name;
+	const char *formats;
+	enum ast_media_type type;
+};
+
+static struct ast_stream_topology *build_topology(const struct stream_parameters *params, size_t num_streams)
+{
+	struct ast_stream_topology *topology;
+	size_t i;
+
+	topology = ast_stream_topology_alloc();
+	if (!topology) {
+		return NULL;
+	}
+
+	for (i = 0; i < num_streams; ++i) {
+		RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
+		struct ast_stream *stream;
+
+		caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+		if (!caps) {
+			goto fail;
+		}
+		if (ast_format_cap_update_by_allow_disallow(caps, params[i].formats, 1) < 0) {
+			goto fail;
+		}
+		stream = ast_stream_alloc(params[i].name, params[i].type);
+		if (!stream) {
+			goto fail;
+		}
+		ast_stream_set_formats(stream, caps);
+		ast_stream_topology_append_stream(topology, stream);
+	}
+
+	return topology;
+
+fail:
+	ast_stream_topology_free(topology);
+	return NULL;
+}
+
+static int validate_stream(struct ast_test *test, struct ast_stream *stream,
+	const struct stream_parameters *params)
+{
+	struct ast_format_cap *stream_caps;
+	struct ast_format_cap *params_caps;
+
+	if (ast_stream_get_type(stream) != params->type) {
+		ast_test_status_update(test, "Expected stream type '%s' but got type '%s'\n",
+			ast_codec_media_type2str(params->type),
+			ast_codec_media_type2str(ast_stream_get_type(stream)));
+		return -1;
+	}
+	if (strcmp(ast_stream_get_name(stream), params->name)) {
+		ast_test_status_update(test, "Expected stream name '%s' but got type '%s'\n",
+			params->name, ast_stream_get_name(stream));
+		return -1;
+	}
+
+	stream_caps = ast_stream_get_formats(stream);
+	params_caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+	ast_format_cap_update_by_allow_disallow(params_caps, params->formats, 1);
+
+	if (ast_format_cap_identical(stream_caps, params_caps)) {
+		ast_test_status_update(test, "Formats are not as expected on stream '%s'\n",
+			ast_stream_get_name(stream));
+		ao2_cleanup(params_caps);
+		return -1;
+	}
+
+	ao2_cleanup(params_caps);
+	return 0;
+}
+
+static int validate_original_streams(struct ast_test *test, struct ast_stream_topology *topology,
+	const struct stream_parameters *params, size_t num_streams)
+{
+	int i;
+
+	if (ast_stream_topology_get_count(topology) < num_streams) {
+		ast_test_status_update(test, "Topology only has %d streams. Needs to have at least %zu\n",
+			ast_stream_topology_get_count(topology), num_streams);
+		return -1;
+	}
+
+	for (i = 0; i < ARRAY_LEN(params); ++i) {
+		if (validate_stream(test, ast_stream_topology_get_stream(topology, i), &params[i])) {
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+AST_TEST_DEFINE(sfu_append_source_streams)
+{
+	enum ast_test_result_state res = AST_TEST_FAIL;
+	static const struct stream_parameters bob_streams[] = {
+		{ "bob_audio", "ulaw,alaw,g722,opus", AST_MEDIA_TYPE_AUDIO, },
+		{ "bob_video", "h264,vp8", AST_MEDIA_TYPE_VIDEO, },
+	};
+	static const struct stream_parameters alice_streams[] = {
+		{ "alice_audio", "ulaw,opus", AST_MEDIA_TYPE_AUDIO, },
+		{ "alice_video", "vp8", AST_MEDIA_TYPE_VIDEO, },
+	};
+	static const struct stream_parameters alice_dest_stream = {
+		"softbridge_dest_PJSIP/Bob-00000001_bob_video", "vp8", AST_MEDIA_TYPE_VIDEO,
+	};
+	static const struct stream_parameters bob_dest_stream = {
+		"softbridge_dest_PJSIP/Alice-00000000_alice_video", "h264,vp8", AST_MEDIA_TYPE_VIDEO,
+	};
+	struct ast_stream_topology *topology_alice = NULL;
+	struct ast_stream_topology *topology_bob = NULL;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "sfu_append_source_streams";
+		info->category = "/bridges/bridge_softmix/";
+		info->summary = "Test appending of video streams";
+		info->description =
+			"This tests does stuff.";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	topology_alice = build_topology(alice_streams, ARRAY_LEN(alice_streams));
+	if (!topology_alice) {
+		goto end;
+	}
+
+	topology_bob = build_topology(bob_streams, ARRAY_LEN(bob_streams));
+	if (!topology_bob) {
+		goto end;
+	}
+
+	if (append_source_streams(topology_alice, "PJSIP/Bob-00000001", topology_bob)) {
+		ast_test_status_update(test, "Failed to append Bob's streams to Alice\n");
+		goto end;
+	}
+
+	if (ast_stream_topology_get_count(topology_alice) != 3) {
+		ast_test_status_update(test, "Alice's topology isn't large enough! It's %d but needs to be %d\n",
+			ast_stream_topology_get_count(topology_alice), 3);
+		goto end;
+	}
+
+	if (validate_original_streams(test, topology_alice, alice_streams, ARRAY_LEN(alice_streams))) {
+		goto end;
+	}
+
+	if (validate_stream(test, ast_stream_topology_get_stream(topology_alice, 2), &alice_dest_stream)) {
+		goto end;
+	}
+
+	if (append_source_streams(topology_bob, "PJSIP/Alice-00000000", topology_alice)) {
+		ast_test_status_update(test, "Failed to append Alice's streams to Bob\n");
+		goto end;
+	}
+
+	if (ast_stream_topology_get_count(topology_bob) != 3) {
+		ast_test_status_update(test, "Bob's topology isn't large enough! It's %d but needs to be %d\n",
+			ast_stream_topology_get_count(topology_bob), 3);
+		goto end;
+	}
+
+	if (validate_original_streams(test, topology_bob, bob_streams, ARRAY_LEN(bob_streams))) {
+		goto end;
+	}
+
+	if (validate_stream(test, ast_stream_topology_get_stream(topology_bob, 2), &bob_dest_stream)) {
+		goto end;
+	}
+
+	res = AST_TEST_PASS;
+
+end:
+	ast_stream_topology_free(topology_alice);
+	ast_stream_topology_free(topology_bob);
+	return res;
+}
+
+AST_TEST_DEFINE(sfu_remove_destination_streams)
+{
+	enum ast_test_result_state res = AST_TEST_FAIL;
+	static const struct stream_parameters params[] = {
+		{ "alice_audio", "ulaw,alaw,g722,opus", AST_MEDIA_TYPE_AUDIO, },
+		{ "alice_video", "h264,vp8", AST_MEDIA_TYPE_VIDEO, },
+		{ "softbridge_dest_PJSIP/Bob-00000001_video", "vp8", AST_MEDIA_TYPE_VIDEO, },
+		{ "softbridge_dest_PJSIP/Carol-00000002_video", "h264", AST_MEDIA_TYPE_VIDEO, },
+	};
+	static const struct {
+		const char *channel_name;
+		int num_streams;
+		int params_index[4];
+	} removal_results[] = {
+		{ "PJSIP/Bob-00000001", 3, { 0, 1, 3, -1 }, },
+		{ "PJSIP/Edward-00000004", 4, { 0, 1, 2, 3 }, },
+		{ "", 2, { 0, 1, -1, -1 }, },
+	};
+	struct ast_stream_topology *orig = NULL;
+	struct ast_stream_topology *result = NULL;
+	int i;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "sfu_remove_destination_streams";
+		info->category = "/bridges/bridge_softmix/";
+		info->summary = "Test removal of destination video streams";
+		info->description =
+			"This tests does stuff.";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	orig = build_topology(params, ARRAY_LEN(params));
+	if (!orig) {
+		ast_test_status_update(test, "Unable to build initial stream topology\n");
+		goto end;
+	}
+
+	for (i = 0; i < ARRAY_LEN(removal_results); ++i) {
+		int j;
+
+		result = ast_stream_topology_alloc();
+		if (!result) {
+			ast_test_status_update(test, "Unable to allocate result stream topology\n");
+			goto end;
+		}
+
+		if (remove_destination_streams(result, removal_results[i].channel_name, orig)) {
+			ast_test_status_update(test, "Failure while attempting to remove video streams\n");
+			goto end;
+		}
+
+		if (ast_stream_topology_get_count(result) != removal_results[i].num_streams) {
+			ast_test_status_update(test, "Resulting topology has %d streams, when %d are expected\n",
+				ast_stream_topology_get_count(result), removal_results[i].num_streams);
+			goto end;
+		}
+
+		for (j = 0; j < removal_results[i].num_streams; ++j) {
+			struct ast_stream *actual;
+			struct ast_stream *expected;
+			int orig_index;
+
+			actual = ast_stream_topology_get_stream(result, j);
+
+			orig_index = removal_results[i].params_index[j];
+			expected = ast_stream_topology_get_stream(orig, orig_index);
+
+			if (!ast_format_cap_identical(ast_stream_get_formats(actual),
+				ast_stream_get_formats(expected))) {
+				struct ast_str *expected_str;
+				struct ast_str *actual_str;
+
+				expected_str = ast_str_alloca(64);
+				actual_str = ast_str_alloca(64);
+
+				ast_test_status_update(test, "Mismatch between expected (%s) and actual (%s) stream formats\n",
+					ast_format_cap_get_names(ast_stream_get_formats(expected), &expected_str),
+					ast_format_cap_get_names(ast_stream_get_formats(actual), &actual_str));
+				goto end;
+			}
+		}
+	}
+
+	res = AST_TEST_PASS;
+
+end:
+	ast_stream_topology_free(orig);
+	ast_stream_topology_free(result);
+	return res;
+}
+
+#endif
 
 static int unload_module(void)
 {
 	ast_bridge_technology_unregister(&softmix_bridge);
+	AST_TEST_UNREGISTER(sfu_append_source_streams);
+	AST_TEST_UNREGISTER(sfu_remove_destination_streams);
 	return 0;
 }
 
@@ -1348,6 +2039,8 @@
 		unload_module();
 		return AST_MODULE_LOAD_DECLINE;
 	}
+	AST_TEST_REGISTER(sfu_append_source_streams);
+	AST_TEST_REGISTER(sfu_remove_destination_streams);
 	return AST_MODULE_LOAD_SUCCESS;
 }
 
diff --git a/include/asterisk/bridge.h b/include/asterisk/bridge.h
index a9b01a6..6915af2 100644
--- a/include/asterisk/bridge.h
+++ b/include/asterisk/bridge.h
@@ -102,6 +102,10 @@
 	/*! A single user's video feed is distributed to all bridge channels, but
 	 *  that feed is automatically picked based on who is talking the most. */
 	AST_BRIDGE_VIDEO_MODE_TALKER_SRC,
+	/*! Operate as a selective forwarding unit. Video from each participant is
+	 * cloned to a dedicated stream on a subset of the remaining participants.
+	 */
+	AST_BRIDGE_VIDEO_MODE_SFU,
 };
 
 /*! \brief This is used for both SINGLE_SRC mode to set what channel
@@ -267,6 +271,8 @@
 	unsigned int binaural_active;
 };
 
+AST_LIST_HEAD_NOLOCK(ast_bridge_channels_list, ast_bridge_channel);
+
 /*!
  * \brief Structure that contains information about a bridge
  */
@@ -284,7 +290,7 @@
 	/*! Call ID associated with the bridge */
 	ast_callid callid;
 	/*! Linked list of channels participating in the bridge */
-	AST_LIST_HEAD_NOLOCK(, ast_bridge_channel) channels;
+	struct ast_bridge_channels_list channels;
 	/*! Queue of actions to perform on the bridge. */
 	AST_LIST_HEAD_NOLOCK(, ast_frame) action_queue;
 	/*! Softmix technology parameters. */
diff --git a/include/asterisk/stream.h b/include/asterisk/stream.h
index b453ab9..fcee3e4 100644
--- a/include/asterisk/stream.h
+++ b/include/asterisk/stream.h
@@ -121,6 +121,7 @@
  * \brief Create a deep clone of an existing stream
  *
  * \param stream The existing stream
+ * \param Optional name for cloned stream. If NULL, then existing stream's name is copied.
  *
  * \retval non-NULL success
  * \retval NULL failure
@@ -130,7 +131,7 @@
  *
  * \since 15
  */
-struct ast_stream *ast_stream_clone(const struct ast_stream *stream);
+struct ast_stream *ast_stream_clone(const struct ast_stream *stream, const char *name);
 
 /*!
  * \brief Get the name of a stream
diff --git a/main/bridge.c b/main/bridge.c
index 9d9a311..7313908 100644
--- a/main/bridge.c
+++ b/main/bridge.c
@@ -3778,6 +3778,8 @@
 		if (bridge->softmix.video_mode.mode_data.talker_src_data.chan_old_vsrc) {
 			ast_channel_unref(bridge->softmix.video_mode.mode_data.talker_src_data.chan_old_vsrc);
 		}
+	case AST_BRIDGE_VIDEO_MODE_SFU:
+		break;
 	}
 	memset(&bridge->softmix.video_mode, 0, sizeof(bridge->softmix.video_mode));
 }
@@ -3873,6 +3875,8 @@
 		if (bridge->softmix.video_mode.mode_data.talker_src_data.chan_old_vsrc) {
 			res++;
 		}
+	case AST_BRIDGE_VIDEO_MODE_SFU:
+		break;
 	}
 	ast_bridge_unlock(bridge);
 	return res;
@@ -3897,7 +3901,8 @@
 		} else if (bridge->softmix.video_mode.mode_data.talker_src_data.chan_old_vsrc == chan) {
 			res = 2;
 		}
-
+	case AST_BRIDGE_VIDEO_MODE_SFU:
+		break;
 	}
 	ast_bridge_unlock(bridge);
 	return res;
@@ -3931,6 +3936,8 @@
 			}
 			bridge->softmix.video_mode.mode_data.talker_src_data.chan_old_vsrc = NULL;
 		}
+	case AST_BRIDGE_VIDEO_MODE_SFU:
+		break;
 	}
 	ast_bridge_unlock(bridge);
 }
diff --git a/main/bridge_channel.c b/main/bridge_channel.c
index 4f166ff..ed3da5d 100644
--- a/main/bridge_channel.c
+++ b/main/bridge_channel.c
@@ -989,6 +989,11 @@
 		/* Media frames need to be mapped to an appropriate write stream */
 		dup->stream_num = AST_VECTOR_GET(
 			&bridge_channel->stream_map.to_bridge, fr->stream_num);
+		if (dup->stream_num == -1) {
+			ast_bridge_channel_unlock(bridge_channel);
+			bridge_frame_free(dup);
+			return 0;
+		}
 	} else {
 		dup->stream_num = -1;
 	}
@@ -2339,7 +2344,8 @@
 	case AST_FRAME_NULL:
 		break;
 	default:
-		if (fr->stream_num >= (int)AST_VECTOR_SIZE(&bridge_channel->stream_map.to_channel)) {
+		if (fr->stream_num >= (int)AST_VECTOR_SIZE(&bridge_channel->stream_map.to_channel) ||
+				AST_VECTOR_GET(&bridge_channel->stream_map.to_channel, fr->stream_num) == -1) {
 			/* Nowhere to write to, so drop it */
 			break;
 		}
@@ -2473,8 +2479,6 @@
 			 * If a stream topology has changed then the bridge_channel's
 			 * media mapping needs to be updated.
 			 */
-			ast_bridge_channel_stream_map(bridge_channel);
-
 			if (bridge_channel->bridge->technology->stream_topology_changed) {
 				bridge_channel->bridge->technology->stream_topology_changed(
 					bridge_channel->bridge, bridge_channel);
diff --git a/main/sdp_state.c b/main/sdp_state.c
index 9b116ca..05ffdc0 100644
--- a/main/sdp_state.c
+++ b/main/sdp_state.c
@@ -800,7 +800,7 @@
 			/* We don't have a stream state that corresponds to the stream in the new topology, so
 			 * create a stream state as appropriate.
 			 */
-			joint_stream = ast_stream_clone(new_stream);
+			joint_stream = ast_stream_clone(new_stream, NULL);
 			if (!joint_stream) {
 				sdp_state_stream_free(joint_state_stream);
 				goto fail;
diff --git a/main/stream.c b/main/stream.c
index 804a0b8..fb14693 100644
--- a/main/stream.c
+++ b/main/stream.c
@@ -95,23 +95,26 @@
 	return stream;
 }
 
-struct ast_stream *ast_stream_clone(const struct ast_stream *stream)
+struct ast_stream *ast_stream_clone(const struct ast_stream *stream, const char *name)
 {
 	struct ast_stream *new_stream;
 	size_t stream_size;
 	int idx;
+	const char *stream_name;
 
 	if (!stream) {
 		return NULL;
 	}
 
-	stream_size = sizeof(*stream) + strlen(stream->name) + 1;
+	stream_name = name ?: stream->name;
+	stream_size = sizeof(*stream) + strlen(stream_name) + 1;
 	new_stream = ast_calloc(1, stream_size);
 	if (!new_stream) {
 		return NULL;
 	}
 
-	memcpy(new_stream, stream, stream_size);
+	memcpy(new_stream, stream, sizeof(*new_stream));
+	strcpy(new_stream->name, stream_name); /* Safe */
 	if (new_stream->formats) {
 		ao2_ref(new_stream->formats, +1);
 	}
@@ -269,7 +272,7 @@
 
 	for (i = 0; i < AST_VECTOR_SIZE(&topology->streams); i++) {
 		struct ast_stream *stream =
-			ast_stream_clone(AST_VECTOR_GET(&topology->streams, i));
+			ast_stream_clone(AST_VECTOR_GET(&topology->streams, i), NULL);
 
 		if (!stream || AST_VECTOR_APPEND(&new_topology->streams, stream)) {
 			ast_stream_free(stream);

-- 
To view, visit https://gerrit.asterisk.org/5643
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I7c44a829cc63acf8b596a337b2dc3c13898a6c4d
Gerrit-PatchSet: 3
Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Owner: Mark Michelson <mmichelson at digium.com>



More information about the asterisk-code-review mailing list