decklink2sink: Add auto-restart property

Support automatic restart when frame dropping happens
This commit is contained in:
Seungha Yang 2023-06-20 21:58:53 +09:00
parent ce71612e83
commit 5125f68b12
3 changed files with 85 additions and 17 deletions

View file

@ -149,8 +149,7 @@ public:
buffer_.resize (cur_size + num_samples); buffer_.resize (cur_size + num_samples);
if (cur_size > 0) { if (cur_size > 0) {
memmove (&buffer_[0] + silence_length_in_bytes, &buffer_[0], memmove (&buffer_[0] + silence_length_in_bytes, &buffer_[0], cur_size);
cur_size);
} }
gst_audio_format_info_fill_silence (info_.finfo, &buffer_[0], gst_audio_format_info_fill_silence (info_.finfo, &buffer_[0],
@ -234,12 +233,16 @@ struct _GstDeckLink2Output
gboolean configured; gboolean configured;
gboolean prerolled; gboolean prerolled;
guint64 late_count;
guint64 drop_count;
guint64 underrun_count;
}; };
static void gst_decklink2_output_dispose (GObject * object); static void gst_decklink2_output_dispose (GObject * object);
static void gst_decklink2_output_finalize (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_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 #define gst_decklink2_output_parent_class parent_class
G_DEFINE_TYPE (GstDeckLink2Output, gst_decklink2_output, GST_TYPE_OBJECT); G_DEFINE_TYPE (GstDeckLink2Output, gst_decklink2_output, GST_TYPE_OBJECT);
@ -308,8 +311,9 @@ public:
} }
if (result == bmdOutputFrameCompleted || if (result == bmdOutputFrameCompleted ||
result == bmdOutputFrameDisplayedLate) { result == bmdOutputFrameDisplayedLate ||
gst_decklink2_output_on_completed (output_); result == bmdOutputFrameDropped) {
gst_decklink2_output_on_completed (output_, result);
} }
return S_OK; return S_OK;
@ -1013,7 +1017,8 @@ gst_decklink2_output_schedule_video_internal (GstDeckLink2Output * self,
HRESULT HRESULT
gst_decklink2_output_schedule_stream (GstDeckLink2Output * output, 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; GstDeckLink2OutputPrivate *priv = output->priv;
HRESULT hr; HRESULT hr;
@ -1022,6 +1027,10 @@ gst_decklink2_output_schedule_stream (GstDeckLink2Output * output,
g_assert (output->configured); g_assert (output->configured);
*drop_count = 0;
*late_count = 0;
*underrun_count = 0;
hr = gst_decklink2_output_is_running (output, &active); hr = gst_decklink2_output_is_running (output, &active);
if (!gst_decklink2_result (hr)) { if (!gst_decklink2_result (hr)) {
GST_ERROR_OBJECT (output, "Couldn't query active state, hr: 0x%x", 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); std::lock_guard < std::mutex > slk (priv->schedule_lock);
priv->audio_buf.Append (audio_buf, audio_buf_size); 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 static HRESULT
@ -1094,6 +1108,10 @@ gst_decklink2_output_stop_internal (GstDeckLink2Output * self)
gst_decklink2_output_disable_video (self); gst_decklink2_output_disable_video (self);
gst_decklink2_output_set_video_callback (self, NULL); gst_decklink2_output_set_video_callback (self, NULL);
self->configured = FALSE; self->configured = FALSE;
self->prerolled = FALSE;
self->late_count = 0;
self->drop_count = 0;
self->underrun_count = 0;
return hr; return hr;
} }
@ -1934,6 +1952,9 @@ gst_decklink2_output_configure (GstDeckLink2Output * output,
output->cdp_hdr_sequence_cntr = 0; output->cdp_hdr_sequence_cntr = 0;
output->configured = TRUE; output->configured = TRUE;
output->pts = 0; output->pts = 0;
output->drop_count = 0;
output->late_count = 0;
output->underrun_count = 0;
return S_OK; return S_OK;
@ -1951,13 +1972,19 @@ gst_decklink2_output_on_stopped (GstDeckLink2Output * self)
} }
static void static void
gst_decklink2_output_on_completed (GstDeckLink2Output * self) gst_decklink2_output_on_completed (GstDeckLink2Output * self,
BMDOutputFrameCompletionResult result)
{ {
GstDeckLink2OutputPrivate *priv = self->priv; GstDeckLink2OutputPrivate *priv = self->priv;
dlbool_t active; dlbool_t active;
guint32 count = 0; guint32 count = 0;
std::lock_guard < std::mutex > lk (priv->schedule_lock); 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) if (!self->last_frame)
return; return;
@ -1966,6 +1993,7 @@ gst_decklink2_output_on_completed (GstDeckLink2Output * self)
hr = gst_decklink2_output_get_num_bufferred (self, &count); hr = gst_decklink2_output_get_num_bufferred (self, &count);
if (gst_decklink2_result (hr) && count <= self->min_buffered) { if (gst_decklink2_result (hr) && count <= self->min_buffered) {
GST_WARNING_OBJECT (self, "Underrun, buffered count %u", count); GST_WARNING_OBJECT (self, "Underrun, buffered count %u", count);
self->underrun_count++;
priv->audio_buf.PrependSilence (); priv->audio_buf.PrependSilence ();
gst_decklink2_output_schedule_video_internal (self, self->last_frame); gst_decklink2_output_schedule_video_internal (self, self->last_frame);
} }

View file

@ -65,7 +65,10 @@ IDeckLinkVideoFrame * gst_decklink2_output_upload (GstDeckLink2Output *
HRESULT gst_decklink2_output_schedule_stream (GstDeckLink2Output * output, HRESULT gst_decklink2_output_schedule_stream (GstDeckLink2Output * output,
IDeckLinkVideoFrame * frame, IDeckLinkVideoFrame * frame,
guint8 *audio_buf, 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); HRESULT gst_decklink2_output_stop (GstDeckLink2Output * output);

View file

@ -49,6 +49,7 @@ enum
PROP_N_PREROLL_FRAMES, PROP_N_PREROLL_FRAMES,
PROP_MIN_BUFFERED_FRAMES, PROP_MIN_BUFFERED_FRAMES,
PROP_MAX_BUFFERED_FRAMES, PROP_MAX_BUFFERED_FRAMES,
PROP_AUTO_RESTART
}; };
#define DEFAULT_MODE bmdModeUnknown #define DEFAULT_MODE bmdModeUnknown
@ -65,6 +66,7 @@ enum
#define DEFAULT_N_PREROLL_FRAMES 7 #define DEFAULT_N_PREROLL_FRAMES 7
#define DEFAULT_MIN_BUFFERED_FRAMES 3 #define DEFAULT_MIN_BUFFERED_FRAMES 3
#define DEFAULT_MAX_BUFFERED_FRAMES 14 #define DEFAULT_MAX_BUFFERED_FRAMES 14
#define DEFAULT_AUTO_RESTART FALSE
struct GstDeckLink2SinkPrivate struct GstDeckLink2SinkPrivate
{ {
@ -88,6 +90,7 @@ struct _GstDeckLink2Sink
GstBufferPool *fallback_pool; GstBufferPool *fallback_pool;
IDeckLinkVideoFrame *prepared_frame; IDeckLinkVideoFrame *prepared_frame;
BMDVideoOutputFlags output_flags;
/* properties */ /* properties */
BMDDisplayMode display_mode; BMDDisplayMode display_mode;
@ -104,6 +107,7 @@ struct _GstDeckLink2Sink
guint n_preroll_frames; guint n_preroll_frames;
guint min_buffered_frames; guint min_buffered_frames;
guint max_buffered_frames; guint max_buffered_frames;
gboolean auto_restart;
}; };
static void gst_decklink2_sink_set_property (GObject * object, 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", "Max number of frames to buffer before dropping",
0, 16, DEFAULT_MAX_BUFFERED_FRAMES, param_flags)); 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 (); GstCaps *templ_caps = gst_decklink2_get_default_template_caps ();
templ_caps = gst_caps_make_writable (templ_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->n_preroll_frames = DEFAULT_N_PREROLL_FRAMES;
self->min_buffered_frames = DEFAULT_MIN_BUFFERED_FRAMES; self->min_buffered_frames = DEFAULT_MIN_BUFFERED_FRAMES;
self->max_buffered_frames = DEFAULT_MAX_BUFFERED_FRAMES; self->max_buffered_frames = DEFAULT_MAX_BUFFERED_FRAMES;
self->auto_restart = DEFAULT_AUTO_RESTART;
self->priv = new GstDeckLink2SinkPrivate (); self->priv = new GstDeckLink2SinkPrivate ();
} }
@ -362,6 +373,9 @@ gst_decklink2_sink_set_property (GObject * object, guint prop_id,
case PROP_MAX_BUFFERED_FRAMES: case PROP_MAX_BUFFERED_FRAMES:
self->max_buffered_frames = g_value_get_int (value); self->max_buffered_frames = g_value_get_int (value);
break; break;
case PROP_AUTO_RESTART:
self->auto_restart = g_value_get_boolean (value);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -419,6 +433,9 @@ gst_decklink2_sink_get_property (GObject * object, guint prop_id,
case PROP_MAX_BUFFERED_FRAMES: case PROP_MAX_BUFFERED_FRAMES:
g_value_set_int (value, self->max_buffered_frames); g_value_set_int (value, self->max_buffered_frames);
break; break;
case PROP_AUTO_RESTART:
g_value_set_boolean (value, self->auto_restart);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; 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 * Note that this flag will have no effect in practice if the video stream
* does not contain timecode metadata. * does not contain timecode metadata.
*/ */
BMDVideoOutputFlags output_flags;
if (self->timecode_format == bmdTimecodeVITC || if (self->timecode_format == bmdTimecodeVITC ||
self->timecode_format == bmdTimecodeVITCField2) { self->timecode_format == bmdTimecodeVITCField2) {
output_flags = bmdVideoOutputVITC; self->output_flags = bmdVideoOutputVITC;
} else { } else {
output_flags = bmdVideoOutputRP188; self->output_flags = bmdVideoOutputRP188;
} }
if (self->caption_line > 0 || self->afd_bar_line > 0) if (self->caption_line > 0 || self->afd_bar_line > 0) {
output_flags = (BMDVideoOutputFlags) (output_flags | bmdVideoOutputVANC); self->output_flags = (BMDVideoOutputFlags)
(self->output_flags | bmdVideoOutputVANC);
}
GST_DEBUG_OBJECT (self, "Configuring output, mode %" GST_FOURCC_FORMAT GST_DEBUG_OBJECT (self, "Configuring output, mode %" GST_FOURCC_FORMAT
", audio-sample-type %d, audio-channles %d", ", 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, hr = gst_decklink2_output_configure (self->output, self->n_preroll_frames,
self->min_buffered_frames, self->max_buffered_frames, self->min_buffered_frames, self->max_buffered_frames,
&self->selected_mode, output_flags, self->profile_id, self->keyer_mode, &self->selected_mode, self->output_flags, self->profile_id,
(guint8) self->keyer_level, self->mapping_format, self->keyer_mode, (guint8) self->keyer_level, self->mapping_format,
self->audio_sample_type, self->audio_channels); self->audio_sample_type, self->audio_channels);
if (hr != S_OK) { if (hr != S_OK) {
GST_ERROR_OBJECT (self, "Couldn't configure output"); GST_ERROR_OBJECT (self, "Couldn't configure output");
@ -858,6 +876,7 @@ gst_decklink2_sink_render (GstBaseSink * sink, GstBuffer * buffer)
GstMapInfo info; GstMapInfo info;
guint8 *audio_data = NULL; guint8 *audio_data = NULL;
gsize audio_data_size = 0; gsize audio_data_size = 0;
guint64 drop_count, late_count, underrun_count;
if (!self->prepared_frame) { if (!self->prepared_frame) {
GST_ERROR_OBJECT (self, "No 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); G_GSIZE_FORMAT, self->prepared_frame, audio_data_size);
hr = gst_decklink2_output_schedule_stream (self->output, 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) if (audio_buf)
gst_buffer_unmap (audio_buf, &info); gst_buffer_unmap (audio_buf, &info);
@ -897,5 +917,22 @@ gst_decklink2_sink_render (GstBaseSink * sink, GstBuffer * buffer)
return GST_FLOW_ERROR; 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; return GST_FLOW_OK;
} }