[vlc-commits] [Git][videolan/vlc][master] alsa: switch to non blocking play/drain

Steve Lhomme (@robUx4) gitlab at videolan.org
Tue Dec 6 06:56:06 UTC 2022



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
b3f42a9a by Denis Charmet at 2022-12-06T06:42:58+00:00
alsa: switch to non blocking play/drain

Fixes #27537

- - - - -


1 changed file:

- modules/audio_output/alsa.c


Changes:

=====================================
modules/audio_output/alsa.c
=====================================
@@ -28,6 +28,10 @@
 # include "config.h"
 #endif
 
+#ifdef HAVE_SYS_EVENTFD_H
+#include <sys/eventfd.h>
+#endif
+
 #include <assert.h>
 
 #include <vlc_common.h>
@@ -35,6 +39,7 @@
 #include <vlc_dialog.h>
 #include <vlc_aout.h>
 #include <vlc_cpu.h>
+#include <vlc_fs.h>
 
 #include <alsa/asoundlib.h>
 #include <alsa/version.h>
@@ -90,6 +95,13 @@ static void DumpDeviceStatus(struct vlc_logger *log, snd_pcm_t *pcm)
     Dump(log, "current status:\n", snd_pcm_status_dump, status);
 }
 
+typedef enum
+{
+    IDLE,
+    PLAYING,
+    PAUSED,
+} pb_state_t;
+
 /** Private data for an ALSA PCM playback stream */
 typedef struct
 {
@@ -102,55 +114,237 @@ typedef struct
     bool soft_mute;
     float soft_gain;
     char *device;
+
+    vlc_thread_t thread;
+    pb_state_t state;
+    bool started;
+    bool draining;
+    bool unrecoverable_error;
+    vlc_mutex_t lock;
+    vlc_sem_t init_sem;
+    int wakefd[2];
+    vlc_frame_t *frame_chain;
+    vlc_frame_t **frame_last;
+    uint64_t queued_samples;
 } aout_sys_t;
 
 #include "audio_output/volume.h"
 
-static int TimeGet(audio_output_t *aout, vlc_tick_t *restrict delay)
+static void wake_poll(aout_sys_t *sys)
 {
-    aout_sys_t *sys = aout->sys;
-    snd_pcm_sframes_t frames;
+    uint64_t val = 1;
+    ssize_t rd = write(sys->wakefd[1], &val, sizeof(val));
+    assert(rd == sizeof(val));
+    (void) rd;
+}
 
-    int val = snd_pcm_delay(sys->pcm, &frames);
-    if (val)
+static int recover_from_pcm_state(snd_pcm_t *pcm)
+{
+    snd_pcm_state_t state = snd_pcm_state(pcm);
+    int err = 0;
+    switch (state)
     {
-        msg_Err(aout, "cannot estimate delay: %s", snd_strerror(val));
-        return -1;
+    case SND_PCM_STATE_RUNNING:
+    case SND_PCM_STATE_PAUSED:
+        return 0;
+    case SND_PCM_STATE_XRUN:
+        err = -EPIPE;
+        break;
+    case SND_PCM_STATE_SUSPENDED:
+        err = -ESTRPIPE;
+        break;
+    default:
+        err = 0;
     }
-    *delay = vlc_tick_from_samples(frames, sys->rate);
-    return 0;
+
+    if (err)
+        return snd_pcm_recover(pcm, err, -1);
+
+    return -1;
 }
 
