[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