SDL: pulseaudio: Redesigned to use pa_threaded_mainloop.

From 35292d7dba88faa667f86e77c63651d19ef49178 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Wed, 24 May 2023 13:36:52 -0400
Subject: [PATCH] pulseaudio: Redesigned to use pa_threaded_mainloop.

We weren't meant to have multiple contexts and mainloops, but we had one
for each opened device and the hotplug detection thread. Instead, use
pa_threaded_mainloop, which can be shared between threads and objects, and
a single context (which, according to the PulseAudio documentation, is
usually meant to be a singleton that represents a global server connection,
possibly with multiple streams hung on it).

Now instead of polling in a loop, threads will block until the
threaded_mainloop runs a callback, and the callback will fire a signal to
unblock the thread.

Prior to this, the code upset ThreadSanitizer, as Pulse has some unprotected
global resource that each mainloop/context would touch.

Reference Issue #7427.
---
 src/audio/pulseaudio/SDL_pulseaudio.c | 473 +++++++++++++++-----------
 src/audio/pulseaudio/SDL_pulseaudio.h |   2 -
 2 files changed, 265 insertions(+), 210 deletions(-)

diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c
index 425e438a2b34..f791c463fb23 100644
--- a/src/audio/pulseaudio/SDL_pulseaudio.c
+++ b/src/audio/pulseaudio/SDL_pulseaudio.c
@@ -19,12 +19,6 @@
   3. This notice may not be removed or altered from any source distribution.
 */
 
-/*
-  The PulseAudio target for SDL 1.3 is based on the 1.3 arts target, with
-   the appropriate parts replaced with the 1.2 PulseAudio target code. This
-   was the cleanest way to move it to 1.3. The 1.2 target was written by
-   Stéphan Kochen: stephan .a.t. kochen.nl
-*/
 #include "SDL_internal.h"
 
 #ifdef SDL_AUDIO_DRIVER_PULSEAUDIO
@@ -45,6 +39,20 @@
 /* should we include monitors in the device list? Set at SDL_Init time */
 static SDL_bool include_monitors = SDL_FALSE;
 
+static pa_threaded_mainloop *pulseaudio_threaded_mainloop = NULL;
+static pa_context *pulseaudio_context = NULL;
+static SDL_Thread *pulseaudio_hotplug_thread = NULL;
+static SDL_AtomicInt pulseaudio_hotplug_thread_active;
+
+/* These are the OS identifiers (i.e. ALSA strings)... */
+static char *default_sink_path = NULL;
+static char *default_source_path = NULL;
+/* ... and these are the descriptions we use in GetDefaultAudioInfo. */
+static char *default_sink_name = NULL;
+static char *default_source_name = NULL;
+
+
+
 #if (PA_API_VERSION < 12)
 /** Return non-zero if the passed state is one of the connected states */
 static SDL_INLINE int PA_CONTEXT_IS_GOOD(pa_context_state_t x)
@@ -62,12 +70,17 @@ static const char *(*PULSEAUDIO_pa_get_library_version)(void);
 static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto)(
     pa_channel_map *, unsigned, pa_channel_map_def_t);
 static const char *(*PULSEAUDIO_pa_strerror)(int);
-static pa_mainloop *(*PULSEAUDIO_pa_mainloop_new)(void);
-static pa_mainloop_api *(*PULSEAUDIO_pa_mainloop_get_api)(pa_mainloop *);
-static int (*PULSEAUDIO_pa_mainloop_iterate)(pa_mainloop *, int, int *);
-static int (*PULSEAUDIO_pa_mainloop_run)(pa_mainloop *, int *);
-static void (*PULSEAUDIO_pa_mainloop_quit)(pa_mainloop *, int);
-static void (*PULSEAUDIO_pa_mainloop_free)(pa_mainloop *);
+
+static pa_threaded_mainloop *(*PULSEAUDIO_pa_threaded_mainloop_new)(void);
+static void (*PULSEAUDIO_pa_threaded_mainloop_set_name)(pa_threaded_mainloop *, const char *);
+static pa_mainloop_api *(*PULSEAUDIO_pa_threaded_mainloop_get_api)(pa_threaded_mainloop *);
+static int (*PULSEAUDIO_pa_threaded_mainloop_start)(pa_threaded_mainloop *);
+static void (*PULSEAUDIO_pa_threaded_mainloop_stop)(pa_threaded_mainloop *);
+static void (*PULSEAUDIO_pa_threaded_mainloop_lock)(pa_threaded_mainloop *);
+static void (*PULSEAUDIO_pa_threaded_mainloop_unlock)(pa_threaded_mainloop *);
+static void (*PULSEAUDIO_pa_threaded_mainloop_wait)(pa_threaded_mainloop *);
+static void (*PULSEAUDIO_pa_threaded_mainloop_signal)(pa_threaded_mainloop *, int);
+static void (*PULSEAUDIO_pa_threaded_mainloop_free)(pa_threaded_mainloop *);
 
 static pa_operation_state_t (*PULSEAUDIO_pa_operation_get_state)(
     const pa_operation *);
