]> git.sesse.net Git - vlc/blobdiff - modules/audio_output/alsa.c
Merge branch 'master' of git@git.videolan.org:vlc
[vlc] / modules / audio_output / alsa.c
index 7509bb5bd664137263166e7e60bac916cf37a34a..4380a170f7d7baf7ae430f72d83376b0c6cd4f89 100644 (file)
  *
  * 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/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. */
@@ -43,6 +44,8 @@
 #define ALSA_PCM_NEW_SW_PARAMS_API
 #include <alsa/asoundlib.h>
 
+/*#define ALSA_DEBUG*/
+
 /*****************************************************************************
  * aout_sys_t: ALSA audio output method descriptor
  *****************************************************************************
@@ -97,15 +100,15 @@ static int FindDevicesCallback( vlc_object_t *p_this, char const *psz_name,
 /*****************************************************************************
  * Module descriptor
  *****************************************************************************/
-static char *ppsz_devices[] = { "default" };
-static char *ppsz_devices_text[] = { N_("Default") };
+static const char *ppsz_devices[] = { "default" };
+static const char *ppsz_devices_text[] = { N_("Default") };
 vlc_module_begin();
     set_shortname( "ALSA" );
     set_description( _("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 );
+                N_("ALSA Device Name"), NULL, false );
         change_string_list( ppsz_devices, ppsz_devices_text, FindDevicesCallback );
         change_action_add( FindDevicesCallback, N_("Refresh list") );
 
@@ -201,7 +204,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;
@@ -212,7 +215,7 @@ static void Probe( aout_instance_t * p_aout,
         }
 
         /* Special case for mono on stereo only boards */
-        i_channels = aout_FormatNbChannels( &p_aout->output.output );        
+        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 )
         {
@@ -225,7 +228,7 @@ static void Probe( aout_instance_t * p_aout,
                 var_Set( p_aout, "audio-device", val );
             }
         }
-        
+
         /* Close the previously opened device */
         snd_pcm_close( p_sys->p_snd_pcm );
     }
