src/audio/nas/SDL_nasaudio.c
author Ryan C. Gordon
Thu, 01 Jan 2009 07:54:58 +0000
changeset 2938 2929ed239d2a
parent 2859 99210400e8b9
child 2942 1e431c2631ee
permissions -rw-r--r--
Adjusted default choice of audio driver.

If a driver can definitely see available devices, it is chosen. Otherwise,
we'll take the first driver that initializes but saw no devices...this might
be because it can't enumerate them, or there really aren't any available.

This prevents the dsp driver from hogging control when there are no /dev/dsp*
nodes (for example, on a Linux box with ALSA and no OSS emulation).
     1 /*
     2     SDL - Simple DirectMedia Layer
     3     Copyright (C) 1997-2009 Sam Lantinga
     4 
     5     This library is free software; you can redistribute it and/or
     6     modify it under the terms of the GNU Lesser General Public
     7     License as published by the Free Software Foundation; either
     8     version 2.1 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     Lesser General Public License for more details.
    14 
    15     You should have received a copy of the GNU Lesser General Public
    16     License along with this library; if not, write to the Free Software
    17     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
    18 
    19     Sam Lantinga
    20     slouken@libsdl.org
    21 
    22     This driver was written by:
    23     Erik Inge Bolsų
    24     knan@mo.himolde.no
    25 */
    26 #include "SDL_config.h"
    27 
    28 /* Allow access to a raw mixing buffer */
    29 
    30 #include <signal.h>
    31 #include <unistd.h>
    32 
    33 #include "SDL_timer.h"
    34 #include "SDL_audio.h"
    35 #include "SDL_loadso.h"
    36 #include "../SDL_audiomem.h"
    37 #include "../SDL_audio_c.h"
    38 #include "SDL_nasaudio.h"
    39 
    40 /* The tag name used by nas audio */
    41 #define NAS_DRIVER_NAME         "nas"
    42 
    43 static struct SDL_PrivateAudioData *this2 = NULL;
    44 
    45 
    46 static void (*NAS_AuCloseServer) (AuServer *);
    47 static void (*NAS_AuNextEvent) (AuServer *, AuBool, AuEvent *);
    48 static AuBool(*NAS_AuDispatchEvent) (AuServer *, AuEvent *);
    49 static AuFlowID(*NAS_AuCreateFlow) (AuServer *, AuStatus *);
    50 static void (*NAS_AuStartFlow) (AuServer *, AuFlowID, AuStatus *);
    51 static void (*NAS_AuSetElements)
    52   (AuServer *, AuFlowID, AuBool, int, AuElement *, AuStatus *);
    53 static void (*NAS_AuWriteElement)
    54   (AuServer *, AuFlowID, int, AuUint32, AuPointer, AuBool, AuStatus *);
    55 static AuServer *(*NAS_AuOpenServer)
    56   (_AuConst char *, int, _AuConst char *, int, _AuConst char *, char **);
    57 static AuEventHandlerRec *(*NAS_AuRegisterEventHandler)
    58   (AuServer *, AuMask, int, AuID, AuEventHandlerCallback, AuPointer);
    59 
    60 
    61 #ifdef SDL_AUDIO_DRIVER_NAS_DYNAMIC
    62 
    63 static const char *nas_library = SDL_AUDIO_DRIVER_NAS_DYNAMIC;
    64 static void *nas_handle = NULL;
    65 
    66 static int
    67 load_nas_sym(const char *fn, void **addr)
    68 {
    69     *addr = SDL_LoadFunction(nas_handle, fn);
    70     if (*addr == NULL) {
    71         return 0;
    72     }
    73     return 1;
    74 }
    75 
    76 /* cast funcs to char* first, to please GCC's strict aliasing rules. */
    77 #define SDL_NAS_SYM(x) \
    78     if (!load_nas_sym(#x, (void **) (char *) &NAS_##x)) return -1
    79 #else
    80 #define SDL_NAS_SYM(x) NAS_##x = x
    81 #endif
    82 
    83 static int
    84 load_nas_syms(void)
    85 {
    86     SDL_NAS_SYM(AuCloseServer);
    87     SDL_NAS_SYM(AuNextEvent);
    88     SDL_NAS_SYM(AuDispatchEvent);
    89     SDL_NAS_SYM(AuCreateFlow);
    90     SDL_NAS_SYM(AuStartFlow);
    91     SDL_NAS_SYM(AuSetElements);
    92     SDL_NAS_SYM(AuWriteElement);
    93     SDL_NAS_SYM(AuOpenServer);
    94     SDL_NAS_SYM(AuRegisterEventHandler);
    95     return 0;
    96 }
    97 
    98 #undef SDL_NAS_SYM
    99 
   100 #ifdef SDL_AUDIO_DRIVER_NAS_DYNAMIC
   101 
   102 static void
   103 UnloadNASLibrary(void)
   104 {
   105     if (nas_handle != NULL) {
   106         SDL_UnloadObject(nas_handle);
   107         nas_handle = NULL;
   108     }
   109 }
   110 
   111 static int
   112 LoadNASLibrary(void)
   113 {
   114     int retval = 0;
   115     if (nas_handle == NULL) {
   116         nas_handle = SDL_LoadObject(nas_library);
   117         if (nas_handle == NULL) {
   118             /* Copy error string so we can use it in a new SDL_SetError(). */
   119             char *origerr = SDL_GetError();
   120             size_t len = SDL_strlen(origerr) + 1;
   121             char *err = (char *) alloca(len);
   122             SDL_strlcpy(err, origerr, len);
   123             retval = -1;
   124             SDL_SetError("NAS: SDL_LoadObject('%s') failed: %s\n",
   125                          nas_library, err);
   126         } else {
   127             retval = load_nas_syms();
   128             if (retval < 0) {
   129                 UnloadNASLibrary();
   130             }
   131         }
   132     }
   133     return retval;
   134 }
   135 
   136 #else
   137 
   138 static void
   139 UnloadNASLibrary(void)
   140 {
   141 }
   142 
   143 static int
   144 LoadNASLibrary(void)
   145 {
   146     load_nas_syms();
   147     return 0;
   148 }
   149 
   150 #endif /* SDL_AUDIO_DRIVER_NAS_DYNAMIC */
   151 
   152 /* This function waits until it is possible to write a full sound buffer */
   153 static void
   154 NAS_WaitDevice(_THIS)
   155 {
   156     while (this->hidden->buf_free < this->hidden->mixlen) {
   157         AuEvent ev;
   158         NAS_AuNextEvent(this->hidden->aud, AuTrue, &ev);
   159         NAS_AuDispatchEvent(this->hidden->aud, &ev);
   160     }
   161 }
   162 
   163 static void
   164 NAS_PlayDevice(_THIS)
   165 {
   166     while (this->hidden->mixlen > this->hidden->buf_free) {
   167         /*
   168          * We think the buffer is full? Yikes! Ask the server for events,
   169          *  in the hope that some of them is LowWater events telling us more
   170          *  of the buffer is free now than what we think.
   171          */
   172         AuEvent ev;
   173         NAS_AuNextEvent(this->hidden->aud, AuTrue, &ev);
   174         NAS_AuDispatchEvent(this->hidden->aud, &ev);
   175     }
   176     this->hidden->buf_free -= this->hidden->mixlen;
   177 
   178     /* Write the audio data */
   179     NAS_AuWriteElement(this->hidden->aud, this->hidden->flow, 0,
   180                        this->hidden->mixlen, this->hidden->mixbuf, AuFalse,
   181                        NULL);
   182 
   183     this->hidden->written += this->hidden->mixlen;
   184 
   185 #ifdef DEBUG_AUDIO
   186     fprintf(stderr, "Wrote %d bytes of audio data\n", this->hidden->mixlen);
   187 #endif
   188 }
   189 
   190 static Uint8 *
   191 NAS_GetDeviceBuf(_THIS)
   192 {
   193     return (this->hidden->mixbuf);
   194 }
   195 
   196 static void
   197 NAS_CloseDevice(_THIS)
   198 {
   199     if (this->hidden != NULL) {
   200         if (this->hidden->mixbuf != NULL) {
   201             SDL_FreeAudioMem(this->hidden->mixbuf);
   202             this->hidden->mixbuf = NULL;
   203         }
   204         if (this->hidden->aud) {
   205             NAS_AuCloseServer(this->hidden->aud);
   206             this->hidden->aud = 0;
   207         }
   208         SDL_free(this->hidden);
   209         this2 = this->hidden = NULL;
   210     }
   211 }
   212 
   213 static unsigned char
   214 sdlformat_to_auformat(unsigned int fmt)
   215 {
   216     switch (fmt) {
   217     case AUDIO_U8:
   218         return AuFormatLinearUnsigned8;
   219     case AUDIO_S8:
   220         return AuFormatLinearSigned8;
   221     case AUDIO_U16LSB:
   222         return AuFormatLinearUnsigned16LSB;
   223     case AUDIO_U16MSB:
   224         return AuFormatLinearUnsigned16MSB;
   225     case AUDIO_S16LSB:
   226         return AuFormatLinearSigned16LSB;
   227     case AUDIO_S16MSB:
   228         return AuFormatLinearSigned16MSB;
   229     }
   230     return AuNone;
   231 }
   232 
   233 static AuBool
   234 event_handler(AuServer * aud, AuEvent * ev, AuEventHandlerRec * hnd)
   235 {
   236     switch (ev->type) {
   237     case AuEventTypeElementNotify:
   238         {
   239             AuElementNotifyEvent *event = (AuElementNotifyEvent *) ev;
   240 
   241             switch (event->kind) {
   242             case AuElementNotifyKindLowWater:
   243                 if (this2->buf_free >= 0) {
   244                     this2->really += event->num_bytes;
   245                     gettimeofday(&this2->last_tv, 0);
   246                     this2->buf_free += event->num_bytes;
   247                 } else {
   248                     this2->buf_free = event->num_bytes;
   249                 }
   250                 break;
   251             case AuElementNotifyKindState:
   252                 switch (event->cur_state) {
   253                 case AuStatePause:
   254                     if (event->reason != AuReasonUser) {
   255                         if (this2->buf_free >= 0) {
   256                             this2->really += event->num_bytes;
   257                             gettimeofday(&this2->last_tv, 0);
   258                             this2->buf_free += event->num_bytes;
   259                         } else {
   260                             this2->buf_free = event->num_bytes;
   261                         }
   262                     }
   263                     break;
   264                 }
   265             }
   266         }
   267     }
   268     return AuTrue;
   269 }
   270 
   271 static AuDeviceID
   272 find_device(_THIS, int nch)
   273 {
   274     /* These "Au" things are all macros, not functions... */
   275     int i;
   276     for (i = 0; i < AuServerNumDevices(this->hidden->aud); i++) {
   277         if ((AuDeviceKind(AuServerDevice(this->hidden->aud, i)) ==
   278              AuComponentKindPhysicalOutput) &&
   279             AuDeviceNumTracks(AuServerDevice(this->hidden->aud, i)) == nch) {
   280             return AuDeviceIdentifier(AuServerDevice(this->hidden->aud, i));
   281         }
   282     }
   283     return AuNone;
   284 }
   285 
   286 static int
   287 NAS_OpenDevice(_THIS, const char *devname, int iscapture)
   288 {
   289     AuElement elms[3];
   290     int buffer_size;
   291     SDL_AudioFormat test_format, format;
   292 
   293     /* Initialize all variables that we clean on shutdown */
   294     this->hidden = (struct SDL_PrivateAudioData *)
   295         SDL_malloc((sizeof *this->hidden));
   296     if (this->hidden == NULL) {
   297         SDL_OutOfMemory();
   298         return 0;
   299     }
   300     SDL_memset(this->hidden, 0, (sizeof *this->hidden));
   301 
   302     /* Try for a closest match on audio format */
   303     format = 0;
   304     for (test_format = SDL_FirstAudioFormat(this->spec.format);
   305          !format && test_format;) {
   306         format = sdlformat_to_auformat(test_format);
   307         if (format == AuNone) {
   308             test_format = SDL_NextAudioFormat();
   309         }
   310     }
   311     if (format == 0) {
   312         NAS_CloseDevice(this);
   313         SDL_SetError("NAS: Couldn't find any hardware audio formats");
   314         return 0;
   315     }
   316     this->spec.format = test_format;
   317 
   318     this->hidden->aud = NAS_AuOpenServer("", 0, NULL, 0, NULL, NULL);
   319     if (this->hidden->aud == 0) {
   320         NAS_CloseDevice(this);
   321         SDL_SetError("NAS: Couldn't open connection to NAS server");
   322         return 0;
   323     }
   324 
   325     this->hidden->dev = find_device(this, this->spec.channels);
   326     if ((this->hidden->dev == AuNone)
   327         || (!(this->hidden->flow = NAS_AuCreateFlow(this->hidden->aud, 0)))) {
   328         NAS_CloseDevice(this);
   329         SDL_SetError("NAS: Couldn't find a fitting device on NAS server");
   330         return 0;
   331     }
   332 
   333     buffer_size = this->spec.freq;
   334     if (buffer_size < 4096)
   335         buffer_size = 4096;
   336 
   337     if (buffer_size > 32768)
   338         buffer_size = 32768;    /* So that the buffer won't get unmanageably big. */
   339 
   340     /* Calculate the final parameters for this audio specification */
   341     SDL_CalculateAudioSpec(&this->spec);
   342 
   343     this2 = this->hidden;
   344 
   345     AuMakeElementImportClient(elms, this->spec.freq, format,
   346                               this->spec.channels, AuTrue, buffer_size,
   347                               buffer_size / 4, 0, NULL);
   348     AuMakeElementExportDevice(elms + 1, 0, this->hidden->dev, this->spec.freq,
   349                               AuUnlimitedSamples, 0, NULL);
   350     NAS_AuSetElements(this->hidden->aud, this->hidden->flow, AuTrue, 2, elms,
   351                       NULL);
   352     NAS_AuRegisterEventHandler(this->hidden->aud, AuEventHandlerIDMask, 0,
   353                                this->hidden->flow, event_handler,
   354                                (AuPointer) NULL);
   355 
   356     NAS_AuStartFlow(this->hidden->aud, this->hidden->flow, NULL);
   357 
   358     /* Allocate mixing buffer */
   359     this->hidden->mixlen = this->spec.size;
   360     this->hidden->mixbuf = (Uint8 *) SDL_AllocAudioMem(this->hidden->mixlen);
   361     if (this->hidden->mixbuf == NULL) {
   362         NAS_CloseDevice(this);
   363         SDL_OutOfMemory();
   364         return 0;
   365     }
   366     SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size);
   367 
   368     /* We're ready to rock and roll. :-) */
   369     return 1;
   370 }
   371 
   372 static void
   373 NAS_Deinitialize(void)
   374 {
   375     UnloadNASLibrary();
   376 }
   377 
   378 static int
   379 NAS_Init(SDL_AudioDriverImpl * impl)
   380 {
   381     if (LoadNASLibrary() < 0) {
   382         return 0;
   383     } else {
   384         AuServer *aud = NAS_AuOpenServer("", 0, NULL, 0, NULL, NULL);
   385         if (aud == NULL) {
   386             SDL_SetError("NAS: AuOpenServer() failed (no audio server?)");
   387             return 0;
   388         }
   389         NAS_AuCloseServer(aud);
   390     }
   391 
   392     /* Set the function pointers */
   393     impl->OpenDevice = NAS_OpenDevice;
   394     impl->PlayDevice = NAS_PlayDevice;
   395     impl->WaitDevice = NAS_WaitDevice;
   396     impl->GetDeviceBuf = NAS_GetDeviceBuf;
   397     impl->CloseDevice = NAS_CloseDevice;
   398     impl->Deinitialize = NAS_Deinitialize;
   399     impl->OnlyHasDefaultOutputDevice = 1;       /* !!! FIXME: is this true? */
   400 
   401     return 2;  /* 2 == definitely has an audio device. */
   402 }
   403 
   404 AudioBootStrap NAS_bootstrap = {
   405     NAS_DRIVER_NAME, "Network Audio System", NAS_Init, 0
   406 };
   407 
   408 /* vi: set ts=4 sw=4 expandtab: */