h264parse: Validate VUI framerate
authorSeungha Yang <seungha@centricular.com>
Tue, 7 Mar 2023 14:46:50 +0000 (23:46 +0900)
committerTim-Philipp Müller <tim@centricular.com>
Wed, 8 Mar 2023 15:43:25 +0000 (15:43 +0000)
A few streams contain invalid/wrong framerate in VUI. Do validate
it based on the spec and ignore invalid VUI framerate values.

Fixes: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/1753
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4131>

subprojects/gst-plugins-bad/gst/videoparsers/gsth264parse.c
subprojects/gst-plugins-bad/gst/videoparsers/gsth264parse.h

index e08e3f0..8bb47f9 100644 (file)
@@ -276,6 +276,7 @@ gst_h264_parse_reset_stream_info (GstH264Parse * h264parse)
   h264parse->packetized = FALSE;
   h264parse->push_codec = FALSE;
   h264parse->first_frame = TRUE;
+  h264parse->ignore_vui_fps = FALSE;
 
   gst_buffer_replace (&h264parse->codec_data, NULL);
   gst_buffer_replace (&h264parse->codec_data_in, NULL);
@@ -2039,6 +2040,82 @@ get_level_string (GstH264SPS * sps)
   }
 }
 
+typedef enum
+{
+  GST_H264_LEVEL_L1 = 10,
+  GST_H264_LEVEL_L1B = 9,
+  GST_H264_LEVEL_L1_1 = 11,
+  GST_H264_LEVEL_L1_2 = 12,
+  GST_H264_LEVEL_L1_3 = 13,
+  GST_H264_LEVEL_L2_0 = 20,
+  GST_H264_LEVEL_L2_1 = 21,
+  GST_H264_LEVEL_L2_2 = 22,
+  GST_H264_LEVEL_L3 = 30,
+  GST_H264_LEVEL_L3_1 = 31,
+  GST_H264_LEVEL_L3_2 = 32,
+  GST_H264_LEVEL_L4 = 40,
+  GST_H264_LEVEL_L4_1 = 41,
+  GST_H264_LEVEL_L4_2 = 42,
+  GST_H264_LEVEL_L5 = 50,
+  GST_H264_LEVEL_L5_1 = 51,
+  GST_H264_LEVEL_L5_2 = 52,
+  GST_H264_LEVEL_L6 = 60,
+  GST_H264_LEVEL_L6_1 = 61,
+  GST_H264_LEVEL_L6_2 = 62,
+} GstH264Level;
+
+typedef struct
+{
+  GstH264Level level;
+  guint max_sample_per_sec;
+} GstH264LevelLimit;
+
+static const GstH264LevelLimit level_limits_map[] = {
+  {GST_H264_LEVEL_L1, 380160},
+  {GST_H264_LEVEL_L1B, 380160},
+  {GST_H264_LEVEL_L1_1, 768000},
+  {GST_H264_LEVEL_L1_2, 1536000},
+  {GST_H264_LEVEL_L1_3, 3041280},
+  {GST_H264_LEVEL_L2_0, 3041280},
+  {GST_H264_LEVEL_L2_1, 5068800},
+  {GST_H264_LEVEL_L2_2, 5184000},
+  {GST_H264_LEVEL_L3, 10368000},
+  {GST_H264_LEVEL_L3_1, 27648000},
+  {GST_H264_LEVEL_L3_2, 55296000},
+  {GST_H264_LEVEL_L4, 62914560},
+  {GST_H264_LEVEL_L4_1, 62914560},
+  {GST_H264_LEVEL_L4_2, 62914560},
+  {GST_H264_LEVEL_L5, 150994994},
+  {GST_H264_LEVEL_L5_1, 251658240},
+  {GST_H264_LEVEL_L5_2, 530841600},
+  {GST_H264_LEVEL_L6, 1069547520},
+  {GST_H264_LEVEL_L6_1, 2139095040},
+  {GST_H264_LEVEL_L6_2, 4278190080},
+};
+
+/* A.3.4 Effect of level limits on frame rate (informative) */
+static guint
+get_max_samples_per_second (const GstH264SPS * sps)
+{
+  guint i;
+  guint n_levels = G_N_ELEMENTS (level_limits_map);
+  GstH264Level level = (GstH264Level) sps->level_idc;
+
+  if (level == GST_H264_LEVEL_L1_1 &&
+      (sps->profile_idc == 66 || sps->profile_idc == 77) &&
+      sps->constraint_set3_flag) {
+    /* Level 1b */
+    level = GST_H264_LEVEL_L1B;
+  }
+
+  for (i = 0; i < n_levels; i++) {
+    if (level == level_limits_map[i].level)
+      return level_limits_map[i].max_sample_per_sec;
+  }
+
+  return level_limits_map[n_levels - 1].max_sample_per_sec;
+}
+
 static void
 gst_h264_parse_update_src_caps (GstH264Parse * h264parse, GstCaps * caps)
 {
@@ -2122,6 +2199,32 @@ gst_h264_parse_update_src_caps (GstH264Parse * h264parse, GstCaps * caps)
      * it in case we have no info */
     gst_h264_video_calculate_framerate (sps, h264parse->field_pic_flag,
         h264parse->sei_pic_struct, &fps_num, &fps_den);
+
+    /* Checks whether given framerate makes sense or not
+     * See also A.3.4 Effect of level limits on frame rate (informative)
+     */
+    h264parse->ignore_vui_fps = FALSE;
+    if (fps_num > 0 && fps_den > 0 && sps->width > 0 && sps->height > 0 &&
+        sps->vui_parameters_present_flag &&
+        sps->vui_parameters.timing_info_present_flag) {
+      guint luma_samples = sps->width * sps->height;
+      guint max_samples = get_max_samples_per_second (sps);
+      gdouble max_fps, cur_fps;
+
+      cur_fps = (gdouble) fps_num / fps_den;
+      max_fps = (gdouble) max_samples / luma_samples;
+
+      /* XXX: allows up to 2x higher framerate */
+      if (max_fps * 2 < cur_fps) {
+        GST_WARNING_OBJECT (h264parse,
+            "VUI framerate %.1f exceeds allowed maximum %.1f",
+            cur_fps, max_fps);
+        fps_num = 0;
+        fps_den = 1;
+        h264parse->ignore_vui_fps = TRUE;
+      }
+    }
+
     if (G_UNLIKELY (h264parse->fps_num != fps_num
             || h264parse->fps_den != fps_den)) {
       GST_DEBUG_OBJECT (h264parse, "framerate changed %d/%d", fps_num, fps_den);
@@ -2461,6 +2564,9 @@ gst_h264_parse_get_duration (GstH264Parse * h264parse, gboolean frame)
   if (!sps) {
     GST_DEBUG_OBJECT (h264parse, "referred SPS invalid");
     goto fps_duration;
+  } else if (h264parse->ignore_vui_fps) {
+    GST_DEBUG_OBJECT (h264parse, "VUI framerate is not reliable");
+    goto fps_duration;
   } else if (!sps->vui_parameters_present_flag) {
     GST_DEBUG_OBJECT (h264parse, "unable to compute duration: VUI not present");
     goto fps_duration;
index 1275ddd..1b6939d 100644 (file)
@@ -117,6 +117,7 @@ struct _GstH264Parse
   guint8 sei_pic_struct;
   guint8 sei_pic_struct_pres_flag;
   guint field_pic_flag;
+  gboolean ignore_vui_fps;
 
   /* cached timestamps */
   /* (trying to) track upstream dts and interpolate */