<p>Joshua Colp has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.asterisk.org/8884">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">rtp: Add support for RTP extension negotiation and abs-send-time.<br><br>When RTP was originally created it had the ability to place a single<br>extension in an RTP packet. In practice people wanted to potentially<br>put multiple extensions in one and so RFC 5285 (obsoleted by RFC<br>8285) came into existence. This allows RTP extensions to be negotiated<br>with a unique identifier to be used in the RTP packet, allowing<br>multiple extensions to be present in the packet.<br><br>This change extends the RTP engine API to add support for this. A<br>user of it can enable extensions and the API provides the ability to<br>retrieve the information (to construct SDP for example) and to provide<br>negotiated information (from SDP). The end result is that the RTP<br>engine can then query to see if the extension has been negotiated and<br>what unique identifier is to be used. It is then up to the RTP engine<br>implementation to construct the packet appropriately.<br><br>The first extension to use this support is abs-send-time which is<br>defined in the REMB draft[1] and is a second timestamp placed in an<br>RTP packet which is for when the packet has left the sending system.<br>It is used to more accurately determine the available bandwidth.<br><br>ASTERISK-27813<br><br>[1] https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03<br><br>Change-Id: I508deac557867b1e27fc7339be890c8018171588<br>---<br>M include/asterisk/rtp_engine.h<br>M main/rtp_engine.c<br>M res/res_pjsip_sdp_rtp.c<br>M res/res_rtp_asterisk.c<br>4 files changed, 618 insertions(+), 8 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/84/8884/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h<br>index 3426b2a..47d6fdc 100644<br>--- a/include/asterisk/rtp_engine.h<br>+++ b/include/asterisk/rtp_engine.h<br>@@ -533,6 +533,18 @@<br>   AST_RTP_DTLS_VERIFY_CERTIFICATE = (1 << 1), /*!< Verify the certificate */<br> };<br> <br>+/*!<br>+ * \brief Known RTP extensions<br>+ */<br>+enum ast_rtp_extension {<br>+  /* Per the RFC 0 should not be used, so we treat it as an unsupported extension placeholder */<br>+       AST_RTP_EXTENSION_UNSUPPORTED = 0,<br>+   /* abs-send-time from https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03 */<br>+  AST_RTP_EXTENSION_ABS_SEND_TIME,<br>+     /* The maximum number of known RTP extensions */<br>+     AST_RTP_EXTENSION_MAX,<br>+};<br>+<br> /*! \brief DTLS configuration structure */<br> struct ast_rtp_dtls_cfg {<br>       unsigned int enabled:1;                /*!< Whether DTLS support is enabled or not */<br>@@ -654,6 +666,8 @@<br>         struct ast_rtp_engine_ice *ice;<br>       /*! Callback to pointer for optional DTLS SRTP support */<br>     struct ast_rtp_engine_dtls *dtls;<br>+    /*! Callback to enable an RTP extension (returns non-zero if supported) */<br>+   int (*extension_enable)(struct ast_rtp_instance *instance, enum ast_rtp_extension extension);<br>         /*! Linked list information */<br>        AST_RWLIST_ENTRY(ast_rtp_engine) entry;<br> };<br>@@ -718,6 +732,22 @@<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>+};<br>+<br>+/*!<br>+ * \brief Directions for RTP extensions<br>+ */<br>+enum ast_rtp_extension_direction {<br>+     /* The extension is not negotiated and is not flowing */<br>+     AST_RTP_EXTENSION_DIRECTION_NONE,<br>+    /* Send and receive */<br>+       AST_RTP_EXTENSION_DIRECTION_SENDRECV,<br>+        /*! Send only */<br>+     AST_RTP_EXTENSION_DIRECTION_SENDONLY,<br>+        /*! Receive only */<br>+  AST_RTP_EXTENSION_DIRECTION_RECVONLY,<br>+        /*! Negotiated but not sending or receiving */<br>+       AST_RTP_EXTENSION_DIRECTION_INACTIVE,<br> };<br> <br> /*!<br>@@ -1247,6 +1277,109 @@<br> struct ast_rtp_codecs *ast_rtp_instance_get_codecs(struct ast_rtp_instance *instance);<br> <br> /*!<br>+ * \brief Enable support for an RTP extension on an instance<br>+ *<br>+ * \param instance The RTP instance to enable the extension on<br>+ * \param id The unique local identifier to use for this extension (-1 to have one auto selected)<br>+ * \param extension The RTP extension<br>+ * \param direction The initial direction that the RTP extension should be used in<br>+ *<br>+ * \retval 0 success<br>+ * \retval -1 failure<br>+ *<br>+ * \since 15.5.0<br>+ */<br>+int ast_rtp_instance_extmap_enable(struct ast_rtp_instance *instance, int id, enum ast_rtp_extension extension,<br>+     enum ast_rtp_extension_direction direction);<br>+<br>+/*!<br>+ * \brief Negotiate received RTP extension information<br>+ *<br>+ * \param instance The RTP instance to set the extension on<br>+ * \param id The local identifier for the extension<br>+ * \param direction The direction that the extension should be used in<br>+ * \param uri The unique URI for the extension<br>+ * \param attributes Attributes specific to this extension<br>+ *<br>+ * \retval 0 success<br>+ * \retval -1 failure<br>+ *<br>+ * \since 15.5.0<br>+ */<br>+int ast_rtp_instance_extmap_negotiate(struct ast_rtp_instance *instance, int id, enum ast_rtp_extension_direction direction,<br>+      const char *uri, const char *attributes);<br>+<br>+/*!<br>+ * \brief Clear negotiated RTP extension information<br>+ *<br>+ * \param instance The RTP instance to clear negotiated extension information on<br>+ */<br>+void ast_rtp_instance_extmap_clear(struct ast_rtp_instance *instance);<br>+<br>+/*!<br>+ * \brief Retrieve the id for an RTP extension<br>+ *<br>+ * \param instance The RTP instance to retrieve the id from<br>+ * \param extension The RTP extension<br>+ *<br>+ * \retval -1 not negotiated<br>+ * \retval id if negotiated<br>+ *<br>+ * \since 15.5.0<br>+ */<br>+int ast_rtp_instance_extmap_get_id(struct ast_rtp_instance *instance, enum ast_rtp_extension extension);<br>+<br>+/*!<br>+ * \brief Get the number of known unique identifiers<br>+ *<br>+ * \param instance The RTP instance to retrieve the count from<br>+ *<br>+ * \return the number of known unique identifiers<br>+ *<br>+ * \since 15.5.0<br>+ */<br>+size_t ast_rtp_instance_extmap_count(struct ast_rtp_instance *instance);<br>+<br>+/*!<br>+ * \brief Retrieve the extension for an RTP extension id<br>+ *<br>+ * \param instance The RTP instance to retrieve the extension from<br>+ * \param id The negotiated RTP extension id<br>+ *<br>+ * \retval extension the extension that maps to the id<br>+ *<br>+ * \since 15.5.0<br>+ *<br>+ * \note This will return AST_RTP_EXTENSION_UNSUPPORTED if an extension was proposed for this unique identifier<br>+ * but it is not supported or if the unique identifier is unused.<br>+ */<br>+enum ast_rtp_extension ast_rtp_instance_extmap_get_extension(struct ast_rtp_instance *instance, int id);<br>+<br>+/*!<br>+ * \brief Retrieve the negotiated direction for an RTP extension id<br>+ *<br>+ * \param instance The RTP instance to retrieve the direction from<br>+ * \param id The negotiated RTP extension id<br>+ *<br>+ * \retval direction the direction that has been negotiated<br>+ *<br>+ * \since 15.5.0<br>+ */<br>+enum ast_rtp_extension_direction ast_rtp_instance_extmap_get_direction(struct ast_rtp_instance *instance, int id);<br>+<br>+/*!<br>+ * \brief Retrieve the URI for an RTP extension id<br>+ *<br>+ * \param instance The RTP instance to retrieve the direction from<br>+ * \param id The negotiated RTP extension id<br>+ *<br>+ * \retval uri The URI for the RTP extension<br>+ *<br>+ * \since 15.5.0<br>+ */<br>+const char *ast_rtp_instance_extmap_get_uri(struct ast_rtp_instance *instance, int id);<br>+<br>+/*!<br>  * \brief Initialize an RTP codecs structure<br>  *<br>  * \param codecs The codecs structure to initialize<br>diff --git a/main/rtp_engine.c b/main/rtp_engine.c<br>index 627605a..1cdf282 100644<br>--- a/main/rtp_engine.c<br>+++ b/main/rtp_engine.c<br>@@ -177,6 +177,14 @@<br> struct ast_srtp_res *res_srtp = NULL;<br> struct ast_srtp_policy_res *res_srtp_policy = NULL;<br> <br>+/*! Structure that contains extmap negotiation information */<br>+struct rtp_extmap {<br>+        /*! The RTP extension */<br>+     enum ast_rtp_extension extension;<br>+    /*! The current negotiated direction */<br>+      enum ast_rtp_extension_direction direction;<br>+};<br>+<br> /*! Structure that represents an RTP session (instance) */<br> struct ast_rtp_instance {<br>  /*! Engine that is handling this RTP instance */<br>@@ -213,6 +221,20 @@<br>        time_t last_tx;<br>       /*! Time of last packet received */<br>   time_t last_rx;<br>+      /*! Enabled RTP extensions */<br>+        AST_VECTOR(, enum ast_rtp_extension_direction) extmap_enabled;<br>+       /*! Negotiated RTP extensions (using index based on extension) */<br>+    AST_VECTOR(, int) extmap_negotiated;<br>+ /*! Negotiated RTP extensions (using index based on unique id) */<br>+    AST_VECTOR(, struct rtp_extmap) extmap_unique_ids;<br>+};<br>+<br>+/*!<br>+ * \brief URIs for known RTP extensions<br>+ */<br>+static const char * const rtp_extension_uris[AST_RTP_EXTENSION_MAX] = {<br>+   [AST_RTP_EXTENSION_UNSUPPORTED]         = "",<br>+      [AST_RTP_EXTENSION_ABS_SEND_TIME]       = "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time",<br> };<br> <br> /*! List of RTP engines that are currently registered */<br>@@ -408,6 +430,10 @@<br> <br>         ast_rtp_codecs_payloads_destroy(&instance->codecs);<br> <br>+        AST_VECTOR_FREE(&instance->extmap_enabled);<br>+   AST_VECTOR_FREE(&instance->extmap_negotiated);<br>+        AST_VECTOR_FREE(&instance->extmap_unique_ids);<br>+<br>      /* Drop our engine reference */<br>       ast_module_unref(instance->engine->mod);<br> <br>@@ -470,6 +496,14 @@<br>       ast_sockaddr_copy(&address, sa);<br> <br>       if (ast_rtp_codecs_payloads_initialize(&instance->codecs)) {<br>+          ao2_ref(instance, -1);<br>+               return NULL;<br>+ }<br>+<br>+ /* Initialize RTP extension support */<br>+       if (AST_VECTOR_INIT(&instance->extmap_enabled, 0) ||<br>+          AST_VECTOR_INIT(&instance->extmap_negotiated, 0) ||<br>+           AST_VECTOR_INIT(&instance->extmap_unique_ids, 0)) {<br>            ao2_ref(instance, -1);<br>                return NULL;<br>  }<br>@@ -680,6 +714,227 @@<br>      return &instance->codecs;<br> }<br> <br>+int ast_rtp_instance_extmap_enable(struct ast_rtp_instance *instance, int id, enum ast_rtp_extension extension,<br>+    enum ast_rtp_extension_direction direction)<br>+{<br>+      struct rtp_extmap extmap = {<br>+         .extension = extension,<br>+              .direction = direction,<br>+      };<br>+<br>+        ao2_lock(instance);<br>+<br>+       if (!instance->engine->extension_enable || !instance->engine->extension_enable(instance, extension)) {<br>+           ao2_unlock(instance);<br>+                return 0;<br>+    }<br>+<br>+ /* We store enabled extensions separately so we can easily do negotiation */<br>+ if (AST_VECTOR_REPLACE(&instance->extmap_enabled, extension, direction)) {<br>+            ao2_unlock(instance);<br>+                return -1;<br>+   }<br>+<br>+ if (id == -1) {<br>+              /* We find a free unique identifier for this extension by just appending it to the<br>+            * vector of unique ids. The size of the vector will become its unique identifier.<br>+            * As well when we are asking for information on the extensions it will be returned,<br>+          * allowing it to be added to the SDP offer.<br>+          */<br>+          if (AST_VECTOR_APPEND(&instance->extmap_unique_ids, extmap)) {<br>+                        AST_VECTOR_REPLACE(&instance->extmap_enabled, extension, AST_RTP_EXTENSION_DIRECTION_NONE);<br>+                   ao2_unlock(instance);<br>+                        return -1;<br>+           }<br>+            id = AST_VECTOR_SIZE(&instance->extmap_unique_ids);<br>+   } else {<br>+             /* Otherwise we put it precisely where they want it */<br>+               if (AST_VECTOR_REPLACE(&instance->extmap_unique_ids, id - 1, extmap)) {<br>+                       AST_VECTOR_REPLACE(&instance->extmap_enabled, extension, AST_RTP_EXTENSION_DIRECTION_NONE);<br>+                   ao2_unlock(instance);<br>+                        return -1;<br>+           }<br>+    }<br>+<br>+ /* Now that we have an id add the extension to here */<br>+       if (AST_VECTOR_REPLACE(&instance->extmap_negotiated, extension, id)) {<br>+                extmap.extension = AST_RTP_EXTENSION_UNSUPPORTED;<br>+            extmap.direction = AST_RTP_EXTENSION_DIRECTION_NONE;<br>+         AST_VECTOR_REPLACE(&instance->extmap_enabled, extension, AST_RTP_EXTENSION_DIRECTION_NONE);<br>+           AST_VECTOR_REPLACE(&instance->extmap_unique_ids, id - 1, extmap);<br>+             ao2_unlock(instance);<br>+                return -1;<br>+   }<br>+<br>+ ao2_unlock(instance);<br>+<br>+     return 0;<br>+}<br>+<br>+/*! \brief Helper function which negotiates two RTP extension directions to get our current direction */<br>+static enum ast_rtp_extension_direction rtp_extmap_negotiate_direction(enum ast_rtp_extension_direction ours,<br>+  enum ast_rtp_extension_direction theirs)<br>+{<br>+ if (theirs == AST_RTP_EXTENSION_DIRECTION_NONE || ours == AST_RTP_EXTENSION_DIRECTION_NONE) {<br>+                /* This should not occur but if it does tolerate either side not having this extension<br>+                * in use.<br>+            */<br>+          return AST_RTP_EXTENSION_DIRECTION_NONE;<br>+     } else if (theirs == AST_RTP_EXTENSION_DIRECTION_INACTIVE) {<br>+         /* Inactive is always inactive on our side */<br>+                return AST_RTP_EXTENSION_DIRECTION_INACTIVE;<br>+ } else if (theirs == AST_RTP_EXTENSION_DIRECTION_SENDRECV) {<br>+         return ours;<br>+ } else if (theirs == AST_RTP_EXTENSION_DIRECTION_SENDONLY) {<br>+         /* If they are send only then we become recvonly if we are configured as sendrecv or recvonly */<br>+             if (ours == AST_RTP_EXTENSION_DIRECTION_SENDRECV || ours == AST_RTP_EXTENSION_DIRECTION_RECVONLY) {<br>+                  return AST_RTP_EXTENSION_DIRECTION_RECVONLY;<br>+         }<br>+    } else if (theirs == AST_RTP_EXTENSION_DIRECTION_RECVONLY) {<br>+         /* If they are recv only then we become sendonly if we are configured as sendrecv or sendonly */<br>+             if (ours == AST_RTP_EXTENSION_DIRECTION_SENDRECV || ours == AST_RTP_EXTENSION_DIRECTION_SENDONLY) {<br>+                  return AST_RTP_EXTENSION_DIRECTION_SENDONLY;<br>+         }<br>+    }<br>+<br>+ return AST_RTP_EXTENSION_DIRECTION_NONE;<br>+}<br>+<br>+int ast_rtp_instance_extmap_negotiate(struct ast_rtp_instance *instance, int id, enum ast_rtp_extension_direction direction,<br>+       const char *uri, const char *attributes)<br>+{<br>+ /* 'attributes' is currently unused but exists in the API to ensure it does not need to be altered<br>+    * in the future in case we need to use it.<br>+   */<br>+  int idx;<br>+     enum ast_rtp_extension extension = AST_RTP_EXTENSION_UNSUPPORTED;<br>+<br>+ /* Convert the provided URI to the internal representation */<br>+        for (idx = 0; idx < ARRAY_LEN(rtp_extension_uris); ++idx) {<br>+               if (!strcasecmp(rtp_extension_uris[idx], uri)) {<br>+                     extension = idx;<br>+                     break;<br>+               }<br>+    }<br>+<br>+ ao2_lock(instance);<br>+  /* We only accept the extension if it is enabled */<br>+  if (extension < AST_VECTOR_SIZE(&instance->extmap_enabled) &&<br>+              AST_VECTOR_GET(&instance->extmap_enabled, extension) != AST_RTP_EXTENSION_DIRECTION_NONE) {<br>+           struct rtp_extmap extmap = {<br>+                 .extension = extension,<br>+                      .direction = rtp_extmap_negotiate_direction(AST_VECTOR_GET(&instance->extmap_enabled, extension), direction),<br>+         };<br>+<br>+                /* If the direction negotiation failed then don't accept or use this extension */<br>+                if (extmap.direction != AST_RTP_EXTENSION_DIRECTION_NONE) {<br>+                  if (extension != AST_RTP_EXTENSION_UNSUPPORTED) {<br>+                            AST_VECTOR_REPLACE(&instance->extmap_negotiated, extension, id);<br>+                      }<br>+                    AST_VECTOR_REPLACE(&instance->extmap_unique_ids, id - 1, extmap);<br>+             }<br>+    }<br>+    ao2_unlock(instance);<br>+<br>+     return 0;<br>+}<br>+<br>+void ast_rtp_instance_extmap_clear(struct ast_rtp_instance *instance)<br>+{<br>+ struct rtp_extmap extmap_none = {<br>+            .extension = AST_RTP_EXTENSION_UNSUPPORTED,<br>+          .direction = AST_RTP_EXTENSION_DIRECTION_NONE,<br>+       };<br>+   int idx;<br>+<br>+  ao2_lock(instance);<br>+<br>+       /* Clear both the known unique ids and the negotiated extensions as we are about to have<br>+      * new results set on us.<br>+     */<br>+  for (idx = 0; idx < AST_VECTOR_SIZE(&instance->extmap_unique_ids); ++idx) {<br>+                AST_VECTOR_REPLACE(&instance->extmap_unique_ids, idx, extmap_none);<br>+   }<br>+<br>+ for (idx = 0; idx < AST_VECTOR_SIZE(&instance->extmap_negotiated); ++idx) {<br>+                AST_VECTOR_REPLACE(&instance->extmap_negotiated, idx, -1);<br>+    }<br>+<br>+ ao2_unlock(instance);<br>+}<br>+<br>+int ast_rtp_instance_extmap_get_id(struct ast_rtp_instance *instance, enum ast_rtp_extension extension)<br>+{<br>+   int id = -1;<br>+<br>+      ao2_lock(instance);<br>+  if (extension < AST_VECTOR_SIZE(&instance->extmap_negotiated)) {<br>+           id = AST_VECTOR_GET(&instance->extmap_negotiated, extension);<br>+ }<br>+    ao2_unlock(instance);<br>+<br>+     return id;<br>+}<br>+<br>+size_t ast_rtp_instance_extmap_count(struct ast_rtp_instance *instance)<br>+{<br>+      size_t count = 0;<br>+<br>+ ao2_lock(instance);<br>+  count = AST_VECTOR_SIZE(&instance->extmap_unique_ids);<br>+        ao2_unlock(instance);<br>+<br>+     return count;<br>+}<br>+<br>+enum ast_rtp_extension ast_rtp_instance_extmap_get_extension(struct ast_rtp_instance *instance, int id)<br>+{<br>+   enum ast_rtp_extension extension = AST_RTP_EXTENSION_UNSUPPORTED;<br>+<br>+ ao2_lock(instance);<br>+<br>+       /* The local unique identifier starts at '1' so the highest unique identifier<br>+         * can be the actual size of the vector. We compensate (as it is 0 index based)<br>+       * by dropping it down to 1 to get the correct information.<br>+   */<br>+  if (id <= AST_VECTOR_SIZE(&instance->extmap_unique_ids)) {<br>+         struct rtp_extmap *extmap = AST_VECTOR_GET_ADDR(&instance->extmap_unique_ids, id - 1);<br>+<br>+             extension = extmap->extension;<br>+    }<br>+    ao2_unlock(instance);<br>+<br>+     return extension;<br>+}<br>+<br>+enum ast_rtp_extension_direction ast_rtp_instance_extmap_get_direction(struct ast_rtp_instance *instance, int id)<br>+{<br>+     enum ast_rtp_extension_direction direction = AST_RTP_EXTENSION_DIRECTION_NONE;<br>+<br>+    ao2_lock(instance);<br>+<br>+       if (id <= AST_VECTOR_SIZE(&instance->extmap_unique_ids)) {<br>+         struct rtp_extmap *extmap = AST_VECTOR_GET_ADDR(&instance->extmap_unique_ids, id - 1);<br>+<br>+             direction = extmap->direction;<br>+    }<br>+    ao2_unlock(instance);<br>+<br>+     return direction;<br>+}<br>+<br>+const char *ast_rtp_instance_extmap_get_uri(struct ast_rtp_instance *instance, int id)<br>+{<br>+        enum ast_rtp_extension extension = ast_rtp_instance_extmap_get_extension(instance, id);<br>+<br>+   if (extension == AST_RTP_EXTENSION_UNSUPPORTED ||<br>+            (unsigned int)extension >= ARRAY_LEN(rtp_extension_uris)) {<br>+               return NULL;<br>+ }<br>+<br>+ return rtp_extension_uris[extension];<br>+}<br>+<br> int ast_rtp_codecs_payloads_initialize(struct ast_rtp_codecs *codecs)<br> {<br>      int res;<br>diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c<br>index 14ed3b1..161afb0 100644<br>--- a/res/res_pjsip_sdp_rtp.c<br>+++ b/res/res_pjsip_sdp_rtp.c<br>@@ -151,8 +151,52 @@<br>   ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RTCP, rtcp_type);<br> }<br> <br>+/*!<br>+ * \brief Enable an RTP extension on an RTP session.<br>+ */<br>+static void enable_rtp_extension(struct ast_sip_session *session, struct ast_sip_session_media *session_media,<br>+       enum ast_rtp_extension extension, enum ast_rtp_extension_direction direction,<br>+        const pjmedia_sdp_session *sdp)<br>+{<br>+  int idx = -1;<br>+<br>+     /* For a bundle group the local unique identifier space is shared across all streams within<br>+   * it.<br>+        */<br>+  if (session_media->bundle_group != -1) {<br>+          int index;<br>+<br>+                for (index = 0; index < sdp->media_count; ++index) {<br>+                   struct ast_sip_session_media *other_session_media;<br>+                   int other_id;<br>+<br>+                     other_session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, index);<br>+                      if (!other_session_media->rtp || other_session_media->bundle_group != session_media->bundle_group) {<br>+                                continue;<br>+                    }<br>+<br>+                 other_id = ast_rtp_instance_extmap_get_id(other_session_media->rtp, extension);<br>+                   if (other_id == -1) {<br>+                                /* Worst case we have to fall back to the highest available free local unique identifier<br>+                              * for the bundle group.<br>+                              */<br>+                          if ((ast_rtp_instance_extmap_count(other_session_media->rtp) + 1) > idx) {<br>+                                     idx = ast_rtp_instance_extmap_count(other_session_media->rtp) + 1;<br>+                                }<br>+                            continue;<br>+                    }<br>+<br>+                 idx = other_id;<br>+                      break;<br>+               }<br>+    }<br>+<br>+ ast_rtp_instance_extmap_enable(session_media->rtp, idx, extension, direction);<br>+}<br>+<br> /*! \brief Internal function which creates an RTP instance */<br>-static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_media *session_media)<br>+static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_media *session_media,<br>+ const pjmedia_sdp_session *sdp)<br> {<br>   struct ast_rtp_engine_ice *ice;<br>       struct ast_sockaddr temp_media_address;<br>@@ -223,6 +267,7 @@<br>          ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RETRANS_RECV, session->endpoint->media.webrtc);<br>               ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RETRANS_SEND, session->endpoint->media.webrtc);<br>               ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_REMB, session->endpoint->media.webrtc);<br>+              enable_rtp_extension(session, session_media, AST_RTP_EXTENSION_ABS_SEND_TIME, AST_RTP_EXTENSION_DIRECTION_SENDRECV, sdp);<br>             if (session->endpoint->media.tos_video || session->endpoint->media.cos_video) {<br>                   ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_video,<br>                                     session->endpoint->media.cos_video, "SIP RTP Video");<br>@@ -1101,6 +1146,111 @@<br>        pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr);<br> }<br> <br>+static void add_extmap_to_stream(struct ast_sip_session *session,<br>+    struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media)<br>+{<br>+  int idx;<br>+     char tmp[256];<br>+<br>+    if (!session->endpoint->media.webrtc || session_media->type != AST_MEDIA_TYPE_VIDEO) {<br>+              return;<br>+      }<br>+<br>+ /* RTP extension local unique identifiers start at '1' */<br>+    for (idx = 1; idx <= ast_rtp_instance_extmap_count(session_media->rtp); ++idx) {<br>+               enum ast_rtp_extension extension = ast_rtp_instance_extmap_get_extension(session_media->rtp, idx);<br>+                const char *direction_str;<br>+           pj_str_t stmp;<br>+               pjmedia_sdp_attr *attr;<br>+<br>+           /* If this is an unsupported RTP extension we can't place it into the SDP */<br>+             if (extension == AST_RTP_EXTENSION_UNSUPPORTED) {<br>+                    continue;<br>+            }<br>+<br>+         switch (ast_rtp_instance_extmap_get_direction(session_media->rtp, idx)) {<br>+         case AST_RTP_EXTENSION_DIRECTION_SENDRECV:<br>+                   /* Lack of a direction indicates sendrecv, so we leave it out */<br>+                     direction_str = "";<br>+                        break;<br>+               case AST_RTP_EXTENSION_DIRECTION_SENDONLY:<br>+                   direction_str = "/sendonly";<br>+                       break;<br>+               case AST_RTP_EXTENSION_DIRECTION_RECVONLY:<br>+                   direction_str = "/recvonly";<br>+                       break;<br>+               case AST_RTP_EXTENSION_DIRECTION_NONE:<br>+                       /* It is impossible for a "none" direction extension to be negotiated but just in case<br>+                      * we treat it as inactive.<br>+                   */<br>+          case AST_RTP_EXTENSION_DIRECTION_INACTIVE:<br>+                   direction_str = "/inactive";<br>+                       break;<br>+               }<br>+<br>+         snprintf(tmp, sizeof(tmp), "%d%s %s", idx, direction_str, ast_rtp_instance_extmap_get_uri(session_media->rtp, idx));<br>+            attr = pjmedia_sdp_attr_create(pool, "extmap", pj_cstr(&stmp, tmp));<br>+           pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr);<br>+       }<br>+}<br>+<br>+/*! \brief Function which processes extmap attributes in a stream */<br>+static void process_extmap_attributes(struct ast_sip_session *session, struct ast_sip_session_media *session_media,<br>+                                   const struct pjmedia_sdp_media *remote_stream)<br>+{<br>+        int index;<br>+<br>+        if (!session->endpoint->media.webrtc || session_media->type != AST_MEDIA_TYPE_VIDEO) {<br>+              return;<br>+      }<br>+<br>+ ast_rtp_instance_extmap_clear(session_media->rtp);<br>+<br>+     for (index = 0; index < remote_stream->attr_count; ++index) {<br>+          pjmedia_sdp_attr *attr = remote_stream->attr[index];<br>+              char attr_value[pj_strlen(&attr->value) + 1];<br>+         char *uri_str;<br>+               int id;<br>+              char direction_str[10] = "", uri[64], attributes[64] = "";<br>+               enum ast_rtp_extension_direction direction = AST_RTP_EXTENSION_DIRECTION_SENDRECV;<br>+<br>+                /* We only care about extmap attributes */<br>+           if (pj_strcmp2(&attr->name, "extmap")) {<br>+                    continue;<br>+            }<br>+<br>+         ast_copy_pj_str(attr_value, &attr->value, sizeof(attr_value));<br>+<br>+             /* Split the combined unique identifier and direction away from the URI and attributes for easier parsing */<br>+         uri_str = strchr(attr_value, ' ');<br>+           if (ast_strlen_zero(uri_str)) {<br>+                      continue;<br>+            }<br>+            *uri_str++ = '\0';<br>+<br>+                if (sscanf(attr_value, "%30d%9s", &id, direction_str) < 1) {<br>+                        /* We require at a minimum the unique identifier */<br>+                  continue;<br>+            }<br>+<br>+         /* Convert from the string to the internal representation */<br>+         if (!strcasecmp(direction_str, "/sendonly")) {<br>+                     direction = AST_RTP_EXTENSION_DIRECTION_SENDONLY;<br>+            } else if (!strcasecmp(direction_str, "/recvonly")) {<br>+                      direction = AST_RTP_EXTENSION_DIRECTION_RECVONLY;<br>+            } else if (!strcasecmp(direction_str, "/inactive")) {<br>+                      direction = AST_RTP_EXTENSION_DIRECTION_INACTIVE;<br>+            }<br>+<br>+         if (sscanf(uri_str, "%63s %63s", uri, attributes) < 1) {<br>+                        /* We require at a minimum the URI */<br>+                        continue;<br>+            }<br>+<br>+         ast_rtp_instance_extmap_negotiate(session_media->rtp, id, direction, uri, attributes);<br>+    }<br>+}<br>+<br> /*! \brief Function which negotiates an incoming media stream */<br> static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,<br>       struct ast_sip_session_media *session_media, const pjmedia_sdp_session *sdp,<br>@@ -1139,11 +1289,12 @@<br>         }<br> <br>  /* Using the connection information create an appropriate RTP instance */<br>-    if (!session_media->rtp && create_rtp(session, session_media)) {<br>+  if (!session_media->rtp && create_rtp(session, session_media, sdp)) {<br>              return -1;<br>    }<br> <br>  process_ssrc_attributes(session, session_media, stream);<br>+     process_extmap_attributes(session, session_media, stream);<br>    session_media_transport = ast_sip_session_media_get_transport(session, session_media);<br> <br>     if (session_media_transport == session_media || !session_media->bundled) {<br>@@ -1377,7 +1528,7 @@<br>          return 1;<br>     }<br> <br>- if (!session_media->rtp && create_rtp(session, session_media)) {<br>+  if (!session_media->rtp && create_rtp(session, session_media, sdp)) {<br>              return -1;<br>    }<br> <br>@@ -1602,6 +1753,7 @@<br>   add_ssrc_to_stream(session, session_media, pool, media);<br>      add_msid_to_stream(session, session_media, pool, media, stream);<br>      add_rtcp_fb_to_stream(session, session_media, pool, media);<br>+  add_extmap_to_stream(session, session_media, pool, media);<br> <br>         /* Add the media stream to the SDP */<br>         sdp->media[sdp->media_count++] = media;<br>@@ -1676,11 +1828,12 @@<br>        }<br> <br>  /* Create an RTP instance if need be */<br>-      if (!session_media->rtp && create_rtp(session, session_media)) {<br>+  if (!session_media->rtp && create_rtp(session, session_media, local)) {<br>            return -1;<br>    }<br> <br>  process_ssrc_attributes(session, session_media, remote_stream);<br>+      process_extmap_attributes(session, session_media, remote_stream);<br> <br>  session_media_transport = ast_sip_session_media_get_transport(session, session_media);<br> <br>diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c<br>index 4ac20d5..9f8c48c 100644<br>--- a/res/res_rtp_asterisk.c<br>+++ b/res/res_rtp_asterisk.c<br>@@ -550,6 +550,7 @@<br> static const char *ast_rtp_get_cname(struct ast_rtp_instance *instance);<br> static void ast_rtp_set_remote_ssrc(struct ast_rtp_instance *instance, unsigned int ssrc);<br> static void ast_rtp_set_stream_num(struct ast_rtp_instance *instance, int stream_num);<br>+static int ast_rtp_extension_enable(struct ast_rtp_instance *instance, enum ast_rtp_extension extension);<br> static int ast_rtp_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent);<br> <br> #ifdef HAVE_OPENSSL_SRTP<br>@@ -2248,6 +2249,7 @@<br>       .cname_get = ast_rtp_get_cname,<br>       .set_remote_ssrc = ast_rtp_set_remote_ssrc,<br>   .set_stream_num = ast_rtp_set_stream_num,<br>+    .extension_enable = ast_rtp_extension_enable,<br>         .bundle = ast_rtp_bundle,<br> };<br> <br>@@ -4281,6 +4283,20 @@<br>     return res;<br> }<br> <br>+static void put_unaligned_time24(void *p, uint32_t time_msw, uint32_t time_lsw)<br>+{<br>+     unsigned char *cp = p;<br>+       uint32_t datum;<br>+<br>+   /* Convert the time to 6.18 format */<br>+        datum = (time_msw << 18) & 0x00fc0000;<br>+     datum |= (time_lsw >> 14) & 0x0003ffff;<br>+<br>+ cp[0] = datum >> 16;<br>+   cp[1] = datum >> 8;<br>+    cp[2] = datum;<br>+}<br>+<br> /*! \pre instance is locked */<br> static int rtp_raw_write(struct ast_rtp_instance *instance, struct ast_frame *frame, int codec)<br> {<br>@@ -4398,13 +4414,40 @@<br> <br>      /* If we know the remote address construct a packet and send it out */<br>        if (!ast_sockaddr_isnull(&remote_address)) {<br>-             int hdrlen = 12, res, ice;<br>-           int packet_len = frame->datalen + hdrlen;<br>-         unsigned char *rtpheader = (unsigned char *)(frame->data.ptr - hdrlen);<br>+           int hdrlen = 12, res, ice, ext = 0, abs_send_time_id;<br>+                int packet_len;<br>+              unsigned char *rtpheader;<br> <br>-         put_unaligned_uint32(rtpheader, htonl((2 << 30) | (codec << 16) | (seqno) | (mark << 23)));<br>+                /* If the abs-send-time extension has been negotiated determine how much space we need */<br>+            abs_send_time_id = ast_rtp_instance_extmap_get_id(instance, AST_RTP_EXTENSION_ABS_SEND_TIME);<br>+                if (abs_send_time_id != -1) {<br>+                        /* 4 bytes for the shared information, 4 bytes for abs-send-time */<br>+                  hdrlen += 8;<br>+                 ext = 1;<br>+             }<br>+<br>+         packet_len = frame->datalen + hdrlen;<br>+             rtpheader = (unsigned char *)(frame->data.ptr - hdrlen);<br>+<br>+               put_unaligned_uint32(rtpheader, htonl((2 << 30) | (ext << 28) | (codec << 16) | (seqno) | (mark << 23)));<br>             put_unaligned_uint32(rtpheader + 4, htonl(rtp->lastts));<br>           put_unaligned_uint32(rtpheader + 8, htonl(rtp->ssrc));<br>+<br>+         /* We assume right now that we will only ever have the abs-send-time extension in the packet<br>+          * which simplifies things a bit.<br>+             */<br>+          if (abs_send_time_id != -1) {<br>+                        unsigned int now_msw, now_lsw;<br>+<br>+                    /* This happens before being placed into the retransmission buffer so that when we<br>+                    * retransmit we only have to update the timestamp, not everything else.<br>+                      */<br>+                  put_unaligned_uint32(rtpheader + 12, htonl((0xBEDE << 16) | 1));<br>+                       put_unaligned_uint32(rtpheader + 16, htonl((abs_send_time_id << 28) | (2 << 24)));<br>+<br>+                    timeval2ntp(ast_tvnow(), &now_msw, &now_lsw);<br>+                        put_unaligned_time24(rtpheader + 17, now_msw, now_lsw);<br>+              }<br> <br>          /* If retransmissions are enabled, we need to store this packet for future use */<br>             if (rtp->send_buffer) {<br>@@ -5262,10 +5305,17 @@<br>   unsigned int pid;       /* Packet ID which refers to seqno of lost packet */<br>  unsigned int blp;       /* Bitmask of following lost packets */<br>       struct ast_sockaddr remote_address = { {0,} };<br>+       int abs_send_time_id;<br>+        unsigned int now_msw = 0, now_lsw = 0;<br> <br>     if (!rtp->send_buffer) {<br>           ast_debug(1, "Tried to handle NACK request, but we don't have a RTP packet storage!\n");<br>                return res;<br>+  }<br>+<br>+ abs_send_time_id = ast_rtp_instance_extmap_get_id(instance, AST_RTP_EXTENSION_ABS_SEND_TIME);<br>+        if (abs_send_time_id != -1) {<br>+                timeval2ntp(ast_tvnow(), &now_msw, &now_lsw);<br>         }<br> <br>  ast_rtp_instance_get_remote_address(instance, &remote_address);<br>@@ -5280,6 +5330,12 @@<br>           /* We know the remote end is missing this packet. Go ahead and send it if we still have it. */<br>                payload = (struct ast_rtp_rtcp_nack_payload *)ast_data_buffer_get(rtp->send_buffer, pid);<br>          if (payload) {<br>+                       if (abs_send_time_id != -1) {<br>+                                /* On retransmission we need to update the timestamp within the packet, as it<br>+                                 * is supposed to contain when the packet was actually sent.<br>+                          */<br>+                          put_unaligned_time24(payload->buf + 17, now_msw, now_lsw);<br>+                        }<br>                     res += rtp_sendto(instance, payload->buf, payload->size, 0, &remote_address, &ice);<br>             } else {<br>                      ast_debug(1, "Received NACK request for RTP packet with seqno %d, but we don't have it\n", pid);<br>@@ -5298,6 +5354,9 @@<br>                                 unsigned int seqno = (pid + blp_index) % 65536;<br>                               payload = (struct ast_rtp_rtcp_nack_payload *)ast_data_buffer_get(rtp->send_buffer, seqno);<br>                                if (payload) {<br>+                                       if (abs_send_time_id != -1) {<br>+                                                put_unaligned_time24(payload->buf + 17, now_msw, now_lsw);<br>+                                        }<br>                                     res += rtp_sendto(instance, payload->buf, payload->size, 0, &remote_address, &ice);<br>                             } else {<br>                                      ast_debug(1, "Remote end also requested RTP packet with seqno %d, but we don't have it\n", seqno);<br>@@ -7158,6 +7217,16 @@<br>      rtp->stream_num = stream_num;<br> }<br> <br>+static int ast_rtp_extension_enable(struct ast_rtp_instance *instance, enum ast_rtp_extension extension)<br>+{<br>+       switch (extension) {<br>+ case AST_RTP_EXTENSION_ABS_SEND_TIME:<br>+                return 1;<br>+    default:<br>+             return 0;<br>+    }<br>+}<br>+<br> /*! \pre child is locked */<br> static int ast_rtp_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent)<br> {<br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/8884">change 8884</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/8884"/><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: newchange </div>
<div style="display:none"> Gerrit-Change-Id: I508deac557867b1e27fc7339be890c8018171588 </div>
<div style="display:none"> Gerrit-Change-Number: 8884 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Joshua Colp <jcolp@digium.com> </div>