1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../../SDL_internal.h"
22
23 #if SDL_AUDIO_DRIVER_NAS
24
25 /* Allow access to a raw mixing buffer */
26
27 #include <signal.h>
28 #include <unistd.h>
29
30 #include "SDL_timer.h"
31 #include "SDL_audio.h"
32 #include "SDL_loadso.h"
33 #include "../SDL_audio_c.h"
34 #include "SDL_nasaudio.h"
35
36 static void (*NAS_AuCloseServer) (AuServer *);
37 static void (*NAS_AuNextEvent) (AuServer *, AuBool, AuEvent *);
38 static AuBool(*NAS_AuDispatchEvent) (AuServer *, AuEvent *);
39 static void (*NAS_AuHandleEvents) (AuServer *);
40 static AuFlowID(*NAS_AuCreateFlow) (AuServer *, AuStatus *);
41 static void (*NAS_AuStartFlow) (AuServer *, AuFlowID, AuStatus *);
42 static void (*NAS_AuSetElements)
43 (AuServer *, AuFlowID, AuBool, int, AuElement *, AuStatus *);
44 static void (*NAS_AuWriteElement)
45 (AuServer *, AuFlowID, int, AuUint32, AuPointer, AuBool, AuStatus *);
46 static AuUint32 (*NAS_AuReadElement)
47 (AuServer *, AuFlowID, int, AuUint32, AuPointer, AuStatus *);
48 static AuServer *(*NAS_AuOpenServer)
49 (_AuConst char *, int, _AuConst char *, int, _AuConst char *, char **);
50 static AuEventHandlerRec *(*NAS_AuRegisterEventHandler)
51 (AuServer *, AuMask, int, AuID, AuEventHandlerCallback, AuPointer);
52
53
54 #ifdef SDL_AUDIO_DRIVER_NAS_DYNAMIC
55
56 static const char *nas_library = SDL_AUDIO_DRIVER_NAS_DYNAMIC;
57 static void *nas_handle = NULL;
58
59 static int
load_nas_sym(const char * fn,void ** addr)60 load_nas_sym(const char *fn, void **addr)
61 {
62 *addr = SDL_LoadFunction(nas_handle, fn);
63 if (*addr == NULL) {
64 return 0;
65 }
66 return 1;
67 }
68
69 /* cast funcs to char* first, to please GCC's strict aliasing rules. */
70 #define SDL_NAS_SYM(x) \
71 if (!load_nas_sym(#x, (void **) (char *) &NAS_##x)) return -1
72 #else
73 #define SDL_NAS_SYM(x) NAS_##x = x
74 #endif
75
76 static int
load_nas_syms(void)77 load_nas_syms(void)
78 {
79 SDL_NAS_SYM(AuCloseServer);
80 SDL_NAS_SYM(AuNextEvent);
81 SDL_NAS_SYM(AuDispatchEvent);
82 SDL_NAS_SYM(AuHandleEvents);
83 SDL_NAS_SYM(AuCreateFlow);
84 SDL_NAS_SYM(AuStartFlow);
85 SDL_NAS_SYM(AuSetElements);
86 SDL_NAS_SYM(AuWriteElement);
87 SDL_NAS_SYM(AuReadElement);
88 SDL_NAS_SYM(AuOpenServer);
89 SDL_NAS_SYM(AuRegisterEventHandler);
90 return 0;
91 }
92
93 #undef SDL_NAS_SYM
94
95 #ifdef SDL_AUDIO_DRIVER_NAS_DYNAMIC
96
97 static void
UnloadNASLibrary(void)98 UnloadNASLibrary(void)
99 {
100 if (nas_handle != NULL) {
101 SDL_UnloadObject(nas_handle);
102 nas_handle = NULL;
103 }
104 }
105
106 static int
LoadNASLibrary(void)107 LoadNASLibrary(void)
108 {
109 int retval = 0;
110 if (nas_handle == NULL) {
111 nas_handle = SDL_LoadObject(nas_library);
112 if (nas_handle == NULL) {
113 /* Copy error string so we can use it in a new SDL_SetError(). */
114 const char *origerr = SDL_GetError();
115 const size_t len = SDL_strlen(origerr) + 1;
116 char *err = (char *) alloca(len);
117 SDL_strlcpy(err, origerr, len);
118 retval = -1;
119 SDL_SetError("NAS: SDL_LoadObject('%s') failed: %s",
120 nas_library, err);
121 } else {
122 retval = load_nas_syms();
123 if (retval < 0) {
124 UnloadNASLibrary();
125 }
126 }
127 }
128 return retval;
129 }
130
131 #else
132
133 static void
UnloadNASLibrary(void)134 UnloadNASLibrary(void)
135 {
136 }
137
138 static int
LoadNASLibrary(void)139 LoadNASLibrary(void)
140 {
141 load_nas_syms();
142 return 0;
143 }
144
145 #endif /* SDL_AUDIO_DRIVER_NAS_DYNAMIC */
146
147 /* This function waits until it is possible to write a full sound buffer */
148 static void
NAS_WaitDevice(_THIS)149 NAS_WaitDevice(_THIS)
150 {
151 while (this->hidden->buf_free < this->hidden->mixlen) {
152 AuEvent ev;
153 NAS_AuNextEvent(this->hidden->aud, AuTrue, &ev);
154 NAS_AuDispatchEvent(this->hidden->aud, &ev);
155 }
156 }
157
158 static void
NAS_PlayDevice(_THIS)159 NAS_PlayDevice(_THIS)
160 {
161 while (this->hidden->mixlen > this->hidden->buf_free) {
162 /*
163 * We think the buffer is full? Yikes! Ask the server for events,
164 * in the hope that some of them is LowWater events telling us more
165 * of the buffer is free now than what we think.
166 */
167 AuEvent ev;
168 NAS_AuNextEvent(this->hidden->aud, AuTrue, &ev);
169 NAS_AuDispatchEvent(this->hidden->aud, &ev);
170 }
171 this->hidden->buf_free -= this->hidden->mixlen;
172
173 /* Write the audio data */
174 NAS_AuWriteElement(this->hidden->aud, this->hidden->flow, 0,
175 this->hidden->mixlen, this->hidden->mixbuf, AuFalse,
176 NULL);
177
178 this->hidden->written += this->hidden->mixlen;
179
180 #ifdef DEBUG_AUDIO
181 fprintf(stderr, "Wrote %d bytes of audio data\n", this->hidden->mixlen);
182 #endif
183 }
184
185 static Uint8 *
NAS_GetDeviceBuf(_THIS)186 NAS_GetDeviceBuf(_THIS)
187 {
188 return (this->hidden->mixbuf);
189 }
190
191 static int
NAS_CaptureFromDevice(_THIS,void * buffer,int buflen)192 NAS_CaptureFromDevice(_THIS, void *buffer, int buflen)
193 {
194 struct SDL_PrivateAudioData *h = this->hidden;
195 int retval;
196
197 while (SDL_TRUE) {
198 /* just keep the event queue moving and the server chattering. */
199 NAS_AuHandleEvents(h->aud);
200
201 retval = (int) NAS_AuReadElement(h->aud, h->flow, 1, buflen, buffer, NULL);
202 /*printf("read %d capture bytes\n", (int) retval);*/
203 if (retval == 0) {
204 SDL_Delay(10); /* don't burn the CPU if we're waiting for data. */
205 } else {
206 break;
207 }
208 }
209
210 return retval;
211 }
212
213 static void
NAS_FlushCapture(_THIS)214 NAS_FlushCapture(_THIS)
215 {
216 struct SDL_PrivateAudioData *h = this->hidden;
217 AuUint32 total = 0;
218 AuUint32 br;
219 Uint8 buf[512];
220
221 do {
222 /* just keep the event queue moving and the server chattering. */
223 NAS_AuHandleEvents(h->aud);
224 br = NAS_AuReadElement(h->aud, h->flow, 1, sizeof (buf), buf, NULL);
225 /*printf("flushed %d capture bytes\n", (int) br);*/
226 total += br;
227 } while ((br == sizeof (buf)) && (total < this->spec.size));
228 }
229
230 static void
NAS_CloseDevice(_THIS)231 NAS_CloseDevice(_THIS)
232 {
233 if (this->hidden->aud) {
234 NAS_AuCloseServer(this->hidden->aud);
235 }
236 SDL_free(this->hidden->mixbuf);
237 SDL_free(this->hidden);
238 }
239
240 static unsigned char
sdlformat_to_auformat(unsigned int fmt)241 sdlformat_to_auformat(unsigned int fmt)
242 {
243 switch (fmt) {
244 case AUDIO_U8:
245 return AuFormatLinearUnsigned8;
246 case AUDIO_S8:
247 return AuFormatLinearSigned8;
248 case AUDIO_U16LSB:
249 return AuFormatLinearUnsigned16LSB;
250 case AUDIO_U16MSB:
251 return AuFormatLinearUnsigned16MSB;
252 case AUDIO_S16LSB:
253 return AuFormatLinearSigned16LSB;
254 case AUDIO_S16MSB:
255 return AuFormatLinearSigned16MSB;
256 }
257 return AuNone;
258 }
259
260 static AuBool
event_handler(AuServer * aud,AuEvent * ev,AuEventHandlerRec * hnd)261 event_handler(AuServer * aud, AuEvent * ev, AuEventHandlerRec * hnd)
262 {
263 SDL_AudioDevice *this = (SDL_AudioDevice *) hnd->data;
264 struct SDL_PrivateAudioData *h = this->hidden;
265 if (this->iscapture) {
266 return AuTrue; /* we don't (currently) care about any of this for capture devices */
267 }
268
269 switch (ev->type) {
270 case AuEventTypeElementNotify:
271 {
272 AuElementNotifyEvent *event = (AuElementNotifyEvent *) ev;
273
274 switch (event->kind) {
275 case AuElementNotifyKindLowWater:
276 if (h->buf_free >= 0) {
277 h->really += event->num_bytes;
278 gettimeofday(&h->last_tv, 0);
279 h->buf_free += event->num_bytes;
280 } else {
281 h->buf_free = event->num_bytes;
282 }
283 break;
284 case AuElementNotifyKindState:
285 switch (event->cur_state) {
286 case AuStatePause:
287 if (event->reason != AuReasonUser) {
288 if (h->buf_free >= 0) {
289 h->really += event->num_bytes;
290 gettimeofday(&h->last_tv, 0);
291 h->buf_free += event->num_bytes;
292 } else {
293 h->buf_free = event->num_bytes;
294 }
295 }
296 break;
297 }
298 }
299 }
300 }
301 return AuTrue;
302 }
303
304 static AuDeviceID
find_device(_THIS)305 find_device(_THIS)
306 {
307 /* These "Au" things are all macros, not functions... */
308 struct SDL_PrivateAudioData *h = this->hidden;
309 const unsigned int devicekind = this->iscapture ? AuComponentKindPhysicalInput : AuComponentKindPhysicalOutput;
310 const int numdevs = AuServerNumDevices(h->aud);
311 const int nch = this->spec.channels;
312 int i;
313
314 /* Try to find exact match on channels first... */
315 for (i = 0; i < numdevs; i++) {
316 const AuDeviceAttributes *dev = AuServerDevice(h->aud, i);
317 if ((AuDeviceKind(dev) == devicekind) && (AuDeviceNumTracks(dev) == nch)) {
318 return AuDeviceIdentifier(dev);
319 }
320 }
321
322 /* Take anything, then... */
323 for (i = 0; i < numdevs; i++) {
324 const AuDeviceAttributes *dev = AuServerDevice(h->aud, i);
325 if (AuDeviceKind(dev) == devicekind) {
326 this->spec.channels = AuDeviceNumTracks(dev);
327 return AuDeviceIdentifier(dev);
328 }
329 }
330 return AuNone;
331 }
332
333 static int
NAS_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)334 NAS_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
335 {
336 AuElement elms[3];
337 int buffer_size;
338 SDL_AudioFormat test_format, format;
339
340 /* Initialize all variables that we clean on shutdown */
341 this->hidden = (struct SDL_PrivateAudioData *)
342 SDL_malloc((sizeof *this->hidden));
343 if (this->hidden == NULL) {
344 return SDL_OutOfMemory();
345 }
346 SDL_zerop(this->hidden);
347
348 /* Try for a closest match on audio format */
349 format = 0;
350 for (test_format = SDL_FirstAudioFormat(this->spec.format);
351 !format && test_format;) {
352 format = sdlformat_to_auformat(test_format);
353 if (format == AuNone) {
354 test_format = SDL_NextAudioFormat();
355 }
356 }
357 if (format == 0) {
358 return SDL_SetError("NAS: Couldn't find any hardware audio formats");
359 }
360 this->spec.format = test_format;
361
362 this->hidden->aud = NAS_AuOpenServer("", 0, NULL, 0, NULL, NULL);
363 if (this->hidden->aud == 0) {
364 return SDL_SetError("NAS: Couldn't open connection to NAS server");
365 }
366
367 this->hidden->dev = find_device(this);
368 if ((this->hidden->dev == AuNone)
369 || (!(this->hidden->flow = NAS_AuCreateFlow(this->hidden->aud, 0)))) {
370 return SDL_SetError("NAS: Couldn't find a fitting device on NAS server");
371 }
372
373 buffer_size = this->spec.freq;
374 if (buffer_size < 4096)
375 buffer_size = 4096;
376
377 if (buffer_size > 32768)
378 buffer_size = 32768; /* So that the buffer won't get unmanageably big. */
379
380 /* Calculate the final parameters for this audio specification */
381 SDL_CalculateAudioSpec(&this->spec);
382
383 if (iscapture) {
384 AuMakeElementImportDevice(elms, this->spec.freq, this->hidden->dev,
385 AuUnlimitedSamples, 0, NULL);
386 AuMakeElementExportClient(elms + 1, 0, this->spec.freq, format,
387 this->spec.channels, AuTrue, buffer_size,
388 buffer_size, 0, NULL);
389 } else {
390 AuMakeElementImportClient(elms, this->spec.freq, format,
391 this->spec.channels, AuTrue, buffer_size,
392 buffer_size / 4, 0, NULL);
393 AuMakeElementExportDevice(elms + 1, 0, this->hidden->dev, this->spec.freq,
394 AuUnlimitedSamples, 0, NULL);
395 }
396
397 NAS_AuSetElements(this->hidden->aud, this->hidden->flow, AuTrue,
398 2, elms, NULL);
399
400 NAS_AuRegisterEventHandler(this->hidden->aud, AuEventHandlerIDMask, 0,
401 this->hidden->flow, event_handler,
402 (AuPointer) this);
403
404 NAS_AuStartFlow(this->hidden->aud, this->hidden->flow, NULL);
405
406 /* Allocate mixing buffer */
407 if (!iscapture) {
408 this->hidden->mixlen = this->spec.size;
409 this->hidden->mixbuf = (Uint8 *) SDL_malloc(this->hidden->mixlen);
410 if (this->hidden->mixbuf == NULL) {
411 return SDL_OutOfMemory();
412 }
413 SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size);
414 }
415
416 /* We're ready to rock and roll. :-) */
417 return 0;
418 }
419
420 static void
NAS_Deinitialize(void)421 NAS_Deinitialize(void)
422 {
423 UnloadNASLibrary();
424 }
425
426 static int
NAS_Init(SDL_AudioDriverImpl * impl)427 NAS_Init(SDL_AudioDriverImpl * impl)
428 {
429 if (LoadNASLibrary() < 0) {
430 return 0;
431 } else {
432 AuServer *aud = NAS_AuOpenServer("", 0, NULL, 0, NULL, NULL);
433 if (aud == NULL) {
434 SDL_SetError("NAS: AuOpenServer() failed (no audio server?)");
435 return 0;
436 }
437 NAS_AuCloseServer(aud);
438 }
439
440 /* Set the function pointers */
441 impl->OpenDevice = NAS_OpenDevice;
442 impl->PlayDevice = NAS_PlayDevice;
443 impl->WaitDevice = NAS_WaitDevice;
444 impl->GetDeviceBuf = NAS_GetDeviceBuf;
445 impl->CaptureFromDevice = NAS_CaptureFromDevice;
446 impl->FlushCapture = NAS_FlushCapture;
447 impl->CloseDevice = NAS_CloseDevice;
448 impl->Deinitialize = NAS_Deinitialize;
449
450 impl->OnlyHasDefaultOutputDevice = 1;
451 impl->OnlyHasDefaultCaptureDevice = 1;
452 impl->HasCaptureSupport = SDL_TRUE;
453
454 return 1; /* this audio target is available. */
455 }
456
457 AudioBootStrap NAS_bootstrap = {
458 "nas", "Network Audio System", NAS_Init, 0
459 };
460
461 #endif /* SDL_AUDIO_DRIVER_NAS */
462
463 /* vi: set ts=4 sw=4 expandtab: */
464