diff --git a/src/.gitignore b/src/.gitignore
index 1157c9fdc..65a7e1072 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -71,6 +71,7 @@ proplist-test
queue-test
remix-test
resampler-test
+resampler-rewind-test
rtpoll-test
rtstutter
sig2str-test
diff --git a/src/tests/meson.build b/src/tests/meson.build
index acd301037..42f60538c 100644
--- a/src/tests/meson.build
+++ b/src/tests/meson.build
@@ -70,6 +70,8 @@ if get_option('daemon')
[ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
[ 'resampler-test', 'resampler-test.c',
[ libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libintl_dep ] ],
+ [ 'resampler-rewind-test', 'resampler-rewind-test.c',
+ [ libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libintl_dep, libm_dep ] ],
[ 'rtpoll-test', 'rtpoll-test.c',
[ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
[ 'smoother-test', 'smoother-test.c',
diff --git a/src/tests/resampler-rewind-test.c b/src/tests/resampler-rewind-test.c
new file mode 100644
index 000000000..158a84214
--- /dev/null
+++ b/src/tests/resampler-rewind-test.c
@@ -0,0 +1,437 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see .
+***/
+
+#ifdef HAVE_CONFIG_H
+#include
+#endif
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
+#define PA_SILENCE_MAX (pa_page_size()*16)
+#define MAX_MATCHING_PERIOD 500
+
+static pa_memblock *silence_memblock_new(pa_mempool *pool, uint8_t c) {
+ pa_memblock *b;
+ size_t length;
+ void *data;
+
+ pa_assert(pool);
+
+ length = PA_MIN(pa_mempool_block_size_max(pool), PA_SILENCE_MAX);
+
+ b = pa_memblock_new(pool, length);
+
+ data = pa_memblock_acquire(b);
+ memset(data, c, length);
+ pa_memblock_release(b);
+
+ pa_memblock_set_is_silence(b, true);
+
+ return b;
+}
+
+/* Calculate number of history bytes needed for the rewind */
+static size_t calculate_resampler_history_bytes(pa_resampler *r, size_t in_rewind_frames) {
+ size_t history_frames, history_max, matching_period, total_frames, remainder;
+ double delay;
+
+ if (!r)
+ 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 = (uint64_t) PA_RESAMPLER_MAX_DELAY_USEC * r->i_ss.rate * 3 / PA_USEC_PER_SEC / 2;
+
+ /* Get the current internal delay of the resampler */
+ delay = pa_resampler_get_delay(r, false);
+
+ /* Calculate the matchiung 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;
+ return history_frames * r->i_fz;
+ }
+
+ /* Initially set the history to 3 times the resampler delay. Use at least 2 ms. */
+ 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 float compare_blocks(const pa_sample_spec *ss, const pa_memchunk *chunk_a, const pa_memchunk *chunk_b) {
+ float *a, *b, max_diff = 0;
+ unsigned i;
+
+ a = pa_memblock_acquire(chunk_a->memblock);
+ b = pa_memblock_acquire(chunk_b->memblock);
+ a += chunk_a->index / pa_frame_size(ss);
+ b += chunk_b->index / pa_frame_size(ss);
+
+ for (i = 0; i < chunk_a->length / pa_frame_size(ss); i++) {
+ if (fabs(a[i] - b[i]) > max_diff)
+ max_diff = fabs(a[i] - b[i]);
+ }
+
+ pa_memblock_release(chunk_a->memblock);
+ pa_memblock_release(chunk_b->memblock);
+
+ return max_diff;
+}
+
+static pa_memblock* generate_block(pa_mempool *pool, const pa_sample_spec *ss, unsigned frequency, double amplitude, size_t nr_of_samples) {
+ pa_memblock *r;
+ float *d;
+ float val;
+ unsigned i;
+ int n;
+ float t, dt, dt_period;
+
+ pa_assert(frequency);
+ pa_assert(nr_of_samples);
+ pa_assert(ss->channels == 1);
+ pa_assert(ss->format == PA_SAMPLE_FLOAT32NE);
+
+ pa_assert_se(r = pa_memblock_new(pool, pa_frame_size(ss) * nr_of_samples));
+ d = pa_memblock_acquire(r);
+
+ /* Generate square wave with given length, frequency and sample rate. */
+ val = amplitude;
+ t = 0;
+ n = 1;
+ dt = 1.0 / ss->rate;
+ dt_period = 1.0 / frequency;
+ for (i=0; i < nr_of_samples; i++) {
+ d[i] = val;
+
+ if ((int)(2 * t / dt_period) >= n) {
+ n++;
+ if (val >= amplitude)
+ val = - amplitude;
+ else
+ val = amplitude;
+ }
+
+ t += dt;
+ }
+
+ pa_memblock_release(r);
+
+ return r;
+}
+
+static void help(const char *argv0) {
+ printf("%s [options]\n\n"
+ "-h, --help Show this help\n"
+ "-v, --verbose Print debug messages\n"
+ " --from-rate=SAMPLERATE From sample rate in Hz (defaults to 44100)\n"
+ " --to-rate=SAMPLERATE To sample rate in Hz (defaults to 44100)\n"
+ " --resample-method=METHOD Resample method (defaults to auto)\n"
+ " --frequency=unsigned Frequency of square wave\n"
+ " --samples=unsigned Number of samples for square wave\n"
+ " --rewind=unsigned Number of output samples to rewind\n"
+ "\n"
+ "This test generates samples for a square wave of given frequency, number of samples\n"
+ "and input sample rate. Then this input data is resampled to the output rate, rewound\n"
+ "by rewind samples and the rewound part is processed again. Then output is compared to\n"
+ "the result of the first pass.\n"
+ "\n"
+ "See --dump-resample-methods for possible values of resample methods.\n",
+ argv0);
+}
+
+enum {
+ ARG_VERSION = 256,
+ ARG_FROM_SAMPLERATE,
+ ARG_TO_SAMPLERATE,
+ ARG_FREQUENCY,
+ ARG_SAMPLES,
+ ARG_REWIND,
+ ARG_RESAMPLE_METHOD,
+ ARG_DUMP_RESAMPLE_METHODS
+};
+
+static void dump_resample_methods(void) {
+ int i;
+
+ for (i = 0; i < PA_RESAMPLER_MAX; i++)
+ if (pa_resample_method_supported(i))
+ printf("%s\n", pa_resample_method_to_string(i));
+
+}
+
+int main(int argc, char *argv[]) {
+ pa_mempool *pool = NULL;
+ pa_sample_spec a, b;
+ pa_resample_method_t method;
+ int ret = 1, c;
+ unsigned samples, frequency, rewind;
+ unsigned crossover_freq = 120;
+ pa_resampler *resampler;
+ pa_memchunk in_chunk, out_chunk, rewound_chunk, silence_chunk;
+ pa_usec_t ts;
+ pa_memblockq *history_queue = NULL;
+ size_t in_rewind_size, in_frame_size, history_size, out_rewind_size, old_length, in_resampler_buffer, n_out_expected;
+ float max_diff;
+ double delay_before, delay_after, delay_expected;
+
+ static const struct option long_options[] = {
+ {"help", 0, NULL, 'h'},
+ {"verbose", 0, NULL, 'v'},
+ {"version", 0, NULL, ARG_VERSION},
+ {"from-rate", 1, NULL, ARG_FROM_SAMPLERATE},
+ {"to-rate", 1, NULL, ARG_TO_SAMPLERATE},
+ {"frequency", 1, NULL, ARG_FREQUENCY},
+ {"samples", 1, NULL, ARG_SAMPLES},
+ {"rewind", 1, NULL, ARG_REWIND},
+ {"resample-method", 1, NULL, ARG_RESAMPLE_METHOD},
+ {"dump-resample-methods", 0, NULL, ARG_DUMP_RESAMPLE_METHODS},
+ {NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+#ifdef ENABLE_NLS
+ bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR);
+#endif
+
+ pa_log_set_level(PA_LOG_WARN);
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_INFO);
+
+ a.channels = b.channels = 1;
+ a.rate = 48000;
+ b.rate = 44100;
+ a.format = b.format = PA_SAMPLE_FLOAT32NE;
+
+ method = PA_RESAMPLER_AUTO;
+ frequency = 1000;
+ samples = 5000;
+ rewind = 2500;
+
+ while ((c = getopt_long(argc, argv, "hv", long_options, NULL)) != -1) {
+
+ switch (c) {
+ case 'h' :
+ help(argv[0]);
+ ret = 0;
+ goto quit;
+
+ case 'v':
+ pa_log_set_level(PA_LOG_DEBUG);
+ break;
+
+ case ARG_VERSION:
+ printf("%s %s\n", argv[0], PACKAGE_VERSION);
+ ret = 0;
+ goto quit;
+
+ case ARG_DUMP_RESAMPLE_METHODS:
+ dump_resample_methods();
+ ret = 0;
+ goto quit;
+
+ case ARG_FROM_SAMPLERATE:
+ a.rate = (uint32_t) atoi(optarg);
+ break;
+
+ case ARG_TO_SAMPLERATE:
+ b.rate = (uint32_t) atoi(optarg);
+ break;
+
+ case ARG_FREQUENCY:
+ frequency = (unsigned) atoi(optarg);
+ break;
+
+ case ARG_SAMPLES:
+ samples = (unsigned) atoi(optarg);
+ break;
+
+ case ARG_REWIND:
+ rewind = (unsigned) atoi(optarg);
+ break;
+
+ case ARG_RESAMPLE_METHOD:
+ if (*optarg == '\0' || pa_streq(optarg, "help")) {
+ dump_resample_methods();
+ ret = 0;
+ goto quit;
+ }
+ method = pa_parse_resample_method(optarg);
+ break;
+
+ default:
+ goto quit;
+ }
+ }
+
+ pa_log_info("=== Square wave %u Hz, %u samples. Resampling using %s from %u Hz to %u Hz, rewinding %u output samples.", frequency,
+ samples, pa_resample_method_to_string(method), a.rate, b.rate, rewind);
+
+ ret = 0;
+ pa_assert_se(pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true));
+
+ pa_log_debug("Compilation CFLAGS: %s", PA_CFLAGS);
+
+ /* Setup resampler */
+ ts = pa_rtclock_now();
+ pa_assert_se(resampler = pa_resampler_new(pool, &a, NULL, &b, NULL, crossover_freq, method, 0));
+ pa_log_info("Init took %llu usec", (long long unsigned)(pa_rtclock_now() - ts));
+
+ /* Generate input data */
+ in_chunk.memblock = generate_block(pool, &a, frequency, 0.5, samples);
+ in_chunk.length = pa_memblock_get_length(in_chunk.memblock);
+ in_chunk.index = 0;
+ in_frame_size = pa_frame_size(&a);
+
+ /* First, resample the full block */
+ ts = pa_rtclock_now();
+ pa_resampler_run(resampler, &in_chunk, &out_chunk);
+ if (!out_chunk.memblock) {
+ pa_memblock_unref(in_chunk.memblock);
+ pa_log_warn("Resampling did not return any output data");
+ ret = 1;
+ goto quit;
+ }
+
+ pa_log_info("resampling took %llu usec.", (long long unsigned)(pa_rtclock_now() - ts));
+ if (rewind > out_chunk.length / pa_frame_size(&b)) {
+ pa_log_warn("Specified number of frames to rewind (%u) larger than number of output frames (%lu), aborting.", rewind, out_chunk.length / pa_frame_size(&b));
+ ret = 1;
+ goto quit1;
+ }
+
+ /* Get delay after first resampling pass */
+ delay_before = pa_resampler_get_delay(resampler, true);
+
+ /* Create and prepare history queue */
+ silence_chunk.memblock = silence_memblock_new(pool, 0);
+ silence_chunk.length = pa_frame_align(pa_memblock_get_length(silence_chunk.memblock), &a);
+ silence_chunk.index = 0;
+ history_queue = pa_memblockq_new("Test-Queue", 0, MEMBLOCKQ_MAXLENGTH, 0, &a, 0, 1, samples * in_frame_size, &silence_chunk);
+ pa_memblock_unref(silence_chunk.memblock);
+
+ pa_memblockq_push(history_queue, &in_chunk);
+ pa_memblockq_drop(history_queue, samples * in_frame_size);
+
+ in_rewind_size = pa_resampler_request(resampler, rewind * pa_frame_size(&b));
+ out_rewind_size = rewind * pa_frame_size(&b);
+ pa_log_debug("Have to rewind %lu input frames", in_rewind_size / in_frame_size);
+ ts = pa_rtclock_now();
+
+ /* Now rewind the resampler */
+ pa_memblockq_rewind(history_queue, in_rewind_size);
+ history_size = calculate_resampler_history_bytes(resampler, in_rewind_size / in_frame_size);
+ pa_log_debug("History is %lu frames.", history_size / in_frame_size);
+ pa_resampler_rewind(resampler, out_rewind_size, history_queue, history_size);
+
+ pa_log_info("Rewind took %llu usec.", (long long unsigned)(pa_rtclock_now() - ts));
+ ts = pa_rtclock_now();
+
+ /* Re-run the resampler */
+ old_length = in_chunk.length;
+ in_chunk.length = in_rewind_size;
+ in_chunk.index = old_length - in_chunk.length;
+ pa_resampler_run(resampler, &in_chunk, &rewound_chunk);
+ if (!rewound_chunk.memblock) {
+ pa_log_warn("Resampler did not return output data for rewind");
+ ret = 1;
+ goto quit1;
+ }
+
+ /* Get delay after rewind */
+ delay_after = pa_resampler_get_delay(resampler, true);
+
+ /* Calculate expected delay */
+ n_out_expected = pa_resampler_result(resampler, in_rewind_size + history_size) / pa_frame_size(&b);
+ delay_expected = delay_before + (double)(in_rewind_size + history_size) / (double)in_frame_size - n_out_expected * (double)a.rate / (double)b.rate;
+
+ /* Check for leftover samples in the resampler buffer */
+ in_resampler_buffer = lround((delay_after - delay_expected) * (double)b.rate / (double)a.rate);
+ if (in_resampler_buffer != 0) {
+ pa_log_debug("%li output frames still in resampler buffer", in_resampler_buffer);
+ }
+
+ pa_log_info("Second resampler run took %llu usec.", (long long unsigned)(pa_rtclock_now() - ts));
+ pa_log_debug("Got %lu output frames", rewound_chunk.length / pa_frame_size(&b));
+ old_length = out_chunk.length;
+ out_chunk.length = rewound_chunk.length;
+ out_chunk.index = old_length - out_chunk.length;
+
+ max_diff = compare_blocks(&b, &out_chunk, &rewound_chunk);
+ pa_log_info("Maximum difference is %.*g", 6, max_diff);
+
+ pa_memblock_unref(rewound_chunk.memblock);
+
+quit1:
+ pa_memblock_unref(in_chunk.memblock);
+ pa_memblock_unref(out_chunk.memblock);
+
+ pa_resampler_free(resampler);
+ if (history_queue)
+ pa_memblockq_free(history_queue);
+
+quit:
+ if (pool)
+ pa_mempool_unref(pool);
+
+ return ret;
+}