static GstCaps * gst_decklink_video_sink_get_caps (GstBaseSink * bsink, GstCaps * filter) { GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (bsink); GstCaps *mode_caps, *caps; if (self->mode == GST_DECKLINK_MODE_AUTO && self->video_format == GST_DECKLINK_VIDEO_FORMAT_AUTO) mode_caps = gst_decklink_mode_get_template_caps (FALSE); else if (self->video_format == GST_DECKLINK_VIDEO_FORMAT_AUTO) mode_caps = gst_decklink_mode_get_caps_all_formats (self->mode, FALSE); else if (self->mode == GST_DECKLINK_MODE_AUTO) mode_caps = gst_decklink_pixel_format_get_caps (gst_decklink_pixel_format_from_type (self->video_format), FALSE); else mode_caps = gst_decklink_mode_get_caps (self->mode, gst_decklink_pixel_format_from_type (self->video_format), FALSE); mode_caps = gst_caps_make_writable (mode_caps); /* For output we support any framerate and only really care about timestamps */ gst_caps_map_in_place (mode_caps, reset_framerate, NULL); if (filter) { caps = gst_caps_intersect_full (filter, mode_caps, GST_CAPS_INTERSECT_FIRST); gst_caps_unref (mode_caps); } else { caps = mode_caps; } return caps; }
void gst_decklink_video_sink_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (object); switch (property_id) { case PROP_MODE: g_value_set_enum (value, self->mode); break; case PROP_DEVICE_NUMBER: g_value_set_int (value, self->device_number); break; case PROP_VIDEO_FORMAT: g_value_set_enum (value, self->video_format); break; case PROP_TIMECODE_FORMAT: g_value_set_enum (value, gst_decklink_timecode_format_to_enum (self->timecode_format)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } }
static gboolean gst_decklink_video_sink_open (GstBaseSink * bsink) { GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (bsink); const GstDecklinkMode *mode; GST_DEBUG_OBJECT (self, "Stopping"); self->output = gst_decklink_acquire_nth_output (self->device_number, GST_ELEMENT_CAST (self), FALSE); if (!self->output) { GST_ERROR_OBJECT (self, "Failed to acquire output"); return FALSE; } mode = gst_decklink_get_mode (self->mode); g_assert (mode != NULL); g_mutex_lock (&self->output->lock); self->output->mode = mode; self->output->start_scheduled_playback = gst_decklink_video_sink_start_scheduled_playback; 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); return TRUE; }
static gboolean gst_decklink_video_sink_set_caps (GstBaseSink * bsink, GstCaps * caps) { GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (bsink); const GstDecklinkMode *mode; HRESULT ret; GST_DEBUG_OBJECT (self, "Setting caps %" GST_PTR_FORMAT, caps); if (!gst_video_info_from_caps (&self->info, caps)) return FALSE; self->output->output->SetScheduledFrameCompletionCallback (new GStreamerVideoOutputCallback (self)); mode = gst_decklink_get_mode (self->mode); g_assert (mode != NULL); ret = self->output->output->EnableVideoOutput (mode->mode, bmdVideoOutputFlagDefault); if (ret != S_OK) { GST_WARNING_OBJECT (self, "Failed to enable video output"); return FALSE; } g_mutex_lock (&self->output->lock); self->output->mode = mode; self->output->video_enabled = TRUE; if (self->output->start_scheduled_playback) self->output->start_scheduled_playback (self->output->videosink); g_mutex_unlock (&self->output->lock); return TRUE; }
static GstClock * gst_decklink_video_sink_provide_clock (GstElement * element) { GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (element); if (!self->output) return NULL; return GST_CLOCK_CAST (gst_object_ref (self->output->clock)); }
static void gst_decklink_video_sink_state_changed (GstElement * element, GstState old_state, GstState new_state, GstState pending_state) { GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (element); // Aka gst_element_lost_state() if (old_state == GST_STATE_PAUSED && new_state == GST_STATE_PAUSED && pending_state == GST_STATE_PAUSED && GST_STATE_TARGET (element) == GST_STATE_PLAYING) { gst_decklink_video_sink_stop_scheduled_playback (self); } }
static GstCaps * gst_decklink_video_sink_get_caps (GstBaseSink * bsink, GstCaps * filter) { GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (bsink); GstCaps *mode_caps, *caps; mode_caps = gst_decklink_mode_get_caps (self->mode); if (filter) { caps = gst_caps_intersect_full (filter, mode_caps, GST_CAPS_INTERSECT_FIRST); gst_caps_unref (mode_caps); } else { caps = mode_caps; } return caps; }
void gst_decklink_video_sink_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) { GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (object); switch (property_id) { case PROP_MODE: self->mode = (GstDecklinkModeEnum) g_value_get_enum (value); break; case PROP_DEVICE_NUMBER: self->device_number = g_value_get_int (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } }
void gst_decklink_video_sink_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) { GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (object); switch (property_id) { case PROP_MODE: self->mode = (GstDecklinkModeEnum) g_value_get_enum (value); break; case PROP_DEVICE_NUMBER: self->device_number = g_value_get_int (value); break; case PROP_VIDEO_FORMAT: self->video_format = (GstDecklinkVideoFormat) g_value_get_enum (value); switch (self->video_format) { case GST_DECKLINK_VIDEO_FORMAT_AUTO: case GST_DECKLINK_VIDEO_FORMAT_8BIT_YUV: case GST_DECKLINK_VIDEO_FORMAT_10BIT_YUV: case GST_DECKLINK_VIDEO_FORMAT_8BIT_ARGB: case GST_DECKLINK_VIDEO_FORMAT_8BIT_BGRA: break; default: GST_ELEMENT_WARNING (GST_ELEMENT (self), CORE, NOT_IMPLEMENTED, ("Format %d not supported", self->video_format), (NULL)); break; } break; case PROP_TIMECODE_FORMAT: self->timecode_format = gst_decklink_timecode_format_from_enum ((GstDecklinkTimecodeFormat) g_value_get_enum (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } }
static gboolean gst_decklink_video_sink_close (GstBaseSink * bsink) { GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (bsink); GST_DEBUG_OBJECT (self, "Closing"); if (self->output) { g_mutex_lock (&self->output->lock); self->output->mode = NULL; self->output->video_enabled = FALSE; if (self->output->start_scheduled_playback) self->output->start_scheduled_playback (self->output->videosink); g_mutex_unlock (&self->output->lock); self->output->output->DisableVideoOutput (); gst_decklink_release_nth_output (self->device_number, GST_ELEMENT_CAST (self), FALSE); self->output = NULL; } return TRUE; }
static gboolean gst_decklink_video_sink_set_caps (GstBaseSink * bsink, GstCaps * caps) { GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (bsink); const GstDecklinkMode *mode; HRESULT ret; BMDVideoOutputFlags flags; GST_DEBUG_OBJECT (self, "Setting caps %" GST_PTR_FORMAT, caps); if (!gst_video_info_from_caps (&self->info, caps)) return FALSE; self->output->output->SetScheduledFrameCompletionCallback (new GStreamerVideoOutputCallback (self)); if (self->mode == GST_DECKLINK_MODE_AUTO) { BMDPixelFormat f; mode = gst_decklink_find_mode_and_format_for_caps (caps, &f); if (mode == NULL) { GST_WARNING_OBJECT (self, "Failed to find compatible mode for caps %" GST_PTR_FORMAT, caps); return FALSE; } if (self->video_format != GST_DECKLINK_VIDEO_FORMAT_AUTO && gst_decklink_pixel_format_from_type (self->video_format) != f) { GST_WARNING_OBJECT (self, "Failed to set pixel format to %d", self->video_format); return FALSE; } } else { /* We don't have to give the format in EnableVideoOutput. Therefore, * even if it's AUTO, we have it stored in self->info and set it in * gst_decklink_video_sink_prepare */ mode = gst_decklink_get_mode (self->mode); g_assert (mode != NULL); }; /* The timecode_format itself is used when we embed the actual timecode data * into the frame. Now we only need to know which of the two standards the * timecode format will adhere to: VITC or RP188, and send the appropriate * flag to EnableVideoOutput. The exact format is specified later. * * Note that this flag will have no effect in practice if the video stream * does not contain timecode metadata. */ if (self->timecode_format == GST_DECKLINK_TIMECODE_FORMAT_VITC || self->timecode_format == GST_DECKLINK_TIMECODE_FORMAT_VITCFIELD2) flags = bmdVideoOutputVITC; else flags = bmdVideoOutputRP188; ret = self->output->output->EnableVideoOutput (mode->mode, flags); if (ret != S_OK) { GST_WARNING_OBJECT (self, "Failed to enable video output: 0x%08x", ret); return FALSE; } g_mutex_lock (&self->output->lock); self->output->mode = mode; self->output->video_enabled = TRUE; if (self->output->start_scheduled_playback) self->output->start_scheduled_playback (self->output->videosink); g_mutex_unlock (&self->output->lock); return TRUE; }
static GstFlowReturn gst_decklink_video_sink_prepare (GstBaseSink * bsink, GstBuffer * buffer) { GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (bsink); GstVideoFrame vframe; IDeckLinkMutableVideoFrame *frame; guint8 *outdata, *indata; GstFlowReturn flow_ret; HRESULT ret; GstClockTime timestamp, duration; GstClockTime running_time, running_time_duration; gint i; GstClock *clock; GST_DEBUG_OBJECT (self, "Preparing buffer %p", buffer); // FIXME: Handle no timestamps if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) { return GST_FLOW_ERROR; } timestamp = GST_BUFFER_TIMESTAMP (buffer); duration = GST_BUFFER_DURATION (buffer); if (duration == GST_CLOCK_TIME_NONE) { duration = gst_util_uint64_scale_int (GST_SECOND, self->info.fps_d, self->info.fps_n); } running_time = gst_segment_to_running_time (&GST_BASE_SINK_CAST (self)->segment, GST_FORMAT_TIME, timestamp); running_time_duration = gst_segment_to_running_time (&GST_BASE_SINK_CAST (self)->segment, GST_FORMAT_TIME, timestamp + duration) - running_time; // FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=742916 // We need to drop late buffers here immediately instead of // potentially overflowing the internal queue of the hardware clock = gst_element_get_clock (GST_ELEMENT_CAST (self)); if (clock) { GstClockTime clock_running_time, base_time, clock_time, latency, max_lateness; base_time = gst_element_get_base_time (GST_ELEMENT_CAST (self)); clock_time = gst_clock_get_time (clock); if (base_time != GST_CLOCK_TIME_NONE && clock_time != GST_CLOCK_TIME_NONE) { clock_running_time = clock_time - base_time; latency = gst_base_sink_get_latency (GST_BASE_SINK_CAST (self)); max_lateness = gst_base_sink_get_max_lateness (GST_BASE_SINK_CAST (self)); if (clock_running_time > running_time + running_time_duration + latency + max_lateness) { GST_DEBUG_OBJECT (self, "Late buffer: %" GST_TIME_FORMAT " > %" GST_TIME_FORMAT, GST_TIME_ARGS (clock_running_time), GST_TIME_ARGS (running_time + running_time_duration)); if (self->last_render_time == GST_CLOCK_TIME_NONE || (self->last_render_time < clock_running_time && clock_running_time - self->last_render_time >= GST_SECOND)) { GST_DEBUG_OBJECT (self, "Rendering frame nonetheless because we had none for more than 1s"); running_time = clock_running_time; running_time_duration = 0; } else { GST_WARNING_OBJECT (self, "Dropping frame"); gst_object_unref (clock); return GST_FLOW_OK; } } } gst_object_unref (clock); } self->last_render_time = running_time; ret = self->output->output->CreateVideoFrame (self->info.width, self->info.height, self->info.stride[0], bmdFormat8BitYUV, bmdFrameFlagDefault, &frame); if (ret != S_OK) { GST_ELEMENT_ERROR (self, STREAM, FAILED, (NULL), ("Failed to create video frame: 0x%08x", ret)); return GST_FLOW_ERROR; } if (!gst_video_frame_map (&vframe, &self->info, buffer, GST_MAP_READ)) { GST_ERROR_OBJECT (self, "Failed to map video frame"); flow_ret = GST_FLOW_ERROR; goto out; } frame->GetBytes ((void **) &outdata); indata = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&vframe, 0); for (i = 0; i < self->info.height; i++) { memcpy (outdata, indata, GST_VIDEO_FRAME_WIDTH (&vframe) * 2); indata += GST_VIDEO_FRAME_PLANE_STRIDE (&vframe, 0); outdata += frame->GetRowBytes (); } gst_video_frame_unmap (&vframe); convert_to_internal_clock (self, &running_time, &running_time_duration); GST_LOG_OBJECT (self, "Scheduling video frame %p at %" GST_TIME_FORMAT " with duration %" GST_TIME_FORMAT, frame, GST_TIME_ARGS (running_time), GST_TIME_ARGS (running_time_duration)); ret = self->output->output->ScheduleVideoFrame (frame, running_time, running_time_duration, GST_SECOND); if (ret != S_OK) { GST_ELEMENT_ERROR (self, STREAM, FAILED, (NULL), ("Failed to schedule frame: 0x%08x", ret)); flow_ret = GST_FLOW_ERROR; goto out; } flow_ret = GST_FLOW_OK; out: frame->Release (); return flow_ret; }
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 void gst_decklink_video_sink_start_scheduled_playback (GstElement * element) { GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (element); GstClockTime start_time; HRESULT res; bool active; if (self->output->video_enabled && (!self->output->audiosink || self->output->audio_enabled) && (GST_STATE (self) == GST_STATE_PLAYING || GST_STATE_PENDING (self) == GST_STATE_PLAYING)) { GstClock *clock = NULL; clock = gst_element_get_clock (element); if (!clock) { GST_ELEMENT_ERROR (self, STREAM, FAILED, (NULL), ("Scheduled playback supposed to start but we have no clock")); return; } // Need to unlock to get the clock time g_mutex_unlock (&self->output->lock); // 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 (clock) - start_time; // FIXME: This will probably not work if (start_time == GST_CLOCK_TIME_NONE) start_time = 0; // Current times of internal and external clock when we go to // playing. We need this to convert the pipeline running time // to the running time of the hardware // // We can't use the normal base time for the external clock // because we might go to PLAYING later than the pipeline self->internal_base_time = gst_clock_get_internal_time (self->output->clock); self->external_base_time = gst_clock_get_internal_time (clock); convert_to_internal_clock (self, &start_time, NULL); g_mutex_lock (&self->output->lock); // Check if someone else started in the meantime if (self->output->started) { gst_object_unref (clock); return; } active = false; self->output->output->IsScheduledPlaybackRunning (&active); if (active) { GST_DEBUG_OBJECT (self, "Stopping scheduled playback"); self->output->started = FALSE; res = self->output->output->StopScheduledPlayback (0, 0, 0); if (res != S_OK) { GST_ELEMENT_ERROR (self, STREAM, FAILED, (NULL), ("Failed to stop scheduled playback: 0x%08x", res)); gst_object_unref (clock); return; } } GST_DEBUG_OBJECT (self, "Starting scheduled playback at %" GST_TIME_FORMAT, GST_TIME_ARGS (start_time)); res = self->output->output->StartScheduledPlayback (start_time, GST_SECOND, 1.0); if (res != S_OK) { GST_ELEMENT_ERROR (self, STREAM, FAILED, (NULL), ("Failed to start scheduled playback: 0x%08x", res)); gst_object_unref (clock); return; } self->output->started = TRUE; self->output->clock_restart = TRUE; // Need to unlock to get the clock time g_mutex_unlock (&self->output->lock); // Sample the clocks again to get the most accurate values // after we started scheduled playback self->internal_base_time = gst_clock_get_internal_time (self->output->clock); self->external_base_time = gst_clock_get_internal_time (clock); g_mutex_lock (&self->output->lock); gst_object_unref (clock); } else { GST_DEBUG_OBJECT (self, "Not starting scheduled playback yet"); } }
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 GstFlowReturn gst_decklink_video_sink_prepare (GstBaseSink * bsink, GstBuffer * buffer) { GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (bsink); GstVideoFrame vframe; IDeckLinkMutableVideoFrame *frame; guint8 *outdata, *indata; GstFlowReturn flow_ret; HRESULT ret; GstClockTime timestamp, duration; GstClockTime running_time, running_time_duration; GstClockTime latency, render_delay; GstClockTimeDiff ts_offset; gint i; GstDecklinkVideoFormat caps_format; BMDPixelFormat format; gint bpp; GstVideoTimeCodeMeta *tc_meta; GST_DEBUG_OBJECT (self, "Preparing buffer %p", buffer); // FIXME: Handle no timestamps if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) { return GST_FLOW_ERROR; } caps_format = gst_decklink_type_from_video_format (self->info.finfo->format); format = gst_decklink_pixel_format_from_type (caps_format); bpp = gst_decklink_bpp_from_type (caps_format); timestamp = GST_BUFFER_TIMESTAMP (buffer); duration = GST_BUFFER_DURATION (buffer); if (duration == GST_CLOCK_TIME_NONE) { duration = gst_util_uint64_scale_int (GST_SECOND, self->info.fps_d, self->info.fps_n); } running_time = gst_segment_to_running_time (&GST_BASE_SINK_CAST (self)->segment, GST_FORMAT_TIME, timestamp); running_time_duration = gst_segment_to_running_time (&GST_BASE_SINK_CAST (self)->segment, GST_FORMAT_TIME, timestamp + duration) - running_time; /* See gst_base_sink_adjust_time() */ latency = gst_base_sink_get_latency (bsink); render_delay = gst_base_sink_get_render_delay (bsink); ts_offset = gst_base_sink_get_ts_offset (bsink); running_time += latency; if (ts_offset < 0) { ts_offset = -ts_offset; if ((GstClockTime) ts_offset < running_time) running_time -= ts_offset; else running_time = 0; } else { running_time += ts_offset; } if (running_time > render_delay) running_time -= render_delay; else running_time = 0; ret = self->output->output->CreateVideoFrame (self->info.width, self->info.height, self->info.stride[0], format, bmdFrameFlagDefault, &frame); if (ret != S_OK) { GST_ELEMENT_ERROR (self, STREAM, FAILED, (NULL), ("Failed to create video frame: 0x%08x", ret)); return GST_FLOW_ERROR; } if (!gst_video_frame_map (&vframe, &self->info, buffer, GST_MAP_READ)) { GST_ERROR_OBJECT (self, "Failed to map video frame"); flow_ret = GST_FLOW_ERROR; goto out; } frame->GetBytes ((void **) &outdata); indata = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&vframe, 0); for (i = 0; i < self->info.height; i++) { memcpy (outdata, indata, GST_VIDEO_FRAME_WIDTH (&vframe) * bpp); indata += GST_VIDEO_FRAME_PLANE_STRIDE (&vframe, 0); outdata += frame->GetRowBytes (); } gst_video_frame_unmap (&vframe); tc_meta = gst_buffer_get_video_time_code_meta (buffer); if (tc_meta) { BMDTimecodeFlags bflags = (BMDTimecodeFlags) 0; gchar *tc_str; if (((GstVideoTimeCodeFlags) (tc_meta->tc. config.flags)) & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) bflags = (BMDTimecodeFlags) (bflags | bmdTimecodeIsDropFrame); else bflags = (BMDTimecodeFlags) (bflags | bmdTimecodeFlagDefault); if (tc_meta->tc.field_count == 2) bflags = (BMDTimecodeFlags) (bflags | bmdTimecodeFieldMark); tc_str = gst_video_time_code_to_string (&tc_meta->tc); ret = frame->SetTimecodeFromComponents (self->timecode_format, (uint8_t) tc_meta->tc.hours, (uint8_t) tc_meta->tc.minutes, (uint8_t) tc_meta->tc.seconds, (uint8_t) tc_meta->tc.frames, bflags); if (ret != S_OK) { GST_ERROR_OBJECT (self, "Failed to set timecode %s to video frame: 0x%08x", tc_str, ret); flow_ret = GST_FLOW_ERROR; g_free (tc_str); goto out; } GST_DEBUG_OBJECT (self, "Set frame timecode to %s", tc_str); g_free (tc_str); } convert_to_internal_clock (self, &running_time, &running_time_duration); GST_LOG_OBJECT (self, "Scheduling video frame %p at %" GST_TIME_FORMAT " with duration %" GST_TIME_FORMAT, frame, GST_TIME_ARGS (running_time), GST_TIME_ARGS (running_time_duration)); ret = self->output->output->ScheduleVideoFrame (frame, running_time, running_time_duration, GST_SECOND); if (ret != S_OK) { GST_ELEMENT_ERROR (self, STREAM, FAILED, (NULL), ("Failed to schedule frame: 0x%08x", ret)); flow_ret = GST_FLOW_ERROR; goto out; } flow_ret = GST_FLOW_OK; out: frame->Release (); return flow_ret; }
GStreamerVideoOutputCallback (GstDecklinkVideoSink * sink) :IDeckLinkVideoOutputCallback (), m_refcount (1) { m_sink = GST_DECKLINK_VIDEO_SINK_CAST (gst_object_ref (sink)); g_mutex_init (&m_mutex); }