Andrew Eikum : winealsa: Avoid underrun by adding a lead-in when starting with few samples.

Alexandre Julliard julliard at winehq.org
Fri Jan 27 11:30:41 CST 2012


Module: wine
Branch: master
Commit: 7c52a257fb74c21e59318becf70dacffa5f9d720
URL:    http://source.winehq.org/git/wine.git/?a=commit;h=7c52a257fb74c21e59318becf70dacffa5f9d720

Author: Andrew Eikum <aeikum at codeweavers.com>
Date:   Mon Jan 23 11:00:14 2012 -0600

winealsa: Avoid underrun by adding a lead-in when starting with few samples.

---

 dlls/winealsa.drv/mmdevdrv.c |   63 ++++++++++++++++++++++++++++++++++--------
 1 files changed, 51 insertions(+), 12 deletions(-)

diff --git a/dlls/winealsa.drv/mmdevdrv.c b/dlls/winealsa.drv/mmdevdrv.c
index fedd040..fdb069a 100644
--- a/dlls/winealsa.drv/mmdevdrv.c
+++ b/dlls/winealsa.drv/mmdevdrv.c
@@ -52,6 +52,7 @@ WINE_DECLARE_DEBUG_CHANNEL(winediag);
 
 static const REFERENCE_TIME DefaultPeriod = 100000;
 static const REFERENCE_TIME MinimumPeriod = 50000;
+#define                     EXTRA_SAFE_RT   40000
 
 struct ACImpl;
 typedef struct ACImpl ACImpl;
@@ -112,6 +113,7 @@ struct ACImpl {
     UINT64 written_frames, last_pos_frames;
     UINT32 bufsize_frames, held_frames, tmp_buffer_frames, mmdev_period_frames;
     UINT32 lcl_offs_frames; /* offs into local_buffer where valid data starts */
+    UINT32 hidden_frames;   /* ALSA reserve to ensure continuous rendering */
 
     HANDLE timer;
     BYTE *local_buffer, *tmp_buffer;
@@ -961,8 +963,6 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface,
     This->mmdev_period_frames = MulDiv(fmt->nSamplesPerSec,
             This->mmdev_period_rt, 10000000);
 
-    This->bufsize_frames = MulDiv(duration, fmt->nSamplesPerSec, 10000000);
-
     /* Buffer 4 ALSA periods if large enough, else 4 mmdevapi periods */
     This->alsa_bufsize_frames = This->mmdev_period_frames * 4;
     if(err < 0 || alsa_period_us < period / 10)
@@ -1034,6 +1034,15 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface,
         goto exit;
     }
 
+    /* Bear in mind weird situations where
+     * ALSA period (50ms) > mmdevapi buffer (3x10ms)
+     * or surprising rounding as seen with 22050x8x1 with Pulse:
+     * ALSA period 220 vs.  221 frames in mmdevapi and
+     *      buffer 883 vs. 2205 frames in mmdevapi! */
+    This->bufsize_frames = MulDiv(duration, fmt->nSamplesPerSec, 10000000);
+    This->hidden_frames = This->alsa_period_frames + This->mmdev_period_frames +
+        MulDiv(fmt->nSamplesPerSec, EXTRA_SAFE_RT, 10000000);
+
     /* Check if the ALSA buffer is so small that it will run out before
      * the next MMDevAPI period tick occurs. Allow a little wiggle room
      * with 120% of the period time. */
@@ -1147,11 +1156,18 @@ static HRESULT WINAPI AudioClient_GetStreamLatency(IAudioClient *iface,
         return AUDCLNT_E_NOT_INITIALIZED;
     }
 
-    LeaveCriticalSection(&This->lock);
+    /* Hide some frames in the ALSA buffer.  Allows to return GetCurrentPadding=0
+     * yet have enough data left to play (as if it were in native's mixer). Add:
+     * + mmdevapi_period such that at the end of it, ALSA still has data;
+     * + EXTRA_SAFE (~4ms) to allow for late callback invocation / fluctuation;
+     * + alsa_period such that ALSA always has at least one period to play. */
+    if(This->dataflow == eRender)
+        *latency = MulDiv(This->hidden_frames, 10000000, This->fmt->nSamplesPerSec);
+    else
+        *latency = MulDiv(This->alsa_period_frames, 10000000, This->fmt->nSamplesPerSec)
+                 + This->mmdev_period_rt;
 
