src/audio/dsp/SDL_dspaudio.c
author Ryan C. Gordon
Wed, 18 Mar 2015 02:01:17 -0400
changeset 9394 bb28e5281770
parent 9393 ed79a66e57e5
child 9619 b94b6d0bff0f
permissions -rw-r--r--
Bunch of reworking to how we manage audio devices.

Device enumeration now happens at startup and then is managed exclusively
through hotplugging instead of full redetection. The device name list now has
a unique "handle" associated with each item and SDL will pass this to the
backend so they don't have to figure out how a human readable name maps to
real hardware for a second time.

Other cleanups, fixes, improvements, plus all the audio backends updated to
the new interface...largely untested at this point, though.
slouken@0
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@8149
     3
  Copyright (C) 1997-2014 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
*/
icculus@8093
    21
#include "../../SDL_internal.h"
slouken@0
    22
slouken@6044
    23
#if SDL_AUDIO_DRIVER_OSS
slouken@6044
    24
slouken@0
    25
/* Allow access to a raw mixing buffer */
slouken@0
    26
slouken@1895
    27
#include <stdio.h>              /* For perror() */
slouken@1895
    28
#include <string.h>             /* For strerror() */
slouken@0
    29
#include <errno.h>
slouken@0
    30
#include <unistd.h>
slouken@0
    31
#include <fcntl.h>
slouken@0
    32
#include <signal.h>
slouken@0
    33
#include <sys/time.h>
slouken@0
    34
#include <sys/ioctl.h>
slouken@0
    35
#include <sys/stat.h>
slouken@1361
    36
slouken@1361
    37
#if SDL_AUDIO_DRIVER_OSS_SOUNDCARD_H
slouken@94
    38
/* This is installed on some systems */
slouken@94
    39
#include <soundcard.h>
slouken@94
    40
#else
slouken@94
    41
/* This is recommended by OSS */
slouken@0
    42
#include <sys/soundcard.h>
slouken@94
    43
#endif
slouken@0
    44
slouken@1358
    45
#include "SDL_timer.h"
slouken@0
    46
#include "SDL_audio.h"
slouken@1361
    47
#include "../SDL_audiomem.h"
slouken@1361
    48
#include "../SDL_audio_c.h"
slouken@1361
    49
#include "../SDL_audiodev_c.h"
slouken@0
    50
#include "SDL_dspaudio.h"
slouken@0
    51
icculus@2049
    52
icculus@2049
    53
static void
icculus@9394
    54
DSP_DetectDevices(void)
slouken@0
    55
{
icculus@9394
    56
    SDL_EnumUnixAudioDevices(0, NULL);
slouken@0
    57
}
slouken@0
    58
icculus@2049
    59
slouken@1895
    60
static void
icculus@2049
    61
DSP_CloseDevice(_THIS)
slouken@0
    62
{
icculus@2049
    63
    if (this->hidden != NULL) {
slouken@7719
    64
        SDL_FreeAudioMem(this->hidden->mixbuf);
slouken@7719
    65
        this->hidden->mixbuf = NULL;
icculus@2049
    66
        if (this->hidden->audio_fd >= 0) {
icculus@2049
    67
            close(this->hidden->audio_fd);
icculus@2049
    68
            this->hidden->audio_fd = -1;
icculus@2049
    69
        }
icculus@2049
    70
        SDL_free(this->hidden);
icculus@2049
    71
        this->hidden = NULL;
slouken@1895
    72
    }
slouken@0
    73
}
slouken@0
    74
icculus@2049
    75
slouken@1895
    76
static int
icculus@9394
    77
