rtph264pay: Handle 'profile' field
authorThibault Saunier <tsaunier@igalia.com>
Thu, 2 Dec 2021 13:32:33 +0000 (13:32 +0000)
committerThibault Saunier <tsaunier@igalia.com>
Sun, 12 Dec 2021 13:59:00 +0000 (10:59 -0300)
In order to allow "level-asymmetry-allowed" we now handle a new
"profile" field, which as the same semantics as the "profile" field in
H.264 stream so that we can force payloaded stream to have the right
format when using the `gst_sdp_media_get_caps_from_media` to set caps
filter after the payloader. This allows a simple negotiation in standard
RTP negotiation based on SDPs (like webrtc) for that particular case,
closely respecting the specs.

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

subprojects/gst-integration-testsuites/testsuites/validate.testslist
subprojects/gst-integration-testsuites/testsuites/validate/rtp/h264/payloader_fail_nego_force_profile.validatetest [new file with mode: 0644]
subprojects/gst-integration-testsuites/testsuites/validate/rtp/h264/payloader_nego_profile.validatetest [new file with mode: 0644]
subprojects/gst-integration-testsuites/testsuites/validate/rtp/h264/payloader_nego_profile/flow-expectations/log-pay-sink-expected [new file with mode: 0644]
subprojects/gst-integration-testsuites/testsuites/validate/rtp/h264/payloader_nego_profile/flow-expectations/log-pay-src-expected [new file with mode: 0644]
subprojects/gst-plugins-good/gst/rtp/gstrtph264pay.c
subprojects/gst-plugins-good/gst/rtp/gstrtph264pay.h

index a78f21d..bf5d235 100644 (file)
@@ -944,6 +944,8 @@ validate.test.mp4.redirect.play_15s
 validate.test.nle.urisource.play
 validate.test.playbin.check_active_stream
 validate.test.playbin3.sourcebin_check_mixed_static_and_dyanmic_pads
+validate.test.rtp.h264.payloader_fail_nego_force_profile
+validate.test.rtp.h264.payloader_nego_profile
 validate.test.rtp.rtpsession_recv_simple
 validate.test.rtp.rtpsession_send_simple
 validate.test.scaletempo.playbin_audio_filter.fast_forward
