Atlas - SDL_qsa_audio.c
Home / ext / SDL / src / audio / qnx Lines: 1 | Size: 17256 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21 22// !!! FIXME: can this target support hotplugging? 23 24#include "../../SDL_internal.h" 25 26#ifdef SDL_AUDIO_DRIVER_QNX 27 28#include <errno.h> 29#include <unistd.h> 30#include <fcntl.h> 31#include <signal.h> 32#include <sys/types.h> 33#include <sys/time.h> 34#include <sched.h> 35#include <sys/select.h> 36#include <sys/neutrino.h> 37#include <sys/asoundlib.h> 38 39#include "SDL3/SDL_timer.h" 40#include "SDL3/SDL_audio.h" 41#include "../../core/unix/SDL_poll.h" 42#include "../SDL_sysaudio.h" 43#include "SDL_qsa_audio.h" 44 45// default channel communication parameters 46#define DEFAULT_CPARAMS_RATE 44100 47#define DEFAULT_CPARAMS_VOICES 1 48 49#define DEFAULT_CPARAMS_FRAG_SIZE 4096 50#define DEFAULT_CPARAMS_FRAGS_MIN 1 51#define DEFAULT_CPARAMS_FRAGS_MAX 1 52 53#define QSA_MAX_NAME_LENGTH 81+16 // Hardcoded in QSA, can't be changed 54 55static bool QSA_SetError(const char *fn, int status) 56{ 57 return SDL_SetError("QSA: %s() failed: %s", fn, snd_strerror(status)); 58} 59 60// !!! FIXME: does this need to be here? Does the SDL version not work? 61static void QSA_ThreadInit(SDL_AudioDevice *device) 62{ 63 // Increase default 10 priority to 25 to avoid jerky sound 64 struct sched_param param; 65 if (SchedGet(0, 0, ¶m) != -1) { 66 param.sched_priority = param.sched_curpriority + 15; 67 SchedSet(0, 0, SCHED_NOCHANGE, ¶m); 68 } 69} 70 71// PCM channel parameters initialize function 72static void QSA_InitAudioParams(snd_pcm_channel_params_t * cpars) 73{ 74 SDL_zerop(cpars); 75 cpars->channel = SND_PCM_CHANNEL_PLAYBACK; 76 cpars->mode = SND_PCM_MODE_BLOCK; 77 cpars->start_mode = SND_PCM_START_DATA; 78 cpars->stop_mode = SND_PCM_STOP_STOP; 79 cpars->format.format = SND_PCM_SFMT_S16_LE; 80 cpars->format.interleave = 1; 81 cpars->format.rate = DEFAULT_CPARAMS_RATE; 82 cpars->format.voices = DEFAULT_CPARAMS_VOICES; 83 cpars->buf.block.frag_size = DEFAULT_CPARAMS_FRAG_SIZE; 84 cpars->buf.block.frags_min = DEFAULT_CPARAMS_FRAGS_MIN; 85 cpars->buf.block.frags_max = DEFAULT_CPARAMS_FRAGS_MAX; 86} 87 88// This function waits until it is possible to write a full sound buffer 89static bool QSA_WaitDevice(SDL_AudioDevice *device) 90{ 91 // Setup timeout for playing one fragment equal to 2 seconds 92 // If timeout occurred then something wrong with hardware or driver 93 // For example, Vortex 8820 audio driver hangs on second DAC because 94 // it doesn't exist ! 95 const int result = SDL_IOReady(device->hidden->audio_fd, 96 device->recording ? SDL_IOR_READ : SDL_IOR_WRITE, 97 2 * 1000); 98 switch (result) { 99 case -1: 100 SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "QSA: SDL_IOReady() failed: %s", strerror(errno)); 101 return false; 102 case 0: 103 device->hidden->timeout_on_wait = true; // !!! FIXME: Should we just disconnect the device in this case? 104 break; 105 default: 106 device->hidden->timeout_on_wait = false; 107 break; 108 } 109 110 return true; 111} 112 113static bool QSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) 114{ 115 if (SDL_GetAtomicInt(&device->shutdown) || !device->hidden) { 116 return true; 117 } 118 119 int towrite = buflen; 120 121 // Write the audio data, checking for EAGAIN (buffer full) and underrun 122 while ((towrite > 0) && !SDL_GetAtomicInt(&device->shutdown)); 123 const int bw = snd_pcm_plugin_write(device->hidden->audio_handle, buffer, towrite); 124 if (bw != towrite) { 125 // Check if samples playback got stuck somewhere in hardware or in the audio device driver 126 if ((errno == EAGAIN) && (bw == 0)) { 127 if (device->hidden->timeout_on_wait) { 128 return true; // oh well, try again next time. !!! FIXME: Should we just disconnect the device in this case? 129 } 130 } 131 132 // Check for errors or conditions 133 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { 134 SDL_Delay(1); // Let a little CPU time go by and try to write again 135 136 // if we wrote some data 137 towrite -= bw; 138 buffer += bw * device->spec.channels; 139 continue; 140 } else if ((errno == EINVAL) || (errno == EIO)) { 141 snd_pcm_channel_status_t cstatus; 142 SDL_zero(cstatus); 143 cstatus.channel = device->recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK; 144 145 int status = snd_pcm_plugin_status(device->hidden->audio_handle, &cstatus); 146 if (status < 0) { 147 QSA_SetError("snd_pcm_plugin_status", status); 148 return false; 149 } else if ((cstatus.status == SND_PCM_STATUS_UNDERRUN) || (cstatus.status == SND_PCM_STATUS_READY)) { 150 status = snd_pcm_plugin_prepare(device->hidden->audio_handle, device->recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK); 151 if (status < 0) { 152 QSA_SetError("snd_pcm_plugin_prepare", status); 153 return false; 154 } 155 } 156 continue; 157 } else { 158 return false; 159 } 160 } else { 161 // we wrote all remaining data 162 towrite -= bw; 163 buffer += bw * device->spec.channels; 164 } 165 } 166 167 // If we couldn't write, assume fatal error for now 168 return (towrite == 0); 169} 170 171static Uint8 *QSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) 172{ 173 return device->hidden->pcm_buf; 174} 175 176static void QSA_CloseDevice(SDL_AudioDevice *device) 177{ 178 if (device->hidden) { 179 if (device->hidden->audio_handle) { 180 #if _NTO_VERSION < 710 181 // Finish playing available samples or cancel unread samples during recording 182 snd_pcm_plugin_flush(device->hidden->audio_handle, device->recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK); 183 #endif 184 snd_pcm_close(device->hidden->audio_handle); 185 } 186 187 SDL_free(device->hidden->pcm_buf); 188 SDL_free(device->hidden); 189 device->hidden = NULL; 190 } 191} 192 193static bool QSA_OpenDevice(SDL_AudioDevice *device) 194{ 195 if (device->recording) { 196 return SDL_SetError("SDL recording support isn't available on QNX atm"); // !!! FIXME: most of this code has support for recording devices, but there's no RecordDevice, etc functions. Fill them in! 197 } 198 199 SDL_assert(device->handle != NULL); // NULL used to mean "system default device" in SDL2; it does not mean that in SDL3. 200 const Uint32 sdlhandle = (Uint32) ((size_t) device->handle); 201 const uint32_t cardno = (uint32_t) (sdlhandle & 0xFFFF); 202 const uint32_t deviceno = (uint32_t) ((sdlhandle >> 16) & 0xFFFF); 203 const bool recording = device->recording; 204 int status = 0; 205 206 // Initialize all variables that we clean on shutdown 207 device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, (sizeof (struct SDL_PrivateAudioData))); 208 if (device->hidden == NULL) { 209 return false; 210 } 211 212 // Initialize channel transfer parameters to default 213 snd_pcm_channel_params_t cparams; 214 QSA_InitAudioParams(&cparams); 215 216 // Open requested audio device 217 status = snd_pcm_open(&device->hidden->audio_handle, cardno, deviceno, recording ? SND_PCM_OPEN_CAPTURE : SND_PCM_OPEN_PLAYBACK); 218 if (status < 0) { 219 device->hidden->audio_handle = NULL; 220 return QSA_SetError("snd_pcm_open", status); 221 } 222 223 // Try for a closest match on audio format 224 SDL_AudioFormat test_format = 0; 225 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); 226 while ((test_format = *(closefmts++)) != 0) { 227 // if match found set format to equivalent QSA format 228 switch (test_format) { 229 #define CHECKFMT(sdlfmt, qsafmt) case SDL_AUDIO_##sdlfmt: cparams.format.format = SND_PCM_SFMT_##qsafmt; break 230 CHECKFMT(U8, U8); 231 CHECKFMT(S8, S8); 232 CHECKFMT(S16LSB, S16_LE); 233 CHECKFMT(S16MSB, S16_BE); 234 CHECKFMT(S32LSB, S32_LE); 235 CHECKFMT(S32MSB, S32_BE); 236 CHECKFMT(F32LSB, FLOAT_LE); 237 CHECKFMT(F32MSB, FLOAT_BE); 238 #undef CHECKFMT 239 default: continue; 240 } 241 break; 242 } 243 244 // assumes test_format not 0 on success 245 if (test_format == 0) { 246 return SDL_SetError("QSA: Couldn't find any hardware audio formats"); 247 } 248 249 device->spec.format = test_format; 250 251 // Set mono/stereo/4ch/6ch/8ch audio 252 cparams.format.voices = device->spec.channels; 253 254 // Set rate 255 cparams.format.rate = device->spec.freq; 256 257 // Setup the transfer parameters according to cparams 258 status = snd_pcm_plugin_params(device->hidden->audio_handle, &cparams); 259 if (status < 0) { 260 return QSA_SetError("snd_pcm_plugin_params", status); 261 } 262 263 // Make sure channel is setup right one last time 264 snd_pcm_channel_setup_t csetup; 265 SDL_zero(csetup); 266 csetup.channel = recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK; 267 if (snd_pcm_plugin_setup(device->hidden->audio_handle, &csetup) < 0) { 268 return SDL_SetError("QSA: Unable to setup channel"); 269 } 270 271 device->sample_frames = csetup.buf.block.frag_size; 272 273 // Calculate the final parameters for this audio specification 274 SDL_UpdatedAudioDeviceFormat(device); 275 276 device->hidden->pcm_buf = (Uint8 *) SDL_malloc(device->buffer_size); 277 if (device->hidden->pcm_buf == NULL) { 278 return false; 279 } 280 SDL_memset(device->hidden->pcm_buf, device->silence_value, device->buffer_size); 281 282 // get the file descriptor 283 device->hidden->audio_fd = snd_pcm_file_descriptor(device->hidden->audio_handle, csetup.channel); 284 if (device->hidden->audio_fd < 0) { 285 return QSA_SetError("snd_pcm_file_descriptor", device->hidden->audio_fd); 286 } 287 288 // Prepare an audio channel 289 status = snd_pcm_plugin_prepare(device->hidden->audio_handle, csetup.channel) 290 if (status < 0) { 291 return QSA_SetError("snd_pcm_plugin_prepare", status); 292 } 293 294 return true; // We're really ready to rock and roll. :-) 295} 296 297static SDL_AudioFormat QnxFormatToSDLFormat(const int32_t qnxfmt) 298{ 299 switch (qnxfmt) { 300 #define CHECKFMT(sdlfmt, qsafmt) case SND_PCM_SFMT_##qsafmt: return SDL_AUDIO_##sdlfmt 301 CHECKFMT(U8, U8); 302 CHECKFMT(S8, S8); 303 CHECKFMT(S16LSB, S16_LE); 304 CHECKFMT(S16MSB, S16_BE); 305 CHECKFMT(S32LSB, S32_LE); 306 CHECKFMT(S32MSB, S32_BE); 307 CHECKFMT(F32LSB, FLOAT_LE); 308 CHECKFMT(F32MSB, FLOAT_BE); 309 #undef CHECKFMT 310 default: break; 311 } 312 return SDL_AUDIO_S16; // oh well. 313} 314 315static void QSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) 316{ 317 // Detect amount of available devices 318 // this value can be changed in the runtime 319 int num_cards = 0; 320 (void) snd_cards_list(NULL, 0, &alloc_num_cards); 321 bool isstack = false; 322 int *cards = SDL_small_alloc(int, num_cards, &isstack); 323 if (!cards) { 324 return; // we're in trouble. 325 } 326 int overflow_cards = 0; 327 const int total_num_cards = snd_cards_list(cards, num_cards, &overflow_cards); 328 // if overflow_cards > 0 or total_num_cards > num_cards, it changed at the last moment; oh well, we lost some. 329 num_cards = SDL_min(num_cards, total_num_cards); // ...but make sure it didn't _shrink_. 330 331 // If io-audio manager is not running we will get 0 as number of available audio devices 332 if (num_cards == 0) { // not any available audio devices? 333 SDL_small_free(cards, isstack); 334 return; 335 } 336 337 // Find requested devices by type 338 for (int it = 0; it < num_cards; it++) { 339 const int card = cards[it]; 340 for (uint32_t deviceno = 0; ; deviceno++) { 341 int32_t status; 342 char name[QSA_MAX_NAME_LENGTH]; 343 344 status = snd_card_get_longname(card, name, sizeof (name)); 345 if (status == EOK) { 346 snd_pcm_t *handle; 347 348 // Add device number to device name 349 char fullname[QSA_MAX_NAME_LENGTH + 32]; 350 SDL_snprintf(fullname, sizeof (fullname), "%s d%d", name, (int) deviceno); 351 352 // Check if this device id could play anything 353 bool recording = false; 354 status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_PLAYBACK); 355 if (status != EOK) { // no? See if it's a recording device instead. 356 #if 0 // !!! FIXME: most of this code has support for recording devices, but there's no RecordDevice, etc functions. Fill them in! 357 status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_CAPTURE); 358 if (status == EOK) { 359 recording = true; 360 } 361 #endif 362 } 363 364 if (status == EOK) { 365 SDL_AudioSpec spec; 366 SDL_zero(spec); 367 SDL_AudioSpec *pspec = &spec; 368 snd_pcm_channel_setup_t csetup; 369 SDL_zero(csetup); 370 csetup.channel = recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK; 371 372 if (snd_pcm_plugin_setup(device->hidden->audio_handle, &csetup) < 0) { 373 pspec = NULL; // go on without spec info. 374 } else { 375 spec.format = QnxFormatToSDLFormat(csetup.format.format); 376 spec.channels = csetup.format.channels; 377 spec.freq = csetup.format.rate; 378 } 379 380 status = snd_pcm_close(handle); 381 if (status == EOK) { 382 // !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not. 383 SDL_assert(card <= 0xFFFF); 384 SDL_assert(deviceno <= 0xFFFF); 385 const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16); 386 SDL_AddAudioDevice(recording, fullname, pspec, (void *) ((size_t) sdlhandle)); 387 } 388 } else { 389 // Check if we got end of devices list 390 if (status == -ENOENT) { 391 break; 392 } 393 } 394 } else { 395 break; 396 } 397 } 398 } 399 400 SDL_small_free(cards, isstack); 401 402 // Try to open the "preferred" devices, which will tell us the card/device pairs for the default devices. 403 snd_pcm_t handle; 404 int cardno, deviceno; 405 if (snd_pcm_open_preferred(&handle, &cardno, &deviceno, SND_PCM_OPEN_PLAYBACK) == 0) { 406 snd_pcm_close(handle); 407 // !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not. 408 SDL_assert(cardno <= 0xFFFF); 409 SDL_assert(deviceno <= 0xFFFF); 410 const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16); 411 *default_playback = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle)); 412 } 413 414 if (snd_pcm_open_preferred(&handle, &cardno, &deviceno, SND_PCM_OPEN_CAPTURE) == 0) { 415 snd_pcm_close(handle); 416 // !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not. 417 SDL_assert(cardno <= 0xFFFF); 418 SDL_assert(deviceno <= 0xFFFF); 419 const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16); 420 *default_recording = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle)); 421 } 422} 423 424static void QSA_Deinitialize(void) 425{ 426 // nothing to do here atm. 427} 428 429static bool QSA_Init(SDL_AudioDriverImpl * impl) 430{ 431 impl->DetectDevices = QSA_DetectDevices; 432 impl->OpenDevice = QSA_OpenDevice; 433 impl->ThreadInit = QSA_ThreadInit; 434 impl->WaitDevice = QSA_WaitDevice; 435 impl->PlayDevice = QSA_PlayDevice; 436 impl->GetDeviceBuf = QSA_GetDeviceBuf; 437 impl->CloseDevice = QSA_CloseDevice; 438 impl->Deinitialize = QSA_Deinitialize; 439 440 // !!! FIXME: most of this code has support for recording devices, but there's no RecordDevice, etc functions. Fill them in! 441 //impl->HasRecordingSupport = true; 442 443 return true; 444} 445 446AudioBootStrap QSAAUDIO_bootstrap = { 447 "qsa", "QNX QSA Audio", QSA_Init, false, false 448}; 449 450#endif // SDL_AUDIO_DRIVER_QNX 451 452[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.