sdp: Add support for parsing the extmap attribute from caps and storing inside caps

The extmap attribute allows mapping RTP extension header IDs to
well-known RTP extension header specifications. See RFC8285 for details.

We store the extmap attribute either as string in the caps
  extmap-X=extensionname
where X is the integer extension header ID, or as 3-tuple of strings
  extmap-X=<direction,extensionname,extensionattributes>
where direction or extensionattributes are allowed to be the empty
string.

Both formats are allowed because usually only the extension name is
given and it's much simpler to handle in caps.
This commit is contained in:
Sebastian Dröge 2019-07-09 14:28:46 +03:00 committed by Nicolas Dufresne
parent 6d49814932
commit 1381e3e2be
2 changed files with 271 additions and 1 deletions

View file

@ -3553,6 +3553,8 @@ gst_sdp_media_add_rtcp_fb_attributes_from_media (const GstSDPMedia * media,
*
* a=fmtp:(payload) (param)[=(value)];...
*
* Note that the extmap attribute is set only by gst_sdp_media_attributes_to_caps().
*
* Returns: a #GstCaps, or %NULL if an error happened
*
* Since: 1.8
@ -3752,6 +3754,8 @@ no_rate:
*
* a=rtcp-fb:(payload) (param1) [param2]...
*
* a=extmap:(id)[/direction] (extensionname) (extensionattributes)
*
* Returns: a #GstSDPResult.
*
* Since: 1.8
@ -3829,7 +3833,7 @@ gst_sdp_media_set_media_from_caps (const GstCaps * caps, GstSDPMedia * media)
}
}
/* collect all other properties and add them to fmtp or attributes */
/* collect all other properties and add them to fmtp, extmap or attributes */
fmtp = g_string_new ("");
g_string_append_printf (fmtp, "%d ", caps_pt);
first = TRUE;
@ -3889,6 +3893,63 @@ gst_sdp_media_set_media_from_caps (const GstCaps * caps, GstSDPMedia * media)
continue;
}
/* extmap */
if (g_str_has_prefix (fname, "extmap-")) {
gchar *endptr;
guint id = strtoull (fname + 7, &endptr, 10);
const GValue *arr;
if (*endptr != '\0' || id == 0 || id == 15 || id > 9999)
continue;
if ((fval = gst_structure_get_string (s, fname))) {
gchar *extmap = g_strdup_printf ("%u %s", id, fval);
gst_sdp_media_add_attribute (media, "extmap", extmap);
g_free (extmap);
} else if ((arr = gst_structure_get_value (s, fname))
&& G_VALUE_HOLDS (arr, GST_TYPE_ARRAY)
&& gst_value_array_get_size (arr) == 3) {
const GValue *val;
const gchar *direction, *extensionname, *extensionattributes;
val = gst_value_array_get_value (arr, 0);
direction = g_value_get_string (val);
val = gst_value_array_get_value (arr, 1);
extensionname = g_value_get_string (val);
val = gst_value_array_get_value (arr, 2);
extensionattributes = g_value_get_string (val);
if (!extensionname || *extensionname == '\0')
continue;
if (direction && *direction != '\0' && extensionattributes
&& *extensionattributes != '\0') {
gchar *extmap =
g_strdup_printf ("%u/%s %s %s", id, direction, extensionname,
extensionattributes);
gst_sdp_media_add_attribute (media, "extmap", extmap);
g_free (extmap);
} else if (direction && *direction != '\0') {
gchar *extmap =
g_strdup_printf ("%u/%s %s", id, direction, extensionname);
gst_sdp_media_add_attribute (media, "extmap", extmap);
g_free (extmap);
} else if (extensionattributes && *extensionattributes != '\0') {
gchar *extmap = g_strdup_printf ("%u %s %s", id, extensionname,
extensionattributes);
gst_sdp_media_add_attribute (media, "extmap", extmap);
g_free (extmap);
} else {
gchar *extmap = g_strdup_printf ("%u %s", id, extensionname);
gst_sdp_media_add_attribute (media, "extmap", extmap);
g_free (extmap);
}
}
continue;
}
if ((fval = gst_structure_get_string (s, fname))) {
g_string_append_printf (fmtp, "%s%s=%s", first ? "" : ";", fname, fval);
first = FALSE;
@ -4056,6 +4117,8 @@ sdp_add_attributes_to_caps (GArray * attributes, GstCaps * caps)
continue;
if (!strcmp (key, "key-mgmt"))
continue;
if (!strcmp (key, "extmap"))
continue;
/* string must be valid UTF8 */
if (!g_utf8_validate (attr->value, -1, NULL))
@ -4075,6 +4138,112 @@ sdp_add_attributes_to_caps (GArray * attributes, GstCaps * caps)
return GST_SDP_OK;
}
static GstSDPResult
gst_sdp_media_add_extmap_attributes (GArray * attributes, GstCaps * caps)
{
const gchar *extmap;
gchar *p, *tmp, *to_free;
guint id, i;
GstStructure *s;
g_return_val_if_fail (attributes != NULL, GST_SDP_EINVAL);
g_return_val_if_fail (caps != NULL && GST_IS_CAPS (caps), GST_SDP_EINVAL);
s = gst_caps_get_structure (caps, 0);
for (i = 0; i < attributes->len; i++) {
GstSDPAttribute *attr;
const gchar *direction, *extensionname, *extensionattributes;
attr = &g_array_index (attributes, GstSDPAttribute, i);
if (strcmp (attr->key, "extmap") != 0)
continue;
extmap = attr->value;
/* p is now of the format id[/direction] extensionname [extensionattributes] */
to_free = p = g_strdup (extmap);
id = strtoul (p, &tmp, 10);
if (id == 0 || id == 15 || id > 9999 || (*tmp != ' ' && *tmp != '/')) {
GST_ERROR ("Invalid extmap '%s'", to_free);
goto next;
} else if (*tmp == '/') {
p = tmp;
p++;
PARSE_STRING (p, " ", direction);
/* Invalid format */
if (direction == NULL || *direction == '\0') {
GST_ERROR ("Invalid extmap '%s'", to_free);
goto next;
}
} else {
/* At the space */
p = tmp;
direction = "";
}
SKIP_SPACES (p);
tmp = strstr (p, " ");
if (tmp == NULL) {
extensionname = p;
extensionattributes = "";
} else {
extensionname = p;
*tmp = '\0';
p = tmp + 1;
SKIP_SPACES (p);
extensionattributes = p;
}
if (extensionname == NULL || *extensionname == '\0') {
GST_ERROR ("Invalid extmap '%s'", to_free);
goto next;
}
if (*direction != '\0' || *extensionattributes != '\0') {
GValue arr = G_VALUE_INIT;
GValue val = G_VALUE_INIT;
gchar *key;
key = g_strdup_printf ("extmap-%u", id);
g_value_init (&arr, GST_TYPE_ARRAY);
g_value_init (&val, G_TYPE_STRING);
g_value_set_string (&val, direction);
gst_value_array_append_value (&arr, &val);
g_value_set_string (&val, extensionname);
gst_value_array_append_value (&arr, &val);
g_value_set_string (&val, extensionattributes);
gst_value_array_append_value (&arr, &val);
gst_structure_set_value (s, key, &arr);
g_value_unset (&val);
g_value_unset (&arr);
GST_DEBUG ("adding caps: %s=<%s,%s,%s>", key, direction, extensionname,
extensionattributes);
g_free (key);
} else {
gchar *key;
key = g_strdup_printf ("extmap-%u", id);
gst_structure_set (s, key, G_TYPE_STRING, extensionname, NULL);
GST_DEBUG ("adding caps: %s=%s", key, extensionname);
g_free (key);
}
next:
g_free (to_free);
}
return GST_SDP_OK;
}
/**
* gst_sdp_message_attributes_to_caps:
* @msg: a #GstSDPMessage
@ -4105,6 +4274,11 @@ gst_sdp_message_attributes_to_caps (const GstSDPMessage * msg, GstCaps * caps)
res = sdp_add_attributes_to_caps (msg->attributes, caps);
if (res == GST_SDP_OK) {
/* parse global extmap field */
res = gst_sdp_media_add_extmap_attributes (msg->attributes, caps);
}
done:
if (mikey)
gst_mikey_message_unref (mikey);
@ -4141,6 +4315,11 @@ gst_sdp_media_attributes_to_caps (const GstSDPMedia * media, GstCaps * caps)
res = sdp_add_attributes_to_caps (media->attributes, caps);
if (res == GST_SDP_OK) {
/* parse media extmap field */
res = gst_sdp_media_add_extmap_attributes (media->attributes, caps);
}
done:
if (mikey)
gst_mikey_message_unref (mikey);

