sdp: support multiple rid parameters
authorMatthew Waters <matthew@centricular.com>
Wed, 1 Sep 2021 04:02:29 +0000 (14:02 +1000)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Thu, 3 Mar 2022 03:34:38 +0000 (03:34 +0000)
As specified formally in RFC8851

Each rid description is placed in its own caps field in the structure.
This is very similar to the already existing extmap-$id sdp<->caps
transformations that already exists.

The mapping is as follows:

  a=rid:0 direction ';'-separated params

where direction is either 'send' or 'recv'

gets put into a caps structure like so:

   rid-0=(string)<"direction","param1","param2",etc>

If there are no rid parameters then the caps structure is generated to
only contain the direction as a single string like:

   rid-0=(string)direction

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/1760>

subprojects/gst-plugins-base/gst-libs/gst/sdp/gstsdpmessage.c
subprojects/gst-plugins-base/tests/check/libs/sdp.c

index a546647..2fb6d68 100644 (file)
@@ -3991,6 +3991,52 @@ gst_sdp_media_set_media_from_caps (const GstCaps * caps, GstSDPMedia * media)
       continue;
     }
 
+    /* rid values */
+    if (g_str_has_prefix (fname, "rid-")) {
+      const char *rid_id = &fname[strlen ("rid-")];
+      const GValue *arr;
+
+      if (!rid_id || !*rid_id)
+        continue;
+
+      if ((fval = gst_structure_get_string (s, fname))) {
+        char *rid_val = g_strdup_printf ("%s %s", rid_id, fval);
+        gst_sdp_media_add_attribute (media, "rid", rid_val);
+        g_free (rid_val);
+      } else if ((arr = gst_structure_get_value (s, fname))
+          && GST_VALUE_HOLDS_ARRAY (arr)
+          && gst_value_array_get_size (arr) > 1) {
+        const gchar *direction, *param;
+        GString *str;
+        guint i, n;
+
+        str = g_string_new (NULL);
+
+        g_string_append_printf (str, "%s ", rid_id);
+
+        n = gst_value_array_get_size (arr);
+        for (i = 0; i < n; i++) {
+          const GValue *val = gst_value_array_get_value (arr, i);
+          if (i == 0) {
+            direction = g_value_get_string (val);
+            g_string_append_printf (str, "%s", direction);
+          } else {
+            param = g_value_get_string (val);
+            if (i == 1)
+              g_string_append_c (str, ' ');
+            else
+              g_string_append_c (str, ';');
+            g_string_append_printf (str, "%s", param);
+          }
+        }
+        gst_sdp_media_add_attribute (media, "rid", str->str);
+        g_string_free (str, TRUE);
+      } else {
+        GST_WARNING ("caps field %s is an unsupported format", fname);
+      }
+      continue;
+    }
+
     if ((fval = gst_structure_get_string (s, fname))) {
 
       /* "profile" is our internal representation of the notion of
@@ -4170,6 +4216,8 @@ sdp_add_attributes_to_caps (GArray * attributes, GstCaps * caps)
         continue;
       if (!strcmp (key, "extmap"))
         continue;
+      if (!strcmp (key, "rid"))
+        continue;
 
       /* string must be valid UTF8 */
       if (!g_utf8_validate (attr->value, -1, NULL))
@@ -4295,6 +4343,138 @@ gst_sdp_media_add_extmap_attributes (GArray * attributes, GstCaps * caps)
   return GST_SDP_OK;
 }
 
