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