[pulseaudio-discuss] source-sink loopback

Lennart Poettering lennart at poettering.net
Thu Aug 20 05:29:09 PDT 2009


On Thu, 20.08.09 00:52, pl bossart (bossart.nospam at gmail.com) wrote:

> > So, basically: if you configure a latency then you should get
> > something in the area what you asked for but we cannot make
> > guarantees. And the connection between latency and block sizes is even
> > fuzzier.
> 
> D'oh. Looks like if I interface with a timing-critical protocol I'd
> need to hide this inside a module...

When you need equally sized blocks of PCM data then you need to queue
things up (e.g. with a pa_memblockq). A number of modules do
that. Including Jason Newton's equalizer module.

> >> 3. For now the source and sink are synchronous but if they are not,
> >> how can I enable a sample-rate converter to correct for clock drifts?
> >> I see some code for SRC in both the input and output IO threads,
> >> however I don't understand how the tracking would be done.
> >
> > module-combine handles this already. It probably would make sense to
> > copy the basic logic here: in the main thread simply measure the
> > latency of the sink and source every now and then, and then update the
> > sampling rate of the sink input with pa_sink_input_set_rate().
> >
> > (This is actually quite hard to get right, and module-combine doesn't
> > entirely get it right. The problem is getting a somewhat atomic
> > snapshot of both latencies, since in the time between asking the two
> > latencies another memblock might have been sent over.)
> 
> Humm, I didn't realize your definition of latency is different from my
> intuitive definition. I thought in terms of samples, but when I
> checked the code in alsa-sink.c, I saw that the latency is really the
> delta between the wall clock and the audio clock+the delayed samples.
> What this means is that if there's a drift between the wall clock and
> the audio clock the latency reported will gradually increase or be
> reduced Is this correct?

Yes, we don't measure the latency in samples since due to resampling,
network latencies and other times summed up it really is nothing where
the unit "sample" (in the client's sample rate) is appropriate. In
the entire pipeline from the per-application buffer to the speakers
only the per-application buffer itself has actually a latency which
could be measured sensibly in samples (in the client's sample
rate). Everything else is either measured in usec anyway or in a
different sample rate. That's why I chose to unify time units over the
entire pipeline and measure them in usec. If you need them in samples
use pa_usec_to_bytes().

> I guess when you substract both latencies you get rid of the wall
> clock component, which is fine in this case.
> And yes we would need to low-pass filter the deviation to focus only
> on the long-term evolution. The clocks shouldn't be more that 1% apart
> anyway on most systems.

module-combine does not low-pass filter time under the assumption that
the underlying source/sink already does that for their timing and we
don't need to low-pass things on every level again. Most backend
modules (such as the ALSA or Bluetooth one) low-pass filter time by
using pa_timer_smoother -- which however does linear regression plus
spline interpolation instead of doing a classic low-pass or a PLL/FLL.

> > In the rewind callback you you simply must rewind the read pointer in
> > the memblockq. It is called whenever we need to rewrite the hardware
> > playback buffer. i.e. let's say we have 2s of buffer. Now a new stream
> > is added to the mix. We need to remix the whole 2s we already
> > wrote. Then we rewind each stream and ask for the data again and write
> > it to the buffer.
> >
> > If you use a memblockq all you need to do is basically forward this
> > call to pa_memblockq_rewind() which does the heavy lifting for you.
> 
> This part is unclear. What you are saying is that basically
> pa_memblockq_rewind() is the opposite of the drop(), this is just a
> play with the read pointer. However when does the data actually get
> marked as used by the sink and when can these memory blocks be
> reclaimed/reused?

Every sink has a value max_rewind associated with it
(sink->thread_info.max_rewind) which should be used by streams to size
the "history" they keep in their buffers. If you use a memblockq then
you should set its max_rewind parameter to this value (though
converted to the stream's sample params, which
pa_sink_input_get_max_rewind() does for you)

The max_rewind value can change, i.e. when the underlying device is
reconfigured or your stream is moved between sinks. You should
register an update_max_rewind() callback in you pa_sink_input to get
notifications each time that happens and then readjust the maxrewind
param of your memblockq.

Lennart

-- 
Lennart Poettering                        Red Hat, Inc.
lennart [at] poettering [dot] net
http://0pointer.net/lennart/           GnuPG 0x1A015CC4



More information about the pulseaudio-discuss mailing list