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
47static struct SDL_PrivateAudioData *this2 = NULL;
48
49static void (*NAS_AuCloseServer) (AuServer *);
50static void (*NAS_AuNextEvent) (AuServer *, AuBool, AuEvent *);
51static AuBool(*NAS_AuDispatchEvent) (AuServer *, AuEvent *);
52static AuFlowID(*NAS_AuCreateFlow) (AuServer *, AuStatus *);
53static void (*NAS_AuStartFlow) (AuServer *, AuFlowID, AuStatus *);
54static void (*NAS_AuSetElements)
55  (AuServer *, AuFlowID, AuBool, int, AuElement *, AuStatus *);
56static void (*NAS_AuWriteElement)
57  (AuServer *, AuFlowID, int, AuUint32, AuPointer, AuBool, AuStatus *);
58static AuServer *(*NAS_AuOpenServer)
59  (_AuConst char *, int, _AuConst char *, int, _AuConst char *, char **);
60static AuEventHandlerRec *(*NAS_AuRegisterEventHandler)
61  (AuServer *, AuMask, int, AuID, AuEventHandlerCallback, AuPointer);
62
63
64#ifdef SDL_AUDIO_DRIVER_NAS_DYNAMIC
65
66static const char *nas_library = SDL_AUDIO_DRIVER_NAS_DYNAMIC;
67static void *nas_handle = NULL;
68
69static int
70load_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
86static int
87load_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
105static void
106UnloadNASLibrary(void)
107{
108    if (nas_handle != NULL) {
109        SDL_UnloadObject(nas_handle);
110        nas_handle = NULL;
111    }
112}
113
114static int
115LoadNASLibrary(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
141static void
142UnloadNASLibrary(void)
143{
144}
145
146static int
147LoadNASLibrary(void)
148{
149    load_nas_syms();
150    return 0;
151}
152
153#endif /* SDL_AUDIO_DRIVER_NAS_DYNAMIC */
154
155
156/* Audio driver functions */
157static int NAS_OpenAudio(_THIS, SDL_AudioSpec *spec);
158static void NAS_WaitAudio(_THIS);
159static void NAS_PlayAudio(_THIS);
160static Uint8 *NAS_GetAudioBuf(_THIS);
161static void NAS_CloseAudio(_THIS);
162
163/* Audio driver bootstrap functions */
164
165static 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
180static void Audio_DeleteDevice(SDL_AudioDevice *device)
181{
182	UnloadNASLibrary();
183	SDL_free(device->hidden);
184	SDL_free(device);
185}
186
187static 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
223AudioBootStrap 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 */
229static 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
238static 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
259static Uint8 *NAS_GetAudioBuf(_THIS)
260{
261	return(this->hidden->mixbuf);
262}
263
264static 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
276static 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
296static AuBool
297event_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
333static AuDeviceID
334find_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
348static 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