Home | History | Annotate | Download | only in nto
      1 /*
      2     SDL - Simple DirectMedia Layer
      3     Copyright (C) 1997-2012 Sam Lantinga
      4 
      5     This library is free software; you can redistribute it and/or
      6     modify it under the terms of the GNU Library General Public
      7     License as published by the Free Software Foundation; either
      8     version 2 of the License, or (at your option) any later version.
      9 
     10     This library is distributed in the hope that it will be useful,
     11     but WITHOUT ANY WARRANTY; without even the implied warranty of
     12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13     Library General Public License for more details.
     14 
     15     You should have received a copy of the GNU Library General Public
     16     License along with this library; if not, write to the Free
     17     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
     18 
     19     Sam Lantinga
     20     slouken (at) libsdl.org
     21 */
     22 #include "SDL_config.h"
     23 
     24 #include <errno.h>
     25 #include <unistd.h>
     26 #include <fcntl.h>
     27 #include <signal.h>
     28 #include <sys/types.h>
     29 #include <sys/time.h>
     30 #include <sched.h>
     31 #include <sys/select.h>
     32 #include <sys/neutrino.h>
     33 #include <sys/asoundlib.h>
     34 
     35 #include "SDL_timer.h"
     36 #include "SDL_audio.h"
     37 #include "../SDL_audiomem.h"
     38 #include "../SDL_audio_c.h"
     39 #include "SDL_nto_audio.h"
     40 
     41 /* The tag name used by NTO audio */
     42 #define DRIVER_NAME "qsa-nto"
     43 
     44 /* default channel communication parameters */
     45 #define DEFAULT_CPARAMS_RATE 22050
     46 #define DEFAULT_CPARAMS_VOICES 1
     47 /* FIXME: need to add in the near future flexible logic with frag_size and frags count */
     48 #define DEFAULT_CPARAMS_FRAG_SIZE 4096
     49 #define DEFAULT_CPARAMS_FRAGS_MIN 1
     50 #define DEFAULT_CPARAMS_FRAGS_MAX 1
     51 
     52 /* Open the audio device for playback, and don't block if busy */
     53 #define OPEN_FLAGS SND_PCM_OPEN_PLAYBACK
     54 
     55 #define QSA_NO_WORKAROUNDS  0x00000000
     56 #define QSA_MMAP_WORKAROUND 0x00000001
     57 
     58 struct BuggyCards
     59 {
     60    char* cardname;
     61    unsigned long bugtype;
     62 };
     63 
     64 #define QSA_WA_CARDS 3
     65 
     66 struct BuggyCards buggycards[QSA_WA_CARDS]=
     67 {
     68    {"Sound Blaster Live!", QSA_MMAP_WORKAROUND},
     69    {"Vortex 8820",         QSA_MMAP_WORKAROUND},
     70    {"Vortex 8830",         QSA_MMAP_WORKAROUND},
     71 };
     72 
     73 /* Audio driver functions */
     74 static void NTO_ThreadInit(_THIS);
     75 static int NTO_OpenAudio(_THIS, SDL_AudioSpec* spec);
     76 static void NTO_WaitAudio(_THIS);
     77 static void NTO_PlayAudio(_THIS);
     78 static Uint8* NTO_GetAudioBuf(_THIS);
     79 static void NTO_CloseAudio(_THIS);
     80 
     81 /* card names check to apply the workarounds */
     82 static int NTO_CheckBuggyCards(_THIS, unsigned long checkfor)
     83 {
     84     char scardname[33];
     85     int it;
     86 
     87     if (snd_card_get_name(cardno, scardname, 32)<0)
     88     {
     89         return 0;
     90     }
     91 
     92     for (it=0; it<QSA_WA_CARDS; it++)
     93     {
     94        if (SDL_strcmp(buggycards[it].cardname, scardname)==0)
     95        {
     96           if (buggycards[it].bugtype==checkfor)
     97           {
     98               return 1;
     99           }
    100        }
    101     }
    102 
    103     return 0;
    104 }
    105 
    106 static void NTO_ThreadInit(_THIS)
    107 {
    108    int status;
    109    struct sched_param param;
    110 
    111    /* increasing default 10 priority to 25 to avoid jerky sound */
    112    status=SchedGet(0, 0, &param);
    113    param.sched_priority=param.sched_curpriority+15;
    114    status=SchedSet(0, 0, SCHED_NOCHANGE, &param);
    115 }
    116 
    117 /* PCM transfer channel parameters initialize function */
    118 static void NTO_InitAudioParams(snd_pcm_channel_params_t* cpars)
    119 {
    120     SDL_memset(cpars, 0, sizeof(snd_pcm_channel_params_t));
    121 
    122     cpars->channel = SND_PCM_CHANNEL_PLAYBACK;
    123     cpars->mode = SND_PCM_MODE_BLOCK;
    124     cpars->start_mode = SND_PCM_START_DATA;
    125     cpars->stop_mode  = SND_PCM_STOP_STOP;
    126     cpars->format.format = SND_PCM_SFMT_S16_LE;
    127     cpars->format.interleave = 1;
    128     cpars->format.rate = DEFAULT_CPARAMS_RATE;
    129     cpars->format.voices = DEFAULT_CPARAMS_VOICES;
    130     cpars->buf.block.frag_size = DEFAULT_CPARAMS_FRAG_SIZE;
    131     cpars->buf.block.frags_min = DEFAULT_CPARAMS_FRAGS_MIN;
    132     cpars->buf.block.frags_max = DEFAULT_CPARAMS_FRAGS_MAX;
    133 }
    134 
    135 static int NTO_AudioAvailable(void)
    136 {
    137     /*  See if we can open a nonblocking channel.
    138         Return value '1' means we can.
    139         Return value '0' means we cannot. */
    140 
    141     int available;
    142     int rval;
    143     snd_pcm_t* handle;
    144 
    145     available = 0;
    146     handle = NULL;
    147 
    148     rval = snd_pcm_open_preferred(&handle, NULL, NULL, OPEN_FLAGS);
    149 
    150     if (rval >= 0)
    151     {
    152         available = 1;
    153 
    154         if ((rval = snd_pcm_close(handle)) < 0)
    155         {
    156             SDL_SetError("NTO_AudioAvailable(): snd_pcm_close failed: %s\n", snd_strerror(rval));
    157             available = 0;
    158         }
    159     }
    160     else
    161     {
    162         SDL_SetError("NTO_AudioAvailable(): there are no available audio devices.\n");
    163     }
    164 
    165     return (available);
    166 }
    167 
    168 static void NTO_DeleteAudioDevice(SDL_AudioDevice *device)
    169 {
    170     if ((device)&&(device->hidden))
    171     {
    172         SDL_free(device->hidden);
    173     }
    174     if (device)
    175     {
    176         SDL_free(device);
    177     }
    178 }
    179 
    180 static SDL_AudioDevice* NTO_CreateAudioDevice(int devindex)
    181 {
    182     SDL_AudioDevice *this;
    183 
    184     /* Initialize all variables that we clean on shutdown */
    185     this = (SDL_AudioDevice *)SDL_malloc(sizeof(SDL_AudioDevice));
    186     if (this)
    187     {
    188         SDL_memset(this, 0, sizeof(SDL_AudioDevice));
    189         this->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(struct SDL_PrivateAudioData));
    190     }
    191     if ((this == NULL) || (this->hidden == NULL))
    192     {
    193         SDL_OutOfMemory();
    194         if (this)
    195         {
    196             SDL_free(this);
    197 	}
    198         return (0);
    199     }
    200     SDL_memset(this->hidden, 0, sizeof(struct SDL_PrivateAudioData));
    201     audio_handle = NULL;
    202 
    203     /* Set the function pointers */
    204     this->ThreadInit = NTO_ThreadInit;
    205     this->OpenAudio = NTO_OpenAudio;
    206     this->WaitAudio = NTO_WaitAudio;
    207     this->PlayAudio = NTO_PlayAudio;
    208     this->GetAudioBuf = NTO_GetAudioBuf;
    209     this->CloseAudio = NTO_CloseAudio;
    210 
    211     this->free = NTO_DeleteAudioDevice;
    212 
    213     return this;
    214 }
    215 
    216 AudioBootStrap QNXNTOAUDIO_bootstrap =
    217 {
    218     DRIVER_NAME, "QNX6 QSA-NTO Audio",
    219     NTO_AudioAvailable,
    220     NTO_CreateAudioDevice
    221 };
    222 
    223 /* This function waits until it is possible to write a full sound buffer */
    224 static void NTO_WaitAudio(_THIS)
    225 {
    226     fd_set wfds;
    227     int selectret;
    228 
    229     FD_ZERO(&wfds);
    230     FD_SET(audio_fd, &wfds);
    231 
    232     do {
    233         selectret=select(audio_fd + 1, NULL, &wfds, NULL, NULL);
    234         switch (selectret)
    235         {
    236             case -1:
    237             case  0: SDL_SetError("NTO_WaitAudio(): select() failed: %s\n", strerror(errno));
    238                      return;
    239             default: if (FD_ISSET(audio_fd, &wfds))
    240                      {
    241                          return;
    242                      }
    243                      break;
    244         }
    245     } while(1);
    246 }
    247 
    248 static void NTO_PlayAudio(_THIS)
    249 {
    250     int written, rval;
    251     int towrite;
    252     void* pcmbuffer;
    253 
    254     if (!this->enabled)
    255     {
    256         return;
    257     }
    258 
    259     towrite = this->spec.size;
    260     pcmbuffer = pcm_buf;
    261 
    262     /* Write the audio data, checking for EAGAIN (buffer full) and underrun */
    263     do {
    264         written = snd_pcm_plugin_write(audio_handle, pcm_buf, towrite);
    265         if (written != towrite)
    266         {
    267             if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
    268             {
    269                 /* Let a little CPU time go by and try to write again */
    270                 SDL_Delay(1);
    271                 /* if we wrote some data */
    272                 towrite -= written;
    273                 pcmbuffer += written * this->spec.channels;
    274                 continue;
    275             }
    276             else
    277             {
    278                 if ((errno == EINVAL) || (errno == EIO))
    279                 {
    280                     SDL_memset(&cstatus, 0, sizeof(cstatus));
    281                     cstatus.channel = SND_PCM_CHANNEL_PLAYBACK;
    282                     if ((rval = snd_pcm_plugin_status(audio_handle, &cstatus)) < 0)
    283                     {
    284                         SDL_SetError("NTO_PlayAudio(): snd_pcm_plugin_status failed: %s\n", snd_strerror(rval));
    285                         return;
    286                     }
    287                     if ((cstatus.status == SND_PCM_STATUS_UNDERRUN) || (cstatus.status == SND_PCM_STATUS_READY))
    288                     {
    289                         if ((rval = snd_pcm_plugin_prepare(audio_handle, SND_PCM_CHANNEL_PLAYBACK)) < 0)
    290                         {
    291                             SDL_SetError("NTO_PlayAudio(): snd_pcm_plugin_prepare failed: %s\n", snd_strerror(rval));
    292                             return;
    293                         }
    294                     }
    295                     continue;
    296                 }
    297                 else
    298                 {
    299                     return;
    300                 }
    301             }
    302         }
    303         else
    304         {
    305             /* we wrote all remaining data */
    306             towrite -= written;
    307             pcmbuffer += written * this->spec.channels;
    308         }
    309     } while ((towrite > 0)  && (this->enabled));
    310 
    311     /* If we couldn't write, assume fatal error for now */
    312     if (towrite != 0)
    313     {
    314         this->enabled = 0;
    315     }
    316 
    317     return;
    318 }
    319 
    320 static Uint8* NTO_GetAudioBuf(_THIS)
    321 {
    322     return pcm_buf;
    323 }
    324 
    325 static void NTO_CloseAudio(_THIS)
    326 {
    327     int rval;
    328 
    329     this->enabled = 0;
    330 
    331     if (audio_handle != NULL)
    332     {
    333         if ((rval = snd_pcm_plugin_flush(audio_handle, SND_PCM_CHANNEL_PLAYBACK)) < 0)
    334         {
    335             SDL_SetError("NTO_CloseAudio(): snd_pcm_plugin_flush failed: %s\n", snd_strerror(rval));
    336             return;
    337         }
    338         if ((rval = snd_pcm_close(audio_handle)) < 0)
    339         {
    340             SDL_SetError("NTO_CloseAudio(): snd_pcm_close failed: %s\n",snd_strerror(rval));
    341             return;
    342         }
    343         audio_handle = NULL;
    344     }
    345 }
    346 
    347 static int NTO_OpenAudio(_THIS, SDL_AudioSpec* spec)
    348 {
    349     int rval;
    350     int format;
    351     Uint16 test_format;
    352     int found;
    353 
    354     audio_handle = NULL;
    355     this->enabled = 0;
    356 
    357     if (pcm_buf != NULL)
    358     {
    359         SDL_FreeAudioMem(pcm_buf);
    360         pcm_buf = NULL;
    361     }
    362 
    363     /* initialize channel transfer parameters to default */
    364     NTO_InitAudioParams(&cparams);
    365 
    366     /* Open the audio device */
    367     rval = snd_pcm_open_preferred(&audio_handle, &cardno, &deviceno, OPEN_FLAGS);
    368     if (rval < 0)
    369     {
    370         SDL_SetError("NTO_OpenAudio(): snd_pcm_open failed: %s\n", snd_strerror(rval));
    371         return (-1);
    372     }
    373 
    374     if (!NTO_CheckBuggyCards(this, QSA_MMAP_WORKAROUND))
    375     {
    376         /* enable count status parameter */
    377         if ((rval = snd_pcm_plugin_set_disable(audio_handle, PLUGIN_DISABLE_MMAP)) < 0)
    378         {
    379             SDL_SetError("snd_pcm_plugin_set_disable failed: %s\n", snd_strerror(rval));
    380             return (-1);
    381         }
    382     }
    383 
    384     /* Try for a closest match on audio format */
    385     format = 0;
    386     /* can't use format as SND_PCM_SFMT_U8 = 0 in nto */
    387     found = 0;
    388 
    389     for (test_format=SDL_FirstAudioFormat(spec->format); !found ;)
    390     {
    391         /* if match found set format to equivalent ALSA format */
    392         switch (test_format)
    393         {
    394             case AUDIO_U8:
    395                            format = SND_PCM_SFMT_U8;
    396                            found = 1;
    397                            break;
    398             case AUDIO_S8:
    399                            format = SND_PCM_SFMT_S8;
    400                            found = 1;
    401                            break;
    402             case AUDIO_S16LSB:
    403                            format = SND_PCM_SFMT_S16_LE;
    404                            found = 1;
    405                            break;
    406             case AUDIO_S16MSB:
    407                            format = SND_PCM_SFMT_S16_BE;
    408                            found = 1;
    409                            break;
    410             case AUDIO_U16LSB:
    411                            format = SND_PCM_SFMT_U16_LE;
    412                            found = 1;
    413                            break;
    414             case AUDIO_U16MSB:
    415                            format = SND_PCM_SFMT_U16_BE;
    416                            found = 1;
    417                            break;
    418             default:
    419                            break;
    420         }
    421 
    422         if (!found)
    423         {
    424             test_format = SDL_NextAudioFormat();
    425         }
    426     }
    427 
    428     /* assumes test_format not 0 on success */
    429     if (test_format == 0)
    430     {
    431         SDL_SetError("NTO_OpenAudio(): Couldn't find any hardware audio formats");
    432         return (-1);
    433     }
    434 
    435     spec->format = test_format;
    436 
    437     /* Set the audio format */
    438     cparams.format.format = format;
    439 
    440     /* Set mono or stereo audio (currently only two channels supported) */
    441     cparams.format.voices = spec->channels;
    442 
    443     /* Set rate */
    444     cparams.format.rate = spec->freq;
    445 
    446     /* Setup the transfer parameters according to cparams */
    447     rval = snd_pcm_plugin_params(audio_handle, &cparams);
    448     if (rval < 0)
    449     {
    450         SDL_SetError("NTO_OpenAudio(): snd_pcm_channel_params failed: %s\n", snd_strerror(rval));
    451         return (-1);
    452     }
    453 
    454     /* Make sure channel is setup right one last time */
    455     SDL_memset(&csetup, 0x00, sizeof(csetup));
    456     csetup.channel = SND_PCM_CHANNEL_PLAYBACK;
    457     if (snd_pcm_plugin_setup(audio_handle, &csetup) < 0)
    458     {
    459         SDL_SetError("NTO_OpenAudio(): Unable to setup playback channel\n");
    460         return -1;
    461     }
    462 
    463 
    464     /* Calculate the final parameters for this audio specification */
    465     SDL_CalculateAudioSpec(spec);
    466 
    467     pcm_len = spec->size;
    468 
    469     if (pcm_len==0)
    470     {
    471         pcm_len = csetup.buf.block.frag_size * spec->channels * (snd_pcm_format_width(format)/8);
    472     }
    473 
    474     /* Allocate memory to the audio buffer and initialize with silence (Note that
    475        buffer size must be a multiple of fragment size, so find closest multiple)
    476     */
    477     pcm_buf = (Uint8*)SDL_AllocAudioMem(pcm_len);
    478     if (pcm_buf == NULL)
    479     {
    480         SDL_SetError("NTO_OpenAudio(): pcm buffer allocation failed\n");
    481         return (-1);
    482     }
    483     SDL_memset(pcm_buf, spec->silence, pcm_len);
    484 
    485     /* get the file descriptor */
    486     if ((audio_fd = snd_pcm_file_descriptor(audio_handle, SND_PCM_CHANNEL_PLAYBACK)) < 0)
    487     {
    488         SDL_SetError("NTO_OpenAudio(): snd_pcm_file_descriptor failed with error code: %s\n", snd_strerror(rval));
    489         return (-1);
    490     }
    491 
    492     /* Trigger audio playback */
    493     rval = snd_pcm_plugin_prepare(audio_handle, SND_PCM_CHANNEL_PLAYBACK);
    494     if (rval < 0)
    495     {
    496         SDL_SetError("snd_pcm_plugin_prepare failed: %s\n", snd_strerror(rval));
    497         return (-1);
    498     }
    499 
    500     this->enabled = 1;
    501 
    502     /* Get the parent process id (we're the parent of the audio thread) */
    503     parent = getpid();
    504 
    505     /* We're really ready to rock and roll. :-) */
    506     return (0);
    507 }
    508