The sad state of audio GetPosition

Joerg-Cyril.Hoehle at Joerg-Cyril.Hoehle at
Fri Aug 19 10:10:59 CDT 2011

Maarten Lankhorst wrote:
[nice to hear from you]

>>  IMHO AudioClient_Stop must not map to 
>> snd_pcm_drop.  It is more like snd_pcm_pause. Or perhaps simply lead 
>> ALSA into an underrun.
>afaict pause, with reset mapped to drop,
Indeed.  But I believe I need a fallback because ALSA says that
pause "works only on the hardware which supports" it.

>I can't remember why pause didn't work, but if it works go for it.
I was solely thinking aloud that pause is TRT, not tried out yet.

However, I received test results from a "Windows 7 Ultimate" machine.
It exhibits a similar bug -- in exclusive mode only:
render.c:948: Test failed: Position 18191 too far after 100ms
Shared mode works as my tests expect it (<= 48000/10 frames).

>> +    snd_pcm_status_alloca(&status);
>HeapAlloc(GetProcessHeap(), HEAP_ZERO_FLAG, snd_pcm_status_sizeof())
> or something like that if available please..
Really? I don't want to go through the overhead of memory allocation
when all I need is a stupid small amount of stack allocated memory.

>> +    if(!This->initted){
>> +        return AUDCLNT_E_NOT_INITIALIZED;
>Unneeded part.
Can't you obtain a handle to that COM object prior to
calling Initialize which sets This->fmt?

>Follow that flow..
I beg your pardon?

>> +    if(0){
>> +    avail_frames = snd_pcm_status_get_avail(status);
>> +    delay_frames = snd_pcm_status_get_delay(status);
>if 0 is bad...

I tried out pcm_status because somebody in alsa-devel mentioned that
it allows to grab avail + delay in one (sync'ed?).
However, I found delay to be always 0 inside status!?!

Also, I found out that I need to call avail_update and delay
in a particular order, otherwise I get stale values from an old call
prior to the last sleep...

>> +    if(avail_frames <= This->bufsize_alsa + MAX_LATE_SECONDS * This->fmt->nSamplesPerSec
>> +       && delay_frames > 0)
>Isn't delay_frames < 0 the definition of underrun?
There are potentially N distinct underruns:
 - the front end -- what snd_pcm_avail_update knows about;
 - intermittent buffers (USB);
 - the speaker -- what snd_pcm_delay knows about.
There could be a short front-end buffer underrun that
goes unnoticed by the speaker if the TCP or USB in
between buffers enough data *and* is able to speed up.

>no point in adding MAX_LATE_SECONDS

That is some form of guard against broken values.  E.g. people reporting
in alsa-devel that PA sometimes complains about avail ~ MAXINT and such weird values.

>Getting an avail update again? Why?
The theory is:
	position = written_frames(into ALSA) - delay
and translates to:
	This->written_frames - This->held_frames - delay

However sometimes I can't trust delay.
I still need to figure out when.
 - IIRC after an underrun, snd_pcm_delay yields error X.
 - or was it before starting?
 - ...

The upper bound on position is always:
	This->written_frames(ReleaseBuffer) - This->held_frames - ALSA_padding
 (what ALSA's front end has not yet processed, in absence of underrun).

Perhaps that would be robust:
1. Compute upper bound
2. position = clamp(0, delay > 0 ? written-delay : written, upper_bound);
2b. except when not yet started ...
2c. except while stopped ...

I was even considering:
3. if position < This->previous_position stick to previous...

Yet perhaps it's better to allow intermittent garbage values
than to stick to garbage!

OTOH, the delay values I see in the logs are subject to such variation (with PA)
that I'm considering going with a clock instead, or perhaps:
 - query delay once per tick (e.g. 10ms) => last_pos
 - when asked, compute position from last_pos + time since tick * rate

The last_pos slot may be needed anyway once stopped in pause mode.

	Jörg Höhle

More information about the wine-devel mailing list