Atlas - SDL_pipewire.c
Home / ext / SDL / src / audio / pipewire Lines: 1 | Size: 47185 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 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_PIPEWIRE 25 26#include "SDL_pipewire.h" 27 28#include <pipewire/extensions/metadata.h> 29#include <spa/param/audio/format-utils.h> 30#include <spa/utils/json.h> 31 32/* 33 * This seems to be a sane lower limit as Pipewire 34 * uses it in several of it's own modules. 35 */ 36#define PW_MIN_SAMPLES 32 // About 0.67ms at 48kHz 37#define PW_BASE_CLOCK_RATE 48000 38 39#define PW_POD_BUFFER_LENGTH 1024 40#define PW_THREAD_NAME_BUFFER_LENGTH 128 41#define PW_MAX_IDENTIFIER_LENGTH 256 42 43enum PW_READY_FLAGS 44{ 45 PW_READY_FLAG_BUFFER_ADDED = 0x1, 46 PW_READY_FLAG_STREAM_READY = 0x2, 47 PW_READY_FLAG_ALL_PREOPEN_BITS = 0x3, 48 PW_READY_FLAG_OPEN_COMPLETE = 0x4, 49 PW_READY_FLAG_ALL_BITS = 0x7 50}; 51 52#define PW_ID_TO_HANDLE(x) (void *)((uintptr_t)x) 53#define PW_HANDLE_TO_ID(x) (uint32_t)((uintptr_t)x) 54 55static bool pipewire_initialized = false; 56 57// Pipewire entry points 58static const char *(*PIPEWIRE_pw_get_library_version)(void); 59static void (*PIPEWIRE_pw_init)(int *, char ***); 60static void (*PIPEWIRE_pw_deinit)(void); 61static struct pw_main_loop *(*PIPEWIRE_pw_main_loop_new)(const struct spa_dict *loop); 62static struct pw_loop *(*PIPEWIRE_pw_main_loop_get_loop)(struct pw_main_loop *loop); 63static int (*PIPEWIRE_pw_main_loop_run)(struct pw_main_loop *loop); 64static int (*PIPEWIRE_pw_main_loop_quit)(struct pw_main_loop *loop); 65static void(*PIPEWIRE_pw_main_loop_destroy)(struct pw_main_loop *loop); 66static struct pw_thread_loop *(*PIPEWIRE_pw_thread_loop_new)(const char *, const struct spa_dict *); 67static void (*PIPEWIRE_pw_thread_loop_destroy)(struct pw_thread_loop *); 68static void (*PIPEWIRE_pw_thread_loop_stop)(struct pw_thread_loop *); 69static struct pw_loop *(*PIPEWIRE_pw_thread_loop_get_loop)(struct pw_thread_loop *); 70static void (*PIPEWIRE_pw_thread_loop_lock)(struct pw_thread_loop *); 71static void (*PIPEWIRE_pw_thread_loop_unlock)(struct pw_thread_loop *); 72static void (*PIPEWIRE_pw_thread_loop_signal)(struct pw_thread_loop *, bool); 73static void (*PIPEWIRE_pw_thread_loop_wait)(struct pw_thread_loop *); 74static int (*PIPEWIRE_pw_thread_loop_start)(struct pw_thread_loop *); 75static struct pw_context *(*PIPEWIRE_pw_context_new)(struct pw_loop *, struct pw_properties *, size_t); 76static void (*PIPEWIRE_pw_context_destroy)(struct pw_context *); 77static struct pw_core *(*PIPEWIRE_pw_context_connect)(struct pw_context *, struct pw_properties *, size_t); 78static void (*PIPEWIRE_pw_proxy_add_object_listener)(struct pw_proxy *, struct spa_hook *, const void *, void *); 79static void *(*PIPEWIRE_pw_proxy_get_user_data)(struct pw_proxy *); 80static void (*PIPEWIRE_pw_proxy_destroy)(struct pw_proxy *); 81static int (*PIPEWIRE_pw_core_disconnect)(struct pw_core *); 82static struct pw_stream *(*PIPEWIRE_pw_stream_new_simple)(struct pw_loop *, const char *, struct pw_properties *, 83 const struct pw_stream_events *, void *); 84static void (*PIPEWIRE_pw_stream_destroy)(struct pw_stream *); 85static int (*PIPEWIRE_pw_stream_connect)(struct pw_stream *, enum pw_direction, uint32_t, enum pw_stream_flags, 86 const struct spa_pod **, uint32_t); 87static enum pw_stream_state (*PIPEWIRE_pw_stream_get_state)(struct pw_stream *stream, const char **error); 88static struct pw_buffer *(*PIPEWIRE_pw_stream_dequeue_buffer)(struct pw_stream *); 89static int (*PIPEWIRE_pw_stream_queue_buffer)(struct pw_stream *, struct pw_buffer *); 90static struct pw_properties *(*PIPEWIRE_pw_properties_new)(const char *, ...)SPA_SENTINEL; 91static int (*PIPEWIRE_pw_properties_set)(struct pw_properties *, const char *, const char *); 92static int (*PIPEWIRE_pw_properties_setf)(struct pw_properties *, const char *, const char *, ...) SPA_PRINTF_FUNC(3, 4); 93 94#ifdef SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC 95 96SDL_ELF_NOTE_DLOPEN( 97 "audio-libpipewire", 98 "Support for audio through libpipewire", 99 SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, 100 SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC 101) 102 103static const char *pipewire_library = SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC; 104static SDL_SharedObject *pipewire_handle = NULL; 105 106static bool pipewire_dlsym(const char *fn, void **addr) 107{ 108 *addr = SDL_LoadFunction(pipewire_handle, fn); 109 if (!*addr) { 110 // Don't call SDL_SetError(): SDL_LoadFunction already did. 111 return false; 112 } 113 114 return true; 115} 116 117#define SDL_PIPEWIRE_SYM(x) \ 118 if (!pipewire_dlsym(#x, (void **)(char *)&PIPEWIRE_##x)) \ 119 return false 120 121static bool load_pipewire_library(void) 122{ 123 pipewire_handle = SDL_LoadObject(pipewire_library); 124 return pipewire_handle ? true : false; 125} 126 127static void unload_pipewire_library(void) 128{ 129 if (pipewire_handle) { 130 SDL_UnloadObject(pipewire_handle); 131 pipewire_handle = NULL; 132 } 133} 134 135#else 136 137#define SDL_PIPEWIRE_SYM(x) PIPEWIRE_##x = x 138 139static bool load_pipewire_library(void) 140{ 141 return true; 142} 143 144static void unload_pipewire_library(void) 145{ 146 // Nothing to do 147} 148 149#endif // SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC 150 151static bool load_pipewire_syms(void) 152{ 153 SDL_PIPEWIRE_SYM(pw_get_library_version); 154 SDL_PIPEWIRE_SYM(pw_init); 155 SDL_PIPEWIRE_SYM(pw_deinit); 156 SDL_PIPEWIRE_SYM(pw_main_loop_new); 157 SDL_PIPEWIRE_SYM(pw_main_loop_get_loop); 158 SDL_PIPEWIRE_SYM(pw_main_loop_run); 159 SDL_PIPEWIRE_SYM(pw_main_loop_quit); 160 SDL_PIPEWIRE_SYM(pw_main_loop_destroy); 161 SDL_PIPEWIRE_SYM(pw_thread_loop_new); 162 SDL_PIPEWIRE_SYM(pw_thread_loop_destroy); 163 SDL_PIPEWIRE_SYM(pw_thread_loop_stop); 164 SDL_PIPEWIRE_SYM(pw_thread_loop_get_loop); 165 SDL_PIPEWIRE_SYM(pw_thread_loop_lock); 166 SDL_PIPEWIRE_SYM(pw_thread_loop_unlock); 167 SDL_PIPEWIRE_SYM(pw_thread_loop_signal); 168 SDL_PIPEWIRE_SYM(pw_thread_loop_wait); 169 SDL_PIPEWIRE_SYM(pw_thread_loop_start); 170 SDL_PIPEWIRE_SYM(pw_context_new); 171 SDL_PIPEWIRE_SYM(pw_context_destroy); 172 SDL_PIPEWIRE_SYM(pw_context_connect); 173 SDL_PIPEWIRE_SYM(pw_proxy_add_object_listener); 174 SDL_PIPEWIRE_SYM(pw_proxy_get_user_data); 175 SDL_PIPEWIRE_SYM(pw_proxy_destroy); 176 SDL_PIPEWIRE_SYM(pw_core_disconnect); 177 SDL_PIPEWIRE_SYM(pw_stream_new_simple); 178 SDL_PIPEWIRE_SYM(pw_stream_destroy); 179 SDL_PIPEWIRE_SYM(pw_stream_connect); 180 SDL_PIPEWIRE_SYM(pw_stream_get_state); 181 SDL_PIPEWIRE_SYM(pw_stream_dequeue_buffer); 182 SDL_PIPEWIRE_SYM(pw_stream_queue_buffer); 183 SDL_PIPEWIRE_SYM(pw_properties_new); 184 SDL_PIPEWIRE_SYM(pw_properties_set); 185 SDL_PIPEWIRE_SYM(pw_properties_setf); 186 187 return true; 188} 189 190static bool init_pipewire_library(void) 191{ 192 if (load_pipewire_library()) { 193 if (load_pipewire_syms()) { 194 PIPEWIRE_pw_init(NULL, NULL); 195 return true; 196 } 197 } 198 199 return false; 200} 201 202static void deinit_pipewire_library(void) 203{ 204 PIPEWIRE_pw_deinit(); 205 unload_pipewire_library(); 206} 207 208// A generic Pipewire node object used for enumeration. 209struct node_object 210{ 211 struct spa_list link; 212 213 Uint32 id; 214 int seq; 215 bool persist; 216 217 /* 218 * NOTE: If used, this is *must* be allocated with SDL_malloc() or similar 219 * as SDL_free() will be called on it when the node_object is destroyed. 220 * 221 * If ownership of the referenced memory is transferred, this must be set 222 * to NULL or the memory will be freed when the node_object is destroyed. 223 */ 224 void *userdata; 225 226 struct pw_proxy *proxy; 227 struct spa_hook node_listener; 228 struct spa_hook core_listener; 229}; 230 231// A sink/source node used for stream I/O. 232struct io_node 233{ 234 struct spa_list link; 235 236 Uint32 id; 237 bool recording; 238 SDL_AudioSpec spec; 239 240 const char *name; // Friendly name 241 const char *path; // OS identifier (i.e. ALSA endpoint) 242 243 char buf[]; // Buffer to hold the name and path strings. 244}; 245 246// The global hotplug thread and associated objects. 247static struct pw_thread_loop *hotplug_loop; 248static struct pw_core *hotplug_core; 249static struct pw_context *hotplug_context; 250static struct pw_registry *hotplug_registry; 251static struct spa_hook hotplug_registry_listener; 252static struct spa_hook hotplug_core_listener; 253static struct spa_list hotplug_pending_list; 254static struct spa_list hotplug_io_list; 255static int hotplug_init_seq_val; 256static bool hotplug_init_complete; 257static bool hotplug_events_enabled; 258 259static int pipewire_version_major; 260static int pipewire_version_minor; 261static int pipewire_version_patch; 262static char *pipewire_default_sink_id = NULL; 263static char *pipewire_default_source_id = NULL; 264 265static bool pipewire_core_version_at_least(int major, int minor, int patch) 266{ 267 return (pipewire_version_major >= major) && 268 (pipewire_version_major > major || pipewire_version_minor >= minor) && 269 (pipewire_version_major > major || pipewire_version_minor > minor || pipewire_version_patch >= patch); 270} 271 272// The active node list 273static bool io_list_check_add(struct io_node *node) 274{ 275 struct io_node *n; 276 277 // See if the node is already in the list 278 spa_list_for_each (n, &hotplug_io_list, link) { 279 if (n->id == node->id) { 280 return false; 281 } 282 } 283 284 // Add to the list if the node doesn't already exist 285 spa_list_append(&hotplug_io_list, &node->link); 286 287 if (hotplug_events_enabled) { 288 SDL_AddAudioDevice(node->recording, node->name, &node->spec, PW_ID_TO_HANDLE(node->id)); 289 } 290 291 return true; 292} 293 294static void io_list_remove(Uint32 id) 295{ 296 struct io_node *n, *temp; 297 298 // Find and remove the node from the list 299 spa_list_for_each_safe (n, temp, &hotplug_io_list, link) { 300 if (n->id == id) { 301 spa_list_remove(&n->link); 302 303 if (hotplug_events_enabled) { 304 SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(PW_ID_TO_HANDLE(id))); 305 } 306 307 SDL_free(n); 308 309 break; 310 } 311 } 312} 313 314static void io_list_clear(void) 315{ 316 struct io_node *n, *temp; 317 318 spa_list_for_each_safe (n, temp, &hotplug_io_list, link) { 319 spa_list_remove(&n->link); 320 SDL_free(n); 321 } 322} 323 324static struct io_node *io_list_get_by_id(Uint32 id) 325{ 326 struct io_node *n, *temp; 327 spa_list_for_each_safe (n, temp, &hotplug_io_list, link) { 328 if (n->id == id) { 329 return n; 330 } 331 } 332 return NULL; 333} 334 335static void node_object_destroy(struct node_object *node) 336{ 337 SDL_assert(node != NULL); 338 339 spa_list_remove(&node->link); 340 spa_hook_remove(&node->node_listener); 341 spa_hook_remove(&node->core_listener); 342 SDL_free(node->userdata); 343 PIPEWIRE_pw_proxy_destroy(node->proxy); 344} 345 346// The pending node list 347static void pending_list_add(struct node_object *node) 348{ 349 SDL_assert(node != NULL); 350 spa_list_append(&hotplug_pending_list, &node->link); 351} 352 353static void pending_list_remove(Uint32 id) 354{ 355 struct node_object *node, *temp; 356 357 spa_list_for_each_safe (node, temp, &hotplug_pending_list, link) { 358 if (node->id == id) { 359 node_object_destroy(node); 360 } 361 } 362} 363 364static void pending_list_clear(void) 365{ 366 struct node_object *node, *temp; 367 368 spa_list_for_each_safe (node, temp, &hotplug_pending_list, link) { 369 node_object_destroy(node); 370 } 371} 372 373static void *node_object_new(Uint32 id, const char *type, Uint32 version, const void *funcs, const struct pw_core_events *core_events) 374{ 375 struct pw_proxy *proxy; 376 struct node_object *node; 377 378 // Create the proxy object 379 proxy = pw_registry_bind(hotplug_registry, id, type, version, sizeof(struct node_object)); 380 if (!proxy) { 381 SDL_SetError("Pipewire: Failed to create proxy object (%i)", errno); 382 return NULL; 383 } 384 385 node = PIPEWIRE_pw_proxy_get_user_data(proxy); 386 SDL_zerop(node); 387 388 node->id = id; 389 node->proxy = proxy; 390 391 // Add the callbacks 392 pw_core_add_listener(hotplug_core, &node->core_listener, core_events, node); 393 PIPEWIRE_pw_proxy_add_object_listener(node->proxy, &node->node_listener, funcs, node); 394 395 // Add the node to the active list 396 pending_list_add(node); 397 398 return node; 399} 400 401// Core sync points 402static void core_events_hotplug_init_callback(void *object, uint32_t id, int seq) 403{ 404 if (id == PW_ID_CORE && seq == hotplug_init_seq_val) { 405 // This core listener is no longer needed. 406 spa_hook_remove(&hotplug_core_listener); 407 408 // Signal that the initial I/O list is populated 409 hotplug_init_complete = true; 410 PIPEWIRE_pw_thread_loop_signal(hotplug_loop, false); 411 } 412} 413 414static void core_events_hotplug_info_callback(void *data, const struct pw_core_info *info) 415{ 416 if (SDL_sscanf(info->version, "%d.%d.%d", &pipewire_version_major, &pipewire_version_minor, &pipewire_version_patch) < 3) { 417 pipewire_version_major = 0; 418 pipewire_version_minor = 0; 419 pipewire_version_patch = 0; 420 } 421} 422 423static void core_events_interface_callback(void *object, uint32_t id, int seq) 424{ 425 struct node_object *node = object; 426 struct io_node *io = node->userdata; 427 428 if (id == PW_ID_CORE && seq == node->seq) { 429 /* 430 * Move the I/O node to the connected list. 431 * On success, the list owns the I/O node object. 432 */ 433 if (io_list_check_add(io)) { 434 node->userdata = NULL; 435 } 436 437 node_object_destroy(node); 438 } 439} 440 441static void core_events_metadata_callback(void *object, uint32_t id, int seq) 442{ 443 struct node_object *node = object; 444 445 if (id == PW_ID_CORE && seq == node->seq && !node->persist) { 446 node_object_destroy(node); 447 } 448} 449 450static const struct pw_core_events hotplug_init_core_events = { PW_VERSION_CORE_EVENTS, .info = core_events_hotplug_info_callback, .done = core_events_hotplug_init_callback }; 451static const struct pw_core_events interface_core_events = { PW_VERSION_CORE_EVENTS, .done = core_events_interface_callback }; 452static const struct pw_core_events metadata_core_events = { PW_VERSION_CORE_EVENTS, .done = core_events_metadata_callback }; 453 454static void hotplug_core_sync(struct node_object *node) 455{ 456 /* 457 * Node sync events *must* come before the hotplug init sync events or the initial 458 * I/O list will be incomplete when the main hotplug sync point is hit. 459 */ 460 if (node) { 461 node->seq = pw_core_sync(hotplug_core, PW_ID_CORE, node->seq); 462 } 463 464 if (!hotplug_init_complete) { 465 hotplug_init_seq_val = pw_core_sync(hotplug_core, PW_ID_CORE, hotplug_init_seq_val); 466 } 467} 468 469// Helpers for retrieving values from params 470static bool get_range_param(const struct spa_pod *param, Uint32 key, int *def, int *min, int *max) 471{ 472 const struct spa_pod_prop *prop; 473 struct spa_pod *value; 474 Uint32 n_values, choice; 475 476 prop = spa_pod_find_prop(param, NULL, key); 477 478 if (prop && prop->value.type == SPA_TYPE_Choice) { 479 value = spa_pod_get_values(&prop->value, &n_values, &choice); 480 481 if (n_values == 3 && choice == SPA_CHOICE_Range) { 482 Uint32 *v = SPA_POD_BODY(value); 483 484 if (v) { 485 if (def) { 486 *def = (int)v[0]; 487 } 488 if (min) { 489 *min = (int)v[1]; 490 } 491 if (max) { 492 *max = (int)v[2]; 493 } 494 495 return true; 496 } 497 } 498 } 499 500 return false; 501} 502 503static bool get_int_param(const struct spa_pod *param, Uint32 key, int *val) 504{ 505 const struct spa_pod_prop *prop; 506 Sint32 v; 507 508 prop = spa_pod_find_prop(param, NULL, key); 509 510 if (prop && spa_pod_get_int(&prop->value, &v) == 0) { 511 if (val) { 512 *val = (int)v; 513 } 514 515 return true; 516 } 517 518 return false; 519} 520 521static SDL_AudioFormat SPAFormatToSDL(enum spa_audio_format spafmt) 522{ 523 switch (spafmt) { 524 #define CHECKFMT(spa,sdl) case SPA_AUDIO_FORMAT_##spa: return SDL_AUDIO_##sdl 525 CHECKFMT(U8, U8); 526 CHECKFMT(S8, S8); 527 CHECKFMT(S16_LE, S16LE); 528 CHECKFMT(S16_BE, S16BE); 529 CHECKFMT(S32_LE, S32LE); 530 CHECKFMT(S32_BE, S32BE); 531 CHECKFMT(F32_LE, F32LE); 532 CHECKFMT(F32_BE, F32BE); 533 #undef CHECKFMT 534 default: break; 535 } 536 537 return SDL_AUDIO_UNKNOWN; 538} 539 540// Interface node callbacks 541static void node_event_info(void *object, const struct pw_node_info *info) 542{ 543 struct node_object *node = object; 544 struct io_node *io = node->userdata; 545 const char *prop_val; 546 Uint32 i; 547 548 if (info) { 549 prop_val = spa_dict_lookup(info->props, PW_KEY_AUDIO_CHANNELS); 550 if (prop_val) { 551 io->spec.channels = (Uint8)SDL_atoi(prop_val); 552 } 553 554 // Need to parse the parameters to get the sample rate 555 for (i = 0; i < info->n_params; ++i) { 556 pw_node_enum_params((struct pw_node *)node->proxy, 0, info->params[i].id, 0, 0, NULL); 557 } 558 559 hotplug_core_sync(node); 560 } 561} 562 563static void node_event_param(void *object, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) 564{ 565 struct node_object *node = object; 566 struct io_node *io = node->userdata; 567 568 if ((id == SPA_PARAM_Format) && (io->spec.format == SDL_AUDIO_UNKNOWN)) { 569 struct spa_audio_info_raw info; 570 SDL_zero(info); 571 if (spa_format_audio_raw_parse(param, &info) == 0) { 572 //SDL_Log("Sink Format: %d, Rate: %d Hz, Channels: %d", info.format, info.rate, info.channels); 573 io->spec.format = SPAFormatToSDL(info.format); 574 } 575 } 576 577 // Get the default frequency 578 if (io->spec.freq == 0) { 579 get_range_param(param, SPA_FORMAT_AUDIO_rate, &io->spec.freq, NULL, NULL); 580 } 581 582 /* 583 * The channel count should have come from the node properties, 584 * but it is stored here as well. If one failed, try the other. 585 */ 586 if (io->spec.channels == 0) { 587 int channels; 588 if (get_int_param(param, SPA_FORMAT_AUDIO_channels, &channels)) { 589 io->spec.channels = (Uint8)channels; 590 } 591 } 592} 593 594static const struct pw_node_events interface_node_events = { PW_VERSION_NODE_EVENTS, .info = node_event_info, 595 .param = node_event_param }; 596 597static char *get_name_from_json(const char *json) 598{ 599 struct spa_json parser[2]; 600 char key[7]; // "name" 601 char value[PW_MAX_IDENTIFIER_LENGTH]; 602 spa_json_init(&parser[0], json, SDL_strlen(json)); 603 if (spa_json_enter_object(&parser[0], &parser[1]) <= 0) { 604 // Not actually JSON 605 return NULL; 606 } 607 if (spa_json_get_string(&parser[1], key, sizeof(key)) <= 0) { 608 // Not actually a key/value pair 609 return NULL; 610 } 611 if (spa_json_get_string(&parser[1], value, sizeof(value)) <= 0) { 612 // Somehow had a key with no value? 613 return NULL; 614 } 615 return SDL_strdup(value); 616} 617 618static void change_default_device(const char *path) 619{ 620 if (hotplug_events_enabled) { 621 struct io_node *n, *temp; 622 spa_list_for_each_safe (n, temp, &hotplug_io_list, link) { 623 if (SDL_strcmp(n->path, path) == 0) { 624 SDL_DefaultAudioDeviceChanged(SDL_FindPhysicalAudioDeviceByHandle(PW_ID_TO_HANDLE(n->id))); 625 return; // found it, we're done. 626 } 627 } 628 } 629} 630 631// Metadata node callback 632static int metadata_property(void *object, Uint32 subject, const char *key, const char *type, const char *value) 633{ 634 struct node_object *node = object; 635 636 if (subject == PW_ID_CORE && key && value) { 637 if (!SDL_strcmp(key, "default.audio.sink")) { 638 SDL_free(pipewire_default_sink_id); 639 pipewire_default_sink_id = get_name_from_json(value); 640 node->persist = true; 641 change_default_device(pipewire_default_sink_id); 642 } else if (!SDL_strcmp(key, "default.audio.source")) { 643 SDL_free(pipewire_default_source_id); 644 pipewire_default_source_id = get_name_from_json(value); 645 node->persist = true; 646 change_default_device(pipewire_default_source_id); 647 } 648 } 649 650 return 0; 651} 652 653static const struct pw_metadata_events metadata_node_events = { PW_VERSION_METADATA_EVENTS, .property = metadata_property }; 654 655// Global registry callbacks 656static void registry_event_global_callback(void *object, uint32_t id, uint32_t permissions, const char *type, uint32_t version, 657 const struct spa_dict *props) 658{ 659 struct node_object *node; 660 661 // We're only interested in interface and metadata nodes. 662 if (!SDL_strcmp(type, PW_TYPE_INTERFACE_Node)) { 663 const char *media_class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); 664 665 if (media_class) { 666 const char *node_desc; 667 const char *node_path; 668 struct io_node *io; 669 bool recording; 670 int desc_buffer_len; 671 int path_buffer_len; 672 673 // Just want sink and source 674 if (!SDL_strcasecmp(media_class, "Audio/Sink")) { 675 recording = false; 676 } else if (!SDL_strcasecmp(media_class, "Audio/Source")) { 677 recording = true; 678 } else { 679 return; 680 } 681 682 node_desc = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION); 683 node_path = spa_dict_lookup(props, PW_KEY_NODE_NAME); 684 685 if (node_desc && node_path) { 686 node = node_object_new(id, type, version, &interface_node_events, &interface_core_events); 687 if (!node) { 688 SDL_SetError("Pipewire: Failed to allocate interface node"); 689 return; 690 } 691 692 // Allocate and initialize the I/O node information struct 693 desc_buffer_len = SDL_strlen(node_desc) + 1; 694 path_buffer_len = SDL_strlen(node_path) + 1; 695 node->userdata = io = SDL_calloc(1, sizeof(struct io_node) + desc_buffer_len + path_buffer_len); 696 if (!io) { 697 node_object_destroy(node); 698 return; 699 } 700 701 // Begin setting the node properties 702 io->id = id; 703 io->recording = recording; 704 if (io->spec.format == SDL_AUDIO_UNKNOWN) { 705 io->spec.format = SDL_AUDIO_S16; // we'll go conservative here if for some reason the format isn't known. 706 } 707 io->name = io->buf; 708 io->path = io->buf + desc_buffer_len; 709 SDL_strlcpy(io->buf, node_desc, desc_buffer_len); 710 SDL_strlcpy(io->buf + desc_buffer_len, node_path, path_buffer_len); 711 712 // Update sync points 713 hotplug_core_sync(node); 714 } 715 } 716 } else if (!SDL_strcmp(type, PW_TYPE_INTERFACE_Metadata)) { 717 node = node_object_new(id, type, version, &metadata_node_events, &metadata_core_events); 718 if (!node) { 719 SDL_SetError("Pipewire: Failed to allocate metadata node"); 720 return; 721 } 722 723 // Update sync points 724 hotplug_core_sync(node); 725 } 726} 727 728static void registry_event_remove_callback(void *object, uint32_t id) 729{ 730 io_list_remove(id); 731 pending_list_remove(id); 732} 733 734static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global_callback, 735 .global_remove = registry_event_remove_callback }; 736 737// The hotplug thread 738static bool hotplug_loop_init(void) 739{ 740 int res; 741 742 spa_list_init(&hotplug_pending_list); 743 spa_list_init(&hotplug_io_list); 744 745 hotplug_loop = PIPEWIRE_pw_thread_loop_new("SDLPwAudioPlug", NULL); 746 if (!hotplug_loop) { 747 return SDL_SetError("Pipewire: Failed to create hotplug detection loop (%i)", errno); 748 } 749 750 hotplug_context = PIPEWIRE_pw_context_new(PIPEWIRE_pw_thread_loop_get_loop(hotplug_loop), NULL, 0); 751 if (!hotplug_context) { 752 return SDL_SetError("Pipewire: Failed to create hotplug detection context (%i)", errno); 753 } 754 755 hotplug_core = PIPEWIRE_pw_context_connect(hotplug_context, NULL, 0); 756 if (!hotplug_core) { 757 return SDL_SetError("Pipewire: Failed to connect hotplug detection context (%i)", errno); 758 } 759 760 hotplug_registry = pw_core_get_registry(hotplug_core, PW_VERSION_REGISTRY, 0); 761 if (!hotplug_registry) { 762 return SDL_SetError("Pipewire: Failed to acquire hotplug detection registry (%i)", errno); 763 } 764 765 spa_zero(hotplug_registry_listener); 766 pw_registry_add_listener(hotplug_registry, &hotplug_registry_listener, ®istry_events, NULL); 767 768 spa_zero(hotplug_core_listener); 769 pw_core_add_listener(hotplug_core, &hotplug_core_listener, &hotplug_init_core_events, NULL); 770 771 hotplug_init_seq_val = pw_core_sync(hotplug_core, PW_ID_CORE, 0); 772 773 res = PIPEWIRE_pw_thread_loop_start(hotplug_loop); 774 if (res != 0) { 775 return SDL_SetError("Pipewire: Failed to start hotplug detection loop"); 776 } 777 778 return true; 779} 780 781static void hotplug_loop_destroy(void) 782{ 783 if (hotplug_loop) { 784 PIPEWIRE_pw_thread_loop_stop(hotplug_loop); 785 } 786 787 pending_list_clear(); 788 io_list_clear(); 789 790 hotplug_init_complete = false; 791 hotplug_events_enabled = false; 792 793 if (pipewire_default_sink_id) { 794 SDL_free(pipewire_default_sink_id); 795 pipewire_default_sink_id = NULL; 796 } 797 if (pipewire_default_source_id) { 798 SDL_free(pipewire_default_source_id); 799 pipewire_default_source_id = NULL; 800 } 801 802 if (hotplug_registry) { 803 PIPEWIRE_pw_proxy_destroy((struct pw_proxy *)hotplug_registry); 804 hotplug_registry = NULL; 805 } 806 807 if (hotplug_core) { 808 PIPEWIRE_pw_core_disconnect(hotplug_core); 809 hotplug_core = NULL; 810 } 811 812 if (hotplug_context) { 813 PIPEWIRE_pw_context_destroy(hotplug_context); 814 hotplug_context = NULL; 815 } 816 817 if (hotplug_loop) { 818 PIPEWIRE_pw_thread_loop_destroy(hotplug_loop); 819 hotplug_loop = NULL; 820 } 821} 822 823static void PIPEWIRE_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) 824{ 825 struct io_node *io; 826 827 PIPEWIRE_pw_thread_loop_lock(hotplug_loop); 828 829 // Wait until the initial registry enumeration is complete 830 if (!hotplug_init_complete) { 831 PIPEWIRE_pw_thread_loop_wait(hotplug_loop); 832 } 833 834 spa_list_for_each (io, &hotplug_io_list, link) { 835 SDL_AudioDevice *device = SDL_AddAudioDevice(io->recording, io->name, &io->spec, PW_ID_TO_HANDLE(io->id)); 836 if (pipewire_default_sink_id && SDL_strcmp(io->path, pipewire_default_sink_id) == 0) { 837 if (!io->recording) { 838 *default_playback = device; 839 } 840 } else if (pipewire_default_source_id && SDL_strcmp(io->path, pipewire_default_source_id) == 0) { 841 if (io->recording) { 842 *default_recording = device; 843 } 844 } 845 } 846 847 hotplug_events_enabled = true; 848 849 PIPEWIRE_pw_thread_loop_unlock(hotplug_loop); 850} 851 852// Channel maps that match the order in SDL_Audio.h 853static const enum spa_audio_channel PIPEWIRE_channel_map_1[] = { SPA_AUDIO_CHANNEL_MONO }; 854static const enum spa_audio_channel PIPEWIRE_channel_map_2[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR }; 855static const enum spa_audio_channel PIPEWIRE_channel_map_3[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_LFE }; 856static const enum spa_audio_channel PIPEWIRE_channel_map_4[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, 857 SPA_AUDIO_CHANNEL_RR }; 858static const enum spa_audio_channel PIPEWIRE_channel_map_5[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, 859 SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR }; 860static const enum spa_audio_channel PIPEWIRE_channel_map_6[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, 861 SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR }; 862static const enum spa_audio_channel PIPEWIRE_channel_map_7[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, 863 SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_RL, 864 SPA_AUDIO_CHANNEL_RR }; 865static const enum spa_audio_channel PIPEWIRE_channel_map_8[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, 866 SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, 867 SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR }; 868 869#define COPY_CHANNEL_MAP(c) SDL_memcpy(info->position, PIPEWIRE_channel_map_##c, sizeof(PIPEWIRE_channel_map_##c)) 870 871static void initialize_spa_info(const SDL_AudioSpec *spec, struct spa_audio_info_raw *info) 872{ 873 info->channels = spec->channels; 874 info->rate = spec->freq; 875 876 switch (spec->channels) { 877 case 1: 878 COPY_CHANNEL_MAP(1); 879 break; 880 case 2: 881 COPY_CHANNEL_MAP(2); 882 break; 883 case 3: 884 COPY_CHANNEL_MAP(3); 885 break; 886 case 4: 887 COPY_CHANNEL_MAP(4); 888 break; 889 case 5: 890 COPY_CHANNEL_MAP(5); 891 break; 892 case 6: 893 COPY_CHANNEL_MAP(6); 894 break; 895 case 7: 896 COPY_CHANNEL_MAP(7); 897 break; 898 case 8: 899 COPY_CHANNEL_MAP(8); 900 break; 901 } 902 903 // Pipewire natively supports all of SDL's sample formats 904 switch (spec->format) { 905 case SDL_AUDIO_U8: 906 info->format = SPA_AUDIO_FORMAT_U8; 907 break; 908 case SDL_AUDIO_S8: 909 info->format = SPA_AUDIO_FORMAT_S8; 910 break; 911 case SDL_AUDIO_S16LE: 912 info->format = SPA_AUDIO_FORMAT_S16_LE; 913 break; 914 case SDL_AUDIO_S16BE: 915 info->format = SPA_AUDIO_FORMAT_S16_BE; 916 break; 917 case SDL_AUDIO_S32LE: 918 info->format = SPA_AUDIO_FORMAT_S32_LE; 919 break; 920 case SDL_AUDIO_S32BE: 921 info->format = SPA_AUDIO_FORMAT_S32_BE; 922 break; 923 case SDL_AUDIO_F32LE: 924 info->format = SPA_AUDIO_FORMAT_F32_LE; 925 break; 926 case SDL_AUDIO_F32BE: 927 info->format = SPA_AUDIO_FORMAT_F32_BE; 928 break; 929 default: 930 info->format = SPA_AUDIO_FORMAT_UNKNOWN; 931 break; 932 } 933} 934 935static Uint8 *PIPEWIRE_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) 936{ 937 // See if a buffer is available. If this returns NULL, SDL_PlaybackAudioThreadIterate will return false, but since we own the thread, it won't kill playback. 938 // !!! FIXME: It's not clear to me if this ever returns NULL or if this was just defensive coding. 939 940 struct pw_stream *stream = device->hidden->stream; 941 struct pw_buffer *pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream); 942 if (pw_buf == NULL) { 943 return NULL; 944 } 945 946 struct spa_buffer *spa_buf = pw_buf->buffer; 947 if (spa_buf->datas[0].data == NULL) { 948 PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); 949 return NULL; 950 } 951 952 device->hidden->pw_buf = pw_buf; 953 return (Uint8 *) spa_buf->datas[0].data; 954} 955 956static bool PIPEWIRE_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) 957{ 958 struct pw_stream *stream = device->hidden->stream; 959 struct pw_buffer *pw_buf = device->hidden->pw_buf; 960 struct spa_buffer *spa_buf = pw_buf->buffer; 961 spa_buf->datas[0].chunk->offset = 0; 962 spa_buf->datas[0].chunk->stride = device->hidden->stride; 963 spa_buf->datas[0].chunk->size = buffer_size; 964 965 PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); 966 device->hidden->pw_buf = NULL; 967 968 return true; 969} 970 971static void output_callback(void *data) 972{ 973 SDL_AudioDevice *device = (SDL_AudioDevice *) data; 974 975 // this callback can fire in a background thread during OpenDevice, while we're still blocking 976 // _with the device lock_ until the stream is ready, causing a deadlock. Write silence in this case. 977 if (device->hidden->stream_init_status != PW_READY_FLAG_ALL_BITS) { 978 int bufsize = 0; 979 Uint8 *buf = PIPEWIRE_GetDeviceBuf(device, &bufsize); 980 if (buf && bufsize) { 981 SDL_memset(buf, device->silence_value, bufsize); 982 } 983 PIPEWIRE_PlayDevice(device, buf, bufsize); 984 return; 985 } 986 987 SDL_PlaybackAudioThreadIterate(device); 988} 989 990static void PIPEWIRE_FlushRecording(SDL_AudioDevice *device) 991{ 992 struct pw_stream *stream = device->hidden->stream; 993 struct pw_buffer *pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream); 994 if (pw_buf) { // just requeue it without any further thought. 995 PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); 996 } 997} 998 999static int PIPEWIRE_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen) 1000{ 1001 struct pw_stream *stream = device->hidden->stream; 1002 struct pw_buffer *pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream); 1003 if (!pw_buf) { 1004 return 0; 1005 } 1006 1007 struct spa_buffer *spa_buf = pw_buf->buffer; 1008 if (!spa_buf) { 1009 PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); 1010 return 0; 1011 } 1012 1013 const Uint8 *src = (const Uint8 *)spa_buf->datas[0].data; 1014 const Uint32 offset = SPA_MIN(spa_buf->datas[0].chunk->offset, spa_buf->datas[0].maxsize); 1015 const Uint32 size = SPA_MIN(spa_buf->datas[0].chunk->size, spa_buf->datas[0].maxsize - offset); 1016 const int cpy = SDL_min(buflen, (int) size); 1017 1018 SDL_assert(size <= buflen); // We'll have to reengineer some stuff if this turns out to not be true. 1019 1020 SDL_memcpy(buffer, src + offset, cpy); 1021 PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); 1022 1023 return cpy; 1024} 1025 1026static void input_callback(void *data) 1027{ 1028 SDL_AudioDevice *device = (SDL_AudioDevice *) data; 1029 1030 // this callback can fire in a background thread during OpenDevice, while we're still blocking 1031 // _with the device lock_ until the stream is ready, causing a deadlock. Drop data in this case. 1032 if (device->hidden->stream_init_status != PW_READY_FLAG_ALL_BITS) { 1033 PIPEWIRE_FlushRecording(device); 1034 return; 1035 } 1036 1037 SDL_RecordingAudioThreadIterate(device); 1038} 1039 1040static void stream_add_buffer_callback(void *data, struct pw_buffer *buffer) 1041{ 1042 SDL_AudioDevice *device = (SDL_AudioDevice *) data; 1043 1044 if (device->recording == false) { 1045 /* Clamp the output spec samples and size to the max size of the Pipewire buffer. 1046 If they exceed the maximum size of the Pipewire buffer, double buffering will be used. */ 1047 if (device->buffer_size > buffer->buffer->datas[0].maxsize) { 1048 SDL_LockMutex(device->lock); 1049 device->sample_frames = buffer->buffer->datas[0].maxsize / device->hidden->stride; 1050 device->buffer_size = buffer->buffer->datas[0].maxsize; 1051 SDL_UnlockMutex(device->lock); 1052 } 1053 } 1054 1055 device->hidden->stream_init_status |= PW_READY_FLAG_BUFFER_ADDED; 1056 PIPEWIRE_pw_thread_loop_signal(device->hidden->loop, false); 1057} 1058 1059static void stream_state_changed_callback(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) 1060{ 1061 SDL_AudioDevice *device = (SDL_AudioDevice *) data; 1062 1063 if (state == PW_STREAM_STATE_STREAMING) { 1064 device->hidden->stream_init_status |= PW_READY_FLAG_STREAM_READY; 1065 } 1066 1067 if (state == PW_STREAM_STATE_STREAMING || state == PW_STREAM_STATE_ERROR) { 1068 PIPEWIRE_pw_thread_loop_signal(device->hidden->loop, false); 1069 } 1070} 1071 1072static const struct pw_stream_events stream_output_events = { PW_VERSION_STREAM_EVENTS, 1073 .state_changed = stream_state_changed_callback, 1074 .add_buffer = stream_add_buffer_callback, 1075 .process = output_callback }; 1076static const struct pw_stream_events stream_input_events = { PW_VERSION_STREAM_EVENTS, 1077 .state_changed = stream_state_changed_callback, 1078 .add_buffer = stream_add_buffer_callback, 1079 .process = input_callback }; 1080 1081static bool PIPEWIRE_OpenDevice(SDL_AudioDevice *device) 1082{ 1083 /* 1084 * NOTE: The PW_STREAM_FLAG_RT_PROCESS flag can be set to call the stream 1085 * processing callback from the realtime thread. However, it comes with some 1086 * caveats: no file IO, allocations, locking or other blocking operations 1087 * must occur in the mixer callback. As this cannot be guaranteed when the 1088 * callback is in the calling application, this flag is omitted. 1089 */ 1090 static const enum pw_stream_flags STREAM_FLAGS = PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS; 1091 1092 char thread_name[PW_THREAD_NAME_BUFFER_LENGTH]; 1093 Uint8 pod_buffer[PW_POD_BUFFER_LENGTH]; 1094 struct spa_pod_builder b = SPA_POD_BUILDER_INIT(pod_buffer, sizeof(pod_buffer)); 1095 struct spa_audio_info_raw spa_info = { 0 }; 1096 const struct spa_pod *params = NULL; 1097 struct SDL_PrivateAudioData *priv; 1098 struct pw_properties *props; 1099 const char *app_name, *icon_name, *app_id, *stream_name, *stream_role, *error; 1100 Uint32 node_id = !device->handle ? PW_ID_ANY : PW_HANDLE_TO_ID(device->handle); 1101 const bool recording = device->recording; 1102 int res; 1103 1104 // Clamp the period size to sane values 1105 const int min_period = PW_MIN_SAMPLES * SPA_MAX(device->spec.freq / PW_BASE_CLOCK_RATE, 1); 1106 1107 // Get the hints for the application name, icon name, stream name and role 1108 app_name = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING); 1109 1110 icon_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_ICON_NAME); 1111 if (!icon_name || *icon_name == '\0') { 1112 icon_name = "applications-games"; 1113 } 1114 1115 // App ID. Default to NULL if not available. 1116 app_id = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING); 1117 1118 stream_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME); 1119 if (!stream_name || *stream_name == '\0') { 1120 if (app_name) { 1121 stream_name = app_name; 1122 } else if (app_id) { 1123 stream_name = app_id; 1124 } else { 1125 stream_name = "SDL Audio Stream"; 1126 } 1127 } 1128 1129 /* 1130 * 'Music' is the default used internally by Pipewire and it's modules, 1131 * but 'Game' seems more appropriate for the majority of SDL applications. 1132 */ 1133 stream_role = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_ROLE); 1134 if (!stream_role || *stream_role == '\0') { 1135 stream_role = "Game"; 1136 } 1137 1138 // Initialize the Pipewire stream info from the SDL audio spec 1139 initialize_spa_info(&device->spec, &spa_info); 1140 params = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &spa_info); 1141 if (!params) { 1142 return SDL_SetError("Pipewire: Failed to set audio format parameters"); 1143 } 1144 1145 priv = SDL_calloc(1, sizeof(struct SDL_PrivateAudioData)); 1146 device->hidden = priv; 1147 if (!priv) { 1148 return false; 1149 } 1150 1151 // Size of a single audio frame in bytes 1152 priv->stride = SDL_AUDIO_FRAMESIZE(device->spec); 1153 1154 if (device->sample_frames < min_period) { 1155 device->sample_frames = min_period; 1156 } 1157 1158 SDL_UpdatedAudioDeviceFormat(device); 1159 1160 SDL_GetAudioThreadName(device, thread_name, sizeof(thread_name)); 1161 priv->loop = PIPEWIRE_pw_thread_loop_new(thread_name, NULL); 1162 if (!priv->loop) { 1163 return SDL_SetError("Pipewire: Failed to create stream loop (%i)", errno); 1164 } 1165 1166 // Load the realtime module so Pipewire can set the loop thread to the appropriate priority. 1167 props = PIPEWIRE_pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", NULL); 1168 if (!props) { 1169 return SDL_SetError("Pipewire: Failed to create stream context properties (%i)", errno); 1170 } 1171 1172 priv->context = PIPEWIRE_pw_context_new(PIPEWIRE_pw_thread_loop_get_loop(priv->loop), props, 0); 1173 if (!priv->context) { 1174 return SDL_SetError("Pipewire: Failed to create stream context (%i)", errno); 1175 } 1176 1177 props = PIPEWIRE_pw_properties_new(NULL, NULL); 1178 if (!props) { 1179 return SDL_SetError("Pipewire: Failed to create stream properties (%i)", errno); 1180 } 1181 1182 PIPEWIRE_pw_properties_set(props, PW_KEY_MEDIA_TYPE, "Audio"); 1183 PIPEWIRE_pw_properties_set(props, PW_KEY_MEDIA_CATEGORY, recording ? "Capture" : "Playback"); 1184 PIPEWIRE_pw_properties_set(props, PW_KEY_MEDIA_ROLE, stream_role); 1185 PIPEWIRE_pw_properties_set(props, PW_KEY_APP_NAME, app_name); 1186 PIPEWIRE_pw_properties_set(props, PW_KEY_APP_ICON_NAME, icon_name); 1187 if (app_id) { 1188 PIPEWIRE_pw_properties_set(props, PW_KEY_APP_ID, app_id); 1189 } 1190 PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_NAME, stream_name); 1191 PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, stream_name); 1192 PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%i", device->sample_frames, device->spec.freq); 1193 PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", device->spec.freq); 1194 PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true"); 1195 1196 // UPDATE: This prevents users from moving the audio to a new sink (device) using standard tools. This is slightly in conflict 1197 // with how SDL wants to manage audio devices, but if people want to do it, we should let them, so this is commented out 1198 // for now. We might revisit later. 1199 //PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_DONT_RECONNECT, "true"); // Requesting a specific device, don't migrate to new default hardware. 1200 1201 if (node_id != PW_ID_ANY) { 1202 PIPEWIRE_pw_thread_loop_lock(hotplug_loop); 1203 const struct io_node *node = io_list_get_by_id(node_id); 1204 if (node) { 1205 PIPEWIRE_pw_properties_set(props, PW_KEY_TARGET_OBJECT, node->path); 1206 } 1207 PIPEWIRE_pw_thread_loop_unlock(hotplug_loop); 1208 } 1209 1210 // Create the new stream 1211 priv->stream = PIPEWIRE_pw_stream_new_simple(PIPEWIRE_pw_thread_loop_get_loop(priv->loop), stream_name, props, 1212 recording ? &stream_input_events : &stream_output_events, device); 1213 if (!priv->stream) { 1214 return SDL_SetError("Pipewire: Failed to create stream (%i)", errno); 1215 } 1216 1217 // The target node is passed via PW_KEY_TARGET_OBJECT; target_id is a legacy parameter and must be PW_ID_ANY. 1218 res = PIPEWIRE_pw_stream_connect(priv->stream, recording ? PW_DIRECTION_INPUT : PW_DIRECTION_OUTPUT, PW_ID_ANY, STREAM_FLAGS, 1219 ¶ms, 1); 1220 if (res != 0) { 1221 return SDL_SetError("Pipewire: Failed to connect stream"); 1222 } 1223 1224 res = PIPEWIRE_pw_thread_loop_start(priv->loop); 1225 if (res != 0) { 1226 return SDL_SetError("Pipewire: Failed to start stream loop"); 1227 } 1228 1229 // Wait until all pre-open init flags are set or the stream has failed. 1230 PIPEWIRE_pw_thread_loop_lock(priv->loop); 1231 while (priv->stream_init_status != PW_READY_FLAG_ALL_PREOPEN_BITS && 1232 PIPEWIRE_pw_stream_get_state(priv->stream, NULL) != PW_STREAM_STATE_ERROR) { 1233 PIPEWIRE_pw_thread_loop_wait(priv->loop); 1234 } 1235 priv->stream_init_status |= PW_READY_FLAG_OPEN_COMPLETE; 1236 PIPEWIRE_pw_thread_loop_unlock(priv->loop); 1237 1238 if (PIPEWIRE_pw_stream_get_state(priv->stream, &error) == PW_STREAM_STATE_ERROR) { 1239 return SDL_SetError("Pipewire: Stream error: %s", error); 1240 } 1241 1242 return true; 1243} 1244 1245static void PIPEWIRE_CloseDevice(SDL_AudioDevice *device) 1246{ 1247 if (!device->hidden) { 1248 return; 1249 } 1250 1251 if (device->hidden->loop) { 1252 PIPEWIRE_pw_thread_loop_stop(device->hidden->loop); 1253 } 1254 1255 if (device->hidden->stream) { 1256 PIPEWIRE_pw_stream_destroy(device->hidden->stream); 1257 } 1258 1259 if (device->hidden->context) { 1260 PIPEWIRE_pw_context_destroy(device->hidden->context); 1261 } 1262 1263 if (device->hidden->loop) { 1264 PIPEWIRE_pw_thread_loop_destroy(device->hidden->loop); 1265 } 1266 1267 SDL_free(device->hidden); 1268 device->hidden = NULL; 1269 1270 SDL_AudioThreadFinalize(device); 1271} 1272 1273static void PIPEWIRE_DeinitializeStart(void) 1274{ 1275 if (pipewire_initialized) { 1276 hotplug_loop_destroy(); 1277 } 1278} 1279 1280static void PIPEWIRE_Deinitialize(void) 1281{ 1282 if (pipewire_initialized) { 1283 hotplug_loop_destroy(); 1284 deinit_pipewire_library(); 1285 pipewire_initialized = false; 1286 } 1287} 1288 1289static bool PipewireInitialize(SDL_AudioDriverImpl *impl) 1290{ 1291 if (!pipewire_initialized) { 1292 if (!init_pipewire_library()) { 1293 return false; 1294 } 1295 1296 pipewire_initialized = true; 1297 1298 if (!hotplug_loop_init()) { 1299 PIPEWIRE_Deinitialize(); 1300 return false; 1301 } 1302 } 1303 1304 impl->DetectDevices = PIPEWIRE_DetectDevices; 1305 impl->OpenDevice = PIPEWIRE_OpenDevice; 1306 impl->DeinitializeStart = PIPEWIRE_DeinitializeStart; 1307 impl->Deinitialize = PIPEWIRE_Deinitialize; 1308 impl->PlayDevice = PIPEWIRE_PlayDevice; 1309 impl->GetDeviceBuf = PIPEWIRE_GetDeviceBuf; 1310 impl->RecordDevice = PIPEWIRE_RecordDevice; 1311 impl->FlushRecording = PIPEWIRE_FlushRecording; 1312 impl->CloseDevice = PIPEWIRE_CloseDevice; 1313 1314 impl->HasRecordingSupport = true; 1315 impl->ProvidesOwnCallbackThread = true; 1316 1317 return true; 1318} 1319 1320static bool PIPEWIRE_PREFERRED_Init(SDL_AudioDriverImpl *impl) 1321{ 1322 if (!PipewireInitialize(impl)) { 1323 return false; 1324 } 1325 1326 // run device detection but don't add any devices to SDL; we're just waiting to see if PipeWire sees any devices. If not, fall back to the next backend. 1327 PIPEWIRE_pw_thread_loop_lock(hotplug_loop); 1328 1329 // Wait until the initial registry enumeration is complete 1330 if (!hotplug_init_complete) { 1331 PIPEWIRE_pw_thread_loop_wait(hotplug_loop); 1332 } 1333 1334 const bool no_devices = spa_list_is_empty(&hotplug_io_list); 1335 1336 PIPEWIRE_pw_thread_loop_unlock(hotplug_loop); 1337 1338 if (no_devices || !pipewire_core_version_at_least(1, 0, 0)) { 1339 PIPEWIRE_Deinitialize(); 1340 return false; 1341 } 1342 1343 return true; // this will move on to PIPEWIRE_DetectDevices and reuse hotplug_io_list. 1344} 1345 1346static bool PIPEWIRE_Init(SDL_AudioDriverImpl *impl) 1347{ 1348 return PipewireInitialize(impl); 1349} 1350 1351AudioBootStrap PIPEWIRE_PREFERRED_bootstrap = { 1352 "pipewire", "Pipewire", PIPEWIRE_PREFERRED_Init, false, true 1353}; 1354AudioBootStrap PIPEWIRE_bootstrap = { 1355 "pipewire", "Pipewire", PIPEWIRE_Init, false, false 1356}; 1357 1358#endif // SDL_AUDIO_DRIVER_PIPEWIRE 1359[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.