static GstStateChangeReturn gst_decklink_video_sink_change_state (GstElement * element, GstStateChange transition) { GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (element); GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; switch (transition) { case GST_STATE_CHANGE_READY_TO_PAUSED: 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_last_time = 0; self->output->clock_offset = 0; g_mutex_unlock (&self->output->lock); gst_element_post_message (element, gst_message_new_clock_provide (GST_OBJECT_CAST (element), self->output->clock, TRUE)); break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING:{ GstClock *clock, *audio_clock; clock = gst_element_get_clock (GST_ELEMENT_CAST (self)); if (clock) { audio_clock = gst_decklink_output_get_audio_clock (self->output); if (clock && clock != self->output->clock && clock != audio_clock) { gst_clock_set_master (self->output->clock, clock); } gst_object_unref (clock); if (audio_clock) gst_object_unref (audio_clock); } else { GST_ELEMENT_ERROR (self, STREAM, FAILED, (NULL), ("Need a clock to go to PLAYING")); ret = GST_STATE_CHANGE_FAILURE; } break; } default: break; } if (ret == GST_STATE_CHANGE_FAILURE) return ret; ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); if (ret == GST_STATE_CHANGE_FAILURE) return ret; switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: gst_element_post_message (element, gst_message_new_clock_lost (GST_OBJECT_CAST (element), self->output->clock)); gst_clock_set_master (self->output->clock, NULL); // Reset calibration to make the clock reusable next time we use it gst_clock_set_calibration (self->output->clock, 0, 0, 1, 1); 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_last_time = 0; self->output->clock_offset = 0; g_mutex_unlock (&self->output->lock); gst_decklink_video_sink_stop (self); break; case GST_STATE_CHANGE_PLAYING_TO_PAUSED:{ if (gst_decklink_video_sink_stop_scheduled_playback (self) == GST_STATE_CHANGE_FAILURE) ret = GST_STATE_CHANGE_FAILURE; break; } case GST_STATE_CHANGE_PAUSED_TO_PLAYING:{ g_mutex_lock (&self->output->lock); if (self->output->start_scheduled_playback) self->output->start_scheduled_playback (self->output->videosink); g_mutex_unlock (&self->output->lock); break; } default: break; } return ret; }
static GstStateChangeReturn gst_decklink_video_sink_change_state (GstElement * element, GstStateChange transition) { GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (element); GstStateChangeReturn ret; switch (transition) { case GST_STATE_CHANGE_READY_TO_PAUSED: g_mutex_lock (&self->output->lock); self->output->clock_start_time = GST_CLOCK_TIME_NONE; self->output->clock_last_time = 0; self->output->clock_offset = 0; g_mutex_unlock (&self->output->lock); gst_element_post_message (element, gst_message_new_clock_provide (GST_OBJECT_CAST (element), self->output->clock, TRUE)); self->last_render_time = GST_CLOCK_TIME_NONE; break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING:{ GstClock *clock, *audio_clock; clock = gst_element_get_clock (GST_ELEMENT_CAST (self)); audio_clock = gst_decklink_output_get_audio_clock (self->output); if (clock && clock != self->output->clock && clock != audio_clock) { gst_clock_set_master (self->output->clock, clock); } if (clock) gst_object_unref (clock); if (audio_clock) gst_object_unref (audio_clock); break; } default: break; } ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); if (ret == GST_STATE_CHANGE_FAILURE) return ret; switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: gst_element_post_message (element, gst_message_new_clock_lost (GST_OBJECT_CAST (element), self->output->clock)); gst_clock_set_master (self->output->clock, NULL); g_mutex_lock (&self->output->lock); self->output->clock_start_time = GST_CLOCK_TIME_NONE; self->output->clock_last_time = 0; self->output->clock_offset = 0; g_mutex_unlock (&self->output->lock); break; case GST_STATE_CHANGE_PLAYING_TO_PAUSED:{ GstClockTime start_time; HRESULT res; // FIXME: start time is the same for the complete pipeline, // but what we need here is the start time of this element! start_time = gst_element_get_base_time (element); if (start_time != GST_CLOCK_TIME_NONE) start_time = gst_clock_get_time (GST_ELEMENT_CLOCK (self)) - start_time; // FIXME: This will probably not work if (start_time == GST_CLOCK_TIME_NONE) start_time = 0; convert_to_internal_clock (self, &start_time, NULL); // The start time is now the running time when we stopped // playback GST_DEBUG_OBJECT (self, "Stopping scheduled playback at %" GST_TIME_FORMAT, GST_TIME_ARGS (start_time)); g_mutex_lock (&self->output->lock); self->output->started = FALSE; g_mutex_unlock (&self->output->lock); res = self->output->output->StopScheduledPlayback (start_time, 0, GST_SECOND); if (res != S_OK) { GST_ELEMENT_ERROR (self, STREAM, FAILED, (NULL), ("Failed to stop scheduled playback: 0x%08x", res)); ret = GST_STATE_CHANGE_FAILURE; } self->internal_base_time = GST_CLOCK_TIME_NONE; self->external_base_time = GST_CLOCK_TIME_NONE; break; } case GST_STATE_CHANGE_PAUSED_TO_PLAYING:{ g_mutex_lock (&self->output->lock); if (self->output->start_scheduled_playback) self->output->start_scheduled_playback (self->output->videosink); g_mutex_unlock (&self->output->lock); break; } default: break; } return ret; }
static void convert_to_internal_clock (GstDecklinkVideoSink * self, GstClockTime * timestamp, GstClockTime * duration) { GstClock *clock, *audio_clock; g_assert (timestamp != NULL); clock = gst_element_get_clock (GST_ELEMENT_CAST (self)); audio_clock = gst_decklink_output_get_audio_clock (self->output); if (clock && clock != self->output->clock && clock != audio_clock) { GstClockTime internal, external, rate_n, rate_d; gst_clock_get_calibration (self->output->clock, &internal, &external, &rate_n, &rate_d); if (self->internal_base_time != GST_CLOCK_TIME_NONE) { GstClockTime external_timestamp = *timestamp; GstClockTime base_time; // Convert to the running time corresponding to both clock times if (internal < self->internal_base_time) internal = 0; else internal -= self->internal_base_time; if (external < self->external_base_time) external = 0; else external -= self->external_base_time; // 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 > self->external_base_time) base_time = 0; else base_time = self->external_base_time - base_time; if (external_timestamp < base_time) external_timestamp = 0; else external_timestamp = external_timestamp - base_time; // Get the difference in the external time, note // that the running time is external time. // Then scale this difference and offset it to // our internal time. Now we have the running time // according to our internal clock. // // For the duration we just scale *timestamp = gst_clock_unadjust_with_calibration (NULL, external_timestamp, internal, external, rate_n, rate_d); GST_LOG_OBJECT (self, "Converted %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT " (internal: %" GST_TIME_FORMAT " external %" GST_TIME_FORMAT " rate: %lf)", GST_TIME_ARGS (external_timestamp), GST_TIME_ARGS (*timestamp), GST_TIME_ARGS (internal), GST_TIME_ARGS (external), ((gdouble) rate_n) / ((gdouble) rate_d)); if (duration) { GstClockTime external_duration = *duration; *duration = gst_util_uint64_scale (external_duration, rate_d, rate_n); GST_LOG_OBJECT (self, "Converted duration %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT " (internal: %" GST_TIME_FORMAT " external %" GST_TIME_FORMAT " rate: %lf)", GST_TIME_ARGS (external_duration), GST_TIME_ARGS (*duration), GST_TIME_ARGS (internal), GST_TIME_ARGS (external), ((gdouble) rate_n) / ((gdouble) rate_d)); } } else { GST_LOG_OBJECT (self, "No clock conversion needed, not started yet"); } } else { GST_LOG_OBJECT (self, "No clock conversion needed, same clocks"); } }
static void convert_to_internal_clock (GstDecklinkVideoSink * self, GstClockTime * timestamp, GstClockTime * duration) { GstClock *clock, *audio_clock; g_assert (timestamp != NULL); clock = gst_element_get_clock (GST_ELEMENT_CAST (self)); audio_clock = gst_decklink_output_get_audio_clock (self->output); if (clock && clock != self->output->clock && clock != audio_clock) { GstClockTime internal, external, rate_n, rate_d; gst_clock_get_calibration (self->output->clock, &internal, &external, &rate_n, &rate_d); if (rate_n != rate_d && self->internal_base_time != GST_CLOCK_TIME_NONE) { GstClockTime external_timestamp = *timestamp; // Convert to the running time corresponding to both clock times internal -= self->internal_base_time; external -= self->external_base_time; // Get the difference in the external time, note // that the running time is external time. // Then scale this difference and offset it to // our internal time. Now we have the running time // according to our internal clock. // // For the duration we just scale if (external > external_timestamp) { guint64 diff = external - external_timestamp; diff = gst_util_uint64_scale (diff, rate_d, rate_n); *timestamp = internal - diff; } else { guint64 diff = external_timestamp - external; diff = gst_util_uint64_scale (diff, rate_d, rate_n); *timestamp = internal + diff; } GST_LOG_OBJECT (self, "Converted %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT " (internal: %" GST_TIME_FORMAT " external %" GST_TIME_FORMAT " rate: %lf)", GST_TIME_ARGS (external_timestamp), GST_TIME_ARGS (*timestamp), GST_TIME_ARGS (internal), GST_TIME_ARGS (external), ((gdouble) rate_n) / ((gdouble) rate_d)); if (duration) { GstClockTime external_duration = *duration; *duration = gst_util_uint64_scale (external_duration, rate_d, rate_n); GST_LOG_OBJECT (self, "Converted duration %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT " (internal: %" GST_TIME_FORMAT " external %" GST_TIME_FORMAT " rate: %lf)", GST_TIME_ARGS (external_duration), GST_TIME_ARGS (*duration), GST_TIME_ARGS (internal), GST_TIME_ARGS (external), ((gdouble) rate_n) / ((gdouble) rate_d)); } } else { GST_LOG_OBJECT (self, "No clock conversion needed, relative rate is 1.0"); } } else { GST_LOG_OBJECT (self, "No clock conversion needed, same clocks"); } }