456 lines
11 KiB
C
456 lines
11 KiB
C
/* Spa */
|
|
/* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */
|
|
/* SPDX-License-Identifier: MIT */
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <getopt.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
#include <math.h>
|
|
|
|
#include <spa/support/log-impl.h>
|
|
#include <spa/debug/mem.h>
|
|
|
|
SPA_LOG_IMPL(logger);
|
|
|
|
#include "resample.h"
|
|
|
|
|
|
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(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) {
|
|
double jf = (i + delay) * b_rate;
|
|
int j;
|
|
double x;
|
|
float bv;
|
|
|
|
j = (int)floor(jf);
|
|
if (j < 0 || j + 1 >= (int)len_b)
|
|
continue;
|
|
|
|
x = jf - j;
|
|
bv = (float)((1 - x) * b[j] + x * b[j + 1]);
|
|
|
|
c += (a[i] - bv) * (a[i] - bv);
|
|
wa += a[i]*a[i];
|
|
wb = bv*bv;
|
|
}
|
|
|
|
if (wa == 0 || wb == 0)
|
|
return 1e30;
|
|
|
|
return c / sqrt(wa * wb);
|
|
}
|
|
|
|
struct find_delay_data {
|
|
const float *a;
|
|
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, data->b_rate);
|
|
}
|
|
|
|
static double minimum(double x1, double x4, double (*func)(double, void *), void *user_data, double tol)
|
|
{
|
|
/* Find minimum with golden section search */
|
|
const double phi = (1 + sqrt(5)) / 2;
|
|
double x2, x3;
|
|
double f2, f3;
|
|
|
|
spa_assert(x4 >= x1);
|
|
|
|
x2 = x4 - (x4 - x1) / phi;
|
|
x3 = x1 + (x4 - x1) / phi;
|
|
|
|
f2 = func(x2, user_data);
|
|
f3 = func(x3, user_data);
|
|
|
|
while (x4 - x1 > tol) {
|
|
if (f2 > f3) {
|
|
x1 = x2;
|
|
x2 = x3;
|
|
x3 = x1 + (x4 - x1) / phi;
|
|
f2 = f3;
|
|
f3 = func(x3, user_data);
|
|
} else {
|
|
x4 = x3;
|
|
x3 = x2;
|
|
x2 = x4 - (x4 - x1) / phi;
|
|
f3 = f2;
|
|
f2 = func(x2, user_data);
|
|
}
|
|
}
|
|
|
|
return (f2 < f3) ? x2 : x3;
|
|
}
|
|
|
|
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, .b_rate = b_rate };
|
|
double best_x, best_f;
|
|
int i;
|
|
|
|
best_x = 0;
|
|
best_f = find_delay_func(best_x, &data);
|
|
|
|
for (i = -maxdelay; i <= maxdelay; ++i) {
|
|
double f = find_delay_func(i, &data);
|
|
|
|
if (f < best_f) {
|
|
best_x = i;
|
|
best_f = f;
|
|
}
|
|
}
|
|
|
|
return minimum(best_x - 2, best_x + 2, find_delay_func, &data, tol);
|
|
}
|
|
|
|
static void test_find_delay(void)
|
|
{
|
|
float v1[1024];
|
|
float v2[1024];
|
|
const double tol = 0.001;
|
|
double delay, expect;
|
|
int i;
|
|
|
|
fprintf(stderr, "\n\n-- test_find_delay\n\n");
|
|
|
|
for (i = 0; i < 1024; ++i) {
|
|
v1[i] = sinf(0.1f * i);
|
|
v2[i] = sinf(0.1f * (i - 3.1234f));
|
|
}
|
|
|
|
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);
|
|
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, bool print)
|
|
{
|
|
uint32_t i, out;
|
|
const void *src[1];
|
|
void *dst[1];
|
|
|
|
for (i = 0; i < in; ++i)
|
|
samp_in[i] = sinf(0.01f * (*phase + i)) * expf(-0.001f * (*phase + i));
|
|
|
|
src[0] = samp_in;
|
|
dst[0] = samp_out;
|
|
out = SPA_N_ELEMENTS(samp_out);
|
|
|
|
*inp = in;
|
|
resample_process(r, src, inp, dst, &out);
|
|
spa_assert_se(*inp == in);
|
|
|
|
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");
|
|
} 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, uint32_t out_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 = 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, 512, &in, &in_phase, false);
|
|
|
|
/* Test delay */
|
|
expect = resample_delay(&r) + (double)resample_phase(&r);
|
|
out = feed_sine(&r, 256, &in, &in_phase, true);
|
|
got = find_delay(samp_in, in, samp_out, out, out_rate / 48000.0, 100, 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 (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(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_vary_rate(double rate, double end_rate, uint32_t out_rate, uint32_t options)
|
|
{
|
|
const double tol = 0.001;
|
|
struct resample r;
|
|
uint32_t in_phase = 0;
|
|
uint32_t in, out;
|
|
double expect, got;
|
|
|
|
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 = 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, false);
|
|
|
|
resample_update_rate(&r, 1.7);
|
|
feed_sine(&r, 128, &in, &in_phase, false);
|
|
|
|
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) + (double)resample_phase(&r);
|
|
out = feed_sine(&r, 256, &in, &in_phase, true);
|
|
got = find_delay(samp_in, in, samp_out, out, out_rate/48000.0, 100, 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_interp_vary_rate(void)
|
|
{
|
|
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(&r);
|
|
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;
|
|
}
|