sdp: Add support for parsing the extmap attribute from caps and storing inside caps
authorSebastian Dröge <sebastian@centricular.com>
Tue, 9 Jul 2019 11:28:46 +0000 (14:28 +0300)
committerNicolas Dufresne <nicolas@ndufresne.ca>
Sat, 13 Jul 2019 12:18:15 +0000 (12:18 +0000)
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.

gst-libs/gst/sdp/gstsdpmessage.c
tests/check/libs/sdp.c

index 3be9df4..ae5fcd2 100644 (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);
index 3ec09c8..d93b96f 100644 (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)
@@ -536,6 +556,75 @@ GST_START_TEST (media_from_caps_rtcp_fb_pt_101)
 }
 
 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)
 {
   GstSDPMessage *message;
@@ -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;
 }