-/**
- * Queues one audio buffer to the hardware.
- */
-static void Play(audio_output_t *aout, block_t *block, vlc_tick_t date)
+static int fill_pfds_from_state_locked(audio_output_t *aout, struct pollfd **pfds, int *pfds_count)
 {
     aout_sys_t *sys = aout->sys;
+    snd_pcm_t *pcm = sys->pcm;
+    switch (sys->state)
+    {
+    case IDLE:
+    case PAUSED:
+        /* We are paused or drained no need to wait for snd pcm*/
+        return 1;
+    case PLAYING:
+    {
+        if (sys->frame_chain == NULL && !sys->draining)
+            return 1; /* Waiting for data */
 
-    if (sys->chans_to_reorder != 0)
-        aout_ChannelReorder(block->p_buffer, block->i_buffer,
-                            sys->chans_to_reorder, sys->chans_table,
-                            sys->format);
+        int cnt = snd_pcm_poll_descriptors_count(pcm);
+        if (unlikely(cnt < 0))
+        {
+            if (!recover_from_pcm_state(pcm))
+                return 0;
 
+            msg_Err(aout, "Cannot retrieve descriptors' count (%d)", cnt);
+            return -1;
+        }
+        else if (cnt + 1 > *pfds_count)
+        {
+            struct pollfd * tmp = realloc(*pfds, sizeof(struct pollfd) * (cnt + 1));
+            if (tmp == NULL)
+            {
+                sys->unrecoverable_error = true;
+                return -1;
+            }
+            *pfds = tmp;
+            *pfds_count = cnt + 1;
+        }
+
+        cnt = snd_pcm_poll_descriptors(pcm, &(*pfds)[1], cnt);
+        if (unlikely(cnt < 0))
+        {
+            if (!recover_from_pcm_state(pcm))
+                return 0;
+
+            msg_Err(aout, "snd_pcm_poll_descriptors failed (%d)", cnt);
+            return -1;
+        }
+        return cnt + 1;
+    }
+    default:
+        return -1;
+    }
+    return -1;
+}
+
+static void * InjectionThread(void * data)
+{
+    audio_output_t *aout = (audio_output_t *) data;
+    aout_sys_t *sys = aout->sys;
     snd_pcm_t *pcm = sys->pcm;
 
-    /* TODO: better overflow handling */
-    /* TODO: no period wake ups */
+    /* We're expecting at least 2 fds:
+     * - one for generic wakeup
+     * - one or more for alsa
+     */
+    struct pollfd * pfds = calloc(2, sizeof(struct pollfd));
+    if (pfds == NULL)
+    {
+      sys->unrecoverable_error = true;
+      vlc_sem_post(&sys->init_sem);
+      return NULL;
+    }
+    int pfds_count = 2;
+
+    pfds[0].fd = sys->wakefd[0];
+    pfds[0].events = POLLIN;
 
-    while (block->i_nb_samples > 0)
+    vlc_sem_post(&sys->init_sem);
+
+    vlc_mutex_lock(&sys->lock);
+    while (sys->started)
     {
-        snd_pcm_sframes_t frames;
+        int cnt = fill_pfds_from_state_locked(aout, &pfds, &pfds_count);
+        if (unlikely(cnt < 0))
+            break;
+        else if (unlikely(cnt == 0))
+            continue;
+
+        vlc_mutex_unlock(&sys->lock);
+
+        cnt = poll(pfds, cnt, -1);
+
+        vlc_mutex_lock(&sys->lock);
+        if (unlikely(cnt < 0))
+        {
+            if (errno == -EINTR)
+                continue;
+            msg_Err(aout, "poll failed (%s)", strerror(errno));
+            break;
+        }
 
-        frames = snd_pcm_writei(pcm, block->p_buffer, block->i_nb_samples);
+        if (pfds[0].revents & POLLIN)
+        {
+            uint64_t val;
+            ssize_t rd = read(sys->wakefd[0], &val, sizeof(val));
+            if (rd != sizeof(val))
+            {
+                msg_Err(aout, "Invalid read on wakefd got %zd (%s)", rd, strerror(errno));
+                break;
+            }
+            /* We either got data or a state change, let's refill the pfds or abort */
+            continue;
+        }
+
+        unsigned short revents;
+        cnt = snd_pcm_poll_descriptors_revents(pcm, &pfds[1], pfds_count-1, &revents);
+        if (cnt != 0)
+        {
+            if (!recover_from_pcm_state(pcm))
+                continue;
+
+            msg_Err(aout, "snd_pcm_poll_descriptors_revents failed (%d)", cnt);
+            break;
+        }
+
+        if (unlikely(revents & POLLERR))
+        {
+            if (!recover_from_pcm_state(pcm))
+                continue;
+            if (sys->draining)
+            {
+                msg_Warn(aout,"Polling error from drain");
+                snd_pcm_prepare(pcm);
+                sys->state = IDLE;
+                sys->draining = false;
+                aout_DrainedReport(aout);
+                continue;
+            }
+            msg_Err(aout, "Unrecoverable polling error");
+            break;
+        }
+
+        if (!(revents & POLLOUT) ||
+            sys->state == PAUSED ||
+            sys->state == IDLE)
+            continue;
+
+        if (sys->frame_chain == NULL)
+        {
+            if (sys->draining)
+            {
+                /* SND_PCM_NONBLOCK makes snd_pcm_drain non blocking so we must
+                 * call poll until its completion
+                 */
+                if (snd_pcm_drain(pcm) == -EAGAIN)
+                    continue;
+                snd_pcm_prepare(pcm);
+                sys->state = IDLE;
+                sys->draining = false;
+                aout_DrainedReport(aout);
+            }
+            continue;
+        }
+
+        vlc_frame_t * f = sys->frame_chain;
+        snd_pcm_sframes_t frames = snd_pcm_writei(pcm, f->p_buffer, f->i_nb_samples);
         if (frames >= 0)
         {
             size_t bytes = snd_pcm_frames_to_bytes(pcm, frames);
-            block->i_nb_samples -= frames;
-            block->p_buffer += bytes;
-            block->i_buffer -= bytes;
+            f->i_nb_samples -= frames;
+            f->p_buffer += bytes;
+            f->i_buffer -= bytes;
+            sys->queued_samples -= frames;
             // pts, length
+            if (f->i_nb_samples == 0)
+            {
+                sys->frame_chain = f->p_next;
+                if (sys->frame_chain == NULL)
+                    sys->frame_last = &sys->frame_chain;
+                vlc_frame_Release(f);
+            }
         }
+        else if (frames == -EAGAIN)
+            continue;
         else
         {
             int val = snd_pcm_recover(pcm, frames, 1);
@@ -164,20 +358,83 @@ static void Play(audio_output_t *aout, block_t *block, vlc_tick_t date)
             msg_Warn(aout, "cannot write samples: %s", snd_strerror(frames));
         }
     }
-    block_Release(block);
+    free(pfds);
+    if (sys->started && !sys->unrecoverable_error)
+    {
+        msg_Err(aout, "Unhandled error in injection thread, requesting aout restart");
+        aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
+    }
+    vlc_mutex_unlock(&sys->lock);
+    return NULL;
+}
+
+static int TimeGet(audio_output_t *aout, vlc_tick_t *restrict delay)
+{
+    aout_sys_t *sys = aout->sys;
+    snd_pcm_sframes_t frames;
+
+    vlc_mutex_lock(&sys->lock);
+    int val = snd_pcm_delay(sys->pcm, &frames);
+    if (val)
+    {
+        msg_Err(aout, "cannot estimate delay: %s", snd_strerror(val));
+        vlc_mutex_unlock(&sys->lock);
+        return -1;
+    }
+    *delay = vlc_tick_from_samples(frames + sys->queued_samples, sys->rate);
+    vlc_mutex_unlock(&sys->lock);
+    return 0;
+}
+
+
+/**
+ * Queues one audio buffer to the hardware.
+ */
+static void Play(audio_output_t *aout, block_t *block, vlc_tick_t date)
+{
+    aout_sys_t *sys = aout->sys;
+
+    if (sys->chans_to_reorder != 0)
+        aout_ChannelReorder(block->p_buffer, block->i_buffer,
+                            sys->chans_to_reorder, sys->chans_table,
+                            sys->format);
+
+    vlc_mutex_lock(&sys->lock);
+    if (unlikely(sys->unrecoverable_error))
+    {
+        vlc_frame_Release(block);
+        vlc_mutex_unlock(&sys->lock);
+        return;
+    }
+    if (sys->frame_chain == NULL)
+        wake_poll(sys);
+    vlc_frame_ChainLastAppend(&sys->frame_last, block);
+    sys->queued_samples += block->i_nb_samples;
+    vlc_mutex_unlock(&sys->lock);
+
     (void) date;
 }
 
 static void PauseDummy(audio_output_t *aout, bool pause, vlc_tick_t date)
 {
-    aout_sys_t *p_sys = aout->sys;
-    snd_pcm_t *pcm = p_sys->pcm;
+    aout_sys_t *sys = aout->sys;
+    snd_pcm_t *pcm = sys->pcm;
 
     /* Stupid device cannot pause. Discard samples. */
+    vlc_mutex_lock(&sys->lock);
     if (pause)
+    {
+        sys->state = PAUSED;
         snd_pcm_drop(pcm);
+    }
     else
+    {
+        sys->state = PLAYING;
         snd_pcm_prepare(pcm);
+    }
+    wake_poll(sys);
+    vlc_mutex_unlock(&sys->lock);
+
     (void) date;
 }
 
