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
load_nas_sym(const char * fn,void ** addr)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
load_nas_syms(void)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
UnloadNASLibrary(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
LoadNASLibrary(void)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
UnloadNASLibrary(void)142 UnloadNASLibrary(void)
143 {
144 }
145 
146 static int
LoadNASLibrary(void)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 
Audio_Available(void)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 
Audio_DeleteDevice(SDL_AudioDevice * device)180 static void Audio_DeleteDevice(SDL_AudioDevice *device)
181 {
182 	UnloadNASLibrary();
183 	SDL_free(device->hidden);
184 	SDL_free(device);
185 }
186 
Audio_CreateDevice(int devindex)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 */
NAS_WaitAudio(_THIS)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 
NAS_PlayAudio(_THIS)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 
NAS_GetAudioBuf(_THIS)259 static Uint8 *NAS_GetAudioBuf(_THIS)
260 {
261 	return(this->hidden->mixbuf);
262 }
263 
NAS_CloseAudio(_THIS)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 
sdlformat_to_auformat(unsigned int fmt)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
event_handler(AuServer * aud,AuEvent * ev,AuEventHandlerRec * hnd)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
find_device(_THIS,int nch)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 
NAS_OpenAudio(_THIS,SDL_AudioSpec * spec)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