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