@@ -76,6 +89,7 @@ static void (*PULSEAUDIO_pa_operation_unref)(pa_operation *);
 
 static pa_context *(*PULSEAUDIO_pa_context_new)(pa_mainloop_api *,
                                                 const char *);
+static void (*PULSEAUDIO_pa_context_set_state_callback)(pa_context *, pa_context_notify_cb_t, void *);
 static int (*PULSEAUDIO_pa_context_connect)(pa_context *, const char *,
                                             pa_context_flags_t, const pa_spawn_api *);
 static pa_operation *(*PULSEAUDIO_pa_context_get_sink_info_list)(pa_context *, pa_sink_info_cb_t, void *);
@@ -90,6 +104,7 @@ static void (*PULSEAUDIO_pa_context_unref)(pa_context *);
 
 static pa_stream *(*PULSEAUDIO_pa_stream_new)(pa_context *, const char *,
                                               const pa_sample_spec *, const pa_channel_map *);
+static void (*PULSEAUDIO_pa_stream_set_state_callback)(pa_stream *, pa_stream_notify_cb_t, void *);
 static int (*PULSEAUDIO_pa_stream_connect_playback)(pa_stream *, const char *,
                                                     const pa_buffer_attr *, pa_stream_flags_t, const pa_cvolume *, pa_stream *);
 static int (*PULSEAUDIO_pa_stream_connect_record)(pa_stream *, const char *,
@@ -108,6 +123,7 @@ static pa_operation *(*PULSEAUDIO_pa_stream_flush)(pa_stream *,
 static int (*PULSEAUDIO_pa_stream_disconnect)(pa_stream *);
 static void (*PULSEAUDIO_pa_stream_unref)(pa_stream *);
 static void (*PULSEAUDIO_pa_stream_set_write_callback)(pa_stream *, pa_stream_request_cb_t, void *);
+static void (*PULSEAUDIO_pa_stream_set_read_callback)(pa_stream *, pa_stream_request_cb_t, void *);
 static pa_operation *(*PULSEAUDIO_pa_context_get_server_info)(pa_context *, pa_server_info_cb_t, void *);
 
 static int load_pulseaudio_syms(void);
@@ -178,16 +194,21 @@ static int LoadPulseAudioLibrary(void)
 static int load_pulseaudio_syms(void)
 {
     SDL_PULSEAUDIO_SYM(pa_get_library_version);
-    SDL_PULSEAUDIO_SYM(pa_mainloop_new);
-    SDL_PULSEAUDIO_SYM(pa_mainloop_get_api);
-    SDL_PULSEAUDIO_SYM(pa_mainloop_iterate);
-    SDL_PULSEAUDIO_SYM(pa_mainloop_run);
-    SDL_PULSEAUDIO_SYM(pa_mainloop_quit);
-    SDL_PULSEAUDIO_SYM(pa_mainloop_free);
+    SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_new);
+    SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_set_name);
+    SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_get_api);
+    SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_start);
+    SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_stop);
+    SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_lock);
+    SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_unlock);
+    SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_wait);
+    SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_signal);
+    SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_free);
     SDL_PULSEAUDIO_SYM(pa_operation_get_state);
     SDL_PULSEAUDIO_SYM(pa_operation_cancel);
     SDL_PULSEAUDIO_SYM(pa_operation_unref);
     SDL_PULSEAUDIO_SYM(pa_context_new);
