* alsa.c : alsa plugin for vlc
*****************************************************************************
* Copyright (C) 2000-2001 VideoLAN
- * $Id: alsa.c,v 1.23 2003/02/20 01:52:45 sigmunau Exp $
+ * $Id: alsa.c,v 1.38 2004/01/25 17:58:29 murray Exp $
*
* Authors: Henri Fallon <henri@videolan.org> - Original Author
* Jeffrey Baker <jwbaker@acm.org> - Port to ALSA 1.0 API
snd_pcm_t * p_snd_pcm;
int i_period_time;
-#ifdef DEBUG
+#ifdef ALSA_DEBUG
snd_output_t * p_snd_stderr;
#endif
+
+ int b_playing; /* playing status */
+ mtime_t start_date;
+
+ vlc_mutex_t lock;
+ vlc_cond_t wait ;
};
#define A52_FRAME_NB 1536
/* These values are in frames.
To convert them to a number of bytes you have to multiply them by the
number of channel(s) (eg. 2 for stereo) and the size of a sample (eg.
- 2 for s16). */
-#define ALSA_DEFAULT_PERIOD_SIZE 2048
-#define ALSA_DEFAULT_BUFFER_SIZE ( ALSA_DEFAULT_PERIOD_SIZE << 4 )
+ 2 for int16_t). */
+#define ALSA_DEFAULT_PERIOD_SIZE 1024
+#define ALSA_DEFAULT_BUFFER_SIZE ( ALSA_DEFAULT_PERIOD_SIZE << 8 )
#define ALSA_SPDIF_PERIOD_SIZE A52_FRAME_NB
#define ALSA_SPDIF_BUFFER_SIZE ( ALSA_SPDIF_PERIOD_SIZE << 4 )
/* Why << 4 ? --Meuuh */
/* Why not ? --Bozo */
/* Right. --Meuuh */
-#define DEFAULT_ALSA_DEVICE "default"
+#define DEFAULT_ALSA_DEVICE N_("default")
/*****************************************************************************
* Local prototypes
/*****************************************************************************
* Module descriptor
*****************************************************************************/
-#define SPDIF_TEXT N_("Try to use S/PDIF output")
-#define SPDIF_LONGTEXT N_( \
- "Sometimes we attempt to use the S/PDIF output, even if nothing is " \
- "connected to it. Un-checking this option disables this behaviour, " \
- "and permanently selects analog PCM output." )
-
vlc_module_begin();
add_category_hint( N_("ALSA"), NULL, VLC_FALSE );
add_string( "alsadev", DEFAULT_ALSA_DEVICE, aout_FindAndRestart,
- N_("ALSA device name"), NULL, VLC_FALSE );
- add_bool( "spdif", 1, NULL, SPDIF_TEXT, SPDIF_LONGTEXT, VLC_FALSE );
- set_description( _("ALSA audio module") );
- set_capability( "audio output", 50 );
+ N_("ALSA Device Name"), NULL, VLC_FALSE );
+ set_description( _("ALSA audio output") );
+ set_capability( "audio output", 150 );
set_callbacks( Open, Close );
vlc_module_end();
*****************************************************************************/
static void Probe( aout_instance_t * p_aout,
const char * psz_device, const char * psz_iec_device,
- int i_snd_pcm_format )
+ int *pi_snd_pcm_format )
{
struct aout_sys_t * p_sys = p_aout->output.p_sys;
- vlc_value_t val;
+ vlc_value_t val, text;
+ int i_ret;
- var_Create ( p_aout, "audio-device", VLC_VAR_STRING | VLC_VAR_HASCHOICE );
+ var_Create ( p_aout, "audio-device", VLC_VAR_INTEGER | VLC_VAR_HASCHOICE );
+ text.psz_string = _("Audio Device");
+ var_Change( p_aout, "audio-device", VLC_VAR_SETTEXT, &text, NULL );
- /* Test for S/PDIF device if needed */
- if ( psz_iec_device )
- {
- /* Opening the device should be enough */
- if ( config_GetInt( p_aout, "spdif" )
- && !snd_pcm_open( &p_sys->p_snd_pcm, psz_iec_device,
- SND_PCM_STREAM_PLAYBACK, 0 ) )
- {
- val.psz_string = N_("A/52 over S/PDIF");
- var_Change( p_aout, "audio-device", VLC_VAR_ADDCHOICE, &val );
- snd_pcm_close( p_sys->p_snd_pcm );
- }
- }
+ /* We'll open the audio device in non blocking mode so we can just exit
+ * when it is already in use, but for the real stuff we'll still use
+ * the blocking mode */
/* Now test linear PCM capabilities */
- if ( !snd_pcm_open( &p_sys->p_snd_pcm, psz_device,
- SND_PCM_STREAM_PLAYBACK, 0 ) )
+ if ( !(i_ret = snd_pcm_open( &p_sys->p_snd_pcm, psz_device,
+ SND_PCM_STREAM_PLAYBACK,
+ SND_PCM_NONBLOCK ) ) )
{
int i_channels;
snd_pcm_hw_params_t * p_hw;
msg_Warn( p_aout, "unable to retrieve initial hardware parameters"
", disabling linear PCM audio" );
snd_pcm_close( p_sys->p_snd_pcm );
+ var_Destroy( p_aout, "audio-device" );
return;
}
if ( snd_pcm_hw_params_set_format( p_sys->p_snd_pcm, p_hw,
- i_snd_pcm_format ) < 0 )
+ *pi_snd_pcm_format ) < 0 )
{
- /* Assume a FPU enabled computer can handle float32 format.
- If somebody tells us it's not always true then we'll have
- to change this */
- msg_Warn( p_aout, "unable to set stream sample size and word order"
- ", disabling linear PCM audio" );
- snd_pcm_close( p_sys->p_snd_pcm );
- return;
+ if( *pi_snd_pcm_format != SND_PCM_FORMAT_S16 )
+ {
+ *pi_snd_pcm_format = SND_PCM_FORMAT_S16;
+ if ( snd_pcm_hw_params_set_format( p_sys->p_snd_pcm, p_hw,
+ *pi_snd_pcm_format ) < 0 )
+ {
+ msg_Warn( p_aout, "unable to set stream sample size and "
+ "word order, disabling linear PCM audio" );
+ snd_pcm_close( p_sys->p_snd_pcm );
+ var_Destroy( p_aout, "audio-device" );
+ return;
+ }
+ }
}
i_channels = aout_FormatNbChannels( &p_aout->output.output );
while ( i_channels > 0 )
{
- /* Here we have to probe multi-channel capabilities but I have
- no idea (at the moment) of how its managed by the ALSA
- library.
- It seems that '6' channels aren't well handled on a stereo
- sound card like my i810 but it requires some more
- investigations. That's why '4' and '6' cases are disabled.
- -- Bozo */
if ( !snd_pcm_hw_params_test_channels( p_sys->p_snd_pcm, p_hw,
i_channels ) )
{
switch ( i_channels )
{
case 1:
- val.psz_string = N_("Mono");
+ val.i_int = AOUT_VAR_MONO;
+ text.psz_string = N_("Mono");
var_Change( p_aout, "audio-device",
- VLC_VAR_ADDCHOICE, &val );
+ VLC_VAR_ADDCHOICE, &val, &text );
break;
case 2:
- val.psz_string = N_("Stereo");
+ val.i_int = AOUT_VAR_STEREO;
+ text.psz_string = N_("Stereo");
var_Change( p_aout, "audio-device",
- VLC_VAR_ADDCHOICE, &val );
+ VLC_VAR_ADDCHOICE, &val, &text );
+ var_Set( p_aout, "audio-device", val );
break;
-/*
case 4:
- val.psz_string = N_("2 Front 2 Rear");
+ val.i_int = AOUT_VAR_2F2R;
+ text.psz_string = N_("2 Front 2 Rear");
var_Change( p_aout, "audio-device",
- VLC_VAR_ADDCHOICE, &val );
+ VLC_VAR_ADDCHOICE, &val, &text );
break;
case 6:
- val.psz_string = N_("5.1");
+ val.i_int = AOUT_VAR_5_1;
+ text.psz_string = N_("5.1");
var_Change( p_aout, "audio-device",
- VLC_VAR_ADDCHOICE, &val );
+ VLC_VAR_ADDCHOICE, &val, &text );
break;
-*/
}
}
/* Close the previously opened device */
snd_pcm_close( p_sys->p_snd_pcm );
}
+ else if ( i_ret == -EBUSY )
+ {
+ msg_Warn( p_aout, "audio device: %s is already in use", psz_device );
+ }
+
+ /* Test for S/PDIF device if needed */
+ if ( psz_iec_device )
+ {
+ /* Opening the device should be enough */
+ if ( !(i_ret = snd_pcm_open( &p_sys->p_snd_pcm, psz_iec_device,
+ SND_PCM_STREAM_PLAYBACK,
+ SND_PCM_NONBLOCK ) ) )
+ {
+ val.i_int = AOUT_VAR_SPDIF;
+ text.psz_string = N_("A/52 over S/PDIF");
+ var_Change( p_aout, "audio-device",
+ VLC_VAR_ADDCHOICE, &val, &text );
+ if( config_GetInt( p_aout, "spdif" ) )
+ var_Set( p_aout, "audio-device", val );
+
+ snd_pcm_close( p_sys->p_snd_pcm );
+ }
+ else if ( i_ret == -EBUSY )
+ {
+ msg_Warn( p_aout, "audio device: %s is already in use",
+ psz_iec_device );
+ }
+ }
+
+ var_Change( p_aout, "audio-device", VLC_VAR_CHOICESCOUNT, &val, NULL );
+ if( val.i_int <= 0 )
+ {
+ /* Probe() has failed. */
+ var_Destroy( p_aout, "audio-device" );
+ return;
+ }
/* Add final settings to the variable */
var_AddCallback( p_aout, "audio-device", aout_ChannelsRestart, NULL );
msg_Err( p_aout, "out of memory" );
return VLC_ENOMEM;
}
+ p_sys->b_playing = VLC_FALSE;
+ p_sys->start_date = 0;
+ vlc_cond_init( p_aout, &p_sys->wait );
+ vlc_mutex_init( p_aout, &p_sys->lock );
/* Get device name */
if( (psz_device = config_GetPsz( p_aout, "alsadev" )) == NULL )
and we have to probe the available audio formats and channels */
if ( var_Type( p_aout, "audio-device" ) == 0 )
{
- Probe( p_aout, psz_device, psz_iec_device, i_snd_pcm_format );
+ Probe( p_aout, psz_device, psz_iec_device, &i_snd_pcm_format );
+ switch( i_snd_pcm_format )
+ {
+ case SND_PCM_FORMAT_FLOAT:
+ i_vlc_pcm_format = VLC_FOURCC('f','l','3','2');
+ break;
+ case SND_PCM_FORMAT_S16:
+ i_vlc_pcm_format = AOUT_FMT_S16_NE;
+ break;
+ }
}
if ( var_Get( p_aout, "audio-device", &val ) < 0 )
return VLC_EGENERIC;
}
- if ( !strcmp( val.psz_string, N_("A/52 over S/PDIF") ) )
+ if ( val.i_int == AOUT_VAR_SPDIF )
{
p_aout->output.output.i_format = VLC_FOURCC('s','p','d','i');
}
- else if ( !strcmp( val.psz_string, N_("5.1") ) )
+ else if ( val.i_int == AOUT_VAR_5_1 )
{
p_aout->output.output.i_format = i_vlc_pcm_format;
p_aout->output.output.i_physical_channels
= AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
| AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT
| AOUT_CHAN_LFE;
+ free( psz_device );
+ psz_device = strdup( "surround51" );
}
- else if ( !strcmp( val.psz_string, N_("2 Front 2 Rear") ) )
+ else if ( val.i_int == AOUT_VAR_2F2R )
{
p_aout->output.output.i_format = i_vlc_pcm_format;
p_aout->output.output.i_physical_channels
= AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT
| AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT;
+ free( psz_device );
+ psz_device = strdup( "surround40" );
}
- else if ( !strcmp( val.psz_string, N_("Stereo") ) )
+ else if ( val.i_int == AOUT_VAR_STEREO )
{
p_aout->output.output.i_format = i_vlc_pcm_format;
p_aout->output.output.i_physical_channels
= AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT;
}
- else if ( !strcmp( val.psz_string, N_("Mono") ) )
+ else if ( val.i_int == AOUT_VAR_MONO )
{
p_aout->output.output.i_format = i_vlc_pcm_format;
p_aout->output.output.i_physical_channels = AOUT_CHAN_CENTER;
else
{
/* This should not happen ! */
- msg_Err( p_aout, "internal: can't find audio-device (%s)",
- val.psz_string );
+ msg_Err( p_aout, "internal: can't find audio-device (%i)", val.i_int );
free( p_sys );
- free( val.psz_string );
return VLC_EGENERIC;
}
- free( val.psz_string );
-#ifdef DEBUG
+#ifdef ALSA_DEBUG
snd_output_stdio_attach( &p_sys->p_snd_stderr, stderr, 0 );
#endif
}
else
{
+ msg_Dbg( p_aout, "opening ALSA device `%s'", psz_device );
+
if ( ( i_snd_rc = snd_pcm_open( &p_sys->p_snd_pcm, psz_device,
SND_PCM_STREAM_PLAYBACK, 0 ) ) < 0 )
{
p_aout->output.output.i_rate, NULL ) ) < 0 )
#endif
{
- msg_Err( p_aout, "unable to set sample rate (%s)",
- snd_strerror( i_snd_rc ) );
- goto error;
+ msg_Warn( p_aout, "The rate %d Hz is not supported by your hardware. "
+ "Using %d Hz instead.\n", p_aout->output.output.i_rate,
+ i_snd_rc );
+ p_aout->output.output.i_rate = i_snd_rc;
}
/* Set buffer size. */
goto error;
}
-#ifdef DEBUG
+#ifdef ALSA_DEBUG
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" );
error:
snd_pcm_close( p_sys->p_snd_pcm );
-#ifdef DEBUG
+#ifdef ALSA_DEBUG
snd_output_close( p_sys->p_snd_stderr );
#endif
free( p_sys );
*****************************************************************************/
static void Play( aout_instance_t *p_aout )
{
+ if( !p_aout->output.p_sys->b_playing )
+ {
+ p_aout->output.p_sys->b_playing = 1;
+
+ /* get the playing date of the first aout buffer */
+ p_aout->output.p_sys->start_date =
+ aout_FifoFirstDate( p_aout, &p_aout->output.fifo );
+
+ /* wake up the audio output thread */
+ vlc_mutex_lock( &p_aout->output.p_sys->lock );
+ vlc_cond_signal( &p_aout->output.p_sys->wait );
+ vlc_mutex_unlock( &p_aout->output.p_sys->lock );
+ }
}
/*****************************************************************************
struct aout_sys_t * p_sys = p_aout->output.p_sys;
int i_snd_rc;
+ /* make sure the audio output thread is waken up */
+ vlc_mutex_lock( &p_aout->output.p_sys->lock );
+ vlc_cond_signal( &p_aout->output.p_sys->wait );
+ vlc_mutex_unlock( &p_aout->output.p_sys->lock );
+
p_aout->b_die = VLC_TRUE;
vlc_thread_join( p_aout );
p_aout->b_die = VLC_FALSE;
snd_strerror( i_snd_rc ) );
}
-#ifdef DEBUG
+#ifdef ALSA_DEBUG
snd_output_close( p_sys->p_snd_stderr );
#endif
*****************************************************************************/
static int ALSAThread( aout_instance_t * p_aout )
{
- struct aout_sys_t * p_sys = p_aout->output.p_sys;
+ /* Wait for the exact time to start playing (avoids resampling) */
+ vlc_mutex_lock( &p_aout->output.p_sys->lock );
+ if( !p_aout->output.p_sys->start_date )
+ vlc_cond_wait( &p_aout->output.p_sys->wait,
+ &p_aout->output.p_sys->lock );
+ vlc_mutex_unlock( &p_aout->output.p_sys->lock );
+
+ mwait( p_aout->output.p_sys->start_date - AOUT_PTS_TOLERANCE / 4 );
while ( !p_aout->b_die )
{
ALSAFill( p_aout );
-
- /* Sleep during less than one period to avoid a lot of buffer
- underruns */
-
- /* Why do we need to sleep ? --Meuuh */
- /* Maybe because I don't want to eat all the cpu by looping
- all the time. --Bozo */
- /* Shouldn't snd_pcm_wait() make us wait ? --Meuuh */
- msleep( p_sys->i_period_time >> 1 );
}
return 0;
snd_pcm_status_alloca( &p_status );
- /* Wait for the device's readiness (ie. there is enough space in the
- buffer to write at least one complete chunk) */
- i_snd_rc = snd_pcm_wait( p_sys->p_snd_pcm, THREAD_SLEEP );
- if( i_snd_rc < 0 )
- {
- msg_Err( p_aout, "ALSA device not ready !!! (%s)",
- snd_strerror( i_snd_rc ) );
- return;
- }
-
/* Fill in the buffer until space or audio output buffer shortage */
- for ( ; ; )
{
/* Get the status */
i_snd_rc = snd_pcm_status( p_sys->p_snd_pcm, p_status );
{
msg_Err( p_aout, "unable to get the device's status (%s)",
snd_strerror( i_snd_rc ) );
+
+ msleep( p_sys->i_period_time >> 1 );
return;
}
i_snd_rc = snd_pcm_status( p_sys->p_snd_pcm, p_status );
if( i_snd_rc < 0 )
{
- msg_Err( p_aout,
- "unable to get the device's status after recovery (%s)",
- snd_strerror( i_snd_rc ) );
+ msg_Err( p_aout, "unable to get the device's status after "
+ "recovery (%s)", snd_strerror( i_snd_rc ) );
+
+ msleep( p_sys->i_period_time >> 1 );
return;
}
}
else
{
msg_Err( p_aout, "unable to recover from buffer underrun" );
+
+ msleep( p_sys->i_period_time >> 1 );
return;
}
- }
- /* Here the device should be either in the RUNNING state either in
- the PREPARE state. p_status is valid. */
+ /* Underrun, try to recover as quickly as possible */
+ next_date = mdate();
+ }
+ else
+ {
+ /* Here the device should be either in the RUNNING state.
+ * p_status is valid. */
- snd_pcm_status_get_tstamp( p_status, &ts_next );
- next_date = (mtime_t)ts_next.tv_sec * 1000000 + ts_next.tv_usec;
- next_date += (mtime_t)snd_pcm_status_get_delay(p_status)
- * 1000000 / p_aout->output.output.i_rate;
+ snd_pcm_status_get_tstamp( p_status, &ts_next );
+ next_date = (mtime_t)ts_next.tv_sec * 1000000 + ts_next.tv_usec;
+ next_date += (mtime_t)snd_pcm_status_get_delay(p_status)
+ * 1000000 / p_aout->output.output.i_rate;
+ }
p_buffer = aout_OutputNextBuffer( p_aout, next_date,
(p_aout->output.output.i_format ==
VLC_FOURCC('s','p','d','i')) );
- /* Audio output buffer shortage -> stop the fill process and
- wait in ALSAThread */
+ /* Audio output buffer shortage -> stop the fill process and wait */
if( p_buffer == NULL )
+ {
+ msleep( p_sys->i_period_time >> 1 );
return;
+ }
i_snd_rc = snd_pcm_writei( p_sys->p_snd_pcm, p_buffer->p_buffer,
p_buffer->i_nb_samples );
}
aout_BufferFree( p_buffer );
-
- msleep( p_sys->i_period_time >> 2 );
}
}
-