Hi,
I have one big concern about MSDN's "Rendering a stream" (shared mode) example w.r.t wrap-around of buffer pointers and how Wine implements it. The example is at http://msdn.microsoft.com/en-us/library/dd316756(v=vs.85).aspx
Suppose the write pointer is near the end of the buffer and the app is a little late, so the HW read pointer is not far away.
|---------------------------------------------------^read------^write-----|
What is GetCurrentPadding going to return? - the tiny write-read portion, says MSDN AFAICT - 0, because there's a new fresh buffer waiting to be filled?
MSDN's example then computes avail = GetBufferSize() - GetCurrentPadding(); What is GetBuffer(avail) going to return?!?
- If it's a pointer to the above buffer, it can only succeed if avail is tiny, otherwise there's a buffer overflow. However avail, as defined above and in MSDN's example, will be large. Wine tricks around that by returning a dynamic local tmp buffer larger than GetBufferSize (I wonder why a fixed one instead, twice GetBufferSize would not suffice?) Does native behave like that? - E_BUFFER_TOO_LARGE ? The MSDN example would exit. - Could it be a pointer to the beginning of the buffer? - Or it's a pointer to another buffer! That's not compatible with Wine's current local_buffer implementation... Isn't Wine's mmdevapi going to produces glitches?
The problematic situation is when avail is small, as the following sleep for half a buffer size in MSDN's example will inevitably produce an xrun. So what actually is the buffer size and how does GetCurrentPadding behave?
I don't expect MSDN's example to be fundamentally flawed. IMHO, they designed their API such that it is the typical use case. I believe that multiple buffers (of size GetBufferSize) are involved (much like ALSA's periods) and that GetBuffer switches among them -- exactly like they say about the exclusive mode ping pong.
I suppose native's mixer (in shared mode) would mix these possibly incompletely filled buffers and everything is fine there. No need to deal with wrap-around!
Why should Wine do something entirely different?
Regards, Jörg Höhle
On 06/01/2011 07:33 AM, Joerg-Cyril.Hoehle@t-systems.com wrote:
The problematic situation is when avail is small, as the following sleep for half a buffer size in MSDN's example will inevitably produce an xrun. So what actually is the buffer size and how does GetCurrentPadding behave?
I don't expect MSDN's example to be fundamentally flawed. IMHO, they designed their API such that it is the typical use case. I believe that multiple buffers (of size GetBufferSize) are involved (much like ALSA's periods) and that GetBuffer switches among them -- exactly like they say about the exclusive mode ping pong.
I don't think I understand your objection. I have been thinking of the buffer as a FIFO queue with a size limit. Then, GetCurrentPadding() returns "write - read" and GetBufferSize() returns the queue size limit. As far as I can tell, this is entirely consistent with how Windows behaves. You can especially see this with the GetBuffer() failure tests (see <dlls/mmdevapi/tests/render.c:509> for example).
What behavior do you think Wine is going to do wrong because of this model?
Thanks, Andrew
Hi,
Andrew Eikum wrote:
Then, GetCurrentPadding() returns "write - read" and GetBufferSize() returns the queue size limit. As far as I can tell, this is entirely consistent with how Windows behaves.
I added some lines to mmdevapi/tests/render.c to find out more about mmdevapi with the help of testbot. Here's a summary of the findings.
- GetBuffer(SamplesPerSec/2) =0.5s worth of samples succeeds, i.e. huge buffers are supported (mmdevapi/tests/render.c:test_clock)
- GetCurrentPadding increases from 0 by frames_written while the stream is not yet started (mmdevapi/tests/render.c:test_padding). -- That's what the tests already in Wine showed me.
- After Initialize(duration=0.5s), 0.5s is the hard limit on how much you can feed into the engine (*both* in one call or split in two parts). I.e. the app will never be 2s in advance over the engine because it cannot write 4 x 0.5s. (More precisely, MSDN says the size is at least what was asked for and GetBufferSize yields the actual size.)
- IAudioClock_GetPosition "arbitrary" units look like bytes. (MSDN says "e.g. bytes"). Position/GetFrequency yields seconds.
- GetCurrentPadding appears to decrease by multiples of 10ms as time advances (sleep), e.g. by 4800 +/- 480 after sleep(100ms). Hence it is not related to a HW position pointer. Once started, GetCurrentPadding decreases in amounts directly proportional to elapsed time. Unlike Wine, it is *not* the case that it would decrease a lot when you feed it a lot.
Omitting underruns, we can summarize as follows:
GetCurrentPadding = ( sum(written) - (rate / playtime - late modulo 10ms) ) modulo GetBufferSize where <late> accounts for a little setup time after Start.
- IAudioClock_GetPosition does not reveal a multiplicator, hence it could be related to either HW position or linear time, but not the 10ms chunks.
To me, GetCurrentPadding clearly reasons it terms of the "front stage", i.e. the 10ms chunks that it feeds into the audio engine, regardless of how much the engine played. OTOH, GetPosition retrieves either an actual "back stage" HW position or simply returns wall clock time (MSDN says about GetPosition that high performance time and position "correlate" - as if it expresses time in "position" units, e.g. bytes - yet also "the sample currently playing through the speakers" - truly a HW position).
- GetBuffer appears to use one main and one secondary buffer. The second one can hold at least as much as GetBufferSize * 15/16th. These buffers are not treated equally, e.g. no ping pong; the main one is used preferably, with varying offsets into it.
- Native may copy data written to the secondary buffer somewhere else (GetBuffer returned the same pointer twice in a row with not enough time elapsed for the previously written data to be processed by HW).
I'm unsure whether using one internal buffer of size 2xGetBufferSize would be enough to avoid copying (2x size resembles exclusive mode's ping pong).
What was not tested: - native machine instead of testbot's vmware - rendering mode other than shared with 500ms duration - repeated start/stop - SetEvent - power suspend/resume (impossible with testbot) - CPU frequency scaling (not possible with vmware?) - duration of each call -- which one takes a long time to return, what is delegated to concurrent threads? - listening to sound -- only playing silence
I believe reproducing timing is as important for Wine as producing the same functional output as native given some input.
How does Wine differ? Compare testbot job 11396 with Wine's output below.
- Given a 0.5s buffer, Wine accepts 4 times nearly GetBufferSize frames within 650ms. Native accepts that at most twice (0.5 prefill, then another nearly 0.5s fits within 0.650s playtime, e.g. after playing for 450ms). - Wine's GetCurrentPadding returns 0 three times after Start, ignoring the huge amount of data written so far. 0 => "can write GetBufferSize frames". - Wine's GetCurrentPadding was != 0 only after 500ms of playtime. - Wine's GetPosition is highly non-linear, initially incrementing by 22500 within 100ms, later only 4310-5000. This looks more like a fill pointer than actual play position or clock time. - I suggest having Wine's GetPosition return bytes too, not samples.
render.c:565: Clock Frequency 48000 render.c:583: data at 0x12beb0 render.c:590: padding 24000 prior to start render.c:621: padding 0 past sleep #2 render.c:628: padding 0 past stop #1 render.c:634: position 24000 render.c:650: data at 0x12beb0 render.c:657: padding 24000 past reset+write render.c:672: position 24000 render.c:679: padding 0 past stop #2 render.c:684: position 24000 render.c:698: data at 0x12beb0 to fill (large 22500, 15/16 of 24000) render.c:710: hpctime 353 after 350ms - QueryPerformanceCounter render.c:722: padding 0 position 22500 iteration 0 render.c:730: data at 0x12beb0 (large 22500) render.c:743: hpctime 454ms render.c:722: padding 0 position 45000 iteration 1 render.c:730: data at 0x12beb0 (large 22500) render.c:743: hpctime 555ms render.c:722: padding 0 position 67500 iteration 2 render.c:730: data at 0x12beb0 (large 22500) render.c:743: hpctime 653ms render.c:722: padding 6119 position 83881 iteration 3 render.c:727: Test failed: GetBuffer large (22500) failed: 88890006 render.c:737: data at 0x12beb0 (small 17881) render.c:743: hpctime 755 render.c:722: padding 19690 position 88191 iteration 4 render.c:727: Test failed: GetBuffer large (22500) failed: 88890006 render.c:737: data at 0x12beb0 (small 4310) render.c:743: hpctime 853 render.c:722: padding 18732 position 93459 iteration 5 render.c:727: Test failed: GetBuffer large (22500) failed: 88890006 render.c:737: data at 0x12beb0 (small 5268) render.c:743: hpctime 954 render.c:722: padding 19691 position 97768 iteration 6 render.c:727: Test failed: GetBuffer large (22500) failed: 88890006 render.c:737: data at 0x12beb0 (small 4309)
Regards, Jörg Höhle