Atlas - SDL_qsa_audio.c

Home / ext / SDL2 / src / audio / qsa Lines: 1 | Size: 22010 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 * !!! FIXME: streamline this a little by removing all the 24 * !!! FIXME: if (capture) {} else {} sections that are identical 25 * !!! FIXME: except for one flag. 26 */ 27 28/* !!! FIXME: can this target support hotplugging? */ 29/* !!! FIXME: ...does SDL2 even support QNX? */ 30 31#include "../../SDL_internal.h" 32 33#if SDL_AUDIO_DRIVER_QSA 34 35#include <errno.h> 36#include <unistd.h> 37#include <fcntl.h> 38#include <signal.h> 39#include <sys/types.h> 40#include <sys/time.h> 41#include <sched.h> 42#include <sys/select.h> 43#include <sys/neutrino.h> 44#include <sys/asoundlib.h> 45 46#include "SDL_timer.h" 47#include "SDL_audio.h" 48#include "../../core/unix/SDL_poll.h" 49#include "../SDL_audio_c.h" 50#include "SDL_qsa_audio.h" 51 52/* default channel communication parameters */ 53#define DEFAULT_CPARAMS_RATE 44100 54#define DEFAULT_CPARAMS_VOICES 1 55 56#define DEFAULT_CPARAMS_FRAG_SIZE 4096 57#define DEFAULT_CPARAMS_FRAGS_MIN 1 58#define DEFAULT_CPARAMS_FRAGS_MAX 1 59 60/* List of found devices */ 61#define QSA_MAX_DEVICES 32 62#define QSA_MAX_NAME_LENGTH 81+16 /* Hardcoded in QSA, can't be changed */ 63 64typedef struct _QSA_Device 65{ 66 char name[QSA_MAX_NAME_LENGTH]; /* Long audio device name for SDL */ 67 int cardno; 68 int deviceno; 69} QSA_Device; 70 71QSA_Device qsa_playback_device[QSA_MAX_DEVICES]; 72uint32_t qsa_playback_devices; 73 74QSA_Device qsa_capture_device[QSA_MAX_DEVICES]; 75uint32_t qsa_capture_devices; 76 77static SDL_INLINE int 78QSA_SetError(const char *fn, int status) 79{ 80 return SDL_SetError("QSA: %s() failed: %s", fn, snd_strerror(status)); 81} 82 83/* !!! FIXME: does this need to be here? Does the SDL version not work? */ 84static void 85QSA_ThreadInit(_THIS) 86{ 87 /* Increase default 10 priority to 25 to avoid jerky sound */ 88 struct sched_param param; 89 if (SchedGet(0, 0, &param) != -1) { 90 param.sched_priority = param.sched_curpriority + 15; 91 SchedSet(0, 0, SCHED_NOCHANGE, &param); 92 } 93} 94 95/* PCM channel parameters initialize function */ 96static void 97QSA_InitAudioParams(snd_pcm_channel_params_t * cpars) 98{ 99 SDL_zerop(cpars); 100 cpars->channel = SND_PCM_CHANNEL_PLAYBACK; 101 cpars->mode = SND_PCM_MODE_BLOCK; 102 cpars->start_mode = SND_PCM_START_DATA; 103 cpars->stop_mode = SND_PCM_STOP_STOP; 104 cpars->format.format = SND_PCM_SFMT_S16_LE; 105 cpars->format.interleave = 1; 106 cpars->format.rate = DEFAULT_CPARAMS_RATE; 107 cpars->format.voices = DEFAULT_CPARAMS_VOICES; 108 cpars->buf.block.frag_size = DEFAULT_CPARAMS_FRAG_SIZE; 109 cpars->buf.block.frags_min = DEFAULT_CPARAMS_FRAGS_MIN; 110 cpars->buf.block.frags_max = DEFAULT_CPARAMS_FRAGS_MAX; 111} 112 113/* This function waits until it is possible to write a full sound buffer */ 114static void 115QSA_WaitDevice(_THIS) 116{ 117 int result; 118 119 /* Setup timeout for playing one fragment equal to 2 seconds */ 120 /* If timeout occured than something wrong with hardware or driver */ 121 /* For example, Vortex 8820 audio driver stucks on second DAC because */ 122 /* it doesn't exist ! */ 123 result = SDL_IOReady(this->hidden->audio_fd, !this->hidden->iscapture, 2 * 1000); 124 switch (result) { 125 case -1: 126 SDL_SetError("QSA: SDL_IOReady() failed: %s", strerror(errno)); 127 break; 128 case 0: 129 SDL_SetError("QSA: timeout on buffer waiting occured"); 130 this->hidden->timeout_on_wait = 1; 131 break; 132 default: 133 this->hidden->timeout_on_wait = 0; 134 break; 135 } 136} 137 138static void 139QSA_PlayDevice(_THIS) 140{ 141 snd_pcm_channel_status_t cstatus; 142 int written; 143 int status; 144 int towrite; 145 void *pcmbuffer; 146 147 if (!SDL_AtomicGet(&this->enabled) || !this->hidden) { 148 return; 149 } 150 151 towrite = this->spec.size; 152 pcmbuffer = this->hidden->pcm_buf; 153 154 /* Write the audio data, checking for EAGAIN (buffer full) and underrun */ 155 do { 156 written = 157 snd_pcm_plugin_write(this->hidden->audio_handle, pcmbuffer, 158 towrite); 159 if (written != towrite) { 160 /* Check if samples playback got stuck somewhere in hardware or in */ 161 /* the audio device driver */ 162 if ((errno == EAGAIN) && (written == 0)) { 163 if (this->hidden->timeout_on_wait != 0) { 164 SDL_SetError("QSA: buffer playback timeout"); 165 return; 166 } 167 } 168 169 /* Check for errors or conditions */ 170 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { 171 /* Let a little CPU time go by and try to write again */ 172 SDL_Delay(1); 173 174 /* if we wrote some data */ 175 towrite -= written; 176 pcmbuffer += written * this->spec.channels; 177 continue; 178 } else { 179 if ((errno == EINVAL) || (errno == EIO)) { 180 SDL_zero(cstatus); 181 if (!this->hidden->iscapture) { 182 cstatus.channel = SND_PCM_CHANNEL_PLAYBACK; 183 } else { 184 cstatus.channel = SND_PCM_CHANNEL_CAPTURE; 185 } 186 187 status = 188 snd_pcm_plugin_status(this->hidden->audio_handle, 189 &cstatus); 190 if (status < 0) { 191 QSA_SetError("snd_pcm_plugin_status", status); 192 return; 193 } 194 195 if ((cstatus.status == SND_PCM_STATUS_UNDERRUN) || 196 (cstatus.status == SND_PCM_STATUS_READY)) { 197 if (!this->hidden->iscapture) { 198 status = 199 snd_pcm_plugin_prepare(this->hidden-> 200 audio_handle, 201 SND_PCM_CHANNEL_PLAYBACK); 202 } else { 203 status = 204 snd_pcm_plugin_prepare(this->hidden-> 205 audio_handle, 206 SND_PCM_CHANNEL_CAPTURE); 207 } 208 if (status < 0) { 209 QSA_SetError("snd_pcm_plugin_prepare", status); 210 return; 211 } 212 } 213 continue; 214 } else { 215 return; 216 } 217 } 218 } else { 219 /* we wrote all remaining data */ 220 towrite -= written; 221 pcmbuffer += written * this->spec.channels; 222 } 223 } while ((towrite > 0) && SDL_AtomicGet(&this->enabled)); 224 225 /* If we couldn't write, assume fatal error for now */ 226 if (towrite != 0) { 227 SDL_OpenedAudioDeviceDisconnected(this); 228 } 229} 230 231static Uint8 * 232QSA_GetDeviceBuf(_THIS) 233{ 234 return this->hidden->pcm_buf; 235} 236 237static void 238QSA_CloseDevice(_THIS) 239{ 240 if (this->hidden->audio_handle != NULL) { 241 if (!this->hidden->iscapture) { 242 /* Finish playing available samples */ 243 snd_pcm_plugin_flush(this->hidden->audio_handle, 244 SND_PCM_CHANNEL_PLAYBACK); 245 } else { 246 /* Cancel unread samples during capture */ 247 snd_pcm_plugin_flush(this->hidden->audio_handle, 248 SND_PCM_CHANNEL_CAPTURE); 249 } 250 snd_pcm_close(this->hidden->audio_handle); 251 } 252 253 SDL_free(this->hidden->pcm_buf); 254 SDL_free(this->hidden); 255} 256 257static int 258QSA_OpenDevice(_THIS, void *handle, const char *devname, int iscapture) 259{ 260 const QSA_Device *device = (const QSA_Device *) handle; 261 int status = 0; 262 int format = 0; 263 SDL_AudioFormat test_format = 0; 264 int found = 0; 265 snd_pcm_channel_setup_t csetup; 266 snd_pcm_channel_params_t cparams; 267 268 /* Initialize all variables that we clean on shutdown */ 269 this->hidden = 270 (struct SDL_PrivateAudioData *) SDL_calloc(1, 271 (sizeof 272 (struct 273 SDL_PrivateAudioData))); 274 if (this->hidden == NULL) { 275 return SDL_OutOfMemory(); 276 } 277 278 /* Initialize channel transfer parameters to default */ 279 QSA_InitAudioParams(&cparams); 280 281 /* Initialize channel direction: capture or playback */ 282 this->hidden->iscapture = iscapture ? SDL_TRUE : SDL_FALSE; 283 284 if (device != NULL) { 285 /* Open requested audio device */ 286 this->hidden->deviceno = device->deviceno; 287 this->hidden->cardno = device->cardno; 288 status = snd_pcm_open(&this->hidden->audio_handle, 289 device->cardno, device->deviceno, 290 iscapture ? SND_PCM_OPEN_CAPTURE : SND_PCM_OPEN_PLAYBACK); 291 } else { 292 /* Open system default audio device */ 293 status = snd_pcm_open_preferred(&this->hidden->audio_handle, 294 &this->hidden->cardno, 295 &this->hidden->deviceno, 296 iscapture ? SND_PCM_OPEN_CAPTURE : SND_PCM_OPEN_PLAYBACK); 297 } 298 299 /* Check if requested device is opened */ 300 if (status < 0) { 301 this->hidden->audio_handle = NULL; 302 return QSA_SetError("snd_pcm_open", status); 303 } 304 305 /* Try for a closest match on audio format */ 306 format = 0; 307 /* can't use format as SND_PCM_SFMT_U8 = 0 in qsa */ 308 found = 0; 309 310 for (test_format = SDL_FirstAudioFormat(this->spec.format); !found;) { 311 /* if match found set format to equivalent QSA format */ 312 switch (test_format) { 313 case AUDIO_U8: 314 { 315 format = SND_PCM_SFMT_U8; 316 found = 1; 317 } 318 break; 319 case AUDIO_S8: 320 { 321 format = SND_PCM_SFMT_S8; 322 found = 1; 323 } 324 break; 325 case AUDIO_S16LSB: 326 { 327 format = SND_PCM_SFMT_S16_LE; 328 found = 1; 329 } 330 break; 331 case AUDIO_S16MSB: 332 { 333 format = SND_PCM_SFMT_S16_BE; 334 found = 1; 335 } 336 break; 337 case AUDIO_U16LSB: 338 { 339 format = SND_PCM_SFMT_U16_LE; 340 found = 1; 341 } 342 break; 343 case AUDIO_U16MSB: 344 { 345 format = SND_PCM_SFMT_U16_BE; 346 found = 1; 347 } 348 break; 349 case AUDIO_S32LSB: 350 { 351 format = SND_PCM_SFMT_S32_LE; 352 found = 1; 353 } 354 break; 355 case AUDIO_S32MSB: 356 { 357 format = SND_PCM_SFMT_S32_BE; 358 found = 1; 359 } 360 break; 361 case AUDIO_F32LSB: 362 { 363 format = SND_PCM_SFMT_FLOAT_LE; 364 found = 1; 365 } 366 break; 367 case AUDIO_F32MSB: 368 { 369 format = SND_PCM_SFMT_FLOAT_BE; 370 found = 1; 371 } 372 break; 373 default: 374 { 375 break; 376 } 377 } 378 379 if (!found) { 380 test_format = SDL_NextAudioFormat(); 381 } 382 } 383 384 /* assumes test_format not 0 on success */ 385 if (test_format == 0) { 386 return SDL_SetError("QSA: Couldn't find any hardware audio formats"); 387 } 388 389 this->spec.format = test_format; 390 391 /* Set the audio format */ 392 cparams.format.format = format; 393 394 /* Set mono/stereo/4ch/6ch/8ch audio */ 395 cparams.format.voices = this->spec.channels; 396 397 /* Set rate */ 398 cparams.format.rate = this->spec.freq; 399 400 /* Setup the transfer parameters according to cparams */ 401 status = snd_pcm_plugin_params(this->hidden->audio_handle, &cparams); 402 if (status < 0) { 403 return QSA_SetError("snd_pcm_plugin_params", status); 404 } 405 406 /* Make sure channel is setup right one last time */ 407 SDL_zero(csetup); 408 if (!this->hidden->iscapture) { 409 csetup.channel = SND_PCM_CHANNEL_PLAYBACK; 410 } else { 411 csetup.channel = SND_PCM_CHANNEL_CAPTURE; 412 } 413 414 /* Setup an audio channel */ 415 if (snd_pcm_plugin_setup(this->hidden->audio_handle, &csetup) < 0) { 416 return SDL_SetError("QSA: Unable to setup channel"); 417 } 418 419 /* Calculate the final parameters for this audio specification */ 420 SDL_CalculateAudioSpec(&this->spec); 421 422 this->hidden->pcm_len = this->spec.size; 423 424 if (this->hidden->pcm_len == 0) { 425 this->hidden->pcm_len = 426 csetup.buf.block.frag_size * this->spec.channels * 427 (snd_pcm_format_width(format) / 8); 428 } 429 430 /* 431 * Allocate memory to the audio buffer and initialize with silence 432 * (Note that buffer size must be a multiple of fragment size, so find 433 * closest multiple) 434 */ 435 this->hidden->pcm_buf = 436 (Uint8 *) SDL_malloc(this->hidden->pcm_len); 437 if (this->hidden->pcm_buf == NULL) { 438 return SDL_OutOfMemory(); 439 } 440 SDL_memset(this->hidden->pcm_buf, this->spec.silence, 441 this->hidden->pcm_len); 442 443 /* get the file descriptor */ 444 if (!this->hidden->iscapture) { 445 this->hidden->audio_fd = 446 snd_pcm_file_descriptor(this->hidden->audio_handle, 447 SND_PCM_CHANNEL_PLAYBACK); 448 } else { 449 this->hidden->audio_fd = 450 snd_pcm_file_descriptor(this->hidden->audio_handle, 451 SND_PCM_CHANNEL_CAPTURE); 452 } 453 454 if (this->hidden->audio_fd < 0) { 455 return QSA_SetError("snd_pcm_file_descriptor", status); 456 } 457 458 /* Prepare an audio channel */ 459 if (!this->hidden->iscapture) { 460 /* Prepare audio playback */ 461 status = 462 snd_pcm_plugin_prepare(this->hidden->audio_handle, 463 SND_PCM_CHANNEL_PLAYBACK); 464 } else { 465 /* Prepare audio capture */ 466 status = 467 snd_pcm_plugin_prepare(this->hidden->audio_handle, 468 SND_PCM_CHANNEL_CAPTURE); 469 } 470 471 if (status < 0) { 472 return QSA_SetError("snd_pcm_plugin_prepare", status); 473 } 474 475 /* We're really ready to rock and roll. :-) */ 476 return 0; 477} 478 479static void 480QSA_DetectDevices(void) 481{ 482 uint32_t it; 483 uint32_t cards; 484 uint32_t devices; 485 int32_t status; 486 487 /* Detect amount of available devices */ 488 /* this value can be changed in the runtime */ 489 cards = snd_cards(); 490 491 /* If io-audio manager is not running we will get 0 as number */ 492 /* of available audio devices */ 493 if (cards == 0) { 494 /* We have no any available audio devices */ 495 return; 496 } 497 498 /* !!! FIXME: code duplication */ 499 /* Find requested devices by type */ 500 { /* output devices */ 501 /* Playback devices enumeration requested */ 502 for (it = 0; it < cards; it++) { 503 devices = 0; 504 do { 505 status = 506 snd_card_get_longname(it, 507 qsa_playback_device 508 [qsa_playback_devices].name, 509 QSA_MAX_NAME_LENGTH); 510 if (status == EOK) { 511 snd_pcm_t *handle; 512 513 /* Add device number to device name */ 514 sprintf(qsa_playback_device[qsa_playback_devices].name + 515 SDL_strlen(qsa_playback_device 516 [qsa_playback_devices].name), " d%d", 517 devices); 518 519 /* Store associated card number id */ 520 qsa_playback_device[qsa_playback_devices].cardno = it; 521 522 /* Check if this device id could play anything */ 523 status = 524 snd_pcm_open(&handle, it, devices, 525 SND_PCM_OPEN_PLAYBACK); 526 if (status == EOK) { 527 qsa_playback_device[qsa_playback_devices].deviceno = 528 devices; 529 status = snd_pcm_close(handle); 530 if (status == EOK) { 531 SDL_AddAudioDevice(SDL_FALSE, qsa_playback_device[qsa_playback_devices].name, &qsa_playback_device[qsa_playback_devices]); 532 qsa_playback_devices++; 533 } 534 } else { 535 /* Check if we got end of devices list */ 536 if (status == -ENOENT) { 537 break; 538 } 539 } 540 } else { 541 break; 542 } 543 544 /* Check if we reached maximum devices count */ 545 if (qsa_playback_devices >= QSA_MAX_DEVICES) { 546 break; 547 } 548 devices++; 549 } while (1); 550 551 /* Check if we reached maximum devices count */ 552 if (qsa_playback_devices >= QSA_MAX_DEVICES) { 553 break; 554 } 555 } 556 } 557 558 { /* capture devices */ 559 /* Capture devices enumeration requested */ 560 for (it = 0; it < cards; it++) { 561 devices = 0; 562 do { 563 status = 564 snd_card_get_longname(it, 565 qsa_capture_device 566 [qsa_capture_devices].name, 567 QSA_MAX_NAME_LENGTH); 568 if (status == EOK) { 569 snd_pcm_t *handle; 570 571 /* Add device number to device name */ 572 sprintf(qsa_capture_device[qsa_capture_devices].name + 573 SDL_strlen(qsa_capture_device 574 [qsa_capture_devices].name), " d%d", 575 devices); 576 577 /* Store associated card number id */ 578 qsa_capture_device[qsa_capture_devices].cardno = it; 579 580 /* Check if this device id could play anything */ 581 status = 582 snd_pcm_open(&handle, it, devices, 583 SND_PCM_OPEN_CAPTURE); 584 if (status == EOK) { 585 qsa_capture_device[qsa_capture_devices].deviceno = 586 devices; 587 status = snd_pcm_close(handle); 588 if (status == EOK) { 589 SDL_AddAudioDevice(SDL_TRUE, qsa_capture_device[qsa_capture_devices].name, &qsa_capture_device[qsa_capture_devices]); 590 qsa_capture_devices++; 591 } 592 } else { 593 /* Check if we got end of devices list */ 594 if (status == -ENOENT) { 595 break; 596 } 597 } 598 599 /* Check if we reached maximum devices count */ 600 if (qsa_capture_devices >= QSA_MAX_DEVICES) { 601 break; 602 } 603 } else { 604 break; 605 } 606 devices++; 607 } while (1); 608 609 /* Check if we reached maximum devices count */ 610 if (qsa_capture_devices >= QSA_MAX_DEVICES) { 611 break; 612 } 613 } 614 } 615} 616 617static void 618QSA_Deinitialize(void) 619{ 620 /* Clear devices array on shutdown */ 621 /* !!! FIXME: we zero these on init...any reason to do it here? */ 622 SDL_zero(qsa_playback_device); 623 SDL_zero(qsa_capture_device); 624 qsa_playback_devices = 0; 625 qsa_capture_devices = 0; 626} 627 628static int 629QSA_Init(SDL_AudioDriverImpl * impl) 630{ 631 /* Clear devices array */ 632 SDL_zero(qsa_playback_device); 633 SDL_zero(qsa_capture_device); 634 qsa_playback_devices = 0; 635 qsa_capture_devices = 0; 636 637 /* Set function pointers */ 638 /* DeviceLock and DeviceUnlock functions are used default, */ 639 /* provided by SDL, which uses pthread_mutex for lock/unlock */ 640 impl->DetectDevices = QSA_DetectDevices; 641 impl->OpenDevice = QSA_OpenDevice; 642 impl->ThreadInit = QSA_ThreadInit; 643 impl->WaitDevice = QSA_WaitDevice; 644 impl->PlayDevice = QSA_PlayDevice; 645 impl->GetDeviceBuf = QSA_GetDeviceBuf; 646 impl->CloseDevice = QSA_CloseDevice; 647 impl->Deinitialize = QSA_Deinitialize; 648 impl->LockDevice = NULL; 649 impl->UnlockDevice = NULL; 650 651 impl->ProvidesOwnCallbackThread = 0; 652 impl->SkipMixerLock = 0; 653 impl->HasCaptureSupport = 1; 654 impl->OnlyHasDefaultOutputDevice = 0; 655 impl->OnlyHasDefaultCaptureDevice = 0; 656 657 return 1; /* this audio target is available. */ 658} 659 660AudioBootStrap QSAAUDIO_bootstrap = { 661 "qsa", "QNX QSA Audio", QSA_Init, 0 662}; 663 664#endif /* SDL_AUDIO_DRIVER_QSA */ 665 666/* vi: set ts=4 sw=4 expandtab: */ 667
[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.