+/* parses RID SDP attributes (RFC8851) into caps */
+static GstSDPResult
+gst_sdp_media_add_rid_attributes (GArray * attributes, GstCaps * caps)
+{
+  const gchar *rid;
+  char *p, *to_free;
+  guint 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);
+  g_return_val_if_fail (gst_caps_is_writable (caps), GST_SDP_EINVAL);
+
+  s = gst_caps_get_structure (caps, 0);
+
+  for (i = 0; i < attributes->len; i++) {
+    GstSDPAttribute *attr;
+    const char *direction, *params, *id;
+    const char *tmp;
+
+    attr = &g_array_index (attributes, GstSDPAttribute, i);
+    if (strcmp (attr->key, "rid") != 0)
+      continue;
+
+    rid = attr->value;
+
+    /* p is now of the format id dir ;-separated-params */
+    to_free = p = g_strdup (rid);
+
+    PARSE_STRING (p, " ", id);
+    if (id == NULL || *id == '\0') {
+      GST_ERROR ("Invalid rid \'%s\'", to_free);
+      goto next;
+    }
+    tmp = id;
+    while (*tmp && (*tmp == '-' || *tmp == '_' || g_ascii_isalnum (*tmp)))
+      tmp++;
+    if (*tmp != '\0') {
+      GST_ERROR ("Invalid rid-id \'%s\'", id);
+      goto next;
+    }
+
+    SKIP_SPACES (p);
+
+    PARSE_STRING (p, " ", direction);
+    if (direction == NULL || *direction == '\0') {
+      direction = p;
+      params = NULL;
+    } else {
+      SKIP_SPACES (p);
+
+      params = p;
+    }
+
+    if (direction == NULL || *direction == '\0'
+        || (g_strcmp0 (direction, "send") != 0
+            && g_strcmp0 (direction, "recv") != 0)) {
+      GST_ERROR ("Invalid rid direction \'%s\'", p);
+      goto next;
+    }
+
+    if (params && *params != '\0') {
+      GValue arr = G_VALUE_INIT;
+      GValue val = G_VALUE_INIT;
+      gchar *key;
+#if !defined(GST_DISABLE_DEBUG)
+      GString *debug_params = g_string_new (NULL);
+      int i = 0;
+#endif
+
+      key = g_strdup_printf ("rid-%s", 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_and_take_value (&arr, &val);
+      val = (GValue) G_VALUE_INIT;
+
+      while (*p) {
+        const char *param;
+        gboolean done = FALSE;
+
+        PARSE_STRING (p, ";", param);
+
+        if (param) {
+        } else if (*p) {
+          param = p;
+          done = TRUE;
+        } else {
+          break;
+        }
+
+        g_value_init (&val, G_TYPE_STRING);
+        g_value_set_string (&val, param);
+        gst_value_array_append_and_take_value (&arr, &val);
+        val = (GValue) G_VALUE_INIT;
+#if !defined(GST_DISABLE_DEBUG)
+        if (i++ > 0)
+          g_string_append_c (debug_params, ',');
+        g_string_append (debug_params, param);
+#endif
+
+        if (done)
+          break;
+      }
+
+      gst_structure_take_value (s, key, &arr);
+      arr = (GValue) G_VALUE_INIT;
+#if !defined(GST_DISABLE_DEBUG)
+      {
+        char *debug_str = g_string_free (debug_params, FALSE);
+        GST_DEBUG ("adding caps: %s=<%s,%s>", key, direction, debug_str);
+        g_free (debug_str);
+      }
+#endif
+      g_free (key);
+    } else {
+      gchar *key;
+
+      key = g_strdup_printf ("rid-%s", id);
+      gst_structure_set (s, key, G_TYPE_STRING, direction, NULL);
+      GST_DEBUG ("adding caps: %s=%s", key, direction);
+      g_free (key);
+    }
+
+  next:
+    g_clear_pointer (&to_free, g_free);
+  }
+  return GST_SDP_OK;
+}
+
 /**
  * gst_sdp_message_attributes_to_caps:
  * @msg: a #GstSDPMessage
@@ -4371,6 +4551,11 @@ gst_sdp_media_attributes_to_caps (const GstSDPMedia * media, GstCaps * caps)
     res = gst_sdp_media_add_extmap_attributes (media->attributes, caps);
   }
 
+  if (res == GST_SDP_OK) {
+    /* parse media rid fields */
+    res = gst_sdp_media_add_rid_attributes (media->attributes, caps);
+  }
+
 done:
   if (mikey)
     gst_mikey_message_unref (mikey);