-    /* one mmdevapi period plus one period we hide in the ALSA buffer */
-    *latency = MulDiv(This->alsa_period_frames, 10000000, This->fmt->nSamplesPerSec)
-             + This->mmdev_period_rt;
+    LeaveCriticalSection(&This->lock);
 
     return S_OK;
 }
@@ -1473,11 +1489,11 @@ static HRESULT WINAPI AudioClient_GetDevicePeriod(IAudioClient *iface,
 }
 
 static snd_pcm_sframes_t alsa_write_best_effort(snd_pcm_t *handle, BYTE *buf,
-        snd_pcm_uframes_t frames, ACImpl *This)
+        snd_pcm_uframes_t frames, ACImpl *This, BOOL mute)
 {
     snd_pcm_sframes_t written;
 
-    if(This->session->mute){
+    if(mute){
         int err;
         if((err = snd_pcm_format_set_silence(This->alsa_format, buf,
                         frames * This->fmt->nChannels)) < 0)
@@ -1510,8 +1526,8 @@ static snd_pcm_sframes_t alsa_write_best_effort(snd_pcm_t *handle, BYTE *buf,
 
 static void alsa_write_data(ACImpl *This)
 {
-    snd_pcm_sframes_t written;
-    snd_pcm_uframes_t to_write, avail, write_limit, max_period, in_alsa;
+    snd_pcm_sframes_t written, in_alsa;
+    snd_pcm_uframes_t to_write, avail, write_limit, max_period;
     int err;
     BYTE *buf =
         This->local_buffer + (This->lcl_offs_frames * This->fmt->nBlockAlign);
@@ -1557,7 +1573,29 @@ static void alsa_write_data(ACImpl *This)
 
     to_write = min(to_write, write_limit);
 
-    written = alsa_write_best_effort(This->pcm_handle, buf, to_write, This);
+    /* Add a lead-in when starting with too few frames to ensure
+     * continuous rendering.  Additional benefit: Force ALSA to start.
+     * GetPosition continues to reflect the speaker position because
+     * snd_pcm_delay includes buffered frames in its total delay
+     * and last_pos_frames prevents moving backwards. */
+    if(!in_alsa && This->held_frames < This->hidden_frames){
+        UINT32 s_frames = This->hidden_frames - This->held_frames;
+        BYTE *silence = HeapAlloc(GetProcessHeap(), 0,
+                s_frames * This->fmt->nBlockAlign);
+
+        if(silence){
+            in_alsa = alsa_write_best_effort(This->pcm_handle,
+                silence, s_frames, This, TRUE);
+            TRACE("lead-in %ld\n", in_alsa);
+            HeapFree(GetProcessHeap(), 0, silence);
+            if(in_alsa <= 0)
+                return;
+        }else
+            WARN("Couldn't allocate lead-in, expect underrun\n");
+    }
+
+    written = alsa_write_best_effort(This->pcm_handle, buf, to_write, This,
+            This->session->mute);
     if(written < 0){
         WARN("Couldn't write: %ld (%s)\n", written, snd_strerror(written));
         return;
@@ -1575,7 +1613,8 @@ static void alsa_write_data(ACImpl *This)
     if(This->held_frames && (written < write_limit)){
         /* wrapped and have some data back at the start to write */
         written = alsa_write_best_effort(This->pcm_handle, This->local_buffer,
-                min(This->held_frames, write_limit - written), This);
+                min(This->held_frames, write_limit - written), This,
+                This->session->mute);
         if(written < 0){
             WARN("Couldn't write: %ld (%s)\n", written, snd_strerror(written));
             return;




More information about the wine-cvs mailing list