<p>Joshua Colp has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.asterisk.org/8883">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/83/8883/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 5242e81..22c4ddf 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>@@ -464,6 +490,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>@@ -674,6 +708,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 f6fb17d..4bc8d9e 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/8883">change 8883</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/8883"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 15 </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: 8883 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Joshua Colp <jcolp@digium.com> </div>