src/audio/alsa/SDL_alsa_audio.c
author Sam Lantinga
Sat, 06 Oct 2012 11:23:47 -0700
changeset 6565 1f3c0df426dc
parent 6138 4c64952a58fb
child 6885 700f1b25f77f
permissions -rw-r--r--
When using Xinerama, XVidMode always works on screen 0. Otherwise use the real X11 screen.
slouken@0
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@6138
     3
  Copyright (C) 1997-2012 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
*/
slouken@1402
    21
#include "SDL_config.h"
slouken@0
    22
slouken@6044
    23
#if SDL_AUDIO_DRIVER_ALSA
slouken@6044
    24
slouken@0
    25
/* Allow access to a raw mixing buffer */
slouken@0
    26
slouken@0
    27
#include <sys/types.h>
slouken@1895
    28
#include <signal.h>             /* For kill() */
icculus@2049
    29
#include <errno.h>
icculus@2049
    30
#include <string.h>
slouken@0
    31
slouken@1361
    32
#include "SDL_timer.h"
slouken@0
    33
#include "SDL_audio.h"
slouken@1361
    34
#include "../SDL_audiomem.h"
slouken@1361
    35
#include "../SDL_audio_c.h"
slouken@0
    36
#include "SDL_alsa_audio.h"
slouken@0
    37
icculus@6046
    38
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
icculus@3362
    39
#include "SDL_loadso.h"
icculus@3362
    40
#endif
slouken@865
    41
icculus@2049
    42
static int (*ALSA_snd_pcm_open)
slouken@2060
    43
  (snd_pcm_t **, const char *, snd_pcm_stream_t, int);
slouken@2060
    44
static int (*ALSA_snd_pcm_close) (snd_pcm_t * pcm);
icculus@2049
    45
static snd_pcm_sframes_t(*ALSA_snd_pcm_writei)
slouken@2060
    46
  (snd_pcm_t *, const void *, snd_pcm_uframes_t);
icculus@3627
    47
static int (*ALSA_snd_pcm_recover) (snd_pcm_t *, int, int);
slouken@2060
    48
static int (*ALSA_snd_pcm_prepare) (snd_pcm_t *);
slouken@2060
    49
static int (*ALSA_snd_pcm_drain) (snd_pcm_t *);
slouken@2060
    50
static const char *(*ALSA_snd_strerror) (int);
slouken@2060
    51
static size_t(*ALSA_snd_pcm_hw_params_sizeof) (void);
slouken@2060
    52
static size_t(*ALSA_snd_pcm_sw_params_sizeof) (void);
icculus@3627
    53
static void (*ALSA_snd_pcm_hw_params_copy)
icculus@3627
    54
  (snd_pcm_hw_params_t *, const snd_pcm_hw_params_t *);
slouken@2060
    55
static int (*ALSA_snd_pcm_hw_params_any) (snd_pcm_t *, snd_pcm_hw_params_t *);
icculus@2049
    56
static int (*ALSA_snd_pcm_hw_params_set_access)
slouken@2060
    57
  (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t);
icculus@2049
    58
static int (*ALSA_snd_pcm_hw_params_set_format)
slouken@2060
    59
  (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t);
icculus@2049
    60
static int (*ALSA_snd_pcm_hw_params_set_channels)
slouken@2060
    61
  (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int);
icculus@3627
    62
static int (*ALSA_snd_pcm_hw_params_get_channels)
icculus@3627
    63
  (const snd_pcm_hw_params_t *, unsigned int *);
icculus@3362
    64
