mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-11-03 09:01:54 -05:00 
			
		
		
		
	Report fractional delay in input samples instead of nsec at the nominal input rate, as that is closer to what the value actually means.
		
			
				
	
	
		
			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;
 | 
						|
}
 |