Home | History | Annotate | Download | only in nas
      1 /*
      2     SDL - Simple DirectMedia Layer
      3     Copyright (C) 1997-2006 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 /* The tag name used by artsc audio */
     41 #define NAS_DRIVER_NAME         "nas"
     42 
     43 static struct SDL_PrivateAudioData *this2 = NULL;
     44 
     45 /* Audio driver functions */
     46 static int NAS_OpenAudio(_THIS, SDL_AudioSpec *spec);
     47 static void NAS_WaitAudio(_THIS);
     48 static void NAS_PlayAudio(_THIS);
     49 static Uint8 *NAS_GetAudioBuf(_THIS);
     50 static void NAS_CloseAudio(_THIS);
     51 
     52 /* Audio driver bootstrap functions */
     53 
     54 static int Audio_Available(void)
     55 {
     56 	AuServer *aud = AuOpenServer("", 0, NULL, 0, NULL, NULL);
     57 	if (!aud) return 0;
     58 
     59 	AuCloseServer(aud);
     60 	return 1;
     61 }
     62 
     63 static void Audio_DeleteDevice(SDL_AudioDevice *device)
     64 {
     65 	SDL_free(device->hidden);
     66 	SDL_free(device);
     67 }
     68 
     69 static SDL_AudioDevice *Audio_CreateDevice(int devindex)
     70 {
     71 	SDL_AudioDevice *this;
     72 
     73 	/* Initialize all variables that we clean on shutdown */
     74 	this = (SDL_AudioDevice *)SDL_malloc(sizeof(SDL_AudioDevice));
     75 	if ( this ) {
     76 		SDL_memset(this, 0, (sizeof *this));
     77 		this->hidden = (struct SDL_PrivateAudioData *)
     78 				SDL_malloc((sizeof *this->hidden));
     79 	}
     80 	if ( (this == NULL) || (this->hidden == NULL) ) {
     81 		SDL_OutOfMemory();
     82 		if ( this ) {
     83 			SDL_free(this);
     84 		}
     85 		return(0);
     86 	}
     87 	SDL_memset(this->hidden, 0, (sizeof *this->hidden));
     88 
     89 	/* Set the function pointers */
     90 	this->OpenAudio = NAS_OpenAudio;
     91 	this->WaitAudio = NAS_WaitAudio;
     92 	this->PlayAudio = NAS_PlayAudio;
     93 	this->GetAudioBuf = NAS_GetAudioBuf;
     94 	this->CloseAudio = NAS_CloseAudio;
     95 
     96 	this->free = Audio_DeleteDevice;
     97 
     98 	return this;
     99 }
    100 
    101 AudioBootStrap NAS_bootstrap = {
    102 	NAS_DRIVER_NAME, "Network Audio System",
    103 	Audio_Available, Audio_CreateDevice
    104 };
    105 
    106 /* This function waits until it is possible to write a full sound buffer */
    107 static void NAS_WaitAudio(_THIS)
    108 {
    109 	while ( this->hidden->buf_free < this->hidden->mixlen ) {
    110 		AuEvent ev;
    111 		AuNextEvent(this->hidden->aud, AuTrue, &ev);
    112 		AuDispatchEvent(this->hidden->aud, &ev);
    113 	}
    114 }
    115 
    116 static void NAS_PlayAudio(_THIS)
    117 {
    118 	while (this->hidden->mixlen > this->hidden->buf_free) { /* We think the buffer is full? Yikes! Ask the server for events,
    119 				    in the hope that some of them is LowWater events telling us more
    120 				    of the buffer is free now than what we think. */
    121 		AuEvent ev;
    122 		AuNextEvent(this->hidden->aud, AuTrue, &ev);
    123 		AuDispatchEvent(this->hidden->aud, &ev);
    124 	}
    125 	this->hidden->buf_free -= this->hidden->mixlen;
    126 
    127 	/* Write the audio data */
    128 	AuWriteElement(this->hidden->aud, this->hidden->flow, 0, this->hidden->mixlen, this->hidden->mixbuf, AuFalse, NULL);
    129 
    130 	this->hidden->written += this->hidden->mixlen;
    131 
    132 #ifdef DEBUG_AUDIO
    133 	fprintf(stderr, "Wrote %d bytes of audio data\n", this->hidden->mixlen);
    134 #endif
    135 }
    136 
    137 static Uint8 *NAS_GetAudioBuf(_THIS)
    138 {
    139 	return(this->hidden->mixbuf);
    140 }
    141 
    142 static void NAS_CloseAudio(_THIS)
    143 {
    144 	if ( this->hidden->mixbuf != NULL ) {
    145 		SDL_FreeAudioMem(this->hidden->mixbuf);
    146 		this->hidden->mixbuf = NULL;
    147 	}
    148 	if ( this->hidden->aud ) {
    149 		AuCloseServer(this->hidden->aud);
    150 		this->hidden->aud = 0;
    151 	}
    152 }
    153 
    154 static unsigned char sdlformat_to_auformat(unsigned int fmt)
    155 {
    156   switch (fmt)
    157     {
    158     case AUDIO_U8:
    159       return AuFormatLinearUnsigned8;
    160     case AUDIO_S8:
    161       return AuFormatLinearSigned8;
    162     case AUDIO_U16LSB:
    163       return AuFormatLinearUnsigned16LSB;
    164     case AUDIO_U16MSB:
    165       return AuFormatLinearUnsigned16MSB;
    166     case AUDIO_S16LSB:
    167       return AuFormatLinearSigned16LSB;
    168     case AUDIO_S16MSB:
    169       return AuFormatLinearSigned16MSB;
    170     }
    171   return AuNone;
    172 }
    173 
    174 static AuBool
    175 event_handler(AuServer* aud, AuEvent* ev, AuEventHandlerRec* hnd)
    176 {
    177 	switch (ev->type) {
    178 	case AuEventTypeElementNotify: {
    179 		AuElementNotifyEvent* event = (AuElementNotifyEvent *)ev;
    180 
    181 		switch (event->kind) {
    182 		case AuElementNotifyKindLowWater:
    183 			if (this2->buf_free >= 0) {
    184 				this2->really += event->num_bytes;
    185 				gettimeofday(&this2->last_tv, 0);
    186 				this2->buf_free += event->num_bytes;
    187 			} else {
    188 				this2->buf_free = event->num_bytes;
    189 			}
    190 			break;
    191 		case AuElementNotifyKindState:
    192 			switch (event->cur_state) {
    193 			case AuStatePause:
    194 				if (event->reason != AuReasonUser) {
    195 					if (this2->buf_free >= 0) {
    196 						this2->really += event->num_bytes;
    197 						gettimeofday(&this2->last_tv, 0);
    198 						this2->buf_free += event->num_bytes;
    199 					} else {
    200 						this2->buf_free = event->num_bytes;
    201 					}
    202 				}
    203 				break;
    204 			}
    205 		}
    206 	}
    207 	}
    208 	return AuTrue;
    209 }
    210 
    211 static AuDeviceID
    212 find_device(_THIS, int nch)
    213 {
    214 	int i;
    215 	for (i = 0; i < AuServerNumDevices(this->hidden->aud); i++) {
    216 		if ((AuDeviceKind(AuServerDevice(this->hidden->aud, i)) ==
    217 				AuComponentKindPhysicalOutput) &&
    218 			AuDeviceNumTracks(AuServerDevice(this->hidden->aud, i)) == nch) {
    219 			return AuDeviceIdentifier(AuServerDevice(this->hidden->aud, i));
    220 		}
    221 	}
    222 	return AuNone;
    223 }
    224 
    225 static int NAS_OpenAudio(_THIS, SDL_AudioSpec *spec)
    226 {
    227 	AuElement elms[3];
    228 	int buffer_size;
    229 	Uint16 test_format, format;
    230 
    231 	this->hidden->mixbuf = NULL;
    232 
    233 	/* Try for a closest match on audio format */
    234 	format = 0;
    235 	for ( test_format = SDL_FirstAudioFormat(spec->format);
    236 						! format && test_format; ) {
    237 		format = sdlformat_to_auformat(test_format);
    238 
    239 		if (format == AuNone) {
    240 			test_format = SDL_NextAudioFormat();
    241 		}
    242 	}
    243 	if ( format == 0 ) {
    244 		SDL_SetError("Couldn't find any hardware audio formats");
    245 		return(-1);
    246 	}
    247 	spec->format = test_format;
    248 
    249 	this->hidden->aud = AuOpenServer("", 0, NULL, 0, NULL, NULL);
    250 	if (this->hidden->aud == 0)
    251 	{
    252 		SDL_SetError("Couldn't open connection to NAS server");
    253 		return (-1);
    254 	}
    255 
    256 	this->hidden->dev = find_device(this, spec->channels);
    257 	if ((this->hidden->dev == AuNone) || (!(this->hidden->flow = AuCreateFlow(this->hidden->aud, NULL)))) {
    258 		AuCloseServer(this->hidden->aud);
    259 		this->hidden->aud = 0;
    260 		SDL_SetError("Couldn't find a fitting playback device on NAS server");
    261 		return (-1);
    262 	}
    263 
    264 	buffer_size = spec->freq;
    265 	if (buffer_size < 4096)
    266 		buffer_size = 4096;
    267 
    268 	if (buffer_size > 32768)
    269 		buffer_size = 32768; /* So that the buffer won't get unmanageably big. */
    270 
    271 	/* Calculate the final parameters for this audio specification */
    272 	SDL_CalculateAudioSpec(spec);
    273 
    274 	this2 = this->hidden;
    275 
    276 	AuMakeElementImportClient(elms, spec->freq, format, spec->channels, AuTrue,
    277 				buffer_size, buffer_size / 4, 0, NULL);
    278 	AuMakeElementExportDevice(elms+1, 0, this->hidden->dev, spec->freq,
    279 				AuUnlimitedSamples, 0, NULL);
    280 	AuSetElements(this->hidden->aud, this->hidden->flow, AuTrue, 2, elms, NULL);
    281 	AuRegisterEventHandler(this->hidden->aud, AuEventHandlerIDMask, 0, this->hidden->flow,
    282 				event_handler, (AuPointer) NULL);
    283 
    284 	AuStartFlow(this->hidden->aud, this->hidden->flow, NULL);
    285 
    286 	/* Allocate mixing buffer */
    287 	this->hidden->mixlen = spec->size;
    288 	this->hidden->mixbuf = (Uint8 *)SDL_AllocAudioMem(this->hidden->mixlen);
    289 	if ( this->hidden->mixbuf == NULL ) {
    290 		return(-1);
    291 	}
    292 	SDL_memset(this->hidden->mixbuf, spec->silence, spec->size);
    293 
    294 	/* Get the parent process id (we're the parent of the audio thread) */
    295 	this->hidden->parent = getpid();
    296 
    297 	/* We're ready to rock and roll. :-) */
    298 	return(0);
    299 }
    300