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