alsa.c 27 KB
Newer Older
1 2 3
/*****************************************************************************
 * alsa.c : alsa plugin for vlc
 *****************************************************************************
4
 * Copyright (C) 2000-2001 the VideoLAN team
5
 * $Id$
6 7 8 9
 *
 * Authors: Henri Fallon <henri@videolan.org> - Original Author
 *          Jeffrey Baker <jwbaker@acm.org> - Port to ALSA 1.0 API
 *          John Paul Lorenti <jpl31@columbia.edu> - Device selection
10
 *          Arnaud de Bossoreille de Ribou <bozo@via.ecp.fr> - S/PDIF and aout3
11 12 13 14 15
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
16
 *
17 18 19 20 21 22 23
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
dionoea's avatar
dionoea committed
24
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 26 27 28 29
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
30 31 32 33
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

34 35
#include <assert.h>

36
#include <vlc_common.h>
37
#include <vlc_plugin.h>
Rémi Denis-Courmont's avatar
More  
Rémi Denis-Courmont committed
38

39
#include <errno.h>                                                 /* ENOMEM */
40
#include <vlc_dialog.h>
41

zorglub's avatar
zorglub committed
42
#include <vlc_aout.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
43
#include <vlc_cpu.h>
44

45
/* ALSA part
46
   Note: we use the new API which is available since 0.9.0beta10a. */
47 48
#define ALSA_PCM_NEW_HW_PARAMS_API
#define ALSA_PCM_NEW_SW_PARAMS_API
49
#include <alsa/asoundlib.h>
50
#include <alsa/version.h>
51

52 53
/*#define ALSA_DEBUG*/

54 55 56 57 58 59 60 61 62
/*****************************************************************************
 * aout_sys_t: ALSA audio output method descriptor
 *****************************************************************************
 * This structure is part of the audio output thread descriptor.
 * It describes the ALSA specific properties of an audio device.
 *****************************************************************************/
struct aout_sys_t
{
    snd_pcm_t         * p_snd_pcm;
dionoea's avatar
dionoea committed
63
    unsigned int                 i_period_time;
Arnaud de Bossoreille de Ribou's avatar
Arnaud de Bossoreille de Ribou committed
64

gbazin's avatar
 
gbazin committed
65
#ifdef ALSA_DEBUG
66 67
    snd_output_t      * p_snd_stderr;
#endif
gbazin's avatar
 
gbazin committed
68

69
    mtime_t      start_date;
70
    vlc_thread_t thread;
71
    vlc_sem_t    wait;
72 73
};

74 75
#define A52_FRAME_NB 1536

76
/* These values are in frames.
77
   To convert them to a number of bytes you have to multiply them by the
78
   number of channel(s) (eg. 2 for stereo) and the size of a sample (eg.
Sam Hocevar's avatar
Sam Hocevar committed
79
   2 for int16_t). */
gbazin's avatar
 
gbazin committed
80
#define ALSA_DEFAULT_PERIOD_SIZE        1024
gbazin's avatar
 
gbazin committed
81
#define ALSA_DEFAULT_BUFFER_SIZE        ( ALSA_DEFAULT_PERIOD_SIZE << 8 )
82
#define ALSA_SPDIF_PERIOD_SIZE          A52_FRAME_NB
83
#define ALSA_SPDIF_BUFFER_SIZE          ( ALSA_SPDIF_PERIOD_SIZE << 4 )
84
/* Why << 4 ? --Meuuh */
85
/* Why not ? --Bozo */
86
/* Right. --Meuuh */
87

88
#define DEFAULT_ALSA_DEVICE "default"
89

90 91 92
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
93 94
static int   Open         ( vlc_object_t * );
static void  Close        ( vlc_object_t * );
95
static void  Play         ( audio_output_t * );
96
static void* ALSAThread   ( void * );
97
static void  ALSAFill     ( audio_output_t * );
98 99
static int FindDevicesCallback( vlc_object_t *p_this, char const *psz_name,
                                vlc_value_t newval, vlc_value_t oldval, void *p_unused );