DSP_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
slouken@0
    78
{
icculus@2049
    79
    const int flags = ((iscapture) ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT);
slouken@1895
    80
    int format;
slouken@1895
    81
    int value;
slouken@1895
    82
    int frag_spec;
icculus@1982
    83
    SDL_AudioFormat test_format;
slouken@0
    84
icculus@2049
    85
    /* We don't care what the devname is...we'll try to open anything. */
icculus@2049
    86
    /*  ...but default to first name in the list... */
icculus@2049
    87
    if (devname == NULL) {
icculus@5593
    88
        devname = SDL_GetAudioDeviceName(0, iscapture);
icculus@5593
    89
        if (devname == NULL) {
icculus@7038
    90
            return SDL_SetError("No such audio device");
icculus@2049
    91
        }
icculus@2049
    92
    }
icculus@2049
    93
icculus@2071
    94
    /* Make sure fragment size stays a power of 2, or OSS fails. */
icculus@2071
    95
    /* I don't know which of these are actually legal values, though... */
icculus@2071
    96
    if (this->spec.channels > 8)
icculus@2071
    97
        this->spec.channels = 8;
icculus@2071
    98
    else if (this->spec.channels > 4)
icculus@2071
    99
        this->spec.channels = 4;
icculus@2071
   100
    else if (this->spec.channels > 2)
icculus@2071
   101
        this->spec.channels = 2;
icculus@2071
   102
icculus@2049
   103
    /* Initialize all variables that we clean on shutdown */
icculus@2049
   104
    this->hidden = (struct SDL_PrivateAudioData *)
slouken@2060
   105
        SDL_malloc((sizeof *this->hidden));
icculus@2049
   106
    if (this->hidden == NULL) {
icculus@7038
   107
        return SDL_OutOfMemory();
icculus@2049
   108
    }
icculus@2049
   109
    SDL_memset(this->hidden, 0, (sizeof *this->hidden));
icculus@2049
   110
slouken@1895
   111
    /* Open the audio device */
icculus@2049
   112
    this->hidden->audio_fd = open(devname, flags, 0);
icculus@2049
   113
    if (this->hidden->audio_fd < 0) {
icculus@2049
   114
        DSP_CloseDevice(this);
icculus@7038
   115
        return SDL_SetError("Couldn't open %s: %s", devname, strerror(errno));
slouken@1895
   116
    }
icculus@2049
   117
    this->hidden->mixbuf = NULL;
slouken@968
   118
slouken@1895
   119
    /* Make the file descriptor use blocking writes with fcntl() */
slouken@1895
   120
    {
icculus@2049
   121
        long ctlflags;
icculus@2049
   122
        ctlflags = fcntl(this->hidden->audio_fd, F_GETFL);
icculus@2049
   123
        ctlflags &= ~O_NONBLOCK;
icculus@2049
   124
        if (fcntl(this->hidden->audio_fd, F_SETFL, ctlflags) < 0) {
icculus@2049
   125
            DSP_CloseDevice(this);
icculus@7038
   126
            return SDL_SetError("Couldn't set audio blocking mode");
slouken@1895
   127
        }
slouken@1895
   128
    }
slouken@968
   129
slouken@1895
   130
    /* Get a list of supported hardware formats */
icculus@2049
   131
    if (ioctl(this->hidden->audio_fd, SNDCTL_DSP_GETFMTS, &value) < 0) {
slouken@1895
   132
        perror("SNDCTL_DSP_GETFMTS");
icculus@2049
   133
        DSP_CloseDevice(this);
icculus@7038
   134
        return SDL_SetError("Couldn't get audio format list");
slouken@1895
   135
    }
slouken@968
   136
slouken@1895
   137
    /* Try for a closest match on audio format */
slouken@1895
   138
    format = 0;
icculus@2049
   139
    for (test_format = SDL_FirstAudioFormat(this->spec.format);
slouken@1895
   140
         !format && test_format;) {
slouken@968
   141
#ifdef DEBUG_AUDIO
slouken@1895
   142
        fprintf(stderr, "Trying format 0x%4.4x\n", test_format);
slouken@968
   143
#endif
slouken@1895
   144
        switch (test_format) {
slouken@1895
   145
        case AUDIO_U8:
slouken@1895
   146
            if (value & AFMT_U8) {
slouken@1895
   147
                format = AFMT_U8;
slouken@1895
   148
            }
slouken@1895
   149
            break;
slouken@1895
   150
        case AUDIO_S16LSB:
slouken@1895
   151
            if (value & AFMT_S16_LE) {
slouken@1895
   152
                format = AFMT_S16_LE;
slouken@1895
   153
            }
slouken@1895
   154
            break;
slouken@1895
   155
        case AUDIO_S16MSB:
slouken@1895
   156
            if (value & AFMT_S16_BE) {
slouken@1895
   157
                format = AFMT_S16_BE;
slouken@1895
   158
            }
slouken@1895
   159
            break;
slouken@968
   160
#if 0
slouken@968
   161
/*
slouken@7191
   162
 * These formats are not used by any real life systems so they are not
slouken@968
   163
 * needed here.
slouken@968
   164
 */
slouken@1895
   165
        case AUDIO_S8:
slouken@1895
   166
            if (value & AFMT_S8) {
slouken@1895
   167
                format = AFMT_S8;
slouken@1895
   168
            }
slouken@1895
   169
            break;
slouken@1895
   170
        case AUDIO_U16LSB:
slouken@1895
   171
            if (value & AFMT_U16_LE) {
slouken@1895
   172
                format = AFMT_U16_LE;
slouken@1895
   173
            }
slouken@1895
   174
            break;
slouken@1895
   175
        case AUDIO_U16MSB:
slouken@1895
   176
            if (value & AFMT_U16_BE) {
slouken@1895
   177
                format = AFMT_U16_BE;
slouken@1895
   178
            }
slouken@1895
   179
            break;
slouken@968
   180
#endif
slouken@1895
   181
        default:
slouken@1895
   182
            format = 0;
slouken@1895
   183
            break;
slouken@1895
   184
        }
slouken@1895
   185
        if (!format) {
slouken@1895
   186
            test_format = SDL_NextAudioFormat();
slouken@1895
   187
        }
slouken@1895
   188
    }
slouken@1895
   189
    if (format == 0) {
icculus@2049
   190
        DSP_CloseDevice(this);
icculus@7038
   191
        return SDL_SetError("Couldn't find any hardware audio formats");
slouken@1895
   192
    }
icculus@2049
   193
    this->spec.format = test_format;
slouken@968
   194
slouken@1895
   195
    /* Set the audio format */
slouken@1895
   196
    value = format;
slouken@2060
   197
    if ((ioctl(this->hidden->audio_fd, SNDCTL_DSP_SETFMT, &value) < 0) ||
slouken@2060
   198
        (value != format)) {
slouken@1895
   199
        perror("SNDCTL_DSP_SETFMT");
icculus@2049
   200
        DSP_CloseDevice(this);
icculus@7038
   201
        return SDL_SetError("Couldn't set audio format");
slouken@1895
   202
    }
slouken@968
   203
slouken@1895
   204
    /* Set the number of channels of output */
icculus@2049
   205
    value = this->spec.channels;
icculus@2049
   206
    if (ioctl(this->hidden->audio_fd, SNDCTL_DSP_CHANNELS, &value) < 0) {
slouken@1895
   207
        perror("SNDCTL_DSP_CHANNELS");
icculus@2049
   208
        DSP_CloseDevice(this);
icculus@7038
   209
        return SDL_SetError("Cannot set the number of channels");
slouken@1895
   210
    }
icculus@2049
   211
    this->spec.channels = value;
slouken@968
   212
slouken@1895
   213
    /* Set the DSP frequency */
icculus@2049
   214
    value = this->spec.freq;
icculus@2049
   215
    if (ioctl(this->hidden->audio_fd, SNDCTL_DSP_SPEED, &value) < 0) {
slouken@1895
   216
        perror("SNDCTL_DSP_SPEED");
icculus@2049
   217
        DSP_CloseDevice(this);
icculus@7038
   218
        return SDL_SetError("Couldn't set audio frequency");
slouken@1895
   219
    }
icculus@2049
   220
    this->spec.freq = value;
slouken@0
   221
slouken@1895
   222
    /* Calculate the final parameters for this audio specification */
icculus@2049
   223
    SDL_CalculateAudioSpec(&this->spec);
slouken@0
   224
slouken@1895
   225
    /* Determine the power of two of the fragment size */
icculus@2049
   226
    for (frag_spec = 0; (0x01U << frag_spec) < this->spec.size; ++frag_spec);
icculus@2049
   227
    if ((0x01U << frag_spec) != this->spec.size) {
icculus@2049
   228
        DSP_CloseDevice(this);
icculus@7038
   229
        return SDL_SetError("Fragment size must be a power of two");
slouken@1895
   230
    }
slouken@1895
   231
    frag_spec |= 0x00020000;    /* two fragments, for low latency */
slouken@0
   232
slouken@1895
   233
    /* Set the audio buffering parameters */
slouken@0
   234
#ifdef DEBUG_AUDIO
slouken@1895
   235
    fprintf(stderr, "Requesting %d fragments of size %d\n",
slouken@1895
   236
            (frag_spec >> 16), 1 << (frag_spec & 0xFFFF));
slouken@0
   237
#endif
icculus@2049
   238
    if (ioctl(this->hidden->audio_fd, SNDCTL_DSP_SETFRAGMENT, &frag_spec) < 0) {
slouken@1895
   239
        perror("SNDCTL_DSP_SETFRAGMENT");
slouken@1895
   240
    }
slouken@0
   241
#ifdef DEBUG_AUDIO
slouken@1895
   242
    {
slouken@1895
   243
        audio_buf_info info;
icculus@2049
   244
        ioctl(this->hidden->audio_fd, SNDCTL_DSP_GETOSPACE, &info);
slouken@1895
   245
        fprintf(stderr, "fragments = %d\n", info.fragments);
slouken@1895
   246
        fprintf(stderr, "fragstotal = %d\n", info.fragstotal);
slouken@1895
   247
        fprintf(stderr, "fragsize = %d\n", info.fragsize);
slouken@1895
   248
        fprintf(stderr, "bytes = %d\n", info.bytes);
slouken@1895
   249
    }
slouken@0
   250
#endif
slouken@0
   251
slouken@1895
   252
    /* Allocate mixing buffer */
icculus@2049
   253
    this->hidden->mixlen = this->spec.size;
icculus@2049
   254
    this->hidden->mixbuf = (Uint8 *) SDL_AllocAudioMem(this->hidden->mixlen);
icculus@2049
   255
    if (this->hidden->mixbuf == NULL) {
icculus@2049
   256
        DSP_CloseDevice(this);
icculus@7038
   257
        return SDL_OutOfMemory();
slouken@1895
   258
    }
icculus@2049
   259
    SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size);
