Home | History | Annotate | Download | only in nas
      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 (at) libsdl.org
     21 
     22     This driver was written by:
     23     Erik Inge Bols
     24     knan (at) 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 }
    424