[pulseaudio-discuss] [PATCH v6 19/25] loopback: Implement adaptive re-sampling

Georg Chini georg at chini.tk
Sun Jun 5 19:05:22 UTC 2016


To account for the difference between source and sink time domains, adaptive
re-sampling is implemented. This massively improves latency stability. Details
of the controller implementation can be found in "rate_estimator.odt".

---
 src/modules/module-loopback.c | 65 +++++++++++++++++++++++++++++++++++--------
 1 file changed, 53 insertions(+), 12 deletions(-)

diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index eee8d53..2893710 100644
--- a/src/modules/module-loopback.c
+++ b/src/modules/module-loopback.c
@@ -59,6 +59,8 @@ PA_MODULE_USAGE(
 
 #define DEFAULT_LATENCY_MSEC 200
 
+#define FILTER_PARAMETER 0.125
+
 #define MEMBLOCKQ_MAXLENGTH (1024*1024*32)
 
 #define MIN_DEVICE_LATENCY (2.5*PA_USEC_PER_MSEC)
@@ -107,12 +109,20 @@ struct userdata {
     pa_usec_t configured_source_latency;
     pa_usec_t minimum_latency;
 
+    /* State variables of the latency controller,
+     * last values */
     pa_usec_t extra_latency;
+    int32_t last_latency_difference;
+
+    /* Filter varables used for adaptive re-sampling */
+    double drift_filter;
+    double drift_compensation_rate;
 
     /* Various booleans */
     bool in_pop;
     bool pop_called;
     bool source_sink_changed;
+    bool underrun_occured;
     bool fixed_alsa_source;
 
     struct {
@@ -195,15 +205,17 @@ static void teardown(struct userdata *u) {
 /* rate controller
  * - maximum deviation from base rate is less than 1%
  * - controller step size is limited to 2.01‰
+ * - implements adaptive re-sampling
  * - exhibits hunting with USB or Bluetooth sources
  */
 static uint32_t rate_controller(
                 struct userdata *u,
                 uint32_t base_rate, uint32_t old_rate,
-                int32_t latency_difference_usec) {
+                int32_t latency_difference_usec,
+                int32_t latency_difference_usec_2) {
 
-    uint32_t new_rate_1, new_rate_2, new_rate;
-    double min_cycles_1, min_cycles_2;
+    double new_rate_1, new_rate_2, new_rate;
+    double min_cycles_1, min_cycles_2, drift_rate, latency_drift;
 
     /* Calculate next rate that is not more than 2‰ away from the last rate */
     min_cycles_1 = (double)abs(latency_difference_usec) / u->real_adjust_time / 0.002 + 1;
@@ -220,7 +232,26 @@ static uint32_t rate_controller(
     else
         new_rate = new_rate_2;
 
-    return new_rate;
+    /* Calculate rate difference between source and sink. Skip calculation
+     * after a source/sink change or an underrun */
+
+    if (!u->underrun_occured && !u->source_sink_changed) {
+        /* Latency difference between last iterations */
+        latency_drift = latency_difference_usec_2 - u->last_latency_difference;
+
+        /* Calculate frequency difference between source and sink */
+        drift_rate = (double)old_rate * latency_drift / u->real_adjust_time + old_rate - base_rate;
+
+        /* 2nd order lowpass filter */
+        u->drift_filter = (1 - FILTER_PARAMETER) * u->drift_filter + FILTER_PARAMETER * drift_rate;
+        u->drift_compensation_rate =  (1 - FILTER_PARAMETER) * u->drift_compensation_rate + FILTER_PARAMETER * u->drift_filter;
+    }
+
+    /* Use drift compensation. Though not likely, the rate might exceed the maximum allowed rate now. */
+    if (new_rate + u->drift_compensation_rate + 0.5 > PA_RATE_MAX * 101 / 100)
+        return PA_RATE_MAX * 101 / 100;
+    else
+        return (int)(new_rate + u->drift_compensation_rate + 0.5);
 }
 
 /* Called from main context */
@@ -254,10 +285,13 @@ static void adjust_rates(struct userdata *u) {
         u->underrun_counter = PA_CLIP_SUB(u->underrun_counter, 1u);
     }
 
-    /* Calculate real adjust time */
-    if (u->source_sink_changed)
+    /* Reset drift compensation parameters if source or sink changed, else calculate real adjust time */
+    if (u->source_sink_changed) {
+        u->drift_compensation_rate = 0;
+        u->drift_filter = 0;
         u->time_stamp = pa_rtclock_now();
-    else {
+
+    } else {
         u->adjust_counter++;
         u->real_adjust_time_sum += pa_rtclock_now() - u->time_stamp;
         u->time_stamp = pa_rtclock_now();
@@ -281,11 +315,11 @@ static void adjust_rates(struct userdata *u) {
     /* Current latency */
     current_latency = current_source_sink_latency + current_buffer_latency;
 
-    /* Latency at base rate */
-    latency_at_optimum_rate = current_source_sink_latency + current_buffer_latency * old_rate / base_rate;
+    /* Latency at optimum rate and latency difference */
+    latency_at_optimum_rate = current_source_sink_latency + current_buffer_latency * old_rate / (u->drift_compensation_rate + base_rate);
 
     final_latency = PA_MAX(u->latency, u->minimum_latency + u->extra_latency);
-    latency_difference = (int32_t)((int64_t)latency_at_optimum_rate - final_latency);
+    latency_difference = (int32_t)((int64_t)current_latency - final_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,
@@ -296,9 +330,13 @@ static void adjust_rates(struct userdata *u) {
     pa_log_debug("Loopback latency at base rate is %0.2f ms", (double)latency_at_optimum_rate / PA_USEC_PER_MSEC);
 
     /* Calculate new rate */
-    new_rate = rate_controller(u, base_rate, old_rate, latency_difference);
+    new_rate = rate_controller(u, base_rate, old_rate, (int)(latency_at_optimum_rate - final_latency), latency_difference);
+
+    /* Save current latency difference at new rate for next cycle and reset flags */
+    u->last_latency_difference = current_source_sink_latency + current_buffer_latency * old_rate / new_rate - final_latency;
 
     u->source_sink_changed = false;
+    u->underrun_occured = false;
 
     /* Set rate */
     pa_sink_input_set_rate(u->sink_input, new_rate);
@@ -742,8 +780,10 @@ static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, in
             if (u->sink_input->thread_info.underrun_for > 0 &&
                 pa_memblockq_is_readable(u->memblockq)) {
 
-                if (!u->source_sink_changed)
+                if (!u->source_sink_changed) {
                     u->underrun_counter +=1;
+                    u->underrun_occured = true;
+                }
                 /* If called from within the pop callback skip the rewind */
                 if (!u->in_pop) {
                     pa_log_debug("Requesting rewind due to end of underrun.");
@@ -1096,6 +1136,7 @@ int pa__init(pa_module *m) {
     u->iteration_counter = 0;
     u->underrun_counter = 0;
     u->source_sink_changed = true;
+    u->underrun_occured = false;
     u->extra_latency = 0;
 
     adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC;
-- 
2.8.1



More information about the pulseaudio-discuss mailing list