h264/5timestamper: provide a workaround for h264/5parse producing pts=NONE buffers
authorMatthew Waters <matthew@centricular.com>
Wed, 9 Nov 2022 07:03:48 +0000 (18:03 +1100)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Tue, 15 Nov 2022 19:32:10 +0000 (19:32 +0000)
A workaround for
https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/649
and
https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/287
which is hard to change baseparse behaviour for both video and audio
parsers.

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

subprojects/gst-plugins-bad/gst/codectimestamper/gstcodectimestamper.c
subprojects/gst-plugins-bad/tests/check/elements/h264timestamper.c [new file with mode: 0644]
subprojects/gst-plugins-bad/tests/check/meson.build

index 6b5741f..9e59e9a 100644 (file)
@@ -62,6 +62,7 @@ struct _GstCodecTimestamperPrivate
   GstClockTime last_dts;
   GstClockTime dts_offset;
   GstClockTime time_adjustment;
+  GstClockTime last_pts;
 
   GstClockTime latency;
 };
@@ -299,6 +300,7 @@ gst_codec_timestamper_flush (GstCodecTimestamper * self)
 
   priv->time_adjustment = GST_CLOCK_TIME_NONE;
   priv->last_dts = GST_CLOCK_TIME_NONE;
+  priv->last_pts = GST_CLOCK_TIME_NONE;
   g_rec_mutex_lock (&priv->lock);
   priv->latency = GST_CLOCK_TIME_NONE;
   g_rec_mutex_unlock (&priv->lock);
@@ -482,6 +484,7 @@ gst_codec_timestamper_drain (GstCodecTimestamper * self)
 
   priv->time_adjustment = GST_CLOCK_TIME_NONE;
   priv->last_dts = GST_CLOCK_TIME_NONE;
+  priv->last_pts = GST_CLOCK_TIME_NONE;
 }
 
 static gint
@@ -540,6 +543,14 @@ gst_codec_timestamper_chain (GstPad * pad, GstObject * parent,
     return ret;
   }
 
+  /* workaround h264/5parse producing pts=NONE buffers when provided with
+   * the same timestamps on sequential buffers */
+  if (GST_CLOCK_TIME_IS_VALID (pts)) {
+    priv->last_pts = pts;
+  } else if (GST_CLOCK_TIME_IS_VALID (priv->last_pts)) {
+    pts = priv->last_pts;
+  }
+
   frame.pts = pts;
   frame.buffer = buffer;
   frame.events = priv->current_frame_events;
@@ -603,6 +614,7 @@ gst_codec_timestamper_reset (GstCodecTimestamper * self)
   priv->latency = GST_CLOCK_TIME_NONE;
   priv->window_size = 0;
   priv->last_dts = GST_CLOCK_TIME_NONE;
