SDL: pipewire: i/o callbacks should avoid higher-level iteration during device open.

From 32cc92dceb3eba19935fd4ea95397877e7a46a16 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Tue, 12 Nov 2024 15:18:41 -0500
Subject: [PATCH] pipewire: i/o callbacks should avoid higher-level iteration
 during device open.

Sometimes these callbacks will fire while we're still waiting on state to
settle down in PIPEWIRE_OpenDevice, which means we're holding the device lock,
but then the i/o callback will fire from a background thread and also try to
grab the device lock, but can't, because PIPEWIRE_OpenDevice is holding it and
waiting for this i/o callback to finish...hence, a deadlock.

So now, if the device is still opening, output callbacks will write silence
and input callbacks will just flush the buffer, without calling the main
iterate function, and thus avoid obtaining the lock.
---
 src/audio/pipewire/SDL_pipewire.c | 27 +++++++++++++++++++++++++--
 1 file changed, 25 insertions(+), 2 deletions(-)

diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c
index d803c8d736805..775c558d97168 100644
--- a/src/audio/pipewire/SDL_pipewire.c
+++ b/src/audio/pipewire/SDL_pipewire.c
@@ -939,7 +939,21 @@ static bool PIPEWIRE_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, in
 
 static void output_callback(void *data)
 {
-    SDL_PlaybackAudioThreadIterate((SDL_AudioDevice *)data);
+    SDL_AudioDevice *device = (SDL_AudioDevice *) data;
+
+    // this callback can fire in a background thread during OpenDevice, while we're still blocking
+    // _with the device lock_ until the stream is ready, causing a deadlock. Write silence in this case.
+    if (device->hidden->stream_init_status != PW_READY_FLAG_ALL_BITS) {
+        int bufsize = 0;
+        Uint8 *buf = PIPEWIRE_GetDeviceBuf(device, &bufsize);
+        if (buf && bufsize) {
+            SDL_memset(buf, device->silence_value, bufsize);
+        }
+        PIPEWIRE_PlayDevice(device, buf, bufsize);
+        return;
+    }
+
+    SDL_PlaybackAudioThreadIterate(device);
 }
 
 static void PIPEWIRE_FlushRecording(SDL_AudioDevice *device)
@@ -980,7 +994,16 @@ static int PIPEWIRE_RecordDevice(SDL_AudioDevice *device, void *buffer, int bufl
 
 static void input_callback(void *data)
 {
-    SDL_RecordingAudioThreadIterate((SDL_AudioDevice *)data);
+    SDL_AudioDevice *device = (SDL_AudioDevice *) data;
+
+    // this callback can fire in a background thread during OpenDevice, while we're still blocking
+    // _with the device lock_ until the stream is ready, causing a deadlock. Drop data in this case.
+    if (device->hidden->stream_init_status != PW_READY_FLAG_ALL_BITS) {
+        PIPEWIRE_FlushRecording(device);
+        return;
+    }
+
+    SDL_RecordingAudioThreadIterate(device);
 }
 
 static void stream_add_buffer_callback(void *data, struct pw_buffer *buffer)