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, ®istry_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 ¶ms, 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