+  priv->last_pts = GST_CLOCK_TIME_NONE;
 
   if (priv->current_frame_events) {
     g_list_free_full (priv->current_frame_events,
diff --git a/subprojects/gst-plugins-bad/tests/check/elements/h264timestamper.c b/subprojects/gst-plugins-bad/tests/check/elements/h264timestamper.c
new file mode 100644 (file)
index 0000000..b7206ba
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * GStreamer
+ *
+ * unit test for h264timestamper
+ *
+ * Copyright (C) 2022 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/check/check.h>
+#include <gst/video/video.h>
+/* SPS */
+static guint8 h264_sps[] = {
+  0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x40, 0x15,
+  0xec, 0xa4, 0xbf, 0x2e, 0x02, 0x20, 0x00, 0x00,
+  0x03, 0x00, 0x2e, 0xe6, 0xb2, 0x80, 0x01, 0xe2,
+  0xc5, 0xb2, 0xc0
+};
+
+/* PPS */
+static guint8 h264_pps[] = {
+  0x00, 0x00, 0x00, 0x01, 0x68, 0xeb, 0xec, 0xb2
+};
+
+/* keyframes all around */
+static guint8 h264_idrframe[] = {
+  0x00, 0x00, 0x00, 0x01, 0x65, 0x88, 0x84, 0x00,
+  0x10, 0xff, 0xfe, 0xf6, 0xf0, 0xfe, 0x05, 0x36,
+  0x56, 0x04, 0x50, 0x96, 0x7b, 0x3f, 0x53, 0xe1
+};
+
+static GstBuffer *
+create_keyframe_with_sps_pps (void)
+{
+  gsize size =
+      G_N_ELEMENTS (h264_sps) + G_N_ELEMENTS (h264_pps) +
+      G_N_ELEMENTS (h264_idrframe);
+  GstBuffer *buffer = gst_buffer_new_allocate (NULL, size, NULL);
+  GstMapInfo map_info;
+  gsize offset = 0;
+
+  g_assert (gst_buffer_map (buffer, &map_info, GST_MAP_WRITE));
+  memcpy (&map_info.data[offset], h264_sps, G_N_ELEMENTS (h264_sps));
+  offset += G_N_ELEMENTS (h264_sps);
+  memcpy (&map_info.data[offset], h264_pps, G_N_ELEMENTS (h264_pps));
+  offset += G_N_ELEMENTS (h264_pps);
+  memcpy (&map_info.data[offset], h264_idrframe, G_N_ELEMENTS (h264_idrframe));
+  offset += G_N_ELEMENTS (h264_idrframe);
+
+  gst_buffer_unmap (buffer, &map_info);
+
+  return buffer;
+}
+
+GST_START_TEST (test_input_dts_none)
+{
+  GstHarness *h = gst_harness_new ("h264timestamper");
+  GstBuffer *buffer;
+  int i;
+
+  gst_harness_set_src_caps_str (h,
+      "video/x-h264,stream-format=byte-stream,alignment=au");
+  gst_harness_set_sink_caps_str (h,
+      "video/x-h264,stream-format=byte-stream,alignment=au");
+
+  buffer = create_keyframe_with_sps_pps ();
+  GST_BUFFER_PTS (buffer) = 0;
+  fail_unless_equals_int (gst_harness_push (h, buffer), GST_FLOW_OK);;
+  buffer = gst_buffer_new_memdup (h264_idrframe, G_N_ELEMENTS (h264_idrframe));
+  GST_BUFFER_PTS (buffer) = 1 * GST_MSECOND;
+  fail_unless_equals_int (gst_harness_push (h, buffer), GST_FLOW_OK);;
+  buffer = gst_buffer_new_memdup (h264_idrframe, G_N_ELEMENTS (h264_idrframe));
+  GST_BUFFER_PTS (buffer) = 2 * GST_MSECOND;
+  fail_unless_equals_int (gst_harness_push (h, buffer), GST_FLOW_OK);;
+  buffer = gst_buffer_new_memdup (h264_idrframe, G_N_ELEMENTS (h264_idrframe));
+  GST_BUFFER_PTS (buffer) = 3 * GST_MSECOND;
+  fail_unless_equals_int (gst_harness_push (h, buffer), GST_FLOW_OK);;
+  buffer = gst_buffer_new_memdup (h264_idrframe, G_N_ELEMENTS (h264_idrframe));
+  GST_BUFFER_PTS (buffer) = 4 * GST_MSECOND;
+  fail_unless_equals_int (gst_harness_push (h, buffer), GST_FLOW_OK);;
+
+  gst_harness_push_event (h, gst_event_new_eos ());
+
+  for (i = 0; i < 5; i++) {
+    buffer = gst_harness_pull (h);
+    fail_unless (buffer != NULL);
+    fail_unless (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_PTS (buffer)));
+    fail_unless (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS (buffer)));
+    fail_unless (GST_BUFFER_PTS (buffer) >= GST_BUFFER_DTS (buffer));
+    gst_buffer_unref (buffer);
+  }
+
+  gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_input_pts_none)
+{
+  GstHarness *h = gst_harness_new ("h264timestamper");
+  GstBuffer *buffer;
+  int i;
+
+  gst_harness_set_src_caps_str (h,
+      "video/x-h264,stream-format=byte-stream,alignment=au");
+  gst_harness_set_sink_caps_str (h,
+      "video/x-h264,stream-format=byte-stream,alignment=au");
+
+  buffer = create_keyframe_with_sps_pps ();
+  GST_BUFFER_PTS (buffer) = 0;
+  fail_unless_equals_int (gst_harness_push (h, buffer), GST_FLOW_OK);;
+  buffer = gst_buffer_new_memdup (h264_idrframe, G_N_ELEMENTS (h264_idrframe));
+  GST_BUFFER_PTS (buffer) = GST_CLOCK_TIME_NONE;
+  fail_unless_equals_int (gst_harness_push (h, buffer), GST_FLOW_OK);;
+  buffer = gst_buffer_new_memdup (h264_idrframe, G_N_ELEMENTS (h264_idrframe));
+  GST_BUFFER_PTS (buffer) = 2 * GST_MSECOND;
+  fail_unless_equals_int (gst_harness_push (h, buffer), GST_FLOW_OK);;
+  buffer = gst_buffer_new_memdup (h264_idrframe, G_N_ELEMENTS (h264_idrframe));
+  GST_BUFFER_PTS (buffer) = GST_CLOCK_TIME_NONE;
+  fail_unless_equals_int (gst_harness_push (h, buffer), GST_FLOW_OK);;
+  buffer = gst_buffer_new_memdup (h264_idrframe, G_N_ELEMENTS (h264_idrframe));
+  GST_BUFFER_PTS (buffer) = 4 * GST_MSECOND;
+  fail_unless_equals_int (gst_harness_push (h, buffer), GST_FLOW_OK);;
+
+  gst_harness_push_event (h, gst_event_new_eos ());
+
+  for (i = 0; i < 5; i++) {
+    buffer = gst_harness_pull (h);
+    fail_unless (buffer != NULL);
+    fail_unless (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_PTS (buffer)));
+    fail_unless (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS (buffer)));
+    fail_unless (GST_BUFFER_PTS (buffer) >= GST_BUFFER_DTS (buffer));
+    gst_buffer_unref (buffer);
+  }
+
+  gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+static Suite *
+h264timestamper_suite (void)
+{
+  Suite *s = suite_create ("h264timestamper");
+  TCase *tc = tcase_create ("general");
+
+  tcase_add_test (tc, test_input_dts_none);
+  tcase_add_test (tc, test_input_pts_none);
+
+  suite_add_tcase (s, tc);
+
+  return s;
+}
+
+GST_CHECK_MAIN (h264timestamper);
index ac96323..59b76cd 100644 (file)
@@ -40,6 +40,7 @@ base_tests = [
   [['elements/gdppay.c'], get_option('gdp').disabled()],
   [['elements/h263parse.c'], false, [libparser_dep, gstcodecparsers_dep]],
   [['elements/h264parse.c'], false, [libparser_dep, gstcodecparsers_dep]],
+  [['elements/h264timestamper.c'], false, [libparser_dep, gstcodecparsers_dep]],
   [['elements/h265parse.c'], false, [libparser_dep, gstcodecparsers_dep]],
   [['elements/hlsdemux_m3u8.c'], not hls_dep.found(), [hls_dep]],
   [['elements/id3mux.c'], get_option('id3tag').disabled()],