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, &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 */
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