From 6265fc84245dfdd1f4c218594eee672ceef90a72 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Wed, 9 Nov 2022 18:03:48 +1100 Subject: [PATCH] h264/5timestamper: provide a workaround for h264/5parse producing pts=NONE buffers 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: --- .../codectimestamper/gstcodectimestamper.c | 12 ++ .../tests/check/elements/h264timestamper.c | 172 ++++++++++++++++++ .../gst-plugins-bad/tests/check/meson.build | 1 + 3 files changed, 185 insertions(+) create mode 100644 subprojects/gst-plugins-bad/tests/check/elements/h264timestamper.c diff --git a/subprojects/gst-plugins-bad/gst/codectimestamper/gstcodectimestamper.c b/subprojects/gst-plugins-bad/gst/codectimestamper/gstcodectimestamper.c index 6b5741f027..9e59e9a656 100644 --- a/subprojects/gst-plugins-bad/gst/codectimestamper/gstcodectimestamper.c +++ b/subprojects/gst-plugins-bad/gst/codectimestamper/gstcodectimestamper.c @@ -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 index 0000000000..b7206bae6b --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/check/elements/h264timestamper.c @@ -0,0 +1,172 @@ +/* + * GStreamer + * + * unit test for h264timestamper + * + * Copyright (C) 2022 Matthew Waters + * + * 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 +#include +/* 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); diff --git a/subprojects/gst-plugins-bad/tests/check/meson.build b/subprojects/gst-plugins-bad/tests/check/meson.build index ac96323dfc..59b76cd86e 100644 --- a/subprojects/gst-plugins-bad/tests/check/meson.build +++ b/subprojects/gst-plugins-bad/tests/check/meson.build @@ -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()],