@@ -269,7 +272,7 @@ static void Probe( aout_instance_t * p_aout,
 
     /* 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 );
 }
 
@@ -304,6 +307,7 @@ static int Open( vlc_object_t *p_this )
 
     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 ) );
@@ -312,9 +316,8 @@ static int Open( vlc_object_t *p_this )
         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;
-    p_sys->p_status = (snd_pcm_status_t *)malloc(snd_pcm_status_sizeof());
     vlc_cond_init( p_aout, &p_sys->wait );
     vlc_mutex_init( p_aout, &p_sys->lock );
 
@@ -322,6 +325,9 @@ static int Open( vlc_object_t *p_this )
     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;
     }
@@ -354,12 +360,12 @@ 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;
     }
-    else */
+    else
     {
         i_vlc_pcm_format = AOUT_FMT_S16_NE;
         i_snd_pcm_format = SND_PCM_FORMAT_S16;
@@ -406,7 +412,7 @@ static int Open( vlc_object_t *p_this )
     {
         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 );
@@ -427,6 +433,9 @@ static int Open( vlc_object_t *p_this )
         {
             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;
@@ -448,7 +457,7 @@ static int Open( vlc_object_t *p_this )
 
         msg_Dbg( p_aout, "opening ALSA device `%s'", psz_device );
 
-        /* Since it seems snd_pcm_close hasen't really released the device at
+        /* 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. */
@@ -459,8 +468,14 @@ static int Open( vlc_object_t *p_this )
                    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",
+                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;
@@ -469,6 +484,9 @@ static int Open( vlc_object_t *p_this )
         {
             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;
@@ -493,113 +511,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)",
-                         snd_strerror( i_snd_rc ) );
-        goto error;
-    }
+        b_retry = false;
 
-    /* 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 );
-    }
+        /* 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;
+        }
 
-    /* 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 )
-    {
-        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,
+            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_snd_rc < 0 )
+        if( i_vlc_pcm_format != VLC_FOURCC('s','p','d','i') )
+        switch( i_snd_pcm_format )
         {
-            msg_Err( p_aout, "unable to set stream sample size and "
-                     "word order (%s)", snd_strerror( i_snd_rc ) );
-            goto error;
+        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( 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;
+        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 buffer size. */
+        /* 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_buffer_size_near( p_sys->p_snd_pcm,
-                                    p_hw, &i_buffer_size ) ) < 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_buffer_size_near( p_sys->p_snd_pcm,
-                                    p_hw, i_buffer_size ) ) < 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_Err( p_aout, "unable to set buffer size (%s)",
-                         snd_strerror( i_snd_rc ) );
-        goto error;
-    }
+        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 period size. */
+        /* 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,
+        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,
+        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)",
+        {
+            msg_Err( p_aout, "unable to set period size (%s)",
                          snd_strerror( i_snd_rc ) );
-        goto error;
-    }
-    p_aout->output.i_nb_samples = i_period_size;
+            goto error;
+        }
+        p_aout->output.i_nb_samples = i_period_size;
 
-    /* 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)",
+/* 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;
+            goto error;
+        }
+
+        /* 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;
+            }
+        }
     }
 
 #ifdef HAVE_ALSA_NEW_API
@@ -622,6 +659,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 )
@@ -640,9 +686,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;
     }
 
@@ -691,9 +737,9 @@ static void Close( vlc_object_t *p_this )
     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 );
 
@@ -707,7 +753,6 @@ static void Close( vlc_object_t *p_this )
     snd_output_close( p_sys->p_snd_stderr );
 #endif
 
-    free( p_sys->p_status );
     free( p_sys );
 }
 
@@ -716,20 +761,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 && !p_aout->b_die )
+        vlc_cond_wait( &p_sys->wait, &p_sys->lock );
+    vlc_mutex_unlock( &p_sys->lock );
+
+    if( p_aout->b_die )
+       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 )
     {
         ALSAFill( p_aout );
     }
 
+cleanup:
+    snd_pcm_drop( p_sys->p_snd_pcm );
+    free( p_aout->output.p_sys->p_status );
     return 0;
 }
 
@@ -739,113 +792,117 @@ 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 = p_sys->p_status;
-    snd_timestamp_t ts_next;
     int i_snd_rc;
     mtime_t next_date;
 
     /* 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 */
-        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 ) );
+        msg_Err( p_aout, "cannot get device status" );
+        goto error;
+    }
 
-            msleep( p_sys->i_period_time >> 1 );
-            return;
-        }
+    /* 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 );
 
-        /* Handle buffer underruns and reget the status */
-        if( snd_pcm_status_get_state( p_status ) == SND_PCM_STATE_XRUN )
+        if( i_snd_rc )
         {
-            /* Prepare the device */
-            i_snd_rc = snd_pcm_prepare( p_sys->p_snd_pcm );
+            msg_Err( p_aout, "cannot recover from buffer underrun" );
+            goto error;
+        }
 
