From 79384530b51e1a17c0f40ba0419c706c2abd3f45 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 16 Jan 2025 19:46:43 +0200 Subject: [PATCH] audioconvert: resampler: add more resampler delay tests Test also conversions between different rates, and with and without prefill. --- .../audioconvert/test-resample-delay.c | 282 ++++++++++++++---- 1 file changed, 228 insertions(+), 54 deletions(-) diff --git a/spa/plugins/audioconvert/test-resample-delay.c b/spa/plugins/audioconvert/test-resample-delay.c index 758328e9a..93da2300d 100644 --- a/spa/plugins/audioconvert/test-resample-delay.c +++ b/spa/plugins/audioconvert/test-resample-delay.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -18,27 +19,37 @@ SPA_LOG_IMPL(logger); #include "resample.h" -static float samp_in[4096]; -static float samp_out[4096]; +static float samp_in[65536]; +static float samp_out[65536]; +static bool force_print; +static void assert_test(bool check) +{ + if (!check) + fprintf(stderr, "FAIL\n\n"); +#if 1 + spa_assert_se(check); +#endif +} -static double difference(float delay, const float *a, size_t len_a, const float *b, size_t len_b) +static double difference(double delay, const float *a, size_t len_a, const float *b, size_t len_b, double b_rate) { size_t i; float c = 0, wa = 0, wb = 0; /* Difference measure: sum((a-b)^2) / sqrt(sum a^2 sum b^2); restricted to overlap */ for (i = 0; i < len_a; ++i) { - float jf = i + delay; + double jf = (i + delay) * b_rate; int j; - float bv, x; + double x; + float bv; - j = (int)floorf(jf); + j = (int)floor(jf); if (j < 0 || j + 1 >= (int)len_b) continue; x = jf - j; - bv = (1 - x) * b[j] + x * b[j + 1]; + bv = (float)((1 - x) * b[j] + x * b[j + 1]); c += (a[i] - bv) * (a[i] - bv); wa += a[i]*a[i]; @@ -56,13 +67,14 @@ struct find_delay_data { const float *b; size_t len_a; size_t len_b; + double b_rate; }; static double find_delay_func(double x, void *user_data) { const struct find_delay_data *data = user_data; - return difference((float)x, data->a, data->len_a, data->b, data->len_b); + return difference((float)x, data->a, data->len_a, data->b, data->len_b, data->b_rate); } static double minimum(double x1, double x4, double (*func)(double, void *), void *user_data, double tol) @@ -99,9 +111,9 @@ static double minimum(double x1, double x4, double (*func)(double, void *), void return (f2 < f3) ? x2 : x3; } -static double find_delay(const float *a, size_t len_a, const float *b, size_t len_b, int maxdelay, double tol) +static double find_delay(const float *a, size_t len_a, const float *b, size_t len_b, double b_rate, int maxdelay, double tol) { - struct find_delay_data data = { .a = a, .len_a = len_a, .b = b, .len_b = len_b }; + struct find_delay_data data = { .a = a, .len_a = len_a, .b = b, .len_b = len_b, .b_rate = b_rate }; double best_x, best_f; int i; @@ -135,13 +147,23 @@ static void test_find_delay(void) v2[i] = sinf(0.1f * (i - 3.1234f)); } - delay = find_delay(v1, SPA_N_ELEMENTS(v1), v2, SPA_N_ELEMENTS(v2), 50, tol); + delay = find_delay(v1, SPA_N_ELEMENTS(v1), v2, SPA_N_ELEMENTS(v2), 1, 50, tol); expect = 3.1234f; fprintf(stderr, "find_delay = %g (exact %g)\n", delay, expect); - spa_assert_se(expect - 2*tol < delay && delay < expect + 2*tol); + assert_test(expect - 2*tol < delay && delay < expect + 2*tol); + + for (i = 0; i < 1024; ++i) { + v1[i] = sinf(0.1f * i); + v2[i] = sinf(0.1f * (i*3.0f/4 - 3.1234f)); + } + + delay = find_delay(v1, SPA_N_ELEMENTS(v1), v2, SPA_N_ELEMENTS(v2), 4.0/3.0, 50, tol); + expect = 3.1234f; + fprintf(stderr, "find_delay = %g (exact %g)\n", delay, expect); + assert_test(expect - 2*tol < delay && delay < expect + 2*tol); } -static uint32_t feed_sine(struct resample *r, uint32_t in, uint32_t *inp, uint32_t *phase) +static uint32_t feed_sine(struct resample *r, uint32_t in, uint32_t *inp, uint32_t *phase, bool print) { uint32_t i, out; const void *src[1]; @@ -158,22 +180,27 @@ static uint32_t feed_sine(struct resample *r, uint32_t in, uint32_t *inp, uint32 resample_process(r, src, inp, dst, &out); spa_assert_se(*inp == in); - fprintf(stderr, "inp(%u) = ", in); - for (uint32_t i = 0; i < in; ++i) - fprintf(stderr, "%g, ", samp_in[i]); - fprintf(stderr, "\n\n"); + if (print || force_print) { + fprintf(stderr, "inp(%u) = ", in); + for (uint32_t i = 0; i < in; ++i) + fprintf(stderr, "%g, ", samp_in[i]); + fprintf(stderr, "\n\n"); - fprintf(stderr, "out(%u) = ", out); - for (uint32_t i = 0; i < out; ++i) - fprintf(stderr, "%g, ", samp_out[i]); - fprintf(stderr, "\n\n"); + fprintf(stderr, "out(%u) = ", out); + for (uint32_t i = 0; i < out; ++i) + fprintf(stderr, "%g, ", samp_out[i]); + fprintf(stderr, "\n\n"); + } else { + fprintf(stderr, "inp(%u) = ...\n", in); + fprintf(stderr, "out(%u) = ...\n", out); + } *phase += in; *inp = in; return out; } -static void check_delay(double rate) +static void check_delay(double rate, uint32_t out_rate, uint32_t options) { const double tol = 0.001; struct resample r; @@ -185,38 +212,58 @@ static void check_delay(double rate) r.log = &logger.log; r.channels = 1; r.i_rate = 48000; - r.o_rate = 48000; + r.o_rate = out_rate; r.quality = RESAMPLE_DEFAULT_QUALITY; + r.options = options; resample_native_init(&r); resample_update_rate(&r, rate); - feed_sine(&r, 64, &in, &in_phase); + feed_sine(&r, 512, &in, &in_phase, false); /* Test delay */ - expect = resample_delay(&r); - out = feed_sine(&r, 64, &in, &in_phase); - got = find_delay(samp_in, in, samp_out, out, 40, tol); - fprintf(stderr, "delay: expect = %g, got = %g\n", expect, got); + expect = resample_delay(&r) + (double)resample_phase_ns(&r) * 48000 / SPA_NSEC_PER_SEC; + out = feed_sine(&r, 256, &in, &in_phase, true); + got = find_delay(samp_in, in, samp_out, out, out_rate / 48000.0, 100, tol); - spa_assert_se(expect - 2*tol < got && got < expect + 2*tol); + fprintf(stderr, "delay: expect = %g, got = %g\n", expect, got); + assert_test(expect - 4*tol < got && got < expect + 4*tol); resample_free(&r); } static void test_delay_copy(void) { - fprintf(stderr, "\n\n-- test_delay_copy\n\n"); - check_delay(1); + fprintf(stderr, "\n\n-- test_delay_copy (no prefill)\n\n"); + check_delay(1, 48000, 0); + + fprintf(stderr, "\n\n-- test_delay_copy (prefill)\n\n"); + check_delay(1, 48000, RESAMPLE_OPTION_PREFILL); +} + +static void test_delay_full(void) +{ + const uint32_t rates[] = { 16000, 32000, 44100, 48000, 88200, 96000, 144000, 192000 }; + unsigned int i; + + for (i = 0; i < SPA_N_ELEMENTS(rates); ++i) { + fprintf(stderr, "\n\n-- test_delay_full(%u, no prefill)\n\n", rates[i]); + check_delay(1, rates[i], 0); + fprintf(stderr, "\n\n-- test_delay_full(%u, prefill)\n\n", rates[i]); + check_delay(1, rates[i], RESAMPLE_OPTION_PREFILL); + } } static void test_delay_interp(void) { - fprintf(stderr, "\n\n-- test_delay_interp\n\n"); - check_delay(1 + 1e-12); + fprintf(stderr, "\n\n-- test_delay_interp(no prefill)\n\n"); + check_delay(1 + 1e-12, 48000, 0); + + fprintf(stderr, "\n\n-- test_delay_interp(prefill)\n\n"); + check_delay(1 + 1e-12, 48000, RESAMPLE_OPTION_PREFILL); } -static void check_delay_interp_vary_rate(double rate) +static void check_delay_vary_rate(double rate, double end_rate, uint32_t out_rate, uint32_t options) { const double tol = 0.001; struct resample r; @@ -224,39 +271,36 @@ static void check_delay_interp_vary_rate(double rate) uint32_t in, out; double expect, got; - fprintf(stderr, "\n\n-- check_delay_vary_rate(%g)\n\n", rate); + fprintf(stderr, "\n\n-- check_delay_vary_rate(%g, %.14g, %u, %s)\n\n", rate, end_rate, out_rate, + (options & RESAMPLE_OPTION_PREFILL) ? "prefill" : "no prefill"); spa_zero(r); r.log = &logger.log; r.channels = 1; r.i_rate = 48000; - r.o_rate = 48000; + r.o_rate = out_rate; r.quality = RESAMPLE_DEFAULT_QUALITY; + r.options = options; resample_native_init(&r); /* Cause nonzero resampler phase */ resample_update_rate(&r, rate); - feed_sine(&r, 128, &in, &in_phase); + feed_sine(&r, 128, &in, &in_phase, false); resample_update_rate(&r, 1.7); - feed_sine(&r, 128, &in, &in_phase); + feed_sine(&r, 128, &in, &in_phase, false); - resample_update_rate(&r, 1 + 1e-12); - feed_sine(&r, 256, &in, &in_phase); + resample_update_rate(&r, end_rate); + feed_sine(&r, 128, &in, &in_phase, false); + feed_sine(&r, 255, &in, &in_phase, false); /* Test delay */ - expect = (double)resample_delay(&r); - out = feed_sine(&r, 64, &in, &in_phase); - got = find_delay(samp_in, in, samp_out, out, 40, tol); - fprintf(stderr, "delay: expect = %g, got = %g\n", expect, got); + expect = (double)resample_delay(&r) + (double)resample_phase_ns(&r) * 48000 / SPA_NSEC_PER_SEC; + out = feed_sine(&r, 256, &in, &in_phase, true); + got = find_delay(samp_in, in, samp_out, out, out_rate/48000.0, 100, tol); -#if 0 - /* XXX: this fails */ - spa_assert_se(expect - 2*tol < got && got < expect + 2*tol); -#else - if (!(expect - 2*tol < got && got < expect + 2*tol)) - fprintf(stderr, "KNOWNFAIL\n"); -#endif + fprintf(stderr, "delay: expect = %g, got = %g\n", expect, got); + assert_test(expect - 4*tol < got && got < expect + 4*tol); resample_free(&r); } @@ -264,19 +308,149 @@ static void check_delay_interp_vary_rate(double rate) static void test_delay_interp_vary_rate(void) { - check_delay_interp_vary_rate(1.0123456789); - check_delay_interp_vary_rate(1.123456789); - check_delay_interp_vary_rate(1.23456789); + const uint32_t rates[] = { 32000, 44100, 48000, 88200, 96000 }; + const double factors[] = { 1.0123456789, 1.123456789, 1.203883, 1.23456789, 1.3456789 }; + unsigned int i, j; + + for (i = 0; i < SPA_N_ELEMENTS(rates); ++i) { + for (j = 0; j < SPA_N_ELEMENTS(factors); ++j) { + /* Interp at end */ + check_delay_vary_rate(factors[j], 1 + 1e-12, rates[i], 0); + + /* Copy/full at end */ + check_delay_vary_rate(factors[j], 1, rates[i], 0); + + /* Interp at end */ + check_delay_vary_rate(factors[j], 1 + 1e-12, rates[i], RESAMPLE_OPTION_PREFILL); + + /* Copy/full at end */ + check_delay_vary_rate(factors[j], 1, rates[i], RESAMPLE_OPTION_PREFILL); + } + } +} + +static void run(uint32_t in_rate, uint32_t out_rate, double end_rate, double mid_rate, uint32_t options) +{ + const double tol = 0.001; + struct resample r; + uint32_t in_phase = 0; + uint32_t in, out; + double expect, got; + + spa_zero(r); + r.log = &logger.log; + r.channels = 1; + r.i_rate = in_rate; + r.o_rate = out_rate; + r.quality = RESAMPLE_DEFAULT_QUALITY; + r.options = options; + resample_native_init(&r); + + /* Cause nonzero resampler phase */ + if (mid_rate != 0.0) { + resample_update_rate(&r, mid_rate); + feed_sine(&r, 128, &in, &in_phase, true); + + resample_update_rate(&r, 1.7); + feed_sine(&r, 128, &in, &in_phase, true); + } + + resample_update_rate(&r, end_rate); + feed_sine(&r, 128, &in, &in_phase, true); + feed_sine(&r, 255, &in, &in_phase, true); + + /* Test delay */ + expect = (double)resample_delay(&r) + (double)resample_phase_ns(&r) * (double)in_rate / SPA_NSEC_PER_SEC; + out = feed_sine(&r, 256, &in, &in_phase, true); + got = find_delay(samp_in, in, samp_out, out, ((double)out_rate)/in_rate, 100, tol); + + fprintf(stderr, "delay: expect = %g, got = %g\n", expect, got); + if (!(expect - 4*tol < got && got < expect + 4*tol)) + fprintf(stderr, "FAIL\n\n"); + + resample_free(&r); } int main(int argc, char *argv[]) { + static const struct option long_options[] = { + { "in-rate", required_argument, NULL, 'i' }, + { "out-rate", required_argument, NULL, 'o' }, + { "end-full", no_argument, NULL, 'f' }, + { "end-interp", no_argument, NULL, 'p' }, + { "mid-rate", required_argument, NULL, 'm' }, + { "prefill", no_argument, NULL, 'r' }, + { "print", no_argument, NULL, 'P' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0} + }; + const char *help = "%s [options]\n" + "\n" + "Check resampler delay. If no arguments, run tests.\n" + "\n" + "-i | --in-rate INRATE input rate\n" + "-o | --out-rate OUTRATE output rate\n" + "-f | --end-full force full (or copy) resampler\n" + "-p | --end-interp force interp resampler\n" + "-m | --mid-rate RELRATE force rate adjustment in the middle\n" + "-r | --prefill enable prefill\n" + "-P | --print force printing\n" + "\n"; + uint32_t in_rate = 0, out_rate = 0; + double end_rate = 1, mid_rate = 0; + uint32_t options = 0; + int c; + logger.log.level = SPA_LOG_LEVEL_TRACE; + while ((c = getopt_long(argc, argv, "i:o:fpm:rPh", long_options, NULL)) != -1) { + switch (c) { + case 'h': + fprintf(stderr, help, argv[0]); + return 0; + case 'i': + if (!spa_atou32(optarg, &in_rate, 0)) + goto error_arg; + break; + case 'o': + if (!spa_atou32(optarg, &out_rate, 0)) + goto error_arg; + break; + case 'f': + end_rate = 1; + break; + case 'p': + end_rate = 1 + 1e-12; + break; + case 'm': + if (!spa_atod(optarg, &mid_rate)) + goto error_arg; + break; + case 'r': + options = RESAMPLE_OPTION_PREFILL; + break; + case 'P': + force_print = true; + break; + default: + goto error_arg; + } + } + + if (in_rate && out_rate) { + run(in_rate, out_rate, end_rate, mid_rate, options); + return 0; + } + test_find_delay(); test_delay_copy(); + test_delay_full(); test_delay_interp(); test_delay_interp_vary_rate(); return 0; + +error_arg: + fprintf(stderr, "Invalid arguments\n"); + return 1; }