[pulseaudio-discuss] [PATCH 04/13] loopback: Adjust rates based on latency difference

Georg Chini georg at chini.tk
Wed Feb 25 10:43:16 PST 2015


For small values of latency_difference, this forms a classical
P-controller between the observed value of latency and the controlled
sample rate of the sink input. The coefficient aims for the full
correction of the observed difference to the next cycle - i.e. the
controller is tuned optimally according to
https://en.wikipedia.org/wiki/Ziegler%E2%80%93Nichols_method
For higher latency values the controller limits the deviation from
the base rate to 0.95%.
---
 src/modules/module-loopback.c | 44 ++++++++++++++++++++++++-------------------
 1 file changed, 25 insertions(+), 19 deletions(-)

diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index 3532728..372c3ed 100644
--- a/src/modules/module-loopback.c
+++ b/src/modules/module-loopback.c
@@ -169,9 +169,30 @@ static void teardown(struct userdata *u) {
     }
 }
 
+/* rate controller
+ * - maximum deviation from base rate is less than 1%
+ * - can create audible artifacts by changing the rate too quickly
+ * - exhibits hunting with USB or Bluetooth sources
+ */
+static uint32_t rate_controller(
+                uint32_t base_rate,
+                pa_usec_t adjust_time,
+                int32_t latency_difference_usec) {
+
+    uint32_t new_rate;
+    double min_cycles;
+
+    /* Calculate best rate to correct the current latency offset, limit at
+     * slightly below 1% difference from base_rate */
+    min_cycles = (double)abs(latency_difference_usec) / adjust_time / 0.0095 + 1;
+    new_rate = base_rate * (1.0 + (double)latency_difference_usec / min_cycles / adjust_time);
+
+    return new_rate;
+}
+
 /* 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 final_latency, current_buffer_latency, current_latency, corrected_latency;
     int32_t latency_difference;
@@ -218,26 +239,11 @@ 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);
-
-    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;
-    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;
+    /* Calculate new rate */
+    new_rate = rate_controller(base_rate, u->adjust_time, latency_difference);
 
-    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));
-        }
-    }
 
+    /* Set rate */
     pa_sink_input_set_rate(u->sink_input, new_rate);
     pa_log_debug("[%s] Updated sampling rate to %lu Hz.", u->sink_input->sink->name, (unsigned long) new_rate);
 
-- 
2.1.4



More information about the pulseaudio-discuss mailing list