diff --git a/subprojects/gst-integration-testsuites/testsuites/validate/rtp/h264/payloader_fail_nego_force_profile.validatetest b/subprojects/gst-integration-testsuites/testsuites/validate/rtp/h264/payloader_fail_nego_force_profile.validatetest
new file mode 100644 (file)
index 0000000..a951c5c
--- /dev/null
@@ -0,0 +1,16 @@
+meta,
+    args = {
+        "videotestsrc num-buffers=1 ! x264enc ! h264parse ! \
+            video/x-h264,level=(string)4,profile=baseline ! rtph264pay name=pay ! \
+            application/x-rtp,profile=constrained-baseline ! fakesink",
+    },
+    expected-issues = {
+        [
+            expected-issue,
+                level=critical,
+                issue-id=runtime::not-negotiated,
+                details=".*Field 'profile'.*can't intersect with filter value.*baseline.*",
+        ],
+    }
+
+
diff --git a/subprojects/gst-integration-testsuites/testsuites/validate/rtp/h264/payloader_nego_profile.validatetest b/subprojects/gst-integration-testsuites/testsuites/validate/rtp/h264/payloader_nego_profile.validatetest
new file mode 100644 (file)
index 0000000..195a343
--- /dev/null
@@ -0,0 +1,13 @@
+meta,
+    args = {
+        "videotestsrc num-buffers=1 ! x264enc ! h264parse ! \
+            video/x-h264,level=(string)4 ! rtph264pay name=pay ! \
+            application/x-rtp,profile=constrained-baseline ! fakesink",
+    },
+    configs = {
+        "$(validateflow), pad=pay:sink, ignored-event-types={tag, segment}, \
+            logged-fields=\"caps={framerate, height, level, profile, stream-format, width}\"",
+        "$(validateflow), pad=pay:src, ignored-event-types={tag, segment}, \
+            logged-fields=\"caps={a-framerate, clock-rate, encoding-name, media, packetization-mode, payload, profile, profile-level-id}\"",
+    }
+
diff --git a/subprojects/gst-integration-testsuites/testsuites/validate/rtp/h264/payloader_nego_profile/flow-expectations/log-pay-sink-expected b/subprojects/gst-integration-testsuites/testsuites/validate/rtp/h264/payloader_nego_profile/flow-expectations/log-pay-sink-expected
new file mode 100644 (file)
index 0000000..5367ae3
--- /dev/null
@@ -0,0 +1,3 @@
+event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE, group-id=(uint)1;
+event caps: video/x-h264, framerate=(fraction)30/1, height=(int)240, level=(string)4, profile=(string)constrained-baseline, stream-format=(string)avc, width=(int)320;
+event eos: (no structure)
diff --git a/subprojects/gst-integration-testsuites/testsuites/validate/rtp/h264/payloader_nego_profile/flow-expectations/log-pay-src-expected b/subprojects/gst-integration-testsuites/testsuites/validate/rtp/h264/payloader_nego_profile/flow-expectations/log-pay-src-expected
new file mode 100644 (file)
index 0000000..4c18169
--- /dev/null
@@ -0,0 +1,3 @@
+event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE, group-id=(uint)1;
+event caps: application/x-rtp, a-framerate=(string)30, clock-rate=(int)90000, encoding-name=(string)H264, media=(string)video, packetization-mode=(string)1, payload=(int)96, profile=(string)constrained-baseline, profile-level-id=(string)42c028;
+event eos: (no structure)
index 49471d8..5df5b8b 100644 (file)
@@ -223,7 +223,7 @@ static void
 gst_rtp_h264_pay_init (GstRtpH264Pay * rtph264pay)
 {
   rtph264pay->queue = g_array_new (FALSE, FALSE, sizeof (guint));
-  rtph264pay->profile = 0;
+  rtph264pay->profile_level = 0;
   rtph264pay->sps = g_ptr_array_new_with_free_func (
       (GDestroyNotify) gst_buffer_unref);
   rtph264pay->pps = g_ptr_array_new_with_free_func (
@@ -321,7 +321,7 @@ gst_rtp_h264_pay_getcaps (GstRTPBasePayload * payload, GstPad * pad,
   for (i = 0; i < gst_caps_get_size (allowed_caps); i++) {
     GstStructure *s = gst_caps_get_structure (allowed_caps, i);
     GstStructure *new_s = gst_structure_new_empty ("video/x-h264");
-    const gchar *profile_level_id;
+    const gchar *profile_level_id, *profile;
 
     profile_level_id = gst_structure_get_string (s, "profile-level-id");
 
@@ -343,9 +343,9 @@ gst_rtp_h264_pay_getcaps (GstRTPBasePayload * payload, GstPad * pad,
         GST_LOG_OBJECT (payload, "In caps, have profile %s and level %s",
             profile, level);
 
-        if (!strcmp (profile, "constrained-baseline"))
+        if (!strcmp (profile, "constrained-baseline")) {
           gst_structure_set (new_s, "profile", G_TYPE_STRING, profile, NULL);
-        else {
+        else {
           GValue val = { 0, };
           GValue profiles = { 0, };
 
@@ -385,6 +385,8 @@ gst_rtp_h264_pay_getcaps (GstRTPBasePayload * payload, GstPad * pad,
         gst_structure_set (new_s,
             "profile", G_TYPE_STRING, "constrained-baseline", NULL);
       }
+    } else if ((profile = gst_structure_get_string (s, "profile"))) {
+      gst_structure_set (new_s, "profile", G_TYPE_STRING, profile, NULL);
     } else {
       /* No profile-level-id means baseline or unrestricted */
 
@@ -457,14 +459,13 @@ gst_rtp_h264_pay_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
   return gst_pad_query_default (pad, parent, query);
 }
 
-
 /* take the currently configured SPS and PPS lists and set them on the caps as
  * sprop-parameter-sets */
 static gboolean
 gst_rtp_h264_pay_set_sps_pps (GstRTPBasePayload * basepayload)
 {
+  GstStructure *s = gst_structure_new_empty ("unused");
   GstRtpH264Pay *payloader = GST_RTP_H264_PAY (basepayload);
-  gchar *profile;
   gchar *set;
   GString *sprops;
   guint count;
@@ -502,24 +503,34 @@ gst_rtp_h264_pay_set_sps_pps (GstRTPBasePayload * basepayload)
   }
 
   if (G_LIKELY (count)) {
-    if (payloader->profile != 0) {
-      /* profile is 24 bit. Force it to respect the limit */
-      profile = g_strdup_printf ("%06x", payloader->profile & 0xffffff);
-      /* combine into output caps */
-      res = gst_rtp_base_payload_set_outcaps (basepayload,
-          "packetization-mode", G_TYPE_STRING, "1",
-          "profile-level-id", G_TYPE_STRING, profile,
-          "sprop-parameter-sets", G_TYPE_STRING, sprops->str, NULL);
-      g_free (profile);
-    } else {
-      res = gst_rtp_base_payload_set_outcaps (basepayload,
-          "packetization-mode", G_TYPE_STRING, "1",
-          "sprop-parameter-sets", G_TYPE_STRING, sprops->str, NULL);
+    gchar *profile_level;
+
+    gst_structure_set (s,
+        "packetization-mode", G_TYPE_STRING, "1",
+        "sprop-parameter-sets", G_TYPE_STRING, sprops->str, NULL);
+
+    if (payloader->profile_level != 0) {
+      guint8 sps[2] = {
+        payloader->profile_level >> 16,
+        payloader->profile_level >> 8,
+      };
+
+      profile_level =
+          g_strdup_printf ("%06x", payloader->profile_level & 0xffffff);
+      gst_structure_set (s,
+          "profile-level-id", G_TYPE_STRING, profile_level,
+          "profile", G_TYPE_STRING, gst_codec_utils_h264_get_profile (sps, 2),
+          NULL);
+
+      g_free (profile_level);
     }
 
+    /* combine into output caps */
+    res = gst_rtp_base_payload_set_outcaps_structure (basepayload, s);
   } else {
     res = gst_rtp_base_payload_set_outcaps (basepayload, NULL);
   }
+  gst_structure_free (s);
   g_string_free (sprops, TRUE);
 
   return res;
@@ -591,8 +602,8 @@ gst_rtp_h264_pay_setcaps (GstRTPBasePayload * basepayload, GstCaps * caps)
     /* AVCProfileIndication */
     /* profile_compat */
     /* AVCLevelIndication */
-    rtph264pay->profile = (data[1] << 16) | (data[2] << 8) | data[3];
-    GST_DEBUG_OBJECT (rtph264pay, "profile %06x", rtph264pay->profile);
+    rtph264pay->profile_level = (data[1] << 16) | (data[2] << 8) | data[3];
+    GST_DEBUG_OBJECT (rtph264pay, "profile %06x", rtph264pay->profile_level);
 
     /* 6 bits reserved | 2 bits lengthSizeMinusOne */
     /* this is the number of bytes in front of the NAL units to mark their
index 47cc896..c983a92 100644 (file)
@@ -65,7 +65,7 @@ struct _GstRtpH264Pay
 {
   GstRTPBasePayload payload;
 
-  guint profile;
+  guint profile_level;
   GPtrArray *sps, *pps;
 
   GstH264StreamFormat stream_format;