src/audio/alsa/SDL_alsa_audio.c
author Sam Lantinga <slouken@libsdl.org>
Sun, 01 Jan 2017 18:33:28 -0800
changeset 10737 3406a0f8b041
parent 10582 0e1dd7514610
child 10936 7ef6524d95c4
permissions -rw-r--r--
Updated copyright for 2017
slouken@0
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@10737
     3
  Copyright (C) 1997-2017 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
philipp@10582
   313
#ifdef SND_CHMAP_API_VERSION
slouken@10560
   314
/* Some devices have the right channel map, no swizzling necessary */
slouken@10560
   315
static void
slouken@10560
   316
no_swizzle(_THIS, void *buffer, Uint32 bufferlen)
slouken@10560
   317
{
slouken@10560
   318
    return;
slouken@10560
   319
}
philipp@10582
   320
#endif /* SND_CHMAP_API_VERSION */
slouken@10560
   321
icculus@1878
   322
slouken@1895
   323
static void
icculus@2049
   324
ALSA_PlayDevice(_THIS)
slouken@0
   325
{
icculus@3627
   326
    const Uint8 *sample_buf = (const Uint8 *) this->hidden->mixbuf;
icculus@10242
   327
    const int frame_size = (((int) SDL_AUDIO_BITSIZE(this->spec.format)) / 8) *
icculus@3627
   328
                                this->spec.channels;
icculus@3627
   329
    snd_pcm_uframes_t frames_left = ((snd_pcm_uframes_t) this->spec.samples);
slouken@765
   330
slouken@10560
   331
    this->hidden->swizzle_func(this, this->hidden->mixbuf, frames_left);
icculus@1878
   332
icculus@10238
   333
    while ( frames_left > 0 && SDL_AtomicGet(&this->enabled) ) {
slouken@10523
   334
        int status;
slouken@10523
   335
slouken@10523
   336
        /* This wait is a work-around for a hang when USB devices are
slouken@10523
   337
           unplugged.  Normally it should not result in any waiting,
slouken@10523
   338
           but in the case of a USB unplug, it serves as a way to
slouken@10523
   339
           join the playback thread after the timeout occurs */
slouken@10523
   340
        status = ALSA_snd_pcm_wait(this->hidden->pcm_handle, 1000);
slouken@10523
   341
        if (status == 0) {
slouken@10523
   342
            /*fprintf(stderr, "ALSA timeout waiting for available buffer space\n");*/
slouken@10523
   343
            SDL_OpenedAudioDeviceDisconnected(this);
slouken@10523
   344
            return;
slouken@10523
   345
        }
slouken@10523
   346
slouken@10523
   347
        status = ALSA_snd_pcm_writei(this->hidden->pcm_handle,
icculus@10242
   348
                                         sample_buf, frames_left);
icculus@2049
   349
slouken@1895
   350
        if (status < 0) {
slouken@1895
   351
            if (status == -EAGAIN) {
icculus@3627
   352
                /* Apparently snd_pcm_recover() doesn't handle this case -
icculus@3627
   353
                   does it assume snd_pcm_wait() above? */
slouken@1895
   354
                SDL_Delay(1);
slouken@1895
   355
                continue;
slouken@1895
   356
            }
icculus@3627
   357
            status = ALSA_snd_pcm_recover(this->hidden->pcm_handle, status, 0);
slouken@1895
   358
            if (status < 0) {
slouken@1895
   359
                /* Hmm, not much we can do - abort */
icculus@3627
   360
                fprintf(stderr, "ALSA write failed (unrecoverable): %s\n",
icculus@3627
   361
                        ALSA_snd_strerror(status));
icculus@9394
   362
                SDL_OpenedAudioDeviceDisconnected(this);
slouken@1895
   363
                return;
slouken@1895
   364
            }
slouken@1895
   365
            continue;
slouken@1895
   366
        }
icculus@3627
   367
        sample_buf += status * frame_size;
icculus@3627
   368
        frames_left -= status;
slouken@1895
   369
    }
slouken@0
   370
}
slouken@0
   371
slouken@1895
   372
static Uint8 *
icculus@2049
   373
ALSA_GetDeviceBuf(_THIS)
slouken@0
   374
{
icculus@2049
   375
    return (this->hidden->mixbuf);
slouken@354
   376
}
slouken@0
   377
icculus@10243
   378
static int
icculus@10243
   379
