[pulseaudio-discuss] [PATCH v6 19/25] loopback: Implement adaptive re-sampling
Georg Chini
georg at chini.tk
Sun Jun 5 19:05:22 UTC 2016
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
More information about the pulseaudio-discuss
mailing list