-            if( i_snd_rc == 0 )
-            {
-                msg_Warn( p_aout, "recovered from buffer underrun" );
+        msg_Dbg( 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 ) );
+        /* 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, "cannot get device status after recovery" );
+            goto error;
+        }
 
-                    msleep( p_sys->i_period_time >> 1 );
-                    return;
-                }
-            }
-            else
-            {
-                msg_Err( p_aout, "unable to recover from buffer underrun" );
+        /* 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 );
 
-                msleep( p_sys->i_period_time >> 1 );
-                return;
-            }
+#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 );
 
-            /* Underrun, try to recover as quickly as possible */
-            next_date = mdate();
-        }
-        else
-        {
-            /* Here the device should be either in the RUNNING state.
-             * p_status is valid. */
+        msg_Dbg( p_aout, "Delay is %ld frames (%d bytes)", delay, i_bytes );
 
-            snd_pcm_status_get_tstamp( p_status, &ts_next );
-            next_date = (mtime_t)ts_next.tv_sec * 1000000 + ts_next.tv_usec;
-            if( next_date )
-            {
-                next_date += (mtime_t)snd_pcm_status_get_delay(p_status)
-                        * 1000000 / p_aout->output.output.i_rate;
-            }
-            else
-            {
-                /* With screwed ALSA drivers the timestamp is always zero;
-                 * use another method then */
-                snd_pcm_sframes_t delay;
-                ssize_t i_bytes = 0;
+        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 );
 
-                if( !snd_pcm_delay( p_sys->p_snd_pcm, &delay ) )
-                {
-                    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;
-            }
-        }
+        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')) );
+    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 )
-        {
-            msleep( p_sys->i_period_time >> 1 );
-            return;
-        }
+    /* 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;
 
-        if( i_snd_rc < 0 )
+        /* 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 )
         {
-            msg_Err( p_aout, "write failed (%s)",
-                             snd_strerror( i_snd_rc ) );
+            msleep( 1000000 );
         }
 
-        aout_BufferFree( p_buffer );
+        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_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 GetDevicesForCard( module_config_t *p_item, int i_card );
 static void GetDevices( module_config_t *p_item );
 
 /*****************************************************************************
@@ -866,8 +923,8 @@ static int FindDevicesCallback( vlc_object_t *p_this, char const *psz_name,
         /* Keep the first entrie */
         for( i = 1; i < p_item->i_list; i++ )
         {
-            free( p_item->ppsz_list[i] );
-            free( p_item->ppsz_list_text[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;
@@ -878,14 +935,14 @@ static int FindDevicesCallback( vlc_object_t *p_this, char const *psz_name,
     GetDevices( p_item );
 
     /* Signal change to the interface */
-    p_item->b_dirty = VLC_TRUE;
+    p_item->b_dirty = true;
 
     return VLC_SUCCESS;
 
 }
 
 
-static void GetDevicesForCard(module_config_t *p_item, int i_card)
+static void GetDevicesForCard( module_config_t *p_item, int i_card )
 {
     int i_pcm_device = -1;
     int i_err = 0;
@@ -893,43 +950,37 @@ static void GetDevicesForCard(module_config_t *p_item, int i_card)
     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 )
-    {
+
+    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)
-    {
+
+    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);
+    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)
-        {
+        if( ( i_err = snd_ctl_pcm_next_device( p_ctl, &i_pcm_device ) ) < 0 )
             i_pcm_device = -1;
-        }
-        if ( i_pcm_device < 0 )
+        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);
+        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)
+        if( ( i_err = snd_ctl_pcm_info( p_ctl, p_pcm_info ) ) < 0 )
         {
-            if (i_err != -ENOENT)
+            if( i_err != -ENOENT )
             {
-/*                printf("get_devices_for_card(): "
+                /*printf( "get_devices_for_card(): "
                          "snd_ctl_pcm_info() "
                          "failed (%d:%d): %s.\n", i_card,
-                         i_pcm_device, snd_strerror(-i_err));*/
+                         i_pcm_device, snd_strerror( -i_err ) );*/
             }
             continue;
         }
@@ -939,17 +990,16 @@ static void GetDevicesForCard(module_config_t *p_item, int i_card)
                   snd_pcm_info_get_name(p_pcm_info), psz_device );
 
         p_item->ppsz_list =
-            (char **)realloc( p_item->ppsz_list,
+            (const 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,
+            (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 );
@@ -961,20 +1011,19 @@ 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)
+
+    if( ( i_err = snd_card_next( &i_card ) ) != 0 )
     {
-//        g_warning("snd_next_card() failed: %s", snd_strerror(-err));
+        /*printf( "snd_card_next() failed: %s", snd_strerror( -i_err ) );*/
         return;
     }
-    
-    while (i_card > -1)
+
+    while( i_card > -1 )
     {
-        GetDevicesForCard(p_item, i_card);
-        if ((i_err = snd_card_next(&i_card)) != 0)
+        GetDevicesForCard( p_item, i_card );
+        if( ( i_err = snd_card_next( &i_card ) ) != 0 )
         {
-//            g_warning("snd_next_card() failed: %s",
-//                  snd_strerror(-err));
+            /*printf( "snd_card_next() failed: %s", snd_strerror( -i_err ) );*/
             break;
         }
     }