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) |
39 | typedef void (*pa_operation_notify_cb_t) (pa_operation *o, void *userdata); |
40 | #endif |
41 | |
42 | typedef 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 |
49 | static bool include_monitors = false; |
50 | |
51 | static pa_threaded_mainloop *pulseaudio_threaded_mainloop = NULL; |
52 | static pa_context *pulseaudio_context = NULL; |
53 | static SDL_Thread *pulseaudio_hotplug_thread = NULL; |
54 | static 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. |
59 | static char *default_sink_path = NULL; |
60 | static char *default_source_path = NULL; |
61 | static bool default_sink_changed = false; |
62 | static bool default_source_changed = false; |
63 | |
64 | |
65 | static const char *(*PULSEAUDIO_pa_get_library_version)(void); |
66 | static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto)( |
67 | pa_channel_map *, unsigned, pa_channel_map_def_t); |
68 | static const char *(*PULSEAUDIO_pa_strerror)(int); |
69 | static pa_proplist *(*PULSEAUDIO_pa_proplist_new)(void); |
70 | static void (*PULSEAUDIO_pa_proplist_free)(pa_proplist *); |
71 | static int (*PULSEAUDIO_pa_proplist_sets)(pa_proplist *, const char *, const char *); |
72 | |
73 | static pa_threaded_mainloop *(*PULSEAUDIO_pa_threaded_mainloop_new)(void); |
74 | static void (*PULSEAUDIO_pa_threaded_mainloop_set_name)(pa_threaded_mainloop *, const char *); |
75 | static pa_mainloop_api *(*PULSEAUDIO_pa_threaded_mainloop_get_api)(pa_threaded_mainloop *); |
76 | static int (*PULSEAUDIO_pa_threaded_mainloop_start)(pa_threaded_mainloop *); |
77 | static void (*PULSEAUDIO_pa_threaded_mainloop_stop)(pa_threaded_mainloop *); |
78 | static void (*PULSEAUDIO_pa_threaded_mainloop_lock)(pa_threaded_mainloop *); |
79 | static void (*PULSEAUDIO_pa_threaded_mainloop_unlock)(pa_threaded_mainloop *); |
80 | static void (*PULSEAUDIO_pa_threaded_mainloop_wait)(pa_threaded_mainloop *); |
81 | static void (*PULSEAUDIO_pa_threaded_mainloop_signal)(pa_threaded_mainloop *, int); |
82 | static void (*PULSEAUDIO_pa_threaded_mainloop_free)(pa_threaded_mainloop *); |
83 | |
84 | static pa_operation_state_t (*PULSEAUDIO_pa_operation_get_state)( |
85 | const pa_operation *); |
86 | static void (*PULSEAUDIO_pa_operation_set_state_callback)(pa_operation *, pa_operation_notify_cb_t, void *); |
87 | static void (*PULSEAUDIO_pa_operation_cancel)(pa_operation *); |
88 | static void (*PULSEAUDIO_pa_operation_unref)(pa_operation *); |
89 | |
90 | static pa_context *(*PULSEAUDIO_pa_context_new_with_proplist)(pa_mainloop_api *, |
91 | const char *, |
92 | const pa_proplist *); |
93 | static void (*PULSEAUDIO_pa_context_set_state_callback)(pa_context *, pa_context_notify_cb_t, void *); |
94 | static int (*PULSEAUDIO_pa_context_connect)(pa_context *, const char *, |
95 | pa_context_flags_t, const pa_spawn_api *); |
96 | static pa_operation *(*PULSEAUDIO_pa_context_get_sink_info_list)(pa_context *, pa_sink_info_cb_t, void *); |
97 | static pa_operation *(*PULSEAUDIO_pa_context_get_source_info_list)(pa_context *, pa_source_info_cb_t, void *); |
98 | static pa_operation *(*PULSEAUDIO_pa_context_get_sink_info_by_index)(pa_context *, uint32_t, pa_sink_info_cb_t, void *); |
99 | static pa_operation *(*PULSEAUDIO_pa_context_get_source_info_by_index)(pa_context *, uint32_t, pa_source_info_cb_t, void *); |
100 | static pa_context_state_t (*PULSEAUDIO_pa_context_get_state)(const pa_context *); |
101 | static pa_operation *(*PULSEAUDIO_pa_context_subscribe)(pa_context *, pa_subscription_mask_t, pa_context_success_cb_t, void *); |
102 | static void (*PULSEAUDIO_pa_context_set_subscribe_callback)(pa_context *, pa_context_subscribe_cb_t, void *); |
103 | static void (*PULSEAUDIO_pa_context_disconnect)(pa_context *); |
104 | static void (*PULSEAUDIO_pa_context_unref)(pa_context *); |
105 | |
106 | static pa_stream *(*PULSEAUDIO_pa_stream_new)(pa_context *, const char *, |
107 | const pa_sample_spec *, const pa_channel_map *); |
108 | static void (*PULSEAUDIO_pa_stream_set_state_callback)(pa_stream *, pa_stream_notify_cb_t, void *); |
109 | static int (*PULSEAUDIO_pa_stream_connect_playback)(pa_stream *, const char *, |
110 | const pa_buffer_attr *, pa_stream_flags_t, const pa_cvolume *, pa_stream *); |
111 | static int (*PULSEAUDIO_pa_stream_connect_record)(pa_stream *, const char *, |
112 | const pa_buffer_attr *, pa_stream_flags_t); |
113 | static const pa_buffer_attr *(*PULSEAUDIO_pa_stream_get_buffer_attr)(pa_stream *); |
114 | static pa_stream_state_t (*PULSEAUDIO_pa_stream_get_state)(const pa_stream *); |
115 | static size_t (*PULSEAUDIO_pa_stream_writable_size)(const pa_stream *); |
116 | static size_t (*PULSEAUDIO_pa_stream_readable_size)(const pa_stream *); |
117 | static int (*PULSEAUDIO_pa_stream_write)(pa_stream *, const void *, size_t, |
118 | pa_free_cb_t, int64_t, pa_seek_mode_t); |
119 | static int (*PULSEAUDIO_pa_stream_begin_write)(pa_stream *, void **, size_t *); |
120 | static pa_operation *(*PULSEAUDIO_pa_stream_drain)(pa_stream *, |
121 | pa_stream_success_cb_t, void *); |
122 | static int (*PULSEAUDIO_pa_stream_peek)(pa_stream *, const void **, size_t *); |
123 | static int (*PULSEAUDIO_pa_stream_drop)(pa_stream *); |
124 | static pa_operation *(*PULSEAUDIO_pa_stream_flush)(pa_stream *, |
125 | pa_stream_success_cb_t, void *); |
126 | static int (*PULSEAUDIO_pa_stream_disconnect)(pa_stream *); |
127 | static void (*PULSEAUDIO_pa_stream_unref)(pa_stream *); |
128 | static void (*PULSEAUDIO_pa_stream_set_write_callback)(pa_stream *, pa_stream_request_cb_t, void *); |
129 | static void (*PULSEAUDIO_pa_stream_set_read_callback)(pa_stream *, pa_stream_request_cb_t, void *); |
130 | static pa_operation *(*PULSEAUDIO_pa_context_get_server_info)(pa_context *, pa_server_info_cb_t, void *); |
131 | |
132 | static bool load_pulseaudio_syms(void); |
133 | |
134 | #ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC |
135 | |
136 | static const char *pulseaudio_library = SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC; |
137 | static SDL_SharedObject *pulseaudio_handle = NULL; |
138 | |
139 | static 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 | |
155 | static void UnloadPulseAudioLibrary(void) |
156 | { |
157 | if (pulseaudio_handle) { |
158 | SDL_UnloadObject(pulseaudio_handle); |
159 | pulseaudio_handle = NULL; |
160 | } |
161 | } |
162 | |
163 | static 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 | |
185 | static void UnloadPulseAudioLibrary(void) |
186 | { |
187 | } |
188 | |
189 | static bool LoadPulseAudioLibrary(void) |
190 | { |
191 | load_pulseaudio_syms(); |
192 | return true; |
193 | } |
194 | |
195 | #endif // SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC |
196 | |
197 | static 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 | |
267 | static const char *getAppName(void) |
268 | { |
269 | return SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING); |
270 | } |
271 | |
272 | static 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. */ |
279 | static 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 | |
298 | static 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 | |
314 | static 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 | |
319 | static 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 | |
392 | failed: |
393 | PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); |
394 | DisconnectFromPulseServer(); |
395 | return false; |
396 | } |
397 | |
398 | static 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 |
407 | static 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 | |
432 | static 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 | |
455 | static 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 | |
471 | static 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 | |
477 | static 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 | |
518 | static 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 | |
542 | static 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 | |
575 | static 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 | |
593 | static 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 | |
598 | static 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 | |
746 | static 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 | |
768 | static 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. |
788 | static 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. |
797 | static 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 | |
806 | static 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 | |
831 | static 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 | |
838 | static 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. |
846 | static 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 | |
875 | static 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. |
892 | static 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 | |
941 | static 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 | |
971 | static 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 | |
978 | static 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 | |
990 | static 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 | |
1004 | static 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 | |
1033 | AudioBootStrap PULSEAUDIO_bootstrap = { |
1034 | "pulseaudio" , "PulseAudio" , PULSEAUDIO_Init, false, false |
1035 | }; |
1036 | |
1037 | #endif // SDL_AUDIO_DRIVER_PULSEAUDIO |
1038 | |