slouken@0
   260
slouken@1895
   261
    /* We're ready to rock and roll. :-) */
icculus@7038
   262
    return 0;
icculus@2049
   263
}
icculus@2049
   264
icculus@2049
   265
icculus@2049
   266
static void
icculus@2049
   267
DSP_PlayDevice(_THIS)
icculus@2049
   268
{
icculus@2049
   269
    const Uint8 *mixbuf = this->hidden->mixbuf;
icculus@2049
   270
    const int mixlen = this->hidden->mixlen;
icculus@2049
   271
    if (write(this->hidden->audio_fd, mixbuf, mixlen) == -1) {
icculus@2049
   272
        perror("Audio write");
icculus@9394
   273
        SDL_OpenedAudioDeviceDisconnected(this);
icculus@2049
   274
    }
icculus@2049
   275
#ifdef DEBUG_AUDIO
icculus@2049
   276
    fprintf(stderr, "Wrote %d bytes of audio data\n", mixlen);
icculus@2049
   277
#endif
slouken@0
   278
}
slouken@1895
   279
icculus@2049
   280
static Uint8 *
icculus@2049
   281
DSP_GetDeviceBuf(_THIS)
icculus@2049
   282
{
icculus@2049
   283
    return (this->hidden->mixbuf);
icculus@2049
   284
}
icculus@2049
   285
