[vlc-devel] [PATCH 3/3] wasapi: start deferred

Thomas Guillem thomas at gllm.fr
Mon Sep 30 16:26:11 CEST 2019


The idea is to act like PulseAudio and coreaudio recently.

Instead of returning a valid date from the first TimeGet() to let the core
handle the initial delay, the initial delay is handled inside this module by
deferring the Start.

The big advantage of this solution is that this module can now handle a change
of play date, like when the clock jitter is changed by the video decoder
latency for example (cf. the "Trying to fix the big one: Frame threading
regressions" patch set).

A Windows Timer is used to defer this start. Audio samples are written while
the AudioClient is not started (behavior handled according to the
documentation). If the audio buffer is full before the AudioClient is started,
the Play function will wait for it, cf. last commit about event-driven
buffering.

RFC: Am I OK regarding threading COM/MTA ? I confess that I don't fully
comprehend this part. Also there is no documentation about threading in the
AudioClient API.
---
 modules/audio_output/wasapi.c | 96 +++++++++++++++++++++++++++++++++--
 1 file changed, 91 insertions(+), 5 deletions(-)

diff --git a/modules/audio_output/wasapi.c b/modules/audio_output/wasapi.c
index 62827caa2e..f09ab29843 100644
--- a/modules/audio_output/wasapi.c
+++ b/modules/audio_output/wasapi.c
@@ -33,6 +33,7 @@
 #include <vlc_common.h>
 #include <vlc_codecs.h>
 #include <vlc_aout.h>
+#include <vlc_atomic.h>
 #include <vlc_plugin.h>
 
 #include <audioclient.h>
@@ -91,6 +92,12 @@ typedef struct aout_stream_sys
 {
     IAudioClient *client;
     HANDLE hEvent;
+    HANDLE hTimer;
+
+#define STARTED_STATE_INIT 0
+#define STARTED_STATE_OK 1
+#define STARTED_STATE_ERROR 2
+    atomic_char started_state;
 
     uint8_t chans_table[AOUT_CHAN_MAX];
     uint8_t chans_to_reorder;
@@ -102,7 +109,6 @@ typedef struct aout_stream_sys
     UINT32 frames; /**< Total buffer size (frames) */
 } aout_stream_sys_t;
 
-
 /*** VLC audio output callbacks ***/
 static HRESULT TimeGet(aout_stream_t *s, vlc_tick_t *restrict delay)
 {
@@ -111,6 +117,9 @@ static HRESULT TimeGet(aout_stream_t *s, vlc_tick_t *restrict delay)
     UINT64 pos, qpcpos, freq;
     HRESULT hr;
 
+    if (atomic_load(&sys->started_state) != STARTED_STATE_OK)
+        return E_FAIL;
+
     hr = IAudioClient_GetService(sys->client, &IID_IAudioClock, &pv);
     if (FAILED(hr))
     {
@@ -141,6 +150,50 @@ static HRESULT TimeGet(aout_stream_t *s, vlc_tick_t *restrict delay)
     return hr;
 }
 
+static void CALLBACK StartDeferredCallback(void *val, BOOLEAN timeout)
+{
+    aout_stream_t *s = val;
+    aout_stream_sys_t *sys = s->sys;
+
+    HRESULT hr = IAudioClient_Start(sys->client);
+    atomic_store(&sys->started_state,
+                 SUCCEEDED(hr) ? STARTED_STATE_OK : STARTED_STATE_ERROR);
+    (void) timeout;
+}
+
+static HRESULT StartDeferred(aout_stream_t *s, vlc_tick_t date)
+{
+    aout_stream_sys_t *sys = s->sys;
+    vlc_tick_t written = vlc_tick_from_frac(sys->written, sys->rate);
+    vlc_tick_t start_delay = date - vlc_tick_now() - written;
+    DWORD start_delay_ms = MS_FROM_VLC_TICK(start_delay);
+    BOOL res;
+
+    /* Create or update the current timer */
+    if (sys->hTimer == NULL)
+        res = CreateTimerQueueTimer(&sys->hTimer, NULL, StartDeferredCallback,
+                                    s, start_delay_ms, 0,
+                                    WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE);
+    else
+        res = ChangeTimerQueueTimer(NULL, sys->hTimer, start_delay_ms, 0);
+
+    if (!res)
+    {
+        msg_Warn(s, "timer update failed, starting now");
+        HRESULT hr = IAudioClient_Start(sys->client);
+        if (FAILED(hr))
+        {
+            atomic_store(&sys->started_state, STARTED_STATE_ERROR);
+            return hr;
+        }
+        atomic_store(&sys->started_state, STARTED_STATE_OK);
+    }
+    else
+        msg_Dbg(s, "deferring start (%"PRId64" us)", start_delay);
+
+    return S_OK;
+}
+
 static HRESULT Play(aout_stream_t *s, block_t *block, vlc_tick_t date)
 {
     (void) date;
@@ -148,6 +201,19 @@ static HRESULT Play(aout_stream_t *s, block_t *block, vlc_tick_t date)
     void *pv;
     HRESULT hr;
 
+    char started_state = atomic_load(&sys->started_state);
+    if (unlikely(started_state == STARTED_STATE_ERROR))
+    {
+        hr = E_FAIL;
+        goto out;
+    }
+    else if (started_state == STARTED_STATE_INIT)
+    {
+        hr = StartDeferred(s, date);
+        if (FAILED(hr))
+            goto out;
+    }
+
     if (sys->chans_to_reorder)
         aout_ChannelReorder(block->p_buffer, block->i_buffer,
                           sys->chans_to_reorder, sys->chans_table, sys->format);
@@ -192,7 +258,6 @@ static HRESULT Play(aout_stream_t *s, block_t *block, vlc_tick_t date)
             msg_Err(s, "cannot release buffer (error 0x%lX)", hr);
             break;
         }
-        IAudioClient_Start(sys->client);
 
         block->p_buffer += copy;
         block->i_buffer -= copy;
@@ -216,13 +281,29 @@ out:
     return hr;
 }
 
+static HRESULT ResetTimerAndStop(aout_stream_t *s)
+{
+    aout_stream_sys_t *sys = s->sys;
+    if (sys->hTimer != NULL)
+    {
+        DeleteTimerQueueTimer(NULL, sys->hTimer, INVALID_HANDLE_VALUE);
+        sys->hTimer = NULL;
+    }
+    if (atomic_load(&sys->started_state) == STARTED_STATE_OK)
+        return IAudioClient_Stop(sys->client);
+    return S_OK;
+}
+
 static HRESULT Pause(aout_stream_t *s, bool paused)
 {
     aout_stream_sys_t *sys = s->sys;
     HRESULT hr;
 
     if (paused)
-        hr = IAudioClient_Stop(sys->client);
+    {
+        hr = ResetTimerAndStop(s);
+        /* Don't reset the timer state, we won't have to start deferred again. */
+    }
     else
         hr = IAudioClient_Start(sys->client);
     if (FAILED(hr))
@@ -236,7 +317,9 @@ static HRESULT Flush(aout_stream_t *s)
     aout_stream_sys_t *sys = s->sys;
     HRESULT hr;
 
-    IAudioClient_Stop(sys->client);
+    ResetTimerAndStop(s);
+    /* Reset the timer state, the next start need to be deferred. */
+    atomic_store(&sys->started_state, STARTED_STATE_INIT);
 
     hr = IAudioClient_Reset(sys->client);
     if (SUCCEEDED(hr))
@@ -466,7 +549,8 @@ static void Stop(aout_stream_t *s)
 {
     aout_stream_sys_t *sys = s->sys;
 
-    IAudioClient_Stop(sys->client); /* should not be needed */
+    ResetTimerAndStop(s);
+
     CloseHandle(sys->hEvent);
     IAudioClient_Release(sys->client);
 
@@ -486,6 +570,8 @@ static HRESULT Start(aout_stream_t *s, audio_sample_format_t *restrict pfmt,
         return E_OUTOFMEMORY;
     sys->client = NULL;
     sys->hEvent = NULL;
+    sys->hTimer = NULL;
+    atomic_init(&sys->started_state, STARTED_STATE_INIT);
 
     /* Configure audio stream */
     WAVEFORMATEXTENSIBLE_IEC61937 wf_iec61937;
-- 
2.20.1



More information about the vlc-devel mailing list