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 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 *req_chan; - struct ast_channel *existing_chan; 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 (bridge_channel) { - ast_bridge_channel_stream_map(bridge_channel); + /* + * 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_get_stream_topology_change_source(bridge_channel->chan) - == &simple_bridge) { - return; + 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; } - 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. */ + return defer; +} + +static void simple_bridge_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) + == &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) {