summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/audio/qnx/SDL_qsa_audio.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/audio/qnx/SDL_qsa_audio.c')
-rw-r--r--contrib/SDL-3.2.8/src/audio/qnx/SDL_qsa_audio.c451
1 files changed, 451 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/audio/qnx/SDL_qsa_audio.c b/contrib/SDL-3.2.8/src/audio/qnx/SDL_qsa_audio.c
new file mode 100644
index 0000000..a31bea4
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/qnx/SDL_qsa_audio.c
@@ -0,0 +1,451 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, &param) != -1) {
66 param.sched_priority = param.sched_curpriority + 15;
67 SchedSet(0, 0, SCHED_NOCHANGE, &param);
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 than something wrong with hardware or driver
93 // For example, Vortex 8820 audio driver stucks 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