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@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/* The tag name used by artsc audio */
41#define NAS_DRIVER_NAME         "nas"
42
43static struct SDL_PrivateAudioData *this2 = NULL;
44
45/* Audio driver functions */
46static int NAS_OpenAudio(_THIS, SDL_AudioSpec *spec);
47static void NAS_WaitAudio(_THIS);
48static void NAS_PlayAudio(_THIS);
49static Uint8 *NAS_GetAudioBuf(_THIS);
50static void NAS_CloseAudio(_THIS);
51
52/* Audio driver bootstrap functions */
53
54static 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
63static void Audio_DeleteDevice(SDL_AudioDevice *device)
64{
65	SDL_free(device->hidden);
66	SDL_free(device);
67}
68
69static 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
101AudioBootStrap 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 */
107static 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
116static 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
137static Uint8 *NAS_GetAudioBuf(_THIS)
138{
139	return(this->hidden->mixbuf);
140}
141
142static 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
154static 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
174static AuBool
175event_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
211static AuDeviceID
212find_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
225static 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