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

Georg Chini georg at chini.tk
Sun Nov 23 12:27:34 PST 2014


This patch makes module loopback adjust to the requested latency if possible
by tweaking the rate adjustment. In the case that the latency is too 
small, at
least 25ms of audio will be kept in the buffer. As the rate adjustment 
can only be
done in small steps, adjustment takes a while for long latencies. I 
could not figure
out how to initially delay playing to the sink until the requested 
latency is reached.
If there is a way to do this, it would simplify the logic of the rate 
calculation
because only small deviations of the sample rate would have to be taken into
account.
This patch includes a modified version of the patch sent on Friday.

---------

  src/modules/module-loopback.c                  | 88 +++++++++++++++++-----
  1 file changed, 68 insertions(+), 20 deletions(-)

---------

diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index b3b9557..cbeec56 100644
--- a/src/modules/module-loopback.c
+++ b/src/modules/module-loopback.c
@@ -25,6 +25,7 @@
  #endif

  #include <stdio.h>
+#include <math.h>

  #include <pulse/xmalloc.h>

@@ -65,6 +66,8 @@ PA_MODULE_USAGE(

  #define DEFAULT_ADJUST_TIME_USEC (10*PA_USEC_PER_SEC)

+#define MIN_BUFFER_USEC (25*PA_USEC_PER_MSEC)
+
  struct userdata {
      pa_core *core;
      pa_module *module;
@@ -87,6 +90,7 @@ struct userdata {
      pa_usec_t latency;

      bool in_pop;
+    bool pop_called;
      size_t min_memblockq_length;

      struct {
@@ -98,6 +102,8 @@ struct userdata {
          size_t sink_input_buffer;
          pa_usec_t sink_latency;

+        pa_usec_t buffer_latency;
+
          size_t min_memblockq_length;
          size_t max_request;
      } latency_snapshot;
@@ -171,9 +177,12 @@ static void teardown(struct userdata *u) {

  /* Called from main context */
  static void adjust_rates(struct userdata *u) {
-    size_t buffer, fs;
+    size_t buffer;
      uint32_t old_rate, base_rate, new_rate;
+    int32_t req_buffer_latency;
+    pa_usec_t final_latency;
      pa_usec_t buffer_latency;
+    double latency_factor;

      pa_assert(u);
      pa_assert_ctl_context();
@@ -191,6 +200,7 @@ static void adjust_rates(struct userdata *u) {
          buffer += PA_CLIP_SUB(buffer, (size_t) 
(u->latency_snapshot.recv_counter - u->latency_snapshot.send_counter));

      buffer_latency = pa_bytes_to_usec(buffer, 
&u->sink_input->sample_spec);
+    u->latency_snapshot.buffer_latency = buffer_latency;

      pa_log_debug("Loopback overall latency is %0.2f ms + %0.2f ms + 
%0.2f ms = %0.2f ms",
                  (double) u->latency_snapshot.sink_latency / 
PA_USEC_PER_MSEC,
@@ -202,26 +212,33 @@ static void adjust_rates(struct userdata *u) {
                  u->latency_snapshot.max_request*2,
                  u->latency_snapshot.min_memblockq_length);

-    fs = pa_frame_size(&u->sink_input->sample_spec);
      old_rate = u->sink_input->sample_spec.rate;
      base_rate = u->source_output->sample_spec.rate;

-    if (u->latency_snapshot.min_memblockq_length < 
u->latency_snapshot.max_request*2)
-        new_rate = base_rate - (((u->latency_snapshot.max_request*2 - 
u->latency_snapshot.min_memblockq_length) / fs) 
*PA_USEC_PER_SEC)/u->adjust_time;
+    final_latency = u->latency;
+    if (u->latency_snapshot.sink_latency + 
u->latency_snapshot.source_latency + MIN_BUFFER_USEC > u->latency)
+       final_latency = u->latency_snapshot.sink_latency + 
u->latency_snapshot.source_latency + MIN_BUFFER_USEC;
+    req_buffer_latency = final_latency - 
u->latency_snapshot.sink_latency - u->latency_snapshot.source_latency;
+    if (u->adjust_time + req_buffer_latency - buffer_latency >0)
+        new_rate = base_rate * u->adjust_time/(u->adjust_time + 
req_buffer_latency - buffer_latency);
      else
-        new_rate = base_rate + 
(((u->latency_snapshot.min_memblockq_length - 
u->latency_snapshot.max_request*2) / fs) *PA_USEC_PER_SEC)/u->adjust_time;
-
-    if (new_rate < (uint32_t) (base_rate*0.8) || new_rate > (uint32_t) 
(base_rate*1.25)) {
-        pa_log_warn("Sample rates too different, not adjusting (%u vs. 
%u).", base_rate, new_rate);
-        new_rate = base_rate;
-    } else {
-        if (base_rate < new_rate + 20 && new_rate < base_rate + 20)
-          new_rate = base_rate;
-        /* Do the adjustment in small steps; 2‰ can be considered 
inaudible */
-        if (new_rate < (uint32_t) (old_rate*0.998) || new_rate > 
(uint32_t) (old_rate*1.002)) {
-            pa_log_info("New rate of %u Hz not within 2‰ of %u Hz, 
forcing smaller adjustment", new_rate, old_rate);
-            new_rate = PA_CLAMP(new_rate, (uint32_t) (old_rate*0.998), 
(uint32_t) (old_rate*1.002));
-        }
+        new_rate = base_rate * 1.02;
+
+    if (base_rate < new_rate + 10 && new_rate < base_rate + 10)
+       new_rate = base_rate;
+
+    latency_factor = fabs((int32_t) (final_latency - 
u->latency_snapshot.sink_latency - buffer_latency - 
u->latency_snapshot.source_latency) / (double) final_latency);
+    latency_factor = 0.02 * latency_factor + 0.01;
+    /* Do not deviate more than between 1% and 3% from the base sample rate
+       depending on the difference between real and configured latency */
+    if (new_rate < (uint32_t) (base_rate*(1-latency_factor)) || 
new_rate > (uint32_t) (base_rate*(1+latency_factor))) {
+       pa_log_info("Sample rates too different (%u vs. %u), limiting 
rate adjustment.", base_rate, new_rate);
+       new_rate = PA_CLAMP(new_rate, (uint32_t) 
(base_rate*(1-latency_factor)), (uint32_t) (base_rate*(1+latency_factor)));
+    }
+    /* Do the adjustment in small steps; 2‰ can be considered inaudible */
+    if (new_rate < (uint32_t) (old_rate*0.998) || new_rate > (uint32_t) 
(old_rate*1.002)) {
+        pa_log_info("New rate of %u Hz not within 2‰ of %u Hz, forcing 
smaller adjustment", new_rate, old_rate);
+        new_rate = PA_CLAMP(new_rate, (uint32_t) (old_rate*0.998), 
(uint32_t) (old_rate*1.002));
      }

      pa_sink_input_set_rate(u->sink_input, new_rate);
@@ -464,6 +481,8 @@ static int sink_input_pop_cb(pa_sink_input *i, 
size_t nbytes, pa_memchunk *chunk
      pa_assert_se(u = i->userdata);
      pa_assert(chunk);

+    if (PA_SINK_IS_RUNNING(u->sink_input->sink->thread_info.state) && 
!u->pop_called)
+        u->pop_called = true;
      u->in_pop = true;
      while (pa_asyncmsgq_process_one(u->asyncmsgq) > 0)
          ;
@@ -494,6 +513,26 @@ static void 
sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
  }

  /* Called from output thread context */
+static void memblockq_adjust(struct userdata *u, size_t length) {
+    int32_t latency_diff;
+    size_t nbytes;
+
+    if (length) {
+       nbytes = PA_MIN(length, pa_memblockq_get_length(u->memblockq));
+       pa_log_debug("Dropping %u Bytes from queue", (uint32_t) nbytes);
+       pa_memblockq_drop(u->memblockq, nbytes);
+       return;
+    }
+    latency_diff = u->latency_snapshot.sink_latency + 
u->latency_snapshot.buffer_latency + u->latency_snapshot.source_latency 
- u->latency;
+    if (latency_diff > 0) {
+       nbytes = PA_MIN(pa_usec_to_bytes(latency_diff, 
&u->sink_input->sample_spec), pa_memblockq_get_length(u->memblockq));
+       pa_log_debug("Dropping %u Bytes from queue", (uint32_t) nbytes);
+       pa_memblockq_drop(u->memblockq, nbytes);
+       u->latency_snapshot.buffer_latency = u->latency - 
u->latency_snapshot.source_latency - u->latency_snapshot.sink_latency;
+   }
+}
+
+/* Called from output thread context */
  static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void 
*data, int64_t offset, pa_memchunk *chunk) {
      struct userdata *u = PA_SINK_INPUT(obj)->userdata;

@@ -515,9 +554,12 @@ static int sink_input_process_msg_cb(pa_msgobject 
*obj, int code, void *data, in

              pa_sink_input_assert_io_context(u->sink_input);

-            if (PA_SINK_IS_OPENED(u->sink_input->sink->thread_info.state))
+            if 
(PA_SINK_IS_RUNNING(u->sink_input->sink->thread_info.state) && 
u->pop_called)
                  pa_memblockq_push_align(u->memblockq, chunk);
-            else
+            else if (u->latency_snapshot.buffer_latency) {
+                pa_memblockq_push_align(u->memblockq, chunk);
+                memblockq_adjust(u, chunk->length);
+            } else
                  pa_memblockq_flush_write(u->memblockq, true);

              update_min_memblockq_length(u);
@@ -542,8 +584,10 @@ static int sink_input_process_msg_cb(pa_msgobject 
*obj, int code, void *data, in

              pa_sink_input_assert_io_context(u->sink_input);

-            if (PA_SINK_IS_OPENED(u->sink_input->sink->thread_info.state))
+            if 
(PA_SINK_IS_RUNNING(u->sink_input->sink->thread_info.state) && 
u->pop_called)
                  pa_memblockq_seek(u->memblockq, -offset, 
PA_SEEK_RELATIVE, true);
+            else if (u->latency_snapshot.buffer_latency)
+                memblockq_adjust(u, 0);
              else
                  pa_memblockq_flush_write(u->memblockq, true);

@@ -607,6 +651,7 @@ static void sink_input_attach_cb(pa_sink_input *i) {
      pa_memblockq_set_maxrewind(u->memblockq, 
pa_sink_input_get_max_rewind(i));

      u->min_memblockq_length = (size_t) -1;
+    u->pop_called = false;
  }

  /* Called from output thread context */
@@ -621,6 +666,8 @@ static void sink_input_detach_cb(pa_sink_input *i) {
          pa_rtpoll_item_free(u->rtpoll_item_read);
          u->rtpoll_item_read = NULL;
      }
+    memblockq_adjust(u, 0);
+    update_min_memblockq_length(u);
  }

  /* Called from output thread context */
@@ -820,6 +867,7 @@ int pa__init(pa_module *m) {
      u->core = m->core;
      u->module = m;
      u->latency = (pa_usec_t) latency_msec * PA_USEC_PER_MSEC;
+    u->pop_called = false;

      adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC;
      if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) 
< 0) {


More information about the pulseaudio-discuss mailing list