From 7c52a257fb74c21e59318becf70dacffa5f9d720 Mon Sep 17 00:00:00 2001 From: Andrew Eikum Date: Mon, 23 Jan 2012 11:00:14 -0600 Subject: [PATCH] winealsa: Avoid underrun by adding a lead-in when starting with few samples. --- dlls/winealsa.drv/mmdevdrv.c | 63 +++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/dlls/winealsa.drv/mmdevdrv.c b/dlls/winealsa.drv/mmdevdrv.c index fedd040a7e5..fdb069a333e 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;