@@ -186,12 +443,19 @@ static void PauseDummy(audio_output_t *aout, bool pause, vlc_tick_t date)
  */
 static void Pause(audio_output_t *aout, bool pause, vlc_tick_t date)
 {
-    aout_sys_t *p_sys = aout->sys;
-    snd_pcm_t *pcm = p_sys->pcm;
+    aout_sys_t *sys = aout->sys;
+    snd_pcm_t *pcm = sys->pcm;
 
     int val = snd_pcm_pause(pcm, pause);
     if (unlikely(val))
+    {
         PauseDummy(aout, pause, date);
+        return;
+    }
+    vlc_mutex_lock(&sys->lock);
+    sys->state = pause? PAUSED: PLAYING;
+    wake_poll(sys);
+    vlc_mutex_unlock(&sys->lock);
 }
 
 /**
@@ -199,8 +463,20 @@ static void Pause(audio_output_t *aout, bool pause, vlc_tick_t date)
  */
 static void Flush (audio_output_t *aout)
 {
-    aout_sys_t *p_sys = aout->sys;
-    snd_pcm_t *pcm = p_sys->pcm;
+    aout_sys_t *sys = aout->sys;
+    snd_pcm_t *pcm = sys->pcm;
+
+    vlc_mutex_lock(&sys->lock);
+    vlc_frame_ChainRelease(sys->frame_chain);
+    sys->frame_chain = NULL;
+    sys->frame_last = &sys->frame_chain;
+    sys->queued_samples = 0;
+    sys->draining = false;
+
+    if (sys->state == IDLE)
+        sys->state = PLAYING;
+    wake_poll(sys);
+    vlc_mutex_unlock(&sys->lock);
 
     snd_pcm_drop(pcm);
     snd_pcm_prepare(pcm);
@@ -211,14 +487,12 @@ static void Flush (audio_output_t *aout)
  */
 static void Drain (audio_output_t *aout)
 {
-    aout_sys_t *p_sys = aout->sys;
-    snd_pcm_t *pcm = p_sys->pcm;
-
-    /* XXX: Synchronous drain, not interruptible. */
-    snd_pcm_drain(pcm);
-    snd_pcm_prepare(pcm);
+    aout_sys_t *sys = aout->sys;
 
-    aout_DrainedReport(aout);
+    vlc_mutex_lock(&sys->lock);
+    sys->draining = true;
+    wake_poll(sys);
+    vlc_mutex_unlock(&sys->lock);
 }
 
 /**
@@ -229,7 +503,20 @@ static void Stop (audio_output_t *aout)
     aout_sys_t *sys = aout->sys;
     snd_pcm_t *pcm = sys->pcm;
 
+    vlc_mutex_lock(&sys->lock);
+    sys->started = false;
+    sys->state = IDLE;
+    sys->draining = false;
+    vlc_frame_ChainRelease(sys->frame_chain);
+    sys->frame_chain = NULL;
+    sys->frame_last = &sys->frame_chain;
+    sys->queued_samples = 0;
+    wake_poll(sys);
     snd_pcm_drop(pcm);
+    vlc_mutex_unlock(&sys->lock);
+
+    vlc_join(sys->thread, NULL);
+
     snd_pcm_close(pcm);
 }
 
@@ -492,7 +779,7 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt)
     /* Open the device */
     snd_pcm_t *pcm;
     /* VLC always has a resampler. No need for ALSA's. */
-    const int mode = SND_PCM_NO_AUTO_RESAMPLE;
+    const int mode = SND_PCM_NO_AUTO_RESAMPLE | SND_PCM_NONBLOCK;
 
     int val = snd_pcm_open (&pcm, device, SND_PCM_STREAM_PLAYBACK, mode);
     if (val != 0)
@@ -699,7 +986,23 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt)
         aout->pause = PauseDummy;
         msg_Warn (aout, "device cannot be paused");
     }
