<p>George Joseph <strong>submitted</strong> this change.</p><p><a href="https://gerrit.asterisk.org/c/asterisk/+/13850">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  George Joseph: Looks good to me, approved; Approved for Submit

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">bridging: Add better support for adding/removing streams.<br><br>This change adds support to bridge_softmix to allow the addition<br>and removal of additional video source streams. When such a change<br>occurs each participant is renegotiated as needed to reflect the<br>update. If another video source is added then each participant<br>gets another source. If a video source is removed then it is<br>removed from each participant. This functionality allows you to<br>have both your webcam and screenshare providing video if you<br>desire, or even more streams. Mapping has been changed to use<br>the topology index on the source channel as a unique identifier<br>for outgoing participant streams, this will never change and<br>provides an easy way to establish the mapping.<br><br>The bridge_simple and bridge_native_rtp modules have also been<br>updated to renegotiate when the stream topology of a party changes<br>allowing the same behavior to occur as added to bridge_softmix.<br>If a screen share is added then the opposite party is renegotiated.<br>If that screen share is removed then the opposite party is<br>renegotiated again.<br><br>Some additional fixes are also included in here. Stream state is<br>now conveyed in SDP so sendonly/recvonly/inactive streams can<br>be requested. Removed streams now also remove previous state<br>from themselves so consumers don't get confused.<br><br>ASTERISK-28733<br><br>Change-Id: I93f41fb41b85646bef71408111c17ccea30cb0c5<br>---<br>M bridges/bridge_native_rtp.c<br>M bridges/bridge_simple.c<br>M bridges/bridge_softmix.c<br>M include/asterisk/channel.h<br>M main/channel.c<br>M main/stream.c<br>M res/res_pjsip_sdp_rtp.c<br>M res/res_pjsip_session.c<br>8 files changed, 571 insertions(+), 150 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/bridges/bridge_native_rtp.c b/bridges/bridge_native_rtp.c</span><br><span>index 7fd4ae1..a6addf2 100644</span><br><span>--- a/bridges/bridge_native_rtp.c</span><br><span>+++ b/bridges/bridge_native_rtp.c</span><br><span>@@ -43,6 +43,7 @@</span><br><span> #include "asterisk/bridge_technology.h"</span><br><span> #include "asterisk/frame.h"</span><br><span> #include "asterisk/rtp_engine.h"</span><br><span style="color: hsl(120, 100%, 40%);">+#include "asterisk/stream.h"</span><br><span> </span><br><span> /*! \brief Internal structure which contains bridged RTP channel hook data */</span><br><span> struct native_rtp_framehook_data {</span><br><span>@@ -85,6 +86,28 @@</span><br><span>       struct rtp_glue_data glue;</span><br><span> };</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/*! \brief Forward declarations */</span><br><span style="color: hsl(120, 100%, 40%);">+static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);</span><br><span style="color: hsl(120, 100%, 40%);">+static void native_rtp_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);</span><br><span style="color: hsl(120, 100%, 40%);">+static void native_rtp_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);</span><br><span style="color: hsl(120, 100%, 40%);">+static void native_rtp_bridge_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);</span><br><span style="color: hsl(120, 100%, 40%);">+static int native_rtp_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame);</span><br><span style="color: hsl(120, 100%, 40%);">+static int native_rtp_bridge_compatible(struct ast_bridge *bridge);</span><br><span style="color: hsl(120, 100%, 40%);">+static void native_rtp_stream_topology_changed(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static struct ast_bridge_technology native_rtp_bridge = {</span><br><span style="color: hsl(120, 100%, 40%);">+     .name = "native_rtp",</span><br><span style="color: hsl(120, 100%, 40%);">+       .capabilities = AST_BRIDGE_CAPABILITY_NATIVE,</span><br><span style="color: hsl(120, 100%, 40%);">+ .preference = AST_BRIDGE_PREFERENCE_BASE_NATIVE,</span><br><span style="color: hsl(120, 100%, 40%);">+      .join = native_rtp_bridge_join,</span><br><span style="color: hsl(120, 100%, 40%);">+       .unsuspend = native_rtp_bridge_unsuspend,</span><br><span style="color: hsl(120, 100%, 40%);">+     .leave = native_rtp_bridge_leave,</span><br><span style="color: hsl(120, 100%, 40%);">+     .suspend = native_rtp_bridge_suspend,</span><br><span style="color: hsl(120, 100%, 40%);">+ .write = native_rtp_bridge_write,</span><br><span style="color: hsl(120, 100%, 40%);">+     .compatible = native_rtp_bridge_compatible,</span><br><span style="color: hsl(120, 100%, 40%);">+   .stream_topology_changed = native_rtp_stream_topology_changed,</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> static void rtp_glue_data_init(struct rtp_glue_data *glue)</span><br><span> {</span><br><span>      glue->cb = NULL;</span><br><span>@@ -831,12 +854,124 @@</span><br><span>         data->hook_data = NULL;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+static struct ast_stream_topology *native_rtp_request_stream_topology_update(</span><br><span style="color: hsl(120, 100%, 40%);">+    struct ast_stream_topology *existing_topology,</span><br><span style="color: hsl(120, 100%, 40%);">+        struct ast_stream_topology *requested_topology)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+    struct ast_stream *stream;</span><br><span style="color: hsl(120, 100%, 40%);">+    struct ast_format_cap *audio_formats = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  struct ast_stream_topology *new_topology;</span><br><span style="color: hsl(120, 100%, 40%);">+     int i;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      new_topology = ast_stream_topology_clone(requested_topology);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!new_topology) {</span><br><span style="color: hsl(120, 100%, 40%);">+          return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* We find an existing stream with negotiated audio formats that we can place into</span><br><span style="color: hsl(120, 100%, 40%);">+     * any audio streams in the new topology to ensure that negotiation succeeds. Some</span><br><span style="color: hsl(120, 100%, 40%);">+     * endpoints incorrectly terminate the call if SDP negotiation fails.</span><br><span style="color: hsl(120, 100%, 40%);">+  */</span><br><span style="color: hsl(120, 100%, 40%);">+   for (i = 0; i < ast_stream_topology_get_count(existing_topology); ++i) {</span><br><span style="color: hsl(120, 100%, 40%);">+           stream = ast_stream_topology_get_stream(existing_topology, i);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+              if (ast_stream_get_type(stream) != AST_MEDIA_TYPE_AUDIO ||</span><br><span style="color: hsl(120, 100%, 40%);">+                    ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {</span><br><span style="color: hsl(120, 100%, 40%);">+                   continue;</span><br><span style="color: hsl(120, 100%, 40%);">+             }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           audio_formats = ast_stream_get_formats(stream);</span><br><span style="color: hsl(120, 100%, 40%);">+               break;</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (audio_formats) {</span><br><span style="color: hsl(120, 100%, 40%);">+          for (i = 0; i < ast_stream_topology_get_count(new_topology); ++i) {</span><br><span style="color: hsl(120, 100%, 40%);">+                        stream = ast_stream_topology_get_stream(new_topology, i);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                   if (ast_stream_get_type(stream) != AST_MEDIA_TYPE_AUDIO ||</span><br><span style="color: hsl(120, 100%, 40%);">+                            ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {</span><br><span style="color: hsl(120, 100%, 40%);">+                           continue;</span><br><span style="color: hsl(120, 100%, 40%);">+                     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                   ast_format_cap_append_from_cap(ast_stream_get_formats(stream), audio_formats,</span><br><span style="color: hsl(120, 100%, 40%);">+                         AST_MEDIA_TYPE_AUDIO);</span><br><span style="color: hsl(120, 100%, 40%);">+                }</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   for (i = 0; i < ast_stream_topology_get_count(new_topology); ++i) {</span><br><span style="color: hsl(120, 100%, 40%);">+                stream = ast_stream_topology_get_stream(new_topology, i);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           /* For both recvonly and sendonly the stream state reflects our state, that is we</span><br><span style="color: hsl(120, 100%, 40%);">+              * are receiving only and we are sending only. Since we are renegotiating a remote</span><br><span style="color: hsl(120, 100%, 40%);">+             * party we need to swap this to reflect what we will be doing. That is, if we are</span><br><span style="color: hsl(120, 100%, 40%);">+             * receiving from Alice then we want to be sending to Bob, so swap recvonly to</span><br><span style="color: hsl(120, 100%, 40%);">+                 * sendonly.</span><br><span style="color: hsl(120, 100%, 40%);">+           */</span><br><span style="color: hsl(120, 100%, 40%);">+           if (ast_stream_get_state(stream) == AST_STREAM_STATE_RECVONLY) {</span><br><span style="color: hsl(120, 100%, 40%);">+                      ast_stream_set_state(stream, AST_STREAM_STATE_SENDONLY);</span><br><span style="color: hsl(120, 100%, 40%);">+              } else if (ast_stream_get_state(stream) == AST_STREAM_STATE_SENDONLY) {</span><br><span style="color: hsl(120, 100%, 40%);">+                       ast_stream_set_state(stream, AST_STREAM_STATE_RECVONLY);</span><br><span style="color: hsl(120, 100%, 40%);">+              }</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   return new_topology;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static void native_rtp_stream_topology_changed(struct ast_bridge *bridge,</span><br><span style="color: hsl(120, 100%, 40%);">+          struct ast_bridge_channel *bridge_channel)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_channel *c0 = bridge_channel->chan;</span><br><span style="color: hsl(120, 100%, 40%);">+     struct ast_channel *c1 = AST_LIST_FIRST(&bridge->channels)->chan;</span><br><span style="color: hsl(120, 100%, 40%);">+   struct ast_stream_topology *req_top;</span><br><span style="color: hsl(120, 100%, 40%);">+  struct ast_stream_topology *existing_top;</span><br><span style="color: hsl(120, 100%, 40%);">+     struct ast_stream_topology *new_top;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        ast_bridge_channel_stream_map(bridge_channel);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      if (ast_channel_get_stream_topology_change_source(bridge_channel->chan)</span><br><span style="color: hsl(120, 100%, 40%);">+            == &native_rtp_bridge) {</span><br><span style="color: hsl(120, 100%, 40%);">+          return;</span><br><span style="color: hsl(120, 100%, 40%);">+       }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (c0 == c1) {</span><br><span style="color: hsl(120, 100%, 40%);">+               c1 = AST_LIST_LAST(&bridge->channels)->chan;</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (c0 == c1) {</span><br><span style="color: hsl(120, 100%, 40%);">+               return;</span><br><span style="color: hsl(120, 100%, 40%);">+       }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* If a party renegotiates we want to renegotiate their counterpart to a matching</span><br><span style="color: hsl(120, 100%, 40%);">+      * topology.</span><br><span style="color: hsl(120, 100%, 40%);">+   */</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_channel_lock_both(c0, c1);</span><br><span style="color: hsl(120, 100%, 40%);">+        req_top = ast_channel_get_stream_topology(c0);</span><br><span style="color: hsl(120, 100%, 40%);">+        existing_top = ast_channel_get_stream_topology(c1);</span><br><span style="color: hsl(120, 100%, 40%);">+   new_top = native_rtp_request_stream_topology_update(existing_top, req_top);</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_channel_unlock(c0);</span><br><span style="color: hsl(120, 100%, 40%);">+       ast_channel_unlock(c1);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     if (!new_top) {</span><br><span style="color: hsl(120, 100%, 40%);">+               /* Failure.  We'll just have to live with the current topology. */</span><br><span style="color: hsl(120, 100%, 40%);">+                return;</span><br><span style="color: hsl(120, 100%, 40%);">+       }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_channel_request_stream_topology_change(c1, new_top, &native_rtp_bridge);</span><br><span style="color: hsl(120, 100%, 40%);">+      ast_stream_topology_free(new_top);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /*!</span><br><span>  * \internal</span><br><span>  * \brief Called by the bridge core 'join' callback for each channel joining he bridge</span><br><span>  */</span><br><span> static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)</span><br><span> {</span><br><span style="color: hsl(120, 100%, 40%);">+       struct ast_stream_topology *req_top;</span><br><span style="color: hsl(120, 100%, 40%);">+  struct ast_stream_topology *existing_top;</span><br><span style="color: hsl(120, 100%, 40%);">+     struct ast_stream_topology *new_top;</span><br><span style="color: hsl(120, 100%, 40%);">+  struct ast_channel *c0 = AST_LIST_FIRST(&bridge->channels)->chan;</span><br><span style="color: hsl(120, 100%, 40%);">+   struct ast_channel *c1 = AST_LIST_LAST(&bridge->channels)->chan;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>         ast_debug(2, "Bridge '%s'.  Channel '%s' is joining bridge tech\n",</span><br><span>                bridge->uniqueid, ast_channel_name(bridge_channel->chan));</span><br><span> </span><br><span>@@ -858,6 +993,27 @@</span><br><span>          return -1;</span><br><span>   }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ if (c0 != c1) {</span><br><span style="color: hsl(120, 100%, 40%);">+               /* When both channels are joined we want to try to improve the experience by</span><br><span style="color: hsl(120, 100%, 40%);">+           * raising the number of streams so they match.</span><br><span style="color: hsl(120, 100%, 40%);">+                */</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_channel_lock_both(c0, c1);</span><br><span style="color: hsl(120, 100%, 40%);">+                req_top = ast_channel_get_stream_topology(c0);</span><br><span style="color: hsl(120, 100%, 40%);">+                existing_top = ast_channel_get_stream_topology(c1);</span><br><span style="color: hsl(120, 100%, 40%);">+           if (ast_stream_topology_get_count(req_top) < ast_stream_topology_get_count(existing_top)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                        SWAP(req_top, existing_top);</span><br><span style="color: hsl(120, 100%, 40%);">+                  SWAP(c0, c1);</span><br><span style="color: hsl(120, 100%, 40%);">+         }</span><br><span style="color: hsl(120, 100%, 40%);">+             new_top = native_rtp_request_stream_topology_update(existing_top, req_top);</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_channel_unlock(c0);</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_channel_unlock(c1);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+             if (new_top) {</span><br><span style="color: hsl(120, 100%, 40%);">+                        ast_channel_request_stream_topology_change(c1, new_top, &native_rtp_bridge);</span><br><span style="color: hsl(120, 100%, 40%);">+                      ast_stream_topology_free(new_top);</span><br><span style="color: hsl(120, 100%, 40%);">+            }</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>  native_rtp_bridge_start(bridge, NULL);</span><br><span>       return 0;</span><br><span> }</span><br><span>@@ -939,18 +1095,6 @@</span><br><span>       return defer;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-static struct ast_bridge_technology native_rtp_bridge = {</span><br><span style="color: hsl(0, 100%, 40%);">- .name = "native_rtp",</span><br><span style="color: hsl(0, 100%, 40%);">- .capabilities = AST_BRIDGE_CAPABILITY_NATIVE,</span><br><span style="color: hsl(0, 100%, 40%);">-   .preference = AST_BRIDGE_PREFERENCE_BASE_NATIVE,</span><br><span style="color: hsl(0, 100%, 40%);">-        .join = native_rtp_bridge_join,</span><br><span style="color: hsl(0, 100%, 40%);">- .unsuspend = native_rtp_bridge_unsuspend,</span><br><span style="color: hsl(0, 100%, 40%);">-       .leave = native_rtp_bridge_leave,</span><br><span style="color: hsl(0, 100%, 40%);">-       .suspend = native_rtp_bridge_suspend,</span><br><span style="color: hsl(0, 100%, 40%);">-   .write = native_rtp_bridge_write,</span><br><span style="color: hsl(0, 100%, 40%);">-       .compatible = native_rtp_bridge_compatible,</span><br><span style="color: hsl(0, 100%, 40%);">-};</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span> static int unload_module(void)</span><br><span> {</span><br><span>         ast_bridge_technology_unregister(&native_rtp_bridge);</span><br><span>diff --git a/bridges/bridge_simple.c b/bridges/bridge_simple.c</span><br><span>index 40f7ddc..545b3ad 100644</span><br><span>--- a/bridges/bridge_simple.c</span><br><span>+++ b/bridges/bridge_simple.c</span><br><span>@@ -46,63 +46,8 @@</span><br><span> </span><br><span> static void simple_bridge_stream_topology_changed(struct ast_bridge *bridge,</span><br><span>          struct ast_bridge_channel *bridge_channel);</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-static int simple_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)</span><br><span style="color: hsl(0, 100%, 40%);">-{</span><br><span style="color: hsl(0, 100%, 40%);">- struct ast_channel *c0 = AST_LIST_FIRST(&bridge->channels)->chan;</span><br><span style="color: hsl(0, 100%, 40%);">-     struct ast_channel *c1 = AST_LIST_LAST(&bridge->channels)->chan;</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-      /*</span><br><span style="color: hsl(0, 100%, 40%);">-       * If this is the first channel we can't make it compatible...</span><br><span style="color: hsl(0, 100%, 40%);">-       * unless we make it compatible with itself.  O.o</span><br><span style="color: hsl(0, 100%, 40%);">-        */</span><br><span style="color: hsl(0, 100%, 40%);">-     if (c0 == c1) {</span><br><span style="color: hsl(0, 100%, 40%);">-         return 0;</span><br><span style="color: hsl(0, 100%, 40%);">-       }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-       if (ast_channel_make_compatible(c0, c1)) {</span><br><span style="color: hsl(0, 100%, 40%);">-              return -1;</span><br><span style="color: hsl(0, 100%, 40%);">-      }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-       /* Align stream topologies */</span><br><span style="color: hsl(0, 100%, 40%);">-   simple_bridge_stream_topology_changed(bridge, NULL);</span><br><span style="color: hsl(0, 100%, 40%);">-    return 0;</span><br><span style="color: hsl(0, 100%, 40%);">-}</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-static int simple_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)</span><br><span style="color: hsl(0, 100%, 40%);">-{</span><br><span style="color: hsl(0, 100%, 40%);">-        const struct ast_control_t38_parameters *t38_parameters;</span><br><span style="color: hsl(0, 100%, 40%);">-        int defer = 0;</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-  if (!ast_bridge_queue_everyone_else(bridge, bridge_channel, frame)) {</span><br><span style="color: hsl(0, 100%, 40%);">-           /* This frame was successfully queued so no need to defer */</span><br><span style="color: hsl(0, 100%, 40%);">-            return 0;</span><br><span style="color: hsl(0, 100%, 40%);">-       }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-       /* Depending on the frame defer it so when the next channel joins it receives it */</span><br><span style="color: hsl(0, 100%, 40%);">-     switch (frame->frametype) {</span><br><span style="color: hsl(0, 100%, 40%);">-  case AST_FRAME_CONTROL:</span><br><span style="color: hsl(0, 100%, 40%);">-         switch (frame->subclass.integer) {</span><br><span style="color: hsl(0, 100%, 40%);">-           case AST_CONTROL_T38_PARAMETERS:</span><br><span style="color: hsl(0, 100%, 40%);">-                        t38_parameters = frame->data.ptr;</span><br><span style="color: hsl(0, 100%, 40%);">-                    switch (t38_parameters->request_response) {</span><br><span style="color: hsl(0, 100%, 40%);">-                  case AST_T38_REQUEST_NEGOTIATE:</span><br><span style="color: hsl(0, 100%, 40%);">-                         defer = -1;</span><br><span style="color: hsl(0, 100%, 40%);">-                             break;</span><br><span style="color: hsl(0, 100%, 40%);">-                  default:</span><br><span style="color: hsl(0, 100%, 40%);">-                                break;</span><br><span style="color: hsl(0, 100%, 40%);">-                  }</span><br><span style="color: hsl(0, 100%, 40%);">-                       break;</span><br><span style="color: hsl(0, 100%, 40%);">-          default:</span><br><span style="color: hsl(0, 100%, 40%);">-                        break;</span><br><span style="color: hsl(0, 100%, 40%);">-          }</span><br><span style="color: hsl(0, 100%, 40%);">-               break;</span><br><span style="color: hsl(0, 100%, 40%);">-  default:</span><br><span style="color: hsl(0, 100%, 40%);">-                break;</span><br><span style="color: hsl(0, 100%, 40%);">-  }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-       return defer;</span><br><span style="color: hsl(0, 100%, 40%);">-}</span><br><span style="color: hsl(120, 100%, 40%);">+static int simple_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);</span><br><span style="color: hsl(120, 100%, 40%);">+static int simple_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame);</span><br><span> </span><br><span> static struct ast_bridge_technology simple_bridge = {</span><br><span>    .name = "simple_bridge",</span><br><span>@@ -157,52 +102,145 @@</span><br><span>          }</span><br><span>    }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ for (i = 0; i < ast_stream_topology_get_count(new_topology); ++i) {</span><br><span style="color: hsl(120, 100%, 40%);">+                stream = ast_stream_topology_get_stream(new_topology, i);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           /* For both recvonly and sendonly the stream state reflects our state, that is we</span><br><span style="color: hsl(120, 100%, 40%);">+              * are receiving only and we are sending only. Since we are renegotiating a remote</span><br><span style="color: hsl(120, 100%, 40%);">+             * party we need to swap this to reflect what we will be doing. That is, if we are</span><br><span style="color: hsl(120, 100%, 40%);">+             * receiving from Alice then we want to be sending to Bob, so swap recvonly to</span><br><span style="color: hsl(120, 100%, 40%);">+                 * sendonly.</span><br><span style="color: hsl(120, 100%, 40%);">+           */</span><br><span style="color: hsl(120, 100%, 40%);">+           if (ast_stream_get_state(stream) == AST_STREAM_STATE_RECVONLY) {</span><br><span style="color: hsl(120, 100%, 40%);">+                      ast_stream_set_state(stream, AST_STREAM_STATE_SENDONLY);</span><br><span style="color: hsl(120, 100%, 40%);">+              } else if (ast_stream_get_state(stream) == AST_STREAM_STATE_SENDONLY) {</span><br><span style="color: hsl(120, 100%, 40%);">+                       ast_stream_set_state(stream, AST_STREAM_STATE_RECVONLY);</span><br><span style="color: hsl(120, 100%, 40%);">+              }</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>  return new_topology;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+static int simple_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_stream_topology *req_top;</span><br><span style="color: hsl(120, 100%, 40%);">+  struct ast_stream_topology *existing_top;</span><br><span style="color: hsl(120, 100%, 40%);">+     struct ast_stream_topology *new_top;</span><br><span style="color: hsl(120, 100%, 40%);">+  struct ast_channel *c0 = AST_LIST_FIRST(&bridge->channels)->chan;</span><br><span style="color: hsl(120, 100%, 40%);">+   struct ast_channel *c1 = AST_LIST_LAST(&bridge->channels)->chan;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  /*</span><br><span style="color: hsl(120, 100%, 40%);">+     * If this is the first channel we can't make it compatible...</span><br><span style="color: hsl(120, 100%, 40%);">+     * unless we make it compatible with itself.  O.o</span><br><span style="color: hsl(120, 100%, 40%);">+      */</span><br><span style="color: hsl(120, 100%, 40%);">+   if (c0 == c1) {</span><br><span style="color: hsl(120, 100%, 40%);">+               return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (ast_channel_make_compatible(c0, c1)) {</span><br><span style="color: hsl(120, 100%, 40%);">+            return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* When both channels are joined we want to try to improve the experience by</span><br><span style="color: hsl(120, 100%, 40%);">+   * raising the number of streams so they match.</span><br><span style="color: hsl(120, 100%, 40%);">+        */</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_channel_lock_both(c0, c1);</span><br><span style="color: hsl(120, 100%, 40%);">+        req_top = ast_channel_get_stream_topology(c0);</span><br><span style="color: hsl(120, 100%, 40%);">+        existing_top = ast_channel_get_stream_topology(c1);</span><br><span style="color: hsl(120, 100%, 40%);">+   if (ast_stream_topology_get_count(req_top) < ast_stream_topology_get_count(existing_top)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                SWAP(req_top, existing_top);</span><br><span style="color: hsl(120, 100%, 40%);">+          SWAP(c0, c1);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+     new_top = simple_bridge_request_stream_topology_update(existing_top, req_top);</span><br><span style="color: hsl(120, 100%, 40%);">+        ast_channel_unlock(c0);</span><br><span style="color: hsl(120, 100%, 40%);">+       ast_channel_unlock(c1);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     if (!new_top) {</span><br><span style="color: hsl(120, 100%, 40%);">+               /* Failure.  We'll just have to live with the current topology. */</span><br><span style="color: hsl(120, 100%, 40%);">+                return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_channel_request_stream_topology_change(c1, new_top, &simple_bridge);</span><br><span style="color: hsl(120, 100%, 40%);">+  ast_stream_topology_free(new_top);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static int simple_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+      const struct ast_control_t38_parameters *t38_parameters;</span><br><span style="color: hsl(120, 100%, 40%);">+      int defer = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      if (!ast_bridge_queue_everyone_else(bridge, bridge_channel, frame)) {</span><br><span style="color: hsl(120, 100%, 40%);">+         /* This frame was successfully queued so no need to defer */</span><br><span style="color: hsl(120, 100%, 40%);">+          return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* Depending on the frame defer it so when the next channel joins it receives it */</span><br><span style="color: hsl(120, 100%, 40%);">+   switch (frame->frametype) {</span><br><span style="color: hsl(120, 100%, 40%);">+        case AST_FRAME_CONTROL:</span><br><span style="color: hsl(120, 100%, 40%);">+               switch (frame->subclass.integer) {</span><br><span style="color: hsl(120, 100%, 40%);">+         case AST_CONTROL_T38_PARAMETERS:</span><br><span style="color: hsl(120, 100%, 40%);">+                      t38_parameters = frame->data.ptr;</span><br><span style="color: hsl(120, 100%, 40%);">+                  switch (t38_parameters->request_response) {</span><br><span style="color: hsl(120, 100%, 40%);">+                        case AST_T38_REQUEST_NEGOTIATE:</span><br><span style="color: hsl(120, 100%, 40%);">+                               defer = -1;</span><br><span style="color: hsl(120, 100%, 40%);">+                           break;</span><br><span style="color: hsl(120, 100%, 40%);">+                        default:</span><br><span style="color: hsl(120, 100%, 40%);">+                              break;</span><br><span style="color: hsl(120, 100%, 40%);">+                        }</span><br><span style="color: hsl(120, 100%, 40%);">+                     break;</span><br><span style="color: hsl(120, 100%, 40%);">+                default:</span><br><span style="color: hsl(120, 100%, 40%);">+                      break;</span><br><span style="color: hsl(120, 100%, 40%);">+                }</span><br><span style="color: hsl(120, 100%, 40%);">+             break;</span><br><span style="color: hsl(120, 100%, 40%);">+        default:</span><br><span style="color: hsl(120, 100%, 40%);">+              break;</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   return defer;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> static void simple_bridge_stream_topology_changed(struct ast_bridge *bridge,</span><br><span>           struct ast_bridge_channel *bridge_channel)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">- struct ast_channel *req_chan;</span><br><span style="color: hsl(0, 100%, 40%);">-   struct ast_channel *existing_chan;</span><br><span style="color: hsl(120, 100%, 40%);">+    struct ast_channel *c0 = bridge_channel->chan;</span><br><span style="color: hsl(120, 100%, 40%);">+     struct ast_channel *c1 = AST_LIST_FIRST(&bridge->channels)->chan;</span><br><span>  struct ast_stream_topology *req_top;</span><br><span>         struct ast_stream_topology *existing_top;</span><br><span>    struct ast_stream_topology *new_top;</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-        if (bridge_channel) {</span><br><span style="color: hsl(0, 100%, 40%);">-           ast_bridge_channel_stream_map(bridge_channel);</span><br><span style="color: hsl(120, 100%, 40%);">+        ast_bridge_channel_stream_map(bridge_channel);</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-              if (ast_channel_get_stream_topology_change_source(bridge_channel->chan)</span><br><span style="color: hsl(0, 100%, 40%);">-                      == &simple_bridge) {</span><br><span style="color: hsl(0, 100%, 40%);">-                        return;</span><br><span style="color: hsl(0, 100%, 40%);">-         }</span><br><span style="color: hsl(0, 100%, 40%);">-       }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-       req_chan = AST_LIST_FIRST(&bridge->channels)->chan;</span><br><span style="color: hsl(0, 100%, 40%);">-   existing_chan = AST_LIST_LAST(&bridge->channels)->chan;</span><br><span style="color: hsl(0, 100%, 40%);">-       if (req_chan == existing_chan) {</span><br><span style="color: hsl(0, 100%, 40%);">-                /* Wait until both channels are in the bridge to align topologies. */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (ast_channel_get_stream_topology_change_source(bridge_channel->chan)</span><br><span style="color: hsl(120, 100%, 40%);">+            == &simple_bridge) {</span><br><span>             return;</span><br><span>      }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   /* Align topologies according to size or first channel to join */</span><br><span style="color: hsl(0, 100%, 40%);">-       ast_channel_lock_both(req_chan, existing_chan);</span><br><span style="color: hsl(0, 100%, 40%);">- req_top = ast_channel_get_stream_topology(req_chan);</span><br><span style="color: hsl(0, 100%, 40%);">-    existing_top = ast_channel_get_stream_topology(existing_chan);</span><br><span style="color: hsl(0, 100%, 40%);">-  if (ast_stream_topology_get_count(req_top) < ast_stream_topology_get_count(existing_top)) {</span><br><span style="color: hsl(0, 100%, 40%);">-          SWAP(req_top, existing_top);</span><br><span style="color: hsl(0, 100%, 40%);">-            SWAP(req_chan, existing_chan);</span><br><span style="color: hsl(120, 100%, 40%);">+        if (c0 == c1) {</span><br><span style="color: hsl(120, 100%, 40%);">+               c1 = AST_LIST_LAST(&bridge->channels)->chan;</span><br><span>       }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (c0 == c1) {</span><br><span style="color: hsl(120, 100%, 40%);">+               return;</span><br><span style="color: hsl(120, 100%, 40%);">+       }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* If a party renegotiates we want to renegotiate their counterpart to a matching</span><br><span style="color: hsl(120, 100%, 40%);">+      * topology.</span><br><span style="color: hsl(120, 100%, 40%);">+   */</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_channel_lock_both(c0, c1);</span><br><span style="color: hsl(120, 100%, 40%);">+        req_top = ast_channel_get_stream_topology(c0);</span><br><span style="color: hsl(120, 100%, 40%);">+        existing_top = ast_channel_get_stream_topology(c1);</span><br><span>  new_top = simple_bridge_request_stream_topology_update(existing_top, req_top);</span><br><span style="color: hsl(0, 100%, 40%);">-  ast_channel_unlock(req_chan);</span><br><span style="color: hsl(0, 100%, 40%);">-   ast_channel_unlock(existing_chan);</span><br><span style="color: hsl(120, 100%, 40%);">+    ast_channel_unlock(c0);</span><br><span style="color: hsl(120, 100%, 40%);">+       ast_channel_unlock(c1);</span><br><span> </span><br><span>  if (!new_top) {</span><br><span>              /* Failure.  We'll just have to live with the current topology. */</span><br><span>               return;</span><br><span>      }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   ast_channel_request_stream_topology_change(existing_chan, new_top, &simple_bridge);</span><br><span style="color: hsl(120, 100%, 40%);">+       ast_channel_request_stream_topology_change(c1, new_top, &simple_bridge);</span><br><span>         ast_stream_topology_free(new_top);</span><br><span> }</span><br><span> </span><br><span>diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c</span><br><span>index c24fa7a..e1c6734 100644</span><br><span>--- a/bridges/bridge_softmix.c</span><br><span>+++ b/bridges/bridge_softmix.c</span><br><span>@@ -462,12 +462,12 @@</span><br><span>  *</span><br><span>  * \param stream The stream to test</span><br><span>  * \param source_channel_name The name of a source video channel to match</span><br><span style="color: hsl(0, 100%, 40%);">- * \param source_stream_name The name of the source video stream to match</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param source_channel_stream_position The position of the video on the source channel</span><br><span>  * \retval 1 The stream is a video destination stream</span><br><span>  * \retval 0 The stream is not a video destination stream</span><br><span>  */</span><br><span> static int is_video_dest(const struct ast_stream *stream, const char *source_channel_name,</span><br><span style="color: hsl(0, 100%, 40%);">-        const char *source_stream_name)</span><br><span style="color: hsl(120, 100%, 40%);">+       int source_channel_stream_position)</span><br><span> {</span><br><span>     char *dest_video_name;</span><br><span>       size_t dest_video_name_len;</span><br><span>@@ -480,17 +480,17 @@</span><br><span>  dest_video_name_len = SOFTBRIDGE_VIDEO_DEST_LEN + 1;</span><br><span>         if (!ast_strlen_zero(source_channel_name)) {</span><br><span>                 dest_video_name_len += strlen(source_channel_name) + 1;</span><br><span style="color: hsl(0, 100%, 40%);">-         if (!ast_strlen_zero(source_stream_name)) {</span><br><span style="color: hsl(0, 100%, 40%);">-                     dest_video_name_len += strlen(source_stream_name) + 1;</span><br><span style="color: hsl(120, 100%, 40%);">+                if (source_channel_stream_position != -1) {</span><br><span style="color: hsl(120, 100%, 40%);">+                   dest_video_name_len += 11;</span><br><span>           }</span><br><span> </span><br><span>                dest_video_name = ast_alloca(dest_video_name_len);</span><br><span style="color: hsl(0, 100%, 40%);">-              if (!ast_strlen_zero(source_stream_name)) {</span><br><span style="color: hsl(0, 100%, 40%);">-                     /* We are looking for an exact stream name */</span><br><span style="color: hsl(0, 100%, 40%);">-                   snprintf(dest_video_name, dest_video_name_len, "%s%c%s%c%s",</span><br><span style="color: hsl(120, 100%, 40%);">+                if (source_channel_stream_position != -1) {</span><br><span style="color: hsl(120, 100%, 40%);">+                   /* We are looking for an exact stream position */</span><br><span style="color: hsl(120, 100%, 40%);">+                     snprintf(dest_video_name, dest_video_name_len, "%s%c%s%c%d",</span><br><span>                               SOFTBRIDGE_VIDEO_DEST_PREFIX, SOFTBRIDGE_VIDEO_DEST_SEPARATOR,</span><br><span>                               source_channel_name, SOFTBRIDGE_VIDEO_DEST_SEPARATOR,</span><br><span style="color: hsl(0, 100%, 40%);">-                           source_stream_name);</span><br><span style="color: hsl(120, 100%, 40%);">+                          source_channel_stream_position);</span><br><span>                     return !strcmp(ast_stream_get_name(stream), dest_video_name);</span><br><span>                }</span><br><span>            snprintf(dest_video_name, dest_video_name_len, "%s%c%s",</span><br><span>@@ -503,46 +503,62 @@</span><br><span>   return !strncmp(ast_stream_get_name(stream), dest_video_name, dest_video_name_len - 1);</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+static int append_source_stream(struct ast_stream_topology *dest,</span><br><span style="color: hsl(120, 100%, 40%);">+   const char *channel_name, const char *sdp_label,</span><br><span style="color: hsl(120, 100%, 40%);">+      struct ast_stream *stream, int index)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+      char *stream_clone_name = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+       struct ast_stream *stream_clone;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    /* We use the stream topology index for the stream to uniquely identify and recognize it.</span><br><span style="color: hsl(120, 100%, 40%);">+      * This is guaranteed to remain the same across renegotiation of the source channel and</span><br><span style="color: hsl(120, 100%, 40%);">+        * ensures that the stream name is unique.</span><br><span style="color: hsl(120, 100%, 40%);">+     */</span><br><span style="color: hsl(120, 100%, 40%);">+   if (ast_asprintf(&stream_clone_name, "%s%c%s%c%d", SOFTBRIDGE_VIDEO_DEST_PREFIX,</span><br><span style="color: hsl(120, 100%, 40%);">+                SOFTBRIDGE_VIDEO_DEST_SEPARATOR, channel_name, SOFTBRIDGE_VIDEO_DEST_SEPARATOR,</span><br><span style="color: hsl(120, 100%, 40%);">+               index) < 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+              return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   stream_clone = ast_stream_clone(stream, stream_clone_name);</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_free(stream_clone_name);</span><br><span style="color: hsl(120, 100%, 40%);">+  if (!stream_clone) {</span><br><span style="color: hsl(120, 100%, 40%);">+          return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* Sends an "a:label" attribute in the SDP for participant event correlation */</span><br><span style="color: hsl(120, 100%, 40%);">+     if (!ast_strlen_zero(sdp_label)) {</span><br><span style="color: hsl(120, 100%, 40%);">+            ast_stream_set_metadata(stream_clone, "SDP:LABEL", sdp_label);</span><br><span style="color: hsl(120, 100%, 40%);">+      }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* We will be sending them a stream and not expecting anything in return */</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_stream_set_state(stream_clone, AST_STREAM_STATE_SENDONLY);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      if (ast_stream_topology_append_stream(dest, stream_clone) < 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_stream_free(stream_clone);</span><br><span style="color: hsl(120, 100%, 40%);">+                return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> static int append_source_streams(struct ast_stream_topology *dest,</span><br><span>       const char *channel_name, const char *sdp_label,</span><br><span>     const struct ast_stream_topology *source)</span><br><span> {</span><br><span>       int i;</span><br><span style="color: hsl(0, 100%, 40%);">-  const char *stream_identify;</span><br><span> </span><br><span>     for (i = 0; i < ast_stream_topology_get_count(source); ++i) {</span><br><span>             struct ast_stream *stream;</span><br><span style="color: hsl(0, 100%, 40%);">-              struct ast_stream *stream_clone;</span><br><span style="color: hsl(0, 100%, 40%);">-                char *stream_clone_name = NULL;</span><br><span> </span><br><span>          stream = ast_stream_topology_get_stream(source, i);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>                if (!is_video_source(stream)) {</span><br><span>                      continue;</span><br><span>            }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-           stream_identify = ast_stream_get_metadata(stream, "MSID:LABEL");</span><br><span style="color: hsl(0, 100%, 40%);">-              if (!stream_identify) {</span><br><span style="color: hsl(0, 100%, 40%);">-                 stream_identify = ast_stream_get_name(stream);</span><br><span style="color: hsl(0, 100%, 40%);">-          }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-               if (ast_asprintf(&stream_clone_name, "%s_%s_%s", SOFTBRIDGE_VIDEO_DEST_PREFIX,</span><br><span style="color: hsl(0, 100%, 40%);">-                    channel_name, stream_identify) < 0) {</span><br><span style="color: hsl(0, 100%, 40%);">-                        return -1;</span><br><span style="color: hsl(0, 100%, 40%);">-              }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-               stream_clone = ast_stream_clone(stream, stream_clone_name);</span><br><span style="color: hsl(0, 100%, 40%);">-             ast_free(stream_clone_name);</span><br><span style="color: hsl(0, 100%, 40%);">-            if (!stream_clone) {</span><br><span style="color: hsl(0, 100%, 40%);">-                    return -1;</span><br><span style="color: hsl(0, 100%, 40%);">-              }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-               /* Sends an "a:label" attribute in the SDP for participant event correlation */</span><br><span style="color: hsl(0, 100%, 40%);">-               if (!ast_strlen_zero(sdp_label)) {</span><br><span style="color: hsl(0, 100%, 40%);">-                      ast_stream_set_metadata(stream_clone, "SDP:LABEL", sdp_label);</span><br><span style="color: hsl(0, 100%, 40%);">-                }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-               if (ast_stream_topology_append_stream(dest, stream_clone) < 0) {</span><br><span style="color: hsl(0, 100%, 40%);">-                     ast_stream_free(stream_clone);</span><br><span style="color: hsl(120, 100%, 40%);">+                if (append_source_stream(dest, channel_name, sdp_label, stream, i)) {</span><br><span>                        return -1;</span><br><span>           }</span><br><span>    }</span><br><span>@@ -752,7 +768,7 @@</span><br><span> </span><br><span>          stream = ast_stream_topology_get_stream(topology, i);</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-               if (is_video_dest(stream, channel_name, NULL)) {</span><br><span style="color: hsl(120, 100%, 40%);">+              if (is_video_dest(stream, channel_name, -1)) {</span><br><span>                       ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);</span><br><span>                      stream_removed = 1;</span><br><span>          }</span><br><span>@@ -2135,13 +2151,13 @@</span><br><span> /*!</span><br><span>  * \brief Map a source stream to all of its destination streams.</span><br><span>  *</span><br><span style="color: hsl(0, 100%, 40%);">- * \param source_stream_name Name of the source stream</span><br><span>  * \param source_channel_name Name of channel where the source stream originates</span><br><span>  * \param bridge_stream_position The slot in the bridge where source video will come from</span><br><span>  * \param participants The bridge_channels in the bridge</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param source_channel_stream_position The position of the stream on the source channel</span><br><span>  */</span><br><span style="color: hsl(0, 100%, 40%);">-static void map_source_to_destinations(const char *source_stream_name, const char *source_channel_name,</span><br><span style="color: hsl(0, 100%, 40%);">-        size_t bridge_stream_position, struct ast_bridge_channels_list *participants)</span><br><span style="color: hsl(120, 100%, 40%);">+static void map_source_to_destinations(const char *source_channel_name,</span><br><span style="color: hsl(120, 100%, 40%);">+        size_t bridge_stream_position, struct ast_bridge_channels_list *participants, int source_channel_stream_position)</span><br><span> {</span><br><span>       struct ast_bridge_channel *participant;</span><br><span> </span><br><span>@@ -2161,7 +2177,7 @@</span><br><span>                  struct ast_stream *stream;</span><br><span> </span><br><span>                       stream = ast_stream_topology_get_stream(topology, i);</span><br><span style="color: hsl(0, 100%, 40%);">-                   if (is_video_dest(stream, source_channel_name, source_stream_name)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                 if (is_video_dest(stream, source_channel_name, source_channel_stream_position)) {</span><br><span>                            struct softmix_channel *sc = participant->tech_pvt;</span><br><span> </span><br><span>                           AST_VECTOR_REPLACE(&participant->stream_map.to_channel, bridge_stream_position, i);</span><br><span>@@ -2228,6 +2244,137 @@</span><br><span>         }</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+static void softmix_bridge_stream_sources_update(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel,</span><br><span style="color: hsl(120, 100%, 40%);">+    struct softmix_channel *sc)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+        int index;</span><br><span style="color: hsl(120, 100%, 40%);">+    struct ast_stream_topology *old_topology = sc->topology;</span><br><span style="color: hsl(120, 100%, 40%);">+   struct ast_stream_topology *new_topology = ast_channel_get_stream_topology(bridge_channel->chan);</span><br><span style="color: hsl(120, 100%, 40%);">+  int removed_streams[MAX(ast_stream_topology_get_count(sc->topology), ast_stream_topology_get_count(new_topology))];</span><br><span style="color: hsl(120, 100%, 40%);">+        size_t removed_streams_count = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+     struct ast_stream_topology *added_streams;</span><br><span style="color: hsl(120, 100%, 40%);">+    struct ast_bridge_channels_list *participants = &bridge->channels;</span><br><span style="color: hsl(120, 100%, 40%);">+     struct ast_bridge_channel *participant;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     added_streams = ast_stream_topology_alloc();</span><br><span style="color: hsl(120, 100%, 40%);">+  if (!added_streams) {</span><br><span style="color: hsl(120, 100%, 40%);">+         return;</span><br><span style="color: hsl(120, 100%, 40%);">+       }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* We go through the old topology comparing it to the new topology to determine what streams</span><br><span style="color: hsl(120, 100%, 40%);">+   * changed state. A state transition can result in the stream being considered a new source</span><br><span style="color: hsl(120, 100%, 40%);">+    * (for example it was removed and is now present) or being removed (a stream became inactive).</span><br><span style="color: hsl(120, 100%, 40%);">+        * Added streams are copied into a topology and added to each other participant while for</span><br><span style="color: hsl(120, 100%, 40%);">+      * removed streams we merely store their position and mark them as removed later.</span><br><span style="color: hsl(120, 100%, 40%);">+      */</span><br><span style="color: hsl(120, 100%, 40%);">+   for (index = 0; index < ast_stream_topology_get_count(sc->topology) && index < ast_stream_topology_get_count(new_topology); ++index) {</span><br><span style="color: hsl(120, 100%, 40%);">+               struct ast_stream *old_stream = ast_stream_topology_get_stream(sc->topology, index);</span><br><span style="color: hsl(120, 100%, 40%);">+               struct ast_stream *new_stream = ast_stream_topology_get_stream(new_topology, index);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                /* Ignore all streams that don't carry video and streams that are strictly outgoing destination streams */</span><br><span style="color: hsl(120, 100%, 40%);">+                if ((ast_stream_get_type(old_stream) != AST_MEDIA_TYPE_VIDEO && ast_stream_get_type(new_stream) != AST_MEDIA_TYPE_VIDEO) ||</span><br><span style="color: hsl(120, 100%, 40%);">+                   !strncmp(ast_stream_get_name(old_stream), SOFTBRIDGE_VIDEO_DEST_PREFIX,</span><br><span style="color: hsl(120, 100%, 40%);">+                               SOFTBRIDGE_VIDEO_DEST_LEN)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                 continue;</span><br><span style="color: hsl(120, 100%, 40%);">+             }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           if (ast_stream_get_type(old_stream) == AST_MEDIA_TYPE_VIDEO && ast_stream_get_type(new_stream) != AST_MEDIA_TYPE_VIDEO) {</span><br><span style="color: hsl(120, 100%, 40%);">+                     /* If a stream renegotiates from video to non-video then we need to remove it as a source */</span><br><span style="color: hsl(120, 100%, 40%);">+                  removed_streams[removed_streams_count++] = index;</span><br><span style="color: hsl(120, 100%, 40%);">+             } else if (ast_stream_get_type(old_stream) != AST_MEDIA_TYPE_VIDEO && ast_stream_get_type(new_stream) == AST_MEDIA_TYPE_VIDEO) {</span><br><span style="color: hsl(120, 100%, 40%);">+                      if (ast_stream_get_state(new_stream) != AST_STREAM_STATE_REMOVED) {</span><br><span style="color: hsl(120, 100%, 40%);">+                           /* If a stream renegotiates from non-video to video in a non-removed state we need to add it as a source */</span><br><span style="color: hsl(120, 100%, 40%);">+                           if (append_source_stream(added_streams, ast_channel_name(bridge_channel->chan),</span><br><span style="color: hsl(120, 100%, 40%);">+                                                    bridge->softmix.send_sdp_label ? ast_channel_uniqueid(bridge_channel->chan) : NULL,</span><br><span style="color: hsl(120, 100%, 40%);">+                                                     new_stream, index)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                                 goto cleanup;</span><br><span style="color: hsl(120, 100%, 40%);">+                         }</span><br><span style="color: hsl(120, 100%, 40%);">+                     }</span><br><span style="color: hsl(120, 100%, 40%);">+             } else if (ast_stream_get_state(old_stream) != AST_STREAM_STATE_REMOVED &&</span><br><span style="color: hsl(120, 100%, 40%);">+                            ast_stream_get_state(new_stream) != AST_STREAM_STATE_SENDRECV && ast_stream_get_state(new_stream) != AST_STREAM_STATE_RECVONLY) {</span><br><span style="color: hsl(120, 100%, 40%);">+                     /* If a stream renegotiates and is removed then we remove it */</span><br><span style="color: hsl(120, 100%, 40%);">+                       removed_streams[removed_streams_count++] = index;</span><br><span style="color: hsl(120, 100%, 40%);">+             } else if (ast_stream_get_state(old_stream) == AST_STREAM_STATE_REMOVED &&</span><br><span style="color: hsl(120, 100%, 40%);">+                            ast_stream_get_state(new_stream) != AST_STREAM_STATE_INACTIVE && ast_stream_get_state(new_stream) != AST_STREAM_STATE_SENDONLY &&</span><br><span style="color: hsl(120, 100%, 40%);">+                             ast_stream_get_state(new_stream) != AST_STREAM_STATE_REMOVED) {</span><br><span style="color: hsl(120, 100%, 40%);">+                       /* If a stream renegotiates and is added then we add it */</span><br><span style="color: hsl(120, 100%, 40%);">+                    if (append_source_stream(added_streams, ast_channel_name(bridge_channel->chan),</span><br><span style="color: hsl(120, 100%, 40%);">+                                            bridge->softmix.send_sdp_label ? ast_channel_uniqueid(bridge_channel->chan) : NULL,</span><br><span style="color: hsl(120, 100%, 40%);">+                                             new_stream, index)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                         goto cleanup;</span><br><span style="color: hsl(120, 100%, 40%);">+                 }</span><br><span style="color: hsl(120, 100%, 40%);">+             }</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* Any newly added streams that did not take the position of a removed stream</span><br><span style="color: hsl(120, 100%, 40%);">+  * will be present at the end of the new topology. Since streams are never</span><br><span style="color: hsl(120, 100%, 40%);">+     * removed from the topology but merely marked as removed we can pick up where we</span><br><span style="color: hsl(120, 100%, 40%);">+      * left off when comparing the old and new topologies.</span><br><span style="color: hsl(120, 100%, 40%);">+         */</span><br><span style="color: hsl(120, 100%, 40%);">+   for (; index < ast_stream_topology_get_count(new_topology); ++index) {</span><br><span style="color: hsl(120, 100%, 40%);">+             struct ast_stream *stream = ast_stream_topology_get_stream(new_topology, index);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+            if (!is_video_source(stream)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                       continue;</span><br><span style="color: hsl(120, 100%, 40%);">+             }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           if (append_source_stream(added_streams, ast_channel_name(bridge_channel->chan),</span><br><span style="color: hsl(120, 100%, 40%);">+                                    bridge->softmix.send_sdp_label ? ast_channel_uniqueid(bridge_channel->chan) : NULL,</span><br><span style="color: hsl(120, 100%, 40%);">+                                     stream, index)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                     goto cleanup;</span><br><span style="color: hsl(120, 100%, 40%);">+         }</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /*  We always update the stored topology if we can to reflect what is currently negotiated */</span><br><span style="color: hsl(120, 100%, 40%);">+ sc->topology = ast_stream_topology_clone(new_topology);</span><br><span style="color: hsl(120, 100%, 40%);">+    if (!sc->topology) {</span><br><span style="color: hsl(120, 100%, 40%);">+               sc->topology = old_topology;</span><br><span style="color: hsl(120, 100%, 40%);">+       } else {</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_stream_topology_free(old_topology);</span><br><span style="color: hsl(120, 100%, 40%);">+       }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* If there are no removed sources and no added sources we don't need to renegotiate the</span><br><span style="color: hsl(120, 100%, 40%);">+   * other participants.</span><br><span style="color: hsl(120, 100%, 40%);">+         */</span><br><span style="color: hsl(120, 100%, 40%);">+   if (!removed_streams_count && !ast_stream_topology_get_count(added_streams)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                goto cleanup;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* Go through each participant adding in the new streams and removing the old ones */</span><br><span style="color: hsl(120, 100%, 40%);">+ AST_LIST_TRAVERSE(participants, participant, entry) {</span><br><span style="color: hsl(120, 100%, 40%);">+         if (participant == bridge_channel) {</span><br><span style="color: hsl(120, 100%, 40%);">+                  continue;</span><br><span style="color: hsl(120, 100%, 40%);">+             }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           sc = participant->tech_pvt;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+              /* We add in all the new streams first so that they do not take the place</span><br><span style="color: hsl(120, 100%, 40%);">+              * of any of our removed streams, allowing the remote side to reset the state</span><br><span style="color: hsl(120, 100%, 40%);">+          * for each removed stream. */</span><br><span style="color: hsl(120, 100%, 40%);">+                if (append_all_streams(sc->topology, added_streams)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                        goto cleanup;</span><br><span style="color: hsl(120, 100%, 40%);">+                }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+         /* Then we go through and remove any ones that were removed */</span><br><span style="color: hsl(120, 100%, 40%);">+                for (index = 0; removed_streams_count && index < ast_stream_topology_get_count(sc->topology); ++index) {</span><br><span style="color: hsl(120, 100%, 40%);">+                        struct ast_stream *stream = ast_stream_topology_get_stream(sc->topology, index);</span><br><span style="color: hsl(120, 100%, 40%);">+                   int removed_stream;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                 for (removed_stream = 0; removed_stream < removed_streams_count; ++removed_stream) {</span><br><span style="color: hsl(120, 100%, 40%);">+                               if (is_video_dest(stream, ast_channel_name(bridge_channel->chan), removed_streams[removed_stream])) {</span><br><span style="color: hsl(120, 100%, 40%);">+                                      ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);</span><br><span style="color: hsl(120, 100%, 40%);">+                               }</span><br><span style="color: hsl(120, 100%, 40%);">+                     }</span><br><span style="color: hsl(120, 100%, 40%);">+             }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                ast_channel_request_stream_topology_change(participant->chan, sc->topology, NULL);</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+cleanup:</span><br><span style="color: hsl(120, 100%, 40%);">+        ast_stream_topology_free(added_streams);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /*!</span><br><span>  * \brief stream_topology_changed callback</span><br><span>  *</span><br><span>@@ -2241,7 +2388,7 @@</span><br><span> static void softmix_bridge_stream_topology_changed(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)</span><br><span> {</span><br><span>    struct softmix_bridge_data *softmix_data = bridge->tech_pvt;</span><br><span style="color: hsl(0, 100%, 40%);">- struct softmix_channel *sc;</span><br><span style="color: hsl(120, 100%, 40%);">+   struct softmix_channel *sc = bridge_channel->tech_pvt;</span><br><span>    struct ast_bridge_channel *participant;</span><br><span>      struct ast_vector_int media_types;</span><br><span>   int nths[AST_MEDIA_TYPE_END] = {0};</span><br><span>@@ -2258,6 +2405,10 @@</span><br><span>                 break;</span><br><span>       }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ ast_channel_lock(bridge_channel->chan);</span><br><span style="color: hsl(120, 100%, 40%);">+    softmix_bridge_stream_sources_update(bridge, bridge_channel, sc);</span><br><span style="color: hsl(120, 100%, 40%);">+     ast_channel_unlock(bridge_channel->chan);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>       AST_VECTOR_INIT(&media_types, AST_MEDIA_TYPE_END);</span><br><span> </span><br><span>   /* The bridge stream identifiers may change, so reset the mapping for them.</span><br><span>@@ -2307,7 +2458,6 @@</span><br><span> </span><br><span>              for (i = 0; i < ast_stream_topology_get_count(topology); ++i) {</span><br><span>                   struct ast_stream *stream = ast_stream_topology_get_stream(topology, i);</span><br><span style="color: hsl(0, 100%, 40%);">-                        const char *stream_identify;</span><br><span> </span><br><span>                     if (is_video_source(stream)) {</span><br><span>                               AST_VECTOR_APPEND(&media_types, AST_MEDIA_TYPE_VIDEO);</span><br><span>@@ -2325,12 +2475,8 @@</span><br><span>                          ast_channel_unlock(participant->chan);</span><br><span>                            ast_bridge_channel_unlock(participant);</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-                             stream_identify = ast_stream_get_metadata(stream, "MSID:LABEL");</span><br><span style="color: hsl(0, 100%, 40%);">-                              if (!stream_identify) {</span><br><span style="color: hsl(0, 100%, 40%);">-                                 stream_identify = ast_stream_get_name(stream);</span><br><span style="color: hsl(0, 100%, 40%);">-                          }</span><br><span style="color: hsl(0, 100%, 40%);">-                               map_source_to_destinations(stream_identify, ast_channel_name(participant->chan),</span><br><span style="color: hsl(0, 100%, 40%);">-                                     AST_VECTOR_SIZE(&media_types) - 1, &bridge->channels);</span><br><span style="color: hsl(120, 100%, 40%);">+                             map_source_to_destinations(ast_channel_name(participant->chan),</span><br><span style="color: hsl(120, 100%, 40%);">+                                    AST_VECTOR_SIZE(&media_types) - 1, &bridge->channels, i);</span><br><span>                                 ast_bridge_channel_lock(participant);</span><br><span>                                ast_channel_lock(participant->chan);</span><br><span>                      } else if (ast_stream_get_type(stream) == AST_MEDIA_TYPE_VIDEO) {</span><br><span>@@ -2495,10 +2641,10 @@</span><br><span>          { "alice_video", "vp8", AST_MEDIA_TYPE_VIDEO, },</span><br><span>         };</span><br><span>   static const struct stream_parameters alice_dest_stream = {</span><br><span style="color: hsl(0, 100%, 40%);">-             "softbridge_dest_PJSIP/Bob-00000001_bob_video", "h264,vp8", AST_MEDIA_TYPE_VIDEO,</span><br><span style="color: hsl(120, 100%, 40%);">+         "softbridge_dest_PJSIP/Bob-00000001_1", "h264,vp8", AST_MEDIA_TYPE_VIDEO,</span><br><span>        };</span><br><span>   static const struct stream_parameters bob_dest_stream = {</span><br><span style="color: hsl(0, 100%, 40%);">-               "softbridge_dest_PJSIP/Alice-00000000_alice_video", "vp8", AST_MEDIA_TYPE_VIDEO,</span><br><span style="color: hsl(120, 100%, 40%);">+          "softbridge_dest_PJSIP/Alice-00000000_1", "vp8", AST_MEDIA_TYPE_VIDEO,</span><br><span>   };</span><br><span>   struct ast_stream_topology *topology_alice = NULL;</span><br><span>   struct ast_stream_topology *topology_bob = NULL;</span><br><span>@@ -2645,7 +2791,7 @@</span><br><span>                             goto end;</span><br><span>                    }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-                   if (is_video_dest(actual, removal_results[i].channel_name, NULL) &&</span><br><span style="color: hsl(120, 100%, 40%);">+                   if (is_video_dest(actual, removal_results[i].channel_name, -1) &&</span><br><span>                            ast_stream_get_state(actual) != AST_STREAM_STATE_REMOVED) {</span><br><span>                          ast_test_status_update(test, "Removed stream %s does not have a state of removed\n", ast_stream_get_name(actual));</span><br><span>                                 goto end;</span><br><span>diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h</span><br><span>index a0b7728..1013003 100644</span><br><span>--- a/include/asterisk/channel.h</span><br><span>+++ b/include/asterisk/channel.h</span><br><span>@@ -206,6 +206,12 @@</span><br><span> </span><br><span> struct ast_stream_topology;</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Set as the change source reason when a channel stream topology has</span><br><span style="color: hsl(120, 100%, 40%);">+ *        been changed externally as a result of the remote side renegotiating.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static const char ast_stream_topology_changed_external[] = "external";</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /*! \todo Add an explanation of an Asterisk generator</span><br><span> */</span><br><span> struct ast_generator {</span><br><span>@@ -5018,6 +5024,20 @@</span><br><span> int ast_channel_stream_topology_changed(struct ast_channel *chan, struct ast_stream_topology *topology);</span><br><span> </span><br><span> /*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Provide notice from a channel that the topology has changed on it as a result</span><br><span style="color: hsl(120, 100%, 40%);">+ *        of the remote party renegotiating.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param chan The channel to provide notice from</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval 0 success</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval -1 failure</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \note This interface is provided for channels to provide notice that a topology change</span><br><span style="color: hsl(120, 100%, 40%);">+ *       has occurred as a result of a remote party renegotiating the stream topology.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+int ast_channel_stream_topology_changed_externally(struct ast_channel *chan);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span>  * \brief Retrieve the source that initiated the last stream topology change</span><br><span>  *</span><br><span>  * \param chan The channel</span><br><span>diff --git a/main/channel.c b/main/channel.c</span><br><span>index 4b77390..63ce64e 100644</span><br><span>--- a/main/channel.c</span><br><span>+++ b/main/channel.c</span><br><span>@@ -11051,6 +11051,25 @@</span><br><span>      return ast_channel_tech(chan)->indicate(chan, AST_CONTROL_STREAM_TOPOLOGY_CHANGED, topology, sizeof(topology));</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+int ast_channel_stream_topology_changed_externally(struct ast_channel *chan)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+  int res;</span><br><span style="color: hsl(120, 100%, 40%);">+      struct ast_frame f = { AST_FRAME_CONTROL, .subclass.integer = AST_CONTROL_STREAM_TOPOLOGY_CHANGED };</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        ast_assert(chan != NULL);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (!ast_channel_is_multistream(chan)) {</span><br><span style="color: hsl(120, 100%, 40%);">+              return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_channel_lock(chan);</span><br><span style="color: hsl(120, 100%, 40%);">+       ast_channel_internal_set_stream_topology_change_source(chan, (void *)&ast_stream_topology_changed_external);</span><br><span style="color: hsl(120, 100%, 40%);">+      res = ast_queue_frame(chan, &f);</span><br><span style="color: hsl(120, 100%, 40%);">+  ast_channel_unlock(chan);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   return res;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> void ast_channel_set_flag(struct ast_channel *chan, unsigned int flag)</span><br><span> {</span><br><span>      ast_channel_lock(chan);</span><br><span>diff --git a/main/stream.c b/main/stream.c</span><br><span>index 47415bf..626fa3a 100644</span><br><span>--- a/main/stream.c</span><br><span>+++ b/main/stream.c</span><br><span>@@ -96,8 +96,9 @@</span><br><span> struct ast_stream *ast_stream_alloc(const char *name, enum ast_media_type type)</span><br><span> {</span><br><span>         struct ast_stream *stream;</span><br><span style="color: hsl(120, 100%, 40%);">+    size_t name_len = MAX(strlen(S_OR(name, "")), 7); /* Ensure there is enough room for 'removed' */</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- stream = ast_calloc(1, sizeof(*stream) + strlen(S_OR(name, "")) + 1);</span><br><span style="color: hsl(120, 100%, 40%);">+       stream = ast_calloc(1, sizeof(*stream) + name_len + 1);</span><br><span>      if (!stream) {</span><br><span>               return NULL;</span><br><span>         }</span><br><span>@@ -113,16 +114,16 @@</span><br><span> struct ast_stream *ast_stream_clone(const struct ast_stream *stream, const char *name)</span><br><span> {</span><br><span>     struct ast_stream *new_stream;</span><br><span style="color: hsl(0, 100%, 40%);">-  size_t stream_size;</span><br><span>  const char *stream_name;</span><br><span style="color: hsl(120, 100%, 40%);">+      size_t name_len;</span><br><span> </span><br><span>         if (!stream) {</span><br><span>               return NULL;</span><br><span>         }</span><br><span> </span><br><span>        stream_name = name ?: stream->name;</span><br><span style="color: hsl(0, 100%, 40%);">-  stream_size = sizeof(*stream) + strlen(stream_name) + 1;</span><br><span style="color: hsl(0, 100%, 40%);">-        new_stream = ast_calloc(1, stream_size);</span><br><span style="color: hsl(120, 100%, 40%);">+      name_len = MAX(strlen(stream_name), 7); /* Ensure there is enough room for 'removed' */</span><br><span style="color: hsl(120, 100%, 40%);">+       new_stream = ast_calloc(1, sizeof(*stream) + name_len + 1);</span><br><span>  if (!new_stream) {</span><br><span>           return NULL;</span><br><span>         }</span><br><span>@@ -205,6 +206,19 @@</span><br><span>     ast_assert(stream != NULL);</span><br><span> </span><br><span>      stream->state = state;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* When a stream is set to removed that means that any previous data for it</span><br><span style="color: hsl(120, 100%, 40%);">+    * is no longer valid. We therefore change its name to removed and remove</span><br><span style="color: hsl(120, 100%, 40%);">+      * any old metadata associated with it.</span><br><span style="color: hsl(120, 100%, 40%);">+        */</span><br><span style="color: hsl(120, 100%, 40%);">+   if (state == AST_STREAM_STATE_REMOVED) {</span><br><span style="color: hsl(120, 100%, 40%);">+              strcpy(stream->name, "removed");</span><br><span style="color: hsl(120, 100%, 40%);">+         ast_variables_destroy(stream->metadata);</span><br><span style="color: hsl(120, 100%, 40%);">+           stream->metadata = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+           if (stream->formats) {</span><br><span style="color: hsl(120, 100%, 40%);">+                     ast_format_cap_remove_by_type(stream->formats, AST_MEDIA_TYPE_UNKNOWN);</span><br><span style="color: hsl(120, 100%, 40%);">+            }</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span> }</span><br><span> </span><br><span> const char *ast_stream_state2str(enum ast_stream_state state)</span><br><span>diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c</span><br><span>index 8d9cece..5906211 100644</span><br><span>--- a/res/res_pjsip_sdp_rtp.c</span><br><span>+++ b/res/res_pjsip_sdp_rtp.c</span><br><span>@@ -1547,6 +1547,8 @@</span><br><span>         static const pj_str_t STR_IP6 = { "IP6", 3};</span><br><span>       static const pj_str_t STR_SENDRECV = { "sendrecv", 8 };</span><br><span>    static const pj_str_t STR_SENDONLY = { "sendonly", 8 };</span><br><span style="color: hsl(120, 100%, 40%);">+     static const pj_str_t STR_RECVONLY = { "recvonly", 8 };</span><br><span style="color: hsl(120, 100%, 40%);">+     static const pj_str_t STR_INACTIVE = { "inactive", 8 };</span><br><span>    pjmedia_sdp_media *media;</span><br><span>    const char *hostip = NULL;</span><br><span>   struct ast_sockaddr addr;</span><br><span>@@ -1813,7 +1815,15 @@</span><br><span> </span><br><span>       /* Add the sendrecv attribute - we purposely don't keep track because pjmedia-sdp will automatically change our offer for us */</span><br><span>  attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr);</span><br><span style="color: hsl(0, 100%, 40%);">-        attr->name = !session_media->locally_held ? STR_SENDRECV : STR_SENDONLY;</span><br><span style="color: hsl(120, 100%, 40%);">+        if (session_media->locally_held || ast_stream_get_state(stream) == AST_STREAM_STATE_SENDONLY) {</span><br><span style="color: hsl(120, 100%, 40%);">+            attr->name = STR_SENDONLY; /* Send sendonly to initate a local hold */</span><br><span style="color: hsl(120, 100%, 40%);">+     } else if (ast_stream_get_state(stream) == AST_STREAM_STATE_RECVONLY) {</span><br><span style="color: hsl(120, 100%, 40%);">+               attr->name = STR_RECVONLY; /* Stream has requested recvonly */</span><br><span style="color: hsl(120, 100%, 40%);">+     } else if (ast_stream_get_state(stream) == AST_STREAM_STATE_INACTIVE) {</span><br><span style="color: hsl(120, 100%, 40%);">+               attr->name = STR_INACTIVE; /* Stream has requested inactive */</span><br><span style="color: hsl(120, 100%, 40%);">+     } else {</span><br><span style="color: hsl(120, 100%, 40%);">+              attr->name = STR_SENDRECV; /* No hold in either direction */</span><br><span style="color: hsl(120, 100%, 40%);">+       }</span><br><span>    media->attr[media->attr_count++] = attr;</span><br><span> </span><br><span>   /* If we've got rtcp-mux enabled, add it unless we received an offer without it */</span><br><span>diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c</span><br><span>index 1371909..504d6f1 100644</span><br><span>--- a/res/res_pjsip_session.c</span><br><span>+++ b/res/res_pjsip_session.c</span><br><span>@@ -945,7 +945,7 @@</span><br><span> {</span><br><span>   int i;</span><br><span>       struct ast_stream_topology *topology;</span><br><span style="color: hsl(0, 100%, 40%);">-   unsigned int changed = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+     unsigned int changed = 0; /* 0 = unchanged, 1 = new source, 2 = new topology */</span><br><span> </span><br><span>  if (!session->pending_media_state->topology) {</span><br><span>                 if (session->active_media_state->topology) {</span><br><span>@@ -1057,6 +1057,14 @@</span><br><span>  topology = ast_stream_topology_clone(session->pending_media_state->topology);</span><br><span>  if (topology) {</span><br><span>              ast_channel_set_stream_topology(session->channel, topology);</span><br><span style="color: hsl(120, 100%, 40%);">+               /* If this is a remotely done renegotiation that has changed the stream topology notify what is</span><br><span style="color: hsl(120, 100%, 40%);">+                * currently handling this channel.</span><br><span style="color: hsl(120, 100%, 40%);">+            */</span><br><span style="color: hsl(120, 100%, 40%);">+           if (pjmedia_sdp_neg_was_answer_remote(session->inv_session->neg) == PJ_FALSE &&</span><br><span style="color: hsl(120, 100%, 40%);">+                 session->active_media_state && session->active_media_state->topology &&</span><br><span style="color: hsl(120, 100%, 40%);">+                      !ast_stream_topology_equal(session->active_media_state->topology, topology)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                  changed = 2;</span><br><span style="color: hsl(120, 100%, 40%);">+          }</span><br><span>    }</span><br><span> </span><br><span>        /* Remove all current file descriptors from the channel */</span><br><span>@@ -1079,10 +1087,12 @@</span><br><span> </span><br><span>     ast_channel_unlock(session->channel);</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-    if (changed) {</span><br><span style="color: hsl(120, 100%, 40%);">+        if (changed == 1) {</span><br><span>          struct ast_frame f = { AST_FRAME_CONTROL, .subclass.integer = AST_CONTROL_STREAM_TOPOLOGY_SOURCE_CHANGED };</span><br><span> </span><br><span>              ast_queue_frame(session->channel, &f);</span><br><span style="color: hsl(120, 100%, 40%);">+ } else if (changed == 2) {</span><br><span style="color: hsl(120, 100%, 40%);">+            ast_channel_stream_topology_changed_externally(session->channel);</span><br><span>         } else {</span><br><span>             ast_queue_frame(session->channel, &ast_null_frame);</span><br><span>   }</span><br><span>@@ -1875,6 +1885,7 @@</span><br><span>            enum ast_media_type type;</span><br><span>            struct ast_sip_session_media *session_media = NULL;</span><br><span>          enum ast_sip_session_sdp_stream_defer res;</span><br><span style="color: hsl(120, 100%, 40%);">+            pjmedia_sdp_media *remote_stream = sdp->media[i];</span><br><span> </span><br><span>             /* We need a null-terminated version of the media string */</span><br><span>          ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media));</span><br><span>@@ -1903,6 +1914,25 @@</span><br><span>                        return -1;</span><br><span>           }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+         /* For backwards compatibility with the core default streams are always sendrecv */</span><br><span style="color: hsl(120, 100%, 40%);">+           if (!ast_sip_session_is_pending_stream_default(session, stream)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                    if (pjmedia_sdp_media_find_attr2(remote_stream, "sendonly", NULL)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                                /* Stream state reflects our state of a stream, so in the case of</span><br><span style="color: hsl(120, 100%, 40%);">+                              * sendonly and recvonly we store the opposite since that is what ours</span><br><span style="color: hsl(120, 100%, 40%);">+                                 * is.</span><br><span style="color: hsl(120, 100%, 40%);">+                                 */</span><br><span style="color: hsl(120, 100%, 40%);">+                           ast_stream_set_state(stream, AST_STREAM_STATE_RECVONLY);</span><br><span style="color: hsl(120, 100%, 40%);">+                      } else if (pjmedia_sdp_media_find_attr2(remote_stream, "recvonly", NULL)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                         ast_stream_set_state(stream, AST_STREAM_STATE_SENDONLY);</span><br><span style="color: hsl(120, 100%, 40%);">+                      } else if (pjmedia_sdp_media_find_attr2(remote_stream, "inactive", NULL)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                         ast_stream_set_state(stream, AST_STREAM_STATE_INACTIVE);</span><br><span style="color: hsl(120, 100%, 40%);">+                      } else {</span><br><span style="color: hsl(120, 100%, 40%);">+                              ast_stream_set_state(stream, AST_STREAM_STATE_SENDRECV);</span><br><span style="color: hsl(120, 100%, 40%);">+                      }</span><br><span style="color: hsl(120, 100%, 40%);">+             } else {</span><br><span style="color: hsl(120, 100%, 40%);">+                      ast_stream_set_state(stream, AST_STREAM_STATE_SENDRECV);</span><br><span style="color: hsl(120, 100%, 40%);">+              }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>          if (session_media->handler) {</span><br><span>                     handler = session_media->handler;</span><br><span>                         if (handler->defer_incoming_sdp_stream) {</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/c/asterisk/+/13850">change 13850</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/c/asterisk/+/13850"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: certified/16.8 </div>
<div style="display:none"> Gerrit-Change-Id: I93f41fb41b85646bef71408111c17ccea30cb0c5 </div>
<div style="display:none"> Gerrit-Change-Number: 13850 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Joshua Colp <jcolp@sangoma.com> </div>
<div style="display:none"> Gerrit-Reviewer: Friendly Automation </div>
<div style="display:none"> Gerrit-Reviewer: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>