rtp: Add support for RTP extension negotiation and abs-send-time.

When RTP was originally created it had the ability to place a single
extension in an RTP packet. In practice people wanted to potentially
put multiple extensions in one and so RFC 5285 (obsoleted by RFC
8285) came into existence. This allows RTP extensions to be negotiated
with a unique identifier to be used in the RTP packet, allowing
multiple extensions to be present in the packet.

This change extends the RTP engine API to add support for this. A
user of it can enable extensions and the API provides the ability to
retrieve the information (to construct SDP for example) and to provide
negotiated information (from SDP). The end result is that the RTP
engine can then query to see if the extension has been negotiated and
what unique identifier is to be used. It is then up to the RTP engine
implementation to construct the packet appropriately.

The first extension to use this support is abs-send-time which is
defined in the REMB draft[1] and is a second timestamp placed in an
RTP packet which is for when the packet has left the sending system.
It is used to more accurately determine the available bandwidth.

ASTERISK-27831

[1] https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03

Change-Id: I508deac557867b1e27fc7339be890c8018171588
This commit is contained in:
Joshua Colp
2018-04-23 14:04:01 +00:00
parent 21dd609e77
commit a507c73a78
4 changed files with 636 additions and 8 deletions

View File

@@ -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);