Hi,
Andrew Eikum replied:
- Questionable use of BUFFERFLAGS_SILENT in WOD_PushData. When no data is available now, the buffer is flooded with silence, hence data submitted shortly afterwards will be delayed by as much (PulseAudio will accumulate 2 seconds of buffer data...), not by a minimal amount.
Are you sure? BUFFERFLAGS_SILENT shouldn't silence the whole buffer, just insert avail_frames worth of silence, which is what was intended. We should only accumulate 2 seconds if there is a 2 second underrun from WinMM's client. That seems like sane behavior to me.
I opened issue #27937 which is related.
This may surprise people, but I argue that winmm and mmdevapi (in rendering mode) have *no* notion of underrun. Therefore there's nothing like "insert avail_frames of silence, which was intended" there.
- Send no data to winmm, and it will play nothing (silence). Send data again and it will start immediately (modulo HW period size). The API doesn't even tell you that a HW underrun occurred. You notice it indirectly when all your wave headers are returned...
- mmdevapi in SHARED MODE implements a mixer. Send it no data and it will mix that with all other currently playing sounds, possibly optimizing the case when no app sends any sound. Start sending data again and I *bet* it'll start immediately (modulo 10ms as the native mixer seems to operate in chunks that size). GetCurrentPadding of zero is an indirect indicator that an underrun likely happened (some data may still be buffered with USB devices...)
This is radically different from ALSA! Stop sending data for 10 seconds, and you need to either - restart or - quickly send 11 seconds worth of data, of which the first 10 will be ignored by ALSA!! Watch avail_update grow and snd_pcm_delay decrease towards minus infinity (modulo 32bit) while this happens... -- I wonder if that alone can explain a few issues in bugzilla where no sound is heard -- You can even pump the late data in chunks much larger than buffer size! e.g. 10MB Alternatively use snd_pcm_forward. It is only after you filed the gap that subsequent data will be fed to the speakers again and you are returned to normal mode where you can't write more than buffer_size and avail_update < buffer_size.
OTOH, mmdevapi capture mode has that glitch flag. But I wasn't talking about capture here.
All in all, I think ALSA's behaviour is good for sync'ing audio with video but otherwise quite surprising.
That's why I argue that playing silence in mmdevapi is introducing delays that don't exist with native.
Regards, Jörg Höhle
Hi,
After much discussion, the ALSA devs identified 2 fundamental API needs a) How much data can I pump into audio NOW? That's what snd_pcm_avail_update is for. b) When sync'ing audio and video, what frame is being heard NOW? That's what snd_pcm_delay is (or is intended to be) about. http://mailman.alsa-project.org/pipermail/alsa-devel/2008-June/thread.html
PlaySound only cares about a) - pump data and be done. In the mmdevapi world, after much thinking (really :) I concluded that: a) translates to GetCurrentPadding b) translates to IAudioClock::GetPosition "the stream position of the sample that is currently playing through the speakers", says MSDN. I can't foresee whether the introduction of IAudioClock2::GetDevicePosition in w7 means that the other was badly designed (or impractical) and will be obsoleted. winealsa.drv/mmdevdrv does not yet use snd_pcm_delay and that is IMHO a sign of a bug.
Now consider the case of a remote desktop connection with bad latency. An app may pump 2 second worth of samples until sound is heard (you wouldn't like to use telnet there). Such a scenario was not considered at the time the WINMM API was conceived. Back then, a DA converter connected to speakers would scan a ring buffer. Apps could write directly into that buffer (DSound) or have the OS copy data to it (WINMM) -- you all know that model.
So what does WaveOutPosition return? Back to the future, I don't know when to return a WAVHDR from winmm: 1) Should it wait until the sample went to the speakers, risking underruns because the app was not prepared for 2s network latency? Or 2) Should it pump as fast as possible, returning buffers as soon as they are not needed anymore, e.g. after snd_pcm_write? Or 3) Select an intermediate way and rate-limit the speed at which buffers are returned to the app in an attempt to simulate an old-style low latency HW ring buffer?
That concern is very real. For instance, even without remote desktop, PulseAudio has a huge 2 seconds buffer that will eat that much data when starting up. In case 2), the poor winmm app may not be prepared for being immediately returned the 3 WAVEHDR it uses, totaling 1s worth of samples and believe in an underrun or totally loose sync. You don't believe that might happen? That's happening exactly now.
Mmdevapi is not winmm, however my tests show that native mmdevapi does not let you pump more than GetBufferSize samples ahead of time, because the mixer slowly eats chunks of 10ms worth of samples. Winealsa eats GetBufferSize + whatever ALSA uses, e.g. 2s with PulseAudio.
I favour 3) for winmm, but it's not yet clear to me where the rate-limitation should happen: mmdevapi and/or winmm.
Regards, Jörg Höhle
Hi,
GetCurrentPadding (GCP) is defined as write_pos - play_pos modulo GetBufferSize
write_pos is what the next GetBuffer returns: the next unwritten position.
play_pos is what the next snd_pcm_write will receive: the position of the frame to send to ALSA next.
If you simplify the current code such that solely the periodic thread writes to the underlying ALSA device, then play_pos is never updated outside that thread -- except by Reset. Therefore, there's no need to call snd_pcm_update_avail outside that thread.
That change is consistent with native's observable behaviour in SHARED mode. GCP is decremented by 480 frames here and then, corresponding to 10ms chunks that the audio engine drains from the app's buffer and sends to the mixer. It is not decreasing continuously. See http://www.winehq.org/pipermail/wine-devel/2011-August/091371.html
As an optimization, Wine's ReleaseBuffer calls snd_pcm_write when the buffer is empty to accelerate (re)starting. Call snd_pcm_avail_update and update play_pos there, still no need to call it from within GCP.
Why does the value returned by GCP matter? The experience is that we see bug reports whenever Wine does not match native's dynamic behaviour as closely as possible.
Regards, Jörg Höhle
Hi,
Andrew Eikum wrote:
playing silence instead of garbage during underruns makes sense anyway.
- if((err = snd_pcm_sw_params_set_silence_size(This->pcm_handle,
sw_params, boundary)) < 0){
That's exactly how I feel about it, remember http://www.winehq.org/pipermail/wine-devel/2011-August/091333.html
I wonder why nobody changed that during the last 10 years of winealsa.drv.
So I played with ALSA over the last days and now I'm beginning to understand why people are so upset about it.
Under Ubuntu Intrepid with built-in Intel AC'97 audio, "plughw:0" which I used this time differs a lot from dmix which I talked about last week.
When plughw:0 loops around its buffer (stop_threshold=boundary), writing after an underrun may cause some of the last part of your audio data to be heard first, then some silence, then only the beginning of the audio data :-( Doesn't that sound like bug #XY? Also, sound typically does not start immediately following snd_pcm_writei().
It sounds as if the write occurs to a fictive play position (modulo underrun), while the HW pointer loops across the buffer. That may be normal for somebody used to ALSA -- after all, the documentation says "device will do the endless loop in the ring buffer" -- but it's not what I naively expected. snd_pcm_reset does not help (presumably there's still an underrun between reset and the following write).
Remember that dmix behaves differently: it will play silence instead of your data until you've fed so much data that you managed to fill the underrun gap (avail_update again <= buffer_size).
As a conclusion, the looping mode is not what Wine needs...
I'm still wondering what snd_pcm_sw_params best fit Wine's (mmdevapi's) needs. Any hints?
BTW, perhaps I wrote into illegal memory during my experiments, but I had a few cases where plughw:0 suddenly stopped updating the counters (avail_update & delay), playing nothing while still pretending to be in RUNNING state... IIRC, snd_pcm_drop and prepare helped.
Thank you for suggestions about good use of snd_pcm_sw_params, Jörg Höhle