[2/6] combine-sink: Add rate controller

Submitted by Georg Chini on April 9, 2018, 4:33 p.m.

Details

Message ID 20180409163321.17279-3-georg@chini.tk
State New
Series "combine-sink, tunnel-sink: Latency fixes"
Headers show

Commit Message

Georg Chini April 9, 2018, 4:33 p.m.
This patch adds a rate controller similar to the one used in module-loopback
to limit step size and maximum deviation from the base rate. Rate changes
are handled more smoothly by the controller. The patch has not much impact
on the behavior of the module, except that there is less rate hunting.
---
 src/modules/module-combine-sink.c | 53 ++++++++++++++++++++++++++-------------
 1 file changed, 36 insertions(+), 17 deletions(-)

Patch hide | download patch | download mbox

diff --git a/src/modules/module-combine-sink.c b/src/modules/module-combine-sink.c
index 75677fb0..7c111246 100644
--- a/src/modules/module-combine-sink.c
+++ b/src/modules/module-combine-sink.c
@@ -200,6 +200,36 @@  static void output_enable(struct output *o);
 static void output_free(struct output *o);
 static int output_create_sink_input(struct output *o);
 
+/* rate controller, called from main context
+ * - maximum deviation from base rate is less than 1%
+ * - controller step size is limited to 2.01‰
+ * - exhibits hunting with USB or Bluetooth devices
+ */
+static uint32_t rate_controller(
+                struct output *o,
+                uint32_t base_rate, uint32_t old_rate,
+                int32_t latency_difference_usec) {
+
+    double new_rate, new_rate_1, new_rate_2;
+    double min_cycles_1, min_cycles_2;
+
+    /* Calculate next rate that is not more than 2‰ away from the last rate */
+    min_cycles_1 = (double)abs(latency_difference_usec) / o->userdata->adjust_time / 0.002 + 1;
+    new_rate_1 = old_rate + base_rate * (double)latency_difference_usec / min_cycles_1 / o->userdata->adjust_time;
+
+    /* Calculate best rate to correct the current latency offset, limit at
+     * 1% difference from base_rate */
+    min_cycles_2 = (double)abs(latency_difference_usec) / o->userdata->adjust_time / 0.01 + 1;
+    new_rate_2 = (double)base_rate * (1.0 + (double)latency_difference_usec / min_cycles_2 / o->userdata->adjust_time);
+
+    /* Choose the rate that is nearer to base_rate */
+    new_rate = new_rate_2;
+    if (abs(new_rate_1 - base_rate) < abs(new_rate_2 - base_rate))
+        new_rate = new_rate_1;
+
+    return (uint32_t)(new_rate + 0.5);
+}
+
 static void adjust_rates(struct userdata *u) {
     struct output *o;
     struct sink_snapshot rdata;
@@ -298,29 +328,18 @@  static void adjust_rates(struct userdata *u) {
 
     base_rate = u->sink->sample_spec.rate;
 
+    /* Calculate and set rates for the sink inputs. */
     PA_IDXSET_FOREACH(o, u->outputs, idx) {
-        uint32_t new_rate = base_rate;
-        uint32_t current_rate;
+        uint32_t new_rate;
+        int32_t latency_difference;
 
         if (!o->sink_input || !PA_SINK_IS_OPENED(pa_sink_get_state(o->sink)))
             continue;
 
-        current_rate = o->sink_input->sample_spec.rate;
-
-        if (o->total_latency != target_latency)
-            new_rate += (uint32_t) (((double) o->total_latency - (double) target_latency) / (double) u->adjust_time * (double) new_rate);
+        latency_difference = (int64_t)o->total_latency - (int64_t)target_latency;
+        new_rate = rate_controller(o, base_rate, o->sink_input->sample_spec.rate, latency_difference);
 
-        if (new_rate < (uint32_t) (base_rate*0.8) || new_rate > (uint32_t) (base_rate*1.25)) {
-            pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", o->sink_input->sink->name, base_rate, new_rate);
-            new_rate = base_rate;
-        } else {
-            /* Do the adjustment in small steps; 2‰ can be considered inaudible */
-            if (new_rate < (uint32_t) (current_rate*0.998) || new_rate > (uint32_t) (current_rate*1.002)) {
-                pa_log_info("[%s] new rate of %u Hz not within 2‰ of %u Hz, forcing smaller adjustment", o->sink_input->sink->name, new_rate, current_rate);
-                new_rate = PA_CLAMP(new_rate, (uint32_t) (current_rate*0.998), (uint32_t) (current_rate*1.002));
-            }
-            pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f.", o->sink_input->sink->name, new_rate, (double) new_rate / base_rate);
-        }
+        pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f.", o->sink_input->sink->name, new_rate, (double) new_rate / base_rate);
         pa_sink_input_set_rate(o->sink_input, new_rate);
     }