ALSA_CaptureFromDevice(_THIS, void *buffer, int buflen)
icculus@10243
   380
{
icculus@10243
   381
    Uint8 *sample_buf = (Uint8 *) buffer;
icculus@10243
   382
    const int frame_size = (((int) SDL_AUDIO_BITSIZE(this->spec.format)) / 8) *
icculus@10243
   383
                                this->spec.channels;
icculus@10243
   384
    const int total_frames = buflen / frame_size;
icculus@10243
   385
    snd_pcm_uframes_t frames_left = total_frames;
icculus@10243
   386
icculus@10243
   387
    SDL_assert((buflen % frame_size) == 0);
icculus@10243
   388
icculus@10243
   389
    while ( frames_left > 0 && SDL_AtomicGet(&this->enabled) ) {
icculus@10243
   390
        /* !!! FIXME: This works, but needs more testing before going live */
icculus@10243
   391
        /* ALSA_snd_pcm_wait(this->hidden->pcm_handle, -1); */
icculus@10243
   392
        int status = ALSA_snd_pcm_readi(this->hidden->pcm_handle,
icculus@10243
   393
                                        sample_buf, frames_left);
icculus@10243
   394
icculus@10243
   395
        if (status < 0) {
icculus@10243
   396
            /*printf("ALSA: capture error %d\n", status);*/
icculus@10243
   397
            if (status == -EAGAIN) {
icculus@10243
   398
                /* Apparently snd_pcm_recover() doesn't handle this case -
icculus@10243
   399
                   does it assume snd_pcm_wait() above? */
icculus@10243
   400
                SDL_Delay(1);
icculus@10243
   401
                continue;
icculus@10243
   402
            }
icculus@10243
   403
            status = ALSA_snd_pcm_recover(this->hidden->pcm_handle, status, 0);
icculus@10243
   404
            if (status < 0) {
icculus@10243
   405
                /* Hmm, not much we can do - abort */
icculus@10243
   406
                fprintf(stderr, "ALSA read failed (unrecoverable): %s\n",
icculus@10243
   407
                        ALSA_snd_strerror(status));
icculus@10243
   408
                return -1;
icculus@10243
   409
            }
icculus@10243
   410
            continue;
icculus@10243
   411
        }
icculus@10243
   412
icculus@10243
   413
        /*printf("ALSA: captured %d bytes\n", status * frame_size);*/
icculus@10243
   414
        sample_buf += status * frame_size;
icculus@10243
   415
        frames_left -= status;
icculus@10243
   416
    }
icculus@10243
   417
slouken@10560
   418
    this->hidden->swizzle_func(this, buffer, total_frames - frames_left);
icculus@10243
   419
icculus@10243
   420
    return (total_frames - frames_left) * frame_size;
icculus@10243
   421
}
icculus@10243
   422
icculus@10243
   423
static void
icculus@10243
   424
ALSA_FlushCapture(_THIS)
icculus@10243
   425
{
icculus@10243
   426
    ALSA_snd_pcm_reset(this->hidden->pcm_handle);
icculus@10243
   427
}
icculus@10243
   428
slouken@1895
   429
static void
icculus@2049
   430
ALSA_CloseDevice(_THIS)
slouken@354
   431
{
icculus@10255
   432
    if (this->hidden->pcm_handle) {
slouken@10497
   433
	/* Wait for the submitted audio to drain
slouken@10497
   434
           ALSA_snd_pcm_drop() can hang, so don't use that.
slouken@10497
   435
         */
slouken@10497
   436
        Uint32 delay = ((this->spec.samples * 1000) / this->spec.freq) * 2;
slouken@10497
   437
        SDL_Delay(delay);
slouken@10497
   438
icculus@10255
   439
        ALSA_snd_pcm_close(this->hidden->pcm_handle);
slouken@1895
   440
    }
icculus@10256
   441
    SDL_free(this->hidden->mixbuf);
icculus@10255
   442
    SDL_free(this->hidden);
slouken@354
   443
}
slouken@0
   444
slouken@1895
   445
static int
icculus@3627
   446
ALSA_finalize_hardware(_THIS, snd_pcm_hw_params_t *hwparams, int override)
icculus@3627
   447
{
icculus@3627
   448
    int status;
icculus@3627
   449
    snd_pcm_uframes_t bufsize;
icculus@3627
   450
icculus@3627
   451
    /* "set" the hardware with the desired parameters */
icculus@3627
   452
    status = ALSA_snd_pcm_hw_params(this->hidden->pcm_handle, hwparams);
icculus@3627
   453
    if ( status < 0 ) {
icculus@3627
   454
        return(-1);
icculus@3627
   455
    }
icculus@3627
   456
icculus@3627
   457
    /* Get samples for the actual buffer size */
icculus@3627
   458
    status = ALSA_snd_pcm_hw_params_get_buffer_size(hwparams, &bufsize);
icculus@3627
   459
    if ( status < 0 ) {
icculus@3627
   460
        return(-1);
icculus@3627
   461
    }
icculus@3627
   462
    if ( !override && bufsize != this->spec.samples * 2 ) {
icculus@3627
   463
        return(-1);
icculus@3627
   464
    }
icculus@3627
   465
icculus@3627
   466
    /* !!! FIXME: Is this safe to do? */
icculus@3627
   467
    this->spec.samples = bufsize / 2;
icculus@3627
   468
icculus@3627
   469
    /* This is useful for debugging */
icculus@3627
   470
    if ( SDL_getenv("SDL_AUDIO_ALSA_DEBUG") ) {
icculus@3627
   471
        snd_pcm_uframes_t persize = 0;
icculus@3627
   472
        unsigned int periods = 0;
icculus@3627
   473
icculus@3627
   474
        ALSA_snd_pcm_hw_params_get_period_size(hwparams, &persize, NULL);
icculus@3627
   475
        ALSA_snd_pcm_hw_params_get_periods(hwparams, &periods, NULL);
icculus@3627
   476
icculus@3627
   477
        fprintf(stderr,
icculus@3627
   478
            "ALSA: period size = %ld, periods = %u, buffer size = %lu\n",
icculus@3627
   479
            persize, periods, bufsize);
icculus@3627
   480
    }
icculus@3627
   481
icculus@3627
   482
    return(0);
icculus@3627
   483
}
icculus@3627
   484
