SDL-1.2.14
[sdl_omap.git] / src / audio / nas / SDL_nasaudio.c
1 /*
2     SDL - Simple DirectMedia Layer
3     Copyright (C) 1997-2009 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
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 }