<p>George Joseph <strong>merged</strong> this change.</p><p><a href="https://gerrit.asterisk.org/5856">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  George Joseph: Looks good to me, but someone else must approve; Approved for Submit
  Joshua Colp: Looks good to me, but someone else must approve
  Kevin Harwell: Looks good to me, approved

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">bridge_native_rtp: Keep rtp instance refs on bridge_channel<br><br>There have been reports of deadlocks caused by an attempt to send a frame<br>to a channel's rtp instance after the channel has left the native bridge<br>and been destroyed.  This patch effectively causes the bridge channel to<br>keep a reference to the glue and both the audio and video rtp instances<br>so what gets started will get stopped.<br><br>ASTERISK-26978 #close<br>Reported-by: Ross Beer<br><br>Change-Id: I9e1ac49fa4af68d64826ccccd152593cf8cdb21a<br>---<br>M bridges/bridge_native_rtp.c<br>M include/asterisk/rtp_engine.h<br>2 files changed, 517 insertions(+), 141 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/bridges/bridge_native_rtp.c b/bridges/bridge_native_rtp.c<br>index 4af93bf..a459c9c 100644<br>--- a/bridges/bridge_native_rtp.c<br>+++ b/bridges/bridge_native_rtp.c<br>@@ -44,76 +44,214 @@<br> #include "asterisk/frame.h"<br> #include "asterisk/rtp_engine.h"<br> <br>-/*! \brief Internal structure which contains information about bridged RTP channels */<br>-struct native_rtp_bridge_data {<br>+/*! \brief Internal structure which contains bridged RTP channel hook data */<br>+struct native_rtp_framehook_data {<br>        /*! \brief Framehook used to intercept certain control frames */<br>      int id;<br>       /*! \brief Set when this framehook has been detached */<br>       unsigned int detached;<br> };<br> <br>-/*! \brief Internal helper function which gets all RTP information (glue and instances) relating to the given channels */<br>-static enum ast_rtp_glue_result native_rtp_bridge_get(struct ast_channel *c0, struct ast_channel *c1, struct ast_rtp_glue **glue0,<br>-      struct ast_rtp_glue **glue1, struct ast_rtp_instance **instance0, struct ast_rtp_instance **instance1,<br>-       struct ast_rtp_instance **vinstance0, struct ast_rtp_instance **vinstance1)<br>-{<br>-      enum ast_rtp_glue_result audio_glue0_res;<br>-    enum ast_rtp_glue_result video_glue0_res;<br>-    enum ast_rtp_glue_result audio_glue1_res;<br>-    enum ast_rtp_glue_result video_glue1_res;<br>+struct rtp_glue_stream {<br>+ /*! \brief RTP instance */<br>+   struct ast_rtp_instance *instance;<br>+   /*! \brief glue result */<br>+    enum ast_rtp_glue_result result;<br>+};<br> <br>-     if (!(*glue0 = ast_rtp_instance_get_glue(ast_channel_tech(c0)->type)) ||<br>-          !(*glue1 = ast_rtp_instance_get_glue(ast_channel_tech(c1)->type))) {<br>-              return AST_RTP_GLUE_RESULT_FORBID;<br>+struct rtp_glue_data {<br>+  /*!<br>+   * \brief glue callbacks<br>+      *<br>+    * \note The glue data is considered valid if cb is not NULL.<br>+         */<br>+  struct ast_rtp_glue *cb;<br>+     struct rtp_glue_stream audio;<br>+        struct rtp_glue_stream video;<br>+        /*! Combined glue result of both bridge channels. */<br>+ enum ast_rtp_glue_result result;<br>+};<br>+<br>+/*! \brief Internal structure which contains instance information about bridged RTP channels */<br>+struct native_rtp_bridge_channel_data {<br>+ /*! \brief Channel's hook data */<br>+        struct native_rtp_framehook_data *hook_data;<br>+ /*!<br>+   * \brief Glue callbacks to bring remote channel streams back to Asterisk.<br>+    * \note NULL if channel streams are local.<br>+   */<br>+  struct ast_rtp_glue *remote_cb;<br>+      /*! \brief Channel's cached RTP glue information */<br>+      struct rtp_glue_data glue;<br>+};<br>+<br>+static void rtp_glue_data_init(struct rtp_glue_data *glue)<br>+{<br>+  glue->cb = NULL;<br>+  glue->audio.instance = NULL;<br>+      glue->audio.result = AST_RTP_GLUE_RESULT_FORBID;<br>+  glue->video.instance = NULL;<br>+      glue->video.result = AST_RTP_GLUE_RESULT_FORBID;<br>+  glue->result = AST_RTP_GLUE_RESULT_FORBID;<br>+}<br>+<br>+static void rtp_glue_data_destroy(struct rtp_glue_data *glue)<br>+{<br>+     if (!glue) {<br>+         return;<br>+      }<br>+    ao2_cleanup(glue->audio.instance);<br>+        ao2_cleanup(glue->video.instance);<br>+}<br>+<br>+static void rtp_glue_data_reset(struct rtp_glue_data *glue)<br>+{<br>+       rtp_glue_data_destroy(glue);<br>+ rtp_glue_data_init(glue);<br>+}<br>+<br>+static void native_rtp_bridge_channel_data_free(struct native_rtp_bridge_channel_data *data)<br>+{<br>+  ast_debug(2, "Destroying channel tech_pvt data %p\n", data);<br>+<br>+    /*<br>+    * hook_data will probably already have been unreferenced by the framehook detach<br>+     * and the pointer set to null.<br>+       */<br>+  ao2_cleanup(data->hook_data);<br>+<br>+  rtp_glue_data_reset(&data->glue);<br>+     ast_free(data);<br>+}<br>+<br>+static struct native_rtp_bridge_channel_data *native_rtp_bridge_channel_data_alloc(void)<br>+{<br>+        struct native_rtp_bridge_channel_data *data;<br>+<br>+      data = ast_calloc(1, sizeof(*data));<br>+ if (data) {<br>+          rtp_glue_data_init(&data->glue);<br>+      }<br>+    return data;<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Helper function which gets all RTP information (glue and instances) relating to the given channels<br>+ *<br>+ * \retval 0 on success.<br>+ * \retval -1 on error.<br>+ */<br>+static int rtp_glue_data_get(struct ast_channel *c0, struct rtp_glue_data *glue0,<br>+  struct ast_channel *c1, struct rtp_glue_data *glue1)<br>+{<br>+     struct ast_rtp_glue *cb0;<br>+    struct ast_rtp_glue *cb1;<br>+    enum ast_rtp_glue_result combined_result;<br>+<br>+ cb0 = ast_rtp_instance_get_glue(ast_channel_tech(c0)->type);<br>+      cb1 = ast_rtp_instance_get_glue(ast_channel_tech(c1)->type);<br>+      if (!cb0 || !cb1) {<br>+          /* One or both channels doesn't have any RTP glue registered. */<br>+         return -1;<br>    }<br> <br>- audio_glue0_res = (*glue0)->get_rtp_info(c0, instance0);<br>-  video_glue0_res = (*glue0)->get_vrtp_info ? (*glue0)->get_vrtp_info(c0, vinstance0) : AST_RTP_GLUE_RESULT_FORBID;<br>+      /* The glue callbacks bump the RTP instance refcounts for us. */<br> <br>-  audio_glue1_res = (*glue1)->get_rtp_info(c1, instance1);<br>-  video_glue1_res = (*glue1)->get_vrtp_info ? (*glue1)->get_vrtp_info(c1, vinstance1) : AST_RTP_GLUE_RESULT_FORBID;<br>+      glue0->cb = cb0;<br>+  glue0->audio.result = cb0->get_rtp_info(c0, &glue0->audio.instance);<br>+    glue0->video.result = cb0->get_vrtp_info<br>+               ? cb0->get_vrtp_info(c0, &glue0->video.instance) : AST_RTP_GLUE_RESULT_FORBID;<br>+<br>+  glue1->cb = cb1;<br>+  glue1->audio.result = cb1->get_rtp_info(c1, &glue1->audio.instance);<br>+    glue1->video.result = cb1->get_vrtp_info<br>+               ? cb1->get_vrtp_info(c1, &glue1->video.instance) : AST_RTP_GLUE_RESULT_FORBID;<br>+<br>+  /*<br>+    * Now determine the combined glue result.<br>+    */<br> <br>        /* Apply any limitations on direct media bridging that may be present */<br>-     if (audio_glue0_res == audio_glue1_res && audio_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {<br>-           if ((*glue0)->allow_rtp_remote && !((*glue0)->allow_rtp_remote(c0, *instance1))) {<br>+     if (glue0->audio.result == glue1->audio.result && glue1->audio.result == AST_RTP_GLUE_RESULT_REMOTE) {<br>+              if (glue0->cb->allow_rtp_remote && !glue0->cb->allow_rtp_remote(c0, glue1->audio.instance)) {<br>                  /* If the allow_rtp_remote indicates that remote isn't allowed, revert to local bridge */<br>-                        audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;<br>-               } else if ((*glue1)->allow_rtp_remote && !((*glue1)->allow_rtp_remote(c1, *instance0))) {<br>-                      audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;<br>+                       glue0->audio.result = glue1->audio.result = AST_RTP_GLUE_RESULT_LOCAL;<br>+         } else if (glue1->cb->allow_rtp_remote && !glue1->cb->allow_rtp_remote(c1, glue0->audio.instance)) {<br>+                  glue0->audio.result = glue1->audio.result = AST_RTP_GLUE_RESULT_LOCAL;<br>          }<br>     }<br>-    if (video_glue0_res == video_glue1_res && video_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {<br>-           if ((*glue0)->allow_vrtp_remote && !((*glue0)->allow_vrtp_remote(c0, *instance1))) {<br>+   if (glue0->video.result == glue1->video.result && glue1->video.result == AST_RTP_GLUE_RESULT_REMOTE) {<br>+              if (glue0->cb->allow_vrtp_remote && !glue0->cb->allow_vrtp_remote(c0, glue1->audio.instance)) {<br>                        /* if the allow_vrtp_remote indicates that remote isn't allowed, revert to local bridge */<br>-                       video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;<br>-               } else if ((*glue1)->allow_vrtp_remote && !((*glue1)->allow_vrtp_remote(c1, *instance0))) {<br>-                    video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;<br>+                       glue0->video.result = glue1->video.result = AST_RTP_GLUE_RESULT_LOCAL;<br>+         } else if (glue1->cb->allow_vrtp_remote && !glue1->cb->allow_vrtp_remote(c1, glue0->audio.instance)) {<br>+                        glue0->video.result = glue1->video.result = AST_RTP_GLUE_RESULT_LOCAL;<br>          }<br>     }<br> <br>  /* If we are carrying video, and both sides are not going to remotely bridge... fail the native bridge */<br>-    if (video_glue0_res != AST_RTP_GLUE_RESULT_FORBID<br>-            && (audio_glue0_res != AST_RTP_GLUE_RESULT_REMOTE<br>-                    || video_glue0_res != AST_RTP_GLUE_RESULT_REMOTE)) {<br>-         audio_glue0_res = AST_RTP_GLUE_RESULT_FORBID;<br>+        if (glue0->video.result != AST_RTP_GLUE_RESULT_FORBID<br>+             && (glue0->audio.result != AST_RTP_GLUE_RESULT_REMOTE<br>+                     || glue0->video.result != AST_RTP_GLUE_RESULT_REMOTE)) {<br>+          glue0->audio.result = AST_RTP_GLUE_RESULT_FORBID;<br>  }<br>-    if (video_glue1_res != AST_RTP_GLUE_RESULT_FORBID<br>-            && (audio_glue1_res != AST_RTP_GLUE_RESULT_REMOTE<br>-                    || video_glue1_res != AST_RTP_GLUE_RESULT_REMOTE)) {<br>-         audio_glue1_res = AST_RTP_GLUE_RESULT_FORBID;<br>+        if (glue1->video.result != AST_RTP_GLUE_RESULT_FORBID<br>+             && (glue1->audio.result != AST_RTP_GLUE_RESULT_REMOTE<br>+                     || glue1->video.result != AST_RTP_GLUE_RESULT_REMOTE)) {<br>+          glue1->audio.result = AST_RTP_GLUE_RESULT_FORBID;<br>  }<br> <br>  /* The order of preference is: forbid, local, and remote. */<br>- if (audio_glue0_res == AST_RTP_GLUE_RESULT_FORBID ||<br>-         audio_glue1_res == AST_RTP_GLUE_RESULT_FORBID) {<br>+     if (glue0->audio.result == AST_RTP_GLUE_RESULT_FORBID<br>+             || glue1->audio.result == AST_RTP_GLUE_RESULT_FORBID) {<br>            /* If any sort of bridge is forbidden just completely bail out and go back to generic bridging */<br>-            return AST_RTP_GLUE_RESULT_FORBID;<br>-   } else if (audio_glue0_res == AST_RTP_GLUE_RESULT_LOCAL ||<br>-           audio_glue1_res == AST_RTP_GLUE_RESULT_LOCAL) {<br>-              return AST_RTP_GLUE_RESULT_LOCAL;<br>+            combined_result = AST_RTP_GLUE_RESULT_FORBID;<br>+        } else if (glue0->audio.result == AST_RTP_GLUE_RESULT_LOCAL<br>+               || glue1->audio.result == AST_RTP_GLUE_RESULT_LOCAL) {<br>+            combined_result = AST_RTP_GLUE_RESULT_LOCAL;<br>  } else {<br>-             return AST_RTP_GLUE_RESULT_REMOTE;<br>+           combined_result = AST_RTP_GLUE_RESULT_REMOTE;<br>         }<br>+    glue0->result = combined_result;<br>+  glue1->result = combined_result;<br>+<br>+       return 0;<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Get the current RTP native bridge combined glue result.<br>+ * \since 15.0.0<br>+ *<br>+ * \param c0 First bridge channel<br>+ * \param c1 Second bridge channel<br>+ *<br>+ * \note Both channels must be locked when calling this function.<br>+ *<br>+ * \return Current combined glue result.<br>+ */<br>+static enum ast_rtp_glue_result rtp_glue_get_current_combined_result(struct ast_channel *c0,<br>+     struct ast_channel *c1)<br>+{<br>+  struct rtp_glue_data glue_a;<br>+ struct rtp_glue_data glue_b;<br>+ struct rtp_glue_data *glue0;<br>+ struct rtp_glue_data *glue1;<br>+ enum ast_rtp_glue_result combined_result;<br>+<br>+ rtp_glue_data_init(&glue_a);<br>+     glue0 = &glue_a;<br>+ rtp_glue_data_init(&glue_b);<br>+     glue1 = &glue_b;<br>+ if (rtp_glue_data_get(c0, glue0, c1, glue1)) {<br>+               return AST_RTP_GLUE_RESULT_FORBID;<br>+   }<br>+<br>+ combined_result = glue0->result;<br>+  rtp_glue_data_destroy(glue0);<br>+        rtp_glue_data_destroy(glue1);<br>+        return combined_result;<br> }<br> <br> /*!<br>@@ -129,52 +267,91 @@<br> {<br>       struct ast_bridge_channel *bc0 = AST_LIST_FIRST(&bridge->channels);<br>    struct ast_bridge_channel *bc1 = AST_LIST_LAST(&bridge->channels);<br>-    enum ast_rtp_glue_result native_type = AST_RTP_GLUE_RESULT_FORBID;<br>-   struct ast_rtp_glue *glue0, *glue1;<br>-  RAII_VAR(struct ast_rtp_instance *, instance0, NULL, ao2_cleanup);<br>-   RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ao2_cleanup);<br>-   RAII_VAR(struct ast_rtp_instance *, vinstance0, NULL, ao2_cleanup);<br>-  RAII_VAR(struct ast_rtp_instance *, vinstance1, NULL, ao2_cleanup);<br>-  RAII_VAR(struct ast_rtp_instance *, tinstance0, NULL, ao2_cleanup);<br>-  RAII_VAR(struct ast_rtp_instance *, tinstance1, NULL, ao2_cleanup);<br>-  RAII_VAR(struct ast_format_cap *, cap0, ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT), ao2_cleanup);<br>-     RAII_VAR(struct ast_format_cap *, cap1, ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT), ao2_cleanup);<br>+     struct native_rtp_bridge_channel_data *data0;<br>+        struct native_rtp_bridge_channel_data *data1;<br>+        struct rtp_glue_data *glue0;<br>+ struct rtp_glue_data *glue1;<br>+ struct ast_format_cap *cap0;<br>+ struct ast_format_cap *cap1;<br>+ enum ast_rtp_glue_result native_type;<br> <br>      if (bc0 == bc1) {<br>             return;<br>       }<br>+    data0 = bc0->tech_pvt;<br>+    data1 = bc1->tech_pvt;<br>+    if (!data0 || !data1) {<br>+              /* Not all channels are joined with the bridge tech yet */<br>+           return;<br>+      }<br>+    glue0 = &data0->glue;<br>+ glue1 = &data1->glue;<br> <br>       ast_channel_lock_both(bc0->chan, bc1->chan);<br>-   if (!bc0->suspended && !bc1->suspended) {<br>-              native_type = native_rtp_bridge_get(bc0->chan, bc1->chan, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1);<br>+<br>+        if (!glue0->cb || !glue1->cb) {<br>+                /*<br>+            * Somebody doesn't have glue data so the bridge isn't running<br>+                *<br>+            * Actually neither side should have glue data.<br>+               */<br>+          ast_assert(!glue0->cb && !glue1->cb);<br>+<br>+               if (rtp_glue_data_get(bc0->chan, glue0, bc1->chan, glue1)) {<br>+                   /*<br>+                    * This might happen if one of the channels got masqueraded<br>+                   * at a critical time.  It's a bit of a stretch even then<br>+                         * since the channel is in a bridge.<br>+                  */<br>+                  goto done;<br>+           }<br>     }<br>+<br>+ ast_debug(2, "Bridge '%s'.  Tech starting '%s' and '%s' with target '%s'\n",<br>+               bridge->uniqueid, ast_channel_name(bc0->chan), ast_channel_name(bc1->chan),<br>+         target ? ast_channel_name(target) : "none");<br>+<br>+    native_type = glue0->result;<br> <br>    switch (native_type) {<br>        case AST_RTP_GLUE_RESULT_LOCAL:<br>-              if (ast_rtp_instance_get_engine(instance0)->local_bridge) {<br>-                       ast_rtp_instance_get_engine(instance0)->local_bridge(instance0, instance1);<br>+               if (ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge) {<br>+                        ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge(glue0->audio.instance, glue1->audio.instance);<br>           }<br>-            if (ast_rtp_instance_get_engine(instance1)->local_bridge) {<br>-                       ast_rtp_instance_get_engine(instance1)->local_bridge(instance1, instance0);<br>+               if (ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge) {<br>+                        ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge(glue1->audio.instance, glue0->audio.instance);<br>           }<br>-            ast_rtp_instance_set_bridged(instance0, instance1);<br>-          ast_rtp_instance_set_bridged(instance1, instance0);<br>+          ast_rtp_instance_set_bridged(glue0->audio.instance, glue1->audio.instance);<br>+            ast_rtp_instance_set_bridged(glue1->audio.instance, glue0->audio.instance);<br>             ast_verb(4, "Locally RTP bridged '%s' and '%s' in stack\n",<br>                         ast_channel_name(bc0->chan), ast_channel_name(bc1->chan));<br>              break;<br>-<br>     case AST_RTP_GLUE_RESULT_REMOTE:<br>-             if (glue0->get_codec) {<br>-                   glue0->get_codec(bc0->chan, cap0);<br>-             }<br>-            if (glue1->get_codec) {<br>-                   glue1->get_codec(bc1->chan, cap1);<br>+             cap0 = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);<br>+            cap1 = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);<br>+            if (!cap0 || !cap1) {<br>+                        ao2_cleanup(cap0);<br>+                   ao2_cleanup(cap1);<br>+                   break;<br>                }<br> <br>-         /* If we have a target, it's the channel that received the UNHOLD or UPDATE_RTP_PEER frame and was told to resume */<br>+             if (glue0->cb->get_codec) {<br>+                    glue0->cb->get_codec(bc0->chan, cap0);<br>+              }<br>+            if (glue1->cb->get_codec) {<br>+                    glue1->cb->get_codec(bc1->chan, cap1);<br>+              }<br>+<br>+         /*<br>+            * If we have a target, it's the channel that received the UNHOLD or<br>+              * UPDATE_RTP_PEER frame and was told to resume<br>+               */<br>           if (!target) {<br>-                       glue0->update_peer(bc0->chan, instance1, vinstance1, tinstance1, cap1, 0);<br>-                     glue1->update_peer(bc1->chan, instance0, vinstance0, tinstance0, cap0, 0);<br>+                     /* Send both channels to remote */<br>+                   data0->remote_cb = glue0->cb;<br>+                  data1->remote_cb = glue1->cb;<br>+                  glue0->cb->update_peer(bc0->chan, glue1->audio.instance, glue1->video.instance, NULL, cap1, 0);<br>+                       glue1->cb->update_peer(bc1->chan, glue0->audio.instance, glue0->video.instance, NULL, cap0, 0);<br>                        ast_verb(4, "Remotely bridged '%s' and '%s' - media will flow directly between them\n",<br>                             ast_channel_name(bc0->chan), ast_channel_name(bc1->chan));<br>              } else {<br>@@ -184,51 +361,121 @@<br>                       * already set up to handle the new media path or will have its own set of updates independent<br>                         * of this pass.<br>                       */<br>+                  ast_debug(2, "Bridge '%s'.  Sending '%s' back to remote\n",<br>+                                bridge->uniqueid, ast_channel_name(target));<br>                       if (bc0->chan == target) {<br>-                                glue0->update_peer(bc0->chan, instance1, vinstance1, tinstance1, cap1, 0);<br>+                             data0->remote_cb = glue0->cb;<br>+                          glue0->cb->update_peer(bc0->chan, glue1->audio.instance, glue1->video.instance, NULL, cap1, 0);<br>                        } else {<br>-                             glue1->update_peer(bc1->chan, instance0, vinstance0, tinstance0, cap0, 0);<br>+                             data1->remote_cb = glue1->cb;<br>+                          glue1->cb->update_peer(bc1->chan, glue0->audio.instance, glue0->video.instance, NULL, cap0, 0);<br>                        }<br>             }<br>+<br>+         ao2_cleanup(cap0);<br>+           ao2_cleanup(cap1);<br>            break;<br>        case AST_RTP_GLUE_RESULT_FORBID:<br>              break;<br>        }<br> <br>+ if (native_type != AST_RTP_GLUE_RESULT_REMOTE) {<br>+             /* Bring any remaining channels back to us. */<br>+               if (data0->remote_cb) {<br>+                   ast_debug(2, "Bridge '%s'.  Bringing back '%s' to us\n",<br>+                           bridge->uniqueid, ast_channel_name(bc0->chan));<br>+                        data0->remote_cb->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);<br>+                        data0->remote_cb = NULL;<br>+          }<br>+            if (data1->remote_cb) {<br>+                   ast_debug(2, "Bridge '%s'.  Bringing back '%s' to us\n",<br>+                           bridge->uniqueid, ast_channel_name(bc1->chan));<br>+                        data1->remote_cb->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);<br>+                        data1->remote_cb = NULL;<br>+          }<br>+    }<br>+<br>+done:<br>  ast_channel_unlock(bc0->chan);<br>     ast_channel_unlock(bc1->chan);<br> }<br> <br>+/*!<br>+ * \internal<br>+ * \brief Stop native RTP bridging of two channels<br>+ *<br>+ * \param bridge The bridge that had native RTP bridging happening on it<br>+ * \param target If remote RTP bridging, the channel that is held.<br>+ *<br>+ * \note The first channel to leave the bridge triggers the cleanup for both channels<br>+ */<br> static void native_rtp_bridge_stop(struct ast_bridge *bridge, struct ast_channel *target)<br> {<br>    struct ast_bridge_channel *bc0 = AST_LIST_FIRST(&bridge->channels);<br>    struct ast_bridge_channel *bc1 = AST_LIST_LAST(&bridge->channels);<br>-    enum ast_rtp_glue_result native_type;<br>-        struct ast_rtp_glue *glue0, *glue1 = NULL;<br>-   RAII_VAR(struct ast_rtp_instance *, instance0, NULL, ao2_cleanup);<br>-   RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ao2_cleanup);<br>-   RAII_VAR(struct ast_rtp_instance *, vinstance0, NULL, ao2_cleanup);<br>-  RAII_VAR(struct ast_rtp_instance *, vinstance1, NULL, ao2_cleanup);<br>+  struct native_rtp_bridge_channel_data *data0;<br>+        struct native_rtp_bridge_channel_data *data1;<br>+        struct rtp_glue_data *glue0;<br>+ struct rtp_glue_data *glue1;<br> <br>       if (bc0 == bc1) {<br>             return;<br>       }<br>+    data0 = bc0->tech_pvt;<br>+    data1 = bc1->tech_pvt;<br>+    if (!data0 || !data1) {<br>+              /* Not all channels are joined with the bridge tech */<br>+               return;<br>+      }<br>+    glue0 = &data0->glue;<br>+ glue1 = &data1->glue;<br>+<br>+      ast_debug(2, "Bridge '%s'.  Tech stopping '%s' and '%s' with target '%s'\n",<br>+               bridge->uniqueid, ast_channel_name(bc0->chan), ast_channel_name(bc1->chan),<br>+         target ? ast_channel_name(target) : "none");<br>+<br>+    if (!glue0->cb || !glue1->cb) {<br>+                /*<br>+            * Somebody doesn't have glue data so the bridge isn't running<br>+                *<br>+            * Actually neither side should have glue data.<br>+               */<br>+          ast_assert(!glue0->cb && !glue1->cb);<br>+          /* At most one channel can be left at the remote endpoint here. */<br>+           ast_assert(!data0->remote_cb || !data1->remote_cb);<br>+<br>+         /* Bring selected channel streams back to us */<br>+              if (data0->remote_cb && (!target || target == bc0->chan)) {<br>+                    ast_channel_lock(bc0->chan);<br>+                      ast_debug(2, "Bridge '%s'.  Bringing back '%s' to us\n",<br>+                           bridge->uniqueid, ast_channel_name(bc0->chan));<br>+                        data0->remote_cb->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);<br>+                        data0->remote_cb = NULL;<br>+                  ast_channel_unlock(bc0->chan);<br>+            }<br>+            if (data1->remote_cb && (!target || target == bc1->chan)) {<br>+                    ast_channel_lock(bc1->chan);<br>+                      ast_debug(2, "Bridge '%s'.  Bringing back '%s' to us\n",<br>+                           bridge->uniqueid, ast_channel_name(bc1->chan));<br>+                        data1->remote_cb->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);<br>+                        data1->remote_cb = NULL;<br>+                  ast_channel_unlock(bc1->chan);<br>+            }<br>+            return;<br>+      }<br> <br>  ast_channel_lock_both(bc0->chan, bc1->chan);<br>-   native_type = native_rtp_bridge_get(bc0->chan, bc1->chan, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1);<br> <br>-        switch (native_type) {<br>+       switch (glue0->result) {<br>   case AST_RTP_GLUE_RESULT_LOCAL:<br>-              if (ast_rtp_instance_get_engine(instance0)->local_bridge) {<br>-                       ast_rtp_instance_get_engine(instance0)->local_bridge(instance0, NULL);<br>+            if (ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge) {<br>+                        ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge(glue0->audio.instance, NULL);<br>               }<br>-            if (instance1 && ast_rtp_instance_get_engine(instance1)->local_bridge) {<br>-                  ast_rtp_instance_get_engine(instance1)->local_bridge(instance1, NULL);<br>+            if (ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge) {<br>+                        ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge(glue1->audio.instance, NULL);<br>               }<br>-            ast_rtp_instance_set_bridged(instance0, NULL);<br>-               if (instance1) {<br>-                     ast_rtp_instance_set_bridged(instance1, NULL);<br>-               }<br>+            ast_rtp_instance_set_bridged(glue0->audio.instance, NULL);<br>+                ast_rtp_instance_set_bridged(glue1->audio.instance, NULL);<br>                 break;<br>        case AST_RTP_GLUE_RESULT_REMOTE:<br>              if (target) {<br>@@ -236,10 +483,38 @@<br>                   * If a target was provided, it is being put on hold and should expect to<br>                      * receive media from Asterisk instead of what it was previously connected to.<br>                         */<br>+                  ast_debug(2, "Bridge '%s'.  Bringing back '%s' to us\n",<br>+                           bridge->uniqueid, ast_channel_name(target));<br>                       if (bc0->chan == target) {<br>-                                glue0->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);<br>+                              data0->remote_cb = NULL;<br>+                          glue0->cb->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);<br>                        } else {<br>-                             glue1->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);<br>+                              data1->remote_cb = NULL;<br>+                          glue1->cb->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);<br>+                       }<br>+            } else {<br>+                     data0->remote_cb = NULL;<br>+                  data1->remote_cb = NULL;<br>+                  /*<br>+                    * XXX We don't want to bring back the channels if we are<br>+                         * switching to T.38.  We have received a reinvite on one channel<br>+                     * and we will be sending a reinvite on the other to start T.38.<br>+                      * If we bring the streams back now we confuse the chan_pjsip<br>+                         * channel driver processing the incoming T.38 reinvite with<br>+                  * reinvite glare.  I think this is really a bug in chan_pjsip<br>+                        * that this exception case is working around.<br>+                        */<br>+                  if (rtp_glue_get_current_combined_result(bc0->chan, bc1->chan)<br>+                         != AST_RTP_GLUE_RESULT_FORBID) {<br>+                             ast_debug(2, "Bridge '%s'.  Bringing back '%s' and '%s' to us\n",<br>+                                  bridge->uniqueid, ast_channel_name(bc0->chan),<br>+                                 ast_channel_name(bc1->chan));<br>+                             glue0->cb->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);<br>+                               glue1->cb->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);<br>+                       } else {<br>+                             ast_debug(2, "Bridge '%s'.  Skip bringing back '%s' and '%s' to us\n",<br>+                                     bridge->uniqueid, ast_channel_name(bc0->chan),<br>+                                 ast_channel_name(bc1->chan));<br>                      }<br>             }<br>             break;<br>@@ -247,10 +522,8 @@<br>          break;<br>        }<br> <br>- if (!target && native_type != AST_RTP_GLUE_RESULT_FORBID) {<br>-          glue0->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);<br>-              glue1->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);<br>-      }<br>+    rtp_glue_data_reset(glue0);<br>+  rtp_glue_data_reset(glue1);<br> <br>        ast_debug(2, "Discontinued RTP bridging of '%s' and '%s' - media will flow through Asterisk core\n",<br>                ast_channel_name(bc0->chan), ast_channel_name(bc1->chan));<br>@@ -259,11 +532,15 @@<br>       ast_channel_unlock(bc1->chan);<br> }<br> <br>-/*! \brief Frame hook that is called to intercept hold/unhold */<br>-static struct ast_frame *native_rtp_framehook(struct ast_channel *chan, struct ast_frame *f, enum ast_framehook_event event, void *data)<br>+/*!<br>+ * \internal<br>+ * \brief Frame hook that is called to intercept hold/unhold<br>+ */<br>+static struct ast_frame *native_rtp_framehook(struct ast_channel *chan,<br>+   struct ast_frame *f, enum ast_framehook_event event, void *data)<br> {<br>  RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);<br>-    struct native_rtp_bridge_data *native_data = data;<br>+   struct native_rtp_framehook_data *native_data = data;<br> <br>      if (!f || (event != AST_FRAMEHOOK_EVENT_WRITE)) {<br>             return f;<br>@@ -293,39 +570,49 @@<br>              }<br>             ast_bridge_unlock(bridge);<br>            ast_channel_lock(chan);<br>-<br>    }<br> <br>  return f;<br> }<br> <br>-/*! \brief Callback function which informs upstream if we are consuming a frame of a specific type */<br>+/*!<br>+ * \internal<br>+ * \brief Callback function which informs upstream if we are consuming a frame of a specific type<br>+ */<br> static int native_rtp_framehook_consume(void *data, enum ast_frame_type type)<br> {<br>   return (type == AST_FRAME_CONTROL ? 1 : 0);<br> }<br> <br>-/*! \brief Internal helper function which checks whether the channels are compatible with our native bridging */<br>+/*!<br>+ * \internal<br>+ * \brief Internal helper function which checks whether a channel is compatible with our native bridging<br>+ */<br> static int native_rtp_bridge_capable(struct ast_channel *chan)<br> {<br>      return !ast_channel_has_hook_requiring_audio(chan);<br> }<br> <br>+/*!<br>+ * \internal<br>+ * \brief Internal helper function which checks whether both channels are compatible with our native bridging<br>+ */<br> static int native_rtp_bridge_compatible_check(struct ast_bridge *bridge, struct ast_bridge_channel *bc0, struct ast_bridge_channel *bc1)<br> {<br>  enum ast_rtp_glue_result native_type;<br>-        struct ast_rtp_glue *glue0;<br>-  struct ast_rtp_glue *glue1;<br>-  RAII_VAR(struct ast_rtp_instance *, instance0, NULL, ao2_cleanup);<br>-   RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ao2_cleanup);<br>-   RAII_VAR(struct ast_rtp_instance *, vinstance0, NULL, ao2_cleanup);<br>-  RAII_VAR(struct ast_rtp_instance *, vinstance1, NULL, ao2_cleanup);<br>-  RAII_VAR(struct ast_format_cap *, cap0, NULL, ao2_cleanup);<br>-  RAII_VAR(struct ast_format_cap *, cap1, NULL, ao2_cleanup);<br>   int read_ptime0;<br>      int read_ptime1;<br>      int write_ptime0;<br>     int write_ptime1;<br>+    struct rtp_glue_data glue_a;<br>+ struct rtp_glue_data glue_b;<br>+ RAII_VAR(struct ast_format_cap *, cap0, NULL, ao2_cleanup);<br>+  RAII_VAR(struct ast_format_cap *, cap1, NULL, ao2_cleanup);<br>+  RAII_VAR(struct rtp_glue_data *, glue0, NULL, rtp_glue_data_destroy);<br>+        RAII_VAR(struct rtp_glue_data *, glue1, NULL, rtp_glue_data_destroy);<br>+<br>+     ast_debug(1, "Bridge '%s'.  Checking compatability for channels '%s' and '%s'\n",<br>+          bridge->uniqueid, ast_channel_name(bc0->chan), ast_channel_name(bc1->chan));<br> <br>      if (!native_rtp_bridge_capable(bc0->chan)) {<br>               ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has features which prevent it\n",<br>@@ -339,8 +626,17 @@<br>             return 0;<br>     }<br> <br>- native_type = native_rtp_bridge_get(bc0->chan, bc1->chan, &glue0, &glue1,<br>-              &instance0, &instance1, &vinstance0, &vinstance1);<br>+   rtp_glue_data_init(&glue_a);<br>+     glue0 = &glue_a;<br>+ rtp_glue_data_init(&glue_b);<br>+     glue1 = &glue_b;<br>+ if (rtp_glue_data_get(bc0->chan, glue0, bc1->chan, glue1)) {<br>+           ast_debug(1, "Bridge '%s' can not use native RTP bridge as could not get details\n",<br>+                       bridge->uniqueid);<br>+                return 0;<br>+    }<br>+    native_type = glue0->result;<br>+<br>    if (native_type == AST_RTP_GLUE_RESULT_FORBID) {<br>              ast_debug(1, "Bridge '%s' can not use native RTP bridge as it was forbidden while getting details\n",<br>                       bridge->uniqueid);<br>@@ -348,25 +644,25 @@<br>  }<br> <br>  if (ao2_container_count(bc0->features->dtmf_hooks)<br>-             && ast_rtp_instance_dtmf_mode_get(instance0)) {<br>+              && ast_rtp_instance_dtmf_mode_get(glue0->audio.instance)) {<br>                ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has DTMF hooks\n",<br>                  bridge->uniqueid, ast_channel_name(bc0->chan));<br>                 return 0;<br>     }<br> <br>  if (ao2_container_count(bc1->features->dtmf_hooks)<br>-             && ast_rtp_instance_dtmf_mode_get(instance1)) {<br>+              && ast_rtp_instance_dtmf_mode_get(glue1->audio.instance)) {<br>                ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has DTMF hooks\n",<br>                  bridge->uniqueid, ast_channel_name(bc1->chan));<br>                 return 0;<br>     }<br> <br>  if (native_type == AST_RTP_GLUE_RESULT_LOCAL<br>-         && (ast_rtp_instance_get_engine(instance0)->local_bridge<br>-                  != ast_rtp_instance_get_engine(instance1)->local_bridge<br>-                   || (ast_rtp_instance_get_engine(instance0)->dtmf_compatible<br>-                               && !ast_rtp_instance_get_engine(instance0)->dtmf_compatible(bc0->chan,<br>-                                 instance0, bc1->chan, instance1)))) {<br>+             && (ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge<br>+                   != ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge<br>+                    || (ast_rtp_instance_get_engine(glue0->audio.instance)->dtmf_compatible<br>+                                && !ast_rtp_instance_get_engine(glue0->audio.instance)->dtmf_compatible(bc0->chan,<br>+                                  glue0->audio.instance, bc1->chan, glue1->audio.instance)))) {<br>                ast_debug(1, "Bridge '%s' can not use local native RTP bridge as local bridge or DTMF is not compatible\n",<br>                         bridge->uniqueid);<br>                 return 0;<br>@@ -379,11 +675,11 @@<br>      }<br> <br>  /* Make sure that codecs match */<br>-    if (glue0->get_codec) {<br>-           glue0->get_codec(bc0->chan, cap0);<br>+     if (glue0->cb->get_codec) {<br>+            glue0->cb->get_codec(bc0->chan, cap0);<br>       }<br>-    if (glue1->get_codec) {<br>-           glue1->get_codec(bc1->chan, cap1);<br>+     if (glue1->cb->get_codec) {<br>+            glue1->cb->get_codec(bc1->chan, cap1);<br>       }<br>     if (ast_format_cap_count(cap0) != 0<br>           && ast_format_cap_count(cap1) != 0<br>@@ -413,6 +709,10 @@<br>      return 1;<br> }<br> <br>+/*!<br>+ * \internal<br>+ * \brief Called by the bridge core "compatible' callback<br>+ */<br> static int native_rtp_bridge_compatible(struct ast_bridge *bridge)<br> {<br>     struct ast_bridge_channel *bc0;<br>@@ -437,10 +737,13 @@<br>        return is_compatible;<br> }<br> <br>-/*! \brief Helper function which adds frame hook to bridge channel */<br>+/*!<br>+ * \internal<br>+ * \brief Helper function which adds frame hook to bridge channel<br>+ */<br> static int native_rtp_bridge_framehook_attach(struct ast_bridge_channel *bridge_channel)<br> {<br>-   struct native_rtp_bridge_data *data = ao2_alloc(sizeof(*data), NULL);<br>+        struct native_rtp_bridge_channel_data *data = bridge_channel->tech_pvt;<br>    static struct ast_framehook_interface hook = {<br>                .version = AST_FRAMEHOOK_INTERFACE_VERSION,<br>           .event_cb = native_rtp_framehook,<br>@@ -449,45 +752,82 @@<br>              .disable_inheritance = 1,<br>     };<br> <br>-        if (!data) {<br>+ ast_assert(data->hook_data == NULL);<br>+      data->hook_data = ao2_alloc_options(sizeof(*data->hook_data), NULL,<br>+            AO2_ALLOC_OPT_LOCK_NOLOCK);<br>+  if (!data->hook_data) {<br>            return -1;<br>    }<br>+<br>+ ast_debug(2, "Bridge '%s'.  Attaching hook data %p to '%s'\n",<br>+             bridge_channel->bridge->uniqueid, data, ast_channel_name(bridge_channel->chan));<br> <br>  ast_channel_lock(bridge_channel->chan);<br>-   hook.data = ao2_bump(data);<br>-  data->id = ast_framehook_attach(bridge_channel->chan, &hook);<br>+      /* We're giving 1 ref to the framehook and keeping the one from the alloc for ourselves */<br>+       hook.data = ao2_bump(data->hook_data);<br>+    data->hook_data->id = ast_framehook_attach(bridge_channel->chan, &hook);<br>         ast_channel_unlock(bridge_channel->chan);<br>- if (data->id < 0) {<br>-            /* We need to drop both the reference we hold, and the one the framehook would hold */<br>-               ao2_ref(data, -2);<br>+   if (data->hook_data->id < 0) {<br>+              /*<br>+            * We need to drop both the reference we hold in data,<br>+                * and the one the framehook would hold.<br>+              */<br>+          ao2_ref(data->hook_data, -2);<br>+             data->hook_data = NULL;<br>+<br>                 return -1;<br>    }<br>-<br>- bridge_channel->tech_pvt = data;<br> <br>        return 0;<br> }<br> <br>-/*! \brief Helper function which removes frame hook from bridge channel */<br>+/*!<br>+ * \internal<br>+ * \brief Helper function which removes frame hook from bridge channel<br>+ */<br> static void native_rtp_bridge_framehook_detach(struct ast_bridge_channel *bridge_channel)<br> {<br>-    RAII_VAR(struct native_rtp_bridge_data *, data, bridge_channel->tech_pvt, ao2_cleanup);<br>+   struct native_rtp_bridge_channel_data *data = bridge_channel->tech_pvt;<br> <br>-        if (!data) {<br>+ if (!data || !data->hook_data) {<br>           return;<br>       }<br> <br>+ ast_debug(2, "Bridge '%s'.  Detaching hook data %p from '%s'\n",<br>+           bridge_channel->bridge->uniqueid, data->hook_data, ast_channel_name(bridge_channel->chan));<br>+<br>    ast_channel_lock(bridge_channel->chan);<br>-   ast_framehook_detach(bridge_channel->chan, data->id);<br>-  data->detached = 1;<br>+       ast_framehook_detach(bridge_channel->chan, data->hook_data->id);<br>+    data->hook_data->detached = 1;<br>  ast_channel_unlock(bridge_channel->chan);<br>- bridge_channel->tech_pvt = NULL;<br>+  ao2_cleanup(data->hook_data);<br>+     data->hook_data = NULL;<br> }<br> <br>+/*!<br>+ * \internal<br>+ * \brief Called by the bridge core 'join' callback for each channel joining he bridge<br>+ */<br> static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)<br> {<br>-  native_rtp_bridge_framehook_detach(bridge_channel);<br>+  ast_debug(2, "Bridge '%s'.  Channel '%s' is joining bridge tech\n",<br>+                bridge->uniqueid, ast_channel_name(bridge_channel->chan));<br>+<br>+  ast_assert(bridge_channel->tech_pvt == NULL);<br>+<br>+  if (bridge_channel->suspended) {<br>+          /* The channel will rejoin when it is unsuspended */<br>+         return 0;<br>+    }<br>+<br>+ bridge_channel->tech_pvt = native_rtp_bridge_channel_data_alloc();<br>+        if (!bridge_channel->tech_pvt) {<br>+          return -1;<br>+   }<br>+<br>  if (native_rtp_bridge_framehook_attach(bridge_channel)) {<br>+            native_rtp_bridge_channel_data_free(bridge_channel->tech_pvt);<br>+            bridge_channel->tech_pvt = NULL;<br>           return -1;<br>    }<br> <br>@@ -495,15 +835,46 @@<br>   return 0;<br> }<br> <br>+/*!<br>+ * \internal<br>+ * \brief Add the channel back into the bridge<br>+ */<br> static void native_rtp_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)<br> {<br>+     ast_debug(2, "Bridge '%s'.  Channel '%s' is unsuspended back to bridge tech\n",<br>+            bridge->uniqueid, ast_channel_name(bridge_channel->chan));<br>      native_rtp_bridge_join(bridge, bridge_channel);<br> }<br> <br>+/*!<br>+ * \internal<br>+ * \brief Leave the bridge<br>+ */<br> static void native_rtp_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)<br> {<br>+       ast_debug(2, "Bridge '%s'.  Channel '%s' is leaving bridge tech\n",<br>+                bridge->uniqueid, ast_channel_name(bridge_channel->chan));<br>+<br>+  if (!bridge_channel->tech_pvt) {<br>+          return;<br>+      }<br>+<br>  native_rtp_bridge_framehook_detach(bridge_channel);<br>   native_rtp_bridge_stop(bridge, NULL);<br>+<br>+     native_rtp_bridge_channel_data_free(bridge_channel->tech_pvt);<br>+    bridge_channel->tech_pvt = NULL;<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Suspend the channel from the bridge<br>+ */<br>+static void native_rtp_bridge_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)<br>+{<br>+      ast_debug(2, "Bridge '%s'.  Channel '%s' is suspending from bridge tech\n",<br>+                bridge->uniqueid, ast_channel_name(bridge_channel->chan));<br>+     native_rtp_bridge_leave(bridge, bridge_channel);<br> }<br> <br> static int native_rtp_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)<br>@@ -548,7 +919,7 @@<br>      .join = native_rtp_bridge_join,<br>       .unsuspend = native_rtp_bridge_unsuspend,<br>     .leave = native_rtp_bridge_leave,<br>-    .suspend = native_rtp_bridge_leave,<br>+  .suspend = native_rtp_bridge_suspend,<br>         .write = native_rtp_bridge_write,<br>     .compatible = native_rtp_bridge_compatible,<br> };<br>diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h<br>index 5f43916..36f499b 100644<br>--- a/include/asterisk/rtp_engine.h<br>+++ b/include/asterisk/rtp_engine.h<br>@@ -640,12 +640,13 @@<br>  /*!<br>    * \brief Used to prevent two channels from remotely bridging audio rtp if the channel tech has a<br>      *        reason for prohibiting it based on qualities that need to be compared from both channels.<br>-   * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, function this is not used.<br>+       * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, this function is not used.<br>        */<br>   int (*allow_rtp_remote)(struct ast_channel *chan1, struct ast_rtp_instance *instance);<br>        /*!<br>    * \brief Callback for retrieving the RTP instance carrying video<br>      * \note This function increases the reference count on the returned RTP instance.<br>+    * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, this function is not used.<br>        */<br>   enum ast_rtp_glue_result (*get_vrtp_info)(struct ast_channel *chan, struct ast_rtp_instance **instance);<br>      /*!<br>@@ -658,11 +659,15 @@<br>    /*!<br>    * \brief Callback for retrieving the RTP instance carrying text<br>       * \note This function increases the reference count on the returned RTP instance.<br>+    * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, this function is not used.<br>        */<br>   enum ast_rtp_glue_result (*get_trtp_info)(struct ast_channel *chan, struct ast_rtp_instance **instance);<br>      /*! Callback for updating the destination that the remote side should send RTP to */<br>  int (*update_peer)(struct ast_channel *chan, struct ast_rtp_instance *instance, struct ast_rtp_instance *vinstance, struct ast_rtp_instance *tinstance, const struct ast_format_cap *cap, int nat_active);<br>-   /*! Callback for retrieving codecs that the channel can do.  Result returned in result_cap. */<br>+       /*!<br>+   * \brief Callback for retrieving codecs that the channel can do.  Result returned in result_cap.<br>+     * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, this function is not used.<br>+       */<br>   void (*get_codec)(struct ast_channel *chan, struct ast_format_cap *result_cap);<br>       /*! Linked list information */<br>        AST_RWLIST_ENTRY(ast_rtp_glue) entry;<br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/5856">change 5856</a>. To unsubscribe, 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/5856"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-MessageType: merged </div>
<div style="display:none"> Gerrit-Change-Id: I9e1ac49fa4af68d64826ccccd152593cf8cdb21a </div>
<div style="display:none"> Gerrit-Change-Number: 5856 </div>
<div style="display:none"> Gerrit-PatchSet: 5 </div>
<div style="display:none"> Gerrit-Owner: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins2 </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Kevin Harwell <kharwell@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Richard Mudgett <rmudgett@digium.com> </div>