[pulseaudio-discuss] [PATCH v4] Make module loopback honor requested latency

Georg Chini georg at chini.tk
Thu Feb 5 13:02:39 PST 2015

On 05.02.2015 16:59, Alexander E. Patrakov wrote:
> 01.02.2015 03:43, Georg Chini wrote:
>> This is the final version of my patch for module-loopback. It is on 
>> top of the
>> patch I sent about an hour ago and contains a lot more changes than 
>> the previous
>> versions:
>> - Honor specified latency if possible, if not adjust to the lowest 
>> possible value
>> - Smooth switching from fixed latency to dynamic latency source or 
>> sink and vice versa
>> - good rate and latency stability, no rate oscillation
>> - adjusts latency as good as your setup allows
>> - fast regulation of latency offsets, adjusts 100 ms offset within 22 
>> seconds (adjust
>>    time=1) to 60 seconds (adjust_time=10)
>> - usable latency range 4 - 30000 ms
>> - Avoid rewinds and "cannot peek into queue" messages during startup 
>> and switching
>> - works with rates between 200 and 190000 Hz
>> - maximum latency offset after source/sink switch or at startup 
>> around is 200 ms
>> I also introduced a new parameter, buffer_latency_msec which can be 
>> used together
>> with latency_msec. If buffer_latency_msec is specified, the resulting 
>> latency
>> will be latency_msec + buffer_latency_msec. Latency_msec then refers 
>> only to
>> the source/sink latency while buffer_latency_msec specifies the 
>> buffer part.
>> This can be used to save a lot of CPU at low latencies, running 10 ms 
>> latency
>> with latency_msec=6 buffer_latency_msec=4 gives 8% CPU on my system 
>> compared to
>> 12% when I only specify latency_msec=10.
> Is there any more detailed profiling data for it? E.g., have you tried 
> running perf record / perf report?

No, I haven't - I just compared CPU for the current module and my 
patched version. I did not
change the way audio is processed so I would not expect large 
differences to the current version
as long as you only use latency_msec.
CPU usage mainly depends on the latency set for sink and source and 
splitting the latency into
buffer and source/sink latency aims at that.
I can provide figures if you would like to see them, but I do not know 
how to use the tools you
mention above. Is there a HowTo somewhere?

> I am not quoting the patch, but my opinion is that it needs either 
> comments or some text in the commit message on the following topics:

Mh, I thought I already put a lot of comments in ...

> Why does it use hand-crafted heuristics instead of a PID-controller? 
> Or, is this "Low pass filtered difference between expectation value 
> and observed latency" a PI controller in disguise?

What do you mean by "handcrafted heuristics"? It's basic math - how many 
samples do I need
for the requested latency. The equation new_rate = base_rate * (1 + 
latency_difference / adjust_time)
is a simple P-regulator without I or D part, rate difference is 
proportional to latency difference.
There were two options - either to get the latency right as quickly as 
possible - that would be
base_rate * (1 + latency_difference / (adjust_time + latency)) - or to 
get the fastest convergence
to the latency at the expected rate. I choose the second option.
I experimented with adding I- or D-parts but did not see any significant 
improvement (maybe
because of incorrect parameter values).

There are however two additional restrictions:
1) Do not deviate too much (I choose 0.75%) from the base rate
2) Do the adjustment in small amounts at a time
The first restriction was solved by introducing min_cycles, which makes 
sure the rate cannot
deviate more than 0.75% from the base rate. It also produces some 
dampening of the P-regulator
and smooths out the steps produced by the second restriction (at least 
when you are approaching
the base rate).
For 2) I just copied what was already in the current code.

And then I was looking for the right criterion when  to stop regulation. 
When is the latency
"good enough"? Using some percentage of the latency will not work well 
when you have a
latency range of 4 - 30000 ms to cover (at varying rates). Just cutting 
off when you are a few
Hz away from the base rate like the current code does is also not a good 
idea for the same
reason (100 - 190000 Hz).
The best you can get is an error of adjust_time / (2 * base_rate). But 
this does not work either
because the latency measurement itself has some error and there is some 
jitter in the latency.
This easily leads to instable rates or oscillation because the 
controller follows every tiny change.
The jitter gets significant for example if you are using USB devices 
that are connected to a
busy USB hub.
The approach was then to additionally use the error of the regulation as 
a stop criterion. I
calculate the next expected latency and compare it to the real latency I 
receive. When
the difference between configured latency and current latency is larger 
than twice this error,
the difference can be considered significant.
Adding the "best error" from above and a bit of safety margin finally 
leads to the stop criterion
in the patch. The low pass filter of the error is necessary to get some 
running average value.
I have to say that finding the right stop criterion was the most 
difficult part.

> How was the lowpass filter tuned?

Not at all - it is a very simple low pass (or you could say running 
average) and the values
I choose initially just seemed to fit, so I did not investigate any 
further. Maybe you could
find better values with some experimentation but my goal - cancel out 
rate oscillations
and do not try to follow "jumping" latencies - was very efficiently 
reached so I did not see
the point. The basic idea was to smooth the error value a bit but still 
react quite quickly
to jumps in the latency.

More information about the pulseaudio-discuss mailing list