icculus@3627
   485
static int
icculus@3627
   486
ALSA_set_period_size(_THIS, snd_pcm_hw_params_t *params, int override)
icculus@3627
   487
{
icculus@3627
   488
    const char *env;
icculus@3627
   489
    int status;
icculus@3627
   490
    snd_pcm_hw_params_t *hwparams;
icculus@3627
   491
    snd_pcm_uframes_t frames;
icculus@3627
   492
    unsigned int periods;
icculus@3627
   493
icculus@3627
   494
    /* Copy the hardware parameters for this setup */
icculus@3627
   495
    snd_pcm_hw_params_alloca(&hwparams);
icculus@3627
   496
    ALSA_snd_pcm_hw_params_copy(hwparams, params);
icculus@3627
   497
icculus@3627
   498
    if ( !override ) {
icculus@3627
   499
        env = SDL_getenv("SDL_AUDIO_ALSA_SET_PERIOD_SIZE");
icculus@3627
   500
        if ( env ) {
icculus@3627
   501
            override = SDL_atoi(env);
icculus@3627
   502
            if ( override == 0 ) {
icculus@3627
   503
                return(-1);
icculus@3627
   504
            }
icculus@3627
   505
        }
icculus@3627
   506
    }
icculus@3627
   507
icculus@3627
   508
    frames = this->spec.samples;
icculus@3627
   509
    status = ALSA_snd_pcm_hw_params_set_period_size_near(
icculus@3627
   510
                this->hidden->pcm_handle, hwparams, &frames, NULL);
icculus@3627
   511
    if ( status < 0 ) {
icculus@3627
   512
        return(-1);
icculus@3627
   513
    }
icculus@3627
   514
icculus@3627
   515
    periods = 2;
icculus@3627
   516
    status = ALSA_snd_pcm_hw_params_set_periods_near(
icculus@3627
   517
                this->hidden->pcm_handle, hwparams, &periods, NULL);
icculus@3627
   518
    if ( status < 0 ) {
icculus@3627
   519
        return(-1);
icculus@3627
   520
    }
icculus@3627
   521
icculus@3627
   522
    return ALSA_finalize_hardware(this, hwparams, override);
icculus@3627
   523
}
icculus@3627
   524
icculus@3627
   525
static int
icculus@3627
   526
ALSA_set_buffer_size(_THIS, snd_pcm_hw_params_t *params, int override)
icculus@3627
   527
{
icculus@3627
   528
    const char *env;
icculus@3627
   529
    int status;
icculus@3627
   530
    snd_pcm_hw_params_t *hwparams;
icculus@3627
   531
    snd_pcm_uframes_t frames;
icculus@3627
   532
icculus@3627
   533
    /* Copy the hardware parameters for this setup */
icculus@3627
   534
    snd_pcm_hw_params_alloca(&hwparams);
icculus@3627
   535
    ALSA_snd_pcm_hw_params_copy(hwparams, params);
icculus@3627
   536
icculus@3627
   537
    if ( !override ) {
icculus@3627
   538
        env = SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE");
icculus@3627
   539
        if ( env ) {
icculus@3627
   540
            override = SDL_atoi(env);
icculus@3627
   541
            if ( override == 0 ) {
icculus@3627
   542
                return(-1);
icculus@3627
   543
            }
icculus@3627
   544
        }
icculus@3627
   545
    }
icculus@3627
   546
icculus@3627
   547
    frames = this->spec.samples * 2;
icculus@3627
   548
    status = ALSA_snd_pcm_hw_params_set_buffer_size_near(
icculus@3627
   549
                    this->hidden->pcm_handle, hwparams, &frames);
icculus@3627
   550
    if ( status < 0 ) {
icculus@3627
   551
        return(-1);
icculus@3627
   552
    }
icculus@3627
   553
icculus@3627
   554
    return ALSA_finalize_hardware(this, hwparams, override);
icculus@3627
   555
}
icculus@3627
   556
icculus@3627
   557
static int
icculus@9394
   558
