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 */ |
51 | static 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 */ |
59 | static 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 | |
67 | static const char *(*PULSEAUDIO_pa_get_library_version) (void); |
68 | static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto) ( |
69 | pa_channel_map *, unsigned, pa_channel_map_def_t); |
70 | static const char * (*PULSEAUDIO_pa_strerror) (int); |
71 | static pa_mainloop * (*PULSEAUDIO_pa_mainloop_new) (void); |
72 | static pa_mainloop_api * (*PULSEAUDIO_pa_mainloop_get_api) (pa_mainloop *); |
73 | static int (*PULSEAUDIO_pa_mainloop_iterate) (pa_mainloop *, int, int *); |
74 | static int (*PULSEAUDIO_pa_mainloop_run) (pa_mainloop *, int *); |
75 | static void (*PULSEAUDIO_pa_mainloop_quit) (pa_mainloop *, int); |
76 | static void (*PULSEAUDIO_pa_mainloop_free) (pa_mainloop *); |
77 | |
78 | static pa_operation_state_t (*PULSEAUDIO_pa_operation_get_state) ( |
79 | pa_operation *); |
80 | static void (*PULSEAUDIO_pa_operation_cancel) (pa_operation *); |
81 | static void (*PULSEAUDIO_pa_operation_unref) (pa_operation *); |
82 | |
83 | static pa_context * (*PULSEAUDIO_pa_context_new) (pa_mainloop_api *, |
84 | const char *); |
85 | static int (*PULSEAUDIO_pa_context_connect) (pa_context *, const char *, |
86 | pa_context_flags_t, const pa_spawn_api *); |
87 | static pa_operation * (*PULSEAUDIO_pa_context_get_sink_info_list) (pa_context *, pa_sink_info_cb_t, void *); |
88 | static pa_operation * (*PULSEAUDIO_pa_context_get_source_info_list) (pa_context *, pa_source_info_cb_t, void *); |
89 | static pa_operation * (*PULSEAUDIO_pa_context_get_sink_info_by_index) (pa_context *, uint32_t, pa_sink_info_cb_t, void *); |
90 | static pa_operation * (*PULSEAUDIO_pa_context_get_source_info_by_index) (pa_context *, uint32_t, pa_source_info_cb_t, void *); |
91 | static pa_context_state_t (*PULSEAUDIO_pa_context_get_state) (pa_context *); |
92 | static pa_operation * (*PULSEAUDIO_pa_context_subscribe) (pa_context *, pa_subscription_mask_t, pa_context_success_cb_t, void *); |
93 | static void (*PULSEAUDIO_pa_context_set_subscribe_callback) (pa_context *, pa_context_subscribe_cb_t, void *); |
94 | static void (*PULSEAUDIO_pa_context_disconnect) (pa_context *); |
95 | static void (*PULSEAUDIO_pa_context_unref) (pa_context *); |
96 | |
97 | static pa_stream * (*PULSEAUDIO_pa_stream_new) (pa_context *, const char *, |
98 | const pa_sample_spec *, const pa_channel_map *); |
99 | static int (*PULSEAUDIO_pa_stream_connect_playback) (pa_stream *, const char *, |
100 | const pa_buffer_attr *, pa_stream_flags_t, pa_cvolume *, pa_stream *); |
101 | static int (*PULSEAUDIO_pa_stream_connect_record) (pa_stream *, const char *, |
102 | const pa_buffer_attr *, pa_stream_flags_t); |
103 | static pa_stream_state_t (*PULSEAUDIO_pa_stream_get_state) (pa_stream *); |
104 | static size_t (*PULSEAUDIO_pa_stream_writable_size) (pa_stream *); |
105 | static size_t (*PULSEAUDIO_pa_stream_readable_size) (pa_stream *); |
106 | static int (*PULSEAUDIO_pa_stream_begin_write) (pa_stream *, void **, size_t*); |
107 | static int (*PULSEAUDIO_pa_stream_cancel_write) (pa_stream *); |
108 | static int (*PULSEAUDIO_pa_stream_write) (pa_stream *, const void *, size_t, |
109 | pa_free_cb_t, int64_t, pa_seek_mode_t); |
110 | static pa_operation * (*PULSEAUDIO_pa_stream_drain) (pa_stream *, |
111 | pa_stream_success_cb_t, void *); |
112 | static int (*PULSEAUDIO_pa_stream_peek) (pa_stream *, const void **, size_t *); |
113 | static int (*PULSEAUDIO_pa_stream_drop) (pa_stream *); |
114 | static pa_operation * (*PULSEAUDIO_pa_stream_flush) (pa_stream *, |
115 | pa_stream_success_cb_t, void *); |
116 | static int (*PULSEAUDIO_pa_stream_disconnect) (pa_stream *); |
117 | static void (*PULSEAUDIO_pa_stream_unref) (pa_stream *); |
118 | |
119 | static int load_pulseaudio_syms(void); |
120 | |
121 | |
122 | #ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC |
123 | |
124 | static const char *pulseaudio_library = SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC; |
125 | static void *pulseaudio_handle = NULL; |
126 | |
127 | static int |
128 | load_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 | |
143 | static void |
144 | UnloadPulseAudioLibrary(void) |
145 | { |
146 | if (pulseaudio_handle != NULL) { |
147 | SDL_UnloadObject(pulseaudio_handle); |
148 | pulseaudio_handle = NULL; |
149 | } |
150 | } |
151 | |
152 | static int |
153 | LoadPulseAudioLibrary(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 | |
175 | static void |
176 | UnloadPulseAudioLibrary(void) |
177 | { |
178 | } |
179 | |
180 | static int |
181 | LoadPulseAudioLibrary(void) |
182 | { |
183 | load_pulseaudio_syms(); |
184 | return 0; |
185 | } |
186 | |
187 | #endif /* SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC */ |
188 | |
189 | |
190 | static int |
191 | load_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 | |
234 | static SDL_INLINE int |
235 | squashVersion(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 */ |
241 | static const char * |
242 | getAppName(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 | |
260 | static void |
261 | WaitForPulseOperation(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 | |
273 | static void |
274 | DisconnectFromPulseServer(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 | |
285 | static int |
286 | ConnectToPulseServer_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 | |
337 | static int |
338 | ConnectToPulseServer(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 */ |
349 | static void |
350 | PULSEAUDIO_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 | |
367 | static void |
368 | PULSEAUDIO_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 | |
379 | static Uint8 * |
380 | PULSEAUDIO_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 | |
400 | static int |
401 | PULSEAUDIO_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 | |
448 | static void |
449 | PULSEAUDIO_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 | |
479 | static void |
480 | PULSEAUDIO_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 | |
496 | static void |
497 | SinkDeviceNameCallback(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 | |
505 | static void |
506 | SourceDeviceNameCallback(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 | |
514 | static SDL_bool |
515 | FindDeviceName(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 | |
536 | static int |
537 | PULSEAUDIO_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 | |
693 | static pa_mainloop *hotplug_mainloop = NULL; |
694 | static pa_context *hotplug_context = NULL; |
695 | static SDL_Thread *hotplug_thread = NULL; |
696 | |
697 | /* device handles are device index + 1, cast to void*, so we never pass a NULL. */ |
698 | |
699 | static SDL_AudioFormat |
700 | PulseFormatToSDLFormat(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. */ |
723 | static void |
724 | SinkInfoCallback(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. */ |
742 | static void |
743 | SourceInfoCallback(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. */ |
764 | static void |
765 | HotplugCallback(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. */ |
787 | static int SDLCALL |
788 | HotplugThread(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 | |
799 | static void |
800 | PULSEAUDIO_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 | |
809 | static void |
810 | PULSEAUDIO_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 | |
825 | static int |
826 | PULSEAUDIO_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 | |
853 | AudioBootStrap 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 | |