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