ALSA_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
slouken@354
   559
{
icculus@2049
   560
    int status = 0;
icculus@2049
   561
    snd_pcm_t *pcm_handle = NULL;
icculus@2049
   562
    snd_pcm_hw_params_t *hwparams = NULL;
icculus@2049
   563
    snd_pcm_sw_params_t *swparams = NULL;
icculus@2049
   564
    snd_pcm_format_t format = 0;
icculus@2049
   565
    SDL_AudioFormat test_format = 0;
icculus@3362
   566
    unsigned int rate = 0;
icculus@3362
   567
    unsigned int channels = 0;
slouken@10561
   568
#ifdef SND_CHMAP_API_VERSION
slouken@10560
   569
    snd_pcm_chmap_t *chmap;
slouken@10560
   570
    char chmap_str[64];
slouken@10561
   571
#endif
icculus@2049
   572
icculus@2049
   573
    /* Initialize all variables that we clean on shutdown */
icculus@2049
   574
    this->hidden = (struct SDL_PrivateAudioData *)
slouken@2060
   575
        SDL_malloc((sizeof *this->hidden));
icculus@2049
   576
    if (this->hidden == NULL) {
icculus@7038
   577
        return SDL_OutOfMemory();
icculus@2049
   578
    }
icculus@10257
   579
    SDL_zerop(this->hidden);
slouken@0
   580
slouken@1895
   581
    /* Open the audio device */
slouken@1895
   582
    /* Name of device should depend on # channels in spec */
icculus@2049
   583
    status = ALSA_snd_pcm_open(&pcm_handle,
icculus@10243
   584
                get_audio_device(handle, this->spec.channels),
icculus@10243
   585
                iscapture ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
icculus@10243
   586
                SND_PCM_NONBLOCK);
slouken@942
   587
slouken@1895
   588
    if (status < 0) {
icculus@7038
   589
        return SDL_SetError("ALSA: Couldn't open audio device: %s",
icculus@7038
   590
                            ALSA_snd_strerror(status));
slouken@1895
   591
    }
slouken@0
   592
icculus@2049
   593
    this->hidden->pcm_handle = pcm_handle;
icculus@2049
   594
slouken@1895
   595
    /* Figure out what the hardware is capable of */
slouken@1895
   596
    snd_pcm_hw_params_alloca(&hwparams);
icculus@2049
   597
    status = ALSA_snd_pcm_hw_params_any(pcm_handle, hwparams);
slouken@1895
   598
    if (status < 0) {
icculus@7038
   599
        return SDL_SetError("ALSA: Couldn't get hardware config: %s",
icculus@7038
   600
                            ALSA_snd_strerror(status));
slouken@1895
   601
    }
slouken@0
   602
slouken@1895
   603
    /* SDL only uses interleaved sample output */
icculus@2049
   604
    status = ALSA_snd_pcm_hw_params_set_access(pcm_handle, hwparams,
icculus@2049
   605
                                               SND_PCM_ACCESS_RW_INTERLEAVED);
slouken@1895
   606
    if (status < 0) {
icculus@7038
   607
        return SDL_SetError("ALSA: Couldn't set interleaved access: %s",
icculus@2049
   608
                     ALSA_snd_strerror(status));
slouken@1895
   609
    }
slouken@0
   610
slouken@1895
   611
    /* Try for a closest match on audio format */
slouken@1895
   612
    status = -1;
icculus@2049
   613
    for (test_format = SDL_FirstAudioFormat(this->spec.format);
slouken@1895
   614
         test_format && (status < 0);) {
slouken@2060
   615
        status = 0;             /* if we can't support a format, it'll become -1. */
slouken@1895
   616
        switch (test_format) {
slouken@1895
   617
        case AUDIO_U8:
slouken@1895
   618
            format = SND_PCM_FORMAT_U8;
slouken@1895
   619
            break;
slouken@1895
   620
        case AUDIO_S8:
slouken@1895
   621
            format = SND_PCM_FORMAT_S8;
slouken@1895
   622
            break;
slouken@1895
   623
        case AUDIO_S16LSB:
slouken@1895
   624
            format = SND_PCM_FORMAT_S16_LE;
slouken@1895
   625
            break;
slouken@1895
   626
        case AUDIO_S16MSB:
slouken@1895
   627
            format = SND_PCM_FORMAT_S16_BE;
slouken@1895
   628
            break;
slouken@1895
   629
        case AUDIO_U16LSB:
slouken@1895
   630
            format = SND_PCM_FORMAT_U16_LE;
slouken@1895
   631
            break;
slouken@1895
   632
        case AUDIO_U16MSB:
slouken@1895
   633
            format = SND_PCM_FORMAT_U16_BE;
slouken@1895
   634
            break;
icculus@1995
   635
        case AUDIO_S32LSB:
icculus@2010
   636
            format = SND_PCM_FORMAT_S32_LE;
icculus@1995
   637
            break;
icculus@1995
   638
        case AUDIO_S32MSB:
icculus@2010
   639
            format = SND_PCM_FORMAT_S32_BE;
icculus@1995
   640
            break;
icculus@1995
   641
        case AUDIO_F32LSB:
icculus@1995
   642
            format = SND_PCM_FORMAT_FLOAT_LE;
icculus@1995
   643
            break;
icculus@1995
   644
        case AUDIO_F32MSB:
icculus@1995
   645
            format = SND_PCM_FORMAT_FLOAT_BE;
icculus@1995
   646
            break;
slouken@1895
   647
        default:
icculus@2009
   648
            status = -1;
slouken@1895
   649
            break;
slouken@1895
   650
        }
icculus@2009
   651
        if (status >= 0) {
icculus@2049
   652
            status = ALSA_snd_pcm_hw_params_set_format(pcm_handle,
icculus@2049
   653
                                                       hwparams, format);
slouken@1895
   654
        }
slouken@1895
   655
        if (status < 0) {
slouken@1895
   656
            test_format = SDL_NextAudioFormat();
slouken@1895
   657
        }
slouken@1895
   658
    }
slouken@1895
   659
    if (status < 0) {
icculus@7038
   660
        return SDL_SetError("ALSA: Couldn't find any hardware audio formats");
slouken@1895
   661
    }
icculus@2049
   662
    this->spec.format = test_format;
slouken@0
   663
slouken@10560
   664
    /* Validate number of channels and determine if swizzling is necessary
slouken@10560
   665
     * Assume original swizzling, until proven otherwise.
slouken@10560
   666
     */
slouken@10560
   667
    this->hidden->swizzle_func = swizzle_alsa_channels;
slouken@10561
   668
#ifdef SND_CHMAP_API_VERSION
slouken@10560
   669
    chmap = ALSA_snd_pcm_get_chmap(pcm_handle);
slouken@10560
   670
    if (chmap) {
slouken@10560
   671
        ALSA_snd_pcm_chmap_print(chmap, sizeof(chmap_str), chmap_str);
slouken@10560
   672
        if (SDL_strcmp("FL FR FC LFE RL RR", chmap_str) == 0 ||
slouken@10560
   673
            SDL_strcmp("FL FR FC LFE SL SR", chmap_str) == 0) {
slouken@10560
   674
            this->hidden->swizzle_func = no_swizzle;
slouken@10560
   675
        }
slouken@10560
   676
        free(chmap);
slouken@10560
   677
    }
slouken@10561
   678
#endif /* SND_CHMAP_API_VERSION */
slouken@10560
   679
slouken@1895
   680
    /* Set the number of channels */
icculus@2049
   681
    status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams,
icculus@2049
   682
                                                 this->spec.channels);
