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

Georg Chini georg at chini.tk
Wed Nov 26 11:08:05 PST 2014


Sorry for the previous noise, I should not send patches before they
really do what i want ...

-----------------------------

This patch makes module loopback adjust to the requested latency if 
possible
by tweaking the rate adjustment. On startup the latency is initialized 
to a value
near the requested latency so that the stable state can be reached 
within a few
iterations of adjust_time.
In the case that the requested latency is smaller than source latency + sink
latency + 25ms, module loopback will try to adjust the buffer latency to 
25ms.
(This value could also be made user configurable if necessary)

----------------------------

  src/modules/module-loopback.c                  | 113 +++++++++++++--------
  1 file changed, 73 insertions(+), 40 deletions(-)

----------------------------
diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index b3b9557..0f61cc2 100644
--- a/src/modules/module-loopback.c
+++ b/src/modules/module-loopback.c
@@ -65,6 +65,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 +89,7 @@ struct userdata {
      pa_usec_t latency;

      bool in_pop;
+    bool pop_called;
      size_t min_memblockq_length;

      struct {
@@ -98,6 +101,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;
@@ -124,7 +129,6 @@ enum {
      SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX,
      SINK_INPUT_MESSAGE_REWIND,
      SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT,
-    SINK_INPUT_MESSAGE_MAX_REQUEST_CHANGED
  };

  enum {
@@ -171,9 +175,10 @@ 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;
-    pa_usec_t buffer_latency;
+    pa_usec_t req_buffer_latency;
+    pa_usec_t final_latency;

      pa_assert(u);
      pa_assert_ctl_context();
@@ -188,40 +193,42 @@ static void adjust_rates(struct userdata *u) {
      if (u->latency_snapshot.recv_counter <= 
u->latency_snapshot.send_counter)
          buffer += (size_t) (u->latency_snapshot.send_counter - 
u->latency_snapshot.recv_counter);
      else
-        buffer += PA_CLIP_SUB(buffer, (size_t) 
(u->latency_snapshot.recv_counter - u->latency_snapshot.send_counter));
+        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 = pa_bytes_to_usec(buffer, 
&u->sink_input->sample_spec);

      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,
-                (double) buffer_latency / PA_USEC_PER_MSEC,
+                (double) u->latency_snapshot.buffer_latency / 
PA_USEC_PER_MSEC,
                  (double) u->latency_snapshot.source_latency / 
PA_USEC_PER_MSEC,
-                ((double) u->latency_snapshot.sink_latency + 
buffer_latency + u->latency_snapshot.source_latency) / PA_USEC_PER_MSEC);
+                ((double) u->latency_snapshot.sink_latency + 
u->latency_snapshot.buffer_latency + u->latency_snapshot.source_latency) 
/ PA_USEC_PER_MSEC);

      pa_log_debug("Should buffer %zu bytes, buffered at minimum %zu bytes",
                  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 = PA_MAX(u->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 ((int32_t)(u->adjust_time + req_buffer_latency - 
u->latency_snapshot.buffer_latency) > 0)
+        new_rate = base_rate * u->adjust_time/(u->adjust_time + 
req_buffer_latency - u->latency_snapshot.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;
+        new_rate = base_rate * 1.01;

-    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));
-        }
+    if (base_rate < new_rate + 10 && new_rate < base_rate + 10)
+       new_rate = base_rate;
+
+    /* Do not deviate more than 1% from the base sample rate */
+    if (new_rate < (uint32_t) (base_rate*0.99) || new_rate > (uint32_t) 
(base_rate*1.01)) {
+       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*0.99), 
(uint32_t) (base_rate*1.01));
+    }
+    /* 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 +471,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 +503,28 @@ 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;
+    uint32_t latency;
+    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 = PA_MAX(u->latency_snapshot.sink_latency + 
u->latency_snapshot.source_latency + MIN_BUFFER_USEC, u->latency);
+    latency_diff = u->latency_snapshot.sink_latency + 
u->latency_snapshot.buffer_latency + u->latency_snapshot.source_latency 
- 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 = 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,10 +546,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 {
                  pa_memblockq_push_align(u->memblockq, chunk);
-            else
-                pa_memblockq_flush_write(u->memblockq, true);
+                memblockq_adjust(u, chunk->length);
+            }             update_min_memblockq_length(u);

@@ -542,10 +575,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
-                pa_memblockq_flush_write(u->memblockq, true);
+            else if (u->latency_snapshot.buffer_latency)
+                memblockq_adjust(u, 0);

              u->recv_counter -= offset;

@@ -574,17 +607,6 @@ static int sink_input_process_msg_cb(pa_msgobject 
*obj, int code, void *data, in
              return 0;
          }

-        case SINK_INPUT_MESSAGE_MAX_REQUEST_CHANGED: {
-            /* This message is sent from the IO thread to the main
-             * thread! So don't be confused. All the user cases above
-             * are executed in thread context, but this one is not! */
-
-            pa_assert_ctl_context();
-
-            if (u->time_event)
-                adjust_rates(u);
-            return 0;
-        }
      }

      return pa_sink_input_process_msg(obj, code, data, offset, chunk);
@@ -607,6 +629,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 +644,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 */
@@ -644,7 +669,6 @@ static void 
sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {

      pa_memblockq_set_prebuf(u->memblockq, nbytes*2);
      pa_log_info("Max request changed");
-    pa_asyncmsgq_post(pa_thread_mq_get()->outq, 
PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_MAX_REQUEST_CHANGED, 
NULL, 0, NULL, NULL);
  }

  /* Called from main thread */
@@ -746,6 +770,7 @@ int pa__init(pa_module *m) {
      uint32_t adjust_time_sec;
      const char *n;
      bool remix = true;
+    uint32_t counter;

      pa_assert(m);

@@ -820,6 +845,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) {
@@ -962,6 +988,13 @@ int pa__init(pa_module *m) {
              0,                      /* minreq */
              0,                      /* maxrewind */
              &silence);              /* silence frame */
+    silence.index = silence.length - pa_usec_to_bytes(PA_USEC_PER_MSEC 
* 100, &u->source_output->sample_spec);
+    silence.length = pa_usec_to_bytes(PA_USEC_PER_MSEC * 100, 
&u->source_output->sample_spec);
+    counter = 150 * PA_USEC_PER_MSEC;
+    while ( counter < u->latency) {
+        pa_memblockq_push_align(u->memblockq, &silence);
+        counter += 100 * PA_USEC_PER_MSEC;
+    }
      pa_memblock_unref(silence.memblock);

      u->asyncmsgq = pa_asyncmsgq_new(0);




More information about the pulseaudio-discuss mailing list