decklink: calculate internal using values closer to the current clock times

In a previous version of the clock time conversion code, scheduled playback used
to be started (from 0) when transitioning to PLAYING and stopped when
transitioning PLAYING->PAUSED. This worked fine when converting running times
using the internal clock.  However, now the decklink clock will produce values
that are monotonically increasing and do not reset to 0 at the same moments as
running time anymore. This means that the clock adjustments could attempt to
convert a small running time based on a large clock time e.g. after pausing
for many hours. As the adjustment code is a simple linear interpolation based on
the current clock times (large) using the provided value (small), the small
differences in the rate could result in very large differences in the
output time.

Fix by instead using both internal and external clock times based on the values
that gst_clock_get_calibration() will return. By doing so, small changes in the
rate calculations between the internal and external clock times will not result
in potentially large differences in the output internal time from
gst_clock_unadjust_with_calibration().

Fixes https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/4197

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/9267>
This commit is contained in:
Matthew Waters 2025-06-18 14:20:32 +10:00 committed by GStreamer Marge Bot
parent d432dbd106
commit cae2b38614
2 changed files with 21 additions and 57 deletions

View File

@ -1343,49 +1343,31 @@ gst_decklink_video_sink_convert_to_internal_clock (GstDecklinkVideoSink * self,
GstClockTime * timestamp, GstClockTime * duration)
{
GstClock *clock;
GstClockTime internal_base, external_base, internal_offset;
GstClockTime internal_base, external_base, base_time;
g_assert (timestamp != NULL);
clock = gst_element_get_clock (GST_ELEMENT_CAST (self));
base_time = gst_element_get_base_time (GST_ELEMENT_CAST (self));
GST_OBJECT_LOCK (self);
internal_base = self->internal_base_time;
external_base = self->external_base_time;
internal_offset = self->internal_time_offset;
GST_OBJECT_UNLOCK (self);
if (!clock || clock != self->output->clock) {
// If we don't have any initial correlation between multiple clocks
if (external_base == GST_CLOCK_TIME_NONE ||
internal_base == GST_CLOCK_TIME_NONE) {
*timestamp = gst_clock_get_internal_time (self->output->clock);
} else if (!clock || clock != self->output->clock) {
GstClockTime internal, external, rate_n, rate_d;
GstClockTime external_timestamp = *timestamp;
GstClockTime base_time;
gst_clock_get_calibration (self->output->clock, &internal, &external,
&rate_n, &rate_d);
// Convert to the running time corresponding to both clock times
if (!GST_CLOCK_TIME_IS_VALID (internal_base) || internal < internal_base)
internal = 0;
else
internal -= internal_base;
if (!GST_CLOCK_TIME_IS_VALID (external_base) || external < external_base)
external = 0;
else
external -= external_base;
// Convert timestamp to the "running time" since we started scheduled
// playback, that is the difference between the pipeline's base time
// and our own base time.
base_time = gst_element_get_base_time (GST_ELEMENT_CAST (self));
if (base_time > external_base)
base_time = 0;
else
base_time = external_base - base_time;
if (external_timestamp < base_time)
external_timestamp = 0;
else
external_timestamp = external_timestamp - base_time;
// get the clock time for this running time
external_timestamp += base_time;
// Get the difference in the external time, note
// that the running time is external time.
@ -1420,18 +1402,12 @@ gst_decklink_video_sink_convert_to_internal_clock (GstDecklinkVideoSink * self,
} else {
GST_LOG_OBJECT (self, "No clock conversion needed, same clocks: %"
GST_TIME_FORMAT, GST_TIME_ARGS (*timestamp));
*timestamp += base_time;
}
if (external_base != GST_CLOCK_TIME_NONE &&
internal_base != GST_CLOCK_TIME_NONE)
*timestamp += internal_offset;
else
*timestamp = gst_clock_get_internal_time (self->output->clock);
GST_DEBUG_OBJECT (self, "Output timestamp %" GST_TIME_FORMAT
" using clock epoch %" GST_TIME_FORMAT " and offset %" GST_TIME_FORMAT,
GST_TIME_ARGS (*timestamp), GST_TIME_ARGS (self->output->clock_epoch),
GST_TIME_ARGS (internal_offset));
" using clock epoch %" GST_TIME_FORMAT,
GST_TIME_ARGS (*timestamp), GST_TIME_ARGS (self->output->clock_epoch));
if (clock)
gst_object_unref (clock);
@ -2086,30 +2062,24 @@ gst_decklink_video_sink_render (GstBaseSink * bsink, GstBuffer * buffer)
GST_OBJECT_LOCK (self);
self->initial_sync = FALSE;
if (clock) {
GstClockTime external_time = gst_clock_get_internal_time (clock);
GstClockTime internal_time = gst_clock_get_internal_time (self->output->clock);
if (clock != self->output->clock) {
gst_clock_set_master (self->output->clock, clock);
}
if (self->external_base_time == GST_CLOCK_TIME_NONE
|| self->internal_base_time == GST_CLOCK_TIME_NONE) {
self->external_base_time = gst_clock_get_internal_time (clock);
self->internal_base_time =
gst_clock_get_internal_time (self->output->clock);
self->internal_time_offset = self->internal_base_time;
} else if (GST_CLOCK_TIME_IS_VALID (self->internal_pause_time)) {
self->internal_time_offset +=
gst_clock_get_internal_time (self->output->clock) -
self->internal_pause_time;
self->internal_pause_time = GST_CLOCK_TIME_NONE;
self->external_base_time = external_time;
self->internal_base_time = internal_time;
}
GST_INFO_OBJECT (self, "clock has been set to %" GST_PTR_FORMAT
", updated base times - internal: %" GST_TIME_FORMAT
" external: %" GST_TIME_FORMAT " internal offset %"
GST_TIME_FORMAT, clock,
" external: %" GST_TIME_FORMAT, clock,
GST_TIME_ARGS (self->internal_base_time),
GST_TIME_ARGS (self->external_base_time),
GST_TIME_ARGS (self->internal_time_offset));
GST_TIME_ARGS (self->external_base_time));
gst_object_unref (clock);
} else {
@ -2487,7 +2457,6 @@ gst_decklink_video_sink_change_state (GstElement * element,
GST_OBJECT_LOCK (self);
self->internal_base_time = GST_CLOCK_TIME_NONE;
self->external_base_time = GST_CLOCK_TIME_NONE;
self->internal_pause_time = GST_CLOCK_TIME_NONE;
GST_OBJECT_UNLOCK (self);
break;
}
@ -2497,8 +2466,6 @@ gst_decklink_video_sink_change_state (GstElement * element,
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
break;
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
self->internal_pause_time =
gst_clock_get_internal_time (self->output->clock);
break;
default:
break;
@ -2527,6 +2494,7 @@ gst_decklink_video_sink_event (GstBaseSink * bsink, GstEvent * event)
/* force a recalculation of clock base times */
self->external_base_time = GST_CLOCK_TIME_NONE;
self->internal_base_time = GST_CLOCK_TIME_NONE;
self->initial_sync = TRUE;
GST_OBJECT_UNLOCK (self);
}
break;

View File

@ -63,10 +63,6 @@ struct _GstDecklinkVideoSink
GstClockTime internal_base_time;
GstClockTime external_base_time;
/* really an internal start time */
GstClockTime internal_time_offset;
GstClockTime internal_pause_time;
GstDecklinkOutput *output;
GstVideoVBIEncoder *vbiencoder;