From 5a5be92b792a2ba81cb8258947f28371bd767bf5 Mon Sep 17 00:00:00 2001 From: "Joshua C. Colp" Date: Sun, 5 Jan 2020 00:11:20 +0000 Subject: [PATCH] bridging: Add better support for adding/removing streams. This change adds support to bridge_softmix to allow the addition and removal of additional video source streams. When such a change occurs each participant is renegotiated as needed to reflect the update. If another video source is added then each participant gets another source. If a video source is removed then it is removed from each participant. This functionality allows you to have both your webcam and screenshare providing video if you desire, or even more streams. Mapping has been changed to use the topology index on the source channel as a unique identifier for outgoing participant streams, this will never change and provides an easy way to establish the mapping. The bridge_simple and bridge_native_rtp modules have also been updated to renegotiate when the stream topology of a party changes allowing the same behavior to occur as added to bridge_softmix. If a screen share is added then the opposite party is renegotiated. If that screen share is removed then the opposite party is renegotiated again. Some additional fixes are also included in here. Stream state is now conveyed in SDP so sendonly/recvonly/inactive streams can be requested. Removed streams now also remove previous state from themselves so consumers don't get confused. ASTERISK-28733 Change-Id: I93f41fb41b85646bef71408111c17ccea30cb0c5 --- bridges/bridge_native_rtp.c | 168 ++++++++++++++++++++++-- bridges/bridge_simple.c | 200 +++++++++++++++++------------ bridges/bridge_softmix.c | 246 ++++++++++++++++++++++++++++-------- include/asterisk/channel.h | 20 +++ main/channel.c | 19 +++ main/stream.c | 22 +++- res/res_pjsip_sdp_rtp.c | 6 + res/res_pjsip_session.c | 34 ++++- 8 files changed, 566 insertions(+), 149 deletions(-) diff --git a/bridges/bridge_native_rtp.c b/bridges/bridge_native_rtp.c index 7fd4ae179e..a6addf2711 100644 --- a/bridges/bridge_native_rtp.c +++ b/bridges/bridge_native_rtp.c @@ -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); diff --git a/bridges/bridge_simple.c b/bridges/bridge_simple.c index 40f7ddc7e5..545b3ad1cb 100644 --- a/bridges/bridge_simple.c +++ b/bridges/bridge_simple.c @@ -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); } diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c index c24fa7a27f..e1c6734966 100644 --- a/bridges/bridge_softmix.c +++ b/bridges/bridge_softmix.c @@ -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; diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 3d8b70a8ce..cc90c8304a 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -215,6 +215,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 { @@ -5025,6 +5031,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 * diff --git a/main/channel.c b/main/channel.c index c350177dbc..694f2396c5 100644 --- a/main/channel.c +++ b/main/channel.c @@ -11028,6 +11028,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); diff --git a/main/stream.c b/main/stream.c index 47415bffff..626fa3a9ca 100644 --- a/main/stream.c +++ b/main/stream.c @@ -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) diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c index dcdb827ffe..d652fc4e3a 100644 --- a/res/res_pjsip_sdp_rtp.c +++ b/res/res_pjsip_sdp_rtp.c @@ -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 */ } diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c index 0eafb9c364..c1e89c5583 100644 --- a/res/res_pjsip_session.c +++ b/res/res_pjsip_session.c @@ -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) {