decklinkvideosrc: fix decklinkvideosrc becomes unrecoverable if it fails to start streaming

See #2611

When transitioning to PLAYING we call IDeckLinkInput::StartStreams() (via GstDecklinkInput::start_streams). This can
fail for various reasons, including if the hardware device is already in use.

Previously, we logged StartStreams() failure as an element error but otherwise continued to successfully transition to
PLAYING. Now, if StartStreams() returns an error we fail the state change.

If StartStreams() fails then a subsequent call to StopStreams(), when transitioning PAUSED to READY, will also fail.

Previously, if StopStreams() failed then the state change also failed. Unfortunately this prevented the element from
later being disposed, which in turn meant the associated hardware resources was never freed. Consequently, if a
decklinkvideosrc failed to transition PAUSED to READY, then the associated hardware device could not subsequently be
used by any other decklinkvideosrc.

Now, we log an element error if StopStreams() fails but otherwise consider the state change to have succeeded. This
means that the element can be disposed and the associated hardware resource released.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/9578>
This commit is contained in:
Ian Napier 2025-08-19 11:15:29 +01:00 committed by GStreamer Marge Bot
parent 6d53576194
commit aee742223c
2 changed files with 29 additions and 14 deletions

View File

@ -596,7 +596,7 @@ struct _GstDecklinkInput {
gboolean audio_enabled;
GstElement *videosrc;
gboolean video_enabled;
void (*start_streams) (GstElement *videosrc);
gboolean (*start_streams) (GstElement *videosrc);
};
GstDecklinkOutput * gst_decklink_acquire_nth_output (gint n, gint64 persistent_id, GstElement * sink, gboolean is_audio);

View File

@ -247,7 +247,7 @@ static gboolean gst_decklink_video_src_close (GstDecklinkVideoSrc * self);
static gboolean gst_decklink_video_src_stop (GstDecklinkVideoSrc * self);
static void gst_decklink_video_src_start_streams (GstElement * element);
static gboolean gst_decklink_video_src_start_streams (GstElement * element);
#define parent_class gst_decklink_video_src_parent_class
G_DEFINE_TYPE (GstDecklinkVideoSrc, gst_decklink_video_src, GST_TYPE_PUSH_SRC);
@ -693,8 +693,12 @@ gst_decklink_video_src_start (GstDecklinkVideoSrc * self)
g_mutex_lock (&self->input->lock);
self->input->mode = mode;
self->input->video_enabled = TRUE;
if (self->input->start_streams)
self->input->start_streams (self->input->videosrc);
if (self->input->start_streams) {
if (!self->input->start_streams (self->input->videosrc)) {
g_mutex_unlock (&self->input->lock);
return FALSE;
}
}
g_mutex_unlock (&self->input->lock);
self->skipped_last = 0;
@ -1897,7 +1901,11 @@ gst_decklink_video_src_stop (GstDecklinkVideoSrc * self)
self->input->video_enabled = FALSE;
g_mutex_unlock (&self->input->lock);
self->input->input->DisableVideoInput ();
const HRESULT res = self->input->input->DisableVideoInput ();
if (FAILED(res)) {
GST_ELEMENT_ERROR (self, STREAM, FAILED,
(NULL), ("Failed to disable video input: 0x%08lx", (unsigned long) res));
}
}
if (self->vbiparser) {
@ -1910,11 +1918,10 @@ gst_decklink_video_src_stop (GstDecklinkVideoSrc * self)
return TRUE;
}
static void
static gboolean
gst_decklink_video_src_start_streams (GstElement * element)
{
GstDecklinkVideoSrc *self = GST_DECKLINK_VIDEO_SRC_CAST (element);
HRESULT res;
if (self->input->video_enabled && (!self->input->audiosrc
|| self->input->audio_enabled)
@ -1937,15 +1944,18 @@ gst_decklink_video_src_start_streams (GstElement * element)
self->next_time_mapping.num = 1;
self->next_time_mapping.den = 1;
g_mutex_unlock (&self->lock);
res = self->input->input->StartStreams ();
if (res != S_OK) {
const HRESULT res = self->input->input->StartStreams ();
if (FAILED(res)) {
GST_ELEMENT_ERROR (self, STREAM, FAILED,
(NULL), ("Failed to start streams: 0x%08lx", (unsigned long) res));
return;
return FALSE;
}
} else {
GST_DEBUG_OBJECT (self, "Not starting streams yet");
}
return TRUE;
}
static GstStateChangeReturn
@ -1997,19 +2007,24 @@ gst_decklink_video_src_change_state (GstElement * element,
HRESULT res;
GST_DEBUG_OBJECT (self, "Stopping streams");
// Even if StopStreams() fails we consider the element state change to
// have succeeded. This can happen if the earlier call to StartStreams()
// failed. Downward state changes should never fail since doing so would
// prevent the element being disposed (and so prevent the associated
// hardware resource being released).
res = self->input->input->StopStreams ();
if (res != S_OK) {
GST_ELEMENT_ERROR (self, STREAM, FAILED,
(NULL), ("Failed to stop streams: 0x%08lx", (unsigned long) res));
ret = GST_STATE_CHANGE_FAILURE;
}
break;
}
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:{
g_mutex_lock (&self->input->lock);
if (self->input->start_streams)
self->input->start_streams (self->input->videosrc);
if (self->input->start_streams) {
if (!self->input->start_streams (self->input->videosrc))
ret = GST_STATE_CHANGE_FAILURE;
}
g_mutex_unlock (&self->input->lock);
break;