+
     aout_SoftVolumeStart (aout);
+
+    sys->queued_samples = 0;
+    sys->started = true;
+    sys->draining = false;
+    sys->state = PLAYING;
+    sys->unrecoverable_error = false;
+    if (vlc_clone(&sys->thread, InjectionThread, aout))
+        goto error;
+
+    vlc_sem_wait(&sys->init_sem);
+    if (sys->unrecoverable_error)
+    {
+        vlc_join(sys->thread, NULL);
+        goto error;
+    }
     return 0;
 
 error:
@@ -787,6 +1090,20 @@ static int Open(vlc_object_t *obj)
 
     if (unlikely(sys == NULL))
         return VLC_ENOMEM;
+
+#ifdef HAVE_EVENTFD
+    sys->wakefd[0] = eventfd(0, EFD_CLOEXEC);
+    sys->wakefd[1] = sys->wakefd[0];
+    if (sys->wakefd[0] < 0)
+      goto error;
+#else
+    if (vlc_pipe(sys->wakefd))
+    {
+      sys->wakefd[0] = sys->wakefd[1] = -1;
+      goto error;
+    }
+#endif
+
     sys->device = var_InheritString (aout, "alsa-audio-device");
     if (unlikely(sys->device == NULL))
         goto error;
@@ -816,6 +1133,12 @@ static int Open(vlc_object_t *obj)
         free (ids);
     }
 
+    sys->state = IDLE;
+    vlc_mutex_init(&sys->lock);
+    sys->frame_chain = NULL;
+    sys->frame_last = &sys->frame_chain;
+    vlc_sem_init(&sys->init_sem, 0);
+
     aout->time_get = TimeGet;
     aout->play = Play;
     aout->flush = Flush;
@@ -823,6 +1146,12 @@ static int Open(vlc_object_t *obj)
 
     return VLC_SUCCESS;
 error:
+    if (sys->wakefd[0] >= 0)
+    {
+      if (sys->wakefd[1] != sys->wakefd[0])
+        vlc_close(sys->wakefd[1]);
+      vlc_close(sys->wakefd[0]);
+    }
     free (sys);
     return VLC_ENOMEM;
 }
@@ -833,6 +1162,9 @@ static void Close(vlc_object_t *obj)
     aout_sys_t *sys = aout->sys;
 
     free (sys->device);
+    if (sys->wakefd[1] != sys->wakefd[0])
+      vlc_close(sys->wakefd[1]);
+    vlc_close(sys->wakefd[0]);
     free (sys);
 }
 



View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/b3f42a9a2039770311a7641aeeabb2c8958859f1

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/b3f42a9a2039770311a7641aeeabb2c8958859f1
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list