src/audio/nas/SDL_nasaudio.c
author Ryan C. Gordon
Thu, 01 Jan 2009 07:54:58 +0000
changeset 2938 2929ed239d2a
parent 2859 99210400e8b9
child 2942 1e431c2631ee
permissions -rw-r--r--
Adjusted default choice of audio driver.

If a driver can definitely see available devices, it is chosen. Otherwise,
we'll take the first driver that initializes but saw no devices...this might
be because it can't enumerate them, or there really aren't any available.

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