[pulseaudio-discuss] [PATCH 15/22] loopback: Optimize adaptive re-sampling
Georg Chini
georg at chini.tk
Mon Feb 13 12:02:12 UTC 2017
The current code assumes that the time domains of source and sink are
equal. This leads to a saw-tooth characteristics of the resulting end
to end latency.
This patch adds an iterative calculation of an optimum rate which accounts
for the difference between the source and sink time domains, thereby massively
enhancing the latency stability. Theoretcal background for the calculation
can be found at
https://www.freedesktop.org/software/pulseaudio/misc/rate_estimator.odt
---
src/modules/module-loopback.c | 88 +++++++++++++++++++++++++++++++++++--------
1 file changed, 73 insertions(+), 15 deletions(-)
diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index 1088789..d856929 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)
@@ -104,6 +106,13 @@ struct userdata {
int64_t sink_latency_offset;
pa_usec_t minimum_latency;
+ /* State variable of the latency controller */
+ int32_t last_latency_difference;
+
+ /* Filter varables used for 2nd order filter */
+ double drift_filter;
+ double drift_compensation_rate;
+
/* lower latency limit found by underruns */
pa_usec_t underrun_latency_limit;
@@ -112,8 +121,12 @@ struct userdata {
uint32_t underrun_counter;
uint32_t adjust_counter;
+ /* Various booleans */
bool fixed_alsa_source;
bool source_sink_changed;
+ bool underrun_occured;
+ bool source_latency_offset_changed;
+ bool sink_latency_offset_changed;
/* Used for sink input and source output snapshots */
struct {
@@ -235,24 +248,26 @@ 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‰
+ * - 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,
- int32_t latency_difference_usec) {
+ int32_t latency_difference_at_optimum_rate,
+ int32_t latency_difference_at_base_rate) {
- uint32_t new_rate, new_rate_1, new_rate_2;
- double min_cycles_1, min_cycles_2;
+ double new_rate, new_rate_1, new_rate_2;
+ 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;
- new_rate_1 = old_rate + base_rate * (double)latency_difference_usec / min_cycles_1 / u->real_adjust_time;
+ min_cycles_1 = (double)abs(latency_difference_at_optimum_rate) / u->real_adjust_time / 0.002 + 1;
+ new_rate_1 = old_rate + base_rate * (double)latency_difference_at_optimum_rate / min_cycles_1 / u->real_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) / 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);
+ 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);
/* Choose the rate that is nearer to base_rate */
if (abs(new_rate_1 - base_rate) < abs(new_rate_2 - base_rate))
@@ -260,7 +275,28 @@ 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, an underrun or latency offset change */
+
+ if (!u->underrun_occured && !u->source_sink_changed && !u->source_latency_offset_changed && !u->sink_latency_offset_changed) {
+ /* Latency difference between last iterations */
+ latency_drift = latency_difference_at_base_rate - u->last_latency_difference;
+
+ /* Calculate frequency difference between source and sink */
+ drift_rate = latency_drift * old_rate / 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. */
+ new_rate = new_rate + u->drift_compensation_rate + 0.5;
+
+ if (new_rate > PA_RATE_MAX * 101 / 100)
+ return PA_RATE_MAX * 101 / 100;
+ else
+ return (int)new_rate;
}
/* Called from main thread.
@@ -361,9 +397,12 @@ static void adjust_rates(struct userdata *u) {
pa_log_info("Underrun counter: %u", u->underrun_counter);
}
- /* Calculate real adjust time */
+ /* Reset drift compensation parameters if source or sink changed, else calculate real adjust time */
now = pa_rtclock_now();
- if (!u->source_sink_changed) {
+ if (u->source_sink_changed) {
+ u->drift_compensation_rate = 0;
+ u->drift_filter = 0;
+ } else {
u->adjust_counter++;
u->real_adjust_time_sum += now - u->adjust_time_stamp;
u->real_adjust_time = u->real_adjust_time_sum / u->adjust_counter;
@@ -387,11 +426,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);
- latency_difference = (int32_t)(latency_at_optimum_rate - final_latency);
+ latency_difference = (int32_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,
@@ -399,12 +438,19 @@ static void adjust_rates(struct userdata *u) {
(double) u->latency_snapshot.source_latency / PA_USEC_PER_MSEC,
(double) current_latency / PA_USEC_PER_MSEC);
- pa_log_debug("Loopback latency at base rate is %0.2f ms", (double)latency_at_optimum_rate / PA_USEC_PER_MSEC);
+ pa_log_debug("Loopback latency at optimum 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, (int32_t)(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;
+
+ /* Set variables that may change between calls of adjust_rate() */
u->source_sink_changed = false;
+ u->underrun_occured = false;
+ u->source_latency_offset_changed = false;
+ u->sink_latency_offset_changed = false;
/* Set rate */
pa_sink_input_set_rate(u->sink_input, new_rate);
@@ -727,6 +773,8 @@ static void source_output_moving_cb(pa_source_output *o, pa_source *dest) {
u->underrun_counter = 0;
u->source_sink_changed = true;
+ u->underrun_occured = false;
+ u->source_latency_offset_changed = false;
/* Send a mesage to the output thread that the source has changed.
* If the sink is invalid here during a profile switching situation
@@ -1097,6 +1145,8 @@ static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
u->underrun_counter = 0;
u->source_sink_changed = true;
+ u->underrun_occured = false;
+ u->sink_latency_offset_changed = false;
/* Send a message to the output thread that the sink has changed */
pa_asyncmsgq_send(dest->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_SINK_CHANGED, NULL, 0, NULL);
@@ -1207,6 +1257,7 @@ static int loopback_process_msg_cb(pa_msgobject *o, int code, void *userdata, in
case LOOPBACK_MESSAGE_UNDERRUN:
u->underrun_counter++;
+ u->underrun_occured = true;
return 0;
}
@@ -1214,6 +1265,7 @@ static int loopback_process_msg_cb(pa_msgobject *o, int code, void *userdata, in
return 0;
}
+/* Called from main thread */
static pa_hook_result_t sink_port_latency_offset_changed_cb(pa_core *core, pa_sink *sink, struct userdata *u) {
if (!u->sink_input->sink)
@@ -1222,12 +1274,14 @@ static pa_hook_result_t sink_port_latency_offset_changed_cb(pa_core *core, pa_si
if (sink != u->sink_input->sink)
return PA_HOOK_OK;
+ u->sink_latency_offset_changed = true;
u->sink_latency_offset = sink->port_latency_offset;
update_minimum_latency(u, sink, true);
return PA_HOOK_OK;
}
+/* Called from main thread */
static pa_hook_result_t source_port_latency_offset_changed_cb(pa_core *core, pa_source *source, struct userdata *u) {
if (!u->source_output->source)
@@ -1236,6 +1290,7 @@ static pa_hook_result_t source_port_latency_offset_changed_cb(pa_core *core, pa_
if (source != u->source_output->source)
return PA_HOOK_OK;
+ u->source_latency_offset_changed = true;
u->source_latency_offset = source->port_latency_offset;
update_minimum_latency(u, u->sink_input->sink, true);
@@ -1355,6 +1410,9 @@ int pa__init(pa_module *m) {
u->source_sink_changed = true;
u->real_adjust_time_sum = 0;
u->adjust_counter = 0;
+ u->underrun_occured = false;
+ u->source_latency_offset_changed = false;
+ u->sink_latency_offset_changed = false;
adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC;
if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) {
--
2.10.1
More information about the pulseaudio-discuss
mailing list