[Bug 28723] Sound stutter in Rage when emulated windows version is set to "Windows 7" (XAudio2 -> mmdevapi sound output path)

wine-bugs at winehq.org wine-bugs at winehq.org
Fri Dec 2 14:55:09 CST 2011


http://bugs.winehq.org/show_bug.cgi?id=28723

--- Comment #65 from Alexey Loukianov <mooroon2 at mail.ru> 2011-12-02 14:55:09 CST ---
Here are results for tests Jörg had asked to conduct in a off-bugzilla mail
message:

02.12.2011 14:29, Joerg-Cyril.Hoehle@<domain-stripped> wrote:
> - Native needs no prefill. Writing one period worth of samples at every event
> produces continuous sound.

>From now on when I write "native" I really mean "Windows 7 Started edition
32bit SP1 running on Acer Aspire One 522 laptop (AMD C-50 CPU/2Gb RAM/Conexant
HDA)". Hadn't done any tests under Vista yet as it turned out that the laptop
I've been given to carry tests on had an OS corrupted by viruses so ATM I'm
"having fun" reinstalling Vista on it :-).

Back to your question: I can confirm that feeding one period of data per event
produces continuous sound on native as long as system hadn't been busy doing
something else and missed the required timeframe because of that. For example,
heavy screen updates may cause stutters, e.t.c.

>  - That GetBuffer/ReleaseBuffer may even happen
> half a period after the event (even 8 from 10ms). (don't use Sleep(5000ms)
> for that, it's not precise. Prefer a busy loop with a high performance
> counter)
> 

Here's what I use for doing less than 10ms "sleeps":

void lx2Sleep(UINT32 dwMsec)
{
    LARGE_INTEGER qpcPosCur, qpcPosOld, qpcFreq;
    QueryPerformanceCounter(&qpcPosCur);
    QueryPerformanceFrequency(&qpcFreq);
    qpcPosOld = qpcPosCur;
    while(*(INT64*)&qpcPosCur < (*(INT64*)&qpcPosOld + *(INT64*)&qpcFreq *
dwMsec / 1000))
          QueryPerformanceCounter(&qpcPosCur);
}

Am I right that by "may even happen half a period after the event" you mean the
following sequence:

1. WaitForSingleObject(event, timeout);
2. "Sleep" for ~5ms.
3. GetBuf/Fill/ReleaseBuff.

To carry this test most likely it would be needed to rewrite my tone generator
not to call sin() for each output sample. I think it would be sufficient to
pre-calculate, say, 50ms of sine-wave and use it as a source data ring buffer
so system won't spend too much time doing float math in realtime. Would write 
a separate message with test results as soon as I would done this.

> I believe the mixer pre-exists, running regularly system wide.  Therefore, an
> app that calls Start should see the first event at seemingly random delays
> from 0 to 10 ms. (However IIRC, the first event only occurs after something
> has been written initially).

Yes, my observations on native confirms it: first event happens at seemingly
random interval ranging from 0 up to period ms time after the stream start. In
some rare circumstances I even been getting about ~20ms before the first event
- for example when the system been stressed by some other threads.

> - GetPosition freezes after Stop, even though there's some data still flowing
>   through the mixer. 
> - Does GetPosition bump to 'sum written - padding' after Stop? 

Important note: On native IAudioClock_GetPosition() seems to return devpos in a
"bytes played" units instead of "frames played". I.e. if I read devpos to be 0
and then send 441 frames of S16_PCM data - devpos would eventually end up equal
to 441 * bytes_per_sample * channels_qty = 1764. What I do in mmdev-test 
is recalculating devpos to frames units using following math:

devpos_frames = 1.f * IAudioClock_GetPosition() / IAudioClock_GetFrequency() *
            fmt.nSamplesPerSec; 

Answering your question, yes it is:
...
T:  1471.3ms S:   66304f P: 448f DP:   64384 dDP:1920
T:  1481.4ms S:   66752f P: 448f DP:   64829 dDP:1923
T:  1491.6ms S:   67200f P: 448f DP:   65280 dDP:1920
Stopped stream
T:  1501.7ms S:   67648f P: 448f DP:   67200 dDP: 448
T:  1509.0ms S:   67648f P: 448f DP:   67200 dDP: 448
T:  1513.9ms S:   67648f P: 448f DP:   67200 dDP: 448
...

Legend:
S - samples_stored; P - GetCurrentPadding; DP: GetPosition; dDP: (S - DP);

Stats at 1501.7ms are collected right after the call to IAC_Stop(). Notice
immediate bump of dDP from 1920f to 448f lag. IOW at the moment Stop() is
called DP is immediately set to be (S - P).