+    SDL_PULSEAUDIO_SYM(pa_context_set_state_callback);
     SDL_PULSEAUDIO_SYM(pa_context_connect);
     SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_list);
     SDL_PULSEAUDIO_SYM(pa_context_get_source_info_list);
@@ -199,6 +220,7 @@ static int load_pulseaudio_syms(void)
     SDL_PULSEAUDIO_SYM(pa_context_disconnect);
     SDL_PULSEAUDIO_SYM(pa_context_unref);
     SDL_PULSEAUDIO_SYM(pa_stream_new);
+    SDL_PULSEAUDIO_SYM(pa_stream_set_state_callback);
     SDL_PULSEAUDIO_SYM(pa_stream_connect_playback);
     SDL_PULSEAUDIO_SYM(pa_stream_connect_record);
     SDL_PULSEAUDIO_SYM(pa_stream_get_state);
@@ -214,6 +236,7 @@ static int load_pulseaudio_syms(void)
     SDL_PULSEAUDIO_SYM(pa_channel_map_init_auto);
     SDL_PULSEAUDIO_SYM(pa_strerror);
     SDL_PULSEAUDIO_SYM(pa_stream_set_write_callback);
+    SDL_PULSEAUDIO_SYM(pa_stream_set_read_callback);
     SDL_PULSEAUDIO_SYM(pa_context_get_server_info);
     return 0;
 }
@@ -248,87 +271,99 @@ static const char *getAppName(void)
     return retval;
 }
 
