tests: h264parse: unit tests for sliced data processing
authorGeorge Kiagiadakis <george.kiagiadakis@collabora.com>
Thu, 28 Mar 2019 15:23:30 +0000 (17:23 +0200)
committerNicolas Dufresne <nicolas@ndufresne.ca>
Wed, 15 Apr 2020 14:10:16 +0000 (14:10 +0000)
tests/check/elements/h264parse.c

index ac41d9a..2079e2f 100644 (file)
@@ -379,7 +379,8 @@ GST_START_TEST (test_parse_drain_garbage)
       garbage_frame, sizeof (garbage_frame));
 }
 
-GST_END_TEST
+GST_END_TEST;
+
 GST_START_TEST (test_parse_split)
 {
   gst_parser_test_split (h264_idrframe, sizeof (h264_idrframe));
@@ -801,6 +802,308 @@ h264parse_packetized_suite (void)
   return s;
 }
 
+/* These were generated using pipeline:
+ * gst-launch-1.0 videotestsrc num-buffers=1 pattern=green \
+ *     ! video/x-raw,width=128,height=128 \
+ *     ! openh264enc num-slices=2 \
+ *     ! fakesink dump=1
+ */
+
+/* SPS */
+static guint8 h264_slicing_sps[] = {
+  0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x0b,
+  0x8c, 0x8d, 0x41, 0x02, 0x24, 0x03, 0xc2, 0x21,
+  0x1a, 0x80
+};
+
+/* PPS */
+static guint8 h264_slicing_pps[] = {
+  0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x3c, 0x80
+};
+
+/* IDR Slice 1 */
+static guint8 h264_idr_slice_1[] = {
+  0x00, 0x00, 0x00, 0x01, 0x65, 0xb8, 0x00, 0x04,
+  0x00, 0x00, 0x11, 0xff, 0xff, 0xf8, 0x22, 0x8a,
+  0x1f, 0x1c, 0x00, 0x04, 0x0a, 0x63, 0x80, 0x00,
+  0x81, 0xec, 0x9a, 0x93, 0x93, 0x93, 0x93, 0x93,
+  0x93, 0xad, 0x57, 0x5d, 0x75, 0xd7, 0x5d, 0x75,
+  0xd7, 0x5d, 0x75, 0xd7, 0x5d, 0x75, 0xd7, 0x5d,
+  0x75, 0xd7, 0x5d, 0x78
+};
+
+/* IDR Slice 2 */
+static guint8 h264_idr_slice_2[] = {
+  0x00, 0x00, 0x00, 0x01, 0x65, 0x04, 0x2e, 0x00,
+  0x01, 0x00, 0x00, 0x04, 0x7f, 0xff, 0xfe, 0x08,
+  0xa2, 0x87, 0xc7, 0x00, 0x01, 0x02, 0x98, 0xe0,
+  0x00, 0x20, 0x7b, 0x26, 0xa4, 0xe4, 0xe4, 0xe4,
+  0xe4, 0xe4, 0xeb, 0x55, 0xd7, 0x5d, 0x75, 0xd7,
+  0x5d, 0x75, 0xd7, 0x5d, 0x75, 0xd7, 0x5d, 0x75,
+  0xd7, 0x5d, 0x75, 0xd7, 0x5e
+};
+
+static inline GstBuffer *
+wrap_buffer (guint8 * buf, gsize size, GstClockTime pts, GstBufferFlags flags)
+{
+  GstBuffer *buffer;
+
+  buffer = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY,
+      buf, size, 0, size, NULL, NULL);
+  GST_BUFFER_PTS (buffer) = pts;
+  GST_BUFFER_FLAGS (buffer) |= flags;
+
+  return buffer;
+}
+
+static inline GstBuffer *
+composite_buffer (GstClockTime pts, GstBufferFlags flags, gint count, ...)
+{
+  va_list vl;
+  gint i;
+  guint8 *data;
+  gsize size;
+  GstBuffer *buffer;
+
+  va_start (vl, count);
+
+  buffer = gst_buffer_new ();
+  for (i = 0; i < count; i++) {
+    data = va_arg (vl, guint8 *);
+    size = va_arg (vl, gsize);
+
+    buffer = gst_buffer_append (buffer, wrap_buffer (data, size, 0, 0));
+  }
+  GST_BUFFER_PTS (buffer) = pts;
+  GST_BUFFER_FLAGS (buffer) |= flags;
+
+  va_end (vl);
+
+  return buffer;
+}
+
+#define pull_and_check_full(h, data, size, pts, flags) \
+{ \
+  GstBuffer *b = gst_harness_pull (h); \
+  gst_check_buffer_data (b, data, size); \
+  fail_unless_equals_clocktime (GST_BUFFER_PTS (b), pts); \
+  if (flags) \
+    fail_unless (GST_BUFFER_FLAG_IS_SET (b, flags)); \
+  gst_buffer_unref (b); \
+}
+
+#define pull_and_check(h, data, pts, flags) \
+  pull_and_check_full (h, data, sizeof (data), pts, flags)
+
+/* used to check NALs for which the parser removes the first 0x00 byte;
+ * this parser behavior is a bit broken, so we may remove that in the future */
+#define pull_and_check_skip1byte(h, data, pts, flags) \
+  pull_and_check_full (h, data + 1, sizeof (data) - 1, pts, flags)
+
+#define pull_and_drop(h) \
+  G_STMT_START { \
+    GstBuffer *b = gst_harness_pull (h); \
+    gst_buffer_unref (b); \
+  } G_STMT_END;
+
+GST_START_TEST (test_parse_sliced_nal_nal)
+{
+  GstHarness *h = gst_harness_new ("h264parse");
+  GstBuffer *buf;
+
+  gst_harness_set_caps_str (h,
+      "video/x-h264,stream-format=byte-stream,alignment=nal,parsed=false,framerate=30/1",
+      "video/x-h264,stream-format=byte-stream,alignment=nal,parsed=true");
+
+  buf = wrap_buffer (h264_slicing_sps, sizeof (h264_slicing_sps), 10, 0);
+  fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+
+  buf = wrap_buffer (h264_slicing_pps, sizeof (h264_slicing_pps), 10, 0);
+  fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+
+  /* parser must have inserted AUD before the headers, with the same PTS */
+  pull_and_check (h, h264_aud, 10, 0);
+
+  /* drop the header buffers */
+  while ((buf = gst_harness_try_pull (h)))
+    gst_buffer_unref (buf);
+
+  /* reported latency must be zero */
+  fail_unless_equals_clocktime (gst_harness_query_latency (h), 0);
+
+  /* test some flow with 2 slices.
+   * 1st slice gets the input PTS, second gets NONE */
+  buf = wrap_buffer (h264_idr_slice_1, sizeof (h264_idr_slice_1), 100, 0);
+  fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+  fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
+  pull_and_check_skip1byte (h, h264_idr_slice_1, 100, 0);
+
+  buf = wrap_buffer (h264_idr_slice_2, sizeof (h264_idr_slice_2), 100, 0);
+  fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+  fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
+  pull_and_check_skip1byte (h, h264_idr_slice_2, -1, 0);
+
+  buf = wrap_buffer (h264_idr_slice_1, sizeof (h264_idr_slice_1), 200, 0);
+  fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+  fail_unless_equals_int (gst_harness_buffers_in_queue (h), 2);
+  pull_and_check (h, h264_aud, 200, 0);
+  pull_and_check_skip1byte (h, h264_idr_slice_1, 200, 0);
+
+  buf = wrap_buffer (h264_idr_slice_2, sizeof (h264_idr_slice_2), 200, 0);
+  fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+  fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
+  pull_and_check_skip1byte (h, h264_idr_slice_2, -1, 0);
+
+  buf = wrap_buffer (h264_idr_slice_1, sizeof (h264_idr_slice_1), 250, 0);
+  fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+  fail_unless_equals_int (gst_harness_buffers_in_queue (h), 2);
+  pull_and_check (h, h264_aud, 250, 0);
+  pull_and_check_skip1byte (h, h264_idr_slice_1, 250, 0);
+
+  /* 1st slice starts a new AU, even though the previous one is incomplete.
+   * DISCONT must also be propagated */
+  buf = wrap_buffer (h264_idr_slice_1, sizeof (h264_idr_slice_1), 400,
+      GST_BUFFER_FLAG_DISCONT);
+  fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+  fail_unless_equals_int (gst_harness_buffers_in_queue (h), 2);
+  pull_and_check (h, h264_aud, 400, 0);
+  pull_and_check_skip1byte (h, h264_idr_slice_1, 400, GST_BUFFER_FLAG_DISCONT);
+
+  gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_parse_sliced_au_nal)
+{
+  GstHarness *h = gst_harness_new ("h264parse");
+  GstBuffer *buf;
+
+  gst_harness_set_caps_str (h,
+      "video/x-h264,stream-format=byte-stream,alignment=au,parsed=false,framerate=30/1",
+      "video/x-h264,stream-format=byte-stream,alignment=nal,parsed=true");
+
+  /* push the whole AU in a single buffer */
+  buf = composite_buffer (100, 0, 4,
+      h264_slicing_sps, sizeof (h264_slicing_sps),
+      h264_slicing_pps, sizeof (h264_slicing_pps),
+      h264_idr_slice_1, sizeof (h264_idr_slice_1),
+      h264_idr_slice_2, sizeof (h264_idr_slice_2));
+  fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+
+  /* parser must have inserted AUD before the headers, with the same PTS */
+  pull_and_check (h, h264_aud, 100, 0);
+
+  /* drop the headers */
+  fail_unless (gst_harness_buffers_in_queue (h) > 2);
+  while (gst_harness_buffers_in_queue (h) > 2)
+    pull_and_drop (h);
+
+  /* reported latency must be zero */
+  fail_unless_equals_clocktime (gst_harness_query_latency (h), 0);
+
+  /* 1st slice here doens't have a PTS
+   * because it was present in the first header NAL */
+  pull_and_check_skip1byte (h, h264_idr_slice_1, -1, 0);
+  pull_and_check_skip1byte (h, h264_idr_slice_2, -1, 0);
+
+  /* new AU. we expect AUD to be inserted and 1st slice to have the same PTS */
+  buf = composite_buffer (200, 0, 2,
+      h264_idr_slice_1, sizeof (h264_idr_slice_1),
+      h264_idr_slice_2, sizeof (h264_idr_slice_2));
+  fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+  fail_unless_equals_int (gst_harness_buffers_in_queue (h), 3);
+  pull_and_check (h, h264_aud, 200, 0);
+  pull_and_check_skip1byte (h, h264_idr_slice_1, 200, 0);
+  pull_and_check_skip1byte (h, h264_idr_slice_2, -1, 0);
+
+  /* DISCONT must be propagated */
+  buf = composite_buffer (400, GST_BUFFER_FLAG_DISCONT, 2,
+      h264_idr_slice_1, sizeof (h264_idr_slice_1),
+      h264_idr_slice_2, sizeof (h264_idr_slice_2));
+  fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+  fail_unless_equals_int (gst_harness_buffers_in_queue (h), 3);
+  pull_and_check (h, h264_aud, 400, 0);
+  pull_and_check_skip1byte (h, h264_idr_slice_1, 400, GST_BUFFER_FLAG_DISCONT);
+  pull_and_check_skip1byte (h, h264_idr_slice_2, -1, 0);
+
+  gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_parse_sliced_nal_au)
+{
+  GstHarness *h = gst_harness_new ("h264parse");
+  GstBuffer *buf;
+
+  gst_harness_set_caps_str (h,
+      "video/x-h264,stream-format=byte-stream,alignment=nal,parsed=false,framerate=30/1",
+      "video/x-h264,stream-format=byte-stream,alignment=au,parsed=true");
+
+  buf = wrap_buffer (h264_slicing_sps, sizeof (h264_slicing_sps), 100, 0);
+  fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+
+  buf = wrap_buffer (h264_slicing_pps, sizeof (h264_slicing_pps), 100, 0);
+  fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+
+  buf = wrap_buffer (h264_idr_slice_1, sizeof (h264_idr_slice_1), 100, 0);
+  fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+
+  buf = wrap_buffer (h264_idr_slice_2, sizeof (h264_idr_slice_2), 100, 0);
+  fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+
+  /* no output yet, it will be pushed as soon as
+   * the parser recognizes the new AU */
+  fail_unless_equals_int (gst_harness_buffers_in_queue (h), 0);
+
+  buf = wrap_buffer (h264_idr_slice_1, sizeof (h264_idr_slice_1), 200, 0);
+  fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+
+  fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
+
+  {
+    GstMapInfo info;
+
+    buf = composite_buffer (100, 0, 5,
+        h264_aud, sizeof (h264_aud),
+        h264_slicing_sps, sizeof (h264_slicing_sps),
+        h264_slicing_pps, sizeof (h264_slicing_pps),
+        h264_idr_slice_1, sizeof (h264_idr_slice_1),
+        h264_idr_slice_2, sizeof (h264_idr_slice_2));
+    gst_buffer_map (buf, &info, GST_MAP_READ);
+
+    pull_and_check_full (h, info.data, info.size, 100, 0);
+
+    gst_buffer_unmap (buf, &info);
+    gst_buffer_unref (buf);
+  }
+
+  /* reported latency must be 1 frame (@ 30fps because of sink pad caps) */
+  fail_unless_equals_clocktime (gst_harness_query_latency (h),
+      gst_util_uint64_scale (GST_SECOND, 1, 30));
+
+  gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
+
+static Suite *
+h264parse_sliced_suite (void)
+{
+  Suite *s = suite_create (ctx_suite);
+  TCase *tc_chain = tcase_create ("general");
+
+  suite_add_tcase (s, tc_chain);
+  tcase_add_test (tc_chain, test_parse_sliced_nal_nal);
+  tcase_add_test (tc_chain, test_parse_sliced_au_nal);
+  tcase_add_test (tc_chain, test_parse_sliced_nal_au);
+
+  return s;
+}
+
+
 GST_START_TEST (test_parse_sei_closedcaptions)
 {
   GstVideoCaptionMeta *cc;
@@ -950,6 +1253,10 @@ main (int argc, char **argv)
   s = h264parse_packetized_suite ();
   nf += gst_check_run_suite (s, ctx_suite, __FILE__ "_packetized.c");
 
+  ctx_suite = "h264parse_sliced";
+  s = h264parse_sliced_suite ();
+  nf += gst_check_run_suite (s, ctx_suite, __FILE__ "_sliced.c");
+
   {
     TCase *tc_chain = tcase_create ("general");