[pulseaudio-discuss] [PATCH v6 11/25] loopback: Calculate and track minimum possible latency

Tanu Kaskinen tanuk at iki.fi
Wed Jul 20 20:33:26 UTC 2016

On Sun, 2016-06-05 at 21:05 +0200, Georg Chini wrote:
> +/* It has been a matter of discussion how to correctly calculate the minimum
> + * latency that module-loopback can deliver with a given source and sink.
> + * The calculation has been placed in a separate function so that the definition
> + * can easily be changed */
> +static void update_minimum_latency(struct userdata *u) {

This function is called from many contexts, but that's not safe. The
mimimum_latency variable is used in adjust_rates() and
memblockq_adjust(). adjust_rates() is called from the main thread, so
when the IO threads notice that minimum_latency needs to be updated,
they should send a message to the main thread. memblockq_adjust() is
called from the sink IO thread, so minimum_latency needs to be saved
also in a separate variable that is only used from the sink IO thread.
Whenever the "main" minimum_latency variable changes, the main thread
should send a message to the sink IO thread to update the other

Note that IO thread -> main thread messages should be asynchronous
(pa_asyncmsgq_post) and main thread -> IO thread messages should be
synchronous (pa_asyncmsgq_send).

I'd be happy if you could somehow clearly divide the variables in
userdata based on from which context they're supposed to be accessed.
For example, pa_sink has field "thread_info", which is an embedded
struct that holds the sink's IO thread variables, and there are other
similar uses of a "thread_info" struct elsewhere. You could add
"sink_thread_info" and "source_thread_info" to the userdata struct.

> +
> +    u->minimum_latency = u->min_sink_latency;
> +    if (u->fixed_alsa_source)
> +        u->minimum_latency += u->core->default_fragment_size_msec * PA_USEC_PER_MSEC;
> +    else
> +        u->minimum_latency += u->min_source_latency / 2;

I think there should be comments explaining why the minimum latency is
calculated as it is. The "fixed alsa source" case is calculated that
way, because we get a wakeup when one fragment is filled, and then we
empty the source buffer, so the source latency never grows much beyond
one fragment (assuming that the CPU doesn't cause a bottleneck), but I
don't remember why in the "not fixed alsa source" case you divide the
source latency by 2. And even if I understood all the reasons, they are
unobvious to people who haven't read through our previous discussion in
the recent past (I will eventually forget the discussion too).

> +/* Called from input thread context */
> +static void update_source_requested_latency_cb(pa_source_output *i) {
> +    struct userdata *u;
> +    pa_usec_t source_latency;
> +
> +    pa_source_output_assert_ref(i);
> +    pa_source_output_assert_io_context(i);
> +    pa_assert_se(u = i->userdata);
> +
> +    /* Source latency may have changed */
> +    source_latency = pa_source_get_requested_latency_within_thread(u->source_output->source);
> +    if (source_latency > u->configured_source_latency) {
> +        /* The minimum latency has changed to a value larger than the configured latency */
> +        pa_log_warn("Source latency increased to %0.2f ms", (double)source_latency / PA_USEC_PER_MSEC);
> +        u->configured_source_latency = source_latency;
> +        u->min_source_latency = source_latency;

Now you only ever increase the configured latency, but if the source at
some point lowers the minimum latency again, should we update the
latency configuration in module-loopback too? I think this can't
currently happen, and I'm ok with keeping this code as it is, but
decreasing the minimum latency might happen at some point, so preparing
for that would not be entirely pointless.

> @@ -1064,11 +1126,15 @@ int pa__init(pa_module *m) {
>      u->source_output->may_move_to = source_output_may_move_to_cb;
>      u->source_output->moving = source_output_moving_cb;
>      u->source_output->suspend = source_output_suspend_cb;
> +    u->source_output->update_source_requested_latency = update_source_requested_latency_cb;
>      u->source_output->userdata = u;
>      update_latency_boundaries(u, u->source_output->source, u->sink_input->sink);
>      set_source_output_latency(u, u->source_output->source);
> +    if (u->latency < u->minimum_latency)
> +       pa_log_warn("Latency limited to %0.2f ms with current combination of source and sink", (double)u->minimum_latency / PA_USEC_PER_MSEC);

If you want to log this warning, I think the warning should also be
logged when moving to a new sink or source. 


More information about the pulseaudio-discuss mailing list