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