100
static void GetDevices( vlc_object_t *, module_config_t * );
101 102 103 104

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
static const char *const ppsz_devices[] = {
    "default", "plug:front",
    "plug:side", "plug:rear", "plug:center_lfe",
    "plug:surround40", "plug:surround41",
    "plug:surround50", "plug:surround51",
    "plug:surround71",
    "hdmi", "iec958",
};
static const char *const ppsz_devices_text[] = {
    N_("Default"), N_("Front speakers"),
    N_("Side speakers"), N_("Rear speakers"), N_("Center and subwoofer"),
    N_("Surround 4.0"), N_("Surround 4.1"),
    N_("Surround 5.0"), N_("Surround 5.1"),
    N_("Surround 7.1"),
    N_("HDMI"), N_("S/PDIF"),
};
121 122 123 124 125
vlc_module_begin ()
    set_shortname( "ALSA" )
    set_description( N_("ALSA audio output") )
    set_category( CAT_AUDIO )
    set_subcategory( SUBCAT_AUDIO_AOUT )
126
    add_string( "alsa-audio-device", DEFAULT_ALSA_DEVICE,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
127
                N_("ALSA Device Name"), NULL, false )
128
        add_deprecated_alias( "alsadev" )   /* deprecated since 0.9.3 */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
129
        change_string_list( ppsz_devices, ppsz_devices_text, FindDevicesCallback )
130
        change_action_add( FindDevicesCallback, N_("Refresh list") )
131

132 133 134
    set_capability( "audio output", 150 )
    set_callbacks( Open, Close )
vlc_module_end ()
135

136 137
/* VLC will insert a resampling filter in any case, so it is best to turn off
 * ALSA (plug) resampling. */
138
static const int mode = SND_PCM_NO_AUTO_RESAMPLE
139 140
/* ALSA just discards extra channels. Not good. Disable it. */
                      | SND_PCM_NO_AUTO_CHANNELS
141 142
/* VLC is currently unable to leverage ALSA softvol. Disable it. */
                      | SND_PCM_NO_SOFTVOL;
143

144 145 146 147
/**
 * Initializes list of devices.
 */
static void Probe (vlc_object_t *obj)
148
{
149 150 151
    /* Due to design bug in audio output core, this hack is required: */
    if (var_Type (obj, "audio-device"))
        return;
152

153 154
    /* The variable does not exist - first call. */
    vlc_value_t text;
155

156 157 158
    var_Create (obj, "audio-device", VLC_VAR_STRING | VLC_VAR_HASCHOICE);
    text.psz_string = _("Audio Device");
    var_Change (obj, "audio-device", VLC_VAR_SETTEXT, &text, NULL);
159

160
    GetDevices (obj, NULL);
gbazin's avatar
 
gbazin committed
161

162 163
    var_AddCallback (obj, "audio-device", aout_ChannelsRestart, NULL);
    var_TriggerCallback (obj, "intf-change");
164 165
}

166 167 168
/*****************************************************************************
 * Open: create a handle and open an alsa device
 *****************************************************************************
169 170 171 172
 * This function opens an alsa device, through the alsa API.
 *
 * Note: the only heap-allocated string is psz_device. All the other pointers
 * are references to psz_device or to stack-allocated data.
173
 *****************************************************************************/
