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