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