174
static int Open (vlc_object_t *obj)
175
{
176
    audio_output_t * p_aout = (audio_output_t *)obj;
177

178
    /* Get device name */
179 180 181 182 183 184
    char *psz_device;

    if (var_Type (p_aout, "audio-device"))
        psz_device = var_GetString (p_aout, "audio-device");
    else
        psz_device = var_InheritString( p_aout, "alsa-audio-device" );
185
    if (unlikely(psz_device == NULL))
186
        return VLC_ENOMEM;
187

188
    snd_pcm_format_t pcm_format; /* ALSA sample format */
189
    vlc_fourcc_t fourcc = p_aout->format.i_format;
190 191
    bool spdif = false;

192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
    switch (fourcc)
    {
        case VLC_CODEC_F64B:
            pcm_format = SND_PCM_FORMAT_FLOAT64_BE;
            break;
        case VLC_CODEC_F64L:
            pcm_format = SND_PCM_FORMAT_FLOAT64_LE;
            break;
        case VLC_CODEC_F32B:
            pcm_format = SND_PCM_FORMAT_FLOAT_BE;
            break;
        case VLC_CODEC_F32L:
            pcm_format = SND_PCM_FORMAT_FLOAT_LE;
            break;
        case VLC_CODEC_FI32:
            fourcc = VLC_CODEC_FL32;
            pcm_format = SND_PCM_FORMAT_FLOAT;
            break;
        case VLC_CODEC_S32B:
            pcm_format = SND_PCM_FORMAT_S32_BE;
            break;
        case VLC_CODEC_S32L:
            pcm_format = SND_PCM_FORMAT_S32_LE;
            break;
        case VLC_CODEC_S24B:
            pcm_format = SND_PCM_FORMAT_S24_3BE;
            break;
        case VLC_CODEC_S24L:
            pcm_format = SND_PCM_FORMAT_S24_3LE;
            break;
        case VLC_CODEC_U24B:
            pcm_format = SND_PCM_FORMAT_U24_3BE;
            break;
        case VLC_CODEC_U24L:
            pcm_format = SND_PCM_FORMAT_U24_3LE;
            break;
        case VLC_CODEC_S16B:
            pcm_format = SND_PCM_FORMAT_S16_BE;
            break;
        case VLC_CODEC_S16L:
            pcm_format = SND_PCM_FORMAT_S16_LE;
            break;
        case VLC_CODEC_U16B:
            pcm_format = SND_PCM_FORMAT_U16_BE;
            break;
        case VLC_CODEC_U16L:
            pcm_format = SND_PCM_FORMAT_U16_LE;
            break;
        case VLC_CODEC_S8:
            pcm_format = SND_PCM_FORMAT_S8;
            break;
        case VLC_CODEC_U8:
            pcm_format = SND_PCM_FORMAT_U8;
            break;
        default:
247
            if (AOUT_FMT_NON_LINEAR(&p_aout->format))
248
                spdif = var_InheritBool (p_aout, "spdif");
249 250 251 252 253 254 255 256 257 258 259
            if (HAVE_FPU)
            {
                fourcc = VLC_CODEC_FL32;
                pcm_format = SND_PCM_FORMAT_FLOAT;
            }
            else
            {
                fourcc = VLC_CODEC_S16N;
                pcm_format = SND_PCM_FORMAT_S16;
            }
    }
260

261 262 263 264 265 266 267
    /* Choose the IEC device for S/PDIF output:
       if the device is overridden by the user then it will be the one
       otherwise we compute the default device based on the output format. */
    if (spdif && !strcmp (psz_device, DEFAULT_ALSA_DEVICE))
    {
        unsigned aes3;

268
        switch (p_aout->format.i_rate)
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
        {
#define FS(freq) \
            case freq: aes3 = IEC958_AES3_CON_FS_ ## freq; break;
            FS( 44100) /* def. */ FS( 48000) FS( 32000)
            FS( 22050)            FS( 24000)
            FS( 88200) FS(768000) FS( 96000)
            FS(176400)            FS(192000)
#undef FS
            default:
                aes3 = IEC958_AES3_CON_FS_NOTID;
                break;
        }

        free (psz_device);
        if (asprintf (&psz_device,
                      "iec958:AES0=0x%x,AES1=0x%x,AES2=0x%x,AES3=0x%x",
                      IEC958_AES0_CON_EMPHASIS_NONE | IEC958_AES0_NONAUDIO,
                      IEC958_AES1_CON_ORIGINAL | IEC958_AES1_CON_PCM_CODER,
                      0, aes3) == -1)
            return VLC_ENOMEM;
    }

291 292 293 294 295 296 297
    /* Allocate structures */
    aout_sys_t *p_sys = malloc (sizeof (*p_sys));
    if (unlikely(p_sys == NULL))
    {
        free (psz_device);
        return VLC_ENOMEM;
    }
298
    p_aout->sys = p_sys;
299

gbazin's avatar
 
gbazin committed
300
#ifdef ALSA_DEBUG
301 302
    snd_output_stdio_attach( &p_sys->p_snd_stderr, stderr, 0 );
#endif
303

304
    /* Open the device */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
305
    msg_Dbg( p_aout, "opening ALSA device `%s'", psz_device );
306
    int val = snd_pcm_open (&p_sys->p_snd_pcm, psz_device,
307
                            SND_PCM_STREAM_PLAYBACK, mode);
308 309 310 311 312 313 314 315 316 317
#if (SND_LIB_VERSION <= 0x010015)
# warning Please update alsa-lib to version > 1.0.21a.
    var_Create (p_aout->p_libvlc, "alsa-working", VLC_VAR_BOOL);
    if (val != 0 && var_GetBool (p_aout->p_libvlc, "alsa-working"))
        dialog_Fatal (p_aout, "ALSA version problem",
            "VLC failed to re-initialize your audio output device.\n"
            "Please update alsa-lib to version 1.0.22 or higher "
            "to fix this issue.");
    var_SetBool (p_aout->p_libvlc, "alsa-working", !val);
#endif
318
    if (val != 0)
319
    {
320 321 322 323 324 325 326 327 328 329 330 331
#if (SND_LIB_VERSION <= 0x010017)
# warning Please update alsa-lib to version > 1.0.23.
        var_Create (p_aout->p_libvlc, "alsa-broken", VLC_VAR_BOOL);
        if (!var_GetBool (p_aout->p_libvlc, "alsa-broken"))
        {
            var_SetBool (p_aout->p_libvlc, "alsa-broken", true);
            dialog_Fatal (p_aout, "Potential ALSA version problem",
                "VLC failed to initialize your audio output device (if any).\n"
                "Please update alsa-lib to version 1.0.24 or higher "
                "to try to fix this issue.");
        }
#endif
332 333 334 335 336 337 338 339
        msg_Err (p_aout, "cannot open ALSA device `%s' (%s)",
                 psz_device, snd_strerror (val));
        dialog_Fatal (p_aout, _("Audio output failed"),
                      _("The audio device \"%s\" could not be used:\n%s."),
                      psz_device, snd_strerror (val));
        free (psz_device);
        free (p_sys);
        return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
340 341 342 343 344
    }
    free( psz_device );

    snd_pcm_uframes_t i_buffer_size;
    snd_pcm_uframes_t i_period_size;
345
    unsigned channels;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
346

347
    if (spdif)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
348
    {
349
        fourcc = VLC_CODEC_SPDIFL;
350
        i_buffer_size = ALSA_SPDIF_BUFFER_SIZE;
351
        pcm_format = SND_PCM_FORMAT_S16;
352
        channels = 2;
353

354 355 356
        p_aout->i_nb_samples = i_period_size = ALSA_SPDIF_PERIOD_SIZE;
        p_aout->format.i_bytes_per_frame = AOUT_SPDIF_SIZE;
        p_aout->format.i_frame_length = A52_FRAME_NB;
357

358
        aout_VolumeNoneInit( p_aout );
359
    }
360
    else
361 362
    {
        i_buffer_size = ALSA_DEFAULT_BUFFER_SIZE;
363
        channels = aout_FormatNbChannels( &p_aout->format );
364

365
        p_aout->i_nb_samples = i_period_size = ALSA_DEFAULT_PERIOD_SIZE;
366

367
        aout_VolumeSoftInit( p_aout );
368 369
    }

370 371
    p_aout->pf_play = Play;
    p_aout->pf_pause = NULL;
372
    p_aout->pf_flush = NULL;
373

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
374 375 376
    snd_pcm_hw_params_t *p_hw;
    snd_pcm_sw_params_t *p_sw;

377 378 379
    snd_pcm_hw_params_alloca(&p_hw);
    snd_pcm_sw_params_alloca(&p_sw);

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
380 381 382
    /* Get Initial hardware parameters */
    val = snd_pcm_hw_params_any( p_sys->p_snd_pcm, p_hw );
    if( val < 0 )
383
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
384 385 386 387
        msg_Err( p_aout, "unable to retrieve hardware parameters (%s)",
                snd_strerror( val ) );
        goto error;
    }
