src/audio/qsa/SDL_qsa_audio.c
author Ryan C. Gordon
Tue, 24 Jan 2017 16:18:25 -0500
changeset 10850 c9dc0068b0e7
parent 10737 3406a0f8b041
child 11046 8dc8b2ed306e
permissions -rw-r--r--
configure: report libsamplerate support status.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2017 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
    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
   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
   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
   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
   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
   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 *
   315 QSA_GetDeviceBuf(_THIS)
   316 {
   317     return this->hidden->pcm_buf;
   318 }
   319 
   320 static void
   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
   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
   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
   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
   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: */