X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Faudio_output%2Falsa.c;h=7f841fc6dbf67cfbc7f53b4c2a9820778df2f768;hb=0597403277c49d3fc23da19fb6f6954e984bae86;hp=47d1df2a7e3bd7c678a5abf197b9553edfe913f9;hpb=110cfc0d7c5611a86f5b3cdc5d4aa659e7a734be;p=vlc diff --git a/modules/audio_output/alsa.c b/modules/audio_output/alsa.c index 47d1df2a7e..7f841fc6db 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.17 2002/12/23 17:22:46 bozo Exp $ + * Copyright (C) 2000-2001 the VideoLAN team + * $Id$ * * Authors: Henri Fallon - Original Author * Jeffrey Baker - Port to ALSA 1.0 API @@ -13,7 +13,7 @@ * 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. - * + * * 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 @@ -21,28 +21,32 @@ * * 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.0rc4. */ + Note: we use the new API which is available since 0.9.0beta10a. */ #define ALSA_PCM_NEW_HW_PARAMS_API #define ALSA_PCM_NEW_SW_PARAMS_API #include +/*#define ALSA_DEBUG*/ + /***************************************************************************** * aout_sys_t: ALSA audio output method descriptor ***************************************************************************** @@ -52,11 +56,19 @@ struct aout_sys_t { snd_pcm_t * p_snd_pcm; - int i_period_time; + unsigned 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 ; + + snd_pcm_status_t *p_status; }; #define A52_FRAME_NB 1536 @@ -64,65 +76,71 @@ 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). */ -#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 *****************************************************************************/ -static int Open ( vlc_object_t * ); -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 Open ( vlc_object_t * ); +static void Close ( vlc_object_t * ); +static void Play ( aout_instance_t * ); +static void* ALSAThread ( vlc_object_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 *****************************************************************************/ -vlc_module_begin(); - add_category_hint( N_("ALSA"), NULL ); - add_string( "alsadev", DEFAULT_ALSA_DEVICE, aout_FindAndRestart, - N_("ALSA device name"), NULL ); - set_description( _("ALSA audio module") ); - set_capability( "audio output", 50 ); - set_callbacks( Open, Close ); -vlc_module_end(); +static const char *const ppsz_devices[] = { "default" }; +static const char *const ppsz_devices_text[] = { N_("Default") }; +vlc_module_begin () + set_shortname( "ALSA" ) + set_description( N_("ALSA audio output") ) + set_category( CAT_AUDIO ) + set_subcategory( SUBCAT_AUDIO_AOUT ) + add_string( "alsa-audio-device", DEFAULT_ALSA_DEVICE, aout_FindAndRestart, + N_("ALSA Device Name"), NULL, false ); + add_deprecated_alias( "alsadev" ) /* deprecated since 0.9.3 */ + 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 () /***************************************************************************** * Probe: probe the audio device for available formats and channels *****************************************************************************/ 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 ( !snd_pcm_open( &p_sys->p_snd_pcm, psz_iec_device, - SND_PCM_STREAM_PLAYBACK, 0 ) ) - { - val.psz_string = N_("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; @@ -133,71 +151,131 @@ static void Probe( aout_instance_t * p_aout, 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; + 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 > 1 ) + 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 = (char*)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 = (char*)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 = (char*)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 = (char*)"5.1"; var_Change( p_aout, "audio-device", - VLC_VAR_ADDCHOICE, &val ); + 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 = (char*)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 ); } + 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 = (char*)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. */ + 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 = true; + var_Set( p_aout, "intf-change", val ); } /***************************************************************************** @@ -230,19 +308,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 = false; + p_sys->start_date = 0; + vlc_cond_init( &p_sys->wait ); + vlc_mutex_init( &p_sys->lock ); /* Get device name */ - if( (psz_device = config_GetPsz( p_aout, "alsadev" )) == NULL ) + if( (psz_device = config_GetPsz( p_aout, "alsa-audio-device" )) == 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; } @@ -275,7 +359,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; @@ -290,7 +374,7 @@ static int Open( vlc_object_t *p_this ) 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 ) @@ -300,51 +384,57 @@ static int Open( vlc_object_t *p_this ) return VLC_EGENERIC; } - if ( !strcmp( val.psz_string, N_("S/PDIF") ) ) + p_aout->output.output.i_format = i_vlc_pcm_format; + if ( val.i_int == AOUT_VAR_5_1 ) { - p_aout->output.output.i_format = VLC_FOURCC('s','p','d','i'); - } - else if ( !strcmp( val.psz_string, N_("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, "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, "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 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; + } - free( val.psz_string ); - -#ifdef DEBUG +#ifdef ALSA_DEBUG snd_output_stdio_attach( &p_sys->p_snd_stderr, stderr, 0 ); #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; @@ -353,6 +443,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; @@ -361,15 +452,48 @@ static int Open( vlc_object_t *p_this ) } 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 ); @@ -386,78 +510,141 @@ 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. */ - 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 ) - { - msg_Err( p_aout, "unable to set sample rate (%s)", - snd_strerror( i_snd_rc ) ); - goto error; - } + /* Set rate. */ + i_old_rate = p_aout->output.output.i_rate; +#ifdef HAVE_ALSA_NEW_API + 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 + 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 + 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. */ - if ( ( i_snd_rc = snd_pcm_hw_params_set_buffer_size_near( p_sys->p_snd_pcm, - p_hw, &i_buffer_size ) ) < 0 ) - { - msg_Err( p_aout, "unable to set buffer size (%s)", + /* Set period 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 ) +#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 ) +#endif + { + 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. */ - if ( ( i_snd_rc = snd_pcm_hw_params_set_period_size_near( p_sys->p_snd_pcm, - p_hw, &i_period_size, NULL ) ) < 0 ) - { - msg_Err( p_aout, "unable to set period size (%s)", +/* Set buffer 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 ) +#else + 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 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 if( ( i_snd_rc = snd_pcm_hw_params_get_period_time( p_hw, &p_sys->i_period_time, NULL ) ) < 0 ) +#else + if( ( p_sys->i_period_time = + (int)snd_pcm_hw_params_get_period_time( p_hw, NULL ) ) < 0 ) +#endif { msg_Err( p_aout, "unable to get period time (%s)", snd_strerror( i_snd_rc ) ); @@ -471,6 +658,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 ) @@ -479,7 +675,7 @@ static int Open( vlc_object_t *p_this ) 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" ); @@ -489,9 +685,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 ) ) { - msg_Err( p_aout, "cannot create ALSA thread (%s)", strerror(errno) ); + msg_Err( p_aout, "cannot create ALSA thread (%m)" ); goto error; } @@ -499,7 +695,7 @@ static int Open( vlc_object_t *p_this ) 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 ); @@ -511,6 +707,19 @@ error: *****************************************************************************/ 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 ); + } } /***************************************************************************** @@ -522,9 +731,17 @@ static void Close( vlc_object_t *p_this ) struct aout_sys_t * p_sys = p_aout->output.p_sys; int i_snd_rc; - p_aout->b_die = VLC_TRUE; + /* 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 ); + + /* */ 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 ); @@ -534,7 +751,7 @@ static void Close( vlc_object_t *p_this ) snd_strerror( i_snd_rc ) ); } -#ifdef DEBUG +#ifdef ALSA_DEBUG snd_output_close( p_sys->p_snd_stderr ); #endif @@ -544,24 +761,34 @@ static void Close( vlc_object_t *p_this ) /***************************************************************************** * ALSAThread: asynchronous thread used to DMA the data to the device *****************************************************************************/ -static int ALSAThread( aout_instance_t * p_aout ) +static void* ALSAThread( vlc_object_t* p_this ) { + aout_instance_t * p_aout = (aout_instance_t*)p_this; struct aout_sys_t * p_sys = p_aout->output.p_sys; + int canc = vlc_savecancel (); + p_sys->p_status = (snd_pcm_status_t *)malloc(snd_pcm_status_sizeof()); - while ( !p_aout->b_die ) - { - ALSAFill( p_aout ); + /* Wait for the exact time to start playing (avoids resampling) */ + 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 ); - /* Sleep during less than one period to avoid a lot of buffer - underruns */ + if( !vlc_object_alive (p_aout) ) + goto cleanup; - /* Why do we need to sleep ? --Meuuh */ - /* Maybe because I don't want to eat all the cpu by looping - all the time. --Bozo */ - msleep( p_sys->i_period_time >> 1 ); + mwait( p_sys->start_date - AOUT_PTS_TOLERANCE / 4 ); + + while ( vlc_object_alive (p_aout) ) + { + ALSAFill( p_aout ); } - return 0; +cleanup: + snd_pcm_drop( p_sys->p_snd_pcm ); + free( p_aout->output.p_sys->p_status ); + vlc_restorecancel (canc); + return NULL; } /***************************************************************************** @@ -570,93 +797,246 @@ 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 */ - /* 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 ); + /* Get the status */ + i_snd_rc = snd_pcm_status( p_sys->p_snd_pcm, p_status ); if( i_snd_rc < 0 ) { - msg_Err( p_aout, "ALSA device not ready !!! (%s)", - snd_strerror( i_snd_rc ) ); - return; + msg_Err( p_aout, "cannot get device status" ); + goto error; } - /* Fill in the buffer until space or audio output buffer shortage */ - for ( ; ; ) + /* Handle buffer underruns and get the status again */ + if( snd_pcm_status_get_state( p_status ) == SND_PCM_STATE_XRUN ) { - /* Get the status */ + /* 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 ) ); - 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 ) ); - return; - } - } - else - { - msg_Err( p_aout, "unable to recover from buffer underrun" ); - return; - } + if( i_snd_rc < 0 ) + msg_Err( p_aout, "cannot write: %s", snd_strerror( i_snd_rc ) ); + + aout_BufferFree( p_buffer ); + 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 ); +} + +static void GetDevicesForCard( module_config_t *p_item, int i_card ); +static void GetDevices( module_config_t *p_item ); + +/***************************************************************************** + * 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; + (void)newval; + (void)oldval; + (void)p_unused; + + p_item = config_FindConfig( p_this, psz_name ); + if( !p_item ) return VLC_SUCCESS; + + /* Clear-up the current list */ + if( p_item->i_list ) + { + /* Keep the first entrie */ + for( i = 1; i < p_item->i_list; i++ ) + { + 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; - /* Here the device should be either in the RUNNING state either in - the PREPARE state. p_status is valid. */ + GetDevices( 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; + /* Signal change to the interface */ + p_item->b_dirty = true; - p_buffer = aout_OutputNextBuffer( p_aout, next_date, - (p_aout->output.output.i_format == - VLC_FOURCC('s','p','d','i')) ); + return VLC_SUCCESS; +} - /* Audio output buffer shortage -> stop the fill process and - wait in ALSAThread */ - if( p_buffer == NULL ) - return; - i_snd_rc = snd_pcm_writei( p_sys->p_snd_pcm, p_buffer->p_buffer, - p_buffer->i_nb_samples ); +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; - if( i_snd_rc < 0 ) + 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 ); + if( asprintf( &psz_device, "hw:%d,%d", i_card, i_pcm_device ) == -1 ) + break; + if( asprintf( &psz_descr, "%s: %s (%s)", psz_card_name, + snd_pcm_info_get_name(p_pcm_info), psz_device ) == -1 ) + { + free( psz_device ); + break; + } - msleep( p_sys->i_period_time >> 2 ); + p_item->ppsz_list = + (char **)realloc( p_item->ppsz_list, + (p_item->i_list + 2) * sizeof(char *) ); + p_item->ppsz_list_text = + (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; + } + } +}