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 #include "../../SDL_internal.h"
23 #include "SDL_hints.h"
24 
25 #if SDL_AUDIO_DRIVER_PIPEWIRE
26 
27 #include "SDL_audio.h"
28 #include "SDL_loadso.h"
29 #include "SDL_pipewire.h"
30 
31 #include <pipewire/extensions/metadata.h>
32 #include <spa/param/audio/format-utils.h>
33 
34 /* Older versions of Pipewire may not define this, but it's safe to pass at
35  * runtime even if older installations don't recognize it.
36  * Taken from src/pipewire/keys.h
37  */
38 #ifndef PW_KEY_NODE_RATE
39 #define PW_KEY_NODE_RATE "node.rate"
40 #endif
41 
42 /*
43  * These seem to be sane limits as Pipewire
44  * uses them in several of it's own modules.
45  *
46  * NOTE: 8192 is a hard upper limit in Pipewire and
47  * increasing this value can lead to buffer overflows.
48  */
49 #define PW_MIN_SAMPLES     32   /* About 0.67ms at 48kHz */
50 #define PW_MAX_SAMPLES     8192 /* About 170.6ms at 48kHz */
51 #define PW_BASE_CLOCK_RATE 48000
52 
53 #define PW_POD_BUFFER_LENGTH         1024
54 #define PW_THREAD_NAME_BUFFER_LENGTH 128
55 
56 #define PW_ID_TO_HANDLE(x) (void *)((uintptr_t)x)
57 #define PW_HANDLE_TO_ID(x) (uint32_t)((uintptr_t)x)
58 
59 static SDL_bool pipewire_initialized = SDL_FALSE;
60 
61 /* Pipewire entry points */
62 static void (*PIPEWIRE_pw_init)(int *, char **);
63 static void (*PIPEWIRE_pw_deinit)(void);
64 static struct pw_thread_loop *(*PIPEWIRE_pw_thread_loop_new)(const char *, const struct spa_dict *);
65 static void (*PIPEWIRE_pw_thread_loop_destroy)(struct pw_thread_loop *);
66 static void (*PIPEWIRE_pw_thread_loop_stop)(struct pw_thread_loop *);
67 static struct pw_loop *(*PIPEWIRE_pw_thread_loop_get_loop)(struct pw_thread_loop *);
68 static void (*PIPEWIRE_pw_thread_loop_lock)(struct pw_thread_loop *);
69 static void (*PIPEWIRE_pw_thread_loop_unlock)(struct pw_thread_loop *);
70 static void (*PIPEWIRE_pw_thread_loop_signal)(struct pw_thread_loop *, bool);
71 static void (*PIPEWIRE_pw_thread_loop_wait)(struct pw_thread_loop *);
72 static int (*PIPEWIRE_pw_thread_loop_start)(struct pw_thread_loop *);
73 static struct pw_context *(*PIPEWIRE_pw_context_new)(struct pw_loop *, struct pw_properties *, size_t);
74 static void (*PIPEWIRE_pw_context_destroy)(struct pw_context *);
75 static struct pw_core *(*PIPEWIRE_pw_context_connect)(struct pw_context *, struct pw_properties *, size_t);
76 static void (*PIPEWIRE_pw_proxy_add_object_listener)(struct pw_proxy *, struct spa_hook *, const void *, void *);
77 static void *(*PIPEWIRE_pw_proxy_get_user_data)(struct pw_proxy *);
78 static void (*PIPEWIRE_pw_proxy_destroy)(struct pw_proxy *);
79 static int (*PIPEWIRE_pw_core_disconnect)(struct pw_core *);
80 static struct pw_stream *(*PIPEWIRE_pw_stream_new_simple)(struct pw_loop *, const char *, struct pw_properties *,
81                                                           const struct pw_stream_events *, void *);
82 static void (*PIPEWIRE_pw_stream_destroy)(struct pw_stream *);
83 static int (*PIPEWIRE_pw_stream_connect)(struct pw_stream *, enum pw_direction, uint32_t, enum pw_stream_flags,
84                                          const struct spa_pod **, uint32_t);
85 static enum pw_stream_state (*PIPEWIRE_pw_stream_get_state)(struct pw_stream *stream, const char **error);
86 static struct pw_buffer *(*PIPEWIRE_pw_stream_dequeue_buffer)(struct pw_stream *);
87 static int (*PIPEWIRE_pw_stream_queue_buffer)(struct pw_stream *, struct pw_buffer *);
88 static struct pw_properties *(*PIPEWIRE_pw_properties_new)(const char *, ...)SPA_SENTINEL;
89 static void (*PIPEWIRE_pw_properties_free)(struct pw_properties *);
90 static int (*PIPEWIRE_pw_properties_set)(struct pw_properties *, const char *, const char *);
91 static int (*PIPEWIRE_pw_properties_setf)(struct pw_properties *, const char *, const char *, ...) SPA_PRINTF_FUNC(3, 4);
92 
93 #ifdef SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC
94 
95 static const char *pipewire_library = SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC;
96 static void *      pipewire_handle  = NULL;
97 
98 static int
pipewire_dlsym(const char * fn,void ** addr)99 pipewire_dlsym(const char *fn, void **addr)
100 {
101     *addr = SDL_LoadFunction(pipewire_handle, fn);
102     if (*addr == NULL) {
103         /* Don't call SDL_SetError(): SDL_LoadFunction already did. */
104         return 0;
105     }
106 
107     return 1;
108 }
109 
110 #define SDL_PIPEWIRE_SYM(x)                                    \
111     if (!pipewire_dlsym(#x, (void **)(char *)&PIPEWIRE_##x)) { \
112         return -1;                                             \
113     }
114 
115 static int
load_pipewire_library()116 load_pipewire_library()
117 {
118     if ((pipewire_handle = SDL_LoadObject(pipewire_library))) {
119         return 0;
120     }
121 
122     return -1;
123 }
124 
125 static void
unload_pipewire_library()126 unload_pipewire_library()
127 {
128     if (pipewire_handle) {
129         SDL_UnloadObject(pipewire_handle);
130         pipewire_handle = NULL;
131     }
132 }
133 
134 #else
135 
136 #define SDL_PIPEWIRE_SYM(x) PIPEWIRE_##x = x
137 
138 static int
load_pipewire_library()139 load_pipewire_library()
140 {
141     return 0;
142 }
143 
144 static void
unload_pipewire_library()145 unload_pipewire_library()
146 { /* Nothing to do */
147 }
148 
149 #endif /* SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC */
150 
151 static int
load_pipewire_syms()152 load_pipewire_syms()
153 {
154     SDL_PIPEWIRE_SYM(pw_init);
155     SDL_PIPEWIRE_SYM(pw_deinit);
156     SDL_PIPEWIRE_SYM(pw_thread_loop_new);
157     SDL_PIPEWIRE_SYM(pw_thread_loop_destroy);
158     SDL_PIPEWIRE_SYM(pw_thread_loop_stop);
159     SDL_PIPEWIRE_SYM(pw_thread_loop_get_loop);
160     SDL_PIPEWIRE_SYM(pw_thread_loop_lock);
161     SDL_PIPEWIRE_SYM(pw_thread_loop_unlock);
162     SDL_PIPEWIRE_SYM(pw_thread_loop_signal);
163     SDL_PIPEWIRE_SYM(pw_thread_loop_wait);
164     SDL_PIPEWIRE_SYM(pw_thread_loop_start);
165     SDL_PIPEWIRE_SYM(pw_context_new);
166     SDL_PIPEWIRE_SYM(pw_context_destroy);
167     SDL_PIPEWIRE_SYM(pw_context_connect);
168     SDL_PIPEWIRE_SYM(pw_proxy_add_object_listener);
169     SDL_PIPEWIRE_SYM(pw_proxy_get_user_data);
170     SDL_PIPEWIRE_SYM(pw_proxy_destroy);
171     SDL_PIPEWIRE_SYM(pw_core_disconnect);
172     SDL_PIPEWIRE_SYM(pw_stream_new_simple);
173     SDL_PIPEWIRE_SYM(pw_stream_destroy);
174     SDL_PIPEWIRE_SYM(pw_stream_connect);
175     SDL_PIPEWIRE_SYM(pw_stream_get_state);
176     SDL_PIPEWIRE_SYM(pw_stream_dequeue_buffer);
177     SDL_PIPEWIRE_SYM(pw_stream_queue_buffer);
178     SDL_PIPEWIRE_SYM(pw_properties_new);
179     SDL_PIPEWIRE_SYM(pw_properties_free);
180     SDL_PIPEWIRE_SYM(pw_properties_set);
181     SDL_PIPEWIRE_SYM(pw_properties_setf);
182 
183     return 0;
184 }
185 
186 static int
init_pipewire_library()187 init_pipewire_library()
188 {
189     if (!load_pipewire_library()) {
190         if (!load_pipewire_syms()) {
191             PIPEWIRE_pw_init(NULL, NULL);
192             return 0;
193         }
194     }
195 
196     return -1;
197 }
198 
199 static void
deinit_pipewire_library()200 deinit_pipewire_library()
201 {
202     PIPEWIRE_pw_deinit();
203     unload_pipewire_library();
204 }
205 
206 /* A generic Pipewire node object used for enumeration. */
207 struct node_object
208 {
209     struct spa_list link;
210 
211     Uint32 id;
212     int    seq;
213 
214     /*
215      * NOTE: If used, this is *must* be allocated with SDL_malloc() or similar
216      * as SDL_free() will be called on it when the node_object is destroyed.
217      *
218      * If ownership of the referenced memory is transferred, this must be set
219      * to NULL or the memory will be freed when the node_object is destroyed.
220      */
221     void *userdata;
222 
223     struct pw_proxy *proxy;
224     struct spa_hook  node_listener;
225     struct spa_hook  core_listener;
226 };
227 
228 /* A sink/source node used for stream I/O. */
229 struct io_node
230 {
231     struct spa_list link;
232 
233     Uint32        id;
234     SDL_bool      is_capture;
235     SDL_AudioSpec spec;
236 
237     char name[];
238 };
239 
240 /* The global hotplug thread and associated objects. */
241 static struct pw_thread_loop *hotplug_loop;
242 static struct pw_core *       hotplug_core;
243 static struct pw_context *    hotplug_context;
244 static struct pw_registry *   hotplug_registry;
245 static struct spa_hook        hotplug_registry_listener;
246 static struct spa_hook        hotplug_core_listener;
247 static struct spa_list        hotplug_pending_list;
248 static struct spa_list        hotplug_io_list;
249 static int                    hotplug_init_seq_val;
250 static SDL_atomic_t           hotplug_init_complete;
251 static SDL_atomic_t           hotplug_events_enabled;
252 
253 static Uint32 pipewire_default_sink_id   = SPA_ID_INVALID;
254 static Uint32 pipewire_default_source_id = SPA_ID_INVALID;
255 
256 /* The active node list */
257 static SDL_bool
io_list_check_add(struct io_node * node)258 io_list_check_add(struct io_node *node)
259 {
260     struct io_node *n;
261     SDL_bool        ret = SDL_TRUE;
262 
263     PIPEWIRE_pw_thread_loop_lock(hotplug_loop);
264 
265     /* See if the node is already in the list */
266     spa_list_for_each (n, &hotplug_io_list, link) {
267         if (n->id == node->id) {
268             ret = SDL_FALSE;
269             goto dup_found;
270         }
271     }
272 
273     /* Add to the list if the node doesn't already exist */
274     spa_list_append(&hotplug_io_list, &node->link);
275 
276     if (SDL_AtomicGet(&hotplug_events_enabled)) {
277         SDL_AddAudioDevice(node->is_capture, node->name, &node->spec, PW_ID_TO_HANDLE(node->id));
278     }
279 
280 dup_found:
281 
282     PIPEWIRE_pw_thread_loop_unlock(hotplug_loop);
283 
284     return ret;
285 }
286 
287 static void
io_list_remove(Uint32 id)288 io_list_remove(Uint32 id)
289 {
290     struct io_node *n, *temp;
291 
292     PIPEWIRE_pw_thread_loop_lock(hotplug_loop);
293 
294     /* Find and remove the node from the list */
295     spa_list_for_each_safe (n, temp, &hotplug_io_list, link) {
296         if (n->id == id) {
297             spa_list_remove(&n->link);
298 
299             if (SDL_AtomicGet(&hotplug_events_enabled)) {
300                 SDL_RemoveAudioDevice(n->is_capture, PW_ID_TO_HANDLE(id));
301             }
302 
303             SDL_free(n);
304 
305             break;
306         }
307     }
308 
309     PIPEWIRE_pw_thread_loop_unlock(hotplug_loop);
310 }
311 
312 static void
io_list_sort()313 io_list_sort()
314 {
315     struct io_node *default_sink = NULL, *default_source = NULL;
316     struct io_node *n, *temp;
317 
318     PIPEWIRE_pw_thread_loop_lock(hotplug_loop);
319 
320     /* Find and move the default nodes to the beginning of the list */
321     spa_list_for_each_safe (n, temp, &hotplug_io_list, link) {
322         if (n->id == pipewire_default_sink_id) {
323             default_sink = n;
324             spa_list_remove(&n->link);
325         } else if (n->id == pipewire_default_source_id) {
326             default_source = n;
327             spa_list_remove(&n->link);
328         }
329     }
330 
331     if (default_source) {
332         spa_list_prepend(&hotplug_io_list, &default_source->link);
333     }
334 
335     if (default_sink) {
336         spa_list_prepend(&hotplug_io_list, &default_sink->link);
337     }
338 
339     PIPEWIRE_pw_thread_loop_unlock(hotplug_loop);
340 }
341 
342 static void
io_list_clear()343 io_list_clear()
344 {
345     struct io_node *n, *temp;
346 
347     spa_list_for_each_safe (n, temp, &hotplug_io_list, link) {
348         spa_list_remove(&n->link);
349         SDL_free(n);
350     }
351 }
352 
353 static void
node_object_destroy(struct node_object * node)354 node_object_destroy(struct node_object *node)
355 {
356     SDL_assert(node);
357 
358     spa_list_remove(&node->link);
359     spa_hook_remove(&node->node_listener);
360     spa_hook_remove(&node->core_listener);
361     SDL_free(node->userdata);
362     PIPEWIRE_pw_proxy_destroy(node->proxy);
363 }
364 
365 /* The pending node list */
366 static void
pending_list_add(struct node_object * node)367 pending_list_add(struct node_object *node)
368 {
369     SDL_assert(node);
370     spa_list_append(&hotplug_pending_list, &node->link);
371 }
372 
373 static void
pending_list_remove(Uint32 id)374 pending_list_remove(Uint32 id)
375 {
376     struct node_object *node, *temp;
377 
378     spa_list_for_each_safe (node, temp, &hotplug_pending_list, link) {
379         if (node->id == id) {
380             node_object_destroy(node);
381         }
382     }
383 }
384 
385 static void
pending_list_clear()386 pending_list_clear()
387 {
388     struct node_object *node, *temp;
389 
390     spa_list_for_each_safe (node, temp, &hotplug_pending_list, link) {
391         node_object_destroy(node);
392     }
393 }
394 
395 static void *
node_object_new(Uint32 id,const char * type,Uint32 version,const void * funcs,const struct pw_core_events * core_events)396 node_object_new(Uint32 id, const char *type, Uint32 version, const void *funcs, const struct pw_core_events *core_events)
397 {
398     struct pw_proxy *   proxy;
399     struct node_object *node;
400 
401     /* Create the proxy object */
402     proxy = pw_registry_bind(hotplug_registry, id, type, version, sizeof(struct node_object));
403     if (proxy == NULL) {
404         SDL_SetError("Pipewire: Failed to create proxy object (%i)", errno);
405         return NULL;
406     }
407 
408     node = PIPEWIRE_pw_proxy_get_user_data(proxy);
409     SDL_zerop(node);
410 
411     node->id    = id;
412     node->proxy = proxy;
413 
414     /* Add the callbacks */
415     pw_core_add_listener(hotplug_core, &node->core_listener, core_events, node);
416     PIPEWIRE_pw_proxy_add_object_listener(node->proxy, &node->node_listener, funcs, node);
417 
418     /* Add the node to the active list */
419     pending_list_add(node);
420 
421     return node;
422 }
423 
424 /* Core sync points */
425 static void
core_events_hotplug_init_callback(void * object,uint32_t id,int seq)426 core_events_hotplug_init_callback(void *object, uint32_t id, int seq)
427 {
428     if (id == PW_ID_CORE && seq == hotplug_init_seq_val) {
429         /* This core listener is no longer needed. */
430         spa_hook_remove(&hotplug_core_listener);
431 
432         /* Signal that the initial I/O list is populated */
433         SDL_AtomicSet(&hotplug_init_complete, 1);
434         PIPEWIRE_pw_thread_loop_signal(hotplug_loop, false);
435     }
436 }
437 
438 static void
core_events_interface_callback(void * object,uint32_t id,int seq)439 core_events_interface_callback(void *object, uint32_t id, int seq)
440 {
441     struct node_object *node = object;
442     struct io_node *    io   = node->userdata;
443 
444     if (id == PW_ID_CORE && seq == node->seq) {
445         /*
446          * Move the I/O node to the connected list.
447          * On success, the list owns the I/O node object.
448          */
449         if (io_list_check_add(io)) {
450             node->userdata = NULL;
451         }
452 
453         node_object_destroy(node);
454     }
455 }
456 
457 static void
core_events_metadata_callback(void * object,uint32_t id,int seq)458 core_events_metadata_callback(void *object, uint32_t id, int seq)
459 {
460     struct node_object *node = object;
461 
462     if (id == PW_ID_CORE && seq == node->seq) {
463         node_object_destroy(node);
464     }
465 }
466 
467 static const struct pw_core_events hotplug_init_core_events = { PW_VERSION_CORE_EVENTS, .done = core_events_hotplug_init_callback };
468 static const struct pw_core_events interface_core_events    = { PW_VERSION_CORE_EVENTS, .done = core_events_interface_callback };
469 static const struct pw_core_events metadata_core_events     = { PW_VERSION_CORE_EVENTS, .done = core_events_metadata_callback };
470 
471 static void
hotplug_core_sync(struct node_object * node)472 hotplug_core_sync(struct node_object *node)
473 {
474     /*
475      * Node sync events *must* come before the hotplug init sync events or the initial
476      * I/O list will be incomplete when the main hotplug sync point is hit.
477      */
478     if (node) {
479         node->seq = pw_core_sync(hotplug_core, PW_ID_CORE, node->seq);
480     }
481 
482     if (!SDL_AtomicGet(&hotplug_init_complete)) {
483         hotplug_init_seq_val = pw_core_sync(hotplug_core, PW_ID_CORE, hotplug_init_seq_val);
484     }
485 }
486 
487 /* Helpers for retrieving values from params */
488 static SDL_bool
get_range_param(const struct spa_pod * param,Uint32 key,int * def,int * min,int * max)489 get_range_param(const struct spa_pod *param, Uint32 key, int *def, int *min, int *max)
490 {
491     const struct spa_pod_prop *prop;
492     struct spa_pod *           value;
493     Uint32                     n_values, choice;
494 
495     prop = spa_pod_find_prop(param, NULL, key);
496 
497     if (prop && prop->value.type == SPA_TYPE_Choice) {
498         value = spa_pod_get_values(&prop->value, &n_values, &choice);
499 
500         if (n_values == 3 && choice == SPA_CHOICE_Range) {
501             Uint32 *v = SPA_POD_BODY(value);
502 
503             if (v) {
504                 if (def) {
505                     *def = (int)v[0];
506                 }
507                 if (min) {
508                     *min = (int)v[1];
509                 }
510                 if (max) {
511                     *max = (int)v[2];
512                 }
513 
514                 return SDL_TRUE;
515             }
516         }
517     }
518 
519     return SDL_FALSE;
520 }
521 
522 static SDL_bool
get_int_param(const struct spa_pod * param,Uint32 key,int * val)523 get_int_param(const struct spa_pod *param, Uint32 key, int *val)
524 {
525     const struct spa_pod_prop *prop;
526     Sint32                     v;
527 
528     prop = spa_pod_find_prop(param, NULL, key);
529 
530     if (prop && spa_pod_get_int(&prop->value, &v) == 0) {
531         if (val) {
532             *val = (int)v;
533         }
534 
535         return SDL_TRUE;
536     }
537 
538     return SDL_FALSE;
539 }
540 
541 /* Interface node callbacks */
542 static void
node_event_info(void * object,const struct pw_node_info * info)543 node_event_info(void *object, const struct pw_node_info *info)
544 {
545     struct node_object *node = object;
546     struct io_node *    io   = node->userdata;
547     const char *        prop_val;
548     Uint32              i;
549 
550     if (info) {
551         prop_val = spa_dict_lookup(info->props, PW_KEY_AUDIO_CHANNELS);
552         if (prop_val) {
553             io->spec.channels = (Uint8)SDL_atoi(prop_val);
554         }
555 
556         /* Need to parse the parameters to get the sample rate */
557         for (i = 0; i < info->n_params; ++i) {
558             pw_node_enum_params(node->proxy, 0, info->params[i].id, 0, 0, NULL);
559         }
560 
561         hotplug_core_sync(node);
562     }
563 }
564 
565 static void
node_event_param(void * object,int seq,uint32_t id,uint32_t index,uint32_t next,const struct spa_pod * param)566 node_event_param(void *object, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param)
567 {
568     struct node_object *node = object;
569     struct io_node *    io   = node->userdata;
570 
571     /* Get the default frequency */
572     if (io->spec.freq == 0) {
573         get_range_param(param, SPA_FORMAT_AUDIO_rate, &io->spec.freq, NULL, NULL);
574     }
575 
576     /*
577      * The channel count should have come from the node properties,
578      * but it is stored here as well. If one failed, try the other.
579      */
580     if (io->spec.channels == 0) {
581         int channels;
582         if (get_int_param(param, SPA_FORMAT_AUDIO_channels, &channels)) {
583             io->spec.channels = (Uint8)channels;
584         }
585     }
586 }
587 
588 static const struct pw_node_events interface_node_events = { PW_VERSION_NODE_EVENTS, .info = node_event_info,
589                                                              .param = node_event_param };
590 
591 /* Metadata node callback */
592 static int
metadata_property(void * object,Uint32 subject,const char * key,const char * type,const char * value)593 metadata_property(void *object, Uint32 subject, const char *key, const char *type, const char *value)
594 {
595     if (subject == PW_ID_CORE && key != NULL && value != NULL) {
596         Uint32 val = SDL_atoi(value);
597 
598         if (!SDL_strcmp(key, "default.audio.sink")) {
599             pipewire_default_sink_id = val;
600         } else if (!SDL_strcmp(key, "default.audio.source")) {
601             pipewire_default_source_id = val;
602         }
603     }
604 
605     return 0;
606 }
607 
608 static const struct pw_metadata_events metadata_node_events = { PW_VERSION_METADATA_EVENTS, .property = metadata_property };
609 
610 /* Global registry callbacks */
611 static void
registry_event_global_callback(void * object,uint32_t id,uint32_t permissions,const char * type,uint32_t version,const struct spa_dict * props)612 registry_event_global_callback(void *object, uint32_t id, uint32_t permissions, const char *type, uint32_t version,
613                                const struct spa_dict *props)
614 {
615     struct node_object *node;
616 
617     /* We're only interested in interface and metadata nodes. */
618     if (!SDL_strcmp(type, PW_TYPE_INTERFACE_Node)) {
619         const char *media_class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
620 
621         if (media_class) {
622             const char *    node_desc;
623             struct io_node *io;
624             SDL_bool        is_capture;
625             int             str_buffer_len;
626 
627             /* Just want sink and capture */
628             if (!SDL_strcasecmp(media_class, "Audio/Sink")) {
629                 is_capture = SDL_FALSE;
630             } else if (!SDL_strcasecmp(media_class, "Audio/Source")) {
631                 is_capture = SDL_TRUE;
632             } else {
633                 return;
634             }
635 
636             node_desc = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION);
637 
638             if (node_desc) {
639                 node = node_object_new(id, type, version, &interface_node_events, &interface_core_events);
640                 if (node == NULL) {
641                     SDL_SetError("Pipewire: Failed to allocate interface node");
642                     return;
643                 }
644 
645                 /* Allocate and initialize the I/O node information struct */
646                 str_buffer_len = SDL_strlen(node_desc) + 1;
647                 node->userdata = io = SDL_calloc(1, sizeof(struct io_node) + str_buffer_len);
648                 if (io == NULL) {
649                     node_object_destroy(node);
650                     SDL_OutOfMemory();
651                     return;
652                 }
653 
654                 /* Begin setting the node properties */
655                 io->id          = id;
656                 io->is_capture  = is_capture;
657                 io->spec.format = AUDIO_F32; /* Pipewire uses floats internally, other formats require conversion. */
658                 SDL_strlcpy(io->name, node_desc, str_buffer_len);
659 
660                 /* Update sync points */
661                 hotplug_core_sync(node);
662             }
663         }
664     } else if (!SDL_strcmp(type, PW_TYPE_INTERFACE_Metadata)) {
665         node = node_object_new(id, type, version, &metadata_node_events, &metadata_core_events);
666         if (node == NULL) {
667             SDL_SetError("Pipewire: Failed to allocate metadata node");
668             return;
669         }
670 
671         /* Update sync points */
672         hotplug_core_sync(node);
673     }
674 }
675 
676 static void
registry_event_remove_callback(void * object,uint32_t id)677 registry_event_remove_callback(void *object, uint32_t id)
678 {
679     io_list_remove(id);
680     pending_list_remove(id);
681 }
682 
683 static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global_callback,
684                                                            .global_remove = registry_event_remove_callback };
685 
686 /* The hotplug thread */
687 static int
hotplug_loop_init()688 hotplug_loop_init()
689 {
690     int res;
691 
692     spa_list_init(&hotplug_pending_list);
693     spa_list_init(&hotplug_io_list);
694 
695     hotplug_loop = PIPEWIRE_pw_thread_loop_new("SDLAudioHotplug", NULL);
696     if (hotplug_loop == NULL) {
697         return SDL_SetError("Pipewire: Failed to create hotplug detection loop (%i)", errno);
698     }
699 
700     hotplug_context = PIPEWIRE_pw_context_new(PIPEWIRE_pw_thread_loop_get_loop(hotplug_loop), NULL, 0);
701     if (hotplug_context == NULL) {
702         return SDL_SetError("Pipewire: Failed to create hotplug detection context (%i)", errno);
703     }
704 
705     hotplug_core = PIPEWIRE_pw_context_connect(hotplug_context, NULL, 0);
706     if (hotplug_core == NULL) {
707         return SDL_SetError("Pipewire: Failed to connect hotplug detection context (%i)", errno);
708     }
709 
710     hotplug_registry = pw_core_get_registry(hotplug_core, PW_VERSION_REGISTRY, 0);
711     if (hotplug_registry == NULL) {
712         return SDL_SetError("Pipewire: Failed to acquire hotplug detection registry (%i)", errno);
713     }
714 
715     spa_zero(hotplug_registry_listener);
716     pw_registry_add_listener(hotplug_registry, &hotplug_registry_listener, &registry_events, NULL);
717 
718     spa_zero(hotplug_core_listener);
719     pw_core_add_listener(hotplug_core, &hotplug_core_listener, &hotplug_init_core_events, NULL);
720 
721     hotplug_init_seq_val = pw_core_sync(hotplug_core, PW_ID_CORE, 0);
722 
723     res = PIPEWIRE_pw_thread_loop_start(hotplug_loop);
724     if (res != 0) {
725         return SDL_SetError("Pipewire: Failed to start hotplug detection loop");
726     }
727 
728     return 0;
729 }
730 
731 static void
hotplug_loop_destroy()732 hotplug_loop_destroy()
733 {
734     if (hotplug_loop) {
735         PIPEWIRE_pw_thread_loop_stop(hotplug_loop);
736     }
737 
738     pending_list_clear();
739     io_list_clear();
740 
741     SDL_AtomicSet(&hotplug_init_complete, 0);
742     SDL_AtomicSet(&hotplug_events_enabled, 0);
743 
744     if (hotplug_registry) {
745         PIPEWIRE_pw_proxy_destroy((struct pw_proxy *)hotplug_registry);
746     }
747 
748     if (hotplug_core) {
749         PIPEWIRE_pw_core_disconnect(hotplug_core);
750     }
751 
752     if (hotplug_context) {
753         PIPEWIRE_pw_context_destroy(hotplug_context);
754     }
755 
756     if (hotplug_loop) {
757         PIPEWIRE_pw_thread_loop_destroy(hotplug_loop);
758     }
759 }
760 
761 static void
PIPEWIRE_DetectDevices()762 PIPEWIRE_DetectDevices()
763 {
764     struct io_node *io;
765 
766     PIPEWIRE_pw_thread_loop_lock(hotplug_loop);
767 
768     /* Wait until the initial registry enumeration is complete */
769     if (!SDL_AtomicGet(&hotplug_init_complete)) {
770         PIPEWIRE_pw_thread_loop_wait(hotplug_loop);
771     }
772 
773     /* Sort the I/O list so the default source/sink are listed first */
774     io_list_sort();
775 
776     spa_list_for_each (io, &hotplug_io_list, link) {
777         SDL_AddAudioDevice(io->is_capture, io->name, &io->spec, PW_ID_TO_HANDLE(io->id));
778     }
779 
780     SDL_AtomicSet(&hotplug_events_enabled, 1);
781 
782     PIPEWIRE_pw_thread_loop_unlock(hotplug_loop);
783 }
784 
785 /* Channel maps that match the order in SDL_Audio.h */
786 static const enum spa_audio_channel PIPEWIRE_channel_map_1[] = { SPA_AUDIO_CHANNEL_MONO };
787 static const enum spa_audio_channel PIPEWIRE_channel_map_2[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR };
788 static const enum spa_audio_channel PIPEWIRE_channel_map_3[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_LFE };
789 static const enum spa_audio_channel PIPEWIRE_channel_map_4[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL,
790                                                                  SPA_AUDIO_CHANNEL_RR };
791 static const enum spa_audio_channel PIPEWIRE_channel_map_5[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC,
792                                                                  SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR };
793 static const enum spa_audio_channel PIPEWIRE_channel_map_6[] = { SPA_AUDIO_CHANNEL_FL,  SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC,
794                                                                  SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR };
795 static const enum spa_audio_channel PIPEWIRE_channel_map_7[] = { SPA_AUDIO_CHANNEL_FL,  SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC,
796                                                                  SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_RL,
797                                                                  SPA_AUDIO_CHANNEL_RR };
798 static const enum spa_audio_channel PIPEWIRE_channel_map_8[] = { SPA_AUDIO_CHANNEL_FL,  SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC,
799                                                                  SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR,
800                                                                  SPA_AUDIO_CHANNEL_SL,  SPA_AUDIO_CHANNEL_SR };
801 
802 #define COPY_CHANNEL_MAP(c) SDL_memcpy(info->position, PIPEWIRE_channel_map_##c, sizeof(PIPEWIRE_channel_map_##c))
803 
804 static void
initialize_spa_info(const SDL_AudioSpec * spec,struct spa_audio_info_raw * info)805 initialize_spa_info(const SDL_AudioSpec *spec, struct spa_audio_info_raw *info)
806 {
807     info->channels = spec->channels;
808     info->rate     = spec->freq;
809 
810     switch (spec->channels) {
811     case 1:
812         COPY_CHANNEL_MAP(1);
813         break;
814     case 2:
815         COPY_CHANNEL_MAP(2);
816         break;
817     case 3:
818         COPY_CHANNEL_MAP(3);
819         break;
820     case 4:
821         COPY_CHANNEL_MAP(4);
822         break;
823     case 5:
824         COPY_CHANNEL_MAP(5);
825         break;
826     case 6:
827         COPY_CHANNEL_MAP(6);
828         break;
829     case 7:
830         COPY_CHANNEL_MAP(7);
831         break;
832     case 8:
833         COPY_CHANNEL_MAP(8);
834         break;
835     }
836 
837     /* Pipewire natively supports all of SDL's sample formats */
838     switch (spec->format) {
839     case AUDIO_U8:
840         info->format = SPA_AUDIO_FORMAT_U8;
841         break;
842     case AUDIO_S8:
843         info->format = SPA_AUDIO_FORMAT_S8;
844         break;
845     case AUDIO_U16LSB:
846         info->format = SPA_AUDIO_FORMAT_U16_LE;
847         break;
848     case AUDIO_S16LSB:
849         info->format = SPA_AUDIO_FORMAT_S16_LE;
850         break;
851     case AUDIO_U16MSB:
852         info->format = SPA_AUDIO_FORMAT_U16_BE;
853         break;
854     case AUDIO_S16MSB:
855         info->format = SPA_AUDIO_FORMAT_S16_BE;
856         break;
857     case AUDIO_S32LSB:
858         info->format = SPA_AUDIO_FORMAT_S32_LE;
859         break;
860     case AUDIO_S32MSB:
861         info->format = SPA_AUDIO_FORMAT_S32_BE;
862         break;
863     case AUDIO_F32LSB:
864         info->format = SPA_AUDIO_FORMAT_F32_LE;
865         break;
866     case AUDIO_F32MSB:
867         info->format = SPA_AUDIO_FORMAT_F32_BE;
868         break;
869     }
870 }
871 
872 static void
output_callback(void * data)873 output_callback(void *data)
874 {
875     struct pw_buffer * pw_buf;
876     struct spa_buffer *spa_buf;
877     Uint8 *            dst;
878 
879     _THIS                    = (SDL_AudioDevice *)data;
880     struct pw_stream *stream = this->hidden->stream;
881 
882     /* Shutting down, don't do anything */
883     if (SDL_AtomicGet(&this->shutdown)) {
884         return;
885     }
886 
887     /* See if a buffer is available */
888     if ((pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream)) == NULL) {
889         return;
890     }
891 
892     spa_buf = pw_buf->buffer;
893 
894     if (spa_buf->datas[0].data == NULL) {
895         return;
896     }
897 
898     /*
899      * If the device is disabled, write silence to the stream buffer
900      * and run the callback with the work buffer to keep the callback
901      * firing regularly in case the audio is being used as a timer.
902      */
903     if (!SDL_AtomicGet(&this->paused)) {
904         if (SDL_AtomicGet(&this->enabled)) {
905             dst = spa_buf->datas[0].data;
906         } else {
907             dst = this->work_buffer;
908             SDL_memset(spa_buf->datas[0].data, this->spec.silence, this->spec.size);
909         }
910 
911         if (!this->stream) {
912             SDL_LockMutex(this->mixer_lock);
913             this->callbackspec.callback(this->callbackspec.userdata, dst, this->callbackspec.size);
914             SDL_UnlockMutex(this->mixer_lock);
915         } else {
916             int got;
917 
918             /* Fire the callback until we have enough to fill a buffer */
919             while (SDL_AudioStreamAvailable(this->stream) < this->spec.size) {
920                 SDL_LockMutex(this->mixer_lock);
921                 this->callbackspec.callback(this->callbackspec.userdata, this->work_buffer, this->callbackspec.size);
922                 SDL_UnlockMutex(this->mixer_lock);
923 
924                 SDL_AudioStreamPut(this->stream, this->work_buffer, this->callbackspec.size);
925             }
926 
927             got = SDL_AudioStreamGet(this->stream, dst, this->spec.size);
928             SDL_assert(got == this->spec.size);
929         }
930     } else {
931         SDL_memset(spa_buf->datas[0].data, this->spec.silence, this->spec.size);
932     }
933 
934     spa_buf->datas[0].chunk->offset = 0;
935     spa_buf->datas[0].chunk->stride = this->hidden->stride;
936     spa_buf->datas[0].chunk->size   = this->spec.size;
937 
938     PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf);
939 }
940 
941 static void
input_callback(void * data)942 input_callback(void *data)
943 {
944     struct pw_buffer * pw_buf;
945     struct spa_buffer *spa_buf;
946     Uint8 *            src;
947     _THIS                    = (SDL_AudioDevice *)data;
948     struct pw_stream *stream = this->hidden->stream;
949 
950     /* Shutting down, don't do anything */
951     if (SDL_AtomicGet(&this->shutdown)) {
952         return;
953     }
954 
955     pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream);
956     if (!pw_buf) {
957         return;
958     }
959 
960     spa_buf = pw_buf->buffer;
961 
962     if ((src = (Uint8 *)spa_buf->datas[0].data) == NULL) {
963         return;
964     }
965 
966     if (!SDL_AtomicGet(&this->paused)) {
967         /* Calculate the offset and data size */
968         const Uint32 offset = SPA_MIN(spa_buf->datas[0].chunk->offset, spa_buf->datas[0].maxsize);
969         const Uint32 size   = SPA_MIN(spa_buf->datas[0].chunk->size, spa_buf->datas[0].maxsize - offset);
970 
971         src += offset;
972 
973         /* Fill the buffer with silence if the stream is disabled. */
974         if (!SDL_AtomicGet(&this->enabled)) {
975             SDL_memset(src, this->callbackspec.silence, size);
976         }
977 
978         /* Pipewire can vary the latency, so buffer all incoming data */
979         SDL_WriteToDataQueue(this->hidden->buffer, src, size);
980 
981         while (SDL_CountDataQueue(this->hidden->buffer) >= this->callbackspec.size) {
982             SDL_ReadFromDataQueue(this->hidden->buffer, this->work_buffer, this->callbackspec.size);
983 
984             SDL_LockMutex(this->mixer_lock);
985             this->callbackspec.callback(this->callbackspec.userdata, this->work_buffer, this->callbackspec.size);
986             SDL_UnlockMutex(this->mixer_lock);
987         }
988     } else { /* Flush the buffer when paused */
989         if (SDL_CountDataQueue(this->hidden->buffer) != 0) {
990             SDL_ClearDataQueue(this->hidden->buffer, this->hidden->buffer_period_size * 2);
991         }
992     }
993 
994     PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf);
995 }
996 
997 static void
stream_state_changed_callback(void * data,enum pw_stream_state old,enum pw_stream_state state,const char * error)998 stream_state_changed_callback(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error)
999 {
1000     _THIS = data;
1001 
1002     if (state == PW_STREAM_STATE_STREAMING || state == PW_STREAM_STATE_ERROR) {
1003         SDL_AtomicSet(&this->hidden->stream_initialized, 1);
1004         PIPEWIRE_pw_thread_loop_signal(this->hidden->loop, false);
1005     }
1006 }
1007 
1008 static const struct pw_stream_events stream_output_events = { PW_VERSION_STREAM_EVENTS,
1009                                                               .state_changed = stream_state_changed_callback,
1010                                                               .process       = output_callback };
1011 static const struct pw_stream_events stream_input_events  = { PW_VERSION_STREAM_EVENTS,
1012                                                              .state_changed = stream_state_changed_callback,
1013                                                              .process       = input_callback };
1014 
1015 static int
PIPEWIRE_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)1016 PIPEWIRE_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
1017 {
1018     /*
1019      * NOTE: The PW_STREAM_FLAG_RT_PROCESS flag can be set to call the stream
1020      * processing callback from the realtime thread.  However, it comes with some
1021      * caveats: no file IO, allocations, locking or other blocking operations
1022      * must occur in the mixer callback.  As this cannot be guaranteed when the
1023      * callback is in the calling application, this flag is omitted.
1024      */
1025     static const enum pw_stream_flags STREAM_FLAGS = PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS;
1026 
1027     char                         thread_name[PW_THREAD_NAME_BUFFER_LENGTH];
1028     Uint8                        pod_buffer[PW_POD_BUFFER_LENGTH];
1029     struct spa_pod_builder       b        = SPA_POD_BUILDER_INIT(pod_buffer, sizeof(pod_buffer));
1030     struct spa_audio_info_raw    spa_info = { 0 };
1031     const struct spa_pod *       params   = NULL;
1032     struct SDL_PrivateAudioData *priv;
1033     struct pw_properties *       props;
1034     const char *                 app_name, *stream_name, *stream_role, *error;
1035     const Uint32                 node_id = this->handle == NULL ? PW_ID_ANY : PW_HANDLE_TO_ID(this->handle);
1036     enum pw_stream_state         state;
1037     int                          res;
1038 
1039     /* Clamp the period size to sane values */
1040     const int min_period       = PW_MIN_SAMPLES * SPA_MAX(this->spec.freq / PW_BASE_CLOCK_RATE, 1);
1041     const int adjusted_samples = SPA_CLAMP(this->spec.samples, min_period, PW_MAX_SAMPLES);
1042 
1043     /* Get the hints for the application name, stream name and role */
1044     app_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME);
1045     if (!app_name || *app_name == '\0') {
1046         app_name = SDL_GetHint(SDL_HINT_APP_NAME);
1047         if (!app_name || *app_name == '\0') {
1048             app_name = "SDL Application";
1049         }
1050     }
1051 
1052     stream_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME);
1053     if (!stream_name || *stream_name == '\0') {
1054         stream_name = "Audio Stream";
1055     }
1056 
1057     /*
1058      * 'Music' is the default used internally by Pipewire and it's modules,
1059      * but 'Game' seems more appropriate for the majority of SDL applications.
1060      */
1061     stream_role = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_ROLE);
1062     if (!stream_role || *stream_role == '\0') {
1063         stream_role = "Game";
1064     }
1065 
1066     /* Initialize the Pipewire stream info from the SDL audio spec */
1067     initialize_spa_info(&this->spec, &spa_info);
1068     params = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &spa_info);
1069     if (params == NULL) {
1070         return SDL_SetError("Pipewire: Failed to set audio format parameters");
1071     }
1072 
1073     if ((this->hidden = priv = SDL_calloc(1, sizeof(struct SDL_PrivateAudioData))) == NULL) {
1074         return SDL_OutOfMemory();
1075     }
1076 
1077     /* Size of a single audio frame in bytes */
1078     priv->stride = (SDL_AUDIO_BITSIZE(this->spec.format) >> 3) * this->spec.channels;
1079 
1080     if (this->spec.samples != adjusted_samples && !iscapture) {
1081         this->spec.samples = adjusted_samples;
1082         this->spec.size    = this->spec.samples * priv->stride;
1083     }
1084 
1085     /* The latency of source nodes can change, so buffering is required. */
1086     if (iscapture) {
1087         priv->buffer_period_size = SPA_MAX(this->spec.samples, adjusted_samples) * priv->stride;
1088 
1089         /* A packet size of 4 periods should be more than is ever needed (no more than 2 should be queued in practice). */
1090         priv->buffer = SDL_NewDataQueue(priv->buffer_period_size * 4, priv->buffer_period_size * 2);
1091         if (priv->buffer == NULL) {
1092             return SDL_SetError("Pipewire: Failed to allocate source buffer");
1093         }
1094     }
1095 
1096     SDL_snprintf(thread_name, sizeof(thread_name), "SDLAudio%c%ld", (iscapture) ? 'C' : 'P', (long)handle);
1097     priv->loop = PIPEWIRE_pw_thread_loop_new(thread_name, NULL);
1098     if (priv->loop == NULL) {
1099         return SDL_SetError("Pipewire: Failed to create stream loop (%i)", errno);
1100     }
1101 
1102     /* Load the rtkit module so Pipewire can set the loop thread to the appropriate priority */
1103     props = PIPEWIRE_pw_properties_new(PW_KEY_CONTEXT_PROFILE_MODULES, "default,rtkit", NULL);
1104     if (props == NULL) {
1105         return SDL_SetError("Pipewire: Failed to create stream context properties (%i)", errno);
1106     }
1107 
1108     /* On success, the context owns the properties object and will free it at destruction time. */
1109     priv->context = PIPEWIRE_pw_context_new(PIPEWIRE_pw_thread_loop_get_loop(priv->loop), props, 0);
1110     if (priv->context == NULL) {
1111         PIPEWIRE_pw_properties_free(props);
1112         return SDL_SetError("Pipewire: Failed to create stream context (%i)", errno);
1113     }
1114 
1115     props = PIPEWIRE_pw_properties_new(NULL, NULL);
1116     if (props == NULL) {
1117         return SDL_SetError("Pipewire: Failed to create stream properties (%i)", errno);
1118     }
1119 
1120     PIPEWIRE_pw_properties_set(props, PW_KEY_MEDIA_TYPE, "Audio");
1121     PIPEWIRE_pw_properties_set(props, PW_KEY_MEDIA_CATEGORY, iscapture ? "Capture" : "Playback");
1122     PIPEWIRE_pw_properties_set(props, PW_KEY_MEDIA_ROLE, stream_role);
1123     PIPEWIRE_pw_properties_set(props, PW_KEY_APP_NAME, app_name);
1124     PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_NAME, stream_name);
1125     PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, stream_name);
1126     PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%i", adjusted_samples, this->spec.freq);
1127     PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", this->spec.freq);
1128     PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true");
1129 
1130     /*
1131      * Create the new stream
1132      * On success, the stream owns the properties object and will free it at destruction time.
1133      */
1134     priv->stream = PIPEWIRE_pw_stream_new_simple(PIPEWIRE_pw_thread_loop_get_loop(priv->loop), stream_name, props,
1135                                                  iscapture ? &stream_input_events : &stream_output_events, this);
1136     if (priv->stream == NULL) {
1137         PIPEWIRE_pw_properties_free(props);
1138         return SDL_SetError("Pipewire: Failed to create stream (%i)", errno);
1139     }
1140 
1141     res = PIPEWIRE_pw_stream_connect(priv->stream, iscapture ? PW_DIRECTION_INPUT : PW_DIRECTION_OUTPUT, node_id, STREAM_FLAGS,
1142                                      &params, 1);
1143     if (res != 0) {
1144         return SDL_SetError("Pipewire: Failed to connect stream");
1145     }
1146 
1147     res = PIPEWIRE_pw_thread_loop_start(priv->loop);
1148     if (res != 0) {
1149         return SDL_SetError("Pipewire: Failed to start stream loop");
1150     }
1151 
1152     /* Wait until the stream is either running or failed */
1153     PIPEWIRE_pw_thread_loop_lock(priv->loop);
1154     if (!SDL_AtomicGet(&priv->stream_initialized)) {
1155         PIPEWIRE_pw_thread_loop_wait(priv->loop);
1156     }
1157     PIPEWIRE_pw_thread_loop_unlock(priv->loop);
1158 
1159     state = PIPEWIRE_pw_stream_get_state(priv->stream, &error);
1160 
1161     if (state == PW_STREAM_STATE_ERROR) {
1162         return SDL_SetError("Pipewire: Stream error: %s", error);
1163     }
1164 
1165     return 0;
1166 }
1167 
PIPEWIRE_CloseDevice(_THIS)1168 static void PIPEWIRE_CloseDevice(_THIS)
1169 {
1170     if (this->hidden->loop) {
1171         PIPEWIRE_pw_thread_loop_stop(this->hidden->loop);
1172     }
1173 
1174     if (this->hidden->stream) {
1175         PIPEWIRE_pw_stream_destroy(this->hidden->stream);
1176     }
1177 
1178     if (this->hidden->context) {
1179         PIPEWIRE_pw_context_destroy(this->hidden->context);
1180     }
1181 
1182     if (this->hidden->loop) {
1183         PIPEWIRE_pw_thread_loop_destroy(this->hidden->loop);
1184     }
1185 
1186     if (this->hidden->buffer) {
1187         SDL_FreeDataQueue(this->hidden->buffer);
1188     }
1189 
1190     SDL_free(this->hidden);
1191 }
1192 
1193 static void
PIPEWIRE_Deinitialize()1194 PIPEWIRE_Deinitialize()
1195 {
1196     if (pipewire_initialized) {
1197         hotplug_loop_destroy();
1198         deinit_pipewire_library();
1199         pipewire_initialized = SDL_FALSE;
1200     }
1201 }
1202 
1203 static int
PIPEWIRE_Init(SDL_AudioDriverImpl * impl)1204 PIPEWIRE_Init(SDL_AudioDriverImpl *impl)
1205 {
1206     if (!pipewire_initialized) {
1207         if (init_pipewire_library() < 0) {
1208             return 0;
1209         }
1210 
1211         pipewire_initialized = SDL_TRUE;
1212 
1213         if (hotplug_loop_init() < 0) {
1214             PIPEWIRE_Deinitialize();
1215             return 0;
1216         }
1217     }
1218 
1219     /* Set the function pointers */
1220     impl->DetectDevices = PIPEWIRE_DetectDevices;
1221     impl->OpenDevice    = PIPEWIRE_OpenDevice;
1222     impl->CloseDevice   = PIPEWIRE_CloseDevice;
1223     impl->Deinitialize  = PIPEWIRE_Deinitialize;
1224 
1225     impl->HasCaptureSupport         = 1;
1226     impl->ProvidesOwnCallbackThread = 1;
1227 
1228     return 1;
1229 }
1230 
1231 AudioBootStrap PIPEWIRE_bootstrap = { "pipewire", "Pipewire", PIPEWIRE_Init, 0 };
1232 
1233 #endif /* SDL_AUDIO_DRIVER_PIPEWIRE */
1234 
1235 /* vi: set ts=4 sw=4 expandtab: */
1236