> - What's GetPosition immediately after Re-Starting?  Does it jump to
> count all data previously written?

No, as it had "jumped to count it" at the moment Stop() had been called.
Here's what's happening on native:
...
T:  2493.9ms S:   67648f P: 448f DP:   67200 dDP: 448
T:  2499.0ms S:   67648f P: 448f DP:   67200 dDP: 448
Started stream back
T:  2504.0ms S:   67648f P: 448f DP:   67200 dDP: 448
T:  2507.5ms S:   68096f P: 448f DP:   67200 dDP: 896
T:  2517.6ms S:   68544f P: 448f DP:   67219 dDP:1325
...

Stats are collected and displayed before data pump-out. At 2504.0ms a call to
IAC_Start() had been made, then stats had been colected and printed. As padding
allowed for another 448f - a chunk of audio data had been pumped out and
execution proceeded to the next loop iteration to eventually hit
WaitForSingleEvent(). At 2507.5ms an event fired (first one after stream
re-start). Devpos remained the same is was ~3.5ms ago, but the audio engine had
already consumed another 448f of samples from the output buffer. Next event
arrived ~10ms later, at 2517.6ms. At that moment audio engine consumed another
448 frames and DP just had started to move forward. Results are repeatable.

> ... I believe that Start after Stop can only resume from the currently available padding.

Testing results prove that you're right.

There's another interesting case to look at: suppose you had stopped pumping
the data out and monitor what happens:
...
Event loop finished, waiting at least 20.3ms for playback to finish...
T:  2011.6ms S:   88704f P: 448f DP:   87452 dDP:1252 
T:  2012.9ms S:   88704f P: 448f DP:   87509 dDP:1195 
T:  2014.1ms S:   88704f P: 448f DP:   87565 dDP:1139 
T:  2015.5ms S:   88704f P: 448f DP:   87624 dDP:1080 
T:  2016.9ms S:   88704f P: 448f DP:   87687 dDP:1017 
T:  2018.9ms S:   88704f P: 448f DP:   87774 dDP: 930 
T:  2020.6ms S:   88704f P:   0f DP:   87851 dDP: 853 
T:  2021.6ms S:   88704f P:   0f DP:   87895 dDP: 809 
T:  2022.6ms S:   88704f P:   0f DP:   87939 dDP: 765 
T:  2023.6ms S:   88704f P:   0f DP:   87983 dDP: 721 
T:  2024.6ms S:   88704f P:   0f DP:   88028 dDP: 676 
T:  2025.8ms S:   88704f P:   0f DP:   88080 dDP: 624 
T:  2027.1ms S:   88704f P:   0f DP:   88138 dDP: 566 
T:  2028.7ms S:   88704f P:   0f DP:   88208 dDP: 496 
T:  2030.5ms S:   88704f P:   0f DP:   88704 dDP:   0 
T:  2031.5ms S:   88704f P:   0f DP:   88704 dDP:   0 
T:  2032.5ms S:   88704f P:   0f DP:   88704 dDP:   0 
T:  2033.5ms S:   88704f P:   0f DP:   88704 dDP:   0 
T:  2035.4ms S:   88704f P:   0f DP:   88704 dDP:   0 

Final stats:
T:  2035.4ms S:   88704f P:   0f DP:   88704 dDP:   0 dAvgDP:1342.21
Average time between events:  10.20ms
Total events count: 197 (197 with non-zero DP delta)

What it interesting to note here is a final "jump" for dDP from 496 down to 0
in ~1.8ms. Looks like as soon as mixer pumps out last period into hw it
immediately updates the DP to be equal to samples_stored thus not reflecting
the fact that the the last period is still being played by HW.

> What I plan for Wine is to let GetPosition continue to grow after Stop,
> because the "sample that is currently playing through the speakers" will
> advance a little after stopping, due to HW or network latency.

That's incorrect - it contradicts both with MSDN docs and with observed
behavior on native. Your planned behavior is more like what's observed for
IAudioClock2_GetDevicePosition() - and that's expected behavior for
_real_low-level_device_position_ as actual HW continues to iterate through its
ring buffer. That is an important difference which is clearly documented in
MSDN: IAudioClock_GetPosition() is supposed to represent client-side of view of
"stream device", and a position returned by it is documented to freeze at
Stop() and continue to move forward after next call to Start(). Also it should
be possible to reset the position back to 0 by calling IAudioClient_Reset()
while stream is as "stopped" state.

-- 
Configure bugmail: http://bugs.winehq.org/userprefs.cgi?tab=email
Do not reply to this email, post in Bugzilla using the
above URL to reply.
------- You are receiving this mail because: -------
You are watching all bug changes.


More information about the wine-bugs mailing list