audio: Make sure to stop ringbuffer on error

Add gst_audio_ring_buffer_set_errored() that will mark the
ringbuffer as errored only if it is currently started or paused,
so gst_audio_ringbuffer_stop() can be sure that the error
state means that the ringbuffer was started and needs stop called.

Fixes a crash with osxaudiosrc if the source element posts
an error, because the ringbuffer would not get stopped and CoreAudio
would continue trying to do callbacks.

Also, anywhere that modifies the ringbuffer state, make sure to
use atomic operations, to guarantee their visibility

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5216>
This commit is contained in:
Jan Schmidt 2023-08-19 01:00:16 +10:00 committed by Tim-Philipp Müller
parent 1347f71b6d
commit df9482f39b
2 changed files with 54 additions and 8 deletions

View file

@ -45,6 +45,10 @@
GST_DEBUG_CATEGORY_STATIC (gst_audio_base_src_debug); GST_DEBUG_CATEGORY_STATIC (gst_audio_base_src_debug);
#define GST_CAT_DEFAULT gst_audio_base_src_debug #define GST_CAT_DEFAULT gst_audio_base_src_debug
/* This function is public in >= 1.23, but internal in 1.22 */
G_GNUC_INTERNAL
void __gst_audio_ring_buffer_set_errored (GstAudioRingBuffer * buf);
struct _GstAudioBaseSrcPrivate struct _GstAudioBaseSrcPrivate
{ {
/* the clock slaving algorithm in use */ /* the clock slaving algorithm in use */
@ -1229,7 +1233,7 @@ gst_audio_base_src_post_message (GstElement * element, GstMessage * message)
* flow error message */ * flow error message */
ret = GST_ELEMENT_CLASS (parent_class)->post_message (element, message); ret = GST_ELEMENT_CLASS (parent_class)->post_message (element, message);
g_atomic_int_set (&ringbuffer->state, GST_AUDIO_RING_BUFFER_STATE_ERROR); __gst_audio_ring_buffer_set_errored (ringbuffer);
GST_AUDIO_RING_BUFFER_SIGNAL (ringbuffer); GST_AUDIO_RING_BUFFER_SIGNAL (ringbuffer);
gst_object_unref (ringbuffer); gst_object_unref (ringbuffer);
} else { } else {

View file

@ -81,7 +81,7 @@ gst_audio_ring_buffer_init (GstAudioRingBuffer * ringbuffer)
{ {
ringbuffer->open = FALSE; ringbuffer->open = FALSE;
ringbuffer->acquired = FALSE; ringbuffer->acquired = FALSE;
ringbuffer->state = GST_AUDIO_RING_BUFFER_STATE_STOPPED; g_atomic_int_set (&ringbuffer->state, GST_AUDIO_RING_BUFFER_STATE_STOPPED);
g_cond_init (&ringbuffer->cond); g_cond_init (&ringbuffer->cond);
ringbuffer->waiting = 0; ringbuffer->waiting = 0;
ringbuffer->empty_seg = NULL; ringbuffer->empty_seg = NULL;
@ -1005,7 +1005,7 @@ gst_audio_ring_buffer_start (GstAudioRingBuffer * buf)
} }
if (G_UNLIKELY (!res)) { if (G_UNLIKELY (!res)) {
buf->state = GST_AUDIO_RING_BUFFER_STATE_PAUSED; g_atomic_int_set (&buf->state, GST_AUDIO_RING_BUFFER_STATE_PAUSED);
GST_DEBUG_OBJECT (buf, "failed to start"); GST_DEBUG_OBJECT (buf, "failed to start");
} else { } else {
GST_DEBUG_OBJECT (buf, "started"); GST_DEBUG_OBJECT (buf, "started");
@ -1036,6 +1036,40 @@ may_not_start:
} }
} }
G_GNUC_INTERNAL
void __gst_audio_ring_buffer_set_errored (GstAudioRingBuffer * buf);
/* __gst_audio_ring_buffer_set_errored:
* @buf: the #GstAudioRingBuffer that has encountered an error
*
* Mark the ringbuffer as errored after it has started.
*
* MT safe.
* Since: 1.24 (internal in 1.22)
*/
void
__gst_audio_ring_buffer_set_errored (GstAudioRingBuffer * buf)
{
gboolean res;
/* If started set to errored */
res = g_atomic_int_compare_and_exchange (&buf->state,
GST_AUDIO_RING_BUFFER_STATE_STARTED, GST_AUDIO_RING_BUFFER_STATE_ERROR);
if (!res) {
GST_DEBUG_OBJECT (buf, "ringbuffer was not started, checking paused");
res = g_atomic_int_compare_and_exchange (&buf->state,
GST_AUDIO_RING_BUFFER_STATE_PAUSED, GST_AUDIO_RING_BUFFER_STATE_ERROR);
}
if (res) {
GST_DEBUG_OBJECT (buf, "ringbuffer is errored");
} else {
GST_DEBUG_OBJECT (buf,
"Could not mark ringbuffer as errored. It must have been stopped or already errored (was state %d)",
g_atomic_int_get (&buf->state));
}
}
static gboolean static gboolean
gst_audio_ring_buffer_pause_unlocked (GstAudioRingBuffer * buf) gst_audio_ring_buffer_pause_unlocked (GstAudioRingBuffer * buf)
{ {
@ -1060,7 +1094,8 @@ gst_audio_ring_buffer_pause_unlocked (GstAudioRingBuffer * buf)
res = rclass->pause (buf); res = rclass->pause (buf);
if (G_UNLIKELY (!res)) { if (G_UNLIKELY (!res)) {
buf->state = GST_AUDIO_RING_BUFFER_STATE_STARTED; /* Restore started state */
g_atomic_int_set (&buf->state, GST_AUDIO_RING_BUFFER_STATE_STARTED);
GST_DEBUG_OBJECT (buf, "failed to pause"); GST_DEBUG_OBJECT (buf, "failed to pause");
} else { } else {
GST_DEBUG_OBJECT (buf, "paused"); GST_DEBUG_OBJECT (buf, "paused");
@ -1071,7 +1106,7 @@ gst_audio_ring_buffer_pause_unlocked (GstAudioRingBuffer * buf)
not_started: not_started:
{ {
/* was not started */ /* was not started */
GST_DEBUG_OBJECT (buf, "was not started"); GST_DEBUG_OBJECT (buf, "was not started (state %d)", buf->state);
return TRUE; return TRUE;
} }
} }
@ -1153,9 +1188,16 @@ gst_audio_ring_buffer_stop (GstAudioRingBuffer * buf)
GST_AUDIO_RING_BUFFER_STATE_PAUSED, GST_AUDIO_RING_BUFFER_STATE_PAUSED,
GST_AUDIO_RING_BUFFER_STATE_STOPPED); GST_AUDIO_RING_BUFFER_STATE_STOPPED);
if (!res) { if (!res) {
/* was not paused either, must have been stopped then */ GST_DEBUG_OBJECT (buf, "was not paused, try errored");
res = g_atomic_int_compare_and_exchange (&buf->state,
GST_AUDIO_RING_BUFFER_STATE_ERROR,
GST_AUDIO_RING_BUFFER_STATE_STOPPED);
}
if (!res) {
/* was not paused or stopped either, must have been stopped then */
res = TRUE; res = TRUE;
GST_DEBUG_OBJECT (buf, "was not paused, must have been stopped"); GST_DEBUG_OBJECT (buf,
"was not paused or errored, must have been stopped");
goto done; goto done;
} }
} }
@ -1169,7 +1211,7 @@ gst_audio_ring_buffer_stop (GstAudioRingBuffer * buf)
res = rclass->stop (buf); res = rclass->stop (buf);
if (G_UNLIKELY (!res)) { if (G_UNLIKELY (!res)) {
buf->state = GST_AUDIO_RING_BUFFER_STATE_STARTED; g_atomic_int_set (&buf->state, GST_AUDIO_RING_BUFFER_STATE_STARTED);
GST_DEBUG_OBJECT (buf, "failed to stop"); GST_DEBUG_OBJECT (buf, "failed to stop");
} else { } else {
GST_DEBUG_OBJECT (buf, "stopped"); GST_DEBUG_OBJECT (buf, "stopped");