AW: Major mmdevapi and winmm audio bugs

Maarten Lankhorst m.b.lankhorst at
Wed Feb 29 10:33:10 CST 2012

Hey Joerg,

Having actually finished the pulseaudio implementation and trying to fix some fallout.

Op 29-02-12 16:28, Joerg-Cyril.Hoehle at schreef:
> Hi,
> Maarten Lankhorst wrote:
>> For capture I plan to just add 2 queues, one for readable packets, and one
>> for packets that can be filled for reading.
> Winecoreaudio uses exactly this.
Working now, with timestamps so all tests pass.

IAudioClock::GetPosition I have to figure out still, I was allowing
it to update based on stream time passed, but dsound fails with
that, so I have to determine windows' behavior some more.
>> ... for the pulse driver, I'm using the auto timing
>> update feature and the callback for when rendering is not running.
>> When the buffer is not underflowed, I'm using the write callback to
>> signal more data is available.
> I'm not familiar with PA's API, so I'm not following you.  I'm not even
> sure we talk about the same thing.
Because pulseaudio receives all data on mainloop you can specify
callbacks that will be run when those events are received too. I'm
using the latency callback when first starting to signal the event
when no data is playing. When data is buffered I use the
pulse_rd_callback which fires every period because of
pa_stream_early_requests. pulse_wr_callback is fired when there's
at least a period buffered.

>> I believe the event should be triggered when there is actual data
>> available, at least that's how I understood the event is supposed
>> to work.
> I distinguish 2 events:
>  a) ALSA/PA backend ready for more data
>  b) mmdevapi SetEventHandle to the app
> Note that b) only exists when using EVENTCALLBACK.
> We have tests about b) that show that mmdevapi delivers events
> to the app even when stopped (Wine fails those with todo_wine). I
> don't know at what rate.
Presumably at the same rate, but since in those cases you
have no data I doubt it matters if you fire less often.

> Regarding a), nothing prevents you from using the back-end's
> favourite notification mechanism to wake up - or use a timer.
Which my winepulse does. :)

> I agree with you that ideally, somehow, b) and a) should be in sync,
> otherwise we'll have clock skew and the XAudio scenario will exhibit
> occasional underruns.
> But there are big HOWEVER:
> - ALSA/PA may not grant us the period we'd like (10ms). 21.333ms is common with ALSA/dmix.
>   I don't know how important the 10ms are.
>   You're familiar with DSound, so perhaps you have an opinion why DSound
>   has been using 10ms intervals in Wine regardless of the back-end's period.
>   I believe that Wine ought to use the same rate as native or some app will exhibit a bug.
If dmix doesn't support 10ms, set default period to 1024 samples
and use that, no point in trying to do something it's not. Don't
forget to update GetStreamLatency in that case, though.

> - We do not need 10ms events when not using EVENTCALLBACK, so why not
>   write all available data (e.g. 333ms from winmm:PlaySound) and sleep
>   that much?  Laptop owners will thank us.
It's possible for winmm and dsound, but the drivers will have to keep
the default period in any case, for when other mmdevapi programs
rely on it.

> - How to continue signaling type a events after IAudioClient_Stop?
>   By continuing to write silence to the back-end?!?
Could work, I'm abusing the auto timing update from pulseaudio,
which fires periodically as well, even when playing nothing.

> - Last but not least, I don't know whether ALSA's signaling and mmdevapi's
>   SetEvent needs can be made compatible even when the periods would agree.
>   mmdevapi's SetEvent means "don't worry, you have one period time to write data"
>   or even "you may write data, but I already have buffered some".
>   ALSA/PA's signal means(?) "hurry up, you're on the verge of underrun". Please correct me.
As far as my understanding of mmdevapi is, it fires every period. If you have
some data gogogogo, but write at least GetStreamLatency worth of samples,
or you'll have an underrun. That also means mmdevapi rendering will have to
be periodic or bad things occur.

> - The test_worst_case aka. XAudio scenario has tough requirements:
>   - Signal events not often enough and you'll enter an underrun.
>   - Signal events too early and it'll write no data. The event was wasted and
>    the next one may come too late to avoid an underrun.
> In summary, if the back-end guarantees 10ms wake-ups, you should be able to
> nicely sync a) and b) (probably by writing silence when stopped...)
> If not, then some mechanism must be there to decouple the two.
Or never use anything but dmix/real hardware with winealsa and
just use winepulse for pulseaudio. You know the period size in that
case and can force a wake up. pulseaudio's alsa wrapper might
have issues because it will wait until at least one frame is filled
before marking it as such, in the worst case hiding the pulseaudio
native length from alsa applications.

> So far we've followed the "decouple" route in winealsa/oss. I believe the key to
> the solution in mmdevapi will come from both being able to vary the sleeping
> intervals to adapt to clock skew *and* not be bound to be exactly in sync with
> the back-end, thanks to a latency-inducing buffer large enough to compensate
> for the variability.
> IOW, the design goal is to reach during normal playback a state where:
>  Event received => GetCurrentPadding has (or pretends to have) decreased by
>  at least 1 period (which implies there's room for writing).
I think the key is to use accurate periods and report GetStreamLatency correctly.
Use it in wine where needed for winmm and dsound.


More information about the wine-devel mailing list