-static void WaitForPulseOperation(pa_mainloop *mainloop, pa_operation *o)
+/* This function assume you are holding `mainloop`'s lock and that `o` has a callback that will signal pulseaudio_threaded_mainloop.
+   The caller may optionally call pa_threaded_mainloop_accept() if the signal is blocking. The operation is
+   unref'd in here, assuming you did the work in the callback and just want to know it's done, though. */
+static void WaitForPulseOperation(pa_operation *o)
 {
     /* This checks for NO errors currently. Either fix that, check results elsewhere, or do things you don't care about. */
-    if (mainloop && o) {
-        SDL_bool okay = SDL_TRUE;
-        while (okay && (PULSEAUDIO_pa_operation_get_state(o) == PA_OPERATION_RUNNING)) {
-            okay = (PULSEAUDIO_pa_mainloop_iterate(mainloop, 1, NULL) >= 0);
+    SDL_assert(pulseaudio_threaded_mainloop != NULL);
+    if (o) {
+        while (PULSEAUDIO_pa_operation_get_state(o) == PA_OPERATION_RUNNING) {
+            PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);  /* this releases the lock and blocks on an internal condition variable. */
         }
         PULSEAUDIO_pa_operation_unref(o);
     }
 }
 
-static void DisconnectFromPulseServer(pa_mainloop *mainloop, pa_context *context)
+static void DisconnectFromPulseServer(void)
 {
-    if (context) {
-        PULSEAUDIO_pa_context_disconnect(context);
-        PULSEAUDIO_pa_context_unref(context);
+    if (pulseaudio_context) {
+        PULSEAUDIO_pa_context_disconnect(pulseaudio_context);
+        PULSEAUDIO_pa_context_unref(pulseaudio_context);
+        pulseaudio_context = NULL;
     }
-    if (mainloop != NULL) {
-        PULSEAUDIO_pa_mainloop_free(mainloop);
+    if (pulseaudio_threaded_mainloop != NULL) {
+        PULSEAUDIO_pa_threaded_mainloop_stop(pulseaudio_threaded_mainloop);
+        PULSEAUDIO_pa_threaded_mainloop_free(pulseaudio_threaded_mainloop);
+        pulseaudio_threaded_mainloop = NULL;
     }
 }
 
-static int ConnectToPulseServer_Internal(pa_mainloop **_mainloop, pa_context **_context)
+static void PulseContextStateChangeCallback(pa_context *context, void *userdata)
+{
+    PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);  /* just signal any waiting code, it can look up the details. */
+}
+
+static int ConnectToPulseServer(void)
 {
-    pa_mainloop *mainloop = NULL;
-    pa_context *context = NULL;
     pa_mainloop_api *mainloop_api = NULL;
     int state = 0;
 
-    *_mainloop = NULL;
-    *_context = NULL;
+    SDL_assert(pulseaudio_threaded_mainloop == NULL);
+    SDL_assert(pulseaudio_context == NULL);
 
     /* Set up a new main loop */
-    if (!(mainloop = PULSEAUDIO_pa_mainloop_new())) {
-        return SDL_SetError("pa_mainloop_new() failed");
+    if (!(pulseaudio_threaded_mainloop = PULSEAUDIO_pa_threaded_mainloop_new())) {
+        return SDL_SetError("pa_threaded_mainloop_new() failed");
+    }
+
+    PULSEAUDIO_pa_threaded_mainloop_set_name(pulseaudio_threaded_mainloop, "PulseMainloop");
+
+    if (PULSEAUDIO_pa_threaded_mainloop_start(pulseaudio_threaded_mainloop) < 0) {
+        PULSEAUDIO_pa_threaded_mainloop_free(pulseaudio_threaded_mainloop);
+        pulseaudio_threaded_mainloop = NULL;
+        return SDL_SetError("pa_threaded_mainloop_start() failed");
     }
 
-    mainloop_api = PULSEAUDIO_pa_mainloop_get_api(mainloop);
+    PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
+
+    mainloop_api = PULSEAUDIO_pa_threaded_mainloop_get_api(pulseaudio_threaded_mainloop);
     SDL_assert(mainloop_api); /* this never fails, right? */
 
-    context = PULSEAUDIO_pa_context_new(mainloop_api, getAppName());
-    if (context == NULL) {
-        PULSEAUDIO_pa_mainloop_free(mainloop);
-        return SDL_SetError("pa_context_new() failed");
+    pulseaudio_context = PULSEAUDIO_pa_context_new(mainloop_api, getAppName());
+    if (pulseaudio_context == NULL) {
+        SDL_SetError("pa_context_new() failed");
+        goto failed;
     }
 
+    PULSEAUDIO_pa_context_set_state_callback(pulseaudio_context, PulseContextStateChangeCallback, NULL);
+
     /* Connect to the PulseAudio server */
-    if (PULSEAUDIO_pa_context_connect(context, NULL, 0, NULL) < 0) {
-        PULSEAUDIO_pa_context_unref(context);
-        PULSEAUDIO_pa_mainloop_free(mainloop);
-        return SDL_SetError("Could not setup connection to PulseAudio");
+    if (PULSEAUDIO_pa_context_connect(pulseaudio_context, NULL, 0, NULL) < 0) {
+        SDL_SetError("Could not setup connection to PulseAudio");
+        goto failed;
     }
 
-    do {
-        if (PULSEAUDIO_pa_mainloop_iterate(mainloop, 1, NULL) < 0) {
-            PULSEAUDIO_pa_context_unref(context);
-            PULSEAUDIO_pa_mainloop_free(mainloop);
-            return SDL_SetError("pa_mainloop_iterate() failed");
-        }
-        state = PULSEAUDIO_pa_context_get_state(context);
-        if (!PA_CONTEXT_IS_GOOD(state)) {
-            PULSEAUDIO_pa_context_unref(context);
-            PULSEAUDIO_pa_mainloop_free(mainloop);
-            return SDL_SetError("Could not connect to PulseAudio");
-        }
-    } while (state != PA_CONTEXT_READY);
+    state = PULSEAUDIO_pa_context_get_state(pulseaudio_context);
+    while (PA_CONTEXT_IS_GOOD(state) && (state != PA_CONTEXT_READY)) {
+        PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
+        state = PULSEAUDIO_pa_context_get_state(pulseaudio_context);
+    }
+
+    if (state != PA_CONTEXT_READY) {
+        return SDL_SetError("Could not connect to PulseAudio");
+        goto failed;
+    }
 
-    *_context = context;
-    *_mainloop = mainloop;
+    PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
 
     return 0; /* connected and ready! */
-}
 
-static int ConnectToPulseServer(pa_mainloop **_mainloop, pa_context **_context)
-{
-    const int retval = ConnectToPulseServer_Internal(_mainloop, _context);
-    if (retval < 0) {
-        DisconnectFromPulseServer(*_mainloop, *_context);
-    }
-    return retval;
+failed:
+    PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
+    DisconnectFromPulseServer();
+    return -1;
 }
 
 /* This function waits until it is possible to write a full sound buffer */
@@ -342,6 +377,7 @@ static void WriteCallback(pa_stream *p, size_t nbytes, void *userdata)
     struct SDL_PrivateAudioData *h = (struct SDL_PrivateAudioData *)userdata;
     /*printf("PULSEAUDIO WRITE CALLBACK! nbytes=%u\n", (unsigned int) nbytes);*/
     h->bytes_requested += nbytes;
+    PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
 }
 
 static void PULSEAUDIO_PlayDevice(SDL_AudioDevice *_this)
@@ -353,12 +389,14 @@ static void PULSEAUDIO_PlayDevice(SDL_AudioDevice *_this)
 
     /*printf("PULSEAUDIO PLAYDEVICE START! mixlen=%d\n", available);*/
 
+    PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
+
     while (SDL_AtomicGet(&_this->enabled) && (available > 0)) {
         cpy = SDL_min(h->bytes_requested, available);
         if (cpy) {
             if (PULSEAUDIO_pa_stream_write(h->stream, h->mixbuf + written, cpy, NULL, 0LL, PA_SEEK_RELATIVE) < 0) {
                 SDL_OpenedAudioDeviceDisconnected(_this);
-                return;
+                break;
             }
             /*printf("PULSEAUDIO FEED! nbytes=%u\n", (unsigned int) cpy);*/
             h->bytes_requested -= cpy;
@@ -366,16 +404,20 @@ static void PULSEAUDIO_PlayDevice(SDL_AudioDevice *_this)
             available -= cpy;
         }
 
-        /* let WriteCallback fire if necessary. */
-        /*printf("PULSEAUDIO ITERATE!\n");*/
-        if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY ||
-            PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY ||
-            PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
-            SDL_OpenedAudioDeviceDisconnected(_this);
-            return;
+        if (available > 0) {
+            /* let WriteCallback fire if necessary. */
+            /*printf("PULSEAUDIO WAIT IN PLAYDEVICE!\n");*/
+            PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
+
+            if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) {
+                /*printf("PULSEAUDIO DEVICE FAILURE IN PLAYDEVICE!\n");*/
+                SDL_OpenedAudioDeviceDisconnected(_this);
+                break;
+            }
         }
     }
 
+    PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
     /*printf("PULSEAUDIO PLAYDEVICE END! written=%d\n", written);*/
 }
 
@@ -384,11 +426,20 @@ static Uint8 *PULSEAUDIO_GetDeviceBuf(SDL_AudioDevice *_this)
     return _this->hidden->mixbuf;
 }
 
+static void ReadCallback(pa_stream *p, size_t nbytes, void *userdata)
+{
+    /*printf("PULSEAUDIO READ CALLBACK! nbytes=%u\n", (unsigned int) nbytes);*/
+    PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);  /* the capture code queries what it needs, we just need to signal to end any wait */
+}
+
 static int PULSEAUDIO_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen)
 {
     struct SDL_PrivateAudioData *h = _this->hidden;
     const void *data = NULL;
     size_t nbytes = 0;
+    int retval = 0;
+
+    PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
 
     while (SDL_AtomicGet(&_this->enabled)) {
         if (h->capturebuf != NULL) {
@@ -401,18 +452,23 @@ static int PULSEAUDIO_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, in
                 h->capturebuf = NULL;
                 PULSEAUDIO_pa_stream_drop(h->stream); /* done with this fragment. */
             }
-            return cpy; /* new data, return it. */
+            retval = cpy; /* new data, return it. */
+            break;
         }
 
-        if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY ||
-            PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY ||
-            PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
-            SDL_OpenedAudioDeviceDisconnected(_this);
-            return -1; /* uhoh, pulse failed! */
+        while (SDL_AtomicGet(&_this->enabled) && (PULSEAUDIO_pa_stream_readable_size(h->stream) == 0)) {
+            PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
+            if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) {
+                /*printf("PULSEAUDIO DEVICE FAILURE IN CAPTUREFROMDEVICE!\n");*/
+                SDL_OpenedAudioDeviceDisconnected(_this);
+                retval = -1;
+                break;
+            }
         }
 
