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