icculus@3362
   683
    channels = this->spec.channels;
slouken@1895
   684
    if (status < 0) {
icculus@3362
   685
        status = ALSA_snd_pcm_hw_params_get_channels(hwparams, &channels);
icculus@3362
   686
        if (status < 0) {
icculus@7038
   687
            return SDL_SetError("ALSA: Couldn't set audio channels");
slouken@1895
   688
        }
icculus@3362
   689
        this->spec.channels = channels;
slouken@1895
   690
    }
slouken@0
   691
slouken@1895
   692
    /* Set the audio rate */
icculus@3362
   693
    rate = this->spec.freq;
icculus@2049
   694
    status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams,
icculus@3362
   695
                                                  &rate, NULL);
slouken@1895
   696
    if (status < 0) {
icculus@7038
   697
        return SDL_SetError("ALSA: Couldn't set audio frequency: %s",
icculus@7038
   698
                            ALSA_snd_strerror(status));
slouken@1895
   699
    }
icculus@3362
   700
    this->spec.freq = rate;
slouken@0
   701
slouken@1895
   702
    /* Set the buffer size, in samples */
icculus@3627
   703
    if ( ALSA_set_period_size(this, hwparams, 0) < 0 &&
icculus@3627
   704
         ALSA_set_buffer_size(this, hwparams, 0) < 0 ) {
icculus@3627
   705
        /* Failed to set desired buffer size, do the best you can... */
philipp@10206
   706
        status = ALSA_set_period_size(this, hwparams, 1);
philipp@10206
   707
        if (status < 0) {
icculus@7038
   708
            return SDL_SetError("Couldn't set hardware audio parameters: %s", ALSA_snd_strerror(status));
icculus@3627
   709
        }
icculus@3362
   710
    }
slouken@1895
   711
    /* Set the software parameters */
slouken@1895
   712
    snd_pcm_sw_params_alloca(&swparams);
icculus@2049
   713
    status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams);
slouken@1895
   714
    if (status < 0) {
icculus@7038
   715
        return SDL_SetError("ALSA: Couldn't get software config: %s",
icculus@7038
   716
                            ALSA_snd_strerror(status));
slouken@1895
   717
    }
icculus@5622
   718
    status = ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, this->spec.samples);
icculus@5622
   719
    if (status < 0) {
icculus@7038
   720
        return SDL_SetError("Couldn't set minimum available samples: %s",
icculus@7038
   721
                            ALSA_snd_strerror(status));
icculus@5622
   722
    }
slouken@2060
   723
    status =
icculus@3627
   724
        ALSA_snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, 1);
slouken@1895
   725
    if (status < 0) {
icculus@7038
   726
        return SDL_SetError("ALSA: Couldn't set start threshold: %s",
icculus@7038
   727
                            ALSA_snd_strerror(status));
slouken@1895
   728
    }
icculus@2049
   729
    status = ALSA_snd_pcm_sw_params(pcm_handle, swparams);
slouken@1895
   730
    if (status < 0) {
icculus@7038
   731
        return SDL_SetError("Couldn't set software audio parameters: %s",
icculus@7038
   732
                            ALSA_snd_strerror(status));
slouken@1895
   733
    }
slouken@0
   734
slouken@1895
   735
    /* Calculate the final parameters for this audio specification */
icculus@2049
   736
    SDL_CalculateAudioSpec(&this->spec);
slouken@0
   737
slouken@1895
   738
    /* Allocate mixing buffer */
icculus@10247
   739
    if (!iscapture) {
icculus@10247
   740
        this->hidden->mixlen = this->spec.size;
icculus@10256
   741
        this->hidden->mixbuf = (Uint8 *) SDL_malloc(this->hidden->mixlen);
icculus@10247
   742
        if (this->hidden->mixbuf == NULL) {
icculus@10247
   743
            return SDL_OutOfMemory();
icculus@10247
   744
        }
icculus@10247
   745
        SDL_memset(this->hidden->mixbuf, this->spec.silence, this->hidden->mixlen);
slouken@1895
   746
    }