index f032ac1..70b2052 100644 (file)
@@ -182,6 +182,19 @@ static const gchar caps_application_raptor_fec_pt_110[] =
     "raptor-scheme-id=(string)1, kmax=(string)8192, t=(string)128, p=(string)A, repair-window=(string)200000, "
     "a-mid=(string)R1";
 
+static const gchar caps_multiple_rid[] =
+    "application/x-unknown, media=(string)video, payload=(int)96, "
+    "clock-rate=(int)90000, encoding-name=(string)VP8, "
+    "rid-h=(string)\"send\", "
+    "rid-m=(string)\"send\", "
+    "rid-l=(string)\"send\", "
+    "a-simulcast=(string)\"send\\ h\\;m\\;l\"";
+
+static const gchar caps_rid_params[] =
+    "application/x-unknown, media=(string)video, payload=(int)96, "
+    "clock-rate=(int)90000, encoding-name=(string)VP8, "
+    "rid-0=(string)<\"send\",\"max-width=1920\",\"max-height=1080\">, "
+    "rid-1=(string)<\"send\",\"max-width=1280\",\"max-height=720\">";
 
 /* *INDENT-ON* */
 
@@ -759,6 +772,132 @@ GST_START_TEST (media_from_caps_h264_with_profile_asymmetry_allowed)
 }
 
 GST_END_TEST
