summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorg Chini <georg@chini.tk>2021-01-01 00:23:10 +0100
committerPulseAudio Marge Bot <pulseaudio-maintainers@lists.freedesktop.org>2021-11-03 18:37:31 +0000
commitda539ed33640ad7cf71bbe3bc6f136e0643bcc1e (patch)
tree6885f47726798e2fb7c567b310b4e5be0e8d9659
parent656179a8fe16126a17b8dc48ae44f97bfcfb26a6 (diff)
sink-input: Implement resampler pseudo rewinding
This patch uses the two previous patches to implemnt pseudo-rewinding for the resamplers by feeding some old data into the resampler after a reset. This is necessary because PA is using external resamplers that do not implement rewinding. To get exactly the same output data from the resampler after a rewind if possible, the matching period is calculated. This is the number of input samples that produces an integral number of output samples. After the matching period, the resampler state repeats. If the matching period is not too large, feeding history into the resampler will start at a point that is a multiple of the matching period back in time. Then the resampler will produce exactly the same samples. The PA_RESAMPLER_MAX_HISTORY value has been replaced by PA_RESAMPLER_MAX_DELAY_USEC and the required number of history samples is calculated from the sink input sample rate. The number of history samples can be as large as about 12500. This fixes glitches during volume changes when the sink runs on a rate different from the sink input rate. Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/120>
-rw-r--r--src/pulsecore/resampler.c43
-rw-r--r--src/pulsecore/resampler.h13
-rw-r--r--src/pulsecore/sink-input.c126
3 files changed, 161 insertions, 21 deletions
diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c
index 0d3ac407e..b035f67ed 100644
--- a/src/pulsecore/resampler.c
+++ b/src/pulsecore/resampler.c
@@ -122,6 +122,24 @@ static int (* const init_table[])(pa_resampler *r) = {
#endif
};
+static void calculate_gcd(pa_resampler *r) {
+ unsigned gcd, n;
+
+ pa_assert(r);
+
+ gcd = r->i_ss.rate;
+ n = r->o_ss.rate;
+
+ while (n != 0) {
+ unsigned tmp = gcd;
+
+ gcd = n;
+ n = tmp % n;
+ }
+
+ r->gcd = gcd;
+}
+
static pa_resample_method_t choose_auto_resampler(pa_resample_flags_t flags) {
pa_resample_method_t method;
@@ -354,6 +372,7 @@ pa_resampler* pa_resampler_new(
/* Fill sample specs */
r->i_ss = *a;
r->o_ss = *b;
+ calculate_gcd(r);
if (am)
r->i_cm = *am;
@@ -485,6 +504,7 @@ void pa_resampler_set_input_rate(pa_resampler *r, uint32_t rate) {
r->out_frames = 0;
r->i_ss.rate = rate;
+ calculate_gcd(r);
r->impl.update_rates(r);
}
@@ -502,6 +522,7 @@ void pa_resampler_set_output_rate(pa_resampler *r, uint32_t rate) {
r->out_frames = 0;
r->o_ss.rate = rate;
+ calculate_gcd(r);
r->impl.update_rates(r);
@@ -1634,6 +1655,28 @@ pa_usec_t pa_resampler_get_delay_usec(pa_resampler *r) {
return (pa_usec_t) (pa_resampler_get_delay(r, false) * PA_USEC_PER_SEC / r->i_ss.rate);
}
+/* Get GCD of input and output rate. */
+unsigned pa_resampler_get_gcd(pa_resampler *r) {
+ pa_assert(r);
+
+ return r->gcd;
+}
+
+/* Get maximum resampler history. The resamplers have finite impulse response, so really old
+ * data (more than 2x the resampler latency) cannot affect the output. This means, that in an
+ * ideal case, we should re-run 2 - 3 times the resampler delay through the resampler when it
+ * is rewound. On the other hand this would mean for high sample rates that more than 25000
+ * samples would need to be used (384k * 33ms). Therefore limit the history to 1.5 times the
+ * maximum resampler delay, which should be fully sufficient in most cases and allows to run
+ * at least more than one delay through the resampler in case of high rates. */
+size_t pa_resampler_get_max_history(pa_resampler *r) {
+
+ if (!r)
+ return 0;
+
+ return (uint64_t) PA_RESAMPLER_MAX_DELAY_USEC * r->i_ss.rate * 3 / PA_USEC_PER_SEC / 2;
+}
+
/*** copy (noop) implementation ***/
static int copy_init(pa_resampler *r) {
diff --git a/src/pulsecore/resampler.h b/src/pulsecore/resampler.h
index e98146d85..2bb21f964 100644
--- a/src/pulsecore/resampler.h
+++ b/src/pulsecore/resampler.h
@@ -73,7 +73,11 @@ typedef enum pa_resample_flags {
PA_RESAMPLER_CONSUME_LFE = 0x0040U,
} pa_resample_flags_t;
-#define PA_RESAMPLER_MAX_HISTORY 64
+/* Currently, the soxr reampler has the largest delay of all supported resamplers.
+ * The maximum value below has been obtained empirically and contains a safety
+ * margin of about 3ms. If the resampler configuration is changed or additional
+ * resamplers are added, the constant must be re-evaluated. */
+#define PA_RESAMPLER_MAX_DELAY_USEC 33000
struct pa_resampler {
pa_resample_method_t method;
@@ -113,6 +117,7 @@ struct pa_resampler {
double in_frames;
double out_frames;
+ unsigned gcd;
pa_lfe_filter_t *lfe_filter;
@@ -176,6 +181,12 @@ double pa_resampler_get_delay(pa_resampler *r, bool allow_negative);
/* Get delay of the resampler in usec */
pa_usec_t pa_resampler_get_delay_usec(pa_resampler *r);
+/* Get the GCD of input and outpu rate */
+unsigned pa_resampler_get_gcd(pa_resampler *r);
+
+/* Get maximum number of history frames */
+size_t pa_resampler_get_max_history(pa_resampler *r);
+
const pa_channel_map* pa_resampler_input_channel_map(pa_resampler *r);
const pa_sample_spec* pa_resampler_input_sample_spec(pa_resampler *r);
const pa_channel_map* pa_resampler_output_channel_map(pa_resampler *r);
diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
index 29c6a795b..81d16bf0c 100644
--- a/src/pulsecore/sink-input.c
+++ b/src/pulsecore/sink-input.c
@@ -29,6 +29,7 @@
#include <pulse/xmalloc.h>
#include <pulse/util.h>
#include <pulse/internal.h>
+#include <pulse/timeval.h>
#include <pulsecore/core-format.h>
#include <pulsecore/mix.h>
@@ -53,6 +54,66 @@ struct volume_factor_entry {
pa_cvolume volume;
};
+/* Calculate number of input samples for the resampler so that either the number
+ * of input samples or the number of output samples matches the defined history
+ * length. */
+static size_t calculate_resampler_history_bytes(pa_sink_input *i, size_t in_rewind_frames) {
+ size_t history_frames, history_max, matching_period, total_frames, remainder;
+ double delay;
+ pa_resampler *r;
+
+ if (!(r = i->thread_info.resampler))
+ return 0;
+
+ /* Initialize some variables, cut off full seconds from the rewind */
+ total_frames = 0;
+ in_rewind_frames = in_rewind_frames % r->i_ss.rate;
+ history_max = pa_resampler_get_max_history(r);
+
+ /* Get the current internal delay of the resampler */
+ delay = pa_resampler_get_delay(r, false);
+
+ /* Calculate the matching period */
+ matching_period = r->i_ss.rate / pa_resampler_get_gcd(r);
+ pa_log_debug("Integral period length is %lu input frames", matching_period);
+
+ /* If the delay is larger than the length of the history queue, we can only
+ * replay as much as we have. */
+ if ((size_t)delay >= history_max) {
+ history_frames = history_max;
+ pa_log_debug("Resampler delay exceeds maximum history");
+ return history_frames * r->i_fz;
+ }
+
+ /* Initially set the history to 3 times the resampler delay. Use at least 2 ms.
+ * We try to find a value between 2 and 3 times the resampler delay to ensure
+ * that the old data has no impact anymore. See also comment to
+ * pa_resampler_get_max_history() in resampler.c. */
+ history_frames = (size_t)(delay * 3.0);
+ history_frames = PA_MAX(history_frames, r->i_ss.rate / 500);
+
+ /* Check how the rewind fits into multiples of the matching period. */
+ remainder = (in_rewind_frames + history_frames) % matching_period;
+
+ /* If possible, use between 2 and 3 times the resampler delay */
+ if (remainder < (size_t)delay && history_frames - remainder <= history_max)
+ total_frames = in_rewind_frames + history_frames - remainder;
+ /* Else, try above 3 times the delay */
+ else if (history_frames + matching_period - remainder <= history_max)
+ total_frames = in_rewind_frames + history_frames + matching_period - remainder;
+
+ if (total_frames != 0)
+ /* We found a perfect match. */
+ history_frames = total_frames - in_rewind_frames;
+ else {
+ /* Try to use 2.5 times the delay. */
+ history_frames = PA_MIN((size_t)(delay * 2.5), history_max);
+ pa_log_debug("No usable integral matching period");
+ }
+
+ return history_frames * r->i_fz;
+}
+
static struct volume_factor_entry *volume_factor_entry_new(const char *key, const pa_cvolume *volume) {
struct volume_factor_entry *entry;
@@ -1149,7 +1210,7 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam
pa_memblockq_flush_write(i->thread_info.history_memblockq, true);
} else if (i->thread_info.rewrite_nbytes > 0) {
- size_t max_rewrite, amount;
+ size_t max_rewrite, sink_amount, sink_input_amount;
/* Calculate how much make sense to rewrite at most */
max_rewrite = nbytes;
@@ -1157,33 +1218,54 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam
max_rewrite += lbq;
/* Transform into local domain */
- max_rewrite = pa_resampler_request(i->thread_info.resampler, max_rewrite);
+ sink_input_amount = pa_resampler_request(i->thread_info.resampler, max_rewrite);
/* Calculate how much of the rewinded data should actually be rewritten */
- amount = PA_MIN(i->thread_info.rewrite_nbytes, max_rewrite);
+ sink_input_amount = PA_MIN(i->thread_info.rewrite_nbytes, sink_input_amount);
+
+ /* Transform to sink domain */
+ sink_amount = pa_resampler_result(i->thread_info.resampler, sink_input_amount);
- if (amount > 0) {
- pa_log_debug("Have to rewind %lu bytes on implementor.", (unsigned long) amount);
+ if (sink_input_amount > 0) {
+ pa_log_debug("Have to rewind %lu bytes on implementor.", (unsigned long) sink_input_amount);
/* Tell the implementor */
if (i->process_rewind)
- i->process_rewind(i, amount);
+ i->process_rewind(i, sink_input_amount);
called = true;
- if (amount > 0)
- /* Update the history write pointer */
- pa_memblockq_seek(i->thread_info.history_memblockq, - ((int64_t) amount), PA_SEEK_RELATIVE, true);
-
- /* Convert back to sink domain */
- amount = pa_resampler_result(i->thread_info.resampler, amount);
-
- if (amount > 0)
- /* Ok, now update the write pointer */
- pa_memblockq_seek(i->thread_info.render_memblockq, - ((int64_t) amount), PA_SEEK_RELATIVE, true);
+ /* Update the write pointer. Use pa_resampler_result(r, sink_input_amount) instead
+ * of sink_amount because the two may differ and the actual replay of the samples
+ * will produce pa_resampler_result(r, sink_input_amount) samples. */
+ pa_memblockq_seek(i->thread_info.render_memblockq, - ((int64_t) pa_resampler_result(i->thread_info.resampler, sink_input_amount)),PA_SEEK_RELATIVE, true);
+
+ /* Rewind the resampler */
+ if (i->thread_info.resampler) {
+ size_t history_bytes;
+ int64_t history_result;
+
+ history_bytes = calculate_resampler_history_bytes(i, sink_input_amount / pa_frame_size(&i->sample_spec));
+
+ if (history_bytes > 0) {
+ history_result = pa_resampler_rewind(i->thread_info.resampler, sink_amount, i->thread_info.history_memblockq, history_bytes);
+
+ /* We may have produced one sample too much or or one sample less than expected.
+ * The replay of the rewound sink input data will then produce a deviation in
+ * the other direction, so that the total number of produced samples matches
+ * pa_resampler_result(r, sink_input_amount + history_bytes). Therefore we have
+ * to correct the write pointer of the render queue accordingly.
+ * Strictly this is only true, if the history can be replayed from a known
+ * resampler state, that is if a true matching period exists. In case where
+ * we are using an approximate matching period, we may still loose or duplicate
+ * one sample during rewind. */
+ history_result -= (int64_t) pa_resampler_result(i->thread_info.resampler, history_bytes);
+ if (history_result != 0)
+ pa_memblockq_seek(i->thread_info.render_memblockq, history_result, PA_SEEK_RELATIVE, true);
+ }
+ }
- /* And rewind the resampler */
- if (i->thread_info.resampler)
- pa_resampler_rewind(i->thread_info.resampler, amount, NULL, 0);
+ /* Update the history write pointer */
+ pa_memblockq_seek(i->thread_info.history_memblockq, - ((int64_t) sink_input_amount), PA_SEEK_RELATIVE, true);
if (i->thread_info.rewrite_flush) {
pa_memblockq_silence(i->thread_info.render_memblockq);
@@ -1223,6 +1305,7 @@ size_t pa_sink_input_get_max_request(pa_sink_input *i) {
/* Called from thread context */
void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */) {
size_t max_rewind;
+ size_t resampler_history;
pa_sink_input_assert_ref(i);
pa_sink_input_assert_io_context(i);
@@ -1232,8 +1315,11 @@ void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes /* in the
pa_memblockq_set_maxrewind(i->thread_info.render_memblockq, nbytes);
max_rewind = pa_resampler_request(i->thread_info.resampler, nbytes);
+ /* Calculate maximum history needed */
+ resampler_history = pa_resampler_get_max_history(i->thread_info.resampler);
+ resampler_history *= pa_frame_size(&i->sample_spec);
- pa_memblockq_set_maxrewind(i->thread_info.history_memblockq, max_rewind + PA_RESAMPLER_MAX_HISTORY * pa_frame_size(&i->sample_spec));
+ pa_memblockq_set_maxrewind(i->thread_info.history_memblockq, max_rewind + resampler_history);
if (i->update_max_rewind)
i->update_max_rewind(i, max_rewind);