-        if (PULSEAUDIO_pa_stream_readable_size(h->stream) == 0) {
-            continue; /* no data available yet. */
+        if ((retval == -1) || !SDL_AtomicGet(&_this->enabled)) {  /* in case this happened while we were blocking. */
+            retval = -1;
+            break;
         }
 
         /* a new fragment is available! */
@@ -429,7 +485,9 @@ static int PULSEAUDIO_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, in
         }
     }
 
-    return -1; /* not enabled? */
+    PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
+
+    return retval;
 }
 
 static void PULSEAUDIO_FlushCapture(SDL_AudioDevice *_this)
@@ -438,32 +496,36 @@ static void PULSEAUDIO_FlushCapture(SDL_AudioDevice *_this)
     const void *data = NULL;
     size_t nbytes = 0;
 
+    PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
+
     if (h->capturebuf != NULL) {
         PULSEAUDIO_pa_stream_drop(h->stream);
         h->capturebuf = NULL;
         h->capturelen = 0;
     }
 
-    while (SDL_AtomicGet(&_this->enabled)) {
-        if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY ||
-            PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY ||
-            PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
+    while (SDL_AtomicGet(&_this->enabled) && (PULSEAUDIO_pa_stream_readable_size(h->stream) > 0)) {
+        PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
+        if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) {
+            /*printf("PULSEAUDIO DEVICE FAILURE IN FLUSHCAPTURE!\n");*/
             SDL_OpenedAudioDeviceDisconnected(_this);
-            return; /* uhoh, pulse failed! */
+            break;
         }
 
-        if (PULSEAUDIO_pa_stream_readable_size(h->stream) == 0) {
-            break; /* no data available, so we're done. */
+        if (PULSEAUDIO_pa_stream_readable_size(h->stream) > 0) {
+            /* a new fragment is available! Just dump it. */
+            PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes);
+            PULSEAUDIO_pa_stream_drop(h->stream); /* drop this fragment. */
         }
-
-        /* a new fragment is available! Just dump it. */
-        PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes);
-        PULSEAUDIO_pa_stream_drop(h->stream); /* drop this fragment. */
     }
