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