From 8b4cb54595baeeb1d9b7d11a842ef7946e43a55a Mon Sep 17 00:00:00 2001 From: Maarten Bosmans Date: Fri, 7 Jan 2011 01:25:55 +0100 Subject: Limit rate adjustments to small, inaudible jumps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The same logic is applied to the sample rate adjustments in module-rtp-recv, module-loopback and module-combine: - Each time an adjustment is made, the new rate can differ at most 2‰ from the old rate. Such a step is equal to 3.5 cents (a cent is 1/100th of a semitone) and as 5 cents is generally considered the smallest observable difference in pitch, this results in inaudible adjustments. - The sample rate of the stream can only differ from the rate of the corresponding sink by 25%. As these adjustments are meant to account for very small clock drifts, any large deviation from the base rate suggests something is seriously wrong. - If the calculated rate is within 20Hz of the base rate, set it to the base rate. This saves CPU because no resampling is necessary. --- src/modules/rtp/module-rtp-recv.c | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) (limited to 'src/modules/rtp/module-rtp-recv.c') diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index 7dbb1efa..3bbeb1fc 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -288,6 +288,9 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { if (s->last_rate_update + RATE_UPDATE_INTERVAL < pa_timeval_load(&now)) { pa_usec_t wi, ri, render_delay, sink_delay = 0, latency, fix; unsigned fix_samples; + uint32_t base_rate = s->sink_input->sink->sample_spec.rate; + uint32_t current_rate = s->sink_input->sample_spec.rate; + uint32_t new_rate; pa_log_debug("Updating sample rate"); @@ -309,7 +312,7 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { else latency = wi - ri; - 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); + 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) @@ -320,19 +323,24 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { /* 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; + if (latency < s->intended_latency) + new_rate = current_rate - fix_samples; + else + new_rate = current_rate + fix_samples; + + 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)); + } } + s->sink_input->sample_spec.rate = new_rate; pa_assert(pa_sample_spec_valid(&s->sink_input->sample_spec)); -- cgit From d053a25b67c5e70c83ed4dcfba1032b293d2cce9 Mon Sep 17 00:00:00 2001 From: Maarten Bosmans Date: Wed, 12 Jan 2011 07:24:58 +0100 Subject: module-rtp-recv: Use new algorithm for adjusting sample rate --- src/modules/rtp/module-rtp-recv.c | 44 ++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 15 deletions(-) (limited to 'src/modules/rtp/module-rtp-recv.c') diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index 3bbeb1fc..4125d281 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -109,6 +109,7 @@ struct session { pa_usec_t sink_latency; pa_usec_t last_rate_update; + pa_usec_t last_latency; }; struct userdata { @@ -286,11 +287,11 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { pa_atomic_store(&s->timestamp, (int) now.tv_sec); if (s->last_rate_update + RATE_UPDATE_INTERVAL < pa_timeval_load(&now)) { - pa_usec_t wi, ri, render_delay, sink_delay = 0, latency, fix; - unsigned fix_samples; + pa_usec_t wi, ri, render_delay, sink_delay = 0, latency; uint32_t base_rate = s->sink_input->sink->sample_spec.rate; uint32_t current_rate = s->sink_input->sample_spec.rate; uint32_t new_rate; + double estimated_rate; pa_log_debug("Updating sample rate"); @@ -314,19 +315,31 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { 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); - - if (latency < s->intended_latency) - new_rate = current_rate - fix_samples; - else - new_rate = current_rate + fix_samples; + /* 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 + * Together Equations (1) and (2) specify the algorithm used below, where a = 7 is used. + */ + estimated_rate = (double) current_rate * (double) RATE_UPDATE_INTERVAL / (double) (RATE_UPDATE_INTERVAL + s->last_latency - latency); + pa_log_debug("Estimated target rate: %.0f Hz", estimated_rate); + new_rate = (uint32_t) ((double) (RATE_UPDATE_INTERVAL + latency/4 - s->intended_latency/4) / (double) RATE_UPDATE_INTERVAL * 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); @@ -488,6 +501,7 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in pa_timeval_load(&now), TRUE); s->last_rate_update = pa_timeval_load(&now); + s->last_latency = LATENCY_USEC; pa_atomic_store(&s->timestamp, (int) now.tv_sec); if ((fd = mcast_socket((const struct sockaddr*) &sdp_info->sa, sdp_info->salen)) < 0) -- cgit From e868638768fc492ff5906a6e38b8223808608976 Mon Sep 17 00:00:00 2001 From: Maarten Bosmans Date: Sun, 16 Jan 2011 01:27:29 +0100 Subject: module-rtp-recv: Average the estimated real sample rate --- src/modules/rtp/module-rtp-recv.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) (limited to 'src/modules/rtp/module-rtp-recv.c') diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index 4125d281..3cf5d890 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -110,6 +110,8 @@ struct session { pa_usec_t last_rate_update; pa_usec_t last_latency; + double estimated_rate; + double avg_estimated_rate; }; struct userdata { @@ -291,7 +293,7 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { uint32_t base_rate = s->sink_input->sink->sample_spec.rate; uint32_t current_rate = s->sink_input->sample_spec.rate; uint32_t new_rate; - double estimated_rate; + double estimated_rate, alpha = 0.02; pa_log_debug("Updating sample rate"); @@ -334,11 +336,25 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { * T - ²∕ₐ₊₁(L̂ - Lⁿ) * Rⁿ⁺ⁱ = ───────────────── R̂ . (2) * T - * Together Equations (1) and (2) specify the algorithm used below, where a = 7 is used. + * 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); - pa_log_debug("Estimated target rate: %.0f Hz", estimated_rate); - new_rate = (uint32_t) ((double) (RATE_UPDATE_INTERVAL + latency/4 - s->intended_latency/4) / (double) RATE_UPDATE_INTERVAL * estimated_rate); + 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)) { @@ -502,6 +518,8 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in TRUE); s->last_rate_update = pa_timeval_load(&now); s->last_latency = LATENCY_USEC; + s->estimated_rate = (double) sink->sample_spec.rate; + s->avg_estimated_rate = (double) sink->sample_spec.rate; pa_atomic_store(&s->timestamp, (int) now.tv_sec); if ((fd = mcast_socket((const struct sockaddr*) &sdp_info->sa, sdp_info->salen)) < 0) -- cgit From 27db0603d6af7d25558af38ed525fc50330a9c32 Mon Sep 17 00:00:00 2001 From: Maarten Bosmans Date: Wed, 12 Jan 2011 07:31:26 +0100 Subject: module-rtp-recv: Remove smoother from write index It isn't necessary anymore with the new algorithm. The slow adjust of the smoother was even detrimental to the accuracy of the rate estimate. --- src/modules/rtp/module-rtp-recv.c | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) (limited to 'src/modules/rtp/module-rtp-recv.c') diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index 3cf5d890..49fdf9b5 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -52,7 +52,6 @@ #include #include #include -#include #include #include @@ -104,7 +103,6 @@ struct session { pa_atomic_t timestamp; - pa_smoother *smoother; pa_usec_t intended_latency; pa_usec_t sink_latency; @@ -197,10 +195,9 @@ static void sink_input_suspend_within_thread(pa_sink_input* i, pa_bool_t b) { pa_sink_input_assert_ref(i); pa_assert_se(s = i->userdata); - if (b) { - pa_smoother_pause(s->smoother, pa_rtclock_now()); + if (b) pa_memblockq_flush_read(s->memblockq); - } else + else s->first_packet = FALSE; } @@ -269,11 +266,6 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { } else pa_rtclock_from_wallclock(&now); - pa_smoother_put(s->smoother, pa_timeval_load(&now), pa_bytes_to_usec((uint64_t) pa_memblockq_get_write_index(s->memblockq), &s->sink_input->sample_spec)); - - /* Tell the smoother that we are rolling now, in case it is still paused */ - pa_smoother_resume(s->smoother, pa_timeval_load(&now), TRUE); - if (pa_memblockq_push(s->memblockq, &chunk) < 0) { pa_log_warn("Queue overrun"); pa_memblockq_seek(s->memblockq, (int64_t) chunk.length, PA_SEEK_RELATIVE, TRUE); @@ -297,7 +289,7 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { pa_log_debug("Updating sample rate"); - wi = pa_smoother_get(s->smoother, pa_timeval_load(&now)); + wi = pa_bytes_to_usec((uint64_t) pa_memblockq_get_write_index(s->memblockq), &s->sink_input->sample_spec); ri = pa_bytes_to_usec((uint64_t) pa_memblockq_get_read_index(s->memblockq), &s->sink_input->sample_spec); pa_log_debug("wi=%lu ri=%lu", (unsigned long) wi, (unsigned long) ri); @@ -508,14 +500,6 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in s->sdp_info = *sdp_info; s->rtpoll_item = NULL; s->intended_latency = LATENCY_USEC; - s->smoother = pa_smoother_new( - PA_USEC_PER_SEC*5, - PA_USEC_PER_SEC*2, - TRUE, - TRUE, - 10, - pa_timeval_load(&now), - TRUE); s->last_rate_update = pa_timeval_load(&now); s->last_latency = LATENCY_USEC; s->estimated_rate = (double) sink->sample_spec.rate; @@ -619,8 +603,6 @@ static void session_free(struct session *s) { pa_sdp_info_destroy(&s->sdp_info); pa_rtp_context_destroy(&s->rtp_context); - pa_smoother_free(s->smoother); - pa_xfree(s); } -- cgit From 72b90ea8ac53e23862284991a2ce355de250f585 Mon Sep 17 00:00:00 2001 From: Maarten Bosmans Date: Wed, 12 Jan 2011 07:34:28 +0100 Subject: module-rtp-recv: Request proper rewind after underrun --- src/modules/rtp/module-rtp-recv.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/modules/rtp/module-rtp-recv.c') diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index 49fdf9b5..b59519fb 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -375,7 +375,9 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { if (pa_memblockq_is_readable(s->memblockq) && s->sink_input->thread_info.underrun_for > 0) { pa_log_debug("Requesting rewind due to end of underrun"); - pa_sink_input_request_rewind(s->sink_input, 0, FALSE, TRUE, FALSE); + pa_sink_input_request_rewind(s->sink_input, + (size_t) (s->sink_input->thread_info.underrun_for == (uint64_t) -1 ? 0 : s->sink_input->thread_info.underrun_for), + FALSE, TRUE, FALSE); } return 1; -- cgit