decklink: calculate the decklink output time from the internal clock

Fixes the time calculations when dealing with a slaved clock (as
will occur with more than one decklink video sink), when performing
flushing seeks causing stalls in the output timeline, pausing.

Tighten up the calculations by relying solely on the internal time
(from the internal clock) for determining when to schedule display
frames instead attempting to track pause lengths from the external
clock and converting to internal time.  This results in a much easier
offset calculation for choosing the output time and ensures that the
clock is always advancing when we need it to.

This is fixup to the 'monotonically increasing output timestamps' goal
in: bf849e9a69442f7a6f9d4f0a1ef30d5a8009f689
This commit is contained in:
Matthew Waters 2018-11-27 22:28:15 -06:00
parent fa9697002a
commit 59c5ae2817
2 changed files with 68 additions and 72 deletions

View File

@ -142,9 +142,6 @@ static void gst_decklink_video_sink_finalize (GObject * object);
static GstStateChangeReturn static GstStateChangeReturn
gst_decklink_video_sink_change_state (GstElement * element, gst_decklink_video_sink_change_state (GstElement * element,
GstStateChange transition); GstStateChange transition);
static void
gst_decklink_video_sink_state_changed (GstElement * element,
GstState old_state, GstState new_state, GstState pending_state);
static GstClock *gst_decklink_video_sink_provide_clock (GstElement * element); static GstClock *gst_decklink_video_sink_provide_clock (GstElement * element);
static GstCaps *gst_decklink_video_sink_get_caps (GstBaseSink * bsink, static GstCaps *gst_decklink_video_sink_get_caps (GstBaseSink * bsink,
@ -160,6 +157,8 @@ static gboolean gst_decklink_video_sink_close (GstBaseSink * bsink);
static gboolean gst_decklink_video_sink_stop (GstDecklinkVideoSink * self); static gboolean gst_decklink_video_sink_stop (GstDecklinkVideoSink * self);
static gboolean gst_decklink_video_sink_propose_allocation (GstBaseSink * bsink, static gboolean gst_decklink_video_sink_propose_allocation (GstBaseSink * bsink,
GstQuery * query); GstQuery * query);
static gboolean gst_decklink_video_sink_event (GstBaseSink * bsink,
GstEvent * event);
static void static void
gst_decklink_video_sink_start_scheduled_playback (GstElement * element); gst_decklink_video_sink_start_scheduled_playback (GstElement * element);
@ -192,8 +191,6 @@ gst_decklink_video_sink_class_init (GstDecklinkVideoSinkClass * klass)
element_class->change_state = element_class->change_state =
GST_DEBUG_FUNCPTR (gst_decklink_video_sink_change_state); GST_DEBUG_FUNCPTR (gst_decklink_video_sink_change_state);
element_class->state_changed =
GST_DEBUG_FUNCPTR (gst_decklink_video_sink_state_changed);
element_class->provide_clock = element_class->provide_clock =
GST_DEBUG_FUNCPTR (gst_decklink_video_sink_provide_clock); GST_DEBUG_FUNCPTR (gst_decklink_video_sink_provide_clock);
@ -208,6 +205,7 @@ gst_decklink_video_sink_class_init (GstDecklinkVideoSinkClass * klass)
basesink_class->stop = GST_DEBUG_FUNCPTR (gst_decklink_video_sink_close); basesink_class->stop = GST_DEBUG_FUNCPTR (gst_decklink_video_sink_close);
basesink_class->propose_allocation = basesink_class->propose_allocation =
GST_DEBUG_FUNCPTR (gst_decklink_video_sink_propose_allocation); GST_DEBUG_FUNCPTR (gst_decklink_video_sink_propose_allocation);
basesink_class->event = GST_DEBUG_FUNCPTR (gst_decklink_video_sink_event);
g_object_class_install_property (gobject_class, PROP_MODE, g_object_class_install_property (gobject_class, PROP_MODE,
g_param_spec_enum ("mode", "Playback Mode", g_param_spec_enum ("mode", "Playback Mode",
@ -555,38 +553,32 @@ gst_decklink_video_sink_convert_to_internal_clock (GstDecklinkVideoSink * self,
GstClockTime * timestamp, GstClockTime * duration) GstClockTime * timestamp, GstClockTime * duration)
{ {
GstClock *clock; GstClock *clock;
GstClockTime internal_base, external_base, internal_offset;
g_assert (timestamp != NULL); g_assert (timestamp != NULL);
clock = gst_element_get_clock (GST_ELEMENT_CAST (self)); clock = gst_element_get_clock (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 (!clock || clock != self->output->clock) {
GstClockTime internal, external, rate_n, rate_d; GstClockTime internal, external, rate_n, rate_d;
GstClockTime internal_base, external_base;
GstClockTime external_timestamp = *timestamp; GstClockTime external_timestamp = *timestamp;
GstClockTime base_time; GstClockTime base_time;
gst_clock_get_calibration (self->output->clock, &internal, &external, gst_clock_get_calibration (self->output->clock, &internal, &external,
&rate_n, &rate_d); &rate_n, &rate_d);
if (self->external_base_time != GST_CLOCK_TIME_NONE &&
self->internal_base_time != GST_CLOCK_TIME_NONE) {
internal_base = self->internal_base_time;
external_base = self->external_base_time;
} else if (GST_CLOCK_TIME_IS_VALID (self->paused_start_time)) {
internal_base = self->paused_start_time;
external_base = 0;
} else {
internal_base = gst_clock_get_internal_time (self->output->clock);
external_base = 0;
}
// Convert to the running time corresponding to both clock times // Convert to the running time corresponding to both clock times
if (internal < internal_base) if (!GST_CLOCK_TIME_IS_VALID (internal_base) || internal < internal_base)
internal = 0; internal = 0;
else else
internal -= internal_base; internal -= internal_base;
if (external < external_base) if (!GST_CLOCK_TIME_IS_VALID (external_base) || external < external_base)
external = 0; external = 0;
else else
external -= external_base; external -= external_base;
@ -640,21 +632,15 @@ gst_decklink_video_sink_convert_to_internal_clock (GstDecklinkVideoSink * self,
GST_TIME_FORMAT, GST_TIME_ARGS (*timestamp)); GST_TIME_FORMAT, GST_TIME_ARGS (*timestamp));
} }
if (GST_CLOCK_TIME_IS_VALID (self->paused_start_time) && if (external_base != GST_CLOCK_TIME_NONE &&
GST_CLOCK_TIME_IS_VALID (self->playing_base_time)) { internal_base != GST_CLOCK_TIME_NONE)
/* add the time since we were last playing. */ *timestamp += internal_offset;
*timestamp += self->paused_start_time + self->playing_base_time; else
} else { *timestamp = gst_clock_get_internal_time (self->output->clock);
/* only valid whil we're in PAUSED and most likely without a set clock,
* we update based on our internal clock */
*timestamp += gst_clock_get_internal_time (self->output->clock);
}
GST_LOG_OBJECT (self, "Output timestamp %" GST_TIME_FORMAT GST_LOG_OBJECT (self, "Output timestamp %" GST_TIME_FORMAT
" using paused start at %" GST_TIME_FORMAT " playing start at %" " using clock epoch %" GST_TIME_FORMAT,
GST_TIME_FORMAT, GST_TIME_ARGS (*timestamp), GST_TIME_ARGS (*timestamp), GST_TIME_ARGS (self->output->clock_epoch));
GST_TIME_ARGS (self->paused_start_time),
GST_TIME_ARGS (self->playing_base_time));
} }
static GstFlowReturn static GstFlowReturn
@ -943,6 +929,10 @@ gst_decklink_video_sink_open (GstBaseSink * bsink)
self->output->clock_epoch += self->output->clock_last_time; self->output->clock_epoch += self->output->clock_last_time;
self->output->clock_last_time = 0; self->output->clock_last_time = 0;
self->output->clock_offset = 0; self->output->clock_offset = 0;
GST_OBJECT_LOCK (self);
self->internal_base_time = GST_CLOCK_TIME_NONE;
self->external_base_time = GST_CLOCK_TIME_NONE;
GST_OBJECT_UNLOCK (self);
g_mutex_unlock (&self->output->lock); g_mutex_unlock (&self->output->lock);
return TRUE; return TRUE;
@ -1099,17 +1089,11 @@ gst_decklink_video_sink_start_scheduled_playback (GstElement * element)
} }
self->output->started = TRUE; self->output->started = TRUE;
self->output->clock_restart = TRUE;
// Need to unlock to get the clock time // Need to unlock to get the clock time
g_mutex_unlock (&self->output->lock); g_mutex_unlock (&self->output->lock);
// Sample the clocks again to get the most accurate values
// after we started scheduled playback
if (clock) { if (clock) {
self->internal_base_time =
gst_clock_get_internal_time (self->output->clock);
self->external_base_time = gst_clock_get_internal_time (clock);
gst_object_unref (clock); gst_object_unref (clock);
} }
g_mutex_lock (&self->output->lock); g_mutex_lock (&self->output->lock);
@ -1144,9 +1128,11 @@ gst_decklink_video_sink_stop_scheduled_playback (GstDecklinkVideoSink * self)
// Wait until scheduled playback actually stopped // Wait until scheduled playback actually stopped
_wait_for_stop_notify (self); _wait_for_stop_notify (self);
} }
g_mutex_unlock (&self->output->lock);
GST_OBJECT_LOCK (self);
self->internal_base_time = GST_CLOCK_TIME_NONE; self->internal_base_time = GST_CLOCK_TIME_NONE;
self->external_base_time = GST_CLOCK_TIME_NONE; self->external_base_time = GST_CLOCK_TIME_NONE;
g_mutex_unlock (&self->output->lock); GST_OBJECT_UNLOCK (self);
return ret; return ret;
} }
@ -1168,7 +1154,6 @@ gst_decklink_video_sink_change_state (GstElement * element,
self->anc_vformat = GST_VIDEO_FORMAT_UNKNOWN; self->anc_vformat = GST_VIDEO_FORMAT_UNKNOWN;
g_mutex_lock (&self->output->lock); g_mutex_lock (&self->output->lock);
self->output->clock_start_time = GST_CLOCK_TIME_NONE;
self->output->clock_epoch += self->output->clock_last_time; self->output->clock_epoch += self->output->clock_last_time;
self->output->clock_last_time = 0; self->output->clock_last_time = 0;
self->output->clock_offset = 0; self->output->clock_offset = 0;
@ -1189,15 +1174,22 @@ gst_decklink_video_sink_change_state (GstElement * element,
if (clock != self->output->clock) { if (clock != self->output->clock) {
gst_clock_set_master (self->output->clock, clock); gst_clock_set_master (self->output->clock, clock);
} }
self->external_base_time = gst_clock_get_internal_time (clock);
self->internal_base_time = GST_OBJECT_LOCK (self);
gst_clock_get_internal_time (self->output->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;
}
GST_INFO_OBJECT (self, "clock has been set to %" GST_PTR_FORMAT GST_INFO_OBJECT (self, "clock has been set to %" GST_PTR_FORMAT
", updated base times - internal: %" GST_TIME_FORMAT ", updated base times - internal: %" GST_TIME_FORMAT
" external: %" GST_TIME_FORMAT, clock, " external: %" GST_TIME_FORMAT " internal offset %"
GST_TIME_FORMAT, clock,
GST_TIME_ARGS (self->internal_base_time), GST_TIME_ARGS (self->internal_base_time),
GST_TIME_ARGS (self->external_base_time)); GST_TIME_ARGS (self->external_base_time),
GST_TIME_ARGS (self->internal_time_offset));
GST_OBJECT_UNLOCK (self);
gst_object_unref (clock); gst_object_unref (clock);
} else { } else {
@ -1213,7 +1205,6 @@ gst_decklink_video_sink_change_state (GstElement * element,
ret = GST_STATE_CHANGE_FAILURE; ret = GST_STATE_CHANGE_FAILURE;
break; break;
case GST_STATE_CHANGE_PLAYING_TO_PAUSED: case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
self->playing_exit_time = gst_clock_get_internal_time (self->output->clock);
break; break;
default: default:
break; break;
@ -1234,12 +1225,15 @@ gst_decklink_video_sink_change_state (GstElement * element,
// Reset calibration to make the clock reusable next time we use it // Reset calibration to make the clock reusable next time we use it
gst_clock_set_calibration (self->output->clock, 0, 0, 1, 1); gst_clock_set_calibration (self->output->clock, 0, 0, 1, 1);
g_mutex_lock (&self->output->lock); g_mutex_lock (&self->output->lock);
self->output->clock_start_time = GST_CLOCK_TIME_NONE;
self->output->clock_epoch += self->output->clock_last_time; self->output->clock_epoch += self->output->clock_last_time;
self->output->clock_last_time = 0; self->output->clock_last_time = 0;
self->output->clock_offset = 0; self->output->clock_offset = 0;
g_mutex_unlock (&self->output->lock); g_mutex_unlock (&self->output->lock);
gst_decklink_video_sink_stop (self); gst_decklink_video_sink_stop (self);
GST_OBJECT_LOCK (self);
self->internal_base_time = GST_CLOCK_TIME_NONE;
self->external_base_time = GST_CLOCK_TIME_NONE;
GST_OBJECT_UNLOCK (self);
break; break;
} }
case GST_STATE_CHANGE_READY_TO_PAUSED:{ case GST_STATE_CHANGE_READY_TO_PAUSED:{
@ -1256,29 +1250,35 @@ gst_decklink_video_sink_change_state (GstElement * element,
return ret; return ret;
} }
static void static gboolean
gst_decklink_video_sink_state_changed (GstElement * element, gst_decklink_video_sink_event (GstBaseSink * bsink, GstEvent * event)
GstState old_state, GstState new_state, GstState pending_state)
{ {
GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (element); GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (bsink);
if (new_state == GST_STATE_PLAYING) { switch (GST_EVENT_TYPE (event)) {
self->playing_base_time = gst_clock_get_internal_time (self->output->clock) - self->playing_exit_time; case GST_EVENT_FLUSH_START:
GST_DEBUG_OBJECT (self, "playing entered paused start time %" {
GST_TIME_FORMAT "playing base time %" GST_TIME_FORMAT, break;
GST_TIME_ARGS (self->paused_start_time),
GST_TIME_ARGS (self->playing_base_time));
}
if (new_state == GST_STATE_PAUSED) {
self->paused_start_time = gst_clock_get_internal_time (self->output->clock);
GST_DEBUG_OBJECT (self, "paused enter at %" GST_TIME_FORMAT,
GST_TIME_ARGS (self->paused_start_time));
if (old_state == GST_STATE_PLAYING) {
self->external_base_time = GST_CLOCK_TIME_NONE;
self->internal_base_time = GST_CLOCK_TIME_NONE;
} }
case GST_EVENT_FLUSH_STOP:
{
gboolean reset_time;
gst_event_parse_flush_stop (event, &reset_time);
if (reset_time) {
GST_OBJECT_LOCK (self);
/* force a recalculation of clock base times */
self->external_base_time = GST_CLOCK_TIME_NONE;
self->internal_base_time = GST_CLOCK_TIME_NONE;
GST_OBJECT_UNLOCK (self);
}
break;
}
default:
break;
} }
return GST_BASE_SINK_CLASS (parent_class)->event (bsink, event);
} }
static GstClock * static GstClock *

View File

@ -61,12 +61,8 @@ struct _GstDecklinkVideoSink
GstClockTime internal_base_time; GstClockTime internal_base_time;
GstClockTime external_base_time; GstClockTime external_base_time;
/* all in internal time of the decklink clock */
/* really an internal base time */
GstClockTime playing_base_time; /* time that we entered playing */
/* really an internal start time */ /* really an internal start time */
GstClockTime paused_start_time; /* time we entered paused, used to track how long we are in paused while the clock is running */ GstClockTime internal_time_offset;
GstClockTime playing_exit_time; /* time that we exit playing */
GstDecklinkOutput *output; GstDecklinkOutput *output;