388

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
389
    /* Set format. */
390
    val = snd_pcm_hw_params_set_format (p_sys->p_snd_pcm, p_hw, pcm_format);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
391 392
    if( val < 0 )
    {
393 394
        msg_Err (p_aout, "cannot set sample format: %s", snd_strerror (val));
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
395 396 397 398 399 400 401 402 403 404
    }

    val = snd_pcm_hw_params_set_access( p_sys->p_snd_pcm, p_hw,
                                        SND_PCM_ACCESS_RW_INTERLEAVED );
    if( val < 0 )
    {
        msg_Err( p_aout, "unable to set interleaved stream format (%s)",
                 snd_strerror( val ) );
        goto error;
    }
405

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
406
    /* Set channels. */
407 408 409 410 411 412 413
    val = snd_pcm_hw_params_set_channels (p_sys->p_snd_pcm, p_hw, channels);
    if (val < 0 && channels > 2) /* Fallback to stereo */
    {
        val = snd_pcm_hw_params_set_channels (p_sys->p_snd_pcm, p_hw, 2);
        channels = 2;
    }
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
414 415 416 417 418
    {
        msg_Err( p_aout, "unable to set number of output channels (%s)",
                 snd_strerror( val ) );
        goto error;
    }
419

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
420
    /* Set rate. */
