[vlc-devel] [PATCH v2 6/7] aout: make the drain implementation asynchronous

Thomas Guillem thomas at gllm.fr
Wed Mar 13 09:55:54 CET 2019


Yes, only the waveout plugin can do an asynchronous drain (for now).

Only the plugin implementation is asynchronous (for now), aout_DecDrain() is
still synchronous since it will wait for drain end from this call. The goal is
to make aout plugins non-blocking so that we can easily change the aout core
implementation in the future.
---
 include/vlc_aout.h               | 18 +++++++++++-
 modules/audio_output/alsa.c      | 48 ++++++++++++++++++++++++++++++--
 modules/audio_output/pulse.c     | 35 +++++++++++++++++++----
 modules/audio_output/waveout.c   | 23 +++++++++++----
 src/audio_output/aout_internal.h |  2 ++
 src/audio_output/dec.c           |  9 ++++++
 src/audio_output/output.c        |  3 ++
 7 files changed, 124 insertions(+), 14 deletions(-)

diff --git a/include/vlc_aout.h b/include/vlc_aout.h
index d25abd874b..d12b72461a 100644
--- a/include/vlc_aout.h
+++ b/include/vlc_aout.h
@@ -112,6 +112,7 @@
 #include <vlc_block.h>
 
 struct vlc_audio_output_events {
+    void (*drained_report)(audio_output_t *);
     void (*timing_report)(audio_output_t *, vlc_tick_t system_now, vlc_tick_t pts);
     void (*volume_report)(audio_output_t *, float);
     void (*mute_report)(audio_output_t *, bool);
@@ -221,7 +222,7 @@ struct audio_output
       * \note This callback cannot be called in stopped state.
       */
     void (*drain)(audio_output_t *);
-    /**< Drain the playback buffers (can be NULL).
+    /**< Drain the playback buffers asynchronously (can be NULL).
       *
       * The implementation can invalidate the stream since this is generally
       * the last call before a stop(). However, a flush() could also be called
@@ -230,6 +231,11 @@ struct audio_output
       *
       * If NULL, the caller will wait for the delay returned by time_get before
       * calling stop().
+      *
+      * \note The implementation must call aout_DrainedReport() from or after
+      * this callback. Draining can *not* be cancelled, therefore the next
+      * flush() or stop() will always be called after that the drained state is
+      * reported.
       */
 
     int (*volume_set)(audio_output_t *, float volume);
@@ -446,6 +452,16 @@ static inline void aout_RestartRequest(audio_output_t *aout, unsigned mode)
     aout->events->restart_request(aout, mode);
 }
 
+/**
+ * Report that the stream is drained
+ *
+ * This function can only be called one time after or from aout->drain().
+ */
+static inline void aout_DrainedReport(audio_output_t *aout)
+{
+    aout->events->drained_report(aout);
+}
+
 /**
  * Default implementation for audio_output_t.time_get
  */
diff --git a/modules/audio_output/alsa.c b/modules/audio_output/alsa.c
index a99faed3cb..7d4b636264 100644
--- a/modules/audio_output/alsa.c
+++ b/modules/audio_output/alsa.c
@@ -51,6 +51,9 @@ typedef struct
     bool soft_mute;
     float soft_gain;
     char *device;
+
+    bool draining;
+    vlc_thread_t drain_thread;
 } aout_sys_t;
 
 enum {
@@ -611,6 +614,7 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt)
     }
     fmt->channel_type = AUDIO_CHANNEL_TYPE_BITMAP;
     sys->format = fmt->i_format;
+    sys->draining = false;
 
     if (snd_pcm_hw_params_can_pause (hw))
         aout->pause = Pause;
@@ -714,6 +718,29 @@ static void PauseDummy (audio_output_t *aout, bool pause, vlc_tick_t date)
     (void) date;
 }
 
+static void DrainCleanup(audio_output_t *aout)
+{
+    aout_sys_t *p_sys = aout->sys;
+    if (p_sys->draining)
+    {
+        vlc_join(p_sys->drain_thread, NULL);
+        p_sys->draining = false;
+    }
+}
+
+static void *DrainThread(void *data)
+{
+    audio_output_t *aout = data;
+    aout_sys_t *p_sys = aout->sys;
+    snd_pcm_t *pcm = p_sys->pcm;
+
+    msg_Dbg(aout, "drained");
+
+    snd_pcm_drain (pcm);
+    aout_DrainedReport(aout);
+    return NULL;
+}
+
 /**
  * Flushes the audio playback buffer.
  */
@@ -721,6 +748,9 @@ static void Flush (audio_output_t *aout)
 {
     aout_sys_t *p_sys = aout->sys;
     snd_pcm_t *pcm = p_sys->pcm;
+
+    DrainCleanup(aout);
+
     snd_pcm_drop (pcm);
     snd_pcm_prepare (pcm);
 }
