In most 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. For details of the implementation, refer to "rate_estimator.odt". --- src/modules/module-loopback.c | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 35a817b..f4e2c2f 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -47,6 +47,7 @@ PA_MODULE_USAGE( "sink=<sink to connect to> " "adjust_time=<how often to readjust rates in s, values >= 100 are in ms> " "latency_msec=<latency in ms> " + "adjust_threshold_usec=<threshold for latency adjustment in usec> " "low_device_latency=<boolean, use half of the normal device latency> " "format=<sample format> " "rate=<sample rate> " @@ -62,6 +63,8 @@ PA_MODULE_USAGE( #define FILTER_PARAMETER 0.125 +#define DEFAULT_ADJUST_THRESHOLD_USEC 200 + #define MEMBLOCKQ_MAXLENGTH (1024*1024*32) #define MIN_DEVICE_LATENCY (2.5*PA_USEC_PER_MSEC) @@ -97,6 +100,7 @@ struct userdata { /* Values from command line configuration */ pa_usec_t latency; + uint32_t adjust_threshold; pa_usec_t adjust_time; bool low_device_latency; @@ -151,6 +155,7 @@ static const char* const valid_modargs[] = { "sink", "adjust_time", "latency_msec", + "adjust_threshold_usec", "low_device_latency", "format", "rate", @@ -213,10 +218,9 @@ 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â?° + * - maximum deviation from optimum rate for P-controller is less than 1% + * - P-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, @@ -225,7 +229,21 @@ static uint32_t rate_controller( int32_t latency_difference_usec_2) { double new_rate_1, new_rate_2, new_rate; - 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 base_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_usec) / 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_usec) / u->real_adjust_time / 0.002 + 1; @@ -234,10 +252,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_usec) / u->real_adjust_time / 0.01 + 1; - new_rate_2 = (double)base_rate * (1.0 + (double)latency_difference_usec / min_cycles_2 / u->real_adjust_time); + new_rate_2 = (double)base_rate * (1.0 + controller_weight * latency_difference_usec / 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; @@ -1109,7 +1128,7 @@ int pa__init(pa_module *m) { pa_source *source = NULL; pa_source_output_new_data source_output_data; bool source_dont_move; - uint32_t latency_msec; + uint32_t latency_msec, adjust_threshold; pa_sample_spec ss; pa_channel_map map; bool format_set = false; @@ -1189,6 +1208,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"); @@ -1212,6 +1237,7 @@ int pa__init(pa_module *m) { u->extra_latency = 0; u->latency_error = 0; u->low_device_latency = low_device_latency; + u->adjust_threshold = adjust_threshold; /* The default adjust time is set to 1s. The adjust time given on the * command line is considered to be in seconds if it is below 100, -- 2.8.1