421 422
    unsigned rate = p_aout->format.i_rate;
    val = snd_pcm_hw_params_set_rate_near (p_sys->p_snd_pcm, p_hw, &rate,
423 424
                                           NULL);
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
425
    {
426 427 428
        msg_Err (p_aout, "unable to set sampling rate (%s)",
                 snd_strerror (val));
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
429
    }
430 431 432
    if (p_aout->format.i_rate != rate)
        msg_Warn (p_aout, "resampling from %d Hz to %d Hz",
                  p_aout->format.i_rate, rate);
433

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
434 435 436 437 438 439 440 441 442
    /* Set period size. */
    val = snd_pcm_hw_params_set_period_size_near( p_sys->p_snd_pcm, p_hw,
                                                  &i_period_size, NULL );
    if( val < 0 )
    {
        msg_Err( p_aout, "unable to set period size (%s)",
                 snd_strerror( val ) );
        goto error;
    }
443
    p_aout->i_nb_samples = i_period_size;
444

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
445 446 447 448 449 450 451 452 453
    /* Set buffer size. */
    val = snd_pcm_hw_params_set_buffer_size_near( p_sys->p_snd_pcm, p_hw,
                                                  &i_buffer_size );
    if( val )
    {
        msg_Err( p_aout, "unable to set buffer size (%s)",
                 snd_strerror( val ) );
        goto error;
    }
454

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
455 456 457 458 459 460 461
    /* Commit hardware parameters. */
    val = snd_pcm_hw_params( p_sys->p_snd_pcm, p_hw );
    if( val < 0 )
    {
        msg_Err( p_aout, "unable to commit hardware configuration (%s)",
                 snd_strerror( val ) );
        goto error;
462 463
    }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
464 465 466
    val = snd_pcm_hw_params_get_period_time( p_hw, &p_sys->i_period_time,
                                             NULL );
    if( val < 0 )
467 468
    {
        msg_Err( p_aout, "unable to get period time (%s)",
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
469
                 snd_strerror( val ) );
470 471
        goto error;
    }
472

473
    /* Get Initial software parameters */
474
    snd_pcm_sw_params_current( p_sys->p_snd_pcm, p_sw );
475

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
476
    snd_pcm_sw_params_set_avail_min( p_sys->p_snd_pcm, p_sw,
477
                                     p_aout->i_nb_samples );
478
    /* start playing when one period has been written */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
479 480 481
    val = snd_pcm_sw_params_set_start_threshold( p_sys->p_snd_pcm, p_sw,
                                                 ALSA_DEFAULT_PERIOD_SIZE);
    if( val < 0 )
482 483
    {
        msg_Err( p_aout, "unable to set start threshold (%s)",
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
484
                 snd_strerror( val ) );
485 486
        goto error;
    }
487

488
    /* Commit software parameters. */
489
    if ( snd_pcm_sw_params( p_sys->p_snd_pcm, p_sw ) < 0 )
490 491
    {
        msg_Err( p_aout, "unable to set software configuration" );
492
        goto error;
493 494
    }

gbazin's avatar
 
gbazin committed
495
#ifdef ALSA_DEBUG
496 497 498 499 500 501 502
    snd_output_printf( p_sys->p_snd_stderr, "\nALSA hardware setup:\n\n" );
    snd_pcm_dump_hw_setup( p_sys->p_snd_pcm, p_sys->p_snd_stderr );
    snd_output_printf( p_sys->p_snd_stderr, "\nALSA software setup:\n\n" );
    snd_pcm_dump_sw_setup( p_sys->p_snd_pcm, p_sys->p_snd_stderr );
    snd_output_printf( p_sys->p_snd_stderr, "\n" );
#endif

503 504 505
    p_sys->start_date = 0;
    vlc_sem_init( &p_sys->wait, 0 );

Christophe Massiot's avatar
Christophe Massiot committed
506
    /* Create ALSA thread and wait for its readiness. */
507 508
    if( vlc_clone( &p_sys->thread, ALSAThread, p_aout,
                   VLC_THREAD_PRIORITY_OUTPUT ) )