@@ -731,8 +761,13 @@ 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;
-    snd_pcm_drain (pcm);
+    assert(!p_sys->draining);
+
+    if (vlc_clone(&p_sys->drain_thread, DrainThread, aout,
+                  VLC_THREAD_PRIORITY_AUDIO))
+        aout_DrainedReport(aout);
+    else
+        p_sys->draining = true;
 }
 
 /**
@@ -743,6 +778,8 @@ static void Stop (audio_output_t *aout)
     aout_sys_t *sys = aout->sys;
     snd_pcm_t *pcm = sys->pcm;
 
+    DrainCleanup(aout);
+
     snd_pcm_drop (pcm);
     snd_pcm_close (pcm);
 }
@@ -854,10 +891,15 @@ static int Open(vlc_object_t *obj)
         free (ids);
     }
 
+    char *thread_safe = getenv("LIBASOUND_THREAD_SAFE");
+
     aout->time_get = TimeGet;
     aout->play = Play;
     aout->flush = Flush;
-    aout->drain = Drain;
+    if (thread_safe && *thread_safe == '0')
+        msg_Warn(aout, "asound is not thread safe: drain will be disabled");
+    else
+        aout->drain = Drain;
 
     return VLC_SUCCESS;
 error:
diff --git a/modules/audio_output/pulse.c b/modules/audio_output/pulse.c
index cc4af1e8c0..67af499966 100644
--- a/modules/audio_output/pulse.c
+++ b/modules/audio_output/pulse.c
@@ -175,14 +175,13 @@ static void stream_start_now(pa_stream *s, audio_output_t *aout)
 {
     pa_operation *op;
 
-    assert ( ((aout_sys_t *)aout->sys)->trigger == NULL );
-
     op = pa_stream_cork(s, 0, NULL, NULL);
     if (op != NULL)
         pa_operation_unref(op);
     op = pa_stream_trigger(s, NULL, NULL);
     if (likely(op != NULL))
         pa_operation_unref(op);
+    (void) aout;
 }
 
 static void stream_stop(pa_stream *s, audio_output_t *aout)
@@ -549,6 +548,21 @@ static void Flush(audio_output_t *aout)
     pa_threaded_mainloop_unlock(sys->mainloop);
 }
 
+static void drain_trigger_cb(pa_mainloop_api *api, pa_time_event *e,
+                             const struct timeval *tv, void *userdata)
+{
+    audio_output_t *aout = userdata;
+    aout_sys_t *sys = aout->sys;
+
+    assert (sys->trigger == e);
+
+    msg_Dbg(aout, "drained");
+    vlc_pa_rttime_free(sys->mainloop, sys->trigger);
+    sys->trigger = NULL;
+    aout_DrainedReport(aout);
+    (void) api; (void) e; (void) tv;
+}
+
 static void Drain(audio_output_t *aout)
 {
     aout_sys_t *sys = aout->sys;
@@ -558,13 +572,24 @@ static void Drain(audio_output_t *aout)
     pa_operation *op = pa_stream_drain(s, NULL, NULL);
     if (op != NULL)
         pa_operation_unref(op);
-    pa_threaded_mainloop_unlock(sys->mainloop);
+
+    if (unlikely(sys->trigger != NULL))
+    {
+        /* Could happen with very very small input */
+        vlc_pa_rttime_free(sys->mainloop, sys->trigger);
+        stream_start_now(sys->stream, aout);
+    }
 
     /* XXX: Loosy drain emulation.
      * See #18141: drain callback is never received */
     vlc_tick_t delay;
-    if (TimeGet(aout, &delay) == 0 && delay <= VLC_TICK_FROM_SEC(5))
-        vlc_tick_sleep(delay);
+    if (TimeGet(aout, &delay) == 0)
+        sys->trigger = pa_context_rttime_new(sys->context,
+                                             delay + pa_rtclock_now(),
+                                             drain_trigger_cb, aout);
+    else
+        aout_DrainedReport(aout);
+    pa_threaded_mainloop_unlock(sys->mainloop);
 }
 
 static int VolumeSet(audio_output_t *aout, float vol)
diff --git a/modules/audio_output/waveout.c b/modules/audio_output/waveout.c
index 3c9884ce01..495fe519bc 100644
--- a/modules/audio_output/waveout.c
+++ b/modules/audio_output/waveout.c
@@ -119,6 +119,8 @@ struct aout_sys_t
     bool b_soft;                            /* Use software gain */
     uint8_t chans_to_reorder;              /* do we need channel reordering */
 
+    bool b_draining;
+
     uint8_t chan_table[AOUT_CHAN_MAX];
     vlc_fourcc_t format;
 
@@ -332,6 +334,7 @@ static int Start( audio_output_t *p_aout, audio_sample_format_t *restrict fmt )
     sys->i_frames = 0;
     sys->i_played_length = 0;
     sys->p_free_list = NULL;
+    sys->b_draining = false;
 
     fmt->channel_type = AUDIO_CHANNEL_TYPE_BITMAP;
 