icculus@2049
   286
static int
slouken@2060
   287
DSP_Init(SDL_AudioDriverImpl * impl)
icculus@2049
   288
{
icculus@2049
   289
    /* Set the function pointers */
icculus@2049
   290
    impl->DetectDevices = DSP_DetectDevices;
icculus@2049
   291
    impl->OpenDevice = DSP_OpenDevice;
icculus@2049
   292
    impl->PlayDevice = DSP_PlayDevice;
icculus@2049
   293
    impl->GetDeviceBuf = DSP_GetDeviceBuf;
icculus@2049
   294
    impl->CloseDevice = DSP_CloseDevice;
icculus@2049
   295
icculus@9394
   296
    impl->AllowsArbitraryDeviceNames = 1;
icculus@9394
   297
icculus@3699
   298
    return 1;   /* this audio target is available. */
icculus@2049
   299
}
icculus@2049
   300
icculus@2049
   301
icculus@2049
   302
AudioBootStrap DSP_bootstrap = {
icculus@5594
   303
    "dsp", "OSS /dev/dsp standard audio", DSP_Init, 0
icculus@2049
   304
};
icculus@2049
   305
slouken@6044
   306
#endif /* SDL_AUDIO_DRIVER_OSS */
slouken@6044
   307
slouken@1895
   308
/* vi: set ts=4 sw=4 expandtab: */