From 1381e3e2be7f0c2151aaacc14ae1668ecdc120b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 9 Jul 2019 14:28:46 +0300 Subject: [PATCH] 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= 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. --- gst-libs/gst/sdp/gstsdpmessage.c | 181 ++++++++++++++++++++++++++++++- tests/check/libs/sdp.c | 91 ++++++++++++++++ 2 files changed, 271 insertions(+), 1 deletion(-) diff --git a/gst-libs/gst/sdp/gstsdpmessage.c b/gst-libs/gst/sdp/gstsdpmessage.c index 3be9df419f..ae5fcd2d6d 100644 --- a/gst-libs/gst/sdp/gstsdpmessage.c +++ b/gst-libs/gst/sdp/gstsdpmessage.c @@ -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); diff --git a/tests/check/libs/sdp.c b/tests/check/libs/sdp.c index 3ec09c8966..d93b96f53d 100644 --- a/tests/check/libs/sdp.c +++ b/tests/check/libs/sdp.c @@ -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; }