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.)