slouken@0
   747
slouken@1895
   748
    /* Switch to blocking mode for playback */
icculus@2049
   749
    ALSA_snd_pcm_nonblock(pcm_handle, 0);
slouken@765
   750
slouken@1895
   751
    /* We're ready to rock and roll. :-) */
icculus@7038
   752
    return 0;
slouken@0
   753
}
slouken@1895
   754
icculus@10229
   755
typedef struct ALSA_Device
icculus@10229
   756
{
icculus@10229
   757
    char *name;
icculus@10229
   758
    SDL_bool iscapture;
icculus@10229
   759
    struct ALSA_Device *next;
icculus@10229
   760
} ALSA_Device;
icculus@10229
   761
icculus@2049
   762
static void
icculus@10229
   763
add_device(const int iscapture, const char *name, void *hint, ALSA_Device **pSeen)
icculus@2049
   764
{
icculus@10229
   765
    ALSA_Device *dev = SDL_malloc(sizeof (ALSA_Device));
icculus@10229
   766
    char *desc = ALSA_snd_device_name_get_hint(hint, "DESC");
icculus@10229
   767
    char *handle = NULL;
icculus@10229
   768
    char *ptr;
icculus@10229
   769
icculus@10229
   770
    if (!desc) {
icculus@10229
   771
        SDL_free(dev);
icculus@10229
   772
        return;
icculus@10229
   773
    } else if (!dev) {
icculus@10229
   774
        free(desc);
icculus@10229
   775
        return;
icculus@10229
   776
    }
icculus@10229
   777
icculus@10229
   778
    SDL_assert(name != NULL);
icculus@10229
   779
icculus@10229
   780
    /* some strings have newlines, like "HDA NVidia, HDMI 0\nHDMI Audio Output".
icculus@10229
   781
       just chop the extra lines off, this seems to get a reasonable device
icculus@10229
   782
       name without extra details. */
icculus@10229
   783
    if ((ptr = strchr(desc, '\n')) != NULL) {
icculus@10229
   784
        *ptr = '\0';
icculus@10229
   785
    }
icculus@10229
   786
icculus@10229
   787
    /*printf("ALSA: adding %s device '%s' (%s)\n", iscapture ? "capture" : "output", name, desc);*/
icculus@10229
   788
icculus@10229
   789
    handle = SDL_strdup(name);
icculus@10229
   790
    if (!handle) {
icculus@10229
   791
        free(desc);
icculus@10229
   792
        SDL_free(dev);
icculus@10229
   793
        return;
icculus@10229
   794
    }
icculus@10229
   795
icculus@10229
   796
    SDL_AddAudioDevice(iscapture, desc, handle);
icculus@10229
   797
    free(desc);
icculus@10229
   798
icculus@10229
   799
    dev->name = handle;
icculus@10229
   800
    dev->iscapture = iscapture;
icculus@10229
   801
    dev->next = *pSeen;
icculus@10229
   802
    *pSeen = dev;
icculus@2049
   803
}
icculus@2049
   804
icculus@10229
   805
icculus@10229
   806
static SDL_atomic_t ALSA_hotplug_shutdown;
icculus@10229
   807
static SDL_Thread *ALSA_hotplug_thread;
icculus@10229
   808
icculus@10229
   809
static int SDLCALL
icculus@10229
   810
