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