1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO
25
26// Allow access to a raw mixing buffer
27
28#ifdef HAVE_SIGNAL_H
29#include <signal.h>
30#endif
31#include <unistd.h>
32#include <sys/types.h>
33
34#include "../SDL_sysaudio.h"
35#include "SDL_pulseaudio.h"
36#include "../../thread/SDL_systhread.h"
37
38#if (PA_PROTOCOL_VERSION < 28)
39typedef void (*pa_operation_notify_cb_t) (pa_operation *o, void *userdata);
40#endif
41
42typedef struct PulseDeviceHandle
43{
44 char *device_path;
45 uint32_t device_index;
46} PulseDeviceHandle;
47
48// should we include monitors in the device list? Set at SDL_Init time
49static bool include_monitors = false;
50
51static pa_threaded_mainloop *pulseaudio_threaded_mainloop = NULL;
52static pa_context *pulseaudio_context = NULL;
53static SDL_Thread *pulseaudio_hotplug_thread = NULL;
54static SDL_AtomicInt pulseaudio_hotplug_thread_active;
55
56// These are the OS identifiers (i.e. ALSA strings)...these are allocated in a callback
57// when the default changes, and noticed by the hotplug thread when it alerts SDL
58// to the change.
59static char *default_sink_path = NULL;
60static char *default_source_path = NULL;
61static bool default_sink_changed = false;
62static bool default_source_changed = false;
63
64
65static const char *(*PULSEAUDIO_pa_get_library_version)(void);
66static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto)(
67 pa_channel_map *, unsigned, pa_channel_map_def_t);
68static const char *(*PULSEAUDIO_pa_strerror)(int);
69static pa_proplist *(*PULSEAUDIO_pa_proplist_new)(void);
70static void (*PULSEAUDIO_pa_proplist_free)(pa_proplist *);
71static int (*PULSEAUDIO_pa_proplist_sets)(pa_proplist *, const char *, const char *);
72
73static pa_threaded_mainloop *(*PULSEAUDIO_pa_threaded_mainloop_new)(void);
74static void (*PULSEAUDIO_pa_threaded_mainloop_set_name)(pa_threaded_mainloop *, const char *);
75static pa_mainloop_api *(*PULSEAUDIO_pa_threaded_mainloop_get_api)(pa_threaded_mainloop *);
76static int (*PULSEAUDIO_pa_threaded_mainloop_start)(pa_threaded_mainloop *);
77static void (*PULSEAUDIO_pa_threaded_mainloop_stop)(pa_threaded_mainloop *);
78static void (*PULSEAUDIO_pa_threaded_mainloop_lock)(pa_threaded_mainloop *);
79static void (*PULSEAUDIO_pa_threaded_mainloop_unlock)(pa_threaded_mainloop *);
80static void (*PULSEAUDIO_pa_threaded_mainloop_wait)(pa_threaded_mainloop *);
81static void (*PULSEAUDIO_pa_threaded_mainloop_signal)(pa_threaded_mainloop *, int);
82static void (*PULSEAUDIO_pa_threaded_mainloop_free)(pa_threaded_mainloop *);
83
84static pa_operation_state_t (*PULSEAUDIO_pa_operation_get_state)(
85 const pa_operation *);
86static void (*PULSEAUDIO_pa_operation_set_state_callback)(pa_operation *, pa_operation_notify_cb_t, void *);
87static void (*PULSEAUDIO_pa_operation_cancel)(pa_operation *);
88static void (*PULSEAUDIO_pa_operation_unref)(pa_operation *);
89
90static pa_context *(*PULSEAUDIO_pa_context_new_with_proplist)(pa_mainloop_api *,
91 const char *,
92 const pa_proplist *);
93static void (*PULSEAUDIO_pa_context_set_state_callback)(pa_context *, pa_context_notify_cb_t, void *);
94static int (*PULSEAUDIO_pa_context_connect)(pa_context *, const char *,
95 pa_context_flags_t, const pa_spawn_api *);
96static pa_operation *(*PULSEAUDIO_pa_context_get_sink_info_list)(pa_context *, pa_sink_info_cb_t, void *);
97static pa_operation *(*PULSEAUDIO_pa_context_get_source_info_list)(pa_context *, pa_source_info_cb_t, void *);
98static pa_operation *(*PULSEAUDIO_pa_context_get_sink_info_by_index)(pa_context *, uint32_t, pa_sink_info_cb_t, void *);
99static pa_operation *(*PULSEAUDIO_pa_context_get_source_info_by_index)(pa_context *, uint32_t, pa_source_info_cb_t, void *);
100static pa_context_state_t (*PULSEAUDIO_pa_context_get_state)(const pa_context *);
101static pa_operation *(*PULSEAUDIO_pa_context_subscribe)(pa_context *, pa_subscription_mask_t, pa_context_success_cb_t, void *);
102static void (*PULSEAUDIO_pa_context_set_subscribe_callback)(pa_context *, pa_context_subscribe_cb_t, void *);
103static void (*PULSEAUDIO_pa_context_disconnect)(pa_context *);
104static void (*PULSEAUDIO_pa_context_unref)(pa_context *);
105
106static pa_stream *(*PULSEAUDIO_pa_stream_new)(pa_context *, const char *,
107 const pa_sample_spec *, const pa_channel_map *);
108static void (*PULSEAUDIO_pa_stream_set_state_callback)(pa_stream *, pa_stream_notify_cb_t, void *);
109static int (*PULSEAUDIO_pa_stream_connect_playback)(pa_stream *, const char *,
110 const pa_buffer_attr *, pa_stream_flags_t, const pa_cvolume *, pa_stream *);
111static int (*PULSEAUDIO_pa_stream_connect_record)(pa_stream *, const char *,
112 const pa_buffer_attr *, pa_stream_flags_t);
113static const pa_buffer_attr *(*PULSEAUDIO_pa_stream_get_buffer_attr)(pa_stream *);
114static pa_stream_state_t (*PULSEAUDIO_pa_stream_get_state)(const pa_stream *);
115static size_t (*PULSEAUDIO_pa_stream_writable_size)(const pa_stream *);
116static size_t (*PULSEAUDIO_pa_stream_readable_size)(const pa_stream *);
117static int (*PULSEAUDIO_pa_stream_write)(pa_stream *, const void *, size_t,
118 pa_free_cb_t, int64_t, pa_seek_mode_t);
119static int (*PULSEAUDIO_pa_stream_begin_write)(pa_stream *, void **, size_t *);
120static pa_operation *(*PULSEAUDIO_pa_stream_drain)(pa_stream *,
121 pa_stream_success_cb_t, void *);
122static int (*PULSEAUDIO_pa_stream_peek)(pa_stream *, const void **, size_t *);
123static int (*PULSEAUDIO_pa_stream_drop)(pa_stream *);
124static pa_operation *(*PULSEAUDIO_pa_stream_flush)(pa_stream *,
125 pa_stream_success_cb_t, void *);
126static int (*PULSEAUDIO_pa_stream_disconnect)(pa_stream *);
127static void (*PULSEAUDIO_pa_stream_unref)(pa_stream *);
128static void (*PULSEAUDIO_pa_stream_set_write_callback)(pa_stream *, pa_stream_request_cb_t, void *);
129static void (*PULSEAUDIO_pa_stream_set_read_callback)(pa_stream *, pa_stream_request_cb_t, void *);
130static pa_operation *(*PULSEAUDIO_pa_context_get_server_info)(pa_context *, pa_server_info_cb_t, void *);
131
132static bool load_pulseaudio_syms(void);
133
134#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC
135
136static const char *pulseaudio_library = SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC;
137static SDL_SharedObject *pulseaudio_handle = NULL;
138
139static bool load_pulseaudio_sym(const char *fn, void **addr)
140{
141 *addr = SDL_LoadFunction(pulseaudio_handle, fn);
142 if (!*addr) {
143 // Don't call SDL_SetError(): SDL_LoadFunction already did.
144 return false;
145 }
146
147 return true;
148}
149
150// cast funcs to char* first, to please GCC's strict aliasing rules.
151#define SDL_PULSEAUDIO_SYM(x) \
152 if (!load_pulseaudio_sym(#x, (void **)(char *)&PULSEAUDIO_##x)) \
153 return false
154
155static void UnloadPulseAudioLibrary(void)
156{
157 if (pulseaudio_handle) {
158 SDL_UnloadObject(pulseaudio_handle);
159 pulseaudio_handle = NULL;
160 }
161}
162
163static bool LoadPulseAudioLibrary(void)
164{
165 bool result = true;
166 if (!pulseaudio_handle) {
167 pulseaudio_handle = SDL_LoadObject(pulseaudio_library);
168 if (!pulseaudio_handle) {
169 result = false;
170 // Don't call SDL_SetError(): SDL_LoadObject already did.
171 } else {
172 result = load_pulseaudio_syms();
173 if (!result) {
174 UnloadPulseAudioLibrary();
175 }
176 }
177 }
178 return result;
179}
180
181#else
182
183#define SDL_PULSEAUDIO_SYM(x) PULSEAUDIO_##x = x
184
185static void UnloadPulseAudioLibrary(void)
186{
187}
188
189static bool LoadPulseAudioLibrary(void)
190{
191 load_pulseaudio_syms();
192 return true;
193}
194
195#endif // SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC
196
197static bool load_pulseaudio_syms(void)
198{
199 SDL_PULSEAUDIO_SYM(pa_get_library_version);
200 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_new);
201 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_get_api);
202 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_start);
203 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_stop);
204 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_lock);
205 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_unlock);
206 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_wait);
207 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_signal);
208 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_free);
209 SDL_PULSEAUDIO_SYM(pa_operation_get_state);
210 SDL_PULSEAUDIO_SYM(pa_operation_cancel);
211 SDL_PULSEAUDIO_SYM(pa_operation_unref);
212 SDL_PULSEAUDIO_SYM(pa_context_new_with_proplist);
213 SDL_PULSEAUDIO_SYM(pa_context_set_state_callback);
214 SDL_PULSEAUDIO_SYM(pa_context_connect);
215 SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_list);
216 SDL_PULSEAUDIO_SYM(pa_context_get_source_info_list);
217 SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_by_index);
218 SDL_PULSEAUDIO_SYM(pa_context_get_source_info_by_index);
219 SDL_PULSEAUDIO_SYM(pa_context_get_state);
220 SDL_PULSEAUDIO_SYM(pa_context_subscribe);
221 SDL_PULSEAUDIO_SYM(pa_context_set_subscribe_callback);
222 SDL_PULSEAUDIO_SYM(pa_context_disconnect);
223 SDL_PULSEAUDIO_SYM(pa_context_unref);
224 SDL_PULSEAUDIO_SYM(pa_stream_new);
225 SDL_PULSEAUDIO_SYM(pa_stream_set_state_callback);
226 SDL_PULSEAUDIO_SYM(pa_stream_connect_playback);
227 SDL_PULSEAUDIO_SYM(pa_stream_connect_record);
228 SDL_PULSEAUDIO_SYM(pa_stream_get_buffer_attr);
229 SDL_PULSEAUDIO_SYM(pa_stream_get_state);
230 SDL_PULSEAUDIO_SYM(pa_stream_writable_size);
231 SDL_PULSEAUDIO_SYM(pa_stream_readable_size);
232 SDL_PULSEAUDIO_SYM(pa_stream_begin_write);
233 SDL_PULSEAUDIO_SYM(pa_stream_write);
234 SDL_PULSEAUDIO_SYM(pa_stream_drain);
235 SDL_PULSEAUDIO_SYM(pa_stream_disconnect);
236 SDL_PULSEAUDIO_SYM(pa_stream_peek);
237 SDL_PULSEAUDIO_SYM(pa_stream_drop);
238 SDL_PULSEAUDIO_SYM(pa_stream_flush);
239 SDL_PULSEAUDIO_SYM(pa_stream_unref);
240 SDL_PULSEAUDIO_SYM(pa_channel_map_init_auto);
241 SDL_PULSEAUDIO_SYM(pa_strerror);
242 SDL_PULSEAUDIO_SYM(pa_stream_set_write_callback);
243 SDL_PULSEAUDIO_SYM(pa_stream_set_read_callback);
244 SDL_PULSEAUDIO_SYM(pa_context_get_server_info);
245 SDL_PULSEAUDIO_SYM(pa_proplist_new);
246 SDL_PULSEAUDIO_SYM(pa_proplist_free);
247 SDL_PULSEAUDIO_SYM(pa_proplist_sets);
248
249 // optional
250#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC
251 load_pulseaudio_sym("pa_operation_set_state_callback", (void **)(char *)&PULSEAUDIO_pa_operation_set_state_callback); // needs pulseaudio 4.0
252 load_pulseaudio_sym("pa_threaded_mainloop_set_name", (void **)(char *)&PULSEAUDIO_pa_threaded_mainloop_set_name); // needs pulseaudio 5.0
253#elif (PA_PROTOCOL_VERSION >= 29)
254 PULSEAUDIO_pa_operation_set_state_callback = pa_operation_set_state_callback;
255 PULSEAUDIO_pa_threaded_mainloop_set_name = pa_threaded_mainloop_set_name;
256#elif (PA_PROTOCOL_VERSION >= 28)
257 PULSEAUDIO_pa_operation_set_state_callback = pa_operation_set_state_callback;
258 PULSEAUDIO_pa_threaded_mainloop_set_name = NULL;
259#else
260 PULSEAUDIO_pa_operation_set_state_callback = NULL;
261 PULSEAUDIO_pa_threaded_mainloop_set_name = NULL;
262#endif
263
264 return true;
265}
266
267static const char *getAppName(void)
268{
269 return SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
270}
271
272static void OperationStateChangeCallback(pa_operation *o, void *userdata)
273{
274 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details.
275}
276
277/* This function assume you are holding `mainloop`'s lock. The operation is unref'd in here, assuming
278 you did the work in the callback and just want to know it's done, though. */
279static void WaitForPulseOperation(pa_operation *o)
280{
281 // This checks for NO errors currently. Either fix that, check results elsewhere, or do things you don't care about.
282 SDL_assert(pulseaudio_threaded_mainloop != NULL);
283 if (o) {
284 // note that if PULSEAUDIO_pa_operation_set_state_callback == NULL, then `o` must have a callback that will signal pulseaudio_threaded_mainloop.
285 // If not, on really old (earlier PulseAudio 4.0, from the year 2013!) installs, this call will block forever.
286 // On more modern installs, we won't ever block forever, and maybe be more efficient, thanks to pa_operation_set_state_callback.
287 // WARNING: at the time of this writing: the Steam Runtime is still on PulseAudio 1.1!
288 if (PULSEAUDIO_pa_operation_set_state_callback) {
289 PULSEAUDIO_pa_operation_set_state_callback(o, OperationStateChangeCallback, NULL);
290 }
291 while (PULSEAUDIO_pa_operation_get_state(o) == PA_OPERATION_RUNNING) {
292 PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); // this releases the lock and blocks on an internal condition variable.
293 }
294 PULSEAUDIO_pa_operation_unref(o);
295 }
296}
297
298static void DisconnectFromPulseServer(void)
299{
300 if (pulseaudio_threaded_mainloop) {
301 PULSEAUDIO_pa_threaded_mainloop_stop(pulseaudio_threaded_mainloop);
302 }
303 if (pulseaudio_context) {
304 PULSEAUDIO_pa_context_disconnect(pulseaudio_context);
305 PULSEAUDIO_pa_context_unref(pulseaudio_context);
306 pulseaudio_context = NULL;
307 }
308 if (pulseaudio_threaded_mainloop) {
309 PULSEAUDIO_pa_threaded_mainloop_free(pulseaudio_threaded_mainloop);
310 pulseaudio_threaded_mainloop = NULL;
311 }
312}
313
314static void PulseContextStateChangeCallback(pa_context *context, void *userdata)
315{
316 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details.
317}
318
319static bool ConnectToPulseServer(void)
320{
321 pa_mainloop_api *mainloop_api = NULL;
322 pa_proplist *proplist = NULL;
323 const char *icon_name;
324 int state = 0;
325
326 SDL_assert(pulseaudio_threaded_mainloop == NULL);
327 SDL_assert(pulseaudio_context == NULL);
328
329 // Set up a new main loop
330 pulseaudio_threaded_mainloop = PULSEAUDIO_pa_threaded_mainloop_new();
331 if (!pulseaudio_threaded_mainloop) {
332 return SDL_SetError("pa_threaded_mainloop_new() failed");
333 }
334
335 if (PULSEAUDIO_pa_threaded_mainloop_set_name) {
336 PULSEAUDIO_pa_threaded_mainloop_set_name(pulseaudio_threaded_mainloop, "PulseMainloop");
337 }
338
339 if (PULSEAUDIO_pa_threaded_mainloop_start(pulseaudio_threaded_mainloop) < 0) {
340 PULSEAUDIO_pa_threaded_mainloop_free(pulseaudio_threaded_mainloop);
341 pulseaudio_threaded_mainloop = NULL;
342 return SDL_SetError("pa_threaded_mainloop_start() failed");
343 }
344
345 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
346
347 mainloop_api = PULSEAUDIO_pa_threaded_mainloop_get_api(pulseaudio_threaded_mainloop);
348 SDL_assert(mainloop_api != NULL); // this never fails, right?
349
350 proplist = PULSEAUDIO_pa_proplist_new();
351 if (!proplist) {
352 SDL_SetError("pa_proplist_new() failed");
353 goto failed;
354 }
355
356 icon_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_ICON_NAME);
357 if (!icon_name || *icon_name == '\0') {
358 icon_name = "applications-games";
359 }
360 PULSEAUDIO_pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, icon_name);
361
362 pulseaudio_context = PULSEAUDIO_pa_context_new_with_proplist(mainloop_api, getAppName(), proplist);
363 if (!pulseaudio_context) {
364 SDL_SetError("pa_context_new_with_proplist() failed");
365 goto failed;
366 }
367 PULSEAUDIO_pa_proplist_free(proplist);
368
369 PULSEAUDIO_pa_context_set_state_callback(pulseaudio_context, PulseContextStateChangeCallback, NULL);
370
371 // Connect to the PulseAudio server
372 if (PULSEAUDIO_pa_context_connect(pulseaudio_context, NULL, 0, NULL) < 0) {
373 SDL_SetError("Could not setup connection to PulseAudio");
374 goto failed;
375 }
376
377 state = PULSEAUDIO_pa_context_get_state(pulseaudio_context);
378 while (PA_CONTEXT_IS_GOOD(state) && (state != PA_CONTEXT_READY)) {
379 PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
380 state = PULSEAUDIO_pa_context_get_state(pulseaudio_context);
381 }
382
383 if (state != PA_CONTEXT_READY) {
384 SDL_SetError("Could not connect to PulseAudio");
385 goto failed;
386 }
387
388 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
389
390 return true; // connected and ready!
391
392failed:
393 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
394 DisconnectFromPulseServer();
395 return false;
396}
397
398static void WriteCallback(pa_stream *p, size_t nbytes, void *userdata)
399{
400 struct SDL_PrivateAudioData *h = (struct SDL_PrivateAudioData *)userdata;
401 //SDL_Log("PULSEAUDIO WRITE CALLBACK! nbytes=%u", (unsigned int) nbytes);
402 h->bytes_requested += nbytes;
403 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
404}
405
406// This function waits until it is possible to write a full sound buffer
407static bool PULSEAUDIO_WaitDevice(SDL_AudioDevice *device)
408{
409 struct SDL_PrivateAudioData *h = device->hidden;
410 bool result = true;
411
412 //SDL_Log("PULSEAUDIO PLAYDEVICE START! mixlen=%d", available);
413
414 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
415
416 while (!SDL_GetAtomicInt(&device->shutdown) && (h->bytes_requested == 0)) {
417 //SDL_Log("PULSEAUDIO WAIT IN WAITDEVICE!");
418 PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
419
420 if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) {
421 //SDL_Log("PULSEAUDIO DEVICE FAILURE IN WAITDEVICE!");
422 result = false;
423 break;
424 }
425 }
426
427 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
428
429 return result;
430}
431
432static bool PULSEAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
433{
434 struct SDL_PrivateAudioData *h = device->hidden;
435
436 //SDL_Log("PULSEAUDIO PLAYDEVICE START! mixlen=%d", available);
437
438 SDL_assert(h->bytes_requested >= buffer_size);
439
440 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
441 const int rc = PULSEAUDIO_pa_stream_write(h->stream, buffer, buffer_size, NULL, 0LL, PA_SEEK_RELATIVE);
442 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
443
444 if (rc < 0) {
445 return false;
446 }
447
448 //SDL_Log("PULSEAUDIO FEED! nbytes=%d", buffer_size);
449 h->bytes_requested -= buffer_size;
450
451 //SDL_Log("PULSEAUDIO PLAYDEVICE END! written=%d", written);
452 return true;
453}
454
455static Uint8 *PULSEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
456{
457 struct SDL_PrivateAudioData *h = device->hidden;
458 const size_t reqsize = (size_t) SDL_min(*buffer_size, h->bytes_requested);
459 size_t nbytes = reqsize;
460 void *data = NULL;
461 if (PULSEAUDIO_pa_stream_begin_write(h->stream, &data, &nbytes) == 0) {
462 *buffer_size = (int) nbytes;
463 return (Uint8 *) data;
464 }
465
466 // don't know why this would fail, but we'll fall back just in case.
467 *buffer_size = (int) reqsize;
468 return device->hidden->mixbuf;
469}
470
471static void ReadCallback(pa_stream *p, size_t nbytes, void *userdata)
472{
473 //SDL_Log("PULSEAUDIO READ CALLBACK! nbytes=%u", (unsigned int) nbytes);
474 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // the recording code queries what it needs, we just need to signal to end any wait
475}
476
477static bool PULSEAUDIO_WaitRecordingDevice(SDL_AudioDevice *device)
478{
479 struct SDL_PrivateAudioData *h = device->hidden;
480
481 if (h->recordingbuf) {
482 return true; // there's still data available to read.
483 }
484
485 bool result = true;
486
487 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
488
489 while (!SDL_GetAtomicInt(&device->shutdown)) {
490 PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
491 if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) {
492 //SDL_Log("PULSEAUDIO DEVICE FAILURE IN WAITRECORDINGDEVICE!");
493 result = false;
494 break;
495 } else if (PULSEAUDIO_pa_stream_readable_size(h->stream) > 0) {
496 // a new fragment is available!
497 const void *data = NULL;
498 size_t nbytes = 0;
499 PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes);
500 SDL_assert(nbytes > 0);
501 if (!data) { // If NULL, then the buffer had a hole, ignore that
502 PULSEAUDIO_pa_stream_drop(h->stream); // drop this fragment.
503 } else {
504 // store this fragment's data for use with RecordDevice
505 //SDL_Log("PULSEAUDIO: recorded %d new bytes", (int) nbytes);
506 h->recordingbuf = (const Uint8 *)data;
507 h->recordinglen = nbytes;
508 break;
509 }
510 }
511 }
512
513 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
514
515 return result;
516}
517
518static int PULSEAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
519{
520 struct SDL_PrivateAudioData *h = device->hidden;
521
522 if (h->recordingbuf) {
523 const int cpy = SDL_min(buflen, h->recordinglen);
524 if (cpy > 0) {
525 //SDL_Log("PULSEAUDIO: fed %d recorded bytes", cpy);
526 SDL_memcpy(buffer, h->recordingbuf, cpy);
527 h->recordingbuf += cpy;
528 h->recordinglen -= cpy;
529 }
530 if (h->recordinglen == 0) {
531 h->recordingbuf = NULL;
532 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); // don't know if you _have_ to lock for this, but just in case.
533 PULSEAUDIO_pa_stream_drop(h->stream); // done with this fragment.
534 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
535 }
536 return cpy; // new data, return it.
537 }
538
539 return 0;
540}
541
542static void PULSEAUDIO_FlushRecording(SDL_AudioDevice *device)
543{
544 struct SDL_PrivateAudioData *h = device->hidden;
545 const void *data = NULL;
546 size_t nbytes = 0, buflen = 0;
547
548 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
549
550 if (h->recordingbuf) {
551 PULSEAUDIO_pa_stream_drop(h->stream);
552 h->recordingbuf = NULL;
553 h->recordinglen = 0;
554 }
555
556 buflen = PULSEAUDIO_pa_stream_readable_size(h->stream);
557 while (!SDL_GetAtomicInt(&device->shutdown) && (buflen > 0)) {
558 PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
559 if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) {
560 //SDL_Log("PULSEAUDIO DEVICE FAILURE IN FLUSHRECORDING!");
561 SDL_AudioDeviceDisconnected(device);
562 break;
563 }
564
565 // a fragment of audio present before FlushCapture was call is
566 // still available! Just drop it.
567 PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes);
568 PULSEAUDIO_pa_stream_drop(h->stream);
569 buflen -= nbytes;
570 }
571
572 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
573}
574
575static void PULSEAUDIO_CloseDevice(SDL_AudioDevice *device)
576{
577 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
578
579 if (device->hidden->stream) {
580 if (device->hidden->recordingbuf) {
581 PULSEAUDIO_pa_stream_drop(device->hidden->stream);
582 }
583 PULSEAUDIO_pa_stream_disconnect(device->hidden->stream);
584 PULSEAUDIO_pa_stream_unref(device->hidden->stream);
585 }
586 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // in case the device thread is waiting somewhere, this will unblock it.
587 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
588
589 SDL_free(device->hidden->mixbuf);
590 SDL_free(device->hidden);
591}
592
593static void PulseStreamStateChangeCallback(pa_stream *stream, void *userdata)
594{
595 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details.
596}
597
598static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device)
599{
600 const bool recording = device->recording;
601 struct SDL_PrivateAudioData *h = NULL;
602 SDL_AudioFormat test_format;
603 const SDL_AudioFormat *closefmts;
604 pa_sample_spec paspec;
605 pa_buffer_attr paattr;
606 pa_channel_map pacmap;
607 pa_stream_flags_t flags = 0;
608 int format = PA_SAMPLE_INVALID;
609 bool result = true;
610
611 SDL_assert(pulseaudio_threaded_mainloop != NULL);
612 SDL_assert(pulseaudio_context != NULL);
613
614 // Initialize all variables that we clean on shutdown
615 h = device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
616 if (!device->hidden) {
617 return false;
618 }
619
620 // Try for a closest match on audio format
621 closefmts = SDL_ClosestAudioFormats(device->spec.format);
622 while ((test_format = *(closefmts++)) != 0) {
623#ifdef DEBUG_AUDIO
624 SDL_Log("pulseaudio: Trying format 0x%4.4x", test_format);
625#endif
626 switch (test_format) {
627 case SDL_AUDIO_U8:
628 format = PA_SAMPLE_U8;
629 break;
630 case SDL_AUDIO_S16LE:
631 format = PA_SAMPLE_S16LE;
632 break;
633 case SDL_AUDIO_S16BE:
634 format = PA_SAMPLE_S16BE;
635 break;
636 case SDL_AUDIO_S32LE:
637 format = PA_SAMPLE_S32LE;
638 break;
639 case SDL_AUDIO_S32BE:
640 format = PA_SAMPLE_S32BE;
641 break;
642 case SDL_AUDIO_F32LE:
643 format = PA_SAMPLE_FLOAT32LE;
644 break;
645 case SDL_AUDIO_F32BE:
646 format = PA_SAMPLE_FLOAT32BE;
647 break;
648 default:
649 continue;
650 }
651 break;
652 }
653 if (!test_format) {
654 return SDL_SetError("pulseaudio: Unsupported audio format");
655 }
656 device->spec.format = test_format;
657 paspec.format = format;
658
659 // Calculate the final parameters for this audio specification
660 SDL_UpdatedAudioDeviceFormat(device);
661
662 // Allocate mixing buffer
663 if (!recording) {
664 h->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
665 if (!h->mixbuf) {
666 return false;
667 }
668 SDL_memset(h->mixbuf, device->silence_value, device->buffer_size);
669 }
670
671 paspec.channels = device->spec.channels;
672 paspec.rate = device->spec.freq;
673
674 // Reduced prebuffering compared to the defaults.
675 paattr.fragsize = device->buffer_size; // despite the name, this is only used for recording devices, according to PulseAudio docs!
676 paattr.tlength = device->buffer_size;
677 paattr.prebuf = -1;
678 paattr.maxlength = -1;
679 paattr.minreq = -1;
680 flags |= PA_STREAM_ADJUST_LATENCY;
681
682 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
683
684 const char *name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME);
685 // The SDL ALSA output hints us that we use Windows' channel mapping
686 // https://bugzilla.libsdl.org/show_bug.cgi?id=110
687 PULSEAUDIO_pa_channel_map_init_auto(&pacmap, device->spec.channels, PA_CHANNEL_MAP_WAVEEX);
688
689 h->stream = PULSEAUDIO_pa_stream_new(
690 pulseaudio_context,
691 (name && *name) ? name : "Audio Stream", // stream description
692 &paspec, // sample format spec
693 &pacmap // channel map
694 );
695
696 if (!h->stream) {
697 result = SDL_SetError("Could not set up PulseAudio stream");
698 } else {
699 int rc;
700
701 PULSEAUDIO_pa_stream_set_state_callback(h->stream, PulseStreamStateChangeCallback, NULL);
702
703 // SDL manages device moves if the default changes, so don't ever let Pulse automatically migrate this stream.
704 flags |= PA_STREAM_DONT_MOVE;
705
706 const char *device_path = ((PulseDeviceHandle *) device->handle)->device_path;
707 if (recording) {
708 PULSEAUDIO_pa_stream_set_read_callback(h->stream, ReadCallback, h);
709 rc = PULSEAUDIO_pa_stream_connect_record(h->stream, device_path, &paattr, flags);
710 } else {
711 PULSEAUDIO_pa_stream_set_write_callback(h->stream, WriteCallback, h);
712 rc = PULSEAUDIO_pa_stream_connect_playback(h->stream, device_path, &paattr, flags, NULL, NULL);
713 }
714
715 if (rc < 0) {
716 result = SDL_SetError("Could not connect PulseAudio stream");
717 } else {
718 int state = PULSEAUDIO_pa_stream_get_state(h->stream);
719 while (PA_STREAM_IS_GOOD(state) && (state != PA_STREAM_READY)) {
720 PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
721 state = PULSEAUDIO_pa_stream_get_state(h->stream);
722 }
723
724 if (!PA_STREAM_IS_GOOD(state)) {
725 result = SDL_SetError("Could not connect PulseAudio stream");
726 } else {
727 const pa_buffer_attr *actual_bufattr = PULSEAUDIO_pa_stream_get_buffer_attr(h->stream);
728 if (!actual_bufattr) {
729 result = SDL_SetError("Could not determine connected PulseAudio stream's buffer attributes");
730 } else {
731 device->buffer_size = (int) recording ? actual_bufattr->tlength : actual_bufattr->fragsize;
732 device->sample_frames = device->buffer_size / SDL_AUDIO_FRAMESIZE(device->spec);
733 }
734 }
735 }
736 }
737
738 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
739
740 // We're (hopefully) ready to rock and roll. :-)
741 return result;
742}
743
744// device handles are device index + 1, cast to void*, so we never pass a NULL.
745
746static SDL_AudioFormat PulseFormatToSDLFormat(pa_sample_format_t format)
747{
748 switch (format) {
749 case PA_SAMPLE_U8:
750 return SDL_AUDIO_U8;
751 case PA_SAMPLE_S16LE:
752 return SDL_AUDIO_S16LE;
753 case PA_SAMPLE_S16BE:
754 return SDL_AUDIO_S16BE;
755 case PA_SAMPLE_S32LE:
756 return SDL_AUDIO_S32LE;
757 case PA_SAMPLE_S32BE:
758 return SDL_AUDIO_S32BE;
759 case PA_SAMPLE_FLOAT32LE:
760 return SDL_AUDIO_F32LE;
761 case PA_SAMPLE_FLOAT32BE:
762 return SDL_AUDIO_F32BE;
763 default:
764 return 0;
765 }
766}
767
768static void AddPulseAudioDevice(const bool recording, const char *description, const char *name, const uint32_t index, const pa_sample_spec *sample_spec)
769{
770 SDL_AudioSpec spec;
771 SDL_zero(spec);
772 spec.format = PulseFormatToSDLFormat(sample_spec->format);
773 spec.channels = sample_spec->channels;
774 spec.freq = sample_spec->rate;
775 PulseDeviceHandle *handle = (PulseDeviceHandle *) SDL_malloc(sizeof (PulseDeviceHandle));
776 if (handle) {
777 handle->device_path = SDL_strdup(name);
778 if (!handle->device_path) {
779 SDL_free(handle);
780 } else {
781 handle->device_index = index;
782 SDL_AddAudioDevice(recording, description, &spec, handle);
783 }
784 }
785}
786
787// This is called when PulseAudio adds an playback ("sink") device.
788static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data)
789{
790 if (i) {
791 AddPulseAudioDevice(false, i->description, i->name, i->index, &i->sample_spec);
792 }
793 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
794}
795
796// This is called when PulseAudio adds a recording ("source") device.
797static void SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_last, void *data)
798{
799 // Maybe skip "monitor" sources. These are just output from other sinks.
800 if (i && (include_monitors || (i->monitor_of_sink == PA_INVALID_INDEX))) {
801 AddPulseAudioDevice(true, i->description, i->name, i->index, &i->sample_spec);
802 }
803 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
804}
805
806static void ServerInfoCallback(pa_context *c, const pa_server_info *i, void *data)
807{
808 //SDL_Log("PULSEAUDIO ServerInfoCallback!");
809
810 if (!default_sink_path || (SDL_strcmp(default_sink_path, i->default_sink_name) != 0)) {
811 char *str = SDL_strdup(i->default_sink_name);
812 if (str) {
813 SDL_free(default_sink_path);
814 default_sink_path = str;
815 default_sink_changed = true;
816 }
817 }
818
819 if (!default_source_path || (SDL_strcmp(default_source_path, i->default_source_name) != 0)) {
820 char *str = SDL_strdup(i->default_source_name);
821 if (str) {
822 SDL_free(default_source_path);
823 default_source_path = str;
824 default_source_changed = true;
825 }
826 }
827
828 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
829}
830
831static bool FindAudioDeviceByIndex(SDL_AudioDevice *device, void *userdata)
832{
833 const uint32_t idx = (uint32_t) (uintptr_t) userdata;
834 const PulseDeviceHandle *handle = (const PulseDeviceHandle *) device->handle;
835 return (handle->device_index == idx);
836}
837
838static bool FindAudioDeviceByPath(SDL_AudioDevice *device, void *userdata)
839{
840 const char *path = (const char *) userdata;
841 const PulseDeviceHandle *handle = (const PulseDeviceHandle *) device->handle;
842 return (SDL_strcmp(handle->device_path, path) == 0);
843}
844
845// This is called when PulseAudio has a device connected/removed/changed.
846static void HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *data)
847{
848 const bool added = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW);
849 const bool removed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE);
850 const bool changed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE);
851
852 if (added || removed || changed) { // we only care about add/remove events.
853 const bool sink = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK);
854 const bool source = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE);
855
856 if (changed) {
857 PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_server_info(pulseaudio_context, ServerInfoCallback, NULL));
858 }
859
860 /* adds need sink details from the PulseAudio server. Another callback...
861 (just unref all these operations right away, because we aren't going to wait on them
862 and their callbacks will handle any work, so they can free as soon as that happens.) */
863 if (added && sink) {
864 PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_sink_info_by_index(pulseaudio_context, idx, SinkInfoCallback, NULL));
865 } else if (added && source) {
866 PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceInfoCallback, NULL));
867 } else if (removed && (sink || source)) {
868 // removes we can handle just with the device index.
869 SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByCallback(FindAudioDeviceByIndex, (void *)(uintptr_t)idx));
870 }
871 }
872 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
873}
874
875static bool CheckDefaultDevice(const bool changed, char *device_path)
876{
877 if (!changed) {
878 return false; // nothing's happening, leave the flag marked as unchanged.
879 } else if (!device_path) {
880 return true; // check again later, we don't have a device name...
881 }
882
883 SDL_AudioDevice *device = SDL_FindPhysicalAudioDeviceByCallback(FindAudioDeviceByPath, device_path);
884 if (device) { // if NULL, we might still be waiting for a SinkInfoCallback or something, we'll try later.
885 SDL_DefaultAudioDeviceChanged(device);
886 return false; // changing complete, set flag to unchanged for future tests.
887 }
888 return true; // couldn't find the changed device, leave it marked as changed to try again later.
889}
890
891// this runs as a thread while the Pulse target is initialized to catch hotplug events.
892static int SDLCALL HotplugThread(void *data)
893{
894 pa_operation *op;
895
896 SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_LOW);
897 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
898 PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, HotplugCallback, NULL);
899
900 // don't WaitForPulseOperation on the subscription; when it's done we'll be able to get hotplug events, but waiting doesn't changing anything.
901 op = PULSEAUDIO_pa_context_subscribe(pulseaudio_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SERVER, NULL, NULL);
902
903 SDL_SignalSemaphore((SDL_Semaphore *) data);
904
905 while (SDL_GetAtomicInt(&pulseaudio_hotplug_thread_active)) {
906 PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
907 if (op && PULSEAUDIO_pa_operation_get_state(op) != PA_OPERATION_RUNNING) {
908 PULSEAUDIO_pa_operation_unref(op);
909 op = NULL;
910 }
911
912 // Update default devices; don't hold the pulse lock during this, since it could deadlock vs a playing device that we're about to lock here.
913 bool check_default_sink = default_sink_changed;
914 bool check_default_source = default_source_changed;
915 char *current_default_sink = check_default_sink ? SDL_strdup(default_sink_path) : NULL;
916 char *current_default_source = check_default_source ? SDL_strdup(default_source_path) : NULL;
917 default_sink_changed = default_source_changed = false;
918 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
919 check_default_sink = CheckDefaultDevice(check_default_sink, current_default_sink);
920 check_default_source = CheckDefaultDevice(check_default_source, current_default_source);
921 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
922
923 // free our copies (which will be NULL if nothing changed)
924 SDL_free(current_default_sink);
925 SDL_free(current_default_source);
926
927 // set these to true if we didn't handle the change OR there was _another_ change while we were working unlocked.
928 default_sink_changed = (default_sink_changed || check_default_sink);
929 default_source_changed = (default_source_changed || check_default_source);
930 }
931
932 if (op) {
933 PULSEAUDIO_pa_operation_unref(op);
934 }
935
936 PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, NULL, NULL);
937 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
938 return 0;
939}
940
941static void PULSEAUDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
942{
943 SDL_Semaphore *ready_sem = SDL_CreateSemaphore(0);
944
945 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
946 WaitForPulseOperation(PULSEAUDIO_pa_context_get_server_info(pulseaudio_context, ServerInfoCallback, NULL));
947 WaitForPulseOperation(PULSEAUDIO_pa_context_get_sink_info_list(pulseaudio_context, SinkInfoCallback, NULL));
948 WaitForPulseOperation(PULSEAUDIO_pa_context_get_source_info_list(pulseaudio_context, SourceInfoCallback, NULL));
949 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
950
951 if (default_sink_path) {
952 *default_playback = SDL_FindPhysicalAudioDeviceByCallback(FindAudioDeviceByPath, default_sink_path);
953 }
954
955 if (default_source_path) {
956 *default_recording = SDL_FindPhysicalAudioDeviceByCallback(FindAudioDeviceByPath, default_source_path);
957 }
958
959 // ok, we have a sane list, let's set up hotplug notifications now...
960 SDL_SetAtomicInt(&pulseaudio_hotplug_thread_active, 1);
961 pulseaudio_hotplug_thread = SDL_CreateThread(HotplugThread, "PulseHotplug", ready_sem);
962 if (pulseaudio_hotplug_thread) {
963 SDL_WaitSemaphore(ready_sem); // wait until the thread hits it's main loop.
964 } else {
965 SDL_SetAtomicInt(&pulseaudio_hotplug_thread_active, 0); // thread failed to start, we'll go on without hotplug.
966 }
967
968 SDL_DestroySemaphore(ready_sem);
969}
970
971static void PULSEAUDIO_FreeDeviceHandle(SDL_AudioDevice *device)
972{
973 PulseDeviceHandle *handle = (PulseDeviceHandle *) device->handle;
974 SDL_free(handle->device_path);
975 SDL_free(handle);
976}
977
978static void PULSEAUDIO_DeinitializeStart(void)
979{
980 if (pulseaudio_hotplug_thread) {
981 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
982 SDL_SetAtomicInt(&pulseaudio_hotplug_thread_active, 0);
983 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
984 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
985 SDL_WaitThread(pulseaudio_hotplug_thread, NULL);
986 pulseaudio_hotplug_thread = NULL;
987 }
988}
989
990static void PULSEAUDIO_Deinitialize(void)
991{
992 DisconnectFromPulseServer();
993
994 SDL_free(default_sink_path);
995 default_sink_path = NULL;
996 default_sink_changed = false;
997 SDL_free(default_source_path);
998 default_source_path = NULL;
999 default_source_changed = false;
1000
1001 UnloadPulseAudioLibrary();
1002}
1003
1004static bool PULSEAUDIO_Init(SDL_AudioDriverImpl *impl)
1005{
1006 if (!LoadPulseAudioLibrary()) {
1007 return false;
1008 } else if (!ConnectToPulseServer()) {
1009 UnloadPulseAudioLibrary();
1010 return false;
1011 }
1012
1013 include_monitors = SDL_GetHintBoolean(SDL_HINT_AUDIO_INCLUDE_MONITORS, false);
1014
1015 impl->DetectDevices = PULSEAUDIO_DetectDevices;
1016 impl->OpenDevice = PULSEAUDIO_OpenDevice;
1017 impl->PlayDevice = PULSEAUDIO_PlayDevice;
1018 impl->WaitDevice = PULSEAUDIO_WaitDevice;
1019 impl->GetDeviceBuf = PULSEAUDIO_GetDeviceBuf;
1020 impl->CloseDevice = PULSEAUDIO_CloseDevice;
1021 impl->DeinitializeStart = PULSEAUDIO_DeinitializeStart;
1022 impl->Deinitialize = PULSEAUDIO_Deinitialize;
1023 impl->WaitRecordingDevice = PULSEAUDIO_WaitRecordingDevice;
1024 impl->RecordDevice = PULSEAUDIO_RecordDevice;
1025 impl->FlushRecording = PULSEAUDIO_FlushRecording;
1026 impl->FreeDeviceHandle = PULSEAUDIO_FreeDeviceHandle;
1027
1028 impl->HasRecordingSupport = true;
1029
1030 return true;
1031}
1032
1033AudioBootStrap PULSEAUDIO_bootstrap = {
1034 "pulseaudio", "PulseAudio", PULSEAUDIO_Init, false, false
1035};
1036
1037#endif // SDL_AUDIO_DRIVER_PULSEAUDIO
1038