Atlas - SDL_pulseaudio.c

Home / ext / SDL2 / src / audio / pulseaudio Lines: 4 | Size: 26713 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2018 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/* 23 The PulseAudio target for SDL 1.3 is based on the 1.3 arts target, with 24 the appropriate parts replaced with the 1.2 PulseAudio target code. This 25 was the cleanest way to move it to 1.3. The 1.2 target was written by 26 Stéphan Kochen: stephan .a.t. kochen.nl 27*/ 28#include "../../SDL_internal.h" 29#include "SDL_assert.h" 30 31#if SDL_AUDIO_DRIVER_PULSEAUDIO 32 33/* Allow access to a raw mixing buffer */ 34 35#ifdef HAVE_SIGNAL_H 36#include <signal.h> 37#endif 38#include <unistd.h> 39#include <sys/types.h> 40#include <pulse/pulseaudio.h> 41 42#include "SDL_timer.h" 43#include "SDL_audio.h" 44#include "../SDL_audio_c.h" 45#include "SDL_pulseaudio.h" 46#include "SDL_loadso.h" 47#include "../../thread/SDL_systhread.h" 48 49#if (PA_API_VERSION < 12) 50/** Return non-zero if the passed state is one of the connected states */ 51static SDL_INLINE int PA_CONTEXT_IS_GOOD(pa_context_state_t x) { 52 return 53 x == PA_CONTEXT_CONNECTING || 54 x == PA_CONTEXT_AUTHORIZING || 55 x == PA_CONTEXT_SETTING_NAME || 56 x == PA_CONTEXT_READY; 57} 58/** Return non-zero if the passed state is one of the connected states */ 59static SDL_INLINE int PA_STREAM_IS_GOOD(pa_stream_state_t x) { 60 return 61 x == PA_STREAM_CREATING || 62 x == PA_STREAM_READY; 63} 64#endif /* pulseaudio <= 0.9.10 */ 65 66 67static const char *(*PULSEAUDIO_pa_get_library_version) (void); 68static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto) ( 69 pa_channel_map *, unsigned, pa_channel_map_def_t); 70static const char * (*PULSEAUDIO_pa_strerror) (int); 71static pa_mainloop * (*PULSEAUDIO_pa_mainloop_new) (void); 72static pa_mainloop_api * (*PULSEAUDIO_pa_mainloop_get_api) (pa_mainloop *); 73static int (*PULSEAUDIO_pa_mainloop_iterate) (pa_mainloop *, int, int *); 74static int (*PULSEAUDIO_pa_mainloop_run) (pa_mainloop *, int *); 75static void (*PULSEAUDIO_pa_mainloop_quit) (pa_mainloop *, int); 76static void (*PULSEAUDIO_pa_mainloop_free) (pa_mainloop *); 77 78static pa_operation_state_t (*PULSEAUDIO_pa_operation_get_state) ( 79 pa_operation *); 80static void (*PULSEAUDIO_pa_operation_cancel) (pa_operation *); 81static void (*PULSEAUDIO_pa_operation_unref) (pa_operation *); 82 83static pa_context * (*PULSEAUDIO_pa_context_new) (pa_mainloop_api *, 84 const char *); 85static int (*PULSEAUDIO_pa_context_connect) (pa_context *, const char *, 86 pa_context_flags_t, const pa_spawn_api *); 87static pa_operation * (*PULSEAUDIO_pa_context_get_sink_info_list) (pa_context *, pa_sink_info_cb_t, void *); 88static pa_operation * (*PULSEAUDIO_pa_context_get_source_info_list) (pa_context *, pa_source_info_cb_t, void *); 89static pa_operation * (*PULSEAUDIO_pa_context_get_sink_info_by_index) (pa_context *, uint32_t, pa_sink_info_cb_t, void *); 90static pa_operation * (*PULSEAUDIO_pa_context_get_source_info_by_index) (pa_context *, uint32_t, pa_source_info_cb_t, void *); 91static pa_context_state_t (*PULSEAUDIO_pa_context_get_state) (pa_context *); 92static pa_operation * (*PULSEAUDIO_pa_context_subscribe) (pa_context *, pa_subscription_mask_t, pa_context_success_cb_t, void *); 93static void (*PULSEAUDIO_pa_context_set_subscribe_callback) (pa_context *, pa_context_subscribe_cb_t, void *); 94static void (*PULSEAUDIO_pa_context_disconnect) (pa_context *); 95static void (*PULSEAUDIO_pa_context_unref) (pa_context *); 96 97static pa_stream * (*PULSEAUDIO_pa_stream_new) (pa_context *, const char *, 98 const pa_sample_spec *, const pa_channel_map *); 99static int (*PULSEAUDIO_pa_stream_connect_playback) (pa_stream *, const char *, 100 const pa_buffer_attr *, pa_stream_flags_t, pa_cvolume *, pa_stream *); 101static int (*PULSEAUDIO_pa_stream_connect_record) (pa_stream *, const char *, 102 const pa_buffer_attr *, pa_stream_flags_t); 103static pa_stream_state_t (*PULSEAUDIO_pa_stream_get_state) (pa_stream *); 104static size_t (*PULSEAUDIO_pa_stream_writable_size) (pa_stream *); 105static size_t (*PULSEAUDIO_pa_stream_readable_size) (pa_stream *); 106static int (*PULSEAUDIO_pa_stream_write) (pa_stream *, const void *, size_t, 107 pa_free_cb_t, int64_t, pa_seek_mode_t); 108static pa_operation * (*PULSEAUDIO_pa_stream_drain) (pa_stream *, 109 pa_stream_success_cb_t, void *); 110static int (*PULSEAUDIO_pa_stream_peek) (pa_stream *, const void **, size_t *); 111static int (*PULSEAUDIO_pa_stream_drop) (pa_stream *); 112static pa_operation * (*PULSEAUDIO_pa_stream_flush) (pa_stream *, 113 pa_stream_success_cb_t, void *); 114static int (*PULSEAUDIO_pa_stream_disconnect) (pa_stream *); 115static void (*PULSEAUDIO_pa_stream_unref) (pa_stream *); 116 117static int load_pulseaudio_syms(void); 118 119 120#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC 121 122static const char *pulseaudio_library = SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC; 123static void *pulseaudio_handle = NULL; 124 125static int 126load_pulseaudio_sym(const char *fn, void **addr) 127{ 128 *addr = SDL_LoadFunction(pulseaudio_handle, fn); 129 if (*addr == NULL) { 130 /* Don't call SDL_SetError(): SDL_LoadFunction already did. */ 131 return 0; 132 } 133 134 return 1; 135} 136 137/* cast funcs to char* first, to please GCC's strict aliasing rules. */ 138#define SDL_PULSEAUDIO_SYM(x) \ 139 if (!load_pulseaudio_sym(#x, (void **) (char *) &PULSEAUDIO_##x)) return -1 140 141static void 142UnloadPulseAudioLibrary(void) 143{ 144 if (pulseaudio_handle != NULL) { 145 SDL_UnloadObject(pulseaudio_handle); 146 pulseaudio_handle = NULL; 147 } 148} 149 150static int 151LoadPulseAudioLibrary(void) 152{ 153 int retval = 0; 154 if (pulseaudio_handle == NULL) { 155 pulseaudio_handle = SDL_LoadObject(pulseaudio_library); 156 if (pulseaudio_handle == NULL) { 157 retval = -1; 158 /* Don't call SDL_SetError(): SDL_LoadObject already did. */ 159 } else { 160 retval = load_pulseaudio_syms(); 161 if (retval < 0) { 162 UnloadPulseAudioLibrary(); 163 } 164 } 165 } 166 return retval; 167} 168 169#else 170 171#define SDL_PULSEAUDIO_SYM(x) PULSEAUDIO_##x = x 172 173static void 174UnloadPulseAudioLibrary(void) 175{ 176} 177 178static int 179LoadPulseAudioLibrary(void) 180{ 181 load_pulseaudio_syms(); 182 return 0; 183} 184 185#endif /* SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC */ 186 187 188static int 189load_pulseaudio_syms(void) 190{ 191 SDL_PULSEAUDIO_SYM(pa_get_library_version); 192 SDL_PULSEAUDIO_SYM(pa_mainloop_new); 193 SDL_PULSEAUDIO_SYM(pa_mainloop_get_api); 194 SDL_PULSEAUDIO_SYM(pa_mainloop_iterate); 195 SDL_PULSEAUDIO_SYM(pa_mainloop_run); 196 SDL_PULSEAUDIO_SYM(pa_mainloop_quit); 197 SDL_PULSEAUDIO_SYM(pa_mainloop_free); 198 SDL_PULSEAUDIO_SYM(pa_operation_get_state); 199 SDL_PULSEAUDIO_SYM(pa_operation_cancel); 200 SDL_PULSEAUDIO_SYM(pa_operation_unref); 201 SDL_PULSEAUDIO_SYM(pa_context_new); 202 SDL_PULSEAUDIO_SYM(pa_context_connect); 203 SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_list); 204 SDL_PULSEAUDIO_SYM(pa_context_get_source_info_list); 205 SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_by_index); 206 SDL_PULSEAUDIO_SYM(pa_context_get_source_info_by_index); 207 SDL_PULSEAUDIO_SYM(pa_context_get_state); 208 SDL_PULSEAUDIO_SYM(pa_context_subscribe); 209 SDL_PULSEAUDIO_SYM(pa_context_set_subscribe_callback); 210 SDL_PULSEAUDIO_SYM(pa_context_disconnect); 211 SDL_PULSEAUDIO_SYM(pa_context_unref); 212 SDL_PULSEAUDIO_SYM(pa_stream_new); 213 SDL_PULSEAUDIO_SYM(pa_stream_connect_playback); 214 SDL_PULSEAUDIO_SYM(pa_stream_connect_record); 215 SDL_PULSEAUDIO_SYM(pa_stream_get_state); 216 SDL_PULSEAUDIO_SYM(pa_stream_writable_size); 217 SDL_PULSEAUDIO_SYM(pa_stream_readable_size); 218 SDL_PULSEAUDIO_SYM(pa_stream_write); 219 SDL_PULSEAUDIO_SYM(pa_stream_drain); 220 SDL_PULSEAUDIO_SYM(pa_stream_disconnect); 221 SDL_PULSEAUDIO_SYM(pa_stream_peek); 222 SDL_PULSEAUDIO_SYM(pa_stream_drop); 223 SDL_PULSEAUDIO_SYM(pa_stream_flush); 224 SDL_PULSEAUDIO_SYM(pa_stream_unref); 225 SDL_PULSEAUDIO_SYM(pa_channel_map_init_auto); 226 SDL_PULSEAUDIO_SYM(pa_strerror); 227 return 0; 228} 229 230static SDL_INLINE int 231squashVersion(const int major, const int minor, const int patch) 232{ 233 return ((major & 0xFF) << 16) | ((minor & 0xFF) << 8) | (patch & 0xFF); 234} 235 236/* Workaround for older pulse: pa_context_new() must have non-NULL appname */ 237static const char * 238getAppName(void) 239{ 240 const char *verstr = PULSEAUDIO_pa_get_library_version(); 241 if (verstr != NULL) { 242 int maj, min, patch; 243 if (SDL_sscanf(verstr, "%d.%d.%d", &maj, &min, &patch) == 3) { 244 if (squashVersion(maj, min, patch) >= squashVersion(0, 9, 15)) { 245 return NULL; /* 0.9.15+ handles NULL correctly. */ 246 } 247 } 248 } 249 return "SDL Application"; /* oh well. */ 250} 251 252static void 253WaitForPulseOperation(pa_mainloop *mainloop, pa_operation *o) 254{ 255 /* This checks for NO errors currently. Either fix that, check results elsewhere, or do things you don't care about. */ 256 if (mainloop && o) { 257 SDL_bool okay = SDL_TRUE; 258 while (okay && (PULSEAUDIO_pa_operation_get_state(o) == PA_OPERATION_RUNNING)) { 259 okay = (PULSEAUDIO_pa_mainloop_iterate(mainloop, 1, NULL) >= 0); 260 } 261 PULSEAUDIO_pa_operation_unref(o); 262 } 263} 264 265static void 266DisconnectFromPulseServer(pa_mainloop *mainloop, pa_context *context) 267{ 268 if (context) { 269 PULSEAUDIO_pa_context_disconnect(context); 270 PULSEAUDIO_pa_context_unref(context); 271 } 272 if (mainloop != NULL) { 273 PULSEAUDIO_pa_mainloop_free(mainloop); 274 } 275} 276 277static int 278ConnectToPulseServer_Internal(pa_mainloop **_mainloop, pa_context **_context) 279{ 280 pa_mainloop *mainloop = NULL; 281 pa_context *context = NULL; 282 pa_mainloop_api *mainloop_api = NULL; 283 int state = 0; 284 285 *_mainloop = NULL; 286 *_context = NULL; 287 288 /* Set up a new main loop */ 289 if (!(mainloop = PULSEAUDIO_pa_mainloop_new())) { 290 return SDL_SetError("pa_mainloop_new() failed"); 291 } 292 293 *_mainloop = mainloop; 294 295 mainloop_api = PULSEAUDIO_pa_mainloop_get_api(mainloop); 296 SDL_assert(mainloop_api); /* this never fails, right? */ 297 298 context = PULSEAUDIO_pa_context_new(mainloop_api, getAppName()); 299 if (!context) { 300 return SDL_SetError("pa_context_new() failed"); 301 } 302 *_context = context; 303 304 /* Connect to the PulseAudio server */ 305 if (PULSEAUDIO_pa_context_connect(context, NULL, 0, NULL) < 0) { 306 return SDL_SetError("Could not setup connection to PulseAudio"); 307 } 308 309 do { 310 if (PULSEAUDIO_pa_mainloop_iterate(mainloop, 1, NULL) < 0) { 311 return SDL_SetError("pa_mainloop_iterate() failed"); 312 } 313 state = PULSEAUDIO_pa_context_get_state(context); 314 if (!PA_CONTEXT_IS_GOOD(state)) { 315 return SDL_SetError("Could not connect to PulseAudio"); 316 } 317 } while (state != PA_CONTEXT_READY); 318 319 return 0; /* connected and ready! */ 320} 321 322static int 323ConnectToPulseServer(pa_mainloop **_mainloop, pa_context **_context) 324{ 325 const int retval = ConnectToPulseServer_Internal(_mainloop, _context); 326 if (retval < 0) { 327 DisconnectFromPulseServer(*_mainloop, *_context); 328 } 329 return retval; 330} 331 332 333/* This function waits until it is possible to write a full sound buffer */ 334static void 335PULSEAUDIO_WaitDevice(_THIS) 336{ 337 struct SDL_PrivateAudioData *h = this->hidden; 338 339 while (SDL_AtomicGet(&this->enabled)) { 340 if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY || 341 PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY || 342 PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) { 343 SDL_OpenedAudioDeviceDisconnected(this); 344 return; 345 } 346 if (PULSEAUDIO_pa_stream_writable_size(h->stream) >= h->mixlen) { 347 return; 348 } 349 } 350} 351 352static void 353PULSEAUDIO_PlayDevice(_THIS) 354{ 355 /* Write the audio data */ 356 struct SDL_PrivateAudioData *h = this->hidden; 357 if (SDL_AtomicGet(&this->enabled)) { 358 if (PULSEAUDIO_pa_stream_write(h->stream, h->mixbuf, h->mixlen, NULL, 0LL, PA_SEEK_RELATIVE) < 0) { 359 SDL_OpenedAudioDeviceDisconnected(this); 360 } 361 } 362} 363 364static Uint8 * 365PULSEAUDIO_GetDeviceBuf(_THIS) 366{ 367 return (this->hidden->mixbuf); 368} 369 370 371static int 372PULSEAUDIO_CaptureFromDevice(_THIS, void *buffer, int buflen) 373{ 374 struct SDL_PrivateAudioData *h = this->hidden; 375 const void *data = NULL; 376 size_t nbytes = 0; 377 378 while (SDL_AtomicGet(&this->enabled)) { 379 if (h->capturebuf != NULL) { 380 const int cpy = SDL_min(buflen, h->capturelen); 381 SDL_memcpy(buffer, h->capturebuf, cpy); 382 /*printf("PULSEAUDIO: fed %d captured bytes\n", cpy);*/ 383 h->capturebuf += cpy; 384 h->capturelen -= cpy; 385 if (h->capturelen == 0) { 386 h->capturebuf = NULL; 387 PULSEAUDIO_pa_stream_drop(h->stream); /* done with this fragment. */ 388 } 389 return cpy; /* new data, return it. */ 390 } 391 392 if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY || 393 PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY || 394 PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) { 395 SDL_OpenedAudioDeviceDisconnected(this); 396 return -1; /* uhoh, pulse failed! */ 397 } 398 399 if (PULSEAUDIO_pa_stream_readable_size(h->stream) == 0) { 400 continue; /* no data available yet. */ 401 } 402 403 /* a new fragment is available! */ 404 PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes); 405 SDL_assert(nbytes > 0); 406 if (data == NULL) { /* NULL==buffer had a hole. Ignore that. */ 407 PULSEAUDIO_pa_stream_drop(h->stream); /* drop this fragment. */ 408 } else { 409 /* store this fragment's data, start feeding it to SDL. */ 410 /*printf("PULSEAUDIO: captured %d new bytes\n", (int) nbytes);*/ 411 h->capturebuf = (const Uint8 *) data; 412 h->capturelen = nbytes; 413 } 414 } 415 416 return -1; /* not enabled? */ 417} 418 419static void 420PULSEAUDIO_FlushCapture(_THIS) 421{ 422 struct SDL_PrivateAudioData *h = this->hidden; 423 const void *data = NULL; 424 size_t nbytes = 0; 425 426 if (h->capturebuf != NULL) { 427 PULSEAUDIO_pa_stream_drop(h->stream); 428 h->capturebuf = NULL; 429 h->capturelen = 0; 430 } 431 432 while (SDL_TRUE) { 433 if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY || 434 PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY || 435 PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) { 436 SDL_OpenedAudioDeviceDisconnected(this); 437 return; /* uhoh, pulse failed! */ 438 } 439 440 if (PULSEAUDIO_pa_stream_readable_size(h->stream) == 0) { 441 break; /* no data available, so we're done. */ 442 } 443 444 /* a new fragment is available! Just dump it. */ 445 PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes); 446 PULSEAUDIO_pa_stream_drop(h->stream); /* drop this fragment. */ 447 } 448} 449 450static void 451PULSEAUDIO_CloseDevice(_THIS) 452{ 453 if (this->hidden->stream) { 454 if (this->hidden->capturebuf != NULL) { 455 PULSEAUDIO_pa_stream_drop(this->hidden->stream); 456 } 457 PULSEAUDIO_pa_stream_disconnect(this->hidden->stream); 458 PULSEAUDIO_pa_stream_unref(this->hidden->stream); 459 } 460 461 DisconnectFromPulseServer(this->hidden->mainloop, this->hidden->context); 462 SDL_free(this->hidden->mixbuf); 463 SDL_free(this->hidden->device_name); 464 SDL_free(this->hidden); 465} 466 467static void 468SinkDeviceNameCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data) 469{ 470 if (i) { 471 char **devname = (char **) data; 472 *devname = SDL_strdup(i->name); 473 } 474} 475 476static void 477SourceDeviceNameCallback(pa_context *c, const pa_source_info *i, int is_last, void *data) 478{ 479 if (i) { 480 char **devname = (char **) data; 481 *devname = SDL_strdup(i->name); 482 } 483} 484 485static SDL_bool 486FindDeviceName(struct SDL_PrivateAudioData *h, const int iscapture, void *handle) 487{ 488 const uint32_t idx = ((uint32_t) ((size_t) handle)) - 1; 489 490 if (handle == NULL) { /* NULL == default device. */ 491 return SDL_TRUE; 492 } 493 494 if (iscapture) { 495 WaitForPulseOperation(h->mainloop, 496 PULSEAUDIO_pa_context_get_source_info_by_index(h->context, idx, 497 SourceDeviceNameCallback, &h->device_name)); 498 } else { 499 WaitForPulseOperation(h->mainloop, 500 PULSEAUDIO_pa_context_get_sink_info_by_index(h->context, idx, 501 SinkDeviceNameCallback, &h->device_name)); 502 } 503 504 return (h->device_name != NULL); 505} 506 507static int 508PULSEAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture) 509{ 510 struct SDL_PrivateAudioData *h = NULL; 511 Uint16 test_format = 0; 512 pa_sample_spec paspec; 513 pa_buffer_attr paattr; 514 pa_channel_map pacmap; 515 pa_stream_flags_t flags = 0; 516 int state = 0; 517 int rc = 0; 518 519 /* Initialize all variables that we clean on shutdown */ 520 h = this->hidden = (struct SDL_PrivateAudioData *) 521 SDL_malloc((sizeof *this->hidden)); 522 if (this->hidden == NULL) { 523 return SDL_OutOfMemory(); 524 } 525 SDL_zerop(this->hidden); 526 527 paspec.format = PA_SAMPLE_INVALID; 528 529 /* Try for a closest match on audio format */ 530 for (test_format = SDL_FirstAudioFormat(this->spec.format); 531 (paspec.format == PA_SAMPLE_INVALID) && test_format;) { 532#ifdef DEBUG_AUDIO 533 fprintf(stderr, "Trying format 0x%4.4x\n", test_format); 534#endif 535 switch (test_format) { 536 case AUDIO_U8: 537 paspec.format = PA_SAMPLE_U8; 538 break; 539 case AUDIO_S16LSB: 540 paspec.format = PA_SAMPLE_S16LE; 541 break; 542 case AUDIO_S16MSB: 543 paspec.format = PA_SAMPLE_S16BE; 544 break; 545 case AUDIO_S32LSB: 546 paspec.format = PA_SAMPLE_S32LE; 547 break; 548 case AUDIO_S32MSB: 549 paspec.format = PA_SAMPLE_S32BE; 550 break; 551 case AUDIO_F32LSB: 552 paspec.format = PA_SAMPLE_FLOAT32LE; 553 break; 554 case AUDIO_F32MSB: 555 paspec.format = PA_SAMPLE_FLOAT32BE; 556 break; 557 default: 558 paspec.format = PA_SAMPLE_INVALID; 559 break; 560 } 561 if (paspec.format == PA_SAMPLE_INVALID) { 562 test_format = SDL_NextAudioFormat(); 563 } 564 } 565 if (paspec.format == PA_SAMPLE_INVALID) { 566 return SDL_SetError("Couldn't find any hardware audio formats"); 567 } 568 this->spec.format = test_format; 569 570 /* Calculate the final parameters for this audio specification */ 571#ifdef PA_STREAM_ADJUST_LATENCY 572 this->spec.samples /= 2; /* Mix in smaller chunck to avoid underruns */ 573#endif 574 SDL_CalculateAudioSpec(&this->spec); 575 576 /* Allocate mixing buffer */ 577 if (!iscapture) { 578 h->mixlen = this->spec.size; 579 h->mixbuf = (Uint8 *) SDL_malloc(h->mixlen); 580 if (h->mixbuf == NULL) { 581 return SDL_OutOfMemory(); 582 } 583 SDL_memset(h->mixbuf, this->spec.silence, this->spec.size); 584 } 585 586 paspec.channels = this->spec.channels; 587 paspec.rate = this->spec.freq; 588 589 /* Reduced prebuffering compared to the defaults. */ 590#ifdef PA_STREAM_ADJUST_LATENCY 591 /* 2x original requested bufsize */ 592 paattr.tlength = h->mixlen * 4; 593 paattr.prebuf = -1; 594 paattr.maxlength = -1; 595 /* -1 can lead to pa_stream_writable_size() >= mixlen never being true */ 596 paattr.minreq = h->mixlen; 597 flags = PA_STREAM_ADJUST_LATENCY; 598#else 599 paattr.tlength = h->mixlen*2; 600 paattr.prebuf = h->mixlen*2; 601 paattr.maxlength = h->mixlen*2; 602 paattr.minreq = h->mixlen; 603#endif 604 605 if (ConnectToPulseServer(&h->mainloop, &h->context) < 0) { 606 return SDL_SetError("Could not connect to PulseAudio server"); 607 } 608 609 if (!FindDeviceName(h, iscapture, handle)) { 610 return SDL_SetError("Requested PulseAudio sink/source missing?"); 611 } 612 613 /* The SDL ALSA output hints us that we use Windows' channel mapping */ 614 /* http://bugzilla.libsdl.org/show_bug.cgi?id=110 */ 615 PULSEAUDIO_pa_channel_map_init_auto(&pacmap, this->spec.channels, 616 PA_CHANNEL_MAP_WAVEEX); 617 618 h->stream = PULSEAUDIO_pa_stream_new( 619 h->context, 620 "Simple DirectMedia Layer", /* stream description */ 621 &paspec, /* sample format spec */ 622 &pacmap /* channel map */ 623 ); 624 625 if (h->stream == NULL) { 626 return SDL_SetError("Could not set up PulseAudio stream"); 627 } 628 629 /* now that we have multi-device support, don't move a stream from 630 a device that was unplugged to something else, unless we're default. */ 631 if (h->device_name != NULL) { 632 flags |= PA_STREAM_DONT_MOVE; 633 } 634 635 if (iscapture) { 636 rc = PULSEAUDIO_pa_stream_connect_record(h->stream, h->device_name, &paattr, flags); 637 } else { 638 rc = PULSEAUDIO_pa_stream_connect_playback(h->stream, h->device_name, &paattr, flags, NULL, NULL); 639 } 640 641 if (rc < 0) { 642 return SDL_SetError("Could not connect PulseAudio stream"); 643 } 644 645 do { 646 if (PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) { 647 return SDL_SetError("pa_mainloop_iterate() failed"); 648 } 649 state = PULSEAUDIO_pa_stream_get_state(h->stream); 650 if (!PA_STREAM_IS_GOOD(state)) { 651 return SDL_SetError("Could not connect PulseAudio stream"); 652 } 653 } while (state != PA_STREAM_READY); 654 655 /* We're ready to rock and roll. :-) */ 656 return 0; 657} 658 659static pa_mainloop *hotplug_mainloop = NULL; 660static pa_context *hotplug_context = NULL; 661static SDL_Thread *hotplug_thread = NULL; 662 663/* device handles are device index + 1, cast to void*, so we never pass a NULL. */ 664 665/* This is called when PulseAudio adds an output ("sink") device. */ 666static void 667SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data) 668{ 669 if (i) { 670 SDL_AddAudioDevice(SDL_FALSE, i->description, (void *) ((size_t) i->index+1)); 671 } 672} 673 674/* This is called when PulseAudio adds a capture ("source") device. */ 675static void 676SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_last, void *data) 677{ 678 if (i) { 679 /* Skip "monitor" sources. These are just output from other sinks. */ 680 if (i->monitor_of_sink == PA_INVALID_INDEX) { 681 SDL_AddAudioDevice(SDL_TRUE, i->description, (void *) ((size_t) i->index+1)); 682 } 683 } 684} 685 686/* This is called when PulseAudio has a device connected/removed/changed. */ 687static void 688HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *data) 689{ 690 const SDL_bool added = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW); 691 const SDL_bool removed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE); 692 693 if (added || removed) { /* we only care about add/remove events. */ 694 const SDL_bool sink = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK); 695 const SDL_bool source = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE); 696 697 /* adds need sink details from the PulseAudio server. Another callback... */ 698 if (added && sink) { 699 PULSEAUDIO_pa_context_get_sink_info_by_index(hotplug_context, idx, SinkInfoCallback, NULL); 700 } else if (added && source) { 701 PULSEAUDIO_pa_context_get_source_info_by_index(hotplug_context, idx, SourceInfoCallback, NULL); 702 } else if (removed && (sink || source)) { 703 /* removes we can handle just with the device index. */ 704 SDL_RemoveAudioDevice(source != 0, (void *) ((size_t) idx+1)); 705 } 706 } 707} 708 709/* this runs as a thread while the Pulse target is initialized to catch hotplug events. */ 710static int SDLCALL 711HotplugThread(void *data) 712{ 713 pa_operation *o; 714 SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW); 715 PULSEAUDIO_pa_context_set_subscribe_callback(hotplug_context, HotplugCallback, NULL); 716 o = PULSEAUDIO_pa_context_subscribe(hotplug_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, NULL, NULL); 717 PULSEAUDIO_pa_operation_unref(o); /* don't wait for it, just do our thing. */ 718 PULSEAUDIO_pa_mainloop_run(hotplug_mainloop, NULL); 719 return 0; 720} 721 722static void 723PULSEAUDIO_DetectDevices() 724{ 725 WaitForPulseOperation(hotplug_mainloop, PULSEAUDIO_pa_context_get_sink_info_list(hotplug_context, SinkInfoCallback, NULL)); 726 WaitForPulseOperation(hotplug_mainloop, PULSEAUDIO_pa_context_get_source_info_list(hotplug_context, SourceInfoCallback, NULL)); 727 728 /* ok, we have a sane list, let's set up hotplug notifications now... */ 729 hotplug_thread = SDL_CreateThreadInternal(HotplugThread, "PulseHotplug", 256 * 1024, NULL); 730} 731 732static void 733PULSEAUDIO_Deinitialize(void) 734{ 735 if (hotplug_thread) { 736 PULSEAUDIO_pa_mainloop_quit(hotplug_mainloop, 0); 737 SDL_WaitThread(hotplug_thread, NULL); 738 hotplug_thread = NULL; 739 } 740 741 DisconnectFromPulseServer(hotplug_mainloop, hotplug_context); 742 hotplug_mainloop = NULL; 743 hotplug_context = NULL; 744 745 UnloadPulseAudioLibrary(); 746} 747 748static int 749PULSEAUDIO_Init(SDL_AudioDriverImpl * impl) 750{ 751 if (LoadPulseAudioLibrary() < 0) { 752 return 0; 753 } 754 755 if (ConnectToPulseServer(&hotplug_mainloop, &hotplug_context) < 0) { 756 UnloadPulseAudioLibrary(); 757 return 0; 758 } 759 760 /* Set the function pointers */ 761 impl->DetectDevices = PULSEAUDIO_DetectDevices; 762 impl->OpenDevice = PULSEAUDIO_OpenDevice; 763 impl->PlayDevice = PULSEAUDIO_PlayDevice; 764 impl->WaitDevice = PULSEAUDIO_WaitDevice; 765 impl->GetDeviceBuf = PULSEAUDIO_GetDeviceBuf; 766 impl->CloseDevice = PULSEAUDIO_CloseDevice; 767 impl->Deinitialize = PULSEAUDIO_Deinitialize; 768 impl->CaptureFromDevice = PULSEAUDIO_CaptureFromDevice; 769 impl->FlushCapture = PULSEAUDIO_FlushCapture; 770 771 impl->HasCaptureSupport = SDL_TRUE; 772 773 return 1; /* this audio target is available. */ 774} 775 776AudioBootStrap PULSEAUDIO_bootstrap = { 777 "pulseaudio", "PulseAudio", PULSEAUDIO_Init, 0 778}; 779 780#endif /* SDL_AUDIO_DRIVER_PULSEAUDIO */ 781 782/* vi: set ts=4 sw=4 expandtab: */ 783
[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.