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