+
+    PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
 }
 
 static void PULSEAUDIO_CloseDevice(SDL_AudioDevice *_this)
 {
+    PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
+
     if (_this->hidden->stream) {
         if (_this->hidden->capturebuf != NULL) {
             PULSEAUDIO_pa_stream_drop(_this->hidden->stream);
@@ -471,8 +533,8 @@ static void PULSEAUDIO_CloseDevice(SDL_AudioDevice *_this)
         PULSEAUDIO_pa_stream_disconnect(_this->hidden->stream);
         PULSEAUDIO_pa_stream_unref(_this->hidden->stream);
     }
+    PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
 
-    DisconnectFromPulseServer(_this->hidden->mainloop, _this->hidden->context);
     SDL_free(_this->hidden->mixbuf);
     SDL_free(_this->hidden->device_name);
     SDL_free(_this->hidden);
@@ -484,6 +546,7 @@ static void SinkDeviceNameCallback(pa_context *c, const pa_sink_info *i, int is_
         char **devname = (char **)data;
         *devname = SDL_strdup(i->name);
     }
+    PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
 }
 
 static void SourceDeviceNameCallback(pa_context *c, const pa_source_info *i, int is_last, void *data)
@@ -492,6 +555,7 @@ static void SourceDeviceNameCallback(pa_context *c, const pa_source_info *i, int
         char **devname = (char **)data;
         *devname = SDL_strdup(i->name);
     }
+    PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
 }
 
 static SDL_bool FindDeviceName(struct SDL_PrivateAudioData *h, const SDL_bool iscapture, void *handle)
