- pa_log_debug("Write index deviates by %0.2f ms, expected %0.2f ms", (double) latency/PA_USEC_PER_MSEC, (double) s->intended_latency/PA_USEC_PER_MSEC);
-
- /* Calculate deviation */
- if (latency < s->intended_latency)
- fix = s->intended_latency - latency;
- else
- fix = latency - s->intended_latency;
-
- /* How many samples is this per second? */
- fix_samples = (unsigned) (fix * (pa_usec_t) s->sink_input->thread_info.sample_spec.rate / (pa_usec_t) RATE_UPDATE_INTERVAL);
-
- /* Check if deviation is in bounds */
- if (fix_samples > s->sink_input->sample_spec.rate*.50)
- pa_log_debug("Hmmm, rate fix is too large (%lu Hz), not applying.", (unsigned long) fix_samples);
- else {
- /* Fix up rate */
- if (latency < s->intended_latency)
- s->sink_input->sample_spec.rate -= fix_samples;
- else
- s->sink_input->sample_spec.rate += fix_samples;
-
- if (s->sink_input->sample_spec.rate > PA_RATE_MAX)
- s->sink_input->sample_spec.rate = PA_RATE_MAX;
+ pa_log_debug("Write index deviates by %0.2f ms, expected %0.2f ms", (double) latency/PA_USEC_PER_MSEC, (double) s->intended_latency/PA_USEC_PER_MSEC);
+
+ /* The buffer is filling with some unknown rate R̂ samples/second. If the rate of reading in
+ * the last T seconds was Rⁿ, then the increase in buffer latency ΔLⁿ = Lⁿ - Lⁿ⁻ⁱ in that
+ * same period is ΔLⁿ = (TR̂ - TRⁿ) / R̂, giving the estimated target rate
+ * T
+ * R̂ = ─────────────── Rⁿ . (1)
+ * T - (Lⁿ - Lⁿ⁻ⁱ)
+ *
+ * Setting the sample rate to R̂ results in the latency being constant (if the estimate of R̂
+ * is correct). But there is also the requirement to keep the buffer at a predefined target
+ * latency L̂. So instead of setting Rⁿ⁺ⁱ to R̂ immediately, the strategy will be to reduce R
+ * from Rⁿ⁺ⁱ to R̂ in a steps of T seconds, where Rⁿ⁺ⁱ is chosen such that in the total time
+ * aT the latency is reduced from Lⁿ to L̂. This strategy translates to the requirements
+ * ₐ R̂ - Rⁿ⁺ʲ a-j+1 j-1
+ * Σ T ────────── = L̂ - Lⁿ with Rⁿ⁺ʲ = ───── Rⁿ⁺ⁱ + ───── R̂ .
+ * ʲ⁼ⁱ R̂ a a
+ * Solving for Rⁿ⁺ⁱ gives
+ * T - ²∕ₐ₊₁(L̂ - Lⁿ)
+ * Rⁿ⁺ⁱ = ───────────────── R̂ . (2)
+ * T
+ * In the code below a = 7 is used.
+ *
+ * Equation (1) is not directly used in (2), but instead an exponentially weighted average
+ * of the estimated rate R̂ is used. This average R̅ is defined as
+ * R̅ⁿ = α R̂ⁿ + (1-α) R̅ⁿ⁻ⁱ .
+ * Because it is difficult to find a fixed value for the coefficient α such that the
+ * averaging is without significant lag but oscillations are filtered out, a heuristic is
+ * used. When the successive estimates R̂ⁿ do not change much then α→1, but when there is a
+ * sudden spike in the estimated rate α→0, such that the deviation is given little weight.
+ */
+ estimated_rate = (double) current_rate * (double) RATE_UPDATE_INTERVAL / (double) (RATE_UPDATE_INTERVAL + s->last_latency - latency);
+ if (fabs(s->estimated_rate - s->avg_estimated_rate) > 1) {
+ double ratio = (estimated_rate + s->estimated_rate - 2*s->avg_estimated_rate) / (s->estimated_rate - s->avg_estimated_rate);
+ alpha = PA_CLAMP(2 * (ratio + fabs(ratio)) / (4 + ratio*ratio), 0.02, 0.8);
+ }
+ s->avg_estimated_rate = alpha * estimated_rate + (1-alpha) * s->avg_estimated_rate;
+ s->estimated_rate = estimated_rate;
+ pa_log_debug("Estimated target rate: %.0f Hz, using average of %.0f Hz (α=%.3f)", estimated_rate, s->avg_estimated_rate, alpha);
+ new_rate = (uint32_t) ((double) (RATE_UPDATE_INTERVAL + latency/4 - s->intended_latency/4) / (double) RATE_UPDATE_INTERVAL * s->avg_estimated_rate);
+ s->last_latency = latency;
+
+ if (new_rate < (uint32_t) (base_rate*0.8) || new_rate > (uint32_t) (base_rate*1.25)) {
+ pa_log_warn("Sample rates too different, not adjusting (%u vs. %u).", base_rate, new_rate);
+ new_rate = base_rate;
+ } else {
+ if (base_rate < new_rate + 20 && new_rate < base_rate + 20)
+ new_rate = base_rate;
+ /* 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("New rate of %u Hz not within 2‰ of %u Hz, forcing smaller adjustment", new_rate, current_rate);
+ new_rate = PA_CLAMP(new_rate, (uint32_t) (current_rate*0.998), (uint32_t) (current_rate*1.002));
+ }