src/audio/alsa/SDL_alsa_audio.c
author Ryan C. Gordon <icculus@icculus.org>
Mon, 21 May 2018 12:00:21 -0400
changeset 11994 8e094f91ddab
parent 11811 5d94cb6b24d3
child 12031 b6c4568cc10b
permissions -rw-r--r--
thread: fixed compiler warnings on non-Linux systems that use pthread.

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