Christophe Massiot's avatar
Christophe Massiot committed
509
    {
510
        msg_Err( p_aout, "cannot create ALSA thread (%m)" );
511
        vlc_sem_destroy( &p_sys->wait );
512
        goto error;
Christophe Massiot's avatar
Christophe Massiot committed
513
    }
514

515 516
    p_aout->format.i_format = fourcc;
    p_aout->format.i_rate = rate;
517 518
    if (channels == 2)
        p_aout->format.i_physical_channels = AOUT_CHAN_LEFT|AOUT_CHAN_RIGHT;
519

520
    Probe (obj);
521
    return 0;
522 523 524

error:
    snd_pcm_close( p_sys->p_snd_pcm );
gbazin's avatar
 
gbazin committed
525
#ifdef ALSA_DEBUG
526 527 528 529
    snd_output_close( p_sys->p_snd_stderr );
#endif
    free( p_sys );
    return VLC_EGENERIC;
530 531
}

532
static void PlayIgnore( audio_output_t *p_aout )
533 534 535 536
{   /* Already playing - nothing to do */
    (void) p_aout;
}

537
/*****************************************************************************
538
 * Play: start playback
539
 *****************************************************************************/
540
static void Play( audio_output_t *p_aout )
541
{
542
    p_aout->pf_play = PlayIgnore;
gbazin's avatar
 
gbazin committed
543

544
    /* get the playing date of the first aout buffer */
545
    p_aout->sys->start_date = aout_FifoFirstDate( &p_aout->fifo );
gbazin's avatar
 
gbazin committed
546

547
    /* wake up the audio output thread */
548
    sem_post( &p_aout->sys->wait );
549 550 551
}

/*****************************************************************************
552
 * Close: close the ALSA device
553
 *****************************************************************************/
554
static void Close (vlc_object_t *obj)
555
{
556
    audio_output_t *p_aout = (audio_output_t *)obj;
557
    struct aout_sys_t * p_sys = p_aout->sys;
558

559
    /* Make sure that the thread will stop once it is waken up */
560 561
    vlc_cancel( p_sys->thread );
    vlc_join( p_sys->thread, NULL );
562
    vlc_sem_destroy( &p_sys->wait );
gbazin's avatar
 
gbazin committed
563

564
    snd_pcm_drop( p_sys->p_snd_pcm );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
565
    snd_pcm_close( p_sys->p_snd_pcm );
gbazin's avatar
 
gbazin committed
566
#ifdef ALSA_DEBUG
567 568
    snd_output_close( p_sys->p_snd_stderr );
#endif
569
    free( p_sys );
570 571 572
}

/*****************************************************************************
573
 * ALSAThread: asynchronous thread used to DMA the data to the device
574
 *****************************************************************************/
575
static void* ALSAThread( void *data )
576
{
577
    audio_output_t * p_aout = data;
578
    struct aout_sys_t * p_sys = p_aout->sys;
579

gbazin's avatar
 
gbazin committed
580
    /* Wait for the exact time to start playing (avoids resampling) */
581
    vlc_sem_wait( &p_sys->wait );
582 583
    mwait( p_sys->start_date - AOUT_MAX_PTS_ADVANCE / 4 );
#warning Should wait for buffer availability instead!
584

585
    for(;;)
586 587
        ALSAFill( p_aout );

588
    assert(0);
589 590 591
}

/*****************************************************************************
592
 * ALSAFill: function used to fill the ALSA buffer as much as possible
593
 *****************************************************************************/