static int (*ALSA_snd_pcm_hw_params_set_rate_near)
icculus@3362
    65
  (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
icculus@3362
    66
static int (*ALSA_snd_pcm_hw_params_set_period_size_near)
icculus@3362
    67
  (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
icculus@3627
    68
static int (*ALSA_snd_pcm_hw_params_get_period_size)
icculus@3627
    69
  (const snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
icculus@3362
    70
static int (*ALSA_snd_pcm_hw_params_set_periods_near)
icculus@3362
    71
  (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
icculus@3627
    72
static int (*ALSA_snd_pcm_hw_params_get_periods)
icculus@3627
    73
  (const snd_pcm_hw_params_t *, unsigned int *, int *);
icculus@3627
    74
static int (*ALSA_snd_pcm_hw_params_set_buffer_size_near)
icculus@3627
    75
  (snd_pcm_t *pcm, snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
icculus@3627
    76
static int (*ALSA_snd_pcm_hw_params_get_buffer_size)
icculus@3627
    77
  (const snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
slouken@2060
    78
static int (*ALSA_snd_pcm_hw_params) (snd_pcm_t *, snd_pcm_hw_params_t *);
slouken@2060
    79
static int (*ALSA_snd_pcm_sw_params_current) (snd_pcm_t *,
slouken@2060
    80
                                              snd_pcm_sw_params_t *);
icculus@2049
    81
static int (*ALSA_snd_pcm_sw_params_set_start_threshold)
slouken@2060
    82
  (snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
slouken@2060
    83
static int (*ALSA_snd_pcm_sw_params) (snd_pcm_t *, snd_pcm_sw_params_t *);
slouken@2060
    84
static int (*ALSA_snd_pcm_nonblock) (snd_pcm_t *, int);
icculus@3627
    85
static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int);
icculus@5622
    86
static int (*ALSA_snd_pcm_sw_params_set_avail_min)
icculus@5622
    87
  (snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
slouken@0
    88
icculus@6046
    89
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
slouken@5315
    90
#define snd_pcm_hw_params_sizeof ALSA_snd_pcm_hw_params_sizeof
slouken@5315
    91
#define snd_pcm_sw_params_sizeof ALSA_snd_pcm_sw_params_sizeof
slouken@865
    92
slouken@1361
    93
static const char *alsa_library = SDL_AUDIO_DRIVER_ALSA_DYNAMIC;
slouken@865
    94
static void *alsa_handle = NULL;
icculus@2049
    95
icculus@2049
    96
static int
icculus@2049
    97
load_alsa_sym(const char *fn, void **addr)
icculus@2049
    98
{
icculus@3362
    99
    *addr = SDL_LoadFunction(alsa_handle, fn);
icculus@3362
   100
    if (*addr == NULL) {
icculus@3362
   101
        /* Don't call SDL_SetError(): SDL_LoadFunction already did. */
icculus@3362
   102
        return 0;
icculus@2049
   103
    }
icculus@2049
   104
icculus@2049
   105
    return 1;
icculus@2049
   106
}
slouken@865
   107
icculus@1161
   108
/* cast funcs to char* first, to please GCC's strict aliasing rules. */
icculus@2049
   109
#define SDL_ALSA_SYM(x) \
icculus@2049
   110
    if (!load_alsa_sym(#x, (void **) (char *) &ALSA_##x)) return -1
icculus@2049
   111
#else
icculus@2049
   112
#define SDL_ALSA_SYM(x) ALSA_##x = x
icculus@2049
   113
#endif
icculus@2049
   114
slouken@2060
   115
static int
slouken@2060
   116
load_alsa_syms(void)
slouken@1895
   117
{
icculus@2049
   118
    SDL_ALSA_SYM(snd_pcm_open);
icculus@2049
   119
    SDL_ALSA_SYM(snd_pcm_close);
icculus@2049
   120
    SDL_ALSA_SYM(snd_pcm_writei);
icculus@3627
   121
    SDL_ALSA_SYM(snd_pcm_recover);
icculus@2049
   122
    SDL_ALSA_SYM(snd_pcm_prepare);
icculus@2049
   123
    SDL_ALSA_SYM(snd_pcm_drain);
icculus@2049
   124
    SDL_ALSA_SYM(snd_strerror);
icculus@2049
   125
    SDL_ALSA_SYM(snd_pcm_hw_params_sizeof);
icculus@2049
   126
    SDL_ALSA_SYM(snd_pcm_sw_params_sizeof);
icculus@3627
   127
    SDL_ALSA_SYM(snd_pcm_hw_params_copy);
icculus@2049
   128
    SDL_ALSA_SYM(snd_pcm_hw_params_any);
icculus@2049
   129
    SDL_ALSA_SYM(snd_pcm_hw_params_set_access);
icculus@2049
   130
    SDL_ALSA_SYM(snd_pcm_hw_params_set_format);
icculus@2049
   131
    SDL_ALSA_SYM(snd_pcm_hw_params_set_channels);
icculus@2049
   132
    SDL_ALSA_SYM(snd_pcm_hw_params_get_channels);
icculus@2049
   133
    SDL_ALSA_SYM(snd_pcm_hw_params_set_rate_near);
icculus@2049
   134
    SDL_ALSA_SYM(snd_pcm_hw_params_set_period_size_near);
icculus@2049
   135
    SDL_ALSA_SYM(snd_pcm_hw_params_get_period_size);
icculus@2049
   136
    SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_near);
icculus@2049
   137
    SDL_ALSA_SYM(snd_pcm_hw_params_get_periods);
icculus@3627
   138
    SDL_ALSA_SYM(snd_pcm_hw_params_set_buffer_size_near);
icculus@3627
   139
    SDL_ALSA_SYM(snd_pcm_hw_params_get_buffer_size);
icculus@2049
   140
    SDL_ALSA_SYM(snd_pcm_hw_params);
icculus@2049
   141
    SDL_ALSA_SYM(snd_pcm_sw_params_current);
icculus@2049
   142
    SDL_ALSA_SYM(snd_pcm_sw_params_set_start_threshold);
icculus@2049
   143
    SDL_ALSA_SYM(snd_pcm_sw_params);
icculus@2049
   144
    SDL_ALSA_SYM(snd_pcm_nonblock);
icculus@3627
   145
    SDL_ALSA_SYM(snd_pcm_wait);
icculus@5622
   146
    SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min);
icculus@2049
   147
    return 0;
icculus@2049
   148
}
slouken@2060
   149
icculus@2049
   150
#undef SDL_ALSA_SYM
icculus@2049
   151
icculus@6046
   152
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
slouken@865
   153
slouken@1895
   154
static void
slouken@1895
   155
UnloadALSALibrary(void)
slouken@1895
   156
{
icculus@2049
   157
    if (alsa_handle != NULL) {
icculus@3362
   158
		SDL_UnloadObject(alsa_handle);
slouken@1895
   159
        alsa_handle = NULL;
slouken@1895
   160
    }
slouken@865
   161
}
slouken@865
   162
slouken@1895
   163
static int
slouken@1895
   164
LoadALSALibrary(void)
slouken@1895
   165
{
icculus@2049
   166
    int retval = 0;
icculus@2049
   167
    if (alsa_handle == NULL) {
icculus@3362
   168
        alsa_handle = SDL_LoadObject(alsa_library);
icculus@2049
   169
        if (alsa_handle == NULL) {
icculus@2049
   170
            retval = -1;
icculus@3362
   171
            /* Don't call SDL_SetError(): SDL_LoadObject already did. */
icculus@2049
   172
        } else {
icculus@2049
   173
            retval = load_alsa_syms();
icculus@2049
   174
            if (retval < 0) {
slouken@1895
   175
                UnloadALSALibrary();
slouken@1895
   176
            }
slouken@1895
   177
        }
slouken@1895
   178
    }
slouken@1895
   179
    return retval;
slouken@865
   180
}
slouken@865
   181
slouken@865
   182
#else
slouken@865
   183
slouken@1895
   184
static void
slouken@1895
   185
UnloadALSALibrary(void)
slouken@1895
   186
{
slouken@865
   187
}
slouken@865
   188
slouken@1895
   189
static int
slouken@1895
   190
LoadALSALibrary(void)
slouken@1895
   191
{
icculus@2049
   192
    load_alsa_syms();
slouken@1895
   193
    return 0;
slouken@865
   194
}
slouken@865
   195
slouken@1361
   196
#endif /* SDL_AUDIO_DRIVER_ALSA_DYNAMIC */
slouken@865
   197
slouken@1895
   198
static const char *
slouken@1895
   199
get_audio_device(int channels)
slouken@354
   200
{
slouken@1895
   201
    const char *device;
slouken@1895
   202
slouken@1895
   203
    device = SDL_getenv("AUDIODEV");    /* Is there a standard variable name? */
slouken@1895
   204
    if (device == NULL) {
icculus@3627
   205
        switch (channels) {
icculus@3627
   206
        case 6:
icculus@3627
   207
            device = "plug:surround51";
icculus@3627
   208
            break;
icculus@3627
   209
        case 4:
icculus@3627
   210
            device = "plug:surround40";
icculus@3627
   211
            break;
icculus@3627
   212
        default:
icculus@3627
   213
            device = "default";
icculus@3627
   214
            break;
icculus@3627
   215
        }
slouken@1895
   216
    }
slouken@1895
   217
    return device;
slouken@0
   218
}
slouken@0
   219
slouken@0
   220
slouken@0
   221
/* This function waits until it is possible to write a full sound buffer */
slouken@1895
   222
static void
icculus@2049
   223
ALSA_WaitDevice(_THIS)
slouken@0
   224
{
icculus@3627
   225
    /* We're in blocking mode, so there's nothing to do here */
slouken@0
   226
}
slouken@0
   227
icculus@1878
   228
icculus@2049
   229
/* !!! FIXME: is there a channel swizzler in alsalib instead? */
icculus@1878
   230
/*
icculus@1878
   231
 * http://bugzilla.libsdl.org/show_bug.cgi?id=110
icculus@1878
   232
 * "For Linux ALSA, this is FL-FR-RL-RR-C-LFE
icculus@1878
   233
 *  and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-RL-RR"
icculus@1878
   234
 */
icculus@1878
   235
#define SWIZ6(T) \
icculus@2049
   236
    T *ptr = (T *) this->hidden->mixbuf; \
icculus@1878
   237
    Uint32 i; \
icculus@3704
   238
    for (i = 0; i < this->spec.samples; i++, ptr += 6) { \
icculus@1878
   239
        T tmp; \
icculus@1878
   240
        tmp = ptr[2]; ptr[2] = ptr[4]; ptr[4] = tmp; \
icculus@1878
   241
        tmp = ptr[3]; ptr[3] = ptr[5]; ptr[5] = tmp; \
icculus@1878
   242
    }
icculus@1878
   243
slouken@1895
   244
static __inline__ void
slouken@1895
   245
swizzle_alsa_channels_6_64bit(_THIS)
slouken@1895
   246
{
slouken@1895
   247
    SWIZ6(Uint64);
slouken@1895
   248
}
slouken@2735
   249
slouken@1895
   250
static __inline__ void
slouken@1895
   251
swizzle_alsa_channels_6_32bit(_THIS)
slouken@1895
   252
{
slouken@1895
   253
    SWIZ6(Uint32);
slouken@1895
   254
}
slouken@2735
   255
slouken@1895
   256
static __inline__ void
slouken@1895
   257
swizzle_alsa_channels_6_16bit(_THIS)
slouken@1895
   258
{
slouken@1895
   259
    SWIZ6(Uint16);
slouken@1895
   260
}
slouken@2735
   261
slouken@1895
   262
static __inline__ void
slouken@1895
   263
swizzle_alsa_channels_6_8bit(_THIS)
slouken@1895
   264
{
slouken@1895
   265
    SWIZ6(Uint8);
slouken@1895
   266
}
icculus@1878
   267
icculus@1878
   268
#undef SWIZ6
icculus@1878
   269
icculus@1878
   270
icculus@1878
   271
/*
icculus@2049
   272
 * Called right before feeding this->hidden->mixbuf to the hardware. Swizzle
icculus@2049
   273
 *  channels from Windows/Mac order to the format alsalib will want.
icculus@1878
   274
 */
slouken@1895
   275
static __inline__ void
slouken@1895
   276
swizzle_alsa_channels(_THIS)
icculus@1878
   277
{
icculus@1878
   278
    if (this->spec.channels == 6) {
slouken@1895
   279
        const Uint16 fmtsize = (this->spec.format & 0xFF);      /* bits/channel. */
icculus@1878
   280
        if (fmtsize == 16)
icculus@1878
   281
            swizzle_alsa_channels_6_16bit(this);
icculus@1878
   282
        else if (fmtsize == 8)
icculus@1878
   283
            swizzle_alsa_channels_6_8bit(this);
icculus@1878
   284
        else if (fmtsize == 32)
icculus@1878
   285
            swizzle_alsa_channels_6_32bit(this);
icculus@1878
   286
        else if (fmtsize == 64)
icculus@1878
   287
            swizzle_alsa_channels_6_64bit(this);
icculus@1878
   288
    }
icculus@1878
   289
icculus@1878
   290
    /* !!! FIXME: update this for 7.1 if needed, later. */
icculus@1878
   291
}
icculus@1878
   292
icculus@1878
   293
slouken@1895
   294
static void
icculus@2049
   295
ALSA_PlayDevice(_THIS)
slouken@0
   296
{
slouken@1895
   297
    int status;
icculus@3627
   298
    const Uint8 *sample_buf = (const Uint8 *) this->hidden->mixbuf;
icculus@3627
   299
    const int frame_size = (((int) (this->spec.format & 0xFF)) / 8) *
icculus@3627
   300
                                this->spec.channels;
icculus@3627
   301
    snd_pcm_uframes_t frames_left = ((snd_pcm_uframes_t) this->spec.samples);
slouken@765
   302
slouken@1895
   303
    swizzle_alsa_channels(this);
icculus@1878
   304
icculus@3627
   305
    while ( frames_left > 0 && this->enabled ) {
icculus@3627
   306
        /* !!! FIXME: This works, but needs more testing before going live */
icculus@3627
   307
        /*ALSA_snd_pcm_wait(this->hidden->pcm_handle, -1);*/
icculus@2049
   308
        status = ALSA_snd_pcm_writei(this->hidden->pcm_handle,
icculus@3627
   309
                                     sample_buf, frames_left);
icculus@2049
   310
slouken@1895
   311
        if (status < 0) {
slouken@1895
   312
            if (status == -EAGAIN) {
icculus@3627
   313
                /* Apparently snd_pcm_recover() doesn't handle this case -
icculus@3627
   314
                   does it assume snd_pcm_wait() above? */
slouken@1895
   315
                SDL_Delay(1);
slouken@1895
   316
                continue;
slouken@1895
   317
            }
icculus@3627
   318
            status = ALSA_snd_pcm_recover(this->hidden->pcm_handle, status, 0);
slouken@1895
   319
            if (status < 0) {
slouken@1895
   320
                /* Hmm, not much we can do - abort */
icculus@3627
   321
                fprintf(stderr, "ALSA write failed (unrecoverable): %s\n",
icculus@3627
   322
                        ALSA_snd_strerror(status));
slouken@1895
   323
                this->enabled = 0;
slouken@1895
   324
                return;
slouken@1895
   325
            }
slouken@1895
   326
            continue;
slouken@1895
   327
        }
icculus@3627
   328
        sample_buf += status * frame_size;
icculus@3627
   329
        frames_left -= status;
slouken@1895
   330
    }
slouken@0
   331
}
slouken@0
   332
slouken@1895
   333
static Uint8 *
icculus@2049
   334
ALSA_GetDeviceBuf(_THIS)
slouken@0
   335
{
icculus@2049
   336
    return (this->hidden->mixbuf);
slouken@0
   337
}
slouken@0
   338
slouken@1895
   339
static void
icculus@2049
   340
ALSA_CloseDevice(_THIS)
slouken@0
   341
{
icculus@2049
   342
    if (this->hidden != NULL) {
icculus@2049
   343
        if (this->hidden->mixbuf != NULL) {
icculus@2049
   344
            SDL_FreeAudioMem(this->hidden->mixbuf);
icculus@2049
   345
            this->hidden->mixbuf = NULL;
icculus@2049
   346
        }
icculus@2049
   347
        if (this->hidden->pcm_handle) {
icculus@2049
   348
            ALSA_snd_pcm_drain(this->hidden->pcm_handle);
icculus@2049
   349
            ALSA_snd_pcm_close(this->hidden->pcm_handle);
icculus@2049
   350
            this->hidden->pcm_handle = NULL;
icculus@2049
   351
        }
icculus@2049
   352
        SDL_free(this->hidden);
icculus@2049
   353
        this->hidden = NULL;
slouken@1895
   354
    }
slouken@0
   355
}
slouken@0
   356
slouken@1895
   357
static int
icculus@3627
   358
ALSA_finalize_hardware(_THIS, snd_pcm_hw_params_t *hwparams, int override)
icculus@3627
   359
{
icculus@3627
   360
    int status;
icculus@3627
   361
    snd_pcm_uframes_t bufsize;
icculus@3627
   362
icculus@3627
   363
    /* "set" the hardware with the desired parameters */
icculus@3627
   364
    status = ALSA_snd_pcm_hw_params(this->hidden->pcm_handle, hwparams);
icculus@3627
   365
    if ( status < 0 ) {
icculus@3627
   366
        return(-1);
icculus@3627
   367
    }
icculus@3627
   368
icculus@3627
   369
    /* Get samples for the actual buffer size */
icculus@3627
   370
    status = ALSA_snd_pcm_hw_params_get_buffer_size(hwparams, &bufsize);
icculus@3627
   371
    if ( status < 0 ) {
icculus@3627
   372
        return(-1);
icculus@3627
   373
    }
icculus@3627
   374
    if ( !override && bufsize != this->spec.samples * 2 ) {
icculus@3627
   375
        return(-1);
icculus@3627
   376
    }
icculus@3627
   377
icculus@3627
   378
    /* !!! FIXME: Is this safe to do? */
icculus@3627
   379
    this->spec.samples = bufsize / 2;
icculus@3627
   380
icculus@3627
   381
    /* This is useful for debugging */
icculus@3627
   382
    if ( SDL_getenv("SDL_AUDIO_ALSA_DEBUG") ) {
icculus@3627
   383
        snd_pcm_uframes_t persize = 0;
icculus@3627
   384
        unsigned int periods = 0;
icculus@3627
   385
icculus@3627
   386
        ALSA_snd_pcm_hw_params_get_period_size(hwparams, &persize, NULL);
icculus@3627
   387
        ALSA_snd_pcm_hw_params_get_periods(hwparams, &periods, NULL);
icculus@3627
   388
icculus@3627
   389
        fprintf(stderr,
icculus@3627
   390
            "ALSA: period size = %ld, periods = %u, buffer size = %lu\n",
icculus@3627
   391
            persize, periods, bufsize);
icculus@3627
   392
    }
icculus@3627
   393
icculus@3627
   394
    return(0);
icculus@3627
   395
}
icculus@3627
   396
icculus@3627
   397
static int
icculus@3627
   398
ALSA_set_period_size(_THIS, snd_pcm_hw_params_t *params, int override)
icculus@3627
   399
{
icculus@3627
   400
    const char *env;
icculus@3627
   401
    int status;
icculus@3627
   402
    snd_pcm_hw_params_t *hwparams;
icculus@3627
   403
    snd_pcm_uframes_t frames;
icculus@3627
   404
    unsigned int periods;
icculus@3627
   405
icculus@3627
   406
    /* Copy the hardware parameters for this setup */
icculus@3627
   407
    snd_pcm_hw_params_alloca(&hwparams);
icculus@3627
   408
    ALSA_snd_pcm_hw_params_copy(hwparams, params);
icculus@3627
   409
icculus@3627
   410
    if ( !override ) {
icculus@3627
   411
        env = SDL_getenv("SDL_AUDIO_ALSA_SET_PERIOD_SIZE");
icculus@3627
   412
        if ( env ) {
icculus@3627
   413
            override = SDL_atoi(env);
icculus@3627
   414
            if ( override == 0 ) {
icculus@3627
   415
                return(-1);
icculus@3627
   416
            }
icculus@3627
   417
        }
icculus@3627
   418
    }
icculus@3627
   419
icculus@3627
   420
    frames = this->spec.samples;
icculus@3627
   421
    status = ALSA_snd_pcm_hw_params_set_period_size_near(
icculus@3627
   422
                this->hidden->pcm_handle, hwparams, &frames, NULL);
icculus@3627
   423
    if ( status < 0 ) {
icculus@3627
   424
        return(-1);
icculus@3627
   425
    }
icculus@3627
   426
icculus@3627
   427
    periods = 2;
icculus@3627
   428
    status = ALSA_snd_pcm_hw_params_set_periods_near(
icculus@3627
   429
                this->hidden->pcm_handle, hwparams, &periods, NULL);
icculus@3627
   430
    if ( status < 0 ) {
icculus@3627
   431
        return(-1);
icculus@3627
   432
    }
icculus@3627
   433
icculus@3627
   434
    return ALSA_finalize_hardware(this, hwparams, override);
icculus@3627
   435
}
icculus@3627
   436
icculus@3627
   437
static int
icculus@3627
   438
ALSA_set_buffer_size(_THIS, snd_pcm_hw_params_t *params, int override)
icculus@3627
   439
{
icculus@3627
   440
    const char *env;
icculus@3627
   441
    int status;
icculus@3627
   442
    snd_pcm_hw_params_t *hwparams;
icculus@3627
   443
    snd_pcm_uframes_t frames;
icculus@3627
   444
icculus@3627
   445
    /* Copy the hardware parameters for this setup */
icculus@3627
   446
    snd_pcm_hw_params_alloca(&hwparams);
icculus@3627
   447
    ALSA_snd_pcm_hw_params_copy(hwparams, params);
icculus@3627
   448
icculus@3627
   449
    if ( !override ) {
icculus@3627
   450
        env = SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE");
icculus@3627
   451
        if ( env ) {
icculus@3627
   452
            override = SDL_atoi(env);
icculus@3627
   453
            if ( override == 0 ) {
icculus@3627
   454
                return(-1);
icculus@3627
   455
            }
icculus@3627
   456
        }
icculus@3627
   457
    }
icculus@3627
   458
icculus@3627
   459
    frames = this->spec.samples * 2;
icculus@3627
   460
    status = ALSA_snd_pcm_hw_params_set_buffer_size_near(
icculus@3627
   461
                    this->hidden->pcm_handle, hwparams, &frames);
icculus@3627
   462
    if ( status < 0 ) {
icculus@3627
   463
        return(-1);
icculus@3627
   464
    }
icculus@3627
   465
icculus@3627
   466
    return ALSA_finalize_hardware(this, hwparams, override);
icculus@3627
   467
}
icculus@3627
   468
icculus@3627
   469
static int
icculus@2049
   470
ALSA_OpenDevice(_THIS, const char *devname, int iscapture)
slouken@0
   471
{
icculus@2049
   472
    int status = 0;
icculus@2049
   473
    snd_pcm_t *pcm_handle = NULL;
icculus@2049
   474
    snd_pcm_hw_params_t *hwparams = NULL;
icculus@2049
   475
    snd_pcm_sw_params_t *swparams = NULL;
icculus@2049
   476
    snd_pcm_format_t format = 0;
icculus@2049
   477
    SDL_AudioFormat test_format = 0;
icculus@3362
   478
    unsigned int rate = 0;
icculus@3362
   479
    unsigned int channels = 0;
icculus@2049
   480
icculus@2049
   481
    /* Initialize all variables that we clean on shutdown */
icculus@2049
   482
    this->hidden = (struct SDL_PrivateAudioData *)
slouken@2060
   483
        SDL_malloc((sizeof *this->hidden));
icculus@2049
   484
    if (this->hidden == NULL) {
icculus@2049
   485
        SDL_OutOfMemory();
icculus@2049
   486
        return 0;
icculus@2049
   487
    }
icculus@2049
   488
    SDL_memset(this->hidden, 0, (sizeof *this->hidden));
slouken@0
   489
slouken@1895
   490
    /* Open the audio device */
slouken@1895
   491
    /* Name of device should depend on # channels in spec */
icculus@2049
   492
    status = ALSA_snd_pcm_open(&pcm_handle,
icculus@2049
   493
                               get_audio_device(this->spec.channels),
icculus@2049
   494
                               SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
slouken@942
   495
slouken@1895
   496
    if (status < 0) {
icculus@2049
   497
        ALSA_CloseDevice(this);
icculus@2049
   498
        SDL_SetError("ALSA: Couldn't open audio device: %s",
icculus@2049
   499
                     ALSA_snd_strerror(status));
icculus@2049
   500
        return 0;
slouken@1895
   501
    }
slouken@354
   502
icculus@2049
   503
    this->hidden->pcm_handle = pcm_handle;
icculus@2049
   504
slouken@1895
   505
    /* Figure out what the hardware is capable of */
slouken@1895
   506
    snd_pcm_hw_params_alloca(&hwparams);
icculus@2049
   507
    status = ALSA_snd_pcm_hw_params_any(pcm_handle, hwparams);
slouken@1895
   508
    if (status < 0) {
icculus@2049
   509
        ALSA_CloseDevice(this);
icculus@2049
   510
        SDL_SetError("ALSA: Couldn't get hardware config: %s",
icculus@2049
   511
                     ALSA_snd_strerror(status));
icculus@2049
   512
        return 0;
slouken@1895
   513
    }
slouken@354
   514
slouken@1895
   515
    /* SDL only uses interleaved sample output */
icculus@2049
   516
    status = ALSA_snd_pcm_hw_params_set_access(pcm_handle, hwparams,
icculus@2049
   517
                                               SND_PCM_ACCESS_RW_INTERLEAVED);
slouken@1895
   518
    if (status < 0) {
icculus@2049
   519
        ALSA_CloseDevice(this);
icculus@2049
   520
        SDL_SetError("ALSA: Couldn't set interleaved access: %s",
icculus@2049
   521
                     ALSA_snd_strerror(status));
icculus@2049
   522
        return 0;
slouken@1895
   523
    }
slouken@0
   524
slouken@1895
   525
    /* Try for a closest match on audio format */
slouken@1895
   526
    status = -1;
icculus@2049
   527
    for (test_format = SDL_FirstAudioFormat(this->spec.format);
slouken@1895
   528
         test_format && (status < 0);) {
slouken@2060
   529
        status = 0;             /* if we can't support a format, it'll become -1. */
slouken@1895
   530
        switch (test_format) {
slouken@1895
   531
        case AUDIO_U8:
slouken@1895
   532
            format = SND_PCM_FORMAT_U8;
slouken@1895
   533
            break;
slouken@1895
   534
        case AUDIO_S8:
slouken@1895
   535
            format = SND_PCM_FORMAT_S8;
slouken@1895
   536
            break;
slouken@1895
   537
        case AUDIO_S16LSB:
slouken@1895
   538
            format = SND_PCM_FORMAT_S16_LE;
slouken@1895
   539
            break;
slouken@1895
   540
        case AUDIO_S16MSB:
slouken@1895
   541
            format = SND_PCM_FORMAT_S16_BE;
slouken@1895
   542
            break;
slouken@1895
   543
        case AUDIO_U16LSB:
slouken@1895
   544
            format = SND_PCM_FORMAT_U16_LE;
slouken@1895
   545
            break;
slouken@1895
   546
        case AUDIO_U16MSB:
slouken@1895
   547
            format = SND_PCM_FORMAT_U16_BE;
slouken@1895
   548
            break;
icculus@1995
   549
        case AUDIO_S32LSB:
icculus@2010
   550
            format = SND_PCM_FORMAT_S32_LE;
icculus@1995
   551
            break;
icculus@1995
   552
        case AUDIO_S32MSB:
icculus@2010
   553
            format = SND_PCM_FORMAT_S32_BE;
icculus@1995
   554
            break;
icculus@1995
   555
        case AUDIO_F32LSB:
icculus@1995
   556
            format = SND_PCM_FORMAT_FLOAT_LE;
icculus@1995
   557
            break;
icculus@1995
   558
        case AUDIO_F32MSB:
icculus@1995
   559
            format = SND_PCM_FORMAT_FLOAT_BE;
icculus@1995
   560
            break;
slouken@1895
   561
        default:
icculus@2009
   562
            status = -1;
slouken@1895
   563
            break;
slouken@1895
   564
        }
icculus@2009
   565
        if (status >= 0) {
icculus@2049
   566
            status = ALSA_snd_pcm_hw_params_set_format(pcm_handle,
icculus@2049
   567
                                                       hwparams, format);
slouken@1895
   568
        }
slouken@1895
   569
        if (status < 0) {
slouken@1895
   570
            test_format = SDL_NextAudioFormat();
slouken@1895
   571
        }
slouken@1895
   572
    }
slouken@1895
   573
    if (status < 0) {
icculus@2049
   574
        ALSA_CloseDevice(this);
icculus@2049
   575
        SDL_SetError("ALSA: Couldn't find any hardware audio formats");
icculus@2049
   576
        return 0;
slouken@1895
   577
    }
icculus@2049
   578
    this->spec.format = test_format;
slouken@0
   579
slouken@1895
   580
    /* Set the number of channels */
icculus@2049
   581
    status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams,
icculus@2049
   582
                                                 this->spec.channels);
icculus@3362
   583
    channels = this->spec.channels;
slouken@1895
   584
    if (status < 0) {
icculus@3362
   585
        status = ALSA_snd_pcm_hw_params_get_channels(hwparams, &channels);
icculus@3362
   586
        if (status < 0) {
icculus@2049
   587
            ALSA_CloseDevice(this);
icculus@2049
   588
            SDL_SetError("ALSA: Couldn't set audio channels");
icculus@2049
   589
            return 0;
slouken@1895
   590
        }
icculus@3362
   591
        this->spec.channels = channels;
slouken@1895
   592
    }
slouken@0
   593
slouken@1895
   594
    /* Set the audio rate */
icculus@3362
   595
    rate = this->spec.freq;
icculus@2049
   596
    status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams,
icculus@3362
   597
                                                  &rate, NULL);
slouken@1895
   598
    if (status < 0) {
icculus@2049
   599
        ALSA_CloseDevice(this);
icculus@2049
   600
        SDL_SetError("ALSA: Couldn't set audio frequency: %s",
icculus@2049
   601
                     ALSA_snd_strerror(status));
icculus@2049
   602
        return 0;
slouken@1895
   603
    }
icculus@3362
   604
    this->spec.freq = rate;
slouken@0
   605
slouken@1895
   606
    /* Set the buffer size, in samples */
icculus@3627
   607
    if ( ALSA_set_period_size(this, hwparams, 0) < 0 &&
icculus@3627
   608
         ALSA_set_buffer_size(this, hwparams, 0) < 0 ) {
icculus@3627
   609
        /* Failed to set desired buffer size, do the best you can... */
icculus@3627
   610
        if ( ALSA_set_period_size(this, hwparams, 1) < 0 ) {
icculus@3627
   611
            ALSA_CloseDevice(this);
icculus@3627
   612
            SDL_SetError("Couldn't set hardware audio parameters: %s", ALSA_snd_strerror(status));
icculus@3627
   613
            return(-1);
icculus@3627
   614
        }
icculus@3362
   615
    }
slouken@1895
   616
    /* Set the software parameters */
slouken@1895
   617
    snd_pcm_sw_params_alloca(&swparams);
icculus@2049
   618
    status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams);
slouken@1895
   619
    if (status < 0) {
icculus@2049
   620
        ALSA_CloseDevice(this);
icculus@2049
   621
        SDL_SetError("ALSA: Couldn't get software config: %s",
icculus@2049
   622
                     ALSA_snd_strerror(status));
icculus@2049
   623
        return 0;
slouken@1895
   624
    }
icculus@5622
   625
    status = ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, this->spec.samples);
icculus@5622
   626
    if (status < 0) {
icculus@5622
   627
        ALSA_CloseDevice(this);
icculus@5622
   628
        SDL_SetError("Couldn't set minimum available samples: %s",
icculus@5622
   629
                     ALSA_snd_strerror(status));
icculus@5622
   630
        return 0;
icculus@5622
   631
    }
slouken@2060
   632
    status =
icculus@3627
   633
        ALSA_snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, 1);
icculus@2049
   634
    if (status < 0) {
icculus@2049
   635
        ALSA_CloseDevice(this);
icculus@2049
   636
        SDL_SetError("ALSA: Couldn't set start threshold: %s",
icculus@2049
   637
                     ALSA_snd_strerror(status));
icculus@2049
   638
        return 0;
icculus@2049
   639
    }
icculus@2049
   640
    status = ALSA_snd_pcm_sw_params(pcm_handle, swparams);
slouken@1895
   641
    if (status < 0) {
icculus@2049
   642
        ALSA_CloseDevice(this);
slouken@1895
   643
        SDL_SetError("Couldn't set software audio parameters: %s",
icculus@2049
   644
                     ALSA_snd_strerror(status));
icculus@2049
   645
        return 0;
slouken@1895
   646
    }
slouken@0
   647
slouken@1895
   648
    /* Calculate the final parameters for this audio specification */
icculus@2049
   649
    SDL_CalculateAudioSpec(&this->spec);
slouken@1895
   650
slouken@1895
   651
    /* Allocate mixing buffer */
icculus@2049
   652
    this->hidden->mixlen = this->spec.size;
icculus@2049
   653
    this->hidden->mixbuf = (Uint8 *) SDL_AllocAudioMem(this->hidden->mixlen);
icculus@2049
   654
    if (this->hidden->mixbuf == NULL) {
icculus@2049
   655
        ALSA_CloseDevice(this);
icculus@2049
   656
        SDL_OutOfMemory();
icculus@2049
   657
        return 0;
slouken@1895
   658
    }
icculus@2049
   659
    SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size);
slouken@0
   660
slouken@1895
   661
    /* Switch to blocking mode for playback */
icculus@2049
   662
    ALSA_snd_pcm_nonblock(pcm_handle, 0);
slouken@0
   663
slouken@1895
   664
    /* We're ready to rock and roll. :-) */
icculus@2049
   665
    return 1;
icculus@2049
   666
}
icculus@2049
   667
icculus@2049
   668
static void
icculus@2049
   669
ALSA_Deinitialize(void)
icculus@2049
   670
{
icculus@2049
   671
    UnloadALSALibrary();
slouken@1895
   672
}
slouken@0
   673
icculus@2049
   674
static int
slouken@2060
   675
ALSA_Init(SDL_AudioDriverImpl * impl)
icculus@2049
   676
{
icculus@2049
   677
    if (LoadALSALibrary() < 0) {
icculus@2049
   678
        return 0;
icculus@2049
   679
    }
icculus@2049
   680
icculus@2049
   681
    /* Set the function pointers */
icculus@2049
   682
    impl->OpenDevice = ALSA_OpenDevice;
icculus@2049
   683
    impl->WaitDevice = ALSA_WaitDevice;
icculus@2049
   684
    impl->GetDeviceBuf = ALSA_GetDeviceBuf;
icculus@2049
   685
    impl->PlayDevice = ALSA_PlayDevice;
icculus@2049
   686
    impl->CloseDevice = ALSA_CloseDevice;
icculus@2049
   687
    impl->Deinitialize = ALSA_Deinitialize;
slouken@2060
   688
    impl->OnlyHasDefaultOutputDevice = 1;       /* !!! FIXME: Add device enum! */
icculus@2049
   689
icculus@3699
   690
    return 1;   /* this audio target is available. */
icculus@2049
   691
}
icculus@2049
   692
icculus@2049
   693
icculus@2049
   694
AudioBootStrap ALSA_bootstrap = {
icculus@5594
   695
    "alsa", "ALSA PCM audio", ALSA_Init, 0
icculus@2049
   696
};
icculus@2049
   697
slouken@6044
   698
#endif /* SDL_AUDIO_DRIVER_ALSA */
slouken@6044
   699
slouken@1895
   700
/* vi: set ts=4 sw=4 expandtab: */