diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h index 670dbbe15df9472c712b0cfe159434ffe7292e19..98d77734fa18c345d66ea4b4a5e81e21dc6d4c4a 100644 --- a/include/asterisk/rtp_engine.h +++ b/include/asterisk/rtp_engine.h @@ -533,6 +533,18 @@ enum ast_rtp_dtls_verify { AST_RTP_DTLS_VERIFY_CERTIFICATE = (1 << 1), /*!< Verify the certificate */ }; +/*! + * \brief Known RTP extensions + */ +enum ast_rtp_extension { + /*! Per the RFC 0 should not be used, so we treat it as an unsupported extension placeholder */ + AST_RTP_EXTENSION_UNSUPPORTED = 0, + /*! abs-send-time from https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03 */ + AST_RTP_EXTENSION_ABS_SEND_TIME, + /*! The maximum number of known RTP extensions */ + AST_RTP_EXTENSION_MAX, +}; + /*! \brief DTLS configuration structure */ struct ast_rtp_dtls_cfg { unsigned int enabled:1; /*!< Whether DTLS support is enabled or not */ @@ -654,6 +666,8 @@ struct ast_rtp_engine { struct ast_rtp_engine_ice *ice; /*! Callback to pointer for optional DTLS SRTP support */ struct ast_rtp_engine_dtls *dtls; + /*! Callback to enable an RTP extension (returns non-zero if supported) */ + int (*extension_enable)(struct ast_rtp_instance *instance, enum ast_rtp_extension extension); /*! Linked list information */ AST_RWLIST_ENTRY(ast_rtp_engine) entry; }; @@ -720,6 +734,22 @@ struct ast_rtp_glue { AST_RWLIST_ENTRY(ast_rtp_glue) entry; }; +/*! + * \brief Directions for RTP extensions + */ +enum ast_rtp_extension_direction { + /*! The extension is not negotiated and is not flowing */ + AST_RTP_EXTENSION_DIRECTION_NONE, + /*! Send and receive */ + AST_RTP_EXTENSION_DIRECTION_SENDRECV, + /*! Send only */ + AST_RTP_EXTENSION_DIRECTION_SENDONLY, + /*! Receive only */ + AST_RTP_EXTENSION_DIRECTION_RECVONLY, + /*! Negotiated but not sending or receiving */ + AST_RTP_EXTENSION_DIRECTION_INACTIVE, +}; + /*! * \brief Allocation routine for \ref ast_rtp_payload_type * @@ -1246,6 +1276,109 @@ int ast_rtp_instance_get_prop(struct ast_rtp_instance *instance, enum ast_rtp_pr */ struct ast_rtp_codecs *ast_rtp_instance_get_codecs(struct ast_rtp_instance *instance); +/*! + * \brief Enable support for an RTP extension on an instance + * + * \param instance The RTP instance to enable the extension on + * \param id The unique local identifier to use for this extension (-1 to have one auto selected) + * \param extension The RTP extension + * \param direction The initial direction that the RTP extension should be used in + * + * \retval 0 success + * \retval -1 failure + * + * \since 15.5.0 + */ +int ast_rtp_instance_extmap_enable(struct ast_rtp_instance *instance, int id, enum ast_rtp_extension extension, + enum ast_rtp_extension_direction direction); + +/*! + * \brief Negotiate received RTP extension information + * + * \param instance The RTP instance to set the extension on + * \param id The local identifier for the extension + * \param direction The direction that the extension should be used in + * \param uri The unique URI for the extension + * \param attributes Attributes specific to this extension (if NULL or empty then no attributes) + * + * \retval 0 success + * \retval -1 failure + * + * \since 15.5.0 + */ +int ast_rtp_instance_extmap_negotiate(struct ast_rtp_instance *instance, int id, enum ast_rtp_extension_direction direction, + const char *uri, const char *attributes); + +/*! + * \brief Clear negotiated RTP extension information + * + * \param instance The RTP instance to clear negotiated extension information on + */ +void ast_rtp_instance_extmap_clear(struct ast_rtp_instance *instance); + +/*! + * \brief Retrieve the id for an RTP extension + * + * \param instance The RTP instance to retrieve the id from + * \param extension The RTP extension + * + * \retval -1 not negotiated + * \retval id if negotiated + * + * \since 15.5.0 + */ +int ast_rtp_instance_extmap_get_id(struct ast_rtp_instance *instance, enum ast_rtp_extension extension); + +/*! + * \brief Get the number of known unique identifiers + * + * \param instance The RTP instance to retrieve the count from + * + * \return the number of known unique identifiers + * + * \since 15.5.0 + */ +size_t ast_rtp_instance_extmap_count(struct ast_rtp_instance *instance); + +/*! + * \brief Retrieve the extension for an RTP extension id + * + * \param instance The RTP instance to retrieve the extension from + * \param id The negotiated RTP extension id + * + * \retval extension the extension that maps to the id + * + * \since 15.5.0 + * + * \note This will return AST_RTP_EXTENSION_UNSUPPORTED if an extension was proposed for this unique identifier + * but it is not supported or if the unique identifier is unused. + */ +enum ast_rtp_extension ast_rtp_instance_extmap_get_extension(struct ast_rtp_instance *instance, int id); + +/*! + * \brief Retrieve the negotiated direction for an RTP extension id + * + * \param instance The RTP instance to retrieve the direction from + * \param id The negotiated RTP extension id + * + * \retval direction the direction that has been negotiated + * + * \since 15.5.0 + */ +enum ast_rtp_extension_direction ast_rtp_instance_extmap_get_direction(struct ast_rtp_instance *instance, int id); + +/*! + * \brief Retrieve the URI for an RTP extension id + * + * \param instance The RTP instance to retrieve the direction from + * \param id The negotiated RTP extension id + * + * \retval uri The URI for the RTP extension + * + * \since 15.5.0 + */ +const char *ast_rtp_instance_extmap_get_uri(struct ast_rtp_instance *instance, int id); + /*! * \brief Initialize an RTP codecs structure * diff --git a/main/rtp_engine.c b/main/rtp_engine.c index 3c8f6e82fabf075967e3deec7a1f64e54719856e..c11027af2f11e845c3c1cbd366e37244b7aae5ca 100644 --- a/main/rtp_engine.c +++ b/main/rtp_engine.c @@ -177,6 +177,14 @@ struct ast_srtp_res *res_srtp = NULL; struct ast_srtp_policy_res *res_srtp_policy = NULL; +/*! Structure that contains extmap negotiation information */ +struct rtp_extmap { + /*! The RTP extension */ + enum ast_rtp_extension extension; + /*! The current negotiated direction */ + enum ast_rtp_extension_direction direction; +}; + /*! Structure that represents an RTP session (instance) */ struct ast_rtp_instance { /*! Engine that is handling this RTP instance */ @@ -213,6 +221,20 @@ struct ast_rtp_instance { time_t last_tx; /*! Time of last packet received */ time_t last_rx; + /*! Enabled RTP extensions */ + AST_VECTOR(, enum ast_rtp_extension_direction) extmap_enabled; + /*! Negotiated RTP extensions (using index based on extension) */ + AST_VECTOR(, int) extmap_negotiated; + /*! Negotiated RTP extensions (using index based on unique id) */ + AST_VECTOR(, struct rtp_extmap) extmap_unique_ids; +}; + +/*! + * \brief URIs for known RTP extensions + */ +static const char * const rtp_extension_uris[AST_RTP_EXTENSION_MAX] = { + [AST_RTP_EXTENSION_UNSUPPORTED] = "", + [AST_RTP_EXTENSION_ABS_SEND_TIME] = "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time", }; /*! List of RTP engines that are currently registered */ @@ -408,6 +430,10 @@ static void instance_destructor(void *obj) ast_rtp_codecs_payloads_destroy(&instance->codecs); + AST_VECTOR_FREE(&instance->extmap_enabled); + AST_VECTOR_FREE(&instance->extmap_negotiated); + AST_VECTOR_FREE(&instance->extmap_unique_ids); + /* Drop our engine reference */ ast_module_unref(instance->engine->mod); @@ -474,6 +500,14 @@ struct ast_rtp_instance *ast_rtp_instance_new(const char *engine_name, return NULL; } + /* Initialize RTP extension support */ + if (AST_VECTOR_INIT(&instance->extmap_enabled, 0) || + AST_VECTOR_INIT(&instance->extmap_negotiated, 0) || + AST_VECTOR_INIT(&instance->extmap_unique_ids, 0)) { + ao2_ref(instance, -1); + return NULL; + } + ast_debug(1, "Using engine '%s' for RTP instance '%p'\n", engine->name, instance); /* @@ -680,6 +714,232 @@ struct ast_rtp_codecs *ast_rtp_instance_get_codecs(struct ast_rtp_instance *inst return &instance->codecs; } +int ast_rtp_instance_extmap_enable(struct ast_rtp_instance *instance, int id, enum ast_rtp_extension extension, + enum ast_rtp_extension_direction direction) +{ + struct rtp_extmap extmap = { + .extension = extension, + .direction = direction, + }; + + ao2_lock(instance); + + if (!instance->engine->extension_enable || !instance->engine->extension_enable(instance, extension)) { + ao2_unlock(instance); + return 0; + } + + /* We store enabled extensions separately so we can easily do negotiation */ + if (AST_VECTOR_REPLACE(&instance->extmap_enabled, extension, direction)) { + ao2_unlock(instance); + return -1; + } + + if (id <= 0) { + /* We find a free unique identifier for this extension by just appending it to the + * vector of unique ids. The size of the vector will become its unique identifier. + * As well when we are asking for information on the extensions it will be returned, + * allowing it to be added to the SDP offer. + */ + if (AST_VECTOR_APPEND(&instance->extmap_unique_ids, extmap)) { + AST_VECTOR_REPLACE(&instance->extmap_enabled, extension, AST_RTP_EXTENSION_DIRECTION_NONE); + ao2_unlock(instance); + return -1; + } + id = AST_VECTOR_SIZE(&instance->extmap_unique_ids); + } else { + /* Otherwise we put it precisely where they want it */ + if (AST_VECTOR_REPLACE(&instance->extmap_unique_ids, id - 1, extmap)) { + AST_VECTOR_REPLACE(&instance->extmap_enabled, extension, AST_RTP_EXTENSION_DIRECTION_NONE); + ao2_unlock(instance); + return -1; + } + } + + /* Now that we have an id add the extension to here */ + if (AST_VECTOR_REPLACE(&instance->extmap_negotiated, extension, id)) { + extmap.extension = AST_RTP_EXTENSION_UNSUPPORTED; + extmap.direction = AST_RTP_EXTENSION_DIRECTION_NONE; + AST_VECTOR_REPLACE(&instance->extmap_enabled, extension, AST_RTP_EXTENSION_DIRECTION_NONE); + AST_VECTOR_REPLACE(&instance->extmap_unique_ids, id - 1, extmap); + ao2_unlock(instance); + return -1; + } + + ao2_unlock(instance); + + return 0; +} + +/*! \brief Helper function which negotiates two RTP extension directions to get our current direction */ +static enum ast_rtp_extension_direction rtp_extmap_negotiate_direction(enum ast_rtp_extension_direction ours, + enum ast_rtp_extension_direction theirs) +{ + if (theirs == AST_RTP_EXTENSION_DIRECTION_NONE || ours == AST_RTP_EXTENSION_DIRECTION_NONE) { + /* This should not occur but if it does tolerate either side not having this extension + * in use. + */ + return AST_RTP_EXTENSION_DIRECTION_NONE; + } else if (theirs == AST_RTP_EXTENSION_DIRECTION_INACTIVE) { + /* Inactive is always inactive on our side */ + return AST_RTP_EXTENSION_DIRECTION_INACTIVE; + } else if (theirs == AST_RTP_EXTENSION_DIRECTION_SENDRECV) { + return ours; + } else if (theirs == AST_RTP_EXTENSION_DIRECTION_SENDONLY) { + /* If they are send only then we become recvonly if we are configured as sendrecv or recvonly */ + if (ours == AST_RTP_EXTENSION_DIRECTION_SENDRECV || ours == AST_RTP_EXTENSION_DIRECTION_RECVONLY) { + return AST_RTP_EXTENSION_DIRECTION_RECVONLY; + } + } else if (theirs == AST_RTP_EXTENSION_DIRECTION_RECVONLY) { + /* If they are recv only then we become sendonly if we are configured as sendrecv or sendonly */ + if (ours == AST_RTP_EXTENSION_DIRECTION_SENDRECV || ours == AST_RTP_EXTENSION_DIRECTION_SENDONLY) { + return AST_RTP_EXTENSION_DIRECTION_SENDONLY; + } + } + + return AST_RTP_EXTENSION_DIRECTION_NONE; +} + +int ast_rtp_instance_extmap_negotiate(struct ast_rtp_instance *instance, int id, enum ast_rtp_extension_direction direction, + const char *uri, const char *attributes) +{ + /* 'attributes' is currently unused but exists in the API to ensure it does not need to be altered + * in the future in case we need to use it. + */ + int idx; + enum ast_rtp_extension extension = AST_RTP_EXTENSION_UNSUPPORTED; + + /* Per the RFC the identifier has to be 1 or above */ + if (id < 1) { + return -1; + } + + /* Convert the provided URI to the internal representation */ + for (idx = 0; idx < ARRAY_LEN(rtp_extension_uris); ++idx) { + if (!strcasecmp(rtp_extension_uris[idx], uri)) { + extension = idx; + break; + } + } + + ao2_lock(instance); + /* We only accept the extension if it is enabled */ + if (extension < AST_VECTOR_SIZE(&instance->extmap_enabled) && + AST_VECTOR_GET(&instance->extmap_enabled, extension) != AST_RTP_EXTENSION_DIRECTION_NONE) { + struct rtp_extmap extmap = { + .extension = extension, + .direction = rtp_extmap_negotiate_direction(AST_VECTOR_GET(&instance->extmap_enabled, extension), direction), + }; + + /* If the direction negotiation failed then don't accept or use this extension */ + if (extmap.direction != AST_RTP_EXTENSION_DIRECTION_NONE) { + if (extension != AST_RTP_EXTENSION_UNSUPPORTED) { + AST_VECTOR_REPLACE(&instance->extmap_negotiated, extension, id); + } + AST_VECTOR_REPLACE(&instance->extmap_unique_ids, id - 1, extmap); + } + } + ao2_unlock(instance); + + return 0; +} + +void ast_rtp_instance_extmap_clear(struct ast_rtp_instance *instance) +{ + static const struct rtp_extmap extmap_none = { + .extension = AST_RTP_EXTENSION_UNSUPPORTED, + .direction = AST_RTP_EXTENSION_DIRECTION_NONE, + }; + int idx; + + ao2_lock(instance); + + /* Clear both the known unique ids and the negotiated extensions as we are about to have + * new results set on us. + */ + for (idx = 0; idx < AST_VECTOR_SIZE(&instance->extmap_unique_ids); ++idx) { + AST_VECTOR_REPLACE(&instance->extmap_unique_ids, idx, extmap_none); + } + + for (idx = 0; idx < AST_VECTOR_SIZE(&instance->extmap_negotiated); ++idx) { + AST_VECTOR_REPLACE(&instance->extmap_negotiated, idx, -1); + } + + ao2_unlock(instance); +} + +int ast_rtp_instance_extmap_get_id(struct ast_rtp_instance *instance, enum ast_rtp_extension extension) +{ + int id = -1; + + ao2_lock(instance); + if (extension < AST_VECTOR_SIZE(&instance->extmap_negotiated)) { + id = AST_VECTOR_GET(&instance->extmap_negotiated, extension); + } + ao2_unlock(instance); + + return id; +} + +size_t ast_rtp_instance_extmap_count(struct ast_rtp_instance *instance) +{ + size_t count; + + ao2_lock(instance); + count = AST_VECTOR_SIZE(&instance->extmap_unique_ids); + ao2_unlock(instance); + + return count; +} + +enum ast_rtp_extension ast_rtp_instance_extmap_get_extension(struct ast_rtp_instance *instance, int id) +{ + enum ast_rtp_extension extension = AST_RTP_EXTENSION_UNSUPPORTED; + + ao2_lock(instance); + + /* The local unique identifier starts at '1' so the highest unique identifier + * can be the actual size of the vector. We compensate (as it is 0 index based) + * by dropping it down to 1 to get the correct information. + */ + if (0 < id && id <= AST_VECTOR_SIZE(&instance->extmap_unique_ids)) { + struct rtp_extmap *extmap = AST_VECTOR_GET_ADDR(&instance->extmap_unique_ids, id - 1); + + extension = extmap->extension; + } + ao2_unlock(instance); + + return extension; +} + +enum ast_rtp_extension_direction ast_rtp_instance_extmap_get_direction(struct ast_rtp_instance *instance, int id) +{ + enum ast_rtp_extension_direction direction = AST_RTP_EXTENSION_DIRECTION_NONE; + + ao2_lock(instance); + + if (0 < id && id <= AST_VECTOR_SIZE(&instance->extmap_unique_ids)) { + struct rtp_extmap *extmap = AST_VECTOR_GET_ADDR(&instance->extmap_unique_ids, id - 1); + + direction = extmap->direction; + } + ao2_unlock(instance); + + return direction; +} + +const char *ast_rtp_instance_extmap_get_uri(struct ast_rtp_instance *instance, int id) +{ + enum ast_rtp_extension extension = ast_rtp_instance_extmap_get_extension(instance, id); + + if (extension == AST_RTP_EXTENSION_UNSUPPORTED || + (unsigned int)extension >= ARRAY_LEN(rtp_extension_uris)) { + return NULL; + } + + return rtp_extension_uris[extension]; +} + int ast_rtp_codecs_payloads_initialize(struct ast_rtp_codecs *codecs) { int res; diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c index 14ed3b186375a1c64ad2763bfdb876d870fd38fe..727aeb0deccdaf11952f750a6bb6bb131c787298 100644 --- a/res/res_pjsip_sdp_rtp.c +++ b/res/res_pjsip_sdp_rtp.c @@ -151,8 +151,57 @@ static void enable_rtcp(struct ast_sip_session *session, struct ast_sip_session_ ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RTCP, rtcp_type); } +/*! + * \brief Enable an RTP extension on an RTP session. + */ +static void enable_rtp_extension(struct ast_sip_session *session, struct ast_sip_session_media *session_media, + enum ast_rtp_extension extension, enum ast_rtp_extension_direction direction, + const pjmedia_sdp_session *sdp) +{ + int id = -1; + + /* For a bundle group the local unique identifier space is shared across all streams within + * it. + */ + if (session_media->bundle_group != -1) { + int index; + + for (index = 0; index < sdp->media_count; ++index) { + struct ast_sip_session_media *other_session_media; + int other_id; + + if (index >= AST_VECTOR_SIZE(&session->pending_media_state->sessions)) { + break; + } + + other_session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, index); + if (!other_session_media->rtp || other_session_media->bundle_group != session_media->bundle_group) { + continue; + } + + other_id = ast_rtp_instance_extmap_get_id(other_session_media->rtp, extension); + if (other_id == -1) { + /* Worst case we have to fall back to the highest available free local unique identifier + * for the bundle group. + */ + other_id = ast_rtp_instance_extmap_count(other_session_media->rtp) + 1; + if (id < other_id) { + id = other_id; + } + continue; + } + + id = other_id; + break; + } + } + + ast_rtp_instance_extmap_enable(session_media->rtp, id, extension, direction); +} + /*! \brief Internal function which creates an RTP instance */ -static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_media *session_media) +static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_media *session_media, + const pjmedia_sdp_session *sdp) { struct ast_rtp_engine_ice *ice; struct ast_sockaddr temp_media_address; @@ -223,6 +272,7 @@ static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_me ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RETRANS_RECV, session->endpoint->media.webrtc); ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RETRANS_SEND, session->endpoint->media.webrtc); ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_REMB, session->endpoint->media.webrtc); + enable_rtp_extension(session, session_media, AST_RTP_EXTENSION_ABS_SEND_TIME, AST_RTP_EXTENSION_DIRECTION_SENDRECV, sdp); if (session->endpoint->media.tos_video || session->endpoint->media.cos_video) { ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_video, session->endpoint->media.cos_video, "SIP RTP Video"); @@ -1101,6 +1151,113 @@ static void add_rtcp_fb_to_stream(struct ast_sip_session *session, pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr); } +static void add_extmap_to_stream(struct ast_sip_session *session, + struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media) +{ + int idx; + char extmap_value[256]; + + if (!session->endpoint->media.webrtc || session_media->type != AST_MEDIA_TYPE_VIDEO) { + return; + } + + /* RTP extension local unique identifiers start at '1' */ + for (idx = 1; idx <= ast_rtp_instance_extmap_count(session_media->rtp); ++idx) { + enum ast_rtp_extension extension = ast_rtp_instance_extmap_get_extension(session_media->rtp, idx); + const char *direction_str = ""; + pj_str_t stmp; + pjmedia_sdp_attr *attr; + + /* If this is an unsupported RTP extension we can't place it into the SDP */ + if (extension == AST_RTP_EXTENSION_UNSUPPORTED) { + continue; + } + + switch (ast_rtp_instance_extmap_get_direction(session_media->rtp, idx)) { + case AST_RTP_EXTENSION_DIRECTION_SENDRECV: + /* Lack of a direction indicates sendrecv, so we leave it out */ + direction_str = ""; + break; + case AST_RTP_EXTENSION_DIRECTION_SENDONLY: + direction_str = "/sendonly"; + break; + case AST_RTP_EXTENSION_DIRECTION_RECVONLY: + direction_str = "/recvonly"; + break; + case AST_RTP_EXTENSION_DIRECTION_NONE: + /* It is impossible for a "none" direction extension to be negotiated but just in case + * we treat it as inactive. + */ + case AST_RTP_EXTENSION_DIRECTION_INACTIVE: + direction_str = "/inactive"; + break; + } + + snprintf(extmap_value, sizeof(extmap_value), "%d%s %s", idx, direction_str, + ast_rtp_instance_extmap_get_uri(session_media->rtp, idx)); + attr = pjmedia_sdp_attr_create(pool, "extmap", pj_cstr(&stmp, extmap_value)); + pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr); + } +} + +/*! \brief Function which processes extmap attributes in a stream */ +static void process_extmap_attributes(struct ast_sip_session *session, struct ast_sip_session_media *session_media, + const struct pjmedia_sdp_media *remote_stream) +{ + int index; + + if (!session->endpoint->media.webrtc || session_media->type != AST_MEDIA_TYPE_VIDEO) { + return; + } + + ast_rtp_instance_extmap_clear(session_media->rtp); + + for (index = 0; index < remote_stream->attr_count; ++index) { + pjmedia_sdp_attr *attr = remote_stream->attr[index]; + char attr_value[pj_strlen(&attr->value) + 1]; + char *uri; + int id; + char direction_str[10] = ""; + char *attributes; + enum ast_rtp_extension_direction direction = AST_RTP_EXTENSION_DIRECTION_SENDRECV; + + /* We only care about extmap attributes */ + if (pj_strcmp2(&attr->name, "extmap")) { + continue; + } + + ast_copy_pj_str(attr_value, &attr->value, sizeof(attr_value)); + + /* Split the combined unique identifier and direction away from the URI and attributes for easier parsing */ + uri = strchr(attr_value, ' '); + if (ast_strlen_zero(uri)) { + continue; + } + *uri++ = '\0'; + + if ((sscanf(attr_value, "%30d%9s", &id, direction_str) < 1) || (id < 1)) { + /* We require at a minimum the unique identifier */ + continue; + } + + /* Convert from the string to the internal representation */ + if (!strcasecmp(direction_str, "/sendonly")) { + direction = AST_RTP_EXTENSION_DIRECTION_SENDONLY; + } else if (!strcasecmp(direction_str, "/recvonly")) { + direction = AST_RTP_EXTENSION_DIRECTION_RECVONLY; + } else if (!strcasecmp(direction_str, "/inactive")) { + direction = AST_RTP_EXTENSION_DIRECTION_INACTIVE; + } + + attributes = strchr(uri, ' '); + if (!ast_strlen_zero(attributes)) { + *attributes++ = '\0'; + } + + ast_rtp_instance_extmap_negotiate(session_media->rtp, id, direction, uri, attributes); + } +} + /*! \brief Function which negotiates an incoming media stream */ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, const pjmedia_sdp_session *sdp, @@ -1139,11 +1296,12 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, } /* Using the connection information create an appropriate RTP instance */ - if (!session_media->rtp && create_rtp(session, session_media)) { + if (!session_media->rtp && create_rtp(session, session_media, sdp)) { return -1; } process_ssrc_attributes(session, session_media, stream); + process_extmap_attributes(session, session_media, stream); session_media_transport = ast_sip_session_media_get_transport(session, session_media); if (session_media_transport == session_media || !session_media->bundled) { @@ -1377,7 +1535,7 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as return 1; } - if (!session_media->rtp && create_rtp(session, session_media)) { + if (!session_media->rtp && create_rtp(session, session_media, sdp)) { return -1; } @@ -1602,6 +1760,7 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as add_ssrc_to_stream(session, session_media, pool, media); add_msid_to_stream(session, session_media, pool, media, stream); add_rtcp_fb_to_stream(session, session_media, pool, media); + add_extmap_to_stream(session, session_media, pool, media); /* Add the media stream to the SDP */ sdp->media[sdp->media_count++] = media; @@ -1676,11 +1835,12 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, } /* Create an RTP instance if need be */ - if (!session_media->rtp && create_rtp(session, session_media)) { + if (!session_media->rtp && create_rtp(session, session_media, local)) { return -1; } process_ssrc_attributes(session, session_media, remote_stream); + process_extmap_attributes(session, session_media, remote_stream); session_media_transport = ast_sip_session_media_get_transport(session, session_media); diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c index c87d87953a648629b8314ca02430504946e481ea..e28e58b9b20895ccc4dbdcc0a6cd8e71ced38dd2 100644 --- a/res/res_rtp_asterisk.c +++ b/res/res_rtp_asterisk.c @@ -551,6 +551,7 @@ static unsigned int ast_rtp_get_ssrc(struct ast_rtp_instance *instance); static const char *ast_rtp_get_cname(struct ast_rtp_instance *instance); static void ast_rtp_set_remote_ssrc(struct ast_rtp_instance *instance, unsigned int ssrc); static void ast_rtp_set_stream_num(struct ast_rtp_instance *instance, int stream_num); +static int ast_rtp_extension_enable(struct ast_rtp_instance *instance, enum ast_rtp_extension extension); static int ast_rtp_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent); #ifdef HAVE_OPENSSL_SRTP @@ -2249,6 +2250,7 @@ static struct ast_rtp_engine asterisk_rtp_engine = { .cname_get = ast_rtp_get_cname, .set_remote_ssrc = ast_rtp_set_remote_ssrc, .set_stream_num = ast_rtp_set_stream_num, + .extension_enable = ast_rtp_extension_enable, .bundle = ast_rtp_bundle, }; @@ -4282,6 +4284,20 @@ static int ast_rtcp_write(const void *data) return res; } +static void put_unaligned_time24(void *p, uint32_t time_msw, uint32_t time_lsw) +{ + unsigned char *cp = p; + uint32_t datum; + + /* Convert the time to 6.18 format */ + datum = (time_msw << 18) & 0x00fc0000; + datum |= (time_lsw >> 14) & 0x0003ffff; + + cp[0] = datum >> 16; + cp[1] = datum >> 8; + cp[2] = datum; +} + /*! \pre instance is locked */ static int rtp_raw_write(struct ast_rtp_instance *instance, struct ast_frame *frame, int codec) { @@ -4399,14 +4415,46 @@ static int rtp_raw_write(struct ast_rtp_instance *instance, struct ast_frame *fr /* If we know the remote address construct a packet and send it out */ if (!ast_sockaddr_isnull(&remote_address)) { - int hdrlen = 12, res, ice; - int packet_len = frame->datalen + hdrlen; - unsigned char *rtpheader = (unsigned char *)(frame->data.ptr - hdrlen); + int hdrlen = 12; + int res; + int ice; + int ext = 0; + int abs_send_time_id; + int packet_len; + unsigned char *rtpheader; - put_unaligned_uint32(rtpheader, htonl((2 << 30) | (codec << 16) | (seqno) | (mark << 23))); + /* If the abs-send-time extension has been negotiated determine how much space we need */ + abs_send_time_id = ast_rtp_instance_extmap_get_id(instance, AST_RTP_EXTENSION_ABS_SEND_TIME); + if (abs_send_time_id != -1) { + /* 4 bytes for the shared information, 1 byte for identifier, 3 bytes for abs-send-time */ + hdrlen += 8; + ext = 1; + } + + packet_len = frame->datalen + hdrlen; + rtpheader = (unsigned char *)(frame->data.ptr - hdrlen); + + put_unaligned_uint32(rtpheader, htonl((2 << 30) | (ext << 28) | (codec << 16) | (seqno) | (mark << 23))); put_unaligned_uint32(rtpheader + 4, htonl(rtp->lastts)); put_unaligned_uint32(rtpheader + 8, htonl(rtp->ssrc)); + /* We assume right now that we will only ever have the abs-send-time extension in the packet + * which simplifies things a bit. + */ + if (abs_send_time_id != -1) { + unsigned int now_msw; + unsigned int now_lsw; + + /* This happens before being placed into the retransmission buffer so that when we + * retransmit we only have to update the timestamp, not everything else. + */ + put_unaligned_uint32(rtpheader + 12, htonl((0xBEDE << 16) | 1)); + rtpheader[16] = (abs_send_time_id << 4) | 2; + + timeval2ntp(ast_tvnow(), &now_msw, &now_lsw); + put_unaligned_time24(rtpheader + 17, now_msw, now_lsw); + } + /* If retransmissions are enabled, we need to store this packet for future use */ if (rtp->send_buffer) { struct ast_rtp_rtcp_nack_payload *payload; @@ -5263,12 +5311,20 @@ static int ast_rtp_rtcp_handle_nack(struct ast_rtp_instance *instance, unsigned unsigned int pid; /* Packet ID which refers to seqno of lost packet */ unsigned int blp; /* Bitmask of following lost packets */ struct ast_sockaddr remote_address = { {0,} }; + int abs_send_time_id; + unsigned int now_msw = 0; + unsigned int now_lsw = 0; if (!rtp->send_buffer) { ast_debug(1, "Tried to handle NACK request, but we don't have a RTP packet storage!\n"); return res; } + abs_send_time_id = ast_rtp_instance_extmap_get_id(instance, AST_RTP_EXTENSION_ABS_SEND_TIME); + if (abs_send_time_id != -1) { + timeval2ntp(ast_tvnow(), &now_msw, &now_lsw); + } + ast_rtp_instance_get_remote_address(instance, &remote_address); /* @@ -5281,6 +5337,12 @@ static int ast_rtp_rtcp_handle_nack(struct ast_rtp_instance *instance, unsigned /* We know the remote end is missing this packet. Go ahead and send it if we still have it. */ payload = (struct ast_rtp_rtcp_nack_payload *)ast_data_buffer_get(rtp->send_buffer, pid); if (payload) { + if (abs_send_time_id != -1) { + /* On retransmission we need to update the timestamp within the packet, as it + * is supposed to contain when the packet was actually sent. + */ + put_unaligned_time24(payload->buf + 17, now_msw, now_lsw); + } res += rtp_sendto(instance, payload->buf, payload->size, 0, &remote_address, &ice); } else { ast_debug(1, "Received NACK request for RTP packet with seqno %d, but we don't have it\n", pid); @@ -5299,6 +5361,9 @@ static int ast_rtp_rtcp_handle_nack(struct ast_rtp_instance *instance, unsigned unsigned int seqno = (pid + blp_index) % 65536; payload = (struct ast_rtp_rtcp_nack_payload *)ast_data_buffer_get(rtp->send_buffer, seqno); if (payload) { + if (abs_send_time_id != -1) { + put_unaligned_time24(payload->buf + 17, now_msw, now_lsw); + } res += rtp_sendto(instance, payload->buf, payload->size, 0, &remote_address, &ice); } else { ast_debug(1, "Remote end also requested RTP packet with seqno %d, but we don't have it\n", seqno); @@ -7165,6 +7230,16 @@ static void ast_rtp_set_stream_num(struct ast_rtp_instance *instance, int stream rtp->stream_num = stream_num; } +static int ast_rtp_extension_enable(struct ast_rtp_instance *instance, enum ast_rtp_extension extension) +{ + switch (extension) { + case AST_RTP_EXTENSION_ABS_SEND_TIME: + return 1; + default: + return 0; + } +} + /*! \pre child is locked */ static int ast_rtp_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent) {