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