@@ -503,18 +567,19 @@ static SDL_bool FindDeviceName(struct SDL_PrivateAudioData *h, const SDL_bool is
     }
 
     if (iscapture) {
-        WaitForPulseOperation(h->mainloop,
-                              PULSEAUDIO_pa_context_get_source_info_by_index(h->context, idx,
-                                                                             SourceDeviceNameCallback, &h->device_name));
+        WaitForPulseOperation(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceDeviceNameCallback, &h->device_name));
     } else {
-        WaitForPulseOperation(h->mainloop,
-                              PULSEAUDIO_pa_context_get_sink_info_by_index(h->context, idx,
-                                                                           SinkDeviceNameCallback, &h->device_name));
+        WaitForPulseOperation(PULSEAUDIO_pa_context_get_sink_info_by_index(pulseaudio_context, idx, SinkDeviceNameCallback, &h->device_name));
     }
 
     return h->device_name != NULL;
 }
 
+static void PulseStreamStateChangeCallback(pa_stream *stream, void *userdata)
+{
+    PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);  /* just signal any waiting code, it can look up the details. */
+}
+
 static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname)
 {
     struct SDL_PrivateAudioData *h = NULL;
@@ -524,10 +589,12 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname)
     pa_buffer_attr paattr;
     pa_channel_map pacmap;
     pa_stream_flags_t flags = 0;
-    const char *name = NULL;
     SDL_bool iscapture = _this->iscapture;
-    int state = 0, format = PA_SAMPLE_INVALID;
-    int rc = 0;
+    int format = PA_SAMPLE_INVALID;
+    int retval = 0;
+
+    SDL_assert(pulseaudio_threaded_mainloop != NULL);
+    SDL_assert(pulseaudio_context != NULL);
 
     /* Initialize all variables that we clean on shutdown */
     h = _this->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(*_this->hidden));
@@ -570,7 +637,7 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname)
         break;
     }
     if (!test_format) {
-        return SDL_SetError("%s: Unsupported audio format", "pulseaudio");
+        return SDL_SetError("pulseaudio: Unsupported audio format");
     }
     _this->spec.format = test_format;
     paspec.format = format;
@@ -599,74 +666,67 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname)
     paattr.minreq = -1;
     flags |= PA_STREAM_ADJUST_LATENCY;
 