594
static void ALSAFill( audio_output_t * p_aout )
595
{
596
    struct aout_sys_t * p_sys = p_aout->sys;
597
    snd_pcm_t *p_pcm = p_sys->p_snd_pcm;
598
    snd_pcm_status_t * p_status;
599
    int i_snd_rc;
Arnaud de Bossoreille de Ribou's avatar
Arnaud de Bossoreille de Ribou committed
600
    mtime_t next_date;
601

602
    int canc = vlc_savecancel();
603
    /* Fill in the buffer until space or audio output buffer shortage */
604 605

    /* Get the status */
606
    snd_pcm_status_alloca(&p_status);
607
    i_snd_rc = snd_pcm_status( p_pcm, p_status );
608
    if( i_snd_rc < 0 )
609
    {
610 611 612
        msg_Err( p_aout, "cannot get device status" );
        goto error;
    }
gbazin's avatar
 
gbazin committed
613

614 615 616 617
    /* Handle buffer underruns and get the status again */
    if( snd_pcm_status_get_state( p_status ) == SND_PCM_STATE_XRUN )
    {
        /* Prepare the device */
618
        i_snd_rc = snd_pcm_prepare( p_pcm );
619
        if( i_snd_rc )
620
        {
621 622 623
            msg_Err( p_aout, "cannot recover from buffer underrun" );
            goto error;
        }
624

625
        msg_Dbg( p_aout, "recovered from buffer underrun" );
626

627
        /* Get the new status */
628
        i_snd_rc = snd_pcm_status( p_pcm, p_status );
629 630 631 632 633
        if( i_snd_rc < 0 )
        {
            msg_Err( p_aout, "cannot get device status after recovery" );
            goto error;
        }
gbazin's avatar
 
gbazin committed
634

635 636 637 638 639
        /* Underrun, try to recover as quickly as possible */
        next_date = mdate();
    }
    else
    {
Rafaël Carré's avatar
Rafaël Carré committed
640
        /* Here the device should be in RUNNING state, p_status is valid. */
641
        snd_pcm_sframes_t delay = snd_pcm_status_get_delay( p_status );
Rafaël Carré's avatar
Rafaël Carré committed
642
        if( delay == 0 ) /* workaround buggy alsa drivers */
643
            if( snd_pcm_delay( p_pcm, &delay ) < 0 )
Rafaël Carré's avatar
Rafaël Carré committed
644
                delay = 0; /* FIXME: use a positive minimal delay */
645

646
        size_t i_bytes = snd_pcm_frames_to_bytes( p_pcm, delay );
647
        mtime_t delay_us = CLOCK_FREQ * i_bytes
648 649 650
                / p_aout->format.i_bytes_per_frame
                / p_aout->format.i_rate
                * p_aout->format.i_frame_length;
651 652 653 654 655 656

#ifdef ALSA_DEBUG
        snd_pcm_state_t state = snd_pcm_status_get_state( p_status );
        if( state != SND_PCM_STATE_RUNNING )
            msg_Err( p_aout, "pcm status (%d) != RUNNING", state );

657
        msg_Dbg( p_aout, "Delay is %ld frames (%zu bytes)", delay, i_bytes );
658

659 660 661
        msg_Dbg( p_aout, "Bytes per frame: %d", p_aout->format.i_bytes_per_frame );
        msg_Dbg( p_aout, "Rate: %d", p_aout->format.i_rate );
        msg_Dbg( p_aout, "Frame length: %d", p_aout->format.i_frame_length );
662
        msg_Dbg( p_aout, "Next date: in %"PRId64" microseconds", delay_us );
663
#endif
664
        next_date = mdate() + delay_us;
665
    }
666

667
    block_t *p_buffer = aout_OutputNextBuffer( p_aout, next_date,
668
           (p_aout->format.i_format ==  VLC_CODEC_SPDIFL) );
669

670 671 672 673
    /* Audio output buffer shortage -> stop the fill process and wait */
    if( p_buffer == NULL )
        goto error;

ivoire's avatar
ivoire committed
674
    block_cleanup_push( p_buffer );
675 676
    for (;;)
    {
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697
        int n = snd_pcm_poll_descriptors_count(p_pcm);
        struct pollfd ufd[n];
        unsigned short revents;

        snd_pcm_poll_descriptors(p_pcm, ufd, n);
        do
        {
            vlc_restorecancel(canc);
            poll(ufd, n, -1);
            canc = vlc_savecancel();
            snd_pcm_poll_descriptors_revents(p_pcm, ufd, n, &revents);
        }
        while(!revents);

        if(revents & POLLOUT)
        {
            i_snd_rc = snd_pcm_writei( p_pcm, p_buffer->p_buffer,
                                       p_buffer->i_nb_samples );
            if( i_snd_rc != -ESTRPIPE )
                break;
        }
698

699 700 701 702
        /* a suspend event occurred
         * (stream is suspended and waiting for an application recovery) */
        msg_Dbg( p_aout, "entering in suspend mode, trying to resume..." );

703
        while( ( i_snd_rc = snd_pcm_resume( p_pcm ) ) == -EAGAIN )
704
        {
705 706 707
            vlc_restorecancel(canc);
            msleep(CLOCK_FREQ); /* device still suspended, wait... */
            canc = vlc_savecancel();
708 709
        }

Arnaud de Bossoreille de Ribou's avatar
Arnaud de Bossoreille de Ribou committed
710
        if( i_snd_rc < 0 )
711 712
            /* Device does not support resuming, restart it */
            i_snd_rc = snd_pcm_prepare( p_pcm );
Arnaud de Bossoreille de Ribou's avatar
Arnaud de Bossoreille de Ribou committed
713

714
    }
715 716 717 718

    if( i_snd_rc < 0 )
        msg_Err( p_aout, "cannot write: %s", snd_strerror( i_snd_rc ) );

719
    vlc_restorecancel(canc);
ivoire's avatar
ivoire committed
720
    vlc_cleanup_run();
721 722 723 724 725
    return;

error:
    if( i_snd_rc < 0 )
        msg_Err( p_aout, "ALSA error: %s", snd_strerror( i_snd_rc ) );
726 727 728

    vlc_restorecancel(canc);
    msleep(p_sys->i_period_time / 2);
729
}
730 731 732 733 734 735 736 737