+GST_START_TEST (caps_multiple_rid_parse)
+{
+  GstSDPMedia media, media2;
+  GstCaps *caps, *expected;
+
+  /* BUG: gst_sdp_media_add_attributes_to_caps() would only set a single rid
+   * string attribute key/value in caps */
+
+  memset (&media, 0, sizeof (media));
+  fail_unless_equals_int (gst_sdp_media_init (&media), GST_SDP_OK);
+
+  fail_unless_equals_int (GST_SDP_OK,
+      gst_sdp_media_set_media (&media, "video"));
+  fail_unless_equals_int (GST_SDP_OK, gst_sdp_media_add_format (&media, "96"));
+  fail_unless_equals_int (GST_SDP_OK,
+      gst_sdp_media_add_attribute (&media, "rtpmap", "96 VP8/90000"));
+  fail_unless_equals_int (GST_SDP_OK,
+      gst_sdp_media_add_attribute (&media, "rid", "h send"));
+  fail_unless_equals_int (GST_SDP_OK,
+      gst_sdp_media_add_attribute (&media, "rid", "m send"));
+  fail_unless_equals_int (GST_SDP_OK,
+      gst_sdp_media_add_attribute (&media, "rid", "l send"));
+  fail_unless_equals_int (GST_SDP_OK,
+      gst_sdp_media_add_attribute (&media, "simulcast", "send h;m;l"));
+
+  expected = gst_caps_from_string (caps_multiple_rid);
+  fail_unless (gst_caps_is_fixed (expected));
+  fail_unless (expected != NULL);
+
+  caps = gst_sdp_media_get_caps_from_media (&media, 96);
+  fail_unless (caps != NULL);
+  fail_unless_equals_int (GST_SDP_OK,
+      gst_sdp_media_attributes_to_caps (&media, caps));
+  fail_unless (gst_caps_is_fixed (caps));
+
+  GST_DEBUG ("    caps %" GST_PTR_FORMAT, caps);
+  GST_DEBUG ("expected %" GST_PTR_FORMAT, expected);
+  fail_unless (gst_caps_is_equal (caps, expected));
+
+  memset (&media2, 0, sizeof (media2));
+  fail_unless_equals_int (GST_SDP_OK, gst_sdp_media_init (&media2));
+  fail_unless_equals_int (GST_SDP_OK,
+      gst_sdp_media_set_media_from_caps (caps, &media2));
+
+  gst_clear_caps (&caps);
+
+  caps = gst_sdp_media_get_caps_from_media (&media, 96);
+  fail_unless (caps != NULL);
+  fail_unless_equals_int (GST_SDP_OK,
+      gst_sdp_media_attributes_to_caps (&media, caps));
+  fail_unless (gst_caps_is_fixed (caps));
+
+  GST_DEBUG ("    caps %" GST_PTR_FORMAT, caps);
+  GST_DEBUG ("expected %" GST_PTR_FORMAT, expected);
+  fail_unless (gst_caps_is_equal (caps, expected));
+
+  gst_sdp_media_uninit (&media);
+  gst_sdp_media_uninit (&media2);
+
+  gst_clear_caps (&caps);
+  gst_clear_caps (&expected);
+}
+
+GST_END_TEST
+GST_START_TEST (caps_multiple_rid_parse_with_params)
+{
+  GstSDPMedia media, media2;
+  GstCaps *caps, *expected;
+
+  /* BUG: gst_sdp_media_add_attributes_to_caps() would only set a single rid
+   * string attribute key/value in caps */
+
+  memset (&media, 0, sizeof (media));
+  fail_unless_equals_int (gst_sdp_media_init (&media), GST_SDP_OK);
+
+  fail_unless_equals_int (GST_SDP_OK,
+      gst_sdp_media_set_media (&media, "video"));
+  fail_unless_equals_int (GST_SDP_OK, gst_sdp_media_add_format (&media, "96"));
+  fail_unless_equals_int (GST_SDP_OK,
+      gst_sdp_media_add_attribute (&media, "rtpmap", "96 VP8/90000"));
+  fail_unless_equals_int (GST_SDP_OK,
+      gst_sdp_media_add_attribute (&media, "rid",
+          "0 send max-width=1920;max-height=1080"));
+  fail_unless_equals_int (GST_SDP_OK,
+      gst_sdp_media_add_attribute (&media, "rid",
+          "1 send max-width=1280;max-height=720"));
+
+  expected = gst_caps_from_string (caps_rid_params);
+  fail_unless (gst_caps_is_fixed (expected));
+  fail_unless (expected != NULL);
+
+  caps = gst_sdp_media_get_caps_from_media (&media, 96);
+  fail_unless (caps != NULL);
+  fail_unless_equals_int (GST_SDP_OK,
+      gst_sdp_media_attributes_to_caps (&media, caps));
+  fail_unless (gst_caps_is_fixed (caps));
+
+  GST_DEBUG ("    caps %" GST_PTR_FORMAT, caps);
+  GST_DEBUG ("expected %" GST_PTR_FORMAT, expected);
+  fail_unless (gst_caps_is_equal (caps, expected));
+
+  memset (&media2, 0, sizeof (media2));
+  fail_unless_equals_int (GST_SDP_OK, gst_sdp_media_init (&media2));
+  fail_unless_equals_int (GST_SDP_OK,
+      gst_sdp_media_set_media_from_caps (caps, &media2));
+
+  gst_clear_caps (&caps);
+
+  caps = gst_sdp_media_get_caps_from_media (&media, 96);
+  fail_unless (caps != NULL);
+  fail_unless_equals_int (GST_SDP_OK,
+      gst_sdp_media_attributes_to_caps (&media, caps));
+  fail_unless (gst_caps_is_fixed (caps));
+
+  GST_DEBUG ("    caps %" GST_PTR_FORMAT, caps);
+  GST_DEBUG ("expected %" GST_PTR_FORMAT, expected);
+  fail_unless (gst_caps_is_equal (caps, expected));
+
+  gst_sdp_media_uninit (&media);
+  gst_sdp_media_uninit (&media2);
+
+  gst_clear_caps (&caps);
+  gst_clear_caps (&expected);
+}
+
+GST_END_TEST
 /*
  * End of test cases
  */
@@ -785,6 +924,8 @@ sdp_suite (void)
   tcase_add_test (tc_chain, media_from_caps_extmap_pt_100);
   tcase_add_test (tc_chain,
       media_from_caps_h264_with_profile_asymmetry_allowed);
+  tcase_add_test (tc_chain, caps_multiple_rid_parse);
+  tcase_add_test (tc_chain, caps_multiple_rid_parse_with_params);
 
   return s;
 }