X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Faudio_output%2Falsa.c;h=e16a05f78543c0b3397431cfe6be22ec4db28e57;hb=62dd14548820fb0966e6b90d586183f74a427e4b;hp=6ce27545582685024f239079fe0ee2fb18ff8e96;hpb=e1aabc8fe10fe18d159c32ac2fb34d3744744927;p=vlc diff --git a/modules/audio_output/alsa.c b/modules/audio_output/alsa.c index 6ce2754558..e16a05f785 100644 --- a/modules/audio_output/alsa.c +++ b/modules/audio_output/alsa.c @@ -1,8 +1,8 @@ /***************************************************************************** * alsa.c : alsa plugin for vlc ***************************************************************************** - * Copyright (C) 2000-2001 VideoLAN - * $Id: alsa.c,v 1.34 2003/08/03 23:11:21 gbazin Exp $ + * Copyright (C) 2000-2001 the VideoLAN team + * $Id$ * * Authors: Henri Fallon - Original Author * Jeffrey Baker - Port to ALSA 1.0 API @@ -21,21 +21,23 @@ * * 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 /* ENOMEM */ -#include /* strerror() */ -#include /* calloc(), malloc(), free() */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif -#include +#include +#include -#include +#include /* ENOMEM */ +#include -#include "aout_internal.h" +#include /* ALSA part Note: we use the new API which is available since 0.9.0beta10a. */ @@ -43,6 +45,8 @@ #define ALSA_PCM_NEW_SW_PARAMS_API #include +/*#define ALSA_DEBUG*/ + /***************************************************************************** * aout_sys_t: ALSA audio output method descriptor ***************************************************************************** @@ -52,7 +56,7 @@ 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; @@ -63,6 +67,8 @@ struct aout_sys_t vlc_mutex_t lock; vlc_cond_t wait ; + + snd_pcm_status_t *p_status; }; #define A52_FRAME_NB 1536 @@ -70,7 +76,7 @@ struct aout_sys_t /* 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 << 8 ) #define ALSA_SPDIF_PERIOD_SIZE A52_FRAME_NB @@ -79,7 +85,7 @@ struct aout_sys_t /* Why not ? --Bozo */ /* Right. --Meuuh */ -#define DEFAULT_ALSA_DEVICE "default" +#define DEFAULT_ALSA_DEVICE N_("default") /***************************************************************************** * Local prototypes @@ -89,15 +95,24 @@ static void Close ( vlc_object_t * ); 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(); @@ -114,7 +129,7 @@ static void Probe( aout_instance_t * p_aout, 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 @@ -142,18 +157,21 @@ static void Probe( aout_instance_t * p_aout, if ( snd_pcm_hw_params_set_format( p_sys->p_snd_pcm, p_hw, *pi_snd_pcm_format ) < 0 ) { + int i_snd_rc = -1; + 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_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; } } @@ -187,7 +205,7 @@ static void Probe( aout_instance_t * p_aout, 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; @@ -197,6 +215,21 @@ static void Probe( aout_instance_t * p_aout, --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 ); } @@ -233,13 +266,14 @@ static void Probe( aout_instance_t * p_aout, 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 ); } @@ -273,23 +307,25 @@ static int Open( vlc_object_t *p_this ) 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; } @@ -322,7 +358,7 @@ static int Open( vlc_object_t *p_this ) /* 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; @@ -338,15 +374,6 @@ static int Open( vlc_object_t *p_this ) if ( var_Type( p_aout, "audio-device" ) == 0 ) { 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 ) @@ -356,13 +383,9 @@ static int Open( vlc_object_t *p_this ) 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 @@ -372,7 +395,6 @@ static int Open( vlc_object_t *p_this ) } 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; @@ -381,21 +403,19 @@ static int Open( vlc_object_t *p_this ) } 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; } @@ -404,13 +424,16 @@ static int Open( vlc_object_t *p_this ) #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; @@ -419,6 +442,7 @@ static int Open( vlc_object_t *p_this ) 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; @@ -427,17 +451,48 @@ static int Open( vlc_object_t *p_this ) } else { + int i; + 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 ) + /* 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 ); @@ -454,90 +509,132 @@ static int Open( vlc_object_t *p_this ) 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 @@ -545,7 +642,7 @@ static int Open( vlc_object_t *p_this ) &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)", @@ -560,6 +657,15 @@ static int Open( vlc_object_t *p_this ) 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 ) @@ -578,9 +684,9 @@ static int Open( vlc_object_t *p_this ) /* 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; } @@ -624,14 +730,17 @@ static void Close( vlc_object_t *p_this ) struct aout_sys_t * p_sys = p_aout->output.p_sys; int i_snd_rc; + /* Make sure that the thread will stop once it is waken up */ + vlc_object_kill( p_aout ); + /* 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; + p_aout->b_die = false; i_snd_rc = snd_pcm_close( p_sys->p_snd_pcm ); @@ -653,20 +762,28 @@ static void Close( vlc_object_t *p_this ) *****************************************************************************/ 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 && vlc_object_alive (p_aout) ) + vlc_cond_wait( &p_sys->wait, &p_sys->lock ); + vlc_mutex_unlock( &p_sys->lock ); + + if( !vlc_object_alive (p_aout) ) + goto cleanup; - mwait( p_aout->output.p_sys->start_date - AOUT_PTS_TOLERANCE / 4 ); + mwait( p_sys->start_date - AOUT_PTS_TOLERANCE / 4 ); - while ( !p_aout->b_die ) + while ( vlc_object_alive (p_aout) ) { ALSAFill( p_aout ); } +cleanup: + snd_pcm_drop( p_sys->p_snd_pcm ); + free( p_aout->output.p_sys->p_status ); return 0; } @@ -676,86 +793,239 @@ static int ALSAThread( aout_instance_t * p_aout ) 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( vlc_object_alive (p_aout) && vlc_object_alive (p_aout->p_libvlc) && + ( 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; + } } }