From 207769edbb082998dbd5cccc9a17301de960f162 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Wed, 1 Sep 2021 14:02:29 +1000 Subject: [PATCH] sdp: support multiple rid parameters 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: --- .../gst-libs/gst/sdp/gstsdpmessage.c | 185 ++++++++++++++++++ .../gst-plugins-base/tests/check/libs/sdp.c | 141 +++++++++++++ 2 files changed, 326 insertions(+) diff --git a/subprojects/gst-plugins-base/gst-libs/gst/sdp/gstsdpmessage.c b/subprojects/gst-plugins-base/gst-libs/gst/sdp/gstsdpmessage.c index a546647f47..2fb6d68ffa 100644 --- a/subprojects/gst-plugins-base/gst-libs/gst/sdp/gstsdpmessage.c +++ b/subprojects/gst-plugins-base/gst-libs/gst/sdp/gstsdpmessage.c @@ -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); diff --git a/subprojects/gst-plugins-base/tests/check/libs/sdp.c b/subprojects/gst-plugins-base/tests/check/libs/sdp.c index f032ac1529..70b2052fd4 100644 --- a/subprojects/gst-plugins-base/tests/check/libs/sdp.c +++ b/subprojects/gst-plugins-base/tests/check/libs/sdp.c @@ -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* */ @@ -758,6 +771,132 @@ GST_START_TEST (media_from_caps_h264_with_profile_asymmetry_allowed) gst_sdp_message_free (message); } +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; }