On 15.11.2015 22:08, Alexander E. Patrakov wrote:
25.02.2015 23:43, Georg Chini wrote:
Hello,
this is the split version of the module-loopback patch. The patch
optimizes the latency
initialization and regulation of the module.
Many thanks to Alexander Patrakov for splitting and reviewing the
patch and also for
his contribution to the code.
Georg Chini (13):
loopback: Fix the obviously-wrong "buffer+=buffer" logic
loopback: Save the timestamps when we snapshot latency
loopback: Improved estimation of latency
loopback: Adjust rates based on latency difference
loopback: Don't track max_request and min_memblockq_length
loopback: Restart the timer right away
loopback: Refactor latency initialization
loopback: Track underruns and cant-peek events
loopback: Track the amount of jitter
loopback: Added a deadband to reduce rate hunting
loopback: Don't change rate abruptly
loopback: Validate the rate parameter
loopback: add parameter buffer_latency_msec
src/modules/module-loopback.c | 578
++++++++++++++++++++++++++++++++----------
src/pulse/sample.c | 5 +-
2 files changed, 443 insertions(+), 140 deletions(-)
I have tested this code again. Here is my opinion.
Thank you a lot!
It would be logical enough to consider the state of the original
loopback module, and compare the state of affairs after 04/13 and
after 13/13 to it. And compare to other implementations of adaptive
resamplers as well.
To test the thing, I used my laptop (that has an internal sound card)
and my desktop (that has an internal PCH soundcard, also I connected
my Rotel RA-1570 amplifier via USB).
On the laptop, I created a 5-minute wav file containing a sine wave
with 1 kHz frequency. I connected the laptop headphone output to the
desktop PCH line input, and used the loopback module to transfer audio
from PCH to the Rotel amplifier. In all cases, the nominal sample rate
44100 Hz was used. In all cases, the sound sent to the Rotel amplifier
was recorded into a wav file, and the resulting file was analysed with
a Python script.
I have tried two techniques for instrumenting the loopback module.
First, I thought that I can record the sound sent to the Rotel sound
card by recording from PulseAudio monitor source. However, this turned
out to be a bad idea: the existing loopback module behaved differently
(according to its log) when its sink was monitored. Namely, it
immediately converged to 44100 Hz - something that it could do only
after two minutes without such monitoring.
So, I created this snippet in .asoundrc:
pcm.savefile {
type file
format "wav"
slave.pcm "hw:PCUSB"
file "/tmp/save.wav"
}
and loaded module-alsa-sink so that PulseAudio saved its output to a
file.
The most important result is that the old module, both when monitored
and when not monitored, does not respect the requested latency in this
test case. It settles down to something between 300 and 400 ms, even
though 200 ms was requested. There is no such bug with the new
implementation.
Now let's talk about the script. It is attached. A mono wav file
containing some silence, recording of a resampled 1 kHz sine wave, and
some silence again, should be passed as an argument. The script will
try to estimate the phase of the sine wave (y = A * sin(omega * t +
phi), the script tries to estimate phi) at various points in time, and
plot the result. The plot will generally contain some linear trend
(due to unsynchronized clocks) and some wobble around that. Then it
will plot again, with this linear trend eliminated (i.e. will estimate
the phase again using the correct signal frequency in terms of the
sink clock). Both results are saved as png files, twice (first the
full version, then only the second half, to hide the startup process).
I chose this metric because it was used by Fons Adriaensen when
discussing the merits of zita-ajbridge:
http://kokkinizita.linuxaudio.org/linuxaudio/zita-ajbridge-doc/quickguide.html
Both my plots and the plots on his page show the unwrapped phase in
degrees. Phase, in our case, is related to the delay. 360 degrees = 1
millisecond = 34 cm of air. In other words, 1 degree = 2.77 us = 0.94
mm of air.
The first result (https://imgur.com/a/twBEk ) is actually without any
loopback module. Just a recording from the line input. We can clearly
see that there is some clock skew between the laptop and desktop
soundcards, and that the clock ratio is even not constant. OK - this
is a reference how good any loopback module can be.
The second result (https://imgur.com/a/eVahQ ) is with the old module.
You see that, with it, the rate oscillates wildly and then snaps to
44100 Hz. However, the final latency is not correct, and it
accumulates due to clock mismatch between the two sound cards in the
desktop. I have not waited enough to see what happens next, but the
result is clearly invalid.
I wonder why the phase is not continuously growing in the final plot. I
would expect this
because the controller snaps to the base rate.
The next result (https://imgur.com/a/Bi2Yz ) is with the new module,
as of PATCH 04/13. There is some "rate hunting", and it results to
some noise in phase. It is generally contained between -150 and 200
degrees.
Next, let's see (https://imgur.com/a/yQGWL ) what the deadband
achieves. Result: it helps against rate hunting, but not much against
phase oscillations. This is because new_rate is snapped to base_rate,
which would only be correct if the cards actually shared the common
clock. So here is what happens: the module runs for some time with 1:1
resampling (i.e. with the rate strictly equal to 44100 Hz), and the
latency difference slowly drifts due to the clock mismatch. When the
difference becomes bigger than the error of its measurement, the
module corrects it, and then runs at 44100 Hz for some time again.
Could you use 2 s as adjust time? 10 seconds is definitely too slow to
achieve good results.
I would expect a similar result but with much smaller amplitude of the
phase oscillation.
However, I'd argue that this phase metric can be improved without the
deadband and beyond what this deadband-based implementation provides.
What is the problem with the dead band? There are physical limitations
to what makes
sense to correct and the dead band just takes care of those limits.
First, see (https://imgur.com/a/P5Y0A ) what happens, at PATCH 04/13,
if we just decrease adjust_time to 1 second. The rate oscillations
become bigger, but phase oscillations stay of the same order as with
the default value of adjust_time (10 seconds). So not good.
Then, let's change the rate controller so that it does not attempt to
correct all of the latency difference in one step. The value of
adjust_time is still 1s, but let's aim to correct the difference after
10 steps by a factor of 2.71. For simplicity (and for testing purposes
only), let's destroy the non-linear part of the controller. Here is
the code:
static uint32_t rate_controller(
uint32_t base_rate,
pa_usec_t adjust_time,
int32_t latency_difference_usec) {
uint32_t new_rate;
new_rate = base_rate * (1.0 + 0.1 *
(double)latency_difference_usec / adjust_time);
return new_rate;
}
As you can see (https://imgur.com/a/eZT8L ), the phase oscillations
are now much more limited!
I have received a private email from Georg where he proposes to use
the deadband, but snap to something other than base_rate, e.g. to a
result of some slow controller. For me, this is equivalent to using
this slow controller, i.e. a good idea.
I'll send you a patch privately later today. I kept the controller and
implemented a more or less
independent controller which takes care of the clock skew / latency
drift. I'd like to discuss
this approach first with you before I send another patch to the list.
Finally, let's compare Georg's code to known competitors.
alsaloop (part of alsa-utils): https://imgur.com/lvZLoli . Could not
settle after 300 seconds, so I am not uploading any processed plots.
alsa_in (part of jack1): https://imgur.com/a/9PnW3 . Rather big
oscillations, definitely worse than what's proposed here.
zita-ajbridge: could not run it, because it deadlocks at the start.
Could be the same as
https://bugs.launchpad.net/ubuntu/+source/zita-ajbridge/+bug/1368716
Also, let's consider the limitations of what we can achieve with
different controllers.
First, power-saving proponents will not let the latency be sampled
more often than once in 100 ms by default. Thus, we can only adjust
rates once in 100 ms. It is a PulseAudio limitation that rate is an
integer. If the correct rate is 44100.5 Hz, then a controller can only
set it to either 44100 or 44101 Hz. In any case, that's 1/88200
seconds per second of clock skew. Per our 100 ms period of sampling
the latency difference, that's 1.13 us. With a 1 kHz test signal (as
used in all examples above), that's about 0.4 degrees of phase. So,
"rate is an integer" is not an important limitation - the
theoretically achievable resolution is only slightly worse than the
natural clock instability of consumer soundcards.
TL;DR: I'm in favor of merging up to PATCH 05/13, and, if someone else
reviews patches 06 and 07, would recommend merging up to PATCH 08/13.
I would recommend waiting two weeks before merging further patches, as
the deadband stuff is likely to get undone and redone if the rate
controller gets replaced. And I am going to experiment further with
different rate controllers on the next weekends.
_______________________________________________
pulseaudio-discuss mailing list
[email protected]
http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss