src/audio/alsa/SDL_alsa_audio.c
author Sam Lantinga <slouken@libsdl.org>
Fri, 28 Oct 2016 17:00:37 -0700
changeset 10561 35fee69e84df
parent 10560 5d9ad1fd5acd
child 10582 0e1dd7514610
permissions -rw-r--r--
Fixed compiling on older versions of ALSA
slouken@0
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@9998
     3
  Copyright (C) 1997-2016 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_ALSA
slouken@6044
    24
slouken@0
    25
/* Allow access to a raw mixing buffer */
slouken@0
    26
slouken@0
    27
#include <sys/types.h>
slouken@1895
    28
#include <signal.h>             /* For kill() */
icculus@2049
    29
#include <errno.h>
icculus@2049
    30
#include <string.h>
slouken@0
    31
icculus@10229
    32
#include "SDL_assert.h"
slouken@1361
    33
#include "SDL_timer.h"
slouken@0
    34
#include "SDL_audio.h"
slouken@1361
    35
#include "../SDL_audio_c.h"
slouken@0
    36
#include "SDL_alsa_audio.h"
slouken@0
    37
icculus@6046
    38
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
icculus@3362
    39
#include "SDL_loadso.h"
icculus@3362
    40
#endif
slouken@865
    41
icculus@2049
    42
static int (*ALSA_snd_pcm_open)
slouken@2060
    43
  (snd_pcm_t **, const char *, snd_pcm_stream_t, int);
slouken@2060
    44
static int (*ALSA_snd_pcm_close) (snd_pcm_t * pcm);
icculus@10243
    45
static snd_pcm_sframes_t (*ALSA_snd_pcm_writei)
slouken@2060
    46
  (snd_pcm_t *, const void *, snd_pcm_uframes_t);
icculus@10243
    47
static snd_pcm_sframes_t (*ALSA_snd_pcm_readi)
icculus@10243
    48
  (snd_pcm_t *, void *, snd_pcm_uframes_t);
icculus@3627
    49
static int (*ALSA_snd_pcm_recover) (snd_pcm_t *, int, int);
slouken@2060
    50
static int (*ALSA_snd_pcm_prepare) (snd_pcm_t *);
slouken@2060
    51
static int (*ALSA_snd_pcm_drain) (snd_pcm_t *);
slouken@2060
    52
static const char *(*ALSA_snd_strerror) (int);
slouken@2060
    53
static size_t(*ALSA_snd_pcm_hw_params_sizeof) (void);
slouken@2060
    54
static size_t(*ALSA_snd_pcm_sw_params_sizeof) (void);
icculus@3627
    55
static void (*ALSA_snd_pcm_hw_params_copy)
icculus@3627
    56
  (snd_pcm_hw_params_t *, const snd_pcm_hw_params_t *);
slouken@2060
    57
static int (*ALSA_snd_pcm_hw_params_any) (snd_pcm_t *, snd_pcm_hw_params_t *);
icculus@2049
    58
static int (*ALSA_snd_pcm_hw_params_set_access)
slouken@2060
    59
  (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t);
icculus@2049
    60
static int (*ALSA_snd_pcm_hw_params_set_format)
slouken@2060
    61
  (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t);
icculus@2049
    62
static int (*ALSA_snd_pcm_hw_params_set_channels)
slouken@2060
    63
  (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int);
icculus@3627
    64
static int (*ALSA_snd_pcm_hw_params_get_channels)
icculus@3627
    65
  (const snd_pcm_hw_params_t *, unsigned int *);
icculus@3362
    66