@@ -395,7 +398,11 @@ static void Stop( audio_output_t *p_aout )
     MMRESULT result = waveOutReset( p_sys->h_waveout );
 
     /* wait for the frames to be queued in cleaning list */
-    WaveOutDrain( p_aout );
+    vlc_mutex_lock( &p_sys->lock );
+    while( p_sys->i_frames )
+        vlc_cond_wait( &p_sys->cond, &p_sys->lock );
+    vlc_mutex_unlock( &p_sys->lock );
+
     WaveOutClean( p_sys );
 
     /* now we can Close the device */
@@ -650,6 +657,11 @@ static void CALLBACK WaveOutCallback( HWAVEOUT h_waveout, UINT uMsg,
     p_waveheader->p_next = sys->p_free_list;
     sys->p_free_list = p_waveheader;
     sys->i_frames--;
+    if (sys->b_draining)
+    {
+        sys->b_draining = false;
+        aout_DrainedReport((audio_output_t *)_p_aout);
+    }
     vlc_cond_broadcast( &sys->cond );
     vlc_mutex_unlock( &sys->lock );
 }
@@ -863,11 +875,12 @@ static void WaveOutFlush( audio_output_t *p_aout)
 
 static void WaveOutDrain( audio_output_t *p_aout)
 {
+    aout_sys_t *sys = p_aout->sys;
     vlc_mutex_lock( &sys->lock );
-    while( sys->i_frames )
-    {
-        vlc_cond_wait( &sys->cond, &sys->lock );
-    }
+    if( sys->i_frames )
+        sys->b_draining = true;
+    else
+        aout_DrainedReport( p_aout );
     vlc_mutex_unlock( &sys->lock );
 }
 
diff --git a/src/audio_output/aout_internal.h b/src/audio_output/aout_internal.h
index ed72cff0f8..ba492cbd8a 100644
--- a/src/audio_output/aout_internal.h
+++ b/src/audio_output/aout_internal.h
@@ -85,6 +85,7 @@ typedef struct
     atomic_uint buffers_played;
     atomic_uchar restart;
 
+    vlc_sem_t report_sem;
     bool drained;
 } aout_owner_t;
 
@@ -147,6 +148,7 @@ void aout_DecDrain(audio_output_t *);
 void aout_RequestRestart (audio_output_t *, unsigned);
 void aout_RequestRetiming(audio_output_t *aout, vlc_tick_t system_ts,
                           vlc_tick_t audio_ts);
+void aout_Drained(audio_output_t *);
 
 static inline void aout_InputRequestRestart(audio_output_t *aout)
 {
diff --git a/src/audio_output/dec.c b/src/audio_output/dec.c
index 683f181f9f..a35c16763f 100644
--- a/src/audio_output/dec.c
+++ b/src/audio_output/dec.c
@@ -531,6 +531,12 @@ void aout_DecFlush(audio_output_t *aout)
     owner->drained = false;
 }
 
+void aout_Drained(audio_output_t *aout)
+{
+    aout_owner_t *owner = aout_owner (aout);
+    vlc_sem_post(&owner->report_sem);
+}
+
 void aout_DecDrain(audio_output_t *aout)
 {
     aout_owner_t *owner = aout_owner (aout);
@@ -544,7 +550,10 @@ void aout_DecDrain(audio_output_t *aout)
         aout->play(aout, block, vlc_tick_now());
 
     if (aout->drain)
+    {
         aout->drain(aout);
+        vlc_sem_wait(&owner->report_sem);
+    }
     else
     {
         vlc_tick_t delay;
diff --git a/src/audio_output/output.c b/src/audio_output/output.c
index cfc786f4a1..db1e41404a 100644
--- a/src/audio_output/output.c
+++ b/src/audio_output/output.c
@@ -165,6 +165,7 @@ static int aout_GainNotify (audio_output_t *aout, float gain)
 }
 
 static const struct vlc_audio_output_events aout_events = {
+    aout_Drained,
     aout_TimingNotify,
     aout_VolumeNotify,
     aout_MuteNotify,
@@ -242,6 +243,7 @@ audio_output_t *aout_New (vlc_object_t *parent)
     vlc_mutex_init (&owner->lock);
     vlc_mutex_init (&owner->dev.lock);
     vlc_mutex_init (&owner->vp.lock);
+    vlc_sem_init(&owner->report_sem, 0);
     vlc_viewpoint_init (&owner->vp.value);
     atomic_init (&owner->vp.update, false);
 
@@ -397,6 +399,7 @@ static void aout_Destructor (vlc_object_t *obj)
     audio_output_t *aout = (audio_output_t *)obj;
     aout_owner_t *owner = aout_owner (aout);
 
+    vlc_sem_destroy(&owner->report_sem);
     vlc_mutex_destroy (&owner->dev.lock);
     for (aout_dev_t *dev = owner->dev.list, *next; dev != NULL; dev = next)
     {
-- 
2.20.1



More information about the vlc-devel mailing list