mmdevapi buffering

Joerg-Cyril.Hoehle at Joerg-Cyril.Hoehle at
Fri Jun 3 04:46:29 CDT 2011


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)

	Jörg Höhle

More information about the wine-devel mailing list