static int (*ALSA_snd_pcm_hw_params_set_rate_near)
icculus@3362
    67
  (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
icculus@3362
    68
static int (*ALSA_snd_pcm_hw_params_set_period_size_near)
icculus@3362
    69
  (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
icculus@3627
    70
static int (*ALSA_snd_pcm_hw_params_get_period_size)
icculus@3627
    71
  (const snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
icculus@3362
    72
static int (*ALSA_snd_pcm_hw_params_set_periods_near)
icculus@3362
    73
  (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
icculus@3627
    74
static int (*ALSA_snd_pcm_hw_params_get_periods)
icculus@3627
    75
  (const snd_pcm_hw_params_t *, unsigned int *, int *);
icculus@3627
    76
static int (*ALSA_snd_pcm_hw_params_set_buffer_size_near)
icculus@3627
    77
  (snd_pcm_t *pcm, snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
icculus@3627
    78
static int (*ALSA_snd_pcm_hw_params_get_buffer_size)
icculus@3627
    79
  (const snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
slouken@2060
    80
static int (*ALSA_snd_pcm_hw_params) (snd_pcm_t *, snd_pcm_hw_params_t *);
slouken@2060
    81
static int (*ALSA_snd_pcm_sw_params_current) (snd_pcm_t *,
slouken@2060
    82
                                              snd_pcm_sw_params_t *);
icculus@2049
    83
static int (*ALSA_snd_pcm_sw_params_set_start_threshold)
slouken@2060
    84
  (snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
slouken@2060
    85
static int (*ALSA_snd_pcm_sw_params) (snd_pcm_t *, snd_pcm_sw_params_t *);
slouken@2060
    86
static int (*ALSA_snd_pcm_nonblock) (snd_pcm_t *, int);
icculus@3627
    87
static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int);
icculus@5622
    88
static int (*ALSA_snd_pcm_sw_params_set_avail_min)
icculus@5622
    89
  (snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
icculus@10243
    90
static int (*ALSA_snd_pcm_reset)(snd_pcm_t *);
icculus@10104
    91
static int (*ALSA_snd_device_name_hint) (int, const char *, void ***);
icculus@10104
    92
static char* (*ALSA_snd_device_name_get_hint) (const void *, const char *);
icculus@10104
    93
static int (*ALSA_snd_device_name_free_hint) (void **);
slouken@10561
    94
#ifdef SND_CHMAP_API_VERSION
slouken@10560
    95
static snd_pcm_chmap_t* (*ALSA_snd_pcm_get_chmap) (snd_pcm_t *);
slouken@10560
    96
static int (*ALSA_snd_pcm_chmap_print) (const snd_pcm_chmap_t *map, size_t maxlen, char *buf);
slouken@10561
    97
#endif
slouken@5315
    98
icculus@6046
    99
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
icculus@2049
   100
#define snd_pcm_hw_params_sizeof ALSA_snd_pcm_hw_params_sizeof
icculus@2049
   101
#define snd_pcm_sw_params_sizeof ALSA_snd_pcm_sw_params_sizeof
icculus@2049
   102
slouken@1361
   103
static const char *alsa_library = SDL_AUDIO_DRIVER_ALSA_DYNAMIC;
slouken@865
   104
static void *alsa_handle = NULL;
slouken@865
   105
icculus@2049
   106
static int
icculus@2049
   107
load_alsa_sym(const char *fn, void **addr)
icculus@2049
   108
{
icculus@3362
   109
    *addr = SDL_LoadFunction(alsa_handle, fn);
icculus@3362
   110
    if (*addr == NULL) {
icculus@3362
   111
        /* Don't call SDL_SetError(): SDL_LoadFunction already did. */
icculus@3362
   112
        return 0;
icculus@2049
   113
    }
icculus@2049
   114
icculus@2049
   115
    return 1;
icculus@2049
   116
}
slouken@865
   117
icculus@1161
   118
/* cast funcs to char* first, to please GCC's strict aliasing rules. */
icculus@2049
   119
#define SDL_ALSA_SYM(x) \
icculus@2049
   120
    if (!load_alsa_sym(#x, (void **) (char *) &ALSA_##x)) return -1
icculus@2049
   121
#else
icculus@2049
   122
#define SDL_ALSA_SYM(x) ALSA_##x = x
icculus@2049
   123
#endif
icculus@2049
   124
slouken@2060
   125
static int
slouken@2060
   126
load_alsa_syms(void)
slouken@1895
   127
{
icculus@2049
   128
    SDL_ALSA_SYM(snd_pcm_open);
icculus@2049
   129
    SDL_ALSA_SYM(snd_pcm_close);
icculus@2049
   130
    SDL_ALSA_SYM(snd_pcm_writei);
icculus@10243
   131
    SDL_ALSA_SYM(snd_pcm_readi);
icculus@3627
   132
    SDL_ALSA_SYM(snd_pcm_recover);
icculus@2049
   133
    SDL_ALSA_SYM(snd_pcm_prepare);
icculus@2049
   134
    SDL_ALSA_SYM(snd_pcm_drain);
icculus@2049
   135
    SDL_ALSA_SYM(snd_strerror);
icculus@2049
   136
    SDL_ALSA_SYM(snd_pcm_hw_params_sizeof);
icculus@2049
   137
    SDL_ALSA_SYM(snd_pcm_sw_params_sizeof);
icculus@3627
   138
    SDL_ALSA_SYM(snd_pcm_hw_params_copy);
icculus@2049
   139
    SDL_ALSA_SYM(snd_pcm_hw_params_any);
icculus@2049
   140
    SDL_ALSA_SYM(snd_pcm_hw_params_set_access);
icculus@2049
   141
    SDL_ALSA_SYM(snd_pcm_hw_params_set_format);
icculus@2049
   142
    SDL_ALSA_SYM(snd_pcm_hw_params_set_channels);
icculus@2049
   143
    SDL_ALSA_SYM(snd_pcm_hw_params_get_channels);
icculus@2049
   144
    SDL_ALSA_SYM(snd_pcm_hw_params_set_rate_near);
icculus@2049
   145
    SDL_ALSA_SYM(snd_pcm_hw_params_set_period_size_near);
icculus@2049
   146
    SDL_ALSA_SYM(snd_pcm_hw_params_get_period_size);
icculus@2049
   147
    SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_near);
icculus@2049
   148
    SDL_ALSA_SYM(snd_pcm_hw_params_get_periods);
icculus@3627
   149
    SDL_ALSA_SYM(snd_pcm_hw_params_set_buffer_size_near);
icculus@3627
   150
    SDL_ALSA_SYM(snd_pcm_hw_params_get_buffer_size);
icculus@2049
   151
    SDL_ALSA_SYM(snd_pcm_hw_params);
icculus@2049
   152
    SDL_ALSA_SYM(snd_pcm_sw_params_current);
icculus@2049
   153
    SDL_ALSA_SYM(snd_pcm_sw_params_set_start_threshold);
icculus@2049
   154
    SDL_ALSA_SYM(snd_pcm_sw_params);
icculus@2049
   155
    SDL_ALSA_SYM(snd_pcm_nonblock);
icculus@3627
   156
    SDL_ALSA_SYM(snd_pcm_wait);
icculus@5622
   157
    SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min);
icculus@10243
   158
    SDL_ALSA_SYM(snd_pcm_reset);
icculus@10104
   159
    SDL_ALSA_SYM(snd_device_name_hint);
icculus@10104
   160
    SDL_ALSA_SYM(snd_device_name_get_hint);
icculus@10104
   161
    SDL_ALSA_SYM(snd_device_name_free_hint);
slouken@10561
   162
#ifdef SND_CHMAP_API_VERSION
slouken@10560
   163
    SDL_ALSA_SYM(snd_pcm_get_chmap);
slouken@10560
   164
    SDL_ALSA_SYM(snd_pcm_chmap_print);
slouken@10561
   165
#endif
icculus@10104
   166
icculus@2049
   167
    return 0;
icculus@2049
   168
}
slouken@2060
   169
icculus@2049
   170
#undef SDL_ALSA_SYM
icculus@2049
   171
icculus@6046
   172
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
slouken@865
   173
slouken@1895
   174
static void
slouken@1895
   175
UnloadALSALibrary(void)
slouken@1895
   176
{
icculus@2049
   177
    if (alsa_handle != NULL) {
slouken@7191
   178
        SDL_UnloadObject(alsa_handle);
slouken@1895
   179
        alsa_handle = NULL;
slouken@1895
   180
    }
slouken@865
   181
}
slouken@865
   182
slouken@1895
   183
static int
slouken@1895
   184
LoadALSALibrary(void)
slouken@1895
   185
{
icculus@2049
   186
    int retval = 0;
icculus@2049
   187
    if (alsa_handle == NULL) {
icculus@3362
   188
        alsa_handle = SDL_LoadObject(alsa_library);
icculus@2049
   189
        if (alsa_handle == NULL) {
icculus@2049
   190
            retval = -1;
icculus@3362
   191
            /* Don't call SDL_SetError(): SDL_LoadObject already did. */
icculus@2049
   192
        } else {
icculus@2049
   193
            retval = load_alsa_syms();
icculus@2049
   194
            if (retval < 0) {
slouken@1895
   195
                UnloadALSALibrary();
slouken@1895
   196
            }
slouken@1895
   197
        }
slouken@1895
   198
    }
slouken@1895
   199
    return retval;
slouken@865
   200
}
slouken@865
   201
slouken@865
   202
#else
slouken@865
   203
slouken@1895
   204
static void
slouken@1895
   205
UnloadALSALibrary(void)
slouken@1895
   206
{
slouken@865
   207
}
slouken@865
   208
slouken@1895
   209
static int
slouken@1895
   210
LoadALSALibrary(void)
slouken@1895
   211
{
icculus@2049
   212
    load_alsa_syms();
slouken@1895
   213
    return 0;
slouken@865
   214
}
slouken@865
   215
slouken@1361
   216
#endif /* SDL_AUDIO_DRIVER_ALSA_DYNAMIC */
slouken@865
   217
slouken@1895
   218
static const char *
icculus@10104
   219
get_audio_device(void *handle, const int channels)
slouken@0
   220
{
slouken@1895
   221
    const char *device;
slouken@1895
   222
icculus@10104
   223
    if (handle != NULL) {
icculus@10104
   224
        return (const char *) handle;
icculus@10104
   225
    }
icculus@10104
   226
icculus@10104
   227
    /* !!! FIXME: we also check "SDL_AUDIO_DEVICE_NAME" at the higher level. */
slouken@1895
   228
    device = SDL_getenv("AUDIODEV");    /* Is there a standard variable name? */
icculus@10104
   229
    if (device != NULL) {
icculus@10104
   230
        return device;
slouken@1895
   231
    }
icculus@10104
   232
icculus@10104
   233
    if (channels == 6) {
icculus@10104
   234
        return "plug:surround51";
icculus@10104
   235
    } else if (channels == 4) {
icculus@10104
   236
        return "plug:surround40";
icculus@10104
   237
    }
icculus@10104
   238
icculus@10104
   239
    return "default";
slouken@0
   240
}
slouken@0
   241
slouken@0
   242
slouken@0
   243
/* This function waits until it is possible to write a full sound buffer */
slouken@1895
   244
static void
icculus@2049
   245
ALSA_WaitDevice(_THIS)
slouken@0
   246
{
icculus@3627
   247
    /* We're in blocking mode, so there's nothing to do here */
slouken@0
   248
}
slouken@0
   249
icculus@1878
   250
icculus@2049
   251
/* !!! FIXME: is there a channel swizzler in alsalib instead? */
icculus@1878
   252
/*
icculus@1878
   253
 * http://bugzilla.libsdl.org/show_bug.cgi?id=110
icculus@1878
   254
 * "For Linux ALSA, this is FL-FR-RL-RR-C-LFE
icculus@1878
   255
 *  and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-RL-RR"
icculus@1878
   256
 */
icculus@10242
   257
#define SWIZ6(T, buf, numframes) \
icculus@10242
   258
    T *ptr = (T *) buf; \
icculus@1878
   259
    Uint32 i; \
icculus@10242
   260
    for (i = 0; i < numframes; i++, ptr += 6) { \
icculus@1878
   261
        T tmp; \
icculus@1878
   262
        tmp = ptr[2]; ptr[2] = ptr[4]; ptr[4] = tmp; \
icculus@1878
   263
        tmp = ptr[3]; ptr[3] = ptr[5]; ptr[5] = tmp; \
icculus@1878
   264
    }
icculus@1878
   265
slouken@10560
   266
static void
icculus@10242
   267
swizzle_alsa_channels_6_64bit(void *buffer, Uint32 bufferlen)
slouken@1895
   268
{
icculus@10242
   269
    SWIZ6(Uint64, buffer, bufferlen);
slouken@1895
   270
}
slouken@2735
   271
slouken@10560
   272
static void
icculus@10242
   273
swizzle_alsa_channels_6_32bit(void *buffer, Uint32 bufferlen)
slouken@1895
   274
{
icculus@10242
   275
    SWIZ6(Uint32, buffer, bufferlen);
slouken@1895
   276
}
slouken@2735
   277
slouken@10560
   278
static void
icculus@10242
   279
swizzle_alsa_channels_6_16bit(void *buffer, Uint32 bufferlen)
slouken@1895
   280
{
icculus@10242
   281
    SWIZ6(Uint16, buffer, bufferlen);
slouken@1895
   282
}
slouken@2735
   283
slouken@10560
   284
static void
icculus@10242
   285
swizzle_alsa_channels_6_8bit(void *buffer, Uint32 bufferlen)
slouken@1895
   286
{
icculus@10242
   287
    SWIZ6(Uint8, buffer, bufferlen);
slouken@1895
   288
}
icculus@1878
   289
icculus@1878
   290
#undef SWIZ6
icculus@1878
   291
icculus@1878
   292
icculus@1878
   293
/*
icculus@2049
   294
 * Called right before feeding this->hidden->mixbuf to the hardware. Swizzle
icculus@2049
   295
 *  channels from Windows/Mac order to the format alsalib will want.
icculus@1878
   296
 */
slouken@10560
   297
static void
icculus@10242
   298
swizzle_alsa_channels(_THIS, void *buffer, Uint32 bufferlen)
icculus@1878
   299
{
icculus@1878
   300
    if (this->spec.channels == 6) {
icculus@10242
   301
        switch (SDL_AUDIO_BITSIZE(this->spec.format)) {
icculus@10242
   302
            case 8: swizzle_alsa_channels_6_8bit(buffer, bufferlen); break;
icculus@10242
   303
            case 16: swizzle_alsa_channels_6_16bit(buffer, bufferlen); break;
icculus@10242
   304
            case 32: swizzle_alsa_channels_6_32bit(buffer, bufferlen); break;
icculus@10242
   305
            case 64: swizzle_alsa_channels_6_64bit(buffer, bufferlen); break;
icculus@10242
   306
            default: SDL_assert(!"unhandled bitsize"); break;
icculus@10242
   307
        }
icculus@1878
   308
    }
icculus@1878
   309
icculus@1878
   310
    /* !!! FIXME: update this for 7.1 if needed, later. */
icculus@1878
   311
}
icculus@1878
   312
slouken@10560
   313
/* Some devices have the right channel map, no swizzling necessary */
slouken@10560
   314
static void
slouken@10560
   315
no_swizzle(_THIS, void *buffer, Uint32 bufferlen)
slouken@10560
   316
{
slouken@10560
   317
    return;
slouken@10560
   318
}
slouken@10560
   319
icculus@1878
   320
slouken@1895
   321
static void
icculus@2049
   322
ALSA_PlayDevice(_THIS)
slouken@0
   323
{
icculus@3627
   324
    const Uint8 *sample_buf = (const Uint8 *) this->hidden->mixbuf;
icculus@10242
   325
    const int frame_size = (((int) SDL_AUDIO_BITSIZE(this->spec.format)) / 8) *
icculus@3627
   326
                                this->spec.channels;
icculus@3627
   327
    snd_pcm_uframes_t frames_left = ((snd_pcm_uframes_t) this->spec.samples);
slouken@765
   328
slouken@10560
   329
    this->hidden->swizzle_func(this, this->hidden->mixbuf, frames_left);
icculus@1878
   330
icculus@10238
   331
    while ( frames_left > 0 && SDL_AtomicGet(&this->enabled) ) {
slouken@10523
   332
        int status;
slouken@10523
   333
slouken@10523
   334
        /* This wait is a work-around for a hang when USB devices are
slouken@10523
   335
           unplugged.  Normally it should not result in any waiting,
slouken@10523
   336
           but in the case of a USB unplug, it serves as a way to
slouken@10523
   337
           join the playback thread after the timeout occurs */
slouken@10523
   338
        status = ALSA_snd_pcm_wait(this->hidden->pcm_handle, 1000);
slouken@10523
   339
        if (status == 0) {
slouken@10523
   340
            /*fprintf(stderr, "ALSA timeout waiting for available buffer space\n");*/
slouken@10523
   341
            SDL_OpenedAudioDeviceDisconnected(this);
slouken@10523
   342
            return;
slouken@10523
   343
        }
slouken@10523
   344
slouken@10523
   345
        status = ALSA_snd_pcm_writei(this->hidden->pcm_handle,
icculus@10242
   346
                                         sample_buf, frames_left);
icculus@2049
   347
slouken@1895
   348
        if (status < 0) {
slouken@1895
   349
            if (status == -EAGAIN) {
icculus@3627
   350
                /* Apparently snd_pcm_recover() doesn't handle this case -
icculus@3627
   351
                   does it assume snd_pcm_wait() above? */
slouken@1895
   352
                SDL_Delay(1);
slouken@1895
   353
                continue;
slouken@1895
   354
            }
icculus@3627
   355
            status = ALSA_snd_pcm_recover(this->hidden->pcm_handle, status, 0);
slouken@1895
   356
            if (status < 0) {
slouken@1895
   357
                /* Hmm, not much we can do - abort */
icculus@3627
   358
                fprintf(stderr, "ALSA write failed (unrecoverable): %s\n",
icculus@3627
   359
                        ALSA_snd_strerror(status));
icculus@9394
   360
                SDL_OpenedAudioDeviceDisconnected(this);
slouken@1895
   361
                return;
slouken@1895
   362
            }
slouken@1895
   363
            continue;
slouken@1895
   364
        }
icculus@3627
   365
        sample_buf += status * frame_size;
icculus@3627
   366
        frames_left -= status;
slouken@1895
   367
    }
slouken@0
   368
}
slouken@0
   369
slouken@1895
   370
static Uint8 *
icculus@2049
   371
ALSA_GetDeviceBuf(_THIS)
slouken@0
   372
{
icculus@2049
   373
    return (this->hidden->mixbuf);
slouken@354
   374
}
slouken@0
   375
icculus@10243
   376
static int
icculus@10243
   377
ALSA_CaptureFromDevice(_THIS, void *buffer, int buflen)
icculus@10243
   378
{
icculus@10243
   379
    Uint8 *sample_buf = (Uint8 *) buffer;
icculus@10243
   380
    const int frame_size = (((int) SDL_AUDIO_BITSIZE(this->spec.format)) / 8) *
icculus@10243
   381
                                this->spec.channels;
icculus@10243
   382
    const int total_frames = buflen / frame_size;
icculus@10243
   383
    snd_pcm_uframes_t frames_left = total_frames;
icculus@10243
   384
icculus@10243
   385
    SDL_assert((buflen % frame_size) == 0);
icculus@10243
   386
icculus@10243
   387
    while ( frames_left > 0 && SDL_AtomicGet(&this->enabled) ) {
icculus@10243
   388
        /* !!! FIXME: This works, but needs more testing before going live */
icculus@10243
   389
        /* ALSA_snd_pcm_wait(this->hidden->pcm_handle, -1); */
icculus@10243
   390
        int status = ALSA_snd_pcm_readi(this->hidden->pcm_handle,
icculus@10243
   391
                                        sample_buf, frames_left);
icculus@10243
   392
icculus@10243
   393
        if (status < 0) {
icculus@10243
   394
            /*printf("ALSA: capture error %d\n", status);*/
icculus@10243
   395
            if (status == -EAGAIN) {
icculus@10243
   396
                /* Apparently snd_pcm_recover() doesn't handle this case -
icculus@10243
   397
                   does it assume snd_pcm_wait() above? */
icculus@10243
   398
                SDL_Delay(1);
icculus@10243
   399
                continue;
icculus@10243
   400
            }
icculus@10243
   401
            status = ALSA_snd_pcm_recover(this->hidden->pcm_handle, status, 0);
icculus@10243
   402
            if (status < 0) {
icculus@10243
   403
                /* Hmm, not much we can do - abort */
icculus@10243
   404
                fprintf(stderr, "ALSA read failed (unrecoverable): %s\n",
icculus@10243
   405
                        ALSA_snd_strerror(status));
icculus@10243
   406
                return -1;
icculus@10243
   407
            }
icculus@10243
   408
            continue;
icculus@10243
   409
        }
icculus@10243
   410
icculus@10243
   411
        /*printf("ALSA: captured %d bytes\n", status * frame_size);*/
icculus@10243
   412
        sample_buf += status * frame_size;
icculus@10243
   413
        frames_left -= status;
icculus@10243
   414
    }
icculus@10243
   415
slouken@10560
   416
    this->hidden->swizzle_func(this, buffer, total_frames - frames_left);
icculus@10243
   417
icculus@10243
   418
    return (total_frames - frames_left) * frame_size;
icculus@10243
   419
}
icculus@10243
   420
icculus@10243
   421
static void
icculus@10243
   422
ALSA_FlushCapture(_THIS)
icculus@10243
   423
{
icculus@10243
   424
    ALSA_snd_pcm_reset(this->hidden->pcm_handle);
icculus@10243
   425
}
icculus@10243
   426
slouken@1895
   427
static void
icculus@2049
   428
ALSA_CloseDevice(_THIS)
slouken@354
   429
{
icculus@10255
   430
    if (this->hidden->pcm_handle) {
slouken@10497
   431
	/* Wait for the submitted audio to drain
slouken@10497
   432
           ALSA_snd_pcm_drop() can hang, so don't use that.
slouken@10497
   433
         */
slouken@10497
   434
        Uint32 delay = ((this->spec.samples * 1000) / this->spec.freq) * 2;
slouken@10497
   435
        SDL_Delay(delay);
slouken@10497
   436
icculus@10255
   437
        ALSA_snd_pcm_close(this->hidden->pcm_handle);
slouken@1895
   438
    }
icculus@10256
   439
    SDL_free(this->hidden->mixbuf);
icculus@10255
   440
    SDL_free(this->hidden);
slouken@354
   441
}
slouken@0
   442
slouken@1895
   443
static int
icculus@3627
   444
ALSA_finalize_hardware(_THIS, snd_pcm_hw_params_t *hwparams, int override)
icculus@3627
   445
{
icculus@3627
   446
    int status;
icculus@3627
   447
    snd_pcm_uframes_t bufsize;
icculus@3627
   448
icculus@3627
   449
    /* "set" the hardware with the desired parameters */
icculus@3627
   450
    status = ALSA_snd_pcm_hw_params(this->hidden->pcm_handle, hwparams);
icculus@3627
   451
    if ( status < 0 ) {
icculus@3627
   452
        return(-1);
icculus@3627
   453
    }
icculus@3627
   454
icculus@3627
   455
    /* Get samples for the actual buffer size */
icculus@3627
   456
    status = ALSA_snd_pcm_hw_params_get_buffer_size(hwparams, &bufsize);
icculus@3627
   457
    if ( status < 0 ) {
icculus@3627
   458
        return(-1);
icculus@3627
   459
    }
icculus@3627
   460
    if ( !override && bufsize != this->spec.samples * 2 ) {
icculus@3627
   461
        return(-1);
icculus@3627
   462
    }
icculus@3627
   463
icculus@3627
   464
    /* !!! FIXME: Is this safe to do? */
icculus@3627
   465
    this->spec.samples = bufsize / 2;
icculus@3627
   466
icculus@3627
   467
    /* This is useful for debugging */
icculus@3627
   468
    if ( SDL_getenv("SDL_AUDIO_ALSA_DEBUG") ) {
icculus@3627
   469
        snd_pcm_uframes_t persize = 0;
icculus@3627
   470
        unsigned int periods = 0;
icculus@3627
   471
icculus@3627
   472
        ALSA_snd_pcm_hw_params_get_period_size(hwparams, &persize, NULL);
icculus@3627
   473
        ALSA_snd_pcm_hw_params_get_periods(hwparams, &periods, NULL);
icculus@3627
   474
icculus@3627
   475
        fprintf(stderr,
icculus@3627
   476
            "ALSA: period size = %ld, periods = %u, buffer size = %lu\n",
icculus@3627
   477
            persize, periods, bufsize);
icculus@3627
   478
    }
icculus@3627
   479
icculus@3627
   480
    return(0);
icculus@3627
   481
}
icculus@3627
   482
icculus@3627
   483
static int
icculus@3627
   484
ALSA_set_period_size(_THIS, snd_pcm_hw_params_t *params, int override)
icculus@3627
   485
{
icculus@3627
   486
    const char *env;
icculus@3627
   487
    int status;
icculus@3627
   488
    snd_pcm_hw_params_t *hwparams;
icculus@3627
   489
    snd_pcm_uframes_t frames;
icculus@3627
   490
    unsigned int periods;
icculus@3627
   491
icculus@3627
   492
    /* Copy the hardware parameters for this setup */
icculus@3627
   493
    snd_pcm_hw_params_alloca(&hwparams);
icculus@3627
   494
    ALSA_snd_pcm_hw_params_copy(hwparams, params);
icculus@3627
   495
icculus@3627
   496
    if ( !override ) {
icculus@3627
   497
        env = SDL_getenv("SDL_AUDIO_ALSA_SET_PERIOD_SIZE");
icculus@3627
   498
        if ( env ) {
icculus@3627
   499
            override = SDL_atoi(env);
icculus@3627
   500
            if ( override == 0 ) {
icculus@3627
   501
                return(-1);
icculus@3627
   502
            }
icculus@3627
   503
        }
icculus@3627
   504
    }
icculus@3627
   505
icculus@3627
   506
    frames = this->spec.samples;
icculus@3627
   507
    status = ALSA_snd_pcm_hw_params_set_period_size_near(
icculus@3627
   508
                this->hidden->pcm_handle, hwparams, &frames, NULL);
icculus@3627
   509
    if ( status < 0 ) {
icculus@3627
   510
        return(-1);
icculus@3627
   511
    }
icculus@3627
   512
icculus@3627
   513
    periods = 2;
icculus@3627
   514
    status = ALSA_snd_pcm_hw_params_set_periods_near(
icculus@3627
   515
                this->hidden->pcm_handle, hwparams, &periods, NULL);
icculus@3627
   516
    if ( status < 0 ) {
icculus@3627
   517
        return(-1);
icculus@3627
   518
    }
icculus@3627
   519
icculus@3627
   520
    return ALSA_finalize_hardware(this, hwparams, override);
icculus@3627
   521
}
icculus@3627
   522
icculus@3627
   523
static int
icculus@3627
   524
ALSA_set_buffer_size(_THIS, snd_pcm_hw_params_t *params, int override)
icculus@3627
   525
{
icculus@3627
   526
    const char *env;
icculus@3627
   527
    int status;
icculus@3627
   528
    snd_pcm_hw_params_t *hwparams;
icculus@3627
   529
    snd_pcm_uframes_t frames;
icculus@3627
   530
icculus@3627
   531
    /* Copy the hardware parameters for this setup */
icculus@3627
   532
    snd_pcm_hw_params_alloca(&hwparams);
icculus@3627
   533
    ALSA_snd_pcm_hw_params_copy(hwparams, params);
icculus@3627
   534
icculus@3627
   535
    if ( !override ) {
icculus@3627
   536
        env = SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE");
icculus@3627
   537
        if ( env ) {
icculus@3627
   538
            override = SDL_atoi(env);
icculus@3627
   539
            if ( override == 0 ) {
icculus@3627
   540
                return(-1);
icculus@3627
   541
            }
icculus@3627
   542
        }
icculus@3627
   543
    }
icculus@3627
   544
icculus@3627
   545
    frames = this->spec.samples * 2;
icculus@3627
   546
    status = ALSA_snd_pcm_hw_params_set_buffer_size_near(
icculus@3627
   547
                    this->hidden->pcm_handle, hwparams, &frames);
icculus@3627
   548
    if ( status < 0 ) {
icculus@3627
   549
        return(-1);
icculus@3627
   550
    }
icculus@3627
   551
icculus@3627
   552
    return ALSA_finalize_hardware(this, hwparams, override);
icculus@3627
   553
}
icculus@3627
   554
icculus@3627
   555
static int
icculus@9394
   556
ALSA_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
slouken@354
   557
{
icculus@2049
   558
    int status = 0;
icculus@2049
   559
    snd_pcm_t *pcm_handle = NULL;
icculus@2049
   560
    snd_pcm_hw_params_t *hwparams = NULL;
icculus@2049
   561
    snd_pcm_sw_params_t *swparams = NULL;
icculus@2049
   562
    snd_pcm_format_t format = 0;
icculus@2049
   563
    SDL_AudioFormat test_format = 0;
icculus@3362
   564
    unsigned int rate = 0;
icculus@3362
   565
    unsigned int channels = 0;
slouken@10561
   566
#ifdef SND_CHMAP_API_VERSION
slouken@10560
   567
    snd_pcm_chmap_t *chmap;
slouken@10560
   568
    char chmap_str[64];
slouken@10561
   569
#endif
icculus@2049
   570
icculus@2049
   571
    /* Initialize all variables that we clean on shutdown */
icculus@2049
   572
    this->hidden = (struct SDL_PrivateAudioData *)
slouken@2060
   573
        SDL_malloc((sizeof *this->hidden));
icculus@2049
   574
    if (this->hidden == NULL) {
icculus@7038
   575
        return SDL_OutOfMemory();
icculus@2049
   576
    }
icculus@10257
   577
    SDL_zerop(this->hidden);
slouken@0
   578
slouken@1895
   579
    /* Open the audio device */
slouken@1895
   580
    /* Name of device should depend on # channels in spec */
icculus@2049
   581
    status = ALSA_snd_pcm_open(&pcm_handle,
icculus@10243
   582
                get_audio_device(handle, this->spec.channels),
icculus@10243
   583
                iscapture ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
icculus@10243
   584
                SND_PCM_NONBLOCK);
slouken@942
   585
slouken@1895
   586
    if (status < 0) {
icculus@7038
   587
        return SDL_SetError("ALSA: Couldn't open audio device: %s",
icculus@7038
   588
                            ALSA_snd_strerror(status));
slouken@1895
   589
    }
slouken@0
   590
icculus@2049
   591
    this->hidden->pcm_handle = pcm_handle;
icculus@2049
   592
slouken@1895
   593
    /* Figure out what the hardware is capable of */
slouken@1895
   594
    snd_pcm_hw_params_alloca(&hwparams);
icculus@2049
   595
    status = ALSA_snd_pcm_hw_params_any(pcm_handle, hwparams);
slouken@1895
   596
    if (status < 0) {
icculus@7038
   597
        return SDL_SetError("ALSA: Couldn't get hardware config: %s",
icculus@7038
   598
                            ALSA_snd_strerror(status));
slouken@1895
   599
    }
slouken@0
   600
slouken@1895
   601
    /* SDL only uses interleaved sample output */
icculus@2049
   602
    status = ALSA_snd_pcm_hw_params_set_access(pcm_handle, hwparams,
icculus@2049
   603
                                               SND_PCM_ACCESS_RW_INTERLEAVED);
slouken@1895
   604
    if (status < 0) {
icculus@7038
   605
        return SDL_SetError("ALSA: Couldn't set interleaved access: %s",
icculus@2049
   606
                     ALSA_snd_strerror(status));
slouken@1895
   607
    }
slouken@0
   608
slouken@1895
   609
    /* Try for a closest match on audio format */
slouken@1895
   610
    status = -1;
icculus@2049
   611
    for (test_format = SDL_FirstAudioFormat(this->spec.format);
slouken@1895
   612
         test_format && (status < 0);) {
slouken@2060
   613
        status = 0;             /* if we can't support a format, it'll become -1. */
slouken@1895
   614
        switch (test_format) {
slouken@1895
   615
        case AUDIO_U8:
slouken@1895
   616
            format = SND_PCM_FORMAT_U8;
slouken@1895
   617
            break;
slouken@1895
   618
        case AUDIO_S8:
slouken@1895
   619
            format = SND_PCM_FORMAT_S8;
slouken@1895
   620
            break;
slouken@1895
   621
        case AUDIO_S16LSB:
slouken@1895
   622
            format = SND_PCM_FORMAT_S16_LE;
slouken@1895
   623
            break;
slouken@1895
   624
        case AUDIO_S16MSB:
slouken@1895
   625
            format = SND_PCM_FORMAT_S16_BE;
slouken@1895
   626
            break;
slouken@1895
   627
        case AUDIO_U16LSB:
slouken@1895
   628
            format = SND_PCM_FORMAT_U16_LE;
slouken@1895
   629
            break;
slouken@1895
   630
        case AUDIO_U16MSB:
slouken@1895
   631
            format = SND_PCM_FORMAT_U16_BE;
slouken@1895
   632
            break;
icculus@1995
   633
        case AUDIO_S32LSB:
icculus@2010
   634
            format = SND_PCM_FORMAT_S32_LE;
icculus@1995
   635
            break;
icculus@1995
   636
        case AUDIO_S32MSB:
icculus@2010
   637
            format = SND_PCM_FORMAT_S32_BE;
icculus@1995
   638
            break;
icculus@1995
   639
        case AUDIO_F32LSB:
icculus@1995
   640
            format = SND_PCM_FORMAT_FLOAT_LE;
icculus@1995
   641
            break;
icculus@1995
   642
        case AUDIO_F32MSB:
icculus@1995
   643
            format = SND_PCM_FORMAT_FLOAT_BE;
icculus@1995
   644
            break;
slouken@1895
   645
        default:
icculus@2009
   646
            status = -1;
slouken@1895
   647
            break;
slouken@1895
   648
        }
icculus@2009
   649
        if (status >= 0) {
icculus@2049
   650
            status = ALSA_snd_pcm_hw_params_set_format(pcm_handle,
icculus@2049
   651
                                                       hwparams, format);
slouken@1895
   652
        }
slouken@1895
   653
        if (status < 0) {
slouken@1895
   654
            test_format = SDL_NextAudioFormat();
slouken@1895
   655
        }
slouken@1895
   656
    }
slouken@1895
   657
    if (status < 0) {
icculus@7038
   658
        return SDL_SetError("ALSA: Couldn't find any hardware audio formats");
slouken@1895
   659
    }
icculus@2049
   660
    this->spec.format = test_format;
slouken@0
   661
slouken@10560
   662
    /* Validate number of channels and determine if swizzling is necessary
slouken@10560
   663
     * Assume original swizzling, until proven otherwise.
slouken@10560
   664
     */
slouken@10560
   665
    this->hidden->swizzle_func = swizzle_alsa_channels;
slouken@10561
   666
#ifdef SND_CHMAP_API_VERSION
slouken@10560
   667
    chmap = ALSA_snd_pcm_get_chmap(pcm_handle);
slouken@10560
   668
    if (chmap) {
slouken@10560
   669
        ALSA_snd_pcm_chmap_print(chmap, sizeof(chmap_str), chmap_str);
slouken@10560
   670
        if (SDL_strcmp("FL FR FC LFE RL RR", chmap_str) == 0 ||
slouken@10560
   671
            SDL_strcmp("FL FR FC LFE SL SR", chmap_str) == 0) {
slouken@10560
   672
            this->hidden->swizzle_func = no_swizzle;
slouken@10560
   673
        }
slouken@10560
   674
        free(chmap);
slouken@10560
   675
    }
slouken@10561
   676
#endif /* SND_CHMAP_API_VERSION */
slouken@10560
   677
slouken@1895
   678
    /* Set the number of channels */
icculus@2049
   679
    status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams,
icculus@2049
   680
                                                 this->spec.channels);
icculus@3362
   681
    channels = this->spec.channels;
slouken@1895
   682
    if (status < 0) {
icculus@3362
   683
        status = ALSA_snd_pcm_hw_params_get_channels(hwparams, &channels);
icculus@3362
   684
        if (status < 0) {
icculus@7038
   685
            return SDL_SetError("ALSA: Couldn't set audio channels");
slouken@1895
   686
        }
icculus@3362
   687
        this->spec.channels = channels;
slouken@1895
   688
    }
slouken@0
   689
slouken@1895
   690
    /* Set the audio rate */
icculus@3362
   691
    rate = this->spec.freq;
icculus@2049
   692
    status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams,
icculus@3362
   693
                                                  &rate, NULL);
slouken@1895
   694
    if (status < 0) {
icculus@7038
   695
        return SDL_SetError("ALSA: Couldn't set audio frequency: %s",
icculus@7038
   696
                            ALSA_snd_strerror(status));
slouken@1895
   697
    }
icculus@3362
   698
    this->spec.freq = rate;
slouken@0
   699
slouken@1895
   700
    /* Set the buffer size, in samples */
icculus@3627
   701
    if ( ALSA_set_period_size(this, hwparams, 0) < 0 &&
icculus@3627
   702
         ALSA_set_buffer_size(this, hwparams, 0) < 0 ) {
icculus@3627
   703
        /* Failed to set desired buffer size, do the best you can... */
philipp@10206
   704
        status = ALSA_set_period_size(this, hwparams, 1);
philipp@10206
   705
        if (status < 0) {
icculus@7038
   706
            return SDL_SetError("Couldn't set hardware audio parameters: %s", ALSA_snd_strerror(status));
icculus@3627
   707
        }
icculus@3362
   708
    }
slouken@1895
   709
    /* Set the software parameters */
slouken@1895
   710
    snd_pcm_sw_params_alloca(&swparams);
icculus@2049
   711
    status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams);
slouken@1895
   712
    if (status < 0) {
icculus@7038
   713
        return SDL_SetError("ALSA: Couldn't get software config: %s",
icculus@7038
   714
                            ALSA_snd_strerror(status));
slouken@1895
   715
    }
icculus@5622
   716
    status = ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, this->spec.samples);
icculus@5622
   717
    if (status < 0) {
icculus@7038
   718
        return SDL_SetError("Couldn't set minimum available samples: %s",
icculus@7038
   719
                            ALSA_snd_strerror(status));
icculus@5622
   720
    }
slouken@2060
   721
    status =
icculus@3627
   722
        ALSA_snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, 1);
slouken@1895
   723
    if (status < 0) {
icculus@7038
   724
        return SDL_SetError("ALSA: Couldn't set start threshold: %s",
icculus@7038
   725
                            ALSA_snd_strerror(status));
slouken@1895
   726
    }
icculus@2049
   727
    status = ALSA_snd_pcm_sw_params(pcm_handle, swparams);
slouken@1895
   728
    if (status < 0) {
icculus@7038
   729
        return SDL_SetError("Couldn't set software audio parameters: %s",
icculus@7038
   730
                            ALSA_snd_strerror(status));
slouken@1895
   731
    }
slouken@0
   732
slouken@1895
   733
    /* Calculate the final parameters for this audio specification */
icculus@2049
   734
    SDL_CalculateAudioSpec(&this->spec);
slouken@0
   735
slouken@1895
   736
    /* Allocate mixing buffer */
icculus@10247
   737
    if (!iscapture) {
icculus@10247
   738
        this->hidden->mixlen = this->spec.size;
icculus@10256
   739
        this->hidden->mixbuf = (Uint8 *) SDL_malloc(this->hidden->mixlen);
icculus@10247
   740
        if (this->hidden->mixbuf == NULL) {
icculus@10247
   741
            return SDL_OutOfMemory();
icculus@10247
   742
        }
icculus@10247
   743
        SDL_memset(this->hidden->mixbuf, this->spec.silence, this->hidden->mixlen);
slouken@1895
   744
    }
slouken@0
   745
slouken@1895
   746
    /* Switch to blocking mode for playback */
icculus@2049
   747
    ALSA_snd_pcm_nonblock(pcm_handle, 0);
slouken@765
   748
slouken@1895
   749
    /* We're ready to rock and roll. :-) */
icculus@7038
   750
    return 0;
slouken@0
   751
}
slouken@1895
   752
icculus@10229
   753
typedef struct ALSA_Device
icculus@10229
   754
{
icculus@10229
   755
    char *name;
icculus@10229
   756
    SDL_bool iscapture;
icculus@10229
   757
    struct ALSA_Device *next;
icculus@10229
   758
} ALSA_Device;
icculus@10229
   759
icculus@2049
   760
static void
icculus@10229
   761
add_device(const int iscapture, const char *name, void *hint, ALSA_Device **pSeen)
icculus@2049
   762
{
icculus@10229
   763
    ALSA_Device *dev = SDL_malloc(sizeof (ALSA_Device));
icculus@10229
   764
    char *desc = ALSA_snd_device_name_get_hint(hint, "DESC");
icculus@10229
   765
    char *handle = NULL;
icculus@10229
   766
    char *ptr;
icculus@10229
   767
icculus@10229
   768
    if (!desc) {
icculus@10229
   769
        SDL_free(dev);
icculus@10229
   770
        return;
icculus@10229
   771
    } else if (!dev) {
icculus@10229
   772
        free(desc);
icculus@10229
   773
        return;
icculus@10229
   774
    }
icculus@10229
   775
icculus@10229
   776
    SDL_assert(name != NULL);
icculus@10229
   777
icculus@10229
   778
    /* some strings have newlines, like "HDA NVidia, HDMI 0\nHDMI Audio Output".
icculus@10229
   779
       just chop the extra lines off, this seems to get a reasonable device
icculus@10229
   780
       name without extra details. */
icculus@10229
   781
    if ((ptr = strchr(desc, '\n')) != NULL) {
icculus@10229
   782
        *ptr = '\0';
icculus@10229
   783
    }
icculus@10229
   784
icculus@10229
   785
    /*printf("ALSA: adding %s device '%s' (%s)\n", iscapture ? "capture" : "output", name, desc);*/
icculus@10229
   786
icculus@10229
   787
    handle = SDL_strdup(name);
icculus@10229
   788
    if (!handle) {
icculus@10229
   789
        free(desc);
icculus@10229
   790
        SDL_free(dev);
icculus@10229
   791
        return;
icculus@10229
   792
    }
icculus@10229
   793
icculus@10229
   794
    SDL_AddAudioDevice(iscapture, desc, handle);
icculus@10229
   795
    free(desc);
icculus@10229
   796
icculus@10229
   797
    dev->name = handle;
icculus@10229
   798
    dev->iscapture = iscapture;
icculus@10229
   799
    dev->next = *pSeen;
icculus@10229
   800
    *pSeen = dev;
icculus@2049
   801
}
icculus@2049
   802
icculus@10229
   803
icculus@10229
   804
static SDL_atomic_t ALSA_hotplug_shutdown;
icculus@10229
   805
static SDL_Thread *ALSA_hotplug_thread;
icculus@10229
   806
icculus@10229
   807
static int SDLCALL
icculus@10229
   808
ALSA_HotplugThread(void *arg)
icculus@10104
   809
{
icculus@10229
   810
    SDL_sem *first_run_semaphore = (SDL_sem *) arg;
icculus@10229
   811
    ALSA_Device *devices = NULL;
icculus@10229
   812
    ALSA_Device *next;
icculus@10229
   813
    ALSA_Device *dev;
icculus@10229
   814
    Uint32 ticks;
icculus@10104
   815
icculus@10229
   816
    while (!SDL_AtomicGet(&ALSA_hotplug_shutdown)) {
icculus@10229
   817
        void **hints = NULL;
icculus@10229
   818
        if (ALSA_snd_device_name_hint(-1, "pcm", &hints) != -1) {
icculus@10229
   819
            ALSA_Device *unseen = devices;
icculus@10229
   820
            ALSA_Device *seen = NULL;
icculus@10229
   821
            ALSA_Device *prev;
icculus@10506
   822
            int i, j;
icculus@10506
   823
            const char *match = NULL;
icculus@10506
   824
            int bestmatch = 0xFFFF;
icculus@10506
   825
            size_t match_len = 0;
icculus@10506
   826
            int defaultdev = -1;
icculus@10506
   827
            static const char * const prefixes[] = {
icculus@10506
   828
                "hw:", "sysdefault:", "default:", NULL
icculus@10506
   829
            };
icculus@10229
   830
icculus@10506
   831
            /* Apparently there are several different ways that ALSA lists
icculus@10506
   832
               actual hardware. It could be prefixed with "hw:" or "default:"
icculus@10506
   833
               or "sysdefault:" and maybe others. Go through the list and see
icculus@10506
   834
               if we can find a preferred prefix for the system. */
slouken@10470
   835
            for (i = 0; hints[i]; i++) {
slouken@10470
   836
                char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
slouken@10470
   837
                if (!name) {
slouken@10470
   838
                    continue;
slouken@10470
   839
                }
slouken@10470
   840
icculus@10506
   841
                /* full name, not a prefix */
icculus@10506
   842
                if ((defaultdev == -1) && (SDL_strcmp(name, "default") == 0)) {
icculus@10506
   843
                    defaultdev = i;
icculus@10506
   844
                }
icculus@10506
   845
icculus@10506
   846
                for (j = 0; prefixes[j]; j++) {
icculus@10506
   847
                    const char *prefix = prefixes[j];
slouken@10507
   848
                    const size_t prefixlen = SDL_strlen(prefix);
icculus@10506
   849
                    if (SDL_strncmp(name, prefix, prefixlen) == 0) {
icculus@10506
   850
                        if (j < bestmatch) {
icculus@10506
   851
                            bestmatch = j;
icculus@10506
   852
                            match = prefix;
icculus@10506
   853
                            match_len = prefixlen;
icculus@10506
   854
                        }
icculus@10506
   855
                    }
slouken@10470
   856
                }
slouken@10470
   857
slouken@10470
   858
                free(name);
slouken@10470
   859
            }
slouken@10470
   860
slouken@10470
   861
            /* look through the list of device names to find matches */
icculus@10229
   862
            for (i = 0; hints[i]; i++) {
icculus@10506
   863
                char *name;
icculus@10506
   864
icculus@10506
   865
                /* if we didn't find a device name prefix we like at all... */
icculus@10506
   866
                if ((!match) && (defaultdev != i)) {
icculus@10506
   867
                    continue;  /* ...skip anything that isn't the default device. */
icculus@10506
   868
                }
icculus@10506
   869
icculus@10506
   870
                name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
icculus@10229
   871
                if (!name) {
icculus@10229
   872
                    continue;
icculus@10229
   873
                }
icculus@10229
   874
icculus@10229
   875
                /* only want physical hardware interfaces */
icculus@10506
   876
                if (!match || (SDL_strncmp(name, match, match_len) == 0)) {
icculus@10229
   877
                    char *ioid = ALSA_snd_device_name_get_hint(hints[i], "IOID");
icculus@10229
   878
                    const SDL_bool isoutput = (ioid == NULL) || (SDL_strcmp(ioid, "Output") == 0);
icculus@10229
   879
                    const SDL_bool isinput = (ioid == NULL) || (SDL_strcmp(ioid, "Input") == 0);
icculus@10229
   880
                    SDL_bool have_output = SDL_FALSE;
icculus@10229
   881
                    SDL_bool have_input = SDL_FALSE;
icculus@10229
   882
icculus@10229
   883
                    free(ioid);
icculus@10229
   884
icculus@10229
   885
                    if (!isoutput && !isinput) {
icculus@10229
   886
                        free(name);
icculus@10229
   887
                        continue;
icculus@10229
   888
                    }
icculus@10229
   889
icculus@10229
   890
                    prev = NULL;
icculus@10229
   891
                    for (dev = unseen; dev; dev = next) {
icculus@10229
   892
                        next = dev->next;
icculus@10229
   893
                        if ( (SDL_strcmp(dev->name, name) == 0) && (((isinput) && dev->iscapture) || ((isoutput) && !dev->iscapture)) ) {
icculus@10229
   894
                            if (prev) {
icculus@10229
   895
                                prev->next = next;
icculus@10229
   896
                            } else {
icculus@10229
   897
                                unseen = next;
icculus@10229
   898
                            }
icculus@10229
   899
                            dev->next = seen;
icculus@10229
   900
                            seen = dev;
icculus@10229
   901
                            if (isinput) have_input = SDL_TRUE;
icculus@10229
   902
                            if (isoutput) have_output = SDL_TRUE;
icculus@10229
   903
                        } else {
icculus@10229
   904
                            prev = dev;
icculus@10229
   905
                        }
icculus@10229
   906
                    }
icculus@10229
   907
icculus@10229
   908
                    if (isinput && !have_input) {
icculus@10229
   909
                        add_device(SDL_TRUE, name, hints[i], &seen);
icculus@10229
   910
                    }
icculus@10229
   911
                    if (isoutput && !have_output) {
icculus@10229
   912
                        add_device(SDL_FALSE, name, hints[i], &seen);
icculus@10229
   913
                    }
icculus@10229
   914
                }
icculus@10229
   915
icculus@10229
   916
                free(name);
icculus@10229
   917
            }
icculus@10229
   918
icculus@10229
   919
            ALSA_snd_device_name_free_hint(hints);
icculus@10229
   920
icculus@10229
   921
            devices = seen;   /* now we have a known-good list of attached devices. */
icculus@10229
   922
icculus@10229
   923
            /* report anything still in unseen as removed. */
icculus@10229
   924
            for (dev = unseen; dev; dev = next) {
icculus@10229
   925
                /*printf("ALSA: removing %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/
icculus@10229
   926
                next = dev->next;
icculus@10229
   927
                SDL_RemoveAudioDevice(dev->iscapture, dev->name);
icculus@10229
   928
                SDL_free(dev->name);
icculus@10229
   929
                SDL_free(dev);
icculus@10229
   930
            }
icculus@10229
   931
        }
icculus@10229
   932
icculus@10229
   933
        /* On first run, tell ALSA_DetectDevices() that we have a complete device list so it can return. */
icculus@10229
   934
        if (first_run_semaphore) {
icculus@10229
   935
            SDL_SemPost(first_run_semaphore);
icculus@10229
   936
            first_run_semaphore = NULL;  /* let other thread clean it up. */
icculus@10229
   937
        }
icculus@10229
   938
icculus@10229
   939
        /* Block awhile before checking again, unless we're told to stop. */
icculus@10229
   940
        ticks = SDL_GetTicks() + 5000;
icculus@10230
   941
        while (!SDL_AtomicGet(&ALSA_hotplug_shutdown) && !SDL_TICKS_PASSED(SDL_GetTicks(), ticks)) {
icculus@10229
   942
            SDL_Delay(100);
icculus@10229
   943
        }
icculus@10142
   944
    }
icculus@10142
   945
icculus@10229
   946
    /* Shutting down! Clean up any data we've gathered. */
icculus@10229
   947
    for (dev = devices; dev; dev = next) {
icculus@10229
   948
        /*printf("ALSA: at shutdown, removing %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/
icculus@10229
   949
        next = dev->next;
icculus@10229
   950
        SDL_free(dev->name);
icculus@10229
   951
        SDL_free(dev);
icculus@10104
   952
    }
icculus@10104
   953
icculus@10229
   954
    return 0;
icculus@10104
   955
}
icculus@10104
   956
icculus@10104
   957
static void
icculus@10104
   958
ALSA_DetectDevices(void)
icculus@10104
   959
{
icculus@10229
   960
    /* Start the device detection thread here, wait for an initial iteration to complete. */
icculus@10229
   961
    SDL_sem *semaphore = SDL_CreateSemaphore(0);
icculus@10229
   962
    if (!semaphore) {
icculus@10104
   963
        return;  /* oh well. */
icculus@10104
   964
    }
icculus@10104
   965
icculus@10229
   966
    SDL_AtomicSet(&ALSA_hotplug_shutdown, 0);
icculus@10104
   967
icculus@10229
   968
    ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", semaphore);
icculus@10229
   969
    if (ALSA_hotplug_thread) {
icculus@10229
   970
        SDL_SemWait(semaphore);  /* wait for the first iteration to finish. */
icculus@10104
   971
    }
icculus@10104
   972
icculus@10229
   973
    SDL_DestroySemaphore(semaphore);
icculus@10104
   974
}
icculus@10104
   975
icculus@10104
   976
static void
icculus@10229
   977
ALSA_Deinitialize(void)
icculus@10104
   978
{
icculus@10229
   979
    if (ALSA_hotplug_thread != NULL) {
icculus@10229
   980
        SDL_AtomicSet(&ALSA_hotplug_shutdown, 1);
icculus@10229
   981
        SDL_WaitThread(ALSA_hotplug_thread, NULL);
icculus@10229
   982
        ALSA_hotplug_thread = NULL;
icculus@10229
   983
    }
icculus@10229
   984
icculus@10229
   985
    UnloadALSALibrary();
icculus@10104
   986
}
icculus@10104
   987
icculus@2049
   988
static int
slouken@2060
   989
ALSA_Init(SDL_AudioDriverImpl * impl)
icculus@2049
   990
{
icculus@2049
   991
    if (LoadALSALibrary() < 0) {
icculus@2049
   992
        return 0;
icculus@2049
   993
    }
icculus@2049
   994
icculus@2049
   995
    /* Set the function pointers */
icculus@10104
   996
    impl->DetectDevices = ALSA_DetectDevices;
icculus@2049
   997
    impl->OpenDevice = ALSA_OpenDevice;
icculus@2049
   998
    impl->WaitDevice = ALSA_WaitDevice;
icculus@2049
   999
    impl->GetDeviceBuf = ALSA_GetDeviceBuf;
icculus@2049
  1000
    impl->PlayDevice = ALSA_PlayDevice;
icculus@2049
  1001
    impl->CloseDevice = ALSA_CloseDevice;
icculus@2049
  1002
    impl->Deinitialize = ALSA_Deinitialize;
icculus@10243
  1003
    impl->CaptureFromDevice = ALSA_CaptureFromDevice;
icculus@10243
  1004
    impl->FlushCapture = ALSA_FlushCapture;
icculus@10243
  1005
icculus@10243
  1006
    impl->HasCaptureSupport = SDL_TRUE;
icculus@2049
  1007
icculus@3699
  1008
    return 1;   /* this audio target is available. */
icculus@2049
  1009
}
icculus@2049
  1010
icculus@2049
  1011
icculus@2049
  1012
AudioBootStrap ALSA_bootstrap = {
icculus@5594
  1013
    "alsa", "ALSA PCM audio", ALSA_Init, 0
icculus@2049
  1014
};
icculus@2049
  1015
slouken@6044
  1016
#endif /* SDL_AUDIO_DRIVER_ALSA */
slouken@6044
  1017
slouken@1895
  1018
/* vi: set ts=4 sw=4 expandtab: */