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(-) 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); } -- 2.14.1