-    if (ConnectToPulseServer(&h->mainloop, &h->context) < 0) {
-        return SDL_SetError("Could not connect to PulseAudio server");
-    }
+    PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
 
     if (!FindDeviceName(h, iscapture, _this->handle)) {
-        return SDL_SetError("Requested PulseAudio sink/source missing?");
-    }
-
-    /* The SDL ALSA output hints us that we use Windows' channel mapping */
-    /* http://bugzilla.libsdl.org/show_bug.cgi?id=110 */
-    PULSEAUDIO_pa_channel_map_init_auto(&pacmap, _this->spec.channels,
-                                        PA_CHANNEL_MAP_WAVEEX);
-
-    name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME);
+        retval = SDL_SetError("Requested PulseAudio sink/source missing?");
+    } else {
+        const char *name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME);
+        /* The SDL ALSA output hints us that we use Windows' channel mapping */
+        /* https://bugzilla.libsdl.org/show_bug.cgi?id=110 */
+        PULSEAUDIO_pa_channel_map_init_auto(&pacmap, _this->spec.channels,
+                                            PA_CHANNEL_MAP_WAVEEX);
+
+        h->stream = PULSEAUDIO_pa_stream_new(
+            pulseaudio_context,
+            (name && *name) ? name : "Audio Stream", /* stream description */
+            &paspec,                                 /* sample format spec */
+            &pacmap                                  /* channel map */
+        );
+
+        if (h->stream == NULL) {
+            retval = SDL_SetError("Could not set up PulseAudio stream");
+        } else {
+            int rc;
 
-    h->stream = PULSEAUDIO_pa_stream_new(
-        h->context,
-        (name && *name) ? name : "Audio Stream", /* stream description */
-        &paspec,                                 /* sample format spec */
-        &pacmap                                  /* channel map */
-    );
+            PULSEAUDIO_pa_stream_set_state_callback(h->stream, PulseStreamStateChangeCallback, NULL);
 
-    if (h->stream == NULL) {
-        return SDL_SetError("Could not set up PulseAudio stream");
-    }
+            /* now that we have multi-device support, don't move a stream from
+                a device that was unplugged to something else, unless we're default. */
+            if (h->device_name != NULL) {
+                flags |= PA_STREAM_DONT_MOVE;
+            }
 
-    /* now that we have multi-device support, don't move a stream from
-        a device that was unplugged to something else, unless we're default. */
-    if (h->device_name != NULL) {
-        flags |= PA_STREAM_DONT_MOVE;
-    }
+            if (iscapture) {
+                PULSEAUDIO_pa_stream_set_read_callback(h->stream, ReadCallback, h);
+                rc = PULSEAUDIO_pa_stream_connect_record(h->stream, h->device_name, &paattr, flags);
+            } else {
+                PULSEAUDIO_pa_stream_set_write_callback(h->stream, WriteCallback, h);
+                rc = PULSEAUDIO_pa_stream_connect_playback(h->stream, h->device_name, &paattr, flags, NULL, NULL);
+            }
 
-    if (iscapture) {
-        rc = PULSEAUDIO_pa_stream_connect_record(h->stream, h->device_name, &paattr, flags);
-    } else {
-        PULSEAUDIO_pa_stream_set_write_callback(h->stream, WriteCallback, h);
-        rc = PULSEAUDIO_pa_stream_connect_playback(h->stream, h->device_name, &paattr, flags, NULL, NULL);
-    }
+            if (rc < 0) {
+                retval = SDL_SetError("Could not connect PulseAudio stream");
+            } else {
+                int state = PULSEAUDIO_pa_stream_get_state(h->stream);
+                while (PA_STREAM_IS_GOOD(state) && (state != PA_STREAM_READY)) {
+                    PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
+                    state = PULSEAUDIO_pa_stream_get_state(h->stream);
+                }
 
-    if (rc < 0) {
-        return SDL_SetError("Could not connect PulseAudio stream");
+                if (!PA_STREAM_IS_GOOD(state)) {
+                    retval = SDL_SetError("Could not connect PulseAudio stream");
+                }
+            }
+        }
     }
 
-    do {
-        if (PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
-            return SDL_SetError("pa_mainloop_iterate() failed");
-        }
-        state = PULSEAUDIO_pa_stream_get_state(h->stream);
-        if (!PA_STREAM_IS_GOOD(state)) {
-            return SDL_SetError("Could not connect PulseAudio stream");
-        }
-    } while (state != PA_STREAM_READY);
+    PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
 
-    /* We're ready to rock and roll. :-) */
-    return 0;
+    /* We're (hopefully) ready to rock and roll. :-) */
+    return retval;
 }
 
-static pa_mainloop *hotplug_mainloop = NULL;
-static pa_context *hotplug_context = NULL;
-static SDL_Thread *hotplug_thread = NULL;
-
-/* These are the OS identifiers (i.e. ALSA strings)... */
-static char *default_sink_path = NULL;
-static char *default_source_path = NULL;
-/* ... and these are the descriptions we use in GetDefaultAudioInfo. */
-static char *default_sink_name = NULL;
-static char *default_source_name = NULL;
-
 /* device handles are device index + 1, cast to void*, so we never pass a NULL. */
 
 static SDL_AudioFormat PulseFormatToSDLFormat(pa_sample_format_t format)
@@ -717,6 +777,7 @@ static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last,
             default_sink_name = SDL_strdup(i->description);
         }
     }
+    PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
 }
 
 /* This is called when PulseAudio adds a capture ("source") device. */
@@ -748,18 +809,16 @@ static void SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_la
             }
         }
     }
+    PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
 }
 
 static void ServerInfoCallback(pa_context *c, const pa_server_info *i, void *data)
 {
-    if (default_sink_path != NULL) {
-        SDL_free(default_sink_path);
-    }
-    if (default_source_path != NULL) {
-        SDL_free(default_source_path);
-    }
+    SDL_free(default_sink_path);
+    SDL_free(defaul

(Patch may be truncated, please check the link at the top of this post.)