[06/10] loopback: Add adjust_threshold_usec parameter

Submitted by Georg Chini on April 9, 2018, 5:17 p.m.

Details

Message ID 20180409171707.17696-7-georg@chini.tk
State New
Series "loopback: Optimize latency stabilization"
Headers show

Commit Message

Georg Chini April 9, 2018, 5:17 p.m.
In many situations, the P-controller is too sensitive and therefore exhibits rate hunting.
To avoid rate hunting, the sensibility of the controller is set by the new parameter
adjust_threshold_usec. The parameter value is the deviation from the target latency in usec
which is needed to produce a 1 Hz deviation from the optimum sample rate.
The default is set to 200 usec, which should be sufficient in most cases. If the accuracy
of the latency reports is bad and rate hunting is observed, the parameter must be increased,
while it can be lowered to achieve less latency jitter if the latency reports are accurate.
More details at
https://www.freedesktop.org/software/pulseaudio/misc/rate_estimator.odt
---
 src/modules/module-loopback.c | 43 +++++++++++++++++++++++++++++++++++--------
 1 file changed, 35 insertions(+), 8 deletions(-)

Patch hide | download patch | download mbox

diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index 1db39ef4..9f18ad1f 100644
--- a/src/modules/module-loopback.c
+++ b/src/modules/module-loopback.c
@@ -46,6 +46,7 @@  PA_MODULE_USAGE(
         "adjust_time=<how often to readjust rates in s> "
         "latency_msec=<latency in ms> "
         "max_latency_msec=<maximum latency in ms> "
+        "adjust_threshold_usec=<threshold for latency adjustment in usec> "
         "format=<sample format> "
         "rate=<sample rate> "
         "channels=<number of channels> "
@@ -60,6 +61,8 @@  PA_MODULE_USAGE(
 
 #define FILTER_PARAMETER 0.125
 
+#define DEFAULT_ADJUST_THRESHOLD_USEC 250
+
 #define MEMBLOCKQ_MAXLENGTH (1024*1024*32)
 
 #define MIN_DEVICE_LATENCY (2.5*PA_USEC_PER_MSEC)
@@ -94,6 +97,7 @@  struct userdata {
     pa_usec_t latency;
     pa_usec_t max_latency;
     pa_usec_t adjust_time;
+    uint32_t adjust_threshold;
 
     /* Latency boundaries and current values */
     pa_usec_t min_source_latency;
@@ -184,6 +188,7 @@  static const char* const valid_modargs[] = {
     "adjust_time",
     "latency_msec",
     "max_latency_msec",
+    "adjust_threshold_usec",
     "format",
     "rate",
     "channels",
@@ -255,11 +260,10 @@  static void teardown(struct userdata *u) {
 }
 
 /* rate controller, called from main context
- * - maximum deviation from base rate is less than 1%
- * - controller step size is limited to 2.01‰
+ * - maximum deviation from optimum rate for P-controller is less than 1%
+ * - P-controller step size is limited to 2.01‰
  * - will calculate an optimum rate
- * - exhibits hunting with USB or Bluetooth sources
- */
+*/
 static uint32_t rate_controller(
                 struct userdata *u,
                 uint32_t base_rate, uint32_t old_rate,
@@ -267,7 +271,21 @@  static uint32_t rate_controller(
                 int32_t latency_difference_at_base_rate) {
 
     double new_rate, new_rate_1, new_rate_2;
-    double min_cycles_1, min_cycles_2, drift_rate, latency_drift;
+    double min_cycles_1, min_cycles_2, drift_rate, latency_drift, controller_weight, min_weight;
+    uint32_t base_rate_with_drift;
+
+    base_rate_with_drift = (int)(base_rate + u->drift_compensation_rate);
+
+    /* If we are less than 2‰ away from the optimum rate, lower weight of the
+     * P-controller. The weight is determined by the fact that a correction
+     * of 0.5 Hz needs to be applied by the controller when the latency
+     * difference gets larger than the threshold. The weight follows
+     * from the definition of the controller. The minimum will only
+     * be reached when one adjust threshold away from the target. */
+    controller_weight = 1;
+    min_weight = PA_CLAMP(0.5 / (double)base_rate * (100.0 + (double)u->real_adjust_time / u->adjust_threshold), 0, 1.0);
+    if ((double)abs((int)(old_rate - base_rate_with_drift)) / base_rate_with_drift < 0.002)
+        controller_weight = PA_CLAMP((double)abs(latency_difference_at_optimum_rate) / u->adjust_threshold * min_weight, min_weight, 1.0);
 
     /* Calculate next rate that is not more than 2‰ away from the last rate */
     min_cycles_1 = (double)abs(latency_difference_at_optimum_rate) / u->real_adjust_time / 0.002 + 1;
@@ -276,10 +294,11 @@  static uint32_t rate_controller(
     /* Calculate best rate to correct the current latency offset, limit at
      * 1% difference from base_rate */
     min_cycles_2 = (double)abs(latency_difference_at_optimum_rate) / u->real_adjust_time / 0.01 + 1;
-    new_rate_2 = (double)base_rate * (1.0 + (double)latency_difference_at_optimum_rate / min_cycles_2 / u->real_adjust_time);
+    new_rate_2 = (double)base_rate * (1.0 + controller_weight * latency_difference_at_optimum_rate / min_cycles_2 / u->real_adjust_time);
 
-    /* Choose the rate that is nearer to base_rate */
-    if (abs(new_rate_1 - base_rate) < abs(new_rate_2 - base_rate))
+    /* Choose the rate that is nearer to base_rate unless we are already near
+     * to the desired latency and rate */
+    if (abs(new_rate_1 - base_rate) < abs(new_rate_2 - base_rate) && controller_weight > 0.99)
         new_rate = new_rate_1;
     else
         new_rate = new_rate_2;
@@ -1435,6 +1454,7 @@  int pa__init(pa_module *m) {
     bool source_dont_move;
     uint32_t latency_msec;
     uint32_t max_latency_msec;
+    uint32_t adjust_threshold;
     pa_sample_spec ss;
     pa_channel_map map;
     bool format_set = false;
@@ -1513,6 +1533,12 @@  int pa__init(pa_module *m) {
     if (pa_modargs_get_value(ma, "channels", NULL) || pa_modargs_get_value(ma, "channel_map", NULL))
         channels_set = true;
 
+    adjust_threshold = DEFAULT_ADJUST_THRESHOLD_USEC;
+    if (pa_modargs_get_value_u32(ma, "adjust_threshold_usec", &adjust_threshold) < 0 || adjust_threshold < 1 || adjust_threshold > 10000) {
+        pa_log_info("Invalid adjust threshold specification");
+        goto fail;
+    }
+
     latency_msec = DEFAULT_LATENCY_MSEC;
     if (pa_modargs_get_value_u32(ma, "latency_msec", &latency_msec) < 0 || latency_msec < 1 || latency_msec > 30000) {
         pa_log("Invalid latency specification");
@@ -1548,6 +1574,7 @@  int pa__init(pa_module *m) {
     u->source_latency_offset_changed = false;
     u->sink_latency_offset_changed = false;
     u->latency_error = 0;
+    u->adjust_threshold = adjust_threshold;
     u->initial_adjust_pending = true;
 
     adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC;