1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 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/*
23 The PulseAudio target for SDL 1.3 is based on the 1.3 arts target, with
24 the appropriate parts replaced with the 1.2 PulseAudio target code. This
25 was the cleanest way to move it to 1.3. The 1.2 target was written by
26 Stéphan Kochen: stephan .a.t. kochen.nl
27*/
28#include "../../SDL_internal.h"
29#include "SDL_hints.h"
30
31#if SDL_AUDIO_DRIVER_PULSEAUDIO
32
33/* Allow access to a raw mixing buffer */
34
35#ifdef HAVE_SIGNAL_H
36#include <signal.h>
37#endif
38#include <unistd.h>
39#include <sys/types.h>
40#include <pulse/pulseaudio.h>
41
42#include "SDL_timer.h"
43#include "SDL_audio.h"
44#include "../SDL_audio_c.h"
45#include "SDL_pulseaudio.h"
46#include "SDL_loadso.h"
47#include "../../thread/SDL_systhread.h"
48
49#if (PA_API_VERSION < 12)
50/** Return non-zero if the passed state is one of the connected states */
51static SDL_INLINE int PA_CONTEXT_IS_GOOD(pa_context_state_t x) {
52 return
53 x == PA_CONTEXT_CONNECTING ||
54 x == PA_CONTEXT_AUTHORIZING ||
55 x == PA_CONTEXT_SETTING_NAME ||
56 x == PA_CONTEXT_READY;
57}
58/** Return non-zero if the passed state is one of the connected states */
59static SDL_INLINE int PA_STREAM_IS_GOOD(pa_stream_state_t x) {
60 return
61 x == PA_STREAM_CREATING ||
62 x == PA_STREAM_READY;
63}
64#endif /* pulseaudio <= 0.9.10 */
65
66
67static const char *(*PULSEAUDIO_pa_get_library_version) (void);
68static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto) (
69 pa_channel_map *, unsigned, pa_channel_map_def_t);
70static const char * (*PULSEAUDIO_pa_strerror) (int);
71static pa_mainloop * (*PULSEAUDIO_pa_mainloop_new) (void);
72static pa_mainloop_api * (*PULSEAUDIO_pa_mainloop_get_api) (pa_mainloop *);
73static int (*PULSEAUDIO_pa_mainloop_iterate) (pa_mainloop *, int, int *);
74static int (*PULSEAUDIO_pa_mainloop_run) (pa_mainloop *, int *);
75static void (*PULSEAUDIO_pa_mainloop_quit) (pa_mainloop *, int);
76static void (*PULSEAUDIO_pa_mainloop_free) (pa_mainloop *);
77
78static pa_operation_state_t (*PULSEAUDIO_pa_operation_get_state) (
79 pa_operation *);
80static void (*PULSEAUDIO_pa_operation_cancel) (pa_operation *);
81static void (*PULSEAUDIO_pa_operation_unref) (pa_operation *);
82
83static pa_context * (*PULSEAUDIO_pa_context_new) (pa_mainloop_api *,
84 const char *);
85static int (*PULSEAUDIO_pa_context_connect) (pa_context *, const char *,
86 pa_context_flags_t, const pa_spawn_api *);
87static pa_operation * (*PULSEAUDIO_pa_context_get_sink_info_list) (pa_context *, pa_sink_info_cb_t, void *);
88static pa_operation * (*PULSEAUDIO_pa_context_get_source_info_list) (pa_context *, pa_source_info_cb_t, void *);
89static pa_operation * (*PULSEAUDIO_pa_context_get_sink_info_by_index) (pa_context *, uint32_t, pa_sink_info_cb_t, void *);
90static pa_operation * (*PULSEAUDIO_pa_context_get_source_info_by_index) (pa_context *, uint32_t, pa_source_info_cb_t, void *);
91static pa_context_state_t (*PULSEAUDIO_pa_context_get_state) (pa_context *);
92static pa_operation * (*PULSEAUDIO_pa_context_subscribe) (pa_context *, pa_subscription_mask_t, pa_context_success_cb_t, void *);
93static void (*PULSEAUDIO_pa_context_set_subscribe_callback) (pa_context *, pa_context_subscribe_cb_t, void *);
94static void (*PULSEAUDIO_pa_context_disconnect) (pa_context *);
95static void (*PULSEAUDIO_pa_context_unref) (pa_context *);
96
97static pa_stream * (*PULSEAUDIO_pa_stream_new) (pa_context *, const char *,
98 const pa_sample_spec *, const pa_channel_map *);
99static int (*PULSEAUDIO_pa_stream_connect_playback) (pa_stream *, const char *,
100 const pa_buffer_attr *, pa_stream_flags_t, pa_cvolume *, pa_stream *);
101static int (*PULSEAUDIO_pa_stream_connect_record) (pa_stream *, const char *,
102 const pa_buffer_attr *, pa_stream_flags_t);
103static pa_stream_state_t (*PULSEAUDIO_pa_stream_get_state) (pa_stream *);
104static size_t (*PULSEAUDIO_pa_stream_writable_size) (pa_stream *);
105static size_t (*PULSEAUDIO_pa_stream_readable_size) (pa_stream *);
106static int (*PULSEAUDIO_pa_stream_begin_write) (pa_stream *, void **, size_t*);
107static int (*PULSEAUDIO_pa_stream_cancel_write) (pa_stream *);
108static int (*PULSEAUDIO_pa_stream_write) (pa_stream *, const void *, size_t,
109 pa_free_cb_t, int64_t, pa_seek_mode_t);
110static pa_operation * (*PULSEAUDIO_pa_stream_drain) (pa_stream *,
111 pa_stream_success_cb_t, void *);
112static int (*PULSEAUDIO_pa_stream_peek) (pa_stream *, const void **, size_t *);
113static int (*PULSEAUDIO_pa_stream_drop) (pa_stream *);
114static pa_operation * (*PULSEAUDIO_pa_stream_flush) (pa_stream *,
115 pa_stream_success_cb_t, void *);
116static int (*PULSEAUDIO_pa_stream_disconnect) (pa_stream *);
117static void (*PULSEAUDIO_pa_stream_unref) (pa_stream *);
118
119static int load_pulseaudio_syms(void);
120
121
122#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC
123
124static const char *pulseaudio_library = SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC;
125static void *pulseaudio_handle = NULL;
126
127static int
128load_pulseaudio_sym(const char *fn, void **addr)
129{
130 *addr = SDL_LoadFunction(pulseaudio_handle, fn);
131 if (*addr == NULL) {
132 /* Don't call SDL_SetError(): SDL_LoadFunction already did. */
133 return 0;
134 }
135
136 return 1;
137}
138
139/* cast funcs to char* first, to please GCC's strict aliasing rules. */
140#define SDL_PULSEAUDIO_SYM(x) \
141 if (!load_pulseaudio_sym(#x, (void **) (char *) &PULSEAUDIO_##x)) return -1
142
143static void
144UnloadPulseAudioLibrary(void)
145{
146 if (pulseaudio_handle != NULL) {
147 SDL_UnloadObject(pulseaudio_handle);
148 pulseaudio_handle = NULL;
149 }
150}
151
152static int
153LoadPulseAudioLibrary(void)
154{
155 int retval = 0;
156 if (pulseaudio_handle == NULL) {
157 pulseaudio_handle = SDL_LoadObject(pulseaudio_library);
158 if (pulseaudio_handle == NULL) {
159 retval = -1;
160 /* Don't call SDL_SetError(): SDL_LoadObject already did. */
161 } else {
162 retval = load_pulseaudio_syms();
163 if (retval < 0) {
164 UnloadPulseAudioLibrary();
165 }
166 }
167 }
168 return retval;
169}
170
171#else
172
173#define SDL_PULSEAUDIO_SYM(x) PULSEAUDIO_##x = x
174
175static void
176UnloadPulseAudioLibrary(void)
177{
178}
179
180static int
181LoadPulseAudioLibrary(void)
182{
183 load_pulseaudio_syms();
184 return 0;
185}
186
187#endif /* SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC */
188
189
190static int
191load_pulseaudio_syms(void)
192{
193 SDL_PULSEAUDIO_SYM(pa_get_library_version);
194 SDL_PULSEAUDIO_SYM(pa_mainloop_new);
195 SDL_PULSEAUDIO_SYM(pa_mainloop_get_api);
196 SDL_PULSEAUDIO_SYM(pa_mainloop_iterate);
197 SDL_PULSEAUDIO_SYM(pa_mainloop_run);
198 SDL_PULSEAUDIO_SYM(pa_mainloop_quit);
199 SDL_PULSEAUDIO_SYM(pa_mainloop_free);
200 SDL_PULSEAUDIO_SYM(pa_operation_get_state);
201 SDL_PULSEAUDIO_SYM(pa_operation_cancel);
202 SDL_PULSEAUDIO_SYM(pa_operation_unref);
203 SDL_PULSEAUDIO_SYM(pa_context_new);
204 SDL_PULSEAUDIO_SYM(pa_context_connect);
205 SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_list);
206 SDL_PULSEAUDIO_SYM(pa_context_get_source_info_list);
207 SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_by_index);
208 SDL_PULSEAUDIO_SYM(pa_context_get_source_info_by_index);
209 SDL_PULSEAUDIO_SYM(pa_context_get_state);
210 SDL_PULSEAUDIO_SYM(pa_context_subscribe);
211 SDL_PULSEAUDIO_SYM(pa_context_set_subscribe_callback);
212 SDL_PULSEAUDIO_SYM(pa_context_disconnect);
213 SDL_PULSEAUDIO_SYM(pa_context_unref);
214 SDL_PULSEAUDIO_SYM(pa_stream_new);
215 SDL_PULSEAUDIO_SYM(pa_stream_connect_playback);
216 SDL_PULSEAUDIO_SYM(pa_stream_connect_record);
217 SDL_PULSEAUDIO_SYM(pa_stream_get_state);
218 SDL_PULSEAUDIO_SYM(pa_stream_writable_size);
219 SDL_PULSEAUDIO_SYM(pa_stream_readable_size);
220 SDL_PULSEAUDIO_SYM(pa_stream_write);
221 SDL_PULSEAUDIO_SYM(pa_stream_begin_write);
222 SDL_PULSEAUDIO_SYM(pa_stream_cancel_write);
223 SDL_PULSEAUDIO_SYM(pa_stream_drain);
224 SDL_PULSEAUDIO_SYM(pa_stream_disconnect);
225 SDL_PULSEAUDIO_SYM(pa_stream_peek);
226 SDL_PULSEAUDIO_SYM(pa_stream_drop);
227 SDL_PULSEAUDIO_SYM(pa_stream_flush);
228 SDL_PULSEAUDIO_SYM(pa_stream_unref);
229 SDL_PULSEAUDIO_SYM(pa_channel_map_init_auto);
230 SDL_PULSEAUDIO_SYM(pa_strerror);
231 return 0;
232}
233
234static SDL_INLINE int
235squashVersion(const int major, const int minor, const int patch)
236{
237 return ((major & 0xFF) << 16) | ((minor & 0xFF) << 8) | (patch & 0xFF);
238}
239
240/* Workaround for older pulse: pa_context_new() must have non-NULL appname */
241static const char *
242getAppName(void)
243{
244 const char *retval = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME);
245 if (!retval || !*retval) {
246 const char *verstr = PULSEAUDIO_pa_get_library_version();
247 retval = "SDL Application"; /* the "oh well" default. */
248 if (verstr != NULL) {
249 int maj, min, patch;
250 if (SDL_sscanf(verstr, "%d.%d.%d", &maj, &min, &patch) == 3) {
251 if (squashVersion(maj, min, patch) >= squashVersion(0, 9, 15)) {
252 retval = NULL; /* 0.9.15+ handles NULL correctly. */
253 }
254 }
255 }
256 }
257 return retval;
258}
259
260static void
261WaitForPulseOperation(pa_mainloop *mainloop, pa_operation *o)
262{
263 /* This checks for NO errors currently. Either fix that, check results elsewhere, or do things you don't care about. */
264 if (mainloop && o) {
265 SDL_bool okay = SDL_TRUE;
266 while (okay && (PULSEAUDIO_pa_operation_get_state(o) == PA_OPERATION_RUNNING)) {
267 okay = (PULSEAUDIO_pa_mainloop_iterate(mainloop, 1, NULL) >= 0);
268 }
269 PULSEAUDIO_pa_operation_unref(o);
270 }
271}
272
273static void
274DisconnectFromPulseServer(pa_mainloop *mainloop, pa_context *context)
275{
276 if (context) {
277 PULSEAUDIO_pa_context_disconnect(context);
278 PULSEAUDIO_pa_context_unref(context);
279 }
280 if (mainloop != NULL) {
281 PULSEAUDIO_pa_mainloop_free(mainloop);
282 }
283}
284
285static int
286ConnectToPulseServer_Internal(pa_mainloop **_mainloop, pa_context **_context)
287{
288 pa_mainloop *mainloop = NULL;
289 pa_context *context = NULL;
290 pa_mainloop_api *mainloop_api = NULL;
291 int state = 0;
292
293 *_mainloop = NULL;
294 *_context = NULL;
295
296 /* Set up a new main loop */
297 if (!(mainloop = PULSEAUDIO_pa_mainloop_new())) {
298 return SDL_SetError("pa_mainloop_new() failed");
299 }
300
301 mainloop_api = PULSEAUDIO_pa_mainloop_get_api(mainloop);
302 SDL_assert(mainloop_api); /* this never fails, right? */
303
304 context = PULSEAUDIO_pa_context_new(mainloop_api, getAppName());
305 if (!context) {
306 PULSEAUDIO_pa_mainloop_free(mainloop);
307 return SDL_SetError("pa_context_new() failed");
308 }
309
310 /* Connect to the PulseAudio server */
311 if (PULSEAUDIO_pa_context_connect(context, NULL, 0, NULL) < 0) {
312 PULSEAUDIO_pa_context_unref(context);
313 PULSEAUDIO_pa_mainloop_free(mainloop);
314 return SDL_SetError("Could not setup connection to PulseAudio");
315 }
316
317 do {
318 if (PULSEAUDIO_pa_mainloop_iterate(mainloop, 1, NULL) < 0) {
319 PULSEAUDIO_pa_context_unref(context);
320 PULSEAUDIO_pa_mainloop_free(mainloop);
321 return SDL_SetError("pa_mainloop_iterate() failed");
322 }
323 state = PULSEAUDIO_pa_context_get_state(context);
324 if (!PA_CONTEXT_IS_GOOD(state)) {
325 PULSEAUDIO_pa_context_unref(context);
326 PULSEAUDIO_pa_mainloop_free(mainloop);
327 return SDL_SetError("Could not connect to PulseAudio");
328 }
329 } while (state != PA_CONTEXT_READY);
330
331 *_context = context;
332 *_mainloop = mainloop;
333
334 return 0; /* connected and ready! */
335}
336
337static int
338ConnectToPulseServer(pa_mainloop **_mainloop, pa_context **_context)
339{
340 const int retval = ConnectToPulseServer_Internal(_mainloop, _context);
341 if (retval < 0) {
342 DisconnectFromPulseServer(*_mainloop, *_context);
343 }
344 return retval;
345}
346
347
348/* This function waits until it is possible to write a full sound buffer */
349static void
350PULSEAUDIO_WaitDevice(_THIS)
351{
352 struct SDL_PrivateAudioData *h = this->hidden;
353
354 while (SDL_AtomicGet(&this->enabled)) {
355 if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY ||
356 PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY ||
357 PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
358 SDL_OpenedAudioDeviceDisconnected(this);
359 return;
360 }
361 if (PULSEAUDIO_pa_stream_writable_size(h->stream) >= h->mixlen) {
362 return;
363 }
364 }
365}
366
367static void
368PULSEAUDIO_PlayDevice(_THIS)
369{
370 /* Write the audio data */
371 struct SDL_PrivateAudioData *h = this->hidden;
372 if (SDL_AtomicGet(&this->enabled)) {
373 if (PULSEAUDIO_pa_stream_write(h->stream, h->pabuf, h->mixlen, NULL, 0LL, PA_SEEK_RELATIVE) < 0) {
374 SDL_OpenedAudioDeviceDisconnected(this);
375 }
376 }
377}
378
379static Uint8 *
380PULSEAUDIO_GetDeviceBuf(_THIS)
381{
382 struct SDL_PrivateAudioData *h = this->hidden;
383 size_t nbytes = h->mixlen;
384 int ret;
385
386 ret = PULSEAUDIO_pa_stream_begin_write(h->stream, &h->pabuf, &nbytes);
387
388 if (ret != 0) {
389 /* fall back it intermediate buffer */
390 h->pabuf = h->mixbuf;
391 } else if (nbytes < h->mixlen) {
392 PULSEAUDIO_pa_stream_cancel_write(h->stream);
393 h->pabuf = h->mixbuf;
394 }
395
396 return (Uint8 *)h->pabuf;
397}
398
399
400static int
401PULSEAUDIO_CaptureFromDevice(_THIS, void *buffer, int buflen)
402{
403 struct SDL_PrivateAudioData *h = this->hidden;
404 const void *data = NULL;
405 size_t nbytes = 0;
406
407 while (SDL_AtomicGet(&this->enabled)) {
408 if (h->capturebuf != NULL) {
409 const int cpy = SDL_min(buflen, h->capturelen);
410 SDL_memcpy(buffer, h->capturebuf, cpy);
411 /*printf("PULSEAUDIO: fed %d captured bytes\n", cpy);*/
412 h->capturebuf += cpy;
413 h->capturelen -= cpy;
414 if (h->capturelen == 0) {
415 h->capturebuf = NULL;
416 PULSEAUDIO_pa_stream_drop(h->stream); /* done with this fragment. */
417 }
418 return cpy; /* new data, return it. */
419 }
420
421 if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY ||
422 PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY ||
423 PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
424 SDL_OpenedAudioDeviceDisconnected(this);
425 return -1; /* uhoh, pulse failed! */
426 }
427
428 if (PULSEAUDIO_pa_stream_readable_size(h->stream) == 0) {
429 continue; /* no data available yet. */
430 }
431
432 /* a new fragment is available! */
433 PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes);
434 SDL_assert(nbytes > 0);
435 if (data == NULL) { /* NULL==buffer had a hole. Ignore that. */
436 PULSEAUDIO_pa_stream_drop(h->stream); /* drop this fragment. */
437 } else {
438 /* store this fragment's data, start feeding it to SDL. */
439 /*printf("PULSEAUDIO: captured %d new bytes\n", (int) nbytes);*/
440 h->capturebuf = (const Uint8 *) data;
441 h->capturelen = nbytes;
442 }
443 }
444
445 return -1; /* not enabled? */
446}
447
448static void
449PULSEAUDIO_FlushCapture(_THIS)
450{
451 struct SDL_PrivateAudioData *h = this->hidden;
452 const void *data = NULL;
453 size_t nbytes = 0;
454
455 if (h->capturebuf != NULL) {
456 PULSEAUDIO_pa_stream_drop(h->stream);
457 h->capturebuf = NULL;
458 h->capturelen = 0;
459 }
460
461 while (SDL_AtomicGet(&this->enabled)) {
462 if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY ||
463 PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY ||
464 PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
465 SDL_OpenedAudioDeviceDisconnected(this);
466 return; /* uhoh, pulse failed! */
467 }
468
469 if (PULSEAUDIO_pa_stream_readable_size(h->stream) == 0) {
470 break; /* no data available, so we're done. */
471 }
472
473 /* a new fragment is available! Just dump it. */
474 PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes);
475 PULSEAUDIO_pa_stream_drop(h->stream); /* drop this fragment. */
476 }
477}
478
479static void
480PULSEAUDIO_CloseDevice(_THIS)
481{
482 if (this->hidden->stream) {
483 if (this->hidden->capturebuf != NULL) {
484 PULSEAUDIO_pa_stream_drop(this->hidden->stream);
485 }
486 PULSEAUDIO_pa_stream_disconnect(this->hidden->stream);
487 PULSEAUDIO_pa_stream_unref(this->hidden->stream);
488 }
489
490 DisconnectFromPulseServer(this->hidden->mainloop, this->hidden->context);
491 SDL_free(this->hidden->mixbuf);
492 SDL_free(this->hidden->device_name);
493 SDL_free(this->hidden);
494}
495
496static void
497SinkDeviceNameCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data)
498{
499 if (i) {
500 char **devname = (char **) data;
501 *devname = SDL_strdup(i->name);
502 }
503}
504
505static void
506SourceDeviceNameCallback(pa_context *c, const pa_source_info *i, int is_last, void *data)
507{
508 if (i) {
509 char **devname = (char **) data;
510 *devname = SDL_strdup(i->name);
511 }
512}
513
514static SDL_bool
515FindDeviceName(struct SDL_PrivateAudioData *h, const int iscapture, void *handle)
516{
517 const uint32_t idx = ((uint32_t) ((size_t) handle)) - 1;
518
519 if (handle == NULL) { /* NULL == default device. */
520 return SDL_TRUE;
521 }
522
523 if (iscapture) {
524 WaitForPulseOperation(h->mainloop,
525 PULSEAUDIO_pa_context_get_source_info_by_index(h->context, idx,
526 SourceDeviceNameCallback, &h->device_name));
527 } else {
528 WaitForPulseOperation(h->mainloop,
529 PULSEAUDIO_pa_context_get_sink_info_by_index(h->context, idx,
530 SinkDeviceNameCallback, &h->device_name));
531 }
532
533 return (h->device_name != NULL);
534}
535
536static int
537PULSEAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
538{
539 struct SDL_PrivateAudioData *h = NULL;
540 Uint16 test_format = 0;
541 pa_sample_spec paspec;
542 pa_buffer_attr paattr;
543 pa_channel_map pacmap;
544 pa_stream_flags_t flags = 0;
545 const char *name = NULL;
546 int state = 0;
547 int rc = 0;
548
549 /* Initialize all variables that we clean on shutdown */
550 h = this->hidden = (struct SDL_PrivateAudioData *)
551 SDL_malloc((sizeof *this->hidden));
552 if (this->hidden == NULL) {
553 return SDL_OutOfMemory();
554 }
555 SDL_zerop(this->hidden);
556
557 paspec.format = PA_SAMPLE_INVALID;
558
559 /* Try for a closest match on audio format */
560 for (test_format = SDL_FirstAudioFormat(this->spec.format);
561 (paspec.format == PA_SAMPLE_INVALID) && test_format;) {
562#ifdef DEBUG_AUDIO
563 fprintf(stderr, "Trying format 0x%4.4x\n", test_format);
564#endif
565 switch (test_format) {
566 case AUDIO_U8:
567 paspec.format = PA_SAMPLE_U8;
568 break;
569 case AUDIO_S16LSB:
570 paspec.format = PA_SAMPLE_S16LE;
571 break;
572 case AUDIO_S16MSB:
573 paspec.format = PA_SAMPLE_S16BE;
574 break;
575 case AUDIO_S32LSB:
576 paspec.format = PA_SAMPLE_S32LE;
577 break;
578 case AUDIO_S32MSB:
579 paspec.format = PA_SAMPLE_S32BE;
580 break;
581 case AUDIO_F32LSB:
582 paspec.format = PA_SAMPLE_FLOAT32LE;
583 break;
584 case AUDIO_F32MSB:
585 paspec.format = PA_SAMPLE_FLOAT32BE;
586 break;
587 default:
588 paspec.format = PA_SAMPLE_INVALID;
589 break;
590 }
591 if (paspec.format == PA_SAMPLE_INVALID) {
592 test_format = SDL_NextAudioFormat();
593 }
594 }
595 if (paspec.format == PA_SAMPLE_INVALID) {
596 return SDL_SetError("Couldn't find any hardware audio formats");
597 }
598 this->spec.format = test_format;
599
600 /* Calculate the final parameters for this audio specification */
601#ifdef PA_STREAM_ADJUST_LATENCY
602 this->spec.samples /= 2; /* Mix in smaller chunck to avoid underruns */
603#endif
604 SDL_CalculateAudioSpec(&this->spec);
605
606 /* Allocate mixing buffer */
607 if (!iscapture) {
608 h->mixlen = this->spec.size;
609 h->mixbuf = (Uint8 *) SDL_malloc(h->mixlen);
610 if (h->mixbuf == NULL) {
611 return SDL_OutOfMemory();
612 }
613 SDL_memset(h->mixbuf, this->spec.silence, this->spec.size);
614 }
615
616 paspec.channels = this->spec.channels;
617 paspec.rate = this->spec.freq;
618
619 /* Reduced prebuffering compared to the defaults. */
620#ifdef PA_STREAM_ADJUST_LATENCY
621 paattr.fragsize = this->spec.size;
622 /* 2x original requested bufsize */
623 paattr.tlength = h->mixlen * 4;
624 paattr.prebuf = -1;
625 paattr.maxlength = -1;
626 /* -1 can lead to pa_stream_writable_size() >= mixlen never being true */
627 paattr.minreq = h->mixlen;
628 flags = PA_STREAM_ADJUST_LATENCY;
629#else
630 paattr.fragsize = this->spec.size;
631 paattr.tlength = h->mixlen*2;
632 paattr.prebuf = h->mixlen*2;
633 paattr.maxlength = h->mixlen*2;
634 paattr.minreq = h->mixlen;
635#endif
636
637 if (ConnectToPulseServer(&h->mainloop, &h->context) < 0) {
638 return SDL_SetError("Could not connect to PulseAudio server");
639 }
640
641 if (!FindDeviceName(h, iscapture, handle)) {
642 return SDL_SetError("Requested PulseAudio sink/source missing?");
643 }
644
645 /* The SDL ALSA output hints us that we use Windows' channel mapping */
646 /* http://bugzilla.libsdl.org/show_bug.cgi?id=110 */
647 PULSEAUDIO_pa_channel_map_init_auto(&pacmap, this->spec.channels,
648 PA_CHANNEL_MAP_WAVEEX);
649
650 name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME);
651
652 h->stream = PULSEAUDIO_pa_stream_new(
653 h->context,
654 (name && *name) ? name : "Audio Stream", /* stream description */
655 &paspec, /* sample format spec */
656 &pacmap /* channel map */
657 );
658
659 if (h->stream == NULL) {
660 return SDL_SetError("Could not set up PulseAudio stream");
661 }
662
663 /* now that we have multi-device support, don't move a stream from
664 a device that was unplugged to something else, unless we're default. */
665 if (h->device_name != NULL) {
666 flags |= PA_STREAM_DONT_MOVE;
667 }
668
669 if (iscapture) {
670 rc = PULSEAUDIO_pa_stream_connect_record(h->stream, h->device_name, &paattr, flags);
671 } else {
672 rc = PULSEAUDIO_pa_stream_connect_playback(h->stream, h->device_name, &paattr, flags, NULL, NULL);
673 }
674
675 if (rc < 0) {
676 return SDL_SetError("Could not connect PulseAudio stream");
677 }
678
679 do {
680 if (PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
681 return SDL_SetError("pa_mainloop_iterate() failed");
682 }
683 state = PULSEAUDIO_pa_stream_get_state(h->stream);
684 if (!PA_STREAM_IS_GOOD(state)) {
685 return SDL_SetError("Could not connect PulseAudio stream");
686 }
687 } while (state != PA_STREAM_READY);
688
689 /* We're ready to rock and roll. :-) */
690 return 0;
691}
692
693static pa_mainloop *hotplug_mainloop = NULL;
694static pa_context *hotplug_context = NULL;
695static SDL_Thread *hotplug_thread = NULL;
696
697/* device handles are device index + 1, cast to void*, so we never pass a NULL. */
698
699static SDL_AudioFormat
700PulseFormatToSDLFormat(pa_sample_format_t format)
701{
702 switch (format) {
703 case PA_SAMPLE_U8:
704 return AUDIO_U8;
705 case PA_SAMPLE_S16LE:
706 return AUDIO_S16LSB;
707 case PA_SAMPLE_S16BE:
708 return AUDIO_S16MSB;
709 case PA_SAMPLE_S32LE:
710 return AUDIO_S32LSB;
711 case PA_SAMPLE_S32BE:
712 return AUDIO_S32MSB;
713 case PA_SAMPLE_FLOAT32LE:
714 return AUDIO_F32LSB;
715 case PA_SAMPLE_FLOAT32BE:
716 return AUDIO_F32MSB;
717 default:
718 return 0;
719 }
720}
721
722/* This is called when PulseAudio adds an output ("sink") device. */
723static void
724SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data)
725{
726 SDL_AudioSpec spec;
727 if (i) {
728 spec.freq = i->sample_spec.rate;
729 spec.channels = i->sample_spec.channels;
730 spec.format = PulseFormatToSDLFormat(i->sample_spec.format);
731 spec.silence = 0;
732 spec.samples = 0;
733 spec.size = 0;
734 spec.callback = NULL;
735 spec.userdata = NULL;
736
737 SDL_AddAudioDevice(SDL_FALSE, i->description, &spec, (void *) ((size_t) i->index+1));
738 }
739}
740
741/* This is called when PulseAudio adds a capture ("source") device. */
742static void
743SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_last, void *data)
744{
745 SDL_AudioSpec spec;
746 if (i) {
747 /* Skip "monitor" sources. These are just output from other sinks. */
748 if (i->monitor_of_sink == PA_INVALID_INDEX) {
749 spec.freq = i->sample_spec.rate;
750 spec.channels = i->sample_spec.channels;
751 spec.format = PulseFormatToSDLFormat(i->sample_spec.format);
752 spec.silence = 0;
753 spec.samples = 0;
754 spec.size = 0;
755 spec.callback = NULL;
756 spec.userdata = NULL;
757
758 SDL_AddAudioDevice(SDL_TRUE, i->description, &spec, (void *) ((size_t) i->index+1));
759 }
760 }
761}
762
763/* This is called when PulseAudio has a device connected/removed/changed. */
764static void
765HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *data)
766{
767 const SDL_bool added = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW);
768 const SDL_bool removed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE);
769
770 if (added || removed) { /* we only care about add/remove events. */
771 const SDL_bool sink = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK);
772 const SDL_bool source = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE);
773
774 /* adds need sink details from the PulseAudio server. Another callback... */
775 if (added && sink) {
776 PULSEAUDIO_pa_context_get_sink_info_by_index(hotplug_context, idx, SinkInfoCallback, NULL);
777 } else if (added && source) {
778 PULSEAUDIO_pa_context_get_source_info_by_index(hotplug_context, idx, SourceInfoCallback, NULL);
779 } else if (removed && (sink || source)) {
780 /* removes we can handle just with the device index. */
781 SDL_RemoveAudioDevice(source != 0, (void *) ((size_t) idx+1));
782 }
783 }
784}
785
786/* this runs as a thread while the Pulse target is initialized to catch hotplug events. */
787static int SDLCALL
788HotplugThread(void *data)
789{
790 pa_operation *o;
791 SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW);
792 PULSEAUDIO_pa_context_set_subscribe_callback(hotplug_context, HotplugCallback, NULL);
793 o = PULSEAUDIO_pa_context_subscribe(hotplug_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, NULL, NULL);
794 PULSEAUDIO_pa_operation_unref(o); /* don't wait for it, just do our thing. */
795 PULSEAUDIO_pa_mainloop_run(hotplug_mainloop, NULL);
796 return 0;
797}
798
799static void
800PULSEAUDIO_DetectDevices()
801{
802 WaitForPulseOperation(hotplug_mainloop, PULSEAUDIO_pa_context_get_sink_info_list(hotplug_context, SinkInfoCallback, NULL));
803 WaitForPulseOperation(hotplug_mainloop, PULSEAUDIO_pa_context_get_source_info_list(hotplug_context, SourceInfoCallback, NULL));
804
805 /* ok, we have a sane list, let's set up hotplug notifications now... */
806 hotplug_thread = SDL_CreateThreadInternal(HotplugThread, "PulseHotplug", 256 * 1024, NULL);
807}
808
809static void
810PULSEAUDIO_Deinitialize(void)
811{
812 if (hotplug_thread) {
813 PULSEAUDIO_pa_mainloop_quit(hotplug_mainloop, 0);
814 SDL_WaitThread(hotplug_thread, NULL);
815 hotplug_thread = NULL;
816 }
817
818 DisconnectFromPulseServer(hotplug_mainloop, hotplug_context);
819 hotplug_mainloop = NULL;
820 hotplug_context = NULL;
821
822 UnloadPulseAudioLibrary();
823}
824
825static int
826PULSEAUDIO_Init(SDL_AudioDriverImpl * impl)
827{
828 if (LoadPulseAudioLibrary() < 0) {
829 return 0;
830 }
831
832 if (ConnectToPulseServer(&hotplug_mainloop, &hotplug_context) < 0) {
833 UnloadPulseAudioLibrary();
834 return 0;
835 }
836
837 /* Set the function pointers */
838 impl->DetectDevices = PULSEAUDIO_DetectDevices;
839 impl->OpenDevice = PULSEAUDIO_OpenDevice;
840 impl->PlayDevice = PULSEAUDIO_PlayDevice;
841 impl->WaitDevice = PULSEAUDIO_WaitDevice;
842 impl->GetDeviceBuf = PULSEAUDIO_GetDeviceBuf;
843 impl->CloseDevice = PULSEAUDIO_CloseDevice;
844 impl->Deinitialize = PULSEAUDIO_Deinitialize;
845 impl->CaptureFromDevice = PULSEAUDIO_CaptureFromDevice;
846 impl->FlushCapture = PULSEAUDIO_FlushCapture;
847
848 impl->HasCaptureSupport = SDL_TRUE;
849
850 return 1; /* this audio target is available. */
851}
852
853AudioBootStrap PULSEAUDIO_bootstrap = {
854 "pulseaudio", "PulseAudio", PULSEAUDIO_Init, 0
855};
856
857#endif /* SDL_AUDIO_DRIVER_PULSEAUDIO */
858
859/* vi: set ts=4 sw=4 expandtab: */
860