/*****************************************************************************
* alsa.c : alsa plugin for vlc
*****************************************************************************
- * Copyright (C) 2000-2001 VideoLAN
- * $Id: alsa.c,v 1.33 2003/07/27 16:20:53 gbazin Exp $
+ * Copyright (C) 2000-2001 the VideoLAN team
+ * $Id$
*
* Authors: Henri Fallon <henri@videolan.org> - Original Author
* Jeffrey Baker <jwbaker@acm.org> - Port to ALSA 1.0 API
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
-#include <errno.h> /* ENOMEM */
-#include <string.h> /* strerror() */
-#include <stdlib.h> /* calloc(), malloc(), free() */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
-#include <vlc/vlc.h>
+#include <vlc_common.h>
+#include <vlc_plugin.h>
-#include <vlc/aout.h>
+#include <errno.h> /* ENOMEM */
+#include <vlc_interface.h>
-#include "aout_internal.h"
+#include <vlc_aout.h>
/* ALSA part
Note: we use the new API which is available since 0.9.0beta10a. */
#define ALSA_PCM_NEW_SW_PARAMS_API
#include <alsa/asoundlib.h>
+/*#define ALSA_DEBUG*/
+
/*****************************************************************************
* aout_sys_t: ALSA audio output method descriptor
*****************************************************************************
struct aout_sys_t
{
snd_pcm_t * p_snd_pcm;
- int i_period_time;
+ unsigned int i_period_time;
#ifdef ALSA_DEBUG
snd_output_t * p_snd_stderr;
vlc_mutex_t lock;
vlc_cond_t wait ;
+
+ snd_pcm_status_t *p_status;
};
#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). */
+ 2 for int16_t). */
#define ALSA_DEFAULT_PERIOD_SIZE 1024
-#define ALSA_DEFAULT_BUFFER_SIZE ( ALSA_DEFAULT_PERIOD_SIZE << 4 )
+#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
static void Play ( aout_instance_t * );
static int ALSAThread ( aout_instance_t * );
static void ALSAFill ( aout_instance_t * );
+static int FindDevicesCallback( vlc_object_t *p_this, char const *psz_name,
+ vlc_value_t newval, vlc_value_t oldval, void *p_unused );
/*****************************************************************************
* Module descriptor
*****************************************************************************/
+static const char *const ppsz_devices[] = { "default" };
+static const char *const ppsz_devices_text[] = { N_("Default") };
vlc_module_begin();
- add_category_hint( N_("ALSA"), NULL, VLC_FALSE );
+ set_shortname( "ALSA" );
+ set_description( N_("ALSA audio output") );
+ set_category( CAT_AUDIO );
+ set_subcategory( SUBCAT_AUDIO_AOUT );
add_string( "alsadev", DEFAULT_ALSA_DEVICE, aout_FindAndRestart,
- N_("ALSA device name"), NULL, VLC_FALSE );
- set_description( _("ALSA audio output") );
+ N_("ALSA Device Name"), NULL, false );
+ change_string_list( ppsz_devices, ppsz_devices_text, FindDevicesCallback );
+ change_action_add( FindDevicesCallback, N_("Refresh list") );
+
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, text;
int i_ret;
var_Create ( p_aout, "audio-device", VLC_VAR_INTEGER | VLC_VAR_HASCHOICE );
- text.psz_string = _("Audio device");
+ text.psz_string = _("Audio Device");
var_Change( p_aout, "audio-device", VLC_VAR_SETTEXT, &text, NULL );
/* We'll open the audio device in non blocking mode so we can just exit
}
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 );
- var_Destroy( p_aout, "audio-device" );
- return;
+ int i_snd_rc = -1;
+
+ if( *pi_snd_pcm_format != SND_PCM_FORMAT_S16 )
+ {
+ *pi_snd_pcm_format = SND_PCM_FORMAT_S16;
+ i_snd_rc = snd_pcm_hw_params_set_format( p_sys->p_snd_pcm,
+ p_hw, *pi_snd_pcm_format );
+ }
+ if ( i_snd_rc < 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 ) )
{
break;
case 6:
val.i_int = AOUT_VAR_5_1;
- text.psz_string = N_("5.1");
+ text.psz_string = "5.1";
var_Change( p_aout, "audio-device",
VLC_VAR_ADDCHOICE, &val, &text );
break;
--i_channels;
}
+ /* Special case for mono on stereo only boards */
+ i_channels = aout_FormatNbChannels( &p_aout->output.output );
+ var_Change( p_aout, "audio-device", VLC_VAR_CHOICESCOUNT, &val, NULL );
+ if( val.i_int <= 0 && i_channels == 1 )
+ {
+ if ( !snd_pcm_hw_params_test_channels( p_sys->p_snd_pcm, p_hw, 2 ))
+ {
+ val.i_int = AOUT_VAR_STEREO;
+ text.psz_string = N_("Stereo");
+ var_Change( p_aout, "audio-device",
+ VLC_VAR_ADDCHOICE, &val, &text );
+ var_Set( p_aout, "audio-device", val );
+ }
+ }
+
/* Close the previously opened device */
snd_pcm_close( p_sys->p_snd_pcm );
}
if( val.i_int <= 0 )
{
/* Probe() has failed. */
+ msg_Dbg( p_aout, "failed to find a useable alsa configuration" );
var_Destroy( p_aout, "audio-device" );
return;
}
/* Add final settings to the variable */
var_AddCallback( p_aout, "audio-device", aout_ChannelsRestart, NULL );
- val.b_bool = VLC_TRUE;
+ val.b_bool = true;
var_Set( p_aout, "intf-change", val );
}
snd_pcm_sw_params_t *p_sw;
int i_snd_rc = -1;
+ unsigned int i_old_rate;
+ bool b_retry = true;
/* Allocate structures */
p_aout->output.p_sys = p_sys = malloc( sizeof( aout_sys_t ) );
if( p_sys == NULL )
- {
- msg_Err( p_aout, "out of memory" );
return VLC_ENOMEM;
- }
- p_sys->b_playing = VLC_FALSE;
+ p_sys->b_playing = false;
p_sys->start_date = 0;
vlc_cond_init( p_aout, &p_sys->wait );
- vlc_mutex_init( p_aout, &p_sys->lock );
+ vlc_mutex_init( &p_sys->lock );
/* Get device name */
if( (psz_device = config_GetPsz( p_aout, "alsadev" )) == NULL )
{
msg_Err( p_aout, "no audio device given (maybe \"default\" ?)" );
+ intf_UserFatal( p_aout, false, _("No Audio Device"),
+ _("No audio device name was given. You might want to " \
+ "enter \"default\".") );
free( p_sys );
return VLC_EGENERIC;
}
/* Choose the linear PCM format (read the comment above about FPU
and float32) */
- if( p_aout->p_libvlc->i_cpu & CPU_CAPABILITY_FPU )
+ if( vlc_CPU() & CPU_CAPABILITY_FPU )
{
i_vlc_pcm_format = VLC_FOURCC('f','l','3','2');
i_snd_pcm_format = SND_PCM_FORMAT_FLOAT;
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 );
}
if ( var_Get( p_aout, "audio-device", &val ) < 0 )
return VLC_EGENERIC;
}
- if ( val.i_int == AOUT_VAR_SPDIF )
- {
- p_aout->output.output.i_format = VLC_FOURCC('s','p','d','i');
- }
- else if ( val.i_int == AOUT_VAR_5_1 )
+ p_aout->output.output.i_format = i_vlc_pcm_format;
+ 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 ( 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 ( 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 ( 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
+ else if( val.i_int != AOUT_VAR_SPDIF )
{
/* This should not happen ! */
msg_Err( p_aout, "internal: can't find audio-device (%i)", val.i_int );
free( p_sys );
+ free( psz_device );
return VLC_EGENERIC;
}
#endif
/* Open the device */
- if ( AOUT_FMT_NON_LINEAR( &p_aout->output.output ) )
+ if ( val.i_int == AOUT_VAR_SPDIF )
{
if ( ( i_snd_rc = snd_pcm_open( &p_sys->p_snd_pcm, psz_iec_device,
SND_PCM_STREAM_PLAYBACK, 0 ) ) < 0 )
{
msg_Err( p_aout, "cannot open ALSA device `%s' (%s)",
psz_iec_device, snd_strerror( i_snd_rc ) );
+ intf_UserFatal( p_aout, false, _("Audio output failed"),
+ _("VLC could not open the ALSA device \"%s\" (%s)."),
+ psz_iec_device, snd_strerror( i_snd_rc ) );
free( p_sys );
free( psz_device );
return VLC_EGENERIC;
i_snd_pcm_format = SND_PCM_FORMAT_S16;
i_channels = 2;
+ i_vlc_pcm_format = VLC_FOURCC('s','p','d','i');
p_aout->output.i_nb_samples = i_period_size = ALSA_SPDIF_PERIOD_SIZE;
p_aout->output.output.i_bytes_per_frame = AOUT_SPDIF_SIZE;
p_aout->output.output.i_frame_length = A52_FRAME_NB;
}
else
{
- if ( ( i_snd_rc = snd_pcm_open( &p_sys->p_snd_pcm, psz_device,
- SND_PCM_STREAM_PLAYBACK, 0 ) ) < 0 )
+ int i;
+
+ msg_Dbg( p_aout, "opening ALSA device `%s'", psz_device );
+
+ /* Since it seems snd_pcm_close hasn't really released the device at
+ the time it returns, probe if the device is available in loop for 1s.
+ We cannot use blocking mode since the we would wait indefinitely when
+ switching from a dmx device to surround51. */
+
+ for( i = 10; i >= 0; i-- )
+ {
+ if ( ( i_snd_rc = snd_pcm_open( &p_sys->p_snd_pcm, psz_device,
+ SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK ) ) == -EBUSY )
+ {
+ if( i ) msleep( 100000 /* 100ms */ );
+ else
+ {
+ msg_Err( p_aout, "audio device: %s is already in use",
+ psz_device );
+ intf_UserFatal( p_aout, false, _("Audio output failed"),
+ _("The audio device \"%s\" is already in use."),
+ psz_device );
+ }
+ continue;
+ }
+ break;
+ }
+ if( i_snd_rc < 0 )
{
msg_Err( p_aout, "cannot open ALSA device `%s' (%s)",
psz_device, snd_strerror( i_snd_rc ) );
+ intf_UserFatal( p_aout, false, _("Audio output failed"),
+ _("VLC could not open the ALSA device \"%s\" (%s)."),
+ psz_device, snd_strerror( i_snd_rc ) );
free( p_sys );
free( psz_device );
return VLC_EGENERIC;
}
+
+ /* We want blocking mode */
+ snd_pcm_nonblock( p_sys->p_snd_pcm, 0 );
+
i_buffer_size = ALSA_DEFAULT_BUFFER_SIZE;
i_channels = aout_FormatNbChannels( &p_aout->output.output );
snd_pcm_hw_params_alloca(&p_hw);
snd_pcm_sw_params_alloca(&p_sw);
- /* Get Initial hardware parameters */
- if ( ( i_snd_rc = snd_pcm_hw_params_any( p_sys->p_snd_pcm, p_hw ) ) < 0 )
+ /* Due to some bugs in alsa with some drivers, we need to retry in s16l
+ if snd_pcm_hw_params fails in fl32 */
+ while ( b_retry )
{
- msg_Err( p_aout, "unable to retrieve initial hardware parameters (%s)",
+ b_retry = false;
+
+ /* Get Initial hardware parameters */
+ if ( ( i_snd_rc = snd_pcm_hw_params_any( p_sys->p_snd_pcm, p_hw ) ) < 0 )
+ {
+ msg_Err( p_aout, "unable to retrieve initial hardware parameters (%s)",
snd_strerror( i_snd_rc ) );
- goto error;
- }
+ goto error;
+ }
- /* Set format. */
- if ( ( i_snd_rc = snd_pcm_hw_params_set_format( p_sys->p_snd_pcm, p_hw,
+ /* Set format. */
+ if ( ( i_snd_rc = snd_pcm_hw_params_set_format( p_sys->p_snd_pcm, p_hw,
i_snd_pcm_format ) ) < 0 )
- {
- msg_Err( p_aout, "unable to set stream sample size and word order (%s)",
- snd_strerror( i_snd_rc ) );
- goto error;
- }
+ {
+ if( i_snd_pcm_format != SND_PCM_FORMAT_S16 )
+ {
+ i_snd_pcm_format = SND_PCM_FORMAT_S16;
+ i_snd_rc = snd_pcm_hw_params_set_format( p_sys->p_snd_pcm,
+ p_hw, i_snd_pcm_format );
+ }
+ if ( i_snd_rc < 0 )
+ {
+ msg_Err( p_aout, "unable to set stream sample size and "
+ "word order (%s)", snd_strerror( i_snd_rc ) );
+ goto error;
+ }
+ }
+ if( i_vlc_pcm_format != VLC_FOURCC('s','p','d','i') )
+ 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;
+ }
+ p_aout->output.output.i_format = i_vlc_pcm_format;
- if ( ( i_snd_rc = snd_pcm_hw_params_set_access( p_sys->p_snd_pcm, p_hw,
+ if ( ( i_snd_rc = snd_pcm_hw_params_set_access( p_sys->p_snd_pcm, p_hw,
SND_PCM_ACCESS_RW_INTERLEAVED ) ) < 0 )
- {
- msg_Err( p_aout, "unable to set interleaved stream format (%s)",
- snd_strerror( i_snd_rc ) );
- goto error;
- }
+ {
+ msg_Err( p_aout, "unable to set interleaved stream format (%s)",
+ snd_strerror( i_snd_rc ) );
+ goto error;
+ }
- /* Set channels. */
- if ( ( i_snd_rc = snd_pcm_hw_params_set_channels( p_sys->p_snd_pcm, p_hw,
+ /* Set channels. */
+ if ( ( i_snd_rc = snd_pcm_hw_params_set_channels( p_sys->p_snd_pcm, p_hw,
i_channels ) ) < 0 )
- {
- msg_Err( p_aout, "unable to set number of output channels (%s)",
- snd_strerror( i_snd_rc ) );
- goto error;
- }
+ {
+ msg_Err( p_aout, "unable to set number of output channels (%s)",
+ snd_strerror( i_snd_rc ) );
+ goto error;
+ }
- /* Set rate. */
+ /* Set rate. */
+ i_old_rate = p_aout->output.output.i_rate;
#ifdef HAVE_ALSA_NEW_API
- if ( ( i_snd_rc = snd_pcm_hw_params_set_rate_near( p_sys->p_snd_pcm, p_hw,
- &p_aout->output.output.i_rate, NULL ) ) < 0 )
+ i_snd_rc = snd_pcm_hw_params_set_rate_near( p_sys->p_snd_pcm, p_hw,
+ &p_aout->output.output.i_rate,
+ NULL );
#else
- if ( ( i_snd_rc = snd_pcm_hw_params_set_rate_near( p_sys->p_snd_pcm, p_hw,
- p_aout->output.output.i_rate, NULL ) ) < 0 )
+ i_snd_rc = snd_pcm_hw_params_set_rate_near( p_sys->p_snd_pcm, p_hw,
+ p_aout->output.output.i_rate,
+ NULL );
#endif
- {
- 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;
- }
+ if( i_snd_rc < 0 || p_aout->output.output.i_rate != i_old_rate )
+ {
+ msg_Warn( p_aout, "The rate %d Hz is not supported by your " \
+ "hardware. Using %d Hz instead.\n", i_old_rate, \
+ p_aout->output.output.i_rate );
+ }
- /* Set buffer size. */
+ /* Set period size. */
#ifdef HAVE_ALSA_NEW_API
- if ( ( i_snd_rc = snd_pcm_hw_params_set_buffer_size_near( p_sys->p_snd_pcm,
- p_hw, &i_buffer_size ) ) < 0 )
+ if ( ( i_snd_rc = snd_pcm_hw_params_set_period_size_near( p_sys->p_snd_pcm,
+ p_hw, &i_period_size, NULL ) ) < 0 )
#else
- if ( ( i_snd_rc = snd_pcm_hw_params_set_buffer_size_near( p_sys->p_snd_pcm,
- p_hw, i_buffer_size ) ) < 0 )
+ if ( ( i_snd_rc = snd_pcm_hw_params_set_period_size_near( p_sys->p_snd_pcm,
+ p_hw, i_period_size, NULL ) ) < 0 )
#endif
- {
- msg_Err( p_aout, "unable to set buffer size (%s)",
+ {
+ msg_Err( p_aout, "unable to set period size (%s)",
snd_strerror( i_snd_rc ) );
- goto error;
- }
+ goto error;
+ }
+ p_aout->output.i_nb_samples = i_period_size;
- /* Set period size. */
+/* Set buffer size. */
#ifdef HAVE_ALSA_NEW_API
- if ( ( i_snd_rc = snd_pcm_hw_params_set_period_size_near( p_sys->p_snd_pcm,
- p_hw, &i_period_size, NULL ) ) < 0 )
+ if ( ( i_snd_rc = snd_pcm_hw_params_set_buffer_size_near( p_sys->p_snd_pcm,
+ p_hw, &i_buffer_size ) ) < 0 )
#else
- if ( ( i_snd_rc = snd_pcm_hw_params_set_period_size_near( p_sys->p_snd_pcm,
- p_hw, i_period_size, NULL ) ) < 0 )
+ if ( ( i_snd_rc = snd_pcm_hw_params_set_buffer_size_near( p_sys->p_snd_pcm,
+ p_hw, i_buffer_size ) ) < 0 )
#endif
- {
- msg_Err( p_aout, "unable to set period size (%s)",
+ {
+ msg_Err( p_aout, "unable to set buffer size (%s)",
snd_strerror( i_snd_rc ) );
- goto error;
- }
- p_aout->output.i_nb_samples = i_period_size;
+ goto error;
+ }
- /* Commit hardware parameters. */
- if ( ( i_snd_rc = snd_pcm_hw_params( p_sys->p_snd_pcm, p_hw ) ) < 0 )
- {
- msg_Err( p_aout, "unable to commit hardware configuration (%s)",
+ /* Commit hardware parameters. */
+ if ( ( i_snd_rc = snd_pcm_hw_params( p_sys->p_snd_pcm, p_hw ) ) < 0 )
+ {
+ if ( b_retry == false &&
+ i_snd_pcm_format == SND_PCM_FORMAT_FLOAT)
+ {
+ b_retry = true;
+ i_snd_pcm_format = SND_PCM_FORMAT_S16;
+ p_aout->output.output.i_format = AOUT_FMT_S16_NE;
+ msg_Warn( p_aout, "unable to commit hardware configuration "
+ "with fl32 samples. Retrying with s16l (%s)", snd_strerror( i_snd_rc ) );
+ }
+ else
+ {
+ msg_Err( p_aout, "unable to commit hardware configuration (%s)",
snd_strerror( i_snd_rc ) );
- goto error;
+ goto error;
+ }
+ }
}
#ifdef HAVE_ALSA_NEW_API
&p_sys->i_period_time, NULL ) ) < 0 )
#else
if( ( p_sys->i_period_time =
- snd_pcm_hw_params_get_period_time( p_hw, NULL ) ) < 0 )
+ (int)snd_pcm_hw_params_get_period_time( p_hw, NULL ) ) < 0 )
#endif
{
msg_Err( p_aout, "unable to get period time (%s)",
i_snd_rc = snd_pcm_sw_params_set_avail_min( p_sys->p_snd_pcm, p_sw,
p_aout->output.i_nb_samples );
+ /* start playing when one period has been written */
+ i_snd_rc = snd_pcm_sw_params_set_start_threshold( p_sys->p_snd_pcm, p_sw,
+ ALSA_DEFAULT_PERIOD_SIZE);
+ if( i_snd_rc < 0 )
+ {
+ msg_Err( p_aout, "unable to set start threshold (%s)",
+ snd_strerror( i_snd_rc ) );
+ goto error;
+ }
/* Commit software parameters. */
if ( snd_pcm_sw_params( p_sys->p_snd_pcm, p_sw ) < 0 )
/* Create ALSA thread and wait for its readiness. */
if( vlc_thread_create( p_aout, "aout", ALSAThread,
- VLC_THREAD_PRIORITY_OUTPUT, VLC_FALSE ) )
+ VLC_THREAD_PRIORITY_OUTPUT, false ) )
{
- msg_Err( p_aout, "cannot create ALSA thread (%s)", strerror(errno) );
+ msg_Err( p_aout, "cannot create ALSA thread (%m)" );
goto error;
}
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_object_kill( p_aout );
vlc_thread_join( p_aout );
- p_aout->b_die = VLC_FALSE;
+ p_aout->b_die = false;
i_snd_rc = snd_pcm_close( p_sys->p_snd_pcm );
*****************************************************************************/
static int ALSAThread( aout_instance_t * p_aout )
{
+ struct aout_sys_t * p_sys = p_aout->output.p_sys;
+ p_sys->p_status = (snd_pcm_status_t *)malloc(snd_pcm_status_sizeof());
+
/* 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 );
+ vlc_mutex_lock( &p_sys->lock );
+ while( !p_sys->start_date && !p_aout->b_die )
+ vlc_cond_wait( &p_sys->wait, &p_sys->lock );
+ vlc_mutex_unlock( &p_sys->lock );
- mwait( p_aout->output.p_sys->start_date - AOUT_PTS_TOLERANCE / 4 );
+ if( p_aout->b_die )
+ goto cleanup;
+
+ mwait( p_sys->start_date - AOUT_PTS_TOLERANCE / 4 );
while ( !p_aout->b_die )
{
ALSAFill( p_aout );
}
+cleanup:
+ snd_pcm_drop( p_sys->p_snd_pcm );
+ free( p_aout->output.p_sys->p_status );
return 0;
}
static void ALSAFill( aout_instance_t * p_aout )
{
struct aout_sys_t * p_sys = p_aout->output.p_sys;
-
aout_buffer_t * p_buffer;
- snd_pcm_status_t * p_status;
- snd_timestamp_t ts_next;
+ snd_pcm_status_t * p_status = p_sys->p_status;
int i_snd_rc;
mtime_t next_date;
- snd_pcm_status_alloca( &p_status );
-
/* Fill in the buffer until space or audio output buffer shortage */
+
+ /* Get the status */
+ i_snd_rc = snd_pcm_status( p_sys->p_snd_pcm, p_status );
+ if( i_snd_rc < 0 )
{
- /* Get the status */
+ msg_Err( p_aout, "cannot get device status" );
+ goto error;
+ }
+
+ /* Handle buffer underruns and get the status again */
+ if( snd_pcm_status_get_state( p_status ) == SND_PCM_STATE_XRUN )
+ {
+ /* Prepare the device */
+ i_snd_rc = snd_pcm_prepare( p_sys->p_snd_pcm );
+
+ if( i_snd_rc )
+ {
+ msg_Err( p_aout, "cannot recover from buffer underrun" );
+ goto error;
+ }
+
+ msg_Dbg( p_aout, "recovered from buffer underrun" );
+
+ /* Get the new status */
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 (%s)",
- snd_strerror( i_snd_rc ) );
-
- msleep( p_sys->i_period_time >> 1 );
- return;
+ msg_Err( p_aout, "cannot get device status after recovery" );
+ goto error;
}
- /* Handle buffer underruns and reget the status */
- if( snd_pcm_status_get_state( p_status ) == SND_PCM_STATE_XRUN )
+ /* Underrun, try to recover as quickly as possible */
+ next_date = mdate();
+ }
+ else
+ {
+ /* Here the device should be in RUNNING state, p_status is valid. */
+ snd_pcm_sframes_t delay = snd_pcm_status_get_delay( p_status );
+ if( delay == 0 ) /* workaround buggy alsa drivers */
+ if( snd_pcm_delay( p_sys->p_snd_pcm, &delay ) < 0 )
+ delay = 0; /* FIXME: use a positive minimal delay */
+ int i_bytes = snd_pcm_frames_to_bytes( p_sys->p_snd_pcm, delay );
+ next_date = mdate() + ( (mtime_t)i_bytes * 1000000
+ / p_aout->output.output.i_bytes_per_frame
+ / p_aout->output.output.i_rate
+ * p_aout->output.output.i_frame_length );
+
+#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 );
+
+ msg_Dbg( p_aout, "Delay is %ld frames (%d bytes)", delay, i_bytes );
+
+ msg_Dbg( p_aout, "Bytes per frame: %d", p_aout->output.output.i_bytes_per_frame );
+ msg_Dbg( p_aout, "Rate: %d", p_aout->output.output.i_rate );
+ msg_Dbg( p_aout, "Frame length: %d", p_aout->output.output.i_frame_length );
+
+ msg_Dbg( p_aout, "Next date is in %d microseconds", (int)(next_date - mdate()) );
+#endif
+ }
+
+ 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 */
+ if( p_buffer == NULL )
+ goto error;
+
+ for (;;)
+ {
+ i_snd_rc = snd_pcm_writei( p_sys->p_snd_pcm, p_buffer->p_buffer,
+ p_buffer->i_nb_samples );
+ if( i_snd_rc != -ESTRPIPE )
+ break;
+
+ /* a suspend event occurred
+ * (stream is suspended and waiting for an application recovery) */
+ msg_Dbg( p_aout, "entering in suspend mode, trying to resume..." );
+
+ while( !p_aout->b_die && !p_aout->p_libvlc->b_die &&
+ ( i_snd_rc = snd_pcm_resume( p_sys->p_snd_pcm ) ) == -EAGAIN )
{
- /* Prepare the device */
+ msleep( 1000000 );
+ }
+
+ if( i_snd_rc < 0 )
+ /* Device does not supprot resuming, restart it */
i_snd_rc = snd_pcm_prepare( p_sys->p_snd_pcm );
- if( i_snd_rc == 0 )
- {
- msg_Warn( p_aout, "recovered from buffer underrun" );
+ }
- /* Reget the status */
- 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 ) );
+ if( i_snd_rc < 0 )
+ msg_Err( p_aout, "cannot write: %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" );
+ aout_BufferFree( p_buffer );
+ return;
- msleep( p_sys->i_period_time >> 1 );
- return;
- }
- }
+error:
+ if( i_snd_rc < 0 )
+ msg_Err( p_aout, "ALSA error: %s", snd_strerror( i_snd_rc ) );
+ msleep( p_sys->i_period_time >> 1 );
+}
- /* Here the device should be either in the RUNNING state either in
- the PREPARE state. p_status is valid. */
+static void GetDevicesForCard( module_config_t *p_item, int i_card );
+static void GetDevices( module_config_t *p_item );
- 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;
+/*****************************************************************************
+ * 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;
+ int i;
- p_buffer = aout_OutputNextBuffer( p_aout, next_date,
- (p_aout->output.output.i_format ==
- VLC_FOURCC('s','p','d','i')) );
+ p_item = config_FindConfig( p_this, psz_name );
+ if( !p_item ) return VLC_SUCCESS;
- /* Audio output buffer shortage -> stop the fill process and wait */
- if( p_buffer == NULL )
+ /* Clear-up the current list */
+ if( p_item->i_list )
+ {
+ /* Keep the first entrie */
+ for( i = 1; i < p_item->i_list; i++ )
{
- msleep( p_sys->i_period_time >> 1 );
- return;
+ free( (char *)p_item->ppsz_list[i] );
+ free( (char *)p_item->ppsz_list_text[i] );
}
+ /* TODO: Remove when no more needed */
+ p_item->ppsz_list[i] = NULL;
+ p_item->ppsz_list_text[i] = NULL;
+ }
+ p_item->i_list = 1;
- i_snd_rc = snd_pcm_writei( p_sys->p_snd_pcm, p_buffer->p_buffer,
- p_buffer->i_nb_samples );
+ GetDevices( p_item );
- if( i_snd_rc < 0 )
+ /* Signal change to the interface */
+ p_item->b_dirty = true;
+
+ return VLC_SUCCESS;
+
+}
+
+
+static void GetDevicesForCard( module_config_t *p_item, int i_card )
+{
+ int i_pcm_device = -1;
+ int i_err = 0;
+ snd_pcm_info_t *p_pcm_info;
+ snd_ctl_t *p_ctl;
+ char psz_dev[64];
+ char *psz_card_name;
+
+ sprintf( psz_dev, "hw:%i", i_card );
+
+ if( ( i_err = snd_ctl_open( &p_ctl, psz_dev, 0 ) ) < 0 )
+ return;
+
+ if( ( i_err = snd_card_get_name( i_card, &psz_card_name ) ) != 0)
+ psz_card_name = _("Unknown soundcard");
+
+ snd_pcm_info_alloca( &p_pcm_info );
+
+ for (;;)
+ {
+ char *psz_device, *psz_descr;
+ if( ( i_err = snd_ctl_pcm_next_device( p_ctl, &i_pcm_device ) ) < 0 )
+ i_pcm_device = -1;
+ if( i_pcm_device < 0 )
+ break;
+
+ snd_pcm_info_set_device( p_pcm_info, i_pcm_device );
+ snd_pcm_info_set_subdevice( p_pcm_info, 0 );
+ snd_pcm_info_set_stream( p_pcm_info, SND_PCM_STREAM_PLAYBACK );
+
+ if( ( i_err = snd_ctl_pcm_info( p_ctl, p_pcm_info ) ) < 0 )
{
- msg_Err( p_aout, "write failed (%s)",
- snd_strerror( i_snd_rc ) );
+ if( i_err != -ENOENT )
+ {
+ /*printf( "get_devices_for_card(): "
+ "snd_ctl_pcm_info() "
+ "failed (%d:%d): %s.\n", i_card,
+ i_pcm_device, snd_strerror( -i_err ) );*/
+ }
+ continue;
}
- aout_BufferFree( p_buffer );
+ asprintf( &psz_device, "hw:%d,%d", i_card, i_pcm_device );
+ asprintf( &psz_descr, "%s: %s (%s)", psz_card_name,
+ snd_pcm_info_get_name(p_pcm_info), psz_device );
+
+ p_item->ppsz_list =
+ (const char **)realloc( p_item->ppsz_list,
+ (p_item->i_list + 2) * sizeof(char *) );
+ p_item->ppsz_list_text =
+ (const char **)realloc( p_item->ppsz_list_text,
+ (p_item->i_list + 2) * sizeof(char *) );
+ p_item->ppsz_list[ p_item->i_list ] = psz_device;
+ p_item->ppsz_list_text[ p_item->i_list ] = psz_descr;
+ p_item->i_list++;
+ p_item->ppsz_list[ p_item->i_list ] = NULL;
+ p_item->ppsz_list_text[ p_item->i_list ] = NULL;
+ }
+
+ snd_ctl_close( p_ctl );
+}
+
+
+
+static void GetDevices( module_config_t *p_item )
+{
+ int i_card = -1;
+ int i_err = 0;
+
+ if( ( i_err = snd_card_next( &i_card ) ) != 0 )
+ {
+ /*printf( "snd_card_next() failed: %s", snd_strerror( -i_err ) );*/
+ return;
+ }
+
+ while( i_card > -1 )
+ {
+ GetDevicesForCard( p_item, i_card );
+ if( ( i_err = snd_card_next( &i_card ) ) != 0 )
+ {
+ /*printf( "snd_card_next() failed: %s", snd_strerror( -i_err ) );*/
+ break;
+ }
}
}