/*****************************************************************************
 * config variable callback
 *****************************************************************************/
static int FindDevicesCallback( vlc_object_t *p_this, char const *psz_name,
                               vlc_value_t newval, vlc_value_t oldval, void *p_unused )
{
    module_config_t *p_item;
738 739 740
    (void)newval;
    (void)oldval;
    (void)p_unused;
741 742 743 744 745 746 747

    p_item = config_FindConfig( p_this, psz_name );
    if( !p_item ) return VLC_SUCCESS;

    /* Clear-up the current list */
    if( p_item->i_list )
    {
748 749
        int i;

750 751 752
        /* Keep the first entrie */
        for( i = 1; i < p_item->i_list; i++ )
        {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
753 754
            free( (char *)p_item->ppsz_list[i] );
            free( (char *)p_item->ppsz_list_text[i] );
755 756 757 758 759 760 761
        }
        /* TODO: Remove when no more needed */
        p_item->ppsz_list[i] = NULL;
        p_item->ppsz_list_text[i] = NULL;
    }
    p_item->i_list = 1;

762
    GetDevices( p_this, p_item );
763 764

    /* Signal change to the interface */
765
    p_item->b_dirty = true;
766 767 768 769 770

    return VLC_SUCCESS;
}


771
static void GetDevices (vlc_object_t *obj, module_config_t *item)
772
{
773
    void **hints;
774

775
    msg_Dbg(obj, "Available ALSA PCM devices:");
776

777
    if (snd_device_name_hint(-1, "pcm", &hints) < 0)
778
        return;
779

780
    for (size_t i = 0; hints[i] != NULL; i++)
781
    {
782
        void *hint = hints[i];
783 784
        char *dev;

785
        char *name = snd_device_name_get_hint(hint, "NAME");
786 787
        if (unlikely(name == NULL))
            continue;
788 789 790 791 792
        if (unlikely(asprintf (&dev, "plug:'%s'", name) == -1))
        {
            free(name);
            continue;
        }
793

794 795 796 797
        char *desc = snd_device_name_get_hint(hint, "DESC");
        if (desc != NULL)
            for (char *lf = strchr(desc, '\n'); lf; lf = strchr(lf, '\n'))
                 *lf = ' ';
798
        msg_Dbg(obj, "%s (%s)", (desc != NULL) ? desc : name, name);
799

800
        if (item != NULL)
801
        {
802 803 804 805
            item->ppsz_list = xrealloc(item->ppsz_list,
                                       (item->i_list + 2) * sizeof(char *));
            item->ppsz_list_text = xrealloc(item->ppsz_list_text,
                                          (item->i_list + 2) * sizeof(char *));
806
            item->ppsz_list[item->i_list] = dev;
807 808
            if (desc == NULL)
                desc = strdup(name);
809 810
            item->ppsz_list_text[item->i_list] = desc;
            item->i_list++;
811 812 813
        }
        else
        {
814 815 816 817 818
            vlc_value_t val, text;

            val.psz_string = dev;
            text.psz_string = desc;
            var_Change(obj, "audio-device", VLC_VAR_ADDCHOICE, &val, &text);
819
            free(desc);
820
            free(dev);
821
            free(name);
822
        }
823 824
    }

825
    snd_device_name_free_hint(hints);
826

827 828 829 830 831
    if (item != NULL)
    {
        item->ppsz_list[item->i_list] = NULL;
        item->ppsz_list_text[item->i_list] = NULL;
    }
832
}