On Fri, Oct 24, 2014 at 12:38:16AM +0600, Alexander E. Patrakov wrote: > 24.10.2014 00:08, Andrew Eikum wrote: > >I have a patch in-hand which improves the current driver > >significantly, but it isn't enough to fix the USB devices problem. If > >someone wants to review the winealsa driver with the patch applied and > >discuss solutions, let me know. I'd love to get this problem solved. > > If you send me the patch, I promise to have a look at it. But, see > my other reply why there cannot be a full solution within the ALSA > API. >
I've attached the patch here. It's been a while since I've worked on this, so I'm fuzzy on the details. It should apply on any recent Wine version, including today's. There's a comment above alsa_write_data() describing how the buffer is laid out and what the offsets point to. alsa_write_data() is where we actually send data to ALSA. AudioClient_Stop() and _Start() are where the stream is rewound and paused. AudioClient_Initialize() is where the device setup occurs. AudioClock_GetPosition() returns the number of played frames. Note that we explicitly toggle handle_underrun to TRUE in make_handle_underrun_config(). Toggling this off does seem to have an impact on USB devices. The underruns aren't fixed in either setting, but it's worth playing with. If I remember correctly, the problem with USB devices is that snd_pcm_avail_update() updates too infrequently. To keep low latency, we set up a buffer size of four ALSA periods (about 40ms, I think). But, snd_pcm_avail_update() updates less frequently than that, so we constantly hit underruns. Some Windows applications behave very badly with large latencies. I think the issue was more complicated than that, but I've forgotten the details in the meantime... I believe disabling BATCH mode on USB devices "fixes" everything with this patch applied, but I presume there was a reason it was introduced in the first place (see PA bug 66962). I could be wrong here, I never totally understood the issue. There are two important programs that I test with: -Any well-behaved dsound program (I use the "American McGee's Alice Demo"; "Marble Arena 1" is another good test program.) -Microsoft Games's Pinball (This uses winmm very abusively, with lots of starts and stops. Good at testing latency.) Once the driver behaves well on those two, I usually run it through the rest of my audio application gauntlet to look for issues. Happy for feedback on any part of the driver. Thanks, Andrew
commit 9ef2799e4545ab1be7440544d45acfd374b4d2e5 Author: Andrew Eikum <aei...@codeweavers.com> Date: Tue Apr 15 10:41:20 2014 -0500 mmdevapi: More accurately track device position diff --git a/dlls/mmdevapi/tests/render.c b/dlls/mmdevapi/tests/render.c index 44472a6..00f5585 100644 --- a/dlls/mmdevapi/tests/render.c +++ b/dlls/mmdevapi/tests/render.c @@ -1020,7 +1020,7 @@ static void test_clock(int share) ok(hr == S_OK, "GetPosition failed: %08x\n", hr); ok(pos >= last, "Position %u vs. last %u\n", (UINT)pos,(UINT)last); last = pos; - if(/*share &&*/ winetest_debug>1) todo_wine + if(/*share &&*/ winetest_debug>1) ok(pos*1000/freq <= slept*1.1, "Position %u too far after stop %ums\n", (UINT)pos, slept); hr = IAudioClient_Start(ac); /* #2 */ @@ -1054,7 +1054,7 @@ static void test_clock(int share) ok(pos * pwfx->nSamplesPerSec <= sum * freq, "Position %u > written %u\n", (UINT)pos, sum); /* Prove that Stop must not drop frames (in shared mode). */ ok(pad ? pos > last : pos >= last, "Position %u vs. last %u\n", (UINT)pos,(UINT)last); - if (share && pad > 0 && winetest_debug>1) todo_wine + if (share && pad > 0 && winetest_debug>1) ok(pos*1000/freq <= slept*1.1, "Position %u too far after playing %ums\n", (UINT)pos, slept); /* in exclusive mode, testbot's w7 machines yield pos > sum-pad */ if(/*share &&*/ winetest_debug>1) @@ -1133,7 +1133,7 @@ static void test_clock(int share) ok(pos >= last, "Position %u vs. last %u\n", (UINT)pos,(UINT)last); ok(pcpos > pcpos0, "pcpos should increase\n"); ok(pos * pwfx->nSamplesPerSec <= sum * freq, "Position %u > written %u\n", (UINT)pos, sum); - if (pad > 0 && winetest_debug>1) todo_wine + if (pad > 0 && winetest_debug>1) ok(pos*1000/freq <= slept*1.1, "Position %u too far after stop %ums\n", (UINT)pos, slept); if(winetest_debug>1) ok(pos * pwfx->nSamplesPerSec == (sum-pad) * freq, @@ -1237,7 +1237,7 @@ static void test_clock(int share) /* ok(hr == AUDCLNT_E_BUFFER_TOO_LARGE || (hr == S_OK && i==0) without todo_wine */ ok(hr == S_OK || hr == AUDCLNT_E_BUFFER_TOO_LARGE, "GetBuffer large (%u) failed: %08x\n", avail, hr); - if(hr == S_OK && i) todo_wine ok(FALSE, "GetBuffer large (%u) at iteration %d\n", avail, i); + if(hr == S_OK && i) ok(FALSE, "GetBuffer large (%u) at iteration %d\n", avail, i); /* Only the first iteration should allow that large a buffer * as prefill was drained during the first 350+100ms sleep. * Afterwards, only 100ms of data should find room per iteration. */ diff --git a/dlls/winealsa.drv/mmdevdrv.c b/dlls/winealsa.drv/mmdevdrv.c index a4c02bf..6012f52 100644 --- a/dlls/winealsa.drv/mmdevdrv.c +++ b/dlls/winealsa.drv/mmdevdrv.c @@ -95,10 +95,12 @@ struct ACImpl { LONG ref; snd_pcm_t *pcm_handle; - snd_pcm_uframes_t alsa_bufsize_frames, alsa_period_frames; + snd_pcm_uframes_t alsa_bufsize_frames, alsa_period_frames, safe_rewind_frames; snd_pcm_hw_params_t *hw_params; /* does not hold state between calls */ snd_pcm_format_t alsa_format; + LARGE_INTEGER last_period_time; + IMMDevice *parent; IUnknown *pUnkFTMarshal; @@ -121,9 +123,10 @@ struct ACImpl { UINT32 lcl_offs_frames; /* offs into local_buffer where valid data starts */ UINT32 wri_offs_frames; /* where to write fresh data in local_buffer */ UINT32 hidden_frames; /* ALSA reserve to ensure continuous rendering */ + UINT32 data_in_alsa_frames; HANDLE timer; - BYTE *local_buffer, *tmp_buffer, *remapping_buf; + BYTE *local_buffer, *tmp_buffer, *remapping_buf, *silence_buf; LONG32 getbuf_last; /* <0 when using tmp_buffer */ CRITICAL_SECTION lock; @@ -1218,6 +1221,18 @@ static HRESULT map_channels(ACImpl *This, const WAVEFORMATEX *fmt) return S_OK; } +static void silence_buffer(ACImpl *This, BYTE *buffer, UINT32 frames) +{ + WAVEFORMATEXTENSIBLE *fmtex = (WAVEFORMATEXTENSIBLE*)This->fmt; + if((This->fmt->wFormatTag == WAVE_FORMAT_PCM || + (This->fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))) && + This->fmt->wBitsPerSample == 8) + memset(buffer, 128, frames * This->fmt->nBlockAlign); + else + memset(buffer, 0, frames * This->fmt->nBlockAlign); +} + static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface, AUDCLNT_SHAREMODE mode, DWORD flags, REFERENCE_TIME duration, REFERENCE_TIME period, const WAVEFORMATEX *fmt, @@ -1397,7 +1412,7 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface, if((err = snd_pcm_sw_params_set_start_threshold(This->pcm_handle, sw_params, 1)) < 0){ - WARN("Unable set start threshold to 0: %d (%s)\n", err, snd_strerror(err)); + WARN("Unable set start threshold to 1: %d (%s)\n", err, snd_strerror(err)); hr = AUDCLNT_E_ENDPOINT_CREATE_FAILED; goto exit; } @@ -1432,6 +1447,8 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface, This->bufsize_frames -= This->bufsize_frames % This->mmdev_period_frames; This->hidden_frames = This->alsa_period_frames + This->mmdev_period_frames + MulDiv(fmt->nSamplesPerSec, EXTRA_SAFE_RT, 10000000); + /* leave no less than about 1.33ms or 256 bytes of data after a rewind */ + This->safe_rewind_frames = max(256 / fmt->nBlockAlign, MulDiv(133, fmt->nSamplesPerSec, 100000)); /* 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 @@ -1440,22 +1457,27 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface, FIXME("ALSA buffer time is too small. Expect underruns. (%lu < %u * 1.2)\n", This->alsa_bufsize_frames, This->mmdev_period_frames); + This->fmt = clone_format(fmt); + if(!This->fmt){ + hr = E_OUTOFMEMORY; + goto exit; + } + This->local_buffer = HeapAlloc(GetProcessHeap(), 0, This->bufsize_frames * fmt->nBlockAlign); if(!This->local_buffer){ hr = E_OUTOFMEMORY; goto exit; } - if (fmt->wBitsPerSample == 8) - memset(This->local_buffer, 128, This->bufsize_frames * fmt->nBlockAlign); - else - memset(This->local_buffer, 0, This->bufsize_frames * fmt->nBlockAlign); + silence_buffer(This, This->local_buffer, This->bufsize_frames); - This->fmt = clone_format(fmt); - if(!This->fmt){ + This->silence_buf = HeapAlloc(GetProcessHeap(), 0, + This->alsa_period_frames * This->fmt->nBlockAlign); + if(!This->silence_buf){ hr = E_OUTOFMEMORY; goto exit; } + silence_buffer(This, This->silence_buf, This->alsa_period_frames); This->vols = HeapAlloc(GetProcessHeap(), 0, fmt->nChannels * sizeof(float)); if(!This->vols){ @@ -1870,7 +1892,7 @@ static HRESULT WINAPI AudioClient_GetDevicePeriod(IAudioClient *iface, if(defperiod) *defperiod = DefaultPeriod; if(minperiod) - *minperiod = MinimumPeriod; + *minperiod = DefaultPeriod; return S_OK; } @@ -1952,8 +1974,8 @@ static BYTE *remap_channels(ACImpl *This, BYTE *buf, snd_pcm_uframes_t frames) return This->remapping_buf; } -static snd_pcm_sframes_t alsa_write_best_effort(snd_pcm_t *handle, BYTE *buf, - snd_pcm_uframes_t frames, ACImpl *This, BOOL mute) +static snd_pcm_sframes_t alsa_write_best_effort(ACImpl *This, BYTE *buf, + snd_pcm_uframes_t frames, BOOL mute) { snd_pcm_sframes_t written; @@ -1967,7 +1989,7 @@ static snd_pcm_sframes_t alsa_write_best_effort(snd_pcm_t *handle, BYTE *buf, buf = remap_channels(This, buf, frames); - written = snd_pcm_writei(handle, buf, frames); + written = snd_pcm_writei(This->pcm_handle, buf, frames); if(written < 0){ int ret; @@ -1978,47 +2000,94 @@ static snd_pcm_sframes_t alsa_write_best_effort(snd_pcm_t *handle, BYTE *buf, WARN("writei failed, recovering: %ld (%s)\n", written, snd_strerror(written)); - ret = snd_pcm_recover(handle, written, 0); + ret = snd_pcm_recover(This->pcm_handle, written, 0); if(ret < 0){ WARN("Could not recover: %d (%s)\n", ret, snd_strerror(ret)); return ret; } - written = snd_pcm_writei(handle, buf, frames); + written = snd_pcm_writei(This->pcm_handle, buf, frames); } return written; } -/* The callback and mmdevapi API functions execute concurrently. - * Shared state & life time after Start: - * This constant until _Release - *->pcm_handle likewise - *->fmt likewise - *->alsa_format, hidden_frames likewise - *->local_buffer, bufsize_frames, alsa_bufsize_frames likewise - *->event Read Only, even constant until _Release(!) - *->started Read Only from cb POV, constant if _Stop kills the cb +static snd_pcm_sframes_t alsa_write_buffer_wrap(ACImpl *This, BYTE *buf, + snd_pcm_uframes_t buflen, snd_pcm_uframes_t offs, + snd_pcm_uframes_t to_write) +{ + snd_pcm_sframes_t ret = 0; + + while(to_write){ + snd_pcm_uframes_t chunk; + snd_pcm_sframes_t tmp; + + if(offs + to_write > buflen) + chunk = buflen - offs; + else + chunk = to_write; + + tmp = alsa_write_best_effort(This, buf + offs * This->fmt->nBlockAlign, chunk, This->session->mute); + if(tmp < 0) + return ret; + if(!tmp) + break; + + ret += tmp; + to_write -= tmp; + offs += tmp; + offs %= buflen; + } + + return ret; +} + +static UINT buf_ptr_diff(UINT left, UINT right, UINT bufsize) +{ + if(left <= right) + return right - left; + return bufsize - (left - right); +} + +static UINT data_not_in_alsa(ACImpl *This) +{ + UINT32 diff; + + diff = buf_ptr_diff(This->lcl_offs_frames, This->wri_offs_frames, This->bufsize_frames); + if(diff) + return diff; + + return This->held_frames - This->data_in_alsa_frames; +} +/* Here's the buffer setup: + * + * vvvvvvvv sent to HW already + * vvvvvvvv in ALSA buffer but rewindable + * [dddddddddddddddd] ALSA buffer + * [dddddddddddddddd--------] mmdevapi buffer + * ^^^^^^^^ data_in_alsa_frames + * ^^^^^^^^^^^^^^^^ held_frames + * ^ lcl_offs_frames + * ^ wri_offs_frames + * + * GetCurrentPadding is held_frames * - *->held_frames is the only R/W object. - *->lcl_offs_frames/wri_offs_frames are written by one side exclusively: - * lcl_offs_frames by CaptureClient & write callback - * wri_offs_frames by read callback & RenderClient + * During period callback, we decrement held_frames, fill ALSA buffer, and move + * lcl_offs forward + * + * During Stop, we rewind the ALSA buffer */ static void alsa_write_data(ACImpl *This) { - snd_pcm_sframes_t written, in_alsa; - snd_pcm_uframes_t to_write, avail, write_limit, max_period; + snd_pcm_sframes_t written; + snd_pcm_uframes_t avail, max_copy_frames, data_frames_played; int err; - BYTE *buf = - This->local_buffer + This->lcl_offs_frames * This->fmt->nBlockAlign; /* this call seems to be required to get an accurate snd_pcm_state() */ avail = snd_pcm_avail_update(This->pcm_handle); - if(snd_pcm_state(This->pcm_handle) == SND_PCM_STATE_XRUN || - avail > This->alsa_bufsize_frames){ - TRACE("XRun state avail %ld, recovering\n", avail); + if(snd_pcm_state(This->pcm_handle) == SND_PCM_STATE_XRUN){ + TRACE("XRun state, recovering\n"); avail = This->alsa_bufsize_frames; @@ -2030,81 +2099,48 @@ static void alsa_write_data(ACImpl *This) if((err = snd_pcm_prepare(This->pcm_handle)) < 0) WARN("snd_pcm_prepare failed: %d (%s)\n", err, snd_strerror(err)); - }else - TRACE("pad: %ld\n", This->alsa_bufsize_frames - avail); - - if(This->held_frames == 0) - return; + } - if(This->lcl_offs_frames + This->held_frames > This->bufsize_frames) - to_write = This->bufsize_frames - This->lcl_offs_frames; - else - to_write = This->held_frames; + TRACE("avail: %ld\n", avail); - max_period = max(This->mmdev_period_frames, This->alsa_period_frames); + /* Add a lead-in when starting with too few frames to ensure + * continuous rendering. Additional benefit: Force ALSA to start. */ + if(This->data_in_alsa_frames == 0 && This->held_frames < This->alsa_period_frames) + alsa_write_best_effort(This, This->silence_buf, This->alsa_period_frames - This->held_frames, FALSE); - /* try to keep 3 ALSA periods or 3 MMDevAPI periods in the ALSA buffer and - * no more */ - write_limit = 0; - in_alsa = This->alsa_bufsize_frames - avail; - while(in_alsa + write_limit < max_period * 3) - write_limit += max_period; - if(write_limit == 0) - return; + if(This->started) + max_copy_frames = data_not_in_alsa(This); + else + max_copy_frames = 0; - to_write = min(to_write, write_limit); + data_frames_played = min(This->data_in_alsa_frames, avail); + This->data_in_alsa_frames -= data_frames_played; - /* 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"); - } + if(This->held_frames > data_frames_played){ + if(This->started) + This->held_frames -= data_frames_played; + }else + This->held_frames = 0; - 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; - } + while(avail && max_copy_frames){ + snd_pcm_uframes_t to_write; - This->lcl_offs_frames += written; - This->lcl_offs_frames %= This->bufsize_frames; - This->held_frames -= written; + to_write = min(avail, max_copy_frames); - if(written < to_write){ - /* ALSA buffer probably full */ - return; - } - - 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, - This->session->mute); - if(written < 0){ - WARN("Couldn't write: %ld (%s)\n", written, snd_strerror(written)); - return; - } + written = alsa_write_buffer_wrap(This, This->local_buffer, + This->bufsize_frames, This->lcl_offs_frames, to_write); + if(written <= 0) + break; + avail -= written; This->lcl_offs_frames += written; This->lcl_offs_frames %= This->bufsize_frames; - This->held_frames -= written; + This->data_in_alsa_frames += written; + max_copy_frames -= written; } + + if(This->event) + SetEvent(This->event); } static void alsa_read_data(ACImpl *This) @@ -2112,6 +2148,9 @@ static void alsa_read_data(ACImpl *This) snd_pcm_sframes_t nread; UINT32 pos = This->wri_offs_frames, limit = This->held_frames; + if(!This->started) + goto exit; + /* FIXME: Detect overrun and signal DATA_DISCONTINUITY * How to count overrun frames and report them as position increase? */ limit = This->bufsize_frames - max(limit, pos); @@ -2153,6 +2192,10 @@ static void alsa_read_data(ACImpl *This) This->wri_offs_frames += nread; This->wri_offs_frames %= This->bufsize_frames; This->held_frames += nread; + +exit: + if(This->event) + SetEvent(This->event); } static void CALLBACK alsa_push_buffer_data(void *user, BOOLEAN timer) @@ -2161,17 +2204,55 @@ static void CALLBACK alsa_push_buffer_data(void *user, BOOLEAN timer) EnterCriticalSection(&This->lock); - if(This->started){ - if(This->dataflow == eRender) - alsa_write_data(This); - else if(This->dataflow == eCapture) - alsa_read_data(This); - } + QueryPerformanceCounter(&This->last_period_time); + + if(This->dataflow == eRender) + alsa_write_data(This); + else if(This->dataflow == eCapture) + alsa_read_data(This); LeaveCriticalSection(&This->lock); +} - if(This->event) - SetEvent(This->event); +static snd_pcm_uframes_t interp_elapsed_frames(ACImpl *This) +{ + LARGE_INTEGER time_freq, current_time, time_diff; + QueryPerformanceFrequency(&time_freq); + QueryPerformanceCounter(¤t_time); + time_diff.QuadPart = current_time.QuadPart - This->last_period_time.QuadPart; + return (time_diff.QuadPart * This->fmt->nSamplesPerSec) / time_freq.QuadPart; +} + +static int alsa_rewind_best_effort(ACImpl *This) +{ + snd_pcm_uframes_t len, leave; + + /* we can't use snd_pcm_rewindable, some PCM devices crash. so follow + * PulseAudio's example and rewind as much data as we believe is in the + * buffer, minus 1.33ms for safety. */ + + /* amount of data to leave in ALSA buffer */ + leave = interp_elapsed_frames(This) + This->safe_rewind_frames; + + if(This->held_frames < leave) + This->held_frames = 0; + else + This->held_frames -= leave; + + if(This->data_in_alsa_frames < leave) + len = 0; + else + len = This->data_in_alsa_frames - leave; + + TRACE("rewinding %lu frames, now held %u\n", len, This->held_frames); + + if(len) + /* snd_pcm_rewind return value is often broken, assume it succeeded */ + snd_pcm_rewind(This->pcm_handle, len); + + This->data_in_alsa_frames = 0; + + return len; } static HRESULT WINAPI AudioClient_Start(IAudioClient *iface) @@ -2201,6 +2282,29 @@ static HRESULT WINAPI AudioClient_Start(IAudioClient *iface) /* dump any data that might be leftover in the ALSA capture buffer */ snd_pcm_readi(This->pcm_handle, This->local_buffer, This->bufsize_frames); + }else{ + snd_pcm_sframes_t avail, written; + snd_pcm_uframes_t offs; + + avail = snd_pcm_avail_update(This->pcm_handle); + avail = min(avail, This->held_frames); + + if(This->wri_offs_frames < This->held_frames) + offs = This->bufsize_frames - This->held_frames + This->wri_offs_frames; + else + offs = This->wri_offs_frames - This->held_frames; + + /* fill it with data */ + written = alsa_write_buffer_wrap(This, This->local_buffer, + This->bufsize_frames, offs, avail); + + if(written > 0){ + This->lcl_offs_frames = (offs + written) % This->bufsize_frames; + This->data_in_alsa_frames = written; + }else{ + This->lcl_offs_frames = offs; + This->data_in_alsa_frames = 0; + } } if(!This->timer){ @@ -2237,6 +2341,9 @@ static HRESULT WINAPI AudioClient_Stop(IAudioClient *iface) return S_FALSE; } + if(This->dataflow == eRender) + alsa_rewind_best_effort(This); + This->started = FALSE; LeaveCriticalSection(&This->lock); @@ -2466,18 +2573,6 @@ static ULONG WINAPI AudioRenderClient_Release(IAudioRenderClient *iface) return AudioClient_Release(&This->IAudioClient_iface); } -static void silence_buffer(ACImpl *This, BYTE *buffer, UINT32 frames) -{ - WAVEFORMATEXTENSIBLE *fmtex = (WAVEFORMATEXTENSIBLE*)This->fmt; - if((This->fmt->wFormatTag == WAVE_FORMAT_PCM || - (This->fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && - IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))) && - This->fmt->wBitsPerSample == 8) - memset(buffer, 128, frames * This->fmt->nBlockAlign); - else - memset(buffer, 0, frames * This->fmt->nBlockAlign); -} - static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface, UINT32 frames, BYTE **data) { @@ -2832,12 +2927,8 @@ static HRESULT WINAPI AudioClock_GetPosition(IAudioClock *iface, UINT64 *pos, UINT64 *qpctime) { ACImpl *This = impl_from_IAudioClock(iface); - UINT64 written_frames, position; - UINT32 held_frames; - int err; + UINT64 position; snd_pcm_state_t alsa_state; - snd_pcm_uframes_t avail_frames; - snd_pcm_sframes_t delay_frames; TRACE("(%p)->(%p, %p)\n", This, pos, qpctime); @@ -2846,40 +2937,37 @@ static HRESULT WINAPI AudioClock_GetPosition(IAudioClock *iface, UINT64 *pos, EnterCriticalSection(&This->lock); - /* call required to get accurate snd_pcm_state() */ - avail_frames = snd_pcm_avail_update(This->pcm_handle); + /* avail_update required to get accurate snd_pcm_state() */ + snd_pcm_avail_update(This->pcm_handle); alsa_state = snd_pcm_state(This->pcm_handle); - written_frames = This->written_frames; - held_frames = This->held_frames; - - err = snd_pcm_delay(This->pcm_handle, &delay_frames); - if(err < 0){ - /* old Pulse, shortly after start */ - WARN("snd_pcm_delay failed in state %u: %d (%s)\n", alsa_state, err, snd_strerror(err)); - } if(This->dataflow == eRender){ - position = written_frames - held_frames; /* maximum */ - if(!This->started || alsa_state > SND_PCM_STATE_RUNNING) - ; /* mmdevapi stopped or ALSA underrun: pretend everything was played */ - else if(err<0 || delay_frames > position - This->last_pos_frames) - /* Pulse bug: past underrun, despite recovery, avail_frames & delay - * may be larger than alsa_bufsize_frames, as if cumulating frames. */ - /* Pulse bug: EIO(-5) shortly after starting: nothing played */ - position = This->last_pos_frames; - else if(delay_frames > 0) - position -= delay_frames; + position = This->written_frames - This->held_frames; + + if(This->started && alsa_state == SND_PCM_STATE_RUNNING && This->held_frames) + /* we should be using snd_pcm_delay here, but it is broken + * especially during ALSA device underrun. instead, let's just + * interpolate between periods with the system timer. */ + position += interp_elapsed_frames(This); + + position = min(position, This->written_frames - This->held_frames + This->mmdev_period_frames); + + position = min(position, This->written_frames); }else - position = written_frames + held_frames; + position = This->written_frames + This->held_frames; /* ensure monotic growth */ - This->last_pos_frames = position; + if(position < This->last_pos_frames) + position = This->last_pos_frames; + else + This->last_pos_frames = position; + + TRACE("frames written: %u, held: %u, state: 0x%x, position: %u\n", + (UINT32)(This->written_frames%1000000000), This->held_frames, + alsa_state, (UINT32)(position%1000000000)); LeaveCriticalSection(&This->lock); - TRACE("frames written: %u, held: %u, avail: %ld, delay: %ld state %d, pos: %u\n", - (UINT32)(written_frames%1000000000), held_frames, - avail_frames, delay_frames, alsa_state, (UINT32)(position%1000000000)); if(This->share == AUDCLNT_SHAREMODE_SHARED) *pos = position * This->fmt->nBlockAlign; else
_______________________________________________ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss