From 5125f68b12eab349ffe7937d0fb29b053a0c6a4d Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Tue, 20 Jun 2023 21:58:53 +0900 Subject: [PATCH] decklink2sink: Add auto-restart property Support automatic restart when frame dropping happens --- .../sys/decklink2/gstdecklink2output.cpp | 44 ++++++++++++--- .../sys/decklink2/gstdecklink2output.h | 5 +- .../sys/decklink2/gstdecklink2sink.cpp | 53 ++++++++++++++++--- 3 files changed, 85 insertions(+), 17 deletions(-) diff --git a/subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2output.cpp b/subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2output.cpp index e042a2e9d7..d580ffe5a6 100644 --- a/subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2output.cpp +++ b/subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2output.cpp @@ -149,8 +149,7 @@ public: buffer_.resize (cur_size + num_samples); if (cur_size > 0) { - memmove (&buffer_[0] + silence_length_in_bytes, &buffer_[0], - cur_size); + memmove (&buffer_[0] + silence_length_in_bytes, &buffer_[0], cur_size); } gst_audio_format_info_fill_silence (info_.finfo, &buffer_[0], @@ -234,12 +233,16 @@ struct _GstDeckLink2Output gboolean configured; gboolean prerolled; + guint64 late_count; + guint64 drop_count; + guint64 underrun_count; }; static void gst_decklink2_output_dispose (GObject * object); static void gst_decklink2_output_finalize (GObject * object); static void gst_decklink2_output_on_stopped (GstDeckLink2Output * self); -static void gst_decklink2_output_on_completed (GstDeckLink2Output * self); +static void gst_decklink2_output_on_completed (GstDeckLink2Output * self, + BMDOutputFrameCompletionResult result); #define gst_decklink2_output_parent_class parent_class G_DEFINE_TYPE (GstDeckLink2Output, gst_decklink2_output, GST_TYPE_OBJECT); @@ -308,8 +311,9 @@ public: } if (result == bmdOutputFrameCompleted || - result == bmdOutputFrameDisplayedLate) { - gst_decklink2_output_on_completed (output_); + result == bmdOutputFrameDisplayedLate || + result == bmdOutputFrameDropped) { + gst_decklink2_output_on_completed (output_, result); } return S_OK; @@ -1013,7 +1017,8 @@ gst_decklink2_output_schedule_video_internal (GstDeckLink2Output * self, HRESULT gst_decklink2_output_schedule_stream (GstDeckLink2Output * output, - IDeckLinkVideoFrame * frame, guint8 * audio_buf, gsize audio_buf_size) + IDeckLinkVideoFrame * frame, guint8 * audio_buf, gsize audio_buf_size, + guint64 * drop_count, guint64 * late_count, guint64 * underrun_count) { GstDeckLink2OutputPrivate *priv = output->priv; HRESULT hr; @@ -1022,6 +1027,10 @@ gst_decklink2_output_schedule_stream (GstDeckLink2Output * output, g_assert (output->configured); + *drop_count = 0; + *late_count = 0; + *underrun_count = 0; + hr = gst_decklink2_output_is_running (output, &active); if (!gst_decklink2_result (hr)) { GST_ERROR_OBJECT (output, "Couldn't query active state, hr: 0x%x", @@ -1056,7 +1065,12 @@ gst_decklink2_output_schedule_stream (GstDeckLink2Output * output, std::lock_guard < std::mutex > slk (priv->schedule_lock); priv->audio_buf.Append (audio_buf, audio_buf_size); - return gst_decklink2_output_schedule_video_internal (output, frame); + hr = gst_decklink2_output_schedule_video_internal (output, frame); + *drop_count = output->drop_count; + *late_count = output->late_count; + *underrun_count = output->underrun_count; + + return hr; } static HRESULT @@ -1094,6 +1108,10 @@ gst_decklink2_output_stop_internal (GstDeckLink2Output * self) gst_decklink2_output_disable_video (self); gst_decklink2_output_set_video_callback (self, NULL); self->configured = FALSE; + self->prerolled = FALSE; + self->late_count = 0; + self->drop_count = 0; + self->underrun_count = 0; return hr; } @@ -1934,6 +1952,9 @@ gst_decklink2_output_configure (GstDeckLink2Output * output, output->cdp_hdr_sequence_cntr = 0; output->configured = TRUE; output->pts = 0; + output->drop_count = 0; + output->late_count = 0; + output->underrun_count = 0; return S_OK; @@ -1951,13 +1972,19 @@ gst_decklink2_output_on_stopped (GstDeckLink2Output * self) } static void -gst_decklink2_output_on_completed (GstDeckLink2Output * self) +gst_decklink2_output_on_completed (GstDeckLink2Output * self, + BMDOutputFrameCompletionResult result) { GstDeckLink2OutputPrivate *priv = self->priv; dlbool_t active; guint32 count = 0; std::lock_guard < std::mutex > lk (priv->schedule_lock); + if (result == bmdOutputFrameDisplayedLate) + self->late_count++; + else if (result == bmdOutputFrameDropped) + self->drop_count++; + if (!self->last_frame) return; @@ -1966,6 +1993,7 @@ gst_decklink2_output_on_completed (GstDeckLink2Output * self) hr = gst_decklink2_output_get_num_bufferred (self, &count); if (gst_decklink2_result (hr) && count <= self->min_buffered) { GST_WARNING_OBJECT (self, "Underrun, buffered count %u", count); + self->underrun_count++; priv->audio_buf.PrependSilence (); gst_decklink2_output_schedule_video_internal (self, self->last_frame); } diff --git a/subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2output.h b/subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2output.h index 551fe5de68..8c81b9d67a 100644 --- a/subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2output.h +++ b/subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2output.h @@ -65,7 +65,10 @@ IDeckLinkVideoFrame * gst_decklink2_output_upload (GstDeckLink2Output * HRESULT gst_decklink2_output_schedule_stream (GstDeckLink2Output * output, IDeckLinkVideoFrame * frame, guint8 *audio_buf, - gsize audio_buf_size); + gsize audio_buf_size, + guint64 * drop_count, + guint64 * late_count, + guint64 * underrun_count); HRESULT gst_decklink2_output_stop (GstDeckLink2Output * output); diff --git a/subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2sink.cpp b/subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2sink.cpp index 9d966108f1..ea59cf6475 100644 --- a/subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2sink.cpp +++ b/subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2sink.cpp @@ -49,6 +49,7 @@ enum PROP_N_PREROLL_FRAMES, PROP_MIN_BUFFERED_FRAMES, PROP_MAX_BUFFERED_FRAMES, + PROP_AUTO_RESTART }; #define DEFAULT_MODE bmdModeUnknown @@ -65,6 +66,7 @@ enum #define DEFAULT_N_PREROLL_FRAMES 7 #define DEFAULT_MIN_BUFFERED_FRAMES 3 #define DEFAULT_MAX_BUFFERED_FRAMES 14 +#define DEFAULT_AUTO_RESTART FALSE struct GstDeckLink2SinkPrivate { @@ -88,6 +90,7 @@ struct _GstDeckLink2Sink GstBufferPool *fallback_pool; IDeckLinkVideoFrame *prepared_frame; + BMDVideoOutputFlags output_flags; /* properties */ BMDDisplayMode display_mode; @@ -104,6 +107,7 @@ struct _GstDeckLink2Sink guint n_preroll_frames; guint min_buffered_frames; guint max_buffered_frames; + gboolean auto_restart; }; static void gst_decklink2_sink_set_property (GObject * object, @@ -225,6 +229,12 @@ gst_decklink2_sink_class_init (GstDeckLink2SinkClass * klass) "Max number of frames to buffer before dropping", 0, 16, DEFAULT_MAX_BUFFERED_FRAMES, param_flags)); + g_object_class_install_property (object_class, PROP_AUTO_RESTART, + g_param_spec_boolean ("auto-restart", "Auto Restart", + "Restart streaming when frame is dropped, late or underrun happens", + DEFAULT_AUTO_RESTART, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + GstCaps *templ_caps = gst_decklink2_get_default_template_caps (); templ_caps = gst_caps_make_writable (templ_caps); @@ -296,6 +306,7 @@ gst_decklink2_sink_init (GstDeckLink2Sink * self) self->n_preroll_frames = DEFAULT_N_PREROLL_FRAMES; self->min_buffered_frames = DEFAULT_MIN_BUFFERED_FRAMES; self->max_buffered_frames = DEFAULT_MAX_BUFFERED_FRAMES; + self->auto_restart = DEFAULT_AUTO_RESTART; self->priv = new GstDeckLink2SinkPrivate (); } @@ -362,6 +373,9 @@ gst_decklink2_sink_set_property (GObject * object, guint prop_id, case PROP_MAX_BUFFERED_FRAMES: self->max_buffered_frames = g_value_get_int (value); break; + case PROP_AUTO_RESTART: + self->auto_restart = g_value_get_boolean (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -419,6 +433,9 @@ gst_decklink2_sink_get_property (GObject * object, guint prop_id, case PROP_MAX_BUFFERED_FRAMES: g_value_set_int (value, self->max_buffered_frames); break; + case PROP_AUTO_RESTART: + g_value_set_boolean (value, self->auto_restart); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -592,16 +609,17 @@ gst_decklink2_sink_set_caps (GstBaseSink * sink, GstCaps * caps) * Note that this flag will have no effect in practice if the video stream * does not contain timecode metadata. */ - BMDVideoOutputFlags output_flags; if (self->timecode_format == bmdTimecodeVITC || self->timecode_format == bmdTimecodeVITCField2) { - output_flags = bmdVideoOutputVITC; + self->output_flags = bmdVideoOutputVITC; } else { - output_flags = bmdVideoOutputRP188; + self->output_flags = bmdVideoOutputRP188; } - if (self->caption_line > 0 || self->afd_bar_line > 0) - output_flags = (BMDVideoOutputFlags) (output_flags | bmdVideoOutputVANC); + if (self->caption_line > 0 || self->afd_bar_line > 0) { + self->output_flags = (BMDVideoOutputFlags) + (self->output_flags | bmdVideoOutputVANC); + } GST_DEBUG_OBJECT (self, "Configuring output, mode %" GST_FOURCC_FORMAT ", audio-sample-type %d, audio-channles %d", @@ -610,8 +628,8 @@ gst_decklink2_sink_set_caps (GstBaseSink * sink, GstCaps * caps) hr = gst_decklink2_output_configure (self->output, self->n_preroll_frames, self->min_buffered_frames, self->max_buffered_frames, - &self->selected_mode, output_flags, self->profile_id, self->keyer_mode, - (guint8) self->keyer_level, self->mapping_format, + &self->selected_mode, self->output_flags, self->profile_id, + self->keyer_mode, (guint8) self->keyer_level, self->mapping_format, self->audio_sample_type, self->audio_channels); if (hr != S_OK) { GST_ERROR_OBJECT (self, "Couldn't configure output"); @@ -858,6 +876,7 @@ gst_decklink2_sink_render (GstBaseSink * sink, GstBuffer * buffer) GstMapInfo info; guint8 *audio_data = NULL; gsize audio_data_size = 0; + guint64 drop_count, late_count, underrun_count; if (!self->prepared_frame) { GST_ERROR_OBJECT (self, "No prepared frame"); @@ -886,7 +905,8 @@ gst_decklink2_sink_render (GstBaseSink * sink, GstBuffer * buffer) G_GSIZE_FORMAT, self->prepared_frame, audio_data_size); hr = gst_decklink2_output_schedule_stream (self->output, - self->prepared_frame, audio_data, audio_data_size); + self->prepared_frame, audio_data, audio_data_size, &drop_count, + &late_count, &underrun_count); if (audio_buf) gst_buffer_unmap (audio_buf, &info); @@ -897,5 +917,22 @@ gst_decklink2_sink_render (GstBaseSink * sink, GstBuffer * buffer) return GST_FLOW_ERROR; } + if (self->auto_restart && (drop_count || late_count || underrun_count)) { + GST_WARNING_OBJECT (self, "Restart output, drop count: %" G_GUINT64_FORMAT + ", late cout: %" G_GUINT64_FORMAT ", %" G_GUINT64_FORMAT, + drop_count, late_count, underrun_count); + + hr = gst_decklink2_output_configure (self->output, self->n_preroll_frames, + self->min_buffered_frames, self->max_buffered_frames, + &self->selected_mode, self->output_flags, self->profile_id, + self->keyer_mode, (guint8) self->keyer_level, self->mapping_format, + self->audio_sample_type, self->audio_channels); + + if (hr != S_OK) { + GST_ERROR_OBJECT (self, "Couldn't configure output"); + return GST_FLOW_OK; + } + } + return GST_FLOW_OK; }