View file

@ -125,6 +125,26 @@ static const gchar caps_video_rtcp_fb_all_pt_102[] =
"clock-rate=(int)90000, encoding-name=(string)H264, "
"rtcp-fb-nack=(boolean)true, rtcp-fb-nack-pli=(boolean)true";
static const gchar * sdp_extmap = "v=0\r\n"
"o=- 123456 2 IN IP4 127.0.0.1 \r\n"
"s=-\r\n"
"t=0 0\r\n"
"a=maxptime:60\r\n"
"a=sendrecv\r\n"
"m=video 1 UDP/TLS/RTP/SAVPF 100 101 102\r\n"
"c=IN IP4 1.1.1.1\r\n"
"a=rtpmap:100 VP8/90000\r\n"
"a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\n"
"a=extmap:3/recvonly http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n"
"a=extmap:4 urn:3gpp:video-orientation attributes\r\n";
static const gchar caps_video_extmap_pt_100[] =
"application/x-unknown, media=(string)video, payload=(int)100, "
"clock-rate=(int)90000, encoding-name=(string)VP8, "
"extmap-2=urn:ietf:params:rtp-hdrext:toffset, "
"extmap-3=(string)<\"recvonly\",\"http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\",\"\">, "
"extmap-4=(string)<\"\",\"urn:3gpp:video-orientation\",\"attributes\">";
/* *INDENT-ON* */
GST_START_TEST (boxed)
@ -535,6 +555,75 @@ GST_START_TEST (media_from_caps_rtcp_fb_pt_101)
gst_sdp_message_free (message);
}
GST_END_TEST
GST_START_TEST (caps_from_media_extmap)
{
GstSDPMessage *message;
glong length = -1;
const GstSDPMedia *media1;
GstCaps *caps1;
GstCaps *result1;
gst_sdp_message_new (&message);
gst_sdp_message_parse_buffer ((guint8 *) sdp_extmap, length, message);
media1 = gst_sdp_message_get_media (message, 0);
fail_unless (media1 != NULL);
caps1 = gst_sdp_media_get_caps_from_media (media1, 100);
gst_sdp_media_attributes_to_caps (media1, caps1);
result1 = gst_caps_from_string (caps_video_extmap_pt_100);
fail_unless (gst_caps_is_strictly_equal (caps1, result1));
gst_caps_unref (result1);
gst_caps_unref (caps1);
gst_sdp_message_free (message);
}
GST_END_TEST
GST_START_TEST (media_from_caps_extmap_pt_100)
{
GstSDPResult ret = GST_SDP_OK;
GstSDPMessage *message;
glong length = -1;
GstSDPMedia *media_caps;
const GstSDPMedia *media_sdp;
GstCaps *caps;
const gchar *attr_val_caps1, *attr_val_caps2, *attr_val_caps3;
const gchar *attr_val_sdp1, *attr_val_sdp2, *attr_val_sdp3;
caps = gst_caps_from_string (caps_video_extmap_pt_100);
gst_sdp_media_new (&media_caps);
fail_unless (media_caps != NULL);
ret = gst_sdp_media_set_media_from_caps (caps, media_caps);
fail_unless (ret == GST_SDP_OK);
gst_caps_unref (caps);
gst_sdp_message_new (&message);
gst_sdp_message_parse_buffer ((guint8 *) sdp_extmap, length, message);
media_sdp = gst_sdp_message_get_media (message, 0);
fail_unless (media_sdp != NULL);
attr_val_caps1 = gst_sdp_media_get_attribute_val_n (media_caps, "extmap", 0);
attr_val_caps2 = gst_sdp_media_get_attribute_val_n (media_caps, "extmap", 1);
attr_val_caps3 = gst_sdp_media_get_attribute_val_n (media_caps, "extmap", 2);
attr_val_sdp1 = gst_sdp_media_get_attribute_val_n (media_sdp, "extmap", 0);
attr_val_sdp2 = gst_sdp_media_get_attribute_val_n (media_sdp, "extmap", 1);
attr_val_sdp3 = gst_sdp_media_get_attribute_val_n (media_sdp, "extmap", 2);
fail_if (g_strcmp0 (attr_val_caps1, attr_val_sdp1) != 0);
fail_if (g_strcmp0 (attr_val_caps2, attr_val_sdp2) != 0);
fail_if (g_strcmp0 (attr_val_caps3, attr_val_sdp3) != 0);
gst_sdp_media_free (media_caps);
gst_sdp_message_free (message);
}
GST_END_TEST
GST_START_TEST (caps_from_media_really_const)
{
@ -588,8 +677,10 @@ sdp_suite (void)
tcase_add_test (tc_chain, media_from_caps);
tcase_add_test (tc_chain, caps_from_media_rtcp_fb);
tcase_add_test (tc_chain, caps_from_media_rtcp_fb_all);
tcase_add_test (tc_chain, caps_from_media_extmap);
tcase_add_test (tc_chain, media_from_caps_rtcp_fb_pt_100);
tcase_add_test (tc_chain, media_from_caps_rtcp_fb_pt_101);
tcase_add_test (tc_chain, media_from_caps_extmap_pt_100);
return s;
}