ALSA_HotplugThread(void *arg)
icculus@10104
   811
{
icculus@10229
   812
    SDL_sem *first_run_semaphore = (SDL_sem *) arg;
icculus@10229
   813
    ALSA_Device *devices = NULL;
icculus@10229
   814
    ALSA_Device *next;
icculus@10229
   815
    ALSA_Device *dev;
icculus@10229
   816
    Uint32 ticks;
icculus@10104
   817
icculus@10229
   818
    while (!SDL_AtomicGet(&ALSA_hotplug_shutdown)) {
icculus@10229
   819
        void **hints = NULL;
icculus@10229
   820
        if (ALSA_snd_device_name_hint(-1, "pcm", &hints) != -1) {
icculus@10229
   821
            ALSA_Device *unseen = devices;
icculus@10229
   822
            ALSA_Device *seen = NULL;
icculus@10229
   823
            ALSA_Device *prev;
icculus@10506
   824
            int i, j;
icculus@10506
   825
            const char *match = NULL;
icculus@10506
   826
            int bestmatch = 0xFFFF;
icculus@10506
   827
            size_t match_len = 0;
icculus@10506
   828
            int defaultdev = -1;
icculus@10506
   829
            static const char * const prefixes[] = {
icculus@10506
   830
                "hw:", "sysdefault:", "default:", NULL
icculus@10506
   831
            };
icculus@10229
   832
icculus@10506
   833
            /* Apparently there are several different ways that ALSA lists
icculus@10506
   834
               actual hardware. It could be prefixed with "hw:" or "default:"
icculus@10506
   835
               or "sysdefault:" and maybe others. Go through the list and see
icculus@10506
   836
               if we can find a preferred prefix for the system. */
slouken@10470
   837
            for (i = 0; hints[i]; i++) {
slouken@10470
   838
                char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
slouken@10470
   839
                if (!name) {
slouken@10470
   840
                    continue;
slouken@10470
   841
                }
slouken@10470
   842
icculus@10506
   843
                /* full name, not a prefix */
icculus@10506
   844
                if ((defaultdev == -1) && (SDL_strcmp(name, "default") == 0)) {
icculus@10506
   845
                    defaultdev = i;
icculus@10506
   846
                }
icculus@10506
   847
icculus@10506
   848
                for (j = 0; prefixes[j]; j++) {
icculus@10506
   849
                    const char *prefix = prefixes[j];
slouken@10507
   850
                    const size_t prefixlen = SDL_strlen(prefix);
icculus@10506
   851
                    if (SDL_strncmp(name, prefix, prefixlen) == 0) {
icculus@10506
   852
                        if (j < bestmatch) {
icculus@10506
   853
                            bestmatch = j;
icculus@10506
   854
                            match = prefix;
icculus@10506
   855
                            match_len = prefixlen;
icculus@10506
   856
                        }
icculus@10506
   857
                    }
slouken@10470
   858
                }
slouken@10470
   859
slouken@10470
   860
                free(name);
slouken@10470
   861
            }
slouken@10470
   862
slouken@10470
   863
            /* look through the list of device names to find matches */
icculus@10229
   864
            for (i = 0; hints[i]; i++) {
icculus@10506
   865
                char *name;
icculus@10506
   866
icculus@10506
   867
                /* if we didn't find a device name prefix we like at all... */
icculus@10506
   868
                if ((!match) && (defaultdev != i)) {
icculus@10506
   869
                    continue;  /* ...skip anything that isn't the default device. */
icculus@10506
   870
                }
icculus@10506
   871
icculus@10506
   872
                name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
icculus@10229
   873
                if (!name) {
icculus@10229
   874
                    continue;
icculus@10229
   875
                }
icculus@10229
   876
icculus@10229
   877
                /* only want physical hardware interfaces */
icculus@10506
   878
                if (!match || (SDL_strncmp(name, match, match_len) == 0)) {
icculus@10229
   879
                    char *ioid = ALSA_snd_device_name_get_hint(hints[i], "IOID");
icculus@10229
   880
                    const SDL_bool isoutput = (ioid == NULL) || (SDL_strcmp(ioid, "Output") == 0);
icculus@10229
   881
                    const SDL_bool isinput = (ioid == NULL) || (SDL_strcmp(ioid, "Input") == 0);
icculus@10229
   882
                    SDL_bool have_output = SDL_FALSE;
icculus@10229
   883
                    SDL_bool have_input = SDL_FALSE;
icculus@10229
   884
icculus@10229
   885
                    free(ioid);
icculus@10229
   886
icculus@10229
   887
                    if (!isoutput && !isinput) {
icculus@10229
   888
                        free(name);
icculus@10229
   889
                        continue;
icculus@10229
   890
                    }
icculus@10229
   891
icculus@10229
   892
                    prev = NULL;
icculus@10229
   893
                    for (dev = unseen; dev; dev = next) {
icculus@10229
   894
                        next = dev->next;
icculus@10229
   895
                        if ( (SDL_strcmp(dev->name, name) == 0) && (((isinput) && dev->iscapture) || ((isoutput) && !dev->iscapture)) ) {
icculus@10229
   896
                            if (prev) {
icculus@10229
   897
                                prev->next = next;
icculus@10229
   898
                            } else {
icculus@10229
   899
                                unseen = next;
icculus@10229
   900
                            }
icculus@10229
   901
                            dev->next = seen;
icculus@10229
   902
                            seen = dev;
icculus@10229
   903
                            if (isinput) have_input = SDL_TRUE;
icculus@10229
   904
                            if (isoutput) have_output = SDL_TRUE;
icculus@10229
   905
                        } else {
icculus@10229
   906
                            prev = dev;
icculus@10229
   907
                        }
icculus@10229
   908
                    }
icculus@10229
   909
icculus@10229
   910
                    if (isinput && !have_input) {
icculus@10229
   911
                        add_device(SDL_TRUE, name, hints[i], &seen);
icculus@10229
   912
                    }
icculus@10229
   913
                    if (isoutput && !have_output) {
icculus@10229
   914
                        add_device(SDL_FALSE, name, hints[i], &seen);
icculus@10229
   915
                    }
icculus@10229
   916
                }
icculus@10229
   917
icculus@10229
   918
                free(name);
icculus@10229
   919
            }
icculus@10229
   920
icculus@10229
   921
            ALSA_snd_device_name_free_hint(hints);
icculus@10229
   922
icculus@10229
   923
            devices = seen;   /* now we have a known-good list of attached devices. */
icculus@10229
   924
icculus@10229
   925
            /* report anything still in unseen as removed. */
icculus@10229
   926
            for (dev = unseen; dev; dev = next) {
icculus@10229
   927
                /*printf("ALSA: removing %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/
icculus@10229
   928
                next = dev->next;
icculus@10229
   929
                SDL_RemoveAudioDevice(dev->iscapture, dev->name);
icculus@10229
   930
                SDL_free(dev->name);
icculus@10229
   931
                SDL_free(dev);
icculus@10229
   932
            }
icculus@10229
   933
        }
icculus@10229
   934
icculus@10229
   935
        /* On first run, tell ALSA_DetectDevices() that we have a complete device list so it can return. */
icculus@10229
   936
        if (first_run_semaphore) {
icculus@10229
   937
            SDL_SemPost(first_run_semaphore);
icculus@10229
   938
            first_run_semaphore = NULL;  /* let other thread clean it up. */
icculus@10229
   939
        }
icculus@10229
   940
icculus@10229
   941
        /* Block awhile before checking again, unless we're told to stop. */
icculus@10229
   942
        ticks = SDL_GetTicks() + 5000;
icculus@10230
   943
        while (!SDL_AtomicGet(&ALSA_hotplug_shutdown) && !SDL_TICKS_PASSED(SDL_GetTicks(), ticks)) {
icculus@10229
   944
            SDL_Delay(100);
icculus@10229
   945
        }
icculus@10142
   946
    }
icculus@10142
   947
icculus@10229
   948
    /* Shutting down! Clean up any data we've gathered. */
icculus@10229
   949
    for (dev = devices; dev; dev = next) {
icculus@10229
   950
        /*printf("ALSA: at shutdown, removing %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/
icculus@10229
   951
        next = dev->next;
icculus@10229
   952
        SDL_free(dev->name);
icculus@10229
   953
        SDL_free(dev);
icculus@10104
   954
    }
icculus@10104
   955
icculus@10229
   956
    return 0;
icculus@10104
   957
}
icculus@10104
   958
icculus@10104
   959
static void
icculus@10104
   960
ALSA_DetectDevices(void)
icculus@10104
   961
{
icculus@10229
   962
    /* Start the device detection thread here, wait for an initial iteration to complete. */
icculus@10229
   963
    SDL_sem *semaphore = SDL_CreateSemaphore(0);
icculus@10229
   964
    if (!semaphore) {
icculus@10104
   965
        return;  /* oh well. */
icculus@10104
   966
    }
icculus@10104
   967
icculus@10229
   968
    SDL_AtomicSet(&ALSA_hotplug_shutdown, 0);
icculus@10104
   969
icculus@10229
   970
    ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", semaphore);
icculus@10229
   971
    if (ALSA_hotplug_thread) {
icculus@10229
   972
        SDL_SemWait(semaphore);  /* wait for the first iteration to finish. */
icculus@10104
   973
    }
icculus@10104
   974
icculus@10229
   975
    SDL_DestroySemaphore(semaphore);
icculus@10104
   976
}
icculus@10104
   977
icculus@10104
   978
static void
icculus@10229
   979
ALSA_Deinitialize(void)
icculus@10104
   980
{
icculus@10229
   981
    if (ALSA_hotplug_thread != NULL) {
icculus@10229
   982
        SDL_AtomicSet(&ALSA_hotplug_shutdown, 1);
icculus@10229
   983
        SDL_WaitThread(ALSA_hotplug_thread, NULL);
icculus@10229
   984
        ALSA_hotplug_thread = NULL;
icculus@10229
   985
    }
icculus@10229
   986
icculus@10229
   987
    UnloadALSALibrary();
icculus@10104
   988
}
icculus@10104
   989
icculus@2049
   990
static int
slouken@2060
   991
ALSA_Init(SDL_AudioDriverImpl * impl)
icculus@2049
   992
{
icculus@2049
   993
    if (LoadALSALibrary() < 0) {
icculus@2049
   994
        return 0;
icculus@2049
   995
    }
icculus@2049
   996
icculus@2049
   997
    /* Set the function pointers */
icculus@10104
   998
    impl->DetectDevices = ALSA_DetectDevices;
icculus@2049
   999
    impl->OpenDevice = ALSA_OpenDevice;
icculus@2049
  1000
    impl->WaitDevice = ALSA_WaitDevice;
icculus@2049
  1001
    impl->GetDeviceBuf = ALSA_GetDeviceBuf;
icculus@2049
  1002
    impl->PlayDevice = ALSA_PlayDevice;
icculus@2049
  1003
    impl->CloseDevice = ALSA_CloseDevice;
icculus@2049
  1004
    impl->Deinitialize = ALSA_Deinitialize;
icculus@10243
  1005
    impl->CaptureFromDevice = ALSA_CaptureFromDevice;
icculus@10243
  1006
    impl->FlushCapture = ALSA_FlushCapture;
icculus@10243
  1007
icculus@10243
  1008
    impl->HasCaptureSupport = SDL_TRUE;
icculus@2049
  1009
icculus@3699
  1010
    return 1;   /* this audio target is available. */
icculus@2049
  1011
}
icculus@2049
  1012
icculus@2049
  1013
icculus@2049
  1014
AudioBootStrap ALSA_bootstrap = {
icculus@5594
  1015
    "alsa", "ALSA PCM audio", ALSA_Init, 0
icculus@2049
  1016
};
icculus@2049
  1017
slouken@6044
  1018
#endif /* SDL_AUDIO_DRIVER_ALSA */
slouken@6044
  1019
slouken@1895
  1020
/* vi: set ts=4 sw=4 expandtab: */