filter-graph: improve latency reporting of convolver

The latency of the convolver depends on the IR used. It's 0 for /dirac,
len/2 for /hilbert and let's assume it is 0 for file IRs.

Fixes #4980
This commit is contained in:
Wim Taymans 2025-11-24 13:56:18 +01:00
parent 33c7d9cba5
commit ed2889cecf
2 changed files with 33 additions and 20 deletions

View file

@ -718,7 +718,9 @@ struct finfo {
SNDFILE *fs; SNDFILE *fs;
#endif #endif
int channels; int channels;
int frames; int def_frames;
int max_frames;
float latency; /* latency relative to number of samples */
uint32_t rate; uint32_t rate;
const char *error; const char *error;
}; };
@ -729,14 +731,18 @@ static int finfo_open(const char *filename, struct finfo *info, int rate)
if (spa_strstartswith(filename, "/hilbert")) { if (spa_strstartswith(filename, "/hilbert")) {
info->channels = 1; info->channels = 1;
info->rate = rate; info->rate = rate;
info->frames = 64; info->def_frames = 64;
info->max_frames = INT_MAX;
info->type = TYPE_HILBERT; info->type = TYPE_HILBERT;
info->latency = 0.5f;
} }
else if (spa_strstartswith(filename, "/dirac")) { else if (spa_strstartswith(filename, "/dirac")) {
info->channels = 1; info->channels = 1;
info->frames = 1; info->def_frames = 1;
info->max_frames = 1;
info->rate = rate; info->rate = rate;
info->type = TYPE_DIRAC; info->type = TYPE_DIRAC;
info->latency = 0.0f;
} }
else if (spa_strstartswith(filename, "/ir:")) { else if (spa_strstartswith(filename, "/ir:")) {
struct spa_json it[1]; struct spa_json it[1];
@ -744,14 +750,16 @@ static int finfo_open(const char *filename, struct finfo *info, int rate)
int rate; int rate;
info->channels = 1; info->channels = 1;
info->type = TYPE_IR; info->type = TYPE_IR;
info->frames = 0; info->def_frames = 0;
if (spa_json_begin_array_relax(&it[0], filename+4, strlen(filename+4)) <= 0) if (spa_json_begin_array_relax(&it[0], filename+4, strlen(filename+4)) <= 0)
return -EINVAL; return -EINVAL;
if (spa_json_get_int(&it[0], &rate) <= 0) if (spa_json_get_int(&it[0], &rate) <= 0)
return -EINVAL; return -EINVAL;
info->rate = rate; info->rate = rate;
while (spa_json_get_float(&it[0], &v) > 0) while (spa_json_get_float(&it[0], &v) > 0)
info->frames++; info->def_frames++;
info->max_frames = info->def_frames;
info->latency = 0.0f;
} else { } else {
#ifdef HAVE_SNDFILE #ifdef HAVE_SNDFILE
info->fs = sf_open(filename, SFM_READ, &info->info); info->fs = sf_open(filename, SFM_READ, &info->info);
@ -760,9 +768,11 @@ static int finfo_open(const char *filename, struct finfo *info, int rate)
return -ENOENT; return -ENOENT;
} }
info->channels = info->info.channels; info->channels = info->info.channels;
info->frames = info->info.frames; info->def_frames = info->info.frames;
info->max_frames = info->def_frames;
info->rate = info->info.samplerate; info->rate = info->info.samplerate;
info->type = TYPE_SNDFILE; info->type = TYPE_SNDFILE;
info->latency = 0.0f;
#else #else
info->error = "compiled without sndfile support, can't load samples"; info->error = "compiled without sndfile support, can't load samples";
return -ENOTSUP; return -ENOTSUP;
@ -772,15 +782,15 @@ static int finfo_open(const char *filename, struct finfo *info, int rate)
} }
static float *finfo_read_samples(struct plugin *pl, struct finfo *info, float gain, int delay, static float *finfo_read_samples(struct plugin *pl, struct finfo *info, float gain, int delay,
int offset, int length, int channel, long unsigned *rate, int *n_samples) int offset, int length, int channel, long unsigned *rate, int *n_samples, int *latency)
{ {
float *samples, v; float *samples, v;
int i, n, h; int i, n, h;
if (length <= 0) if (length <= 0)
length = info->frames; length = info->def_frames;
else else
length = SPA_MIN(length, info->frames); length = SPA_MIN(length, info->max_frames);
length -= SPA_MIN(offset, length); length -= SPA_MIN(offset, length);
@ -837,6 +847,7 @@ static float *finfo_read_samples(struct plugin *pl, struct finfo *info, float ga
} }
*n_samples = n; *n_samples = n;
*rate = info->rate; *rate = info->rate;
*latency = (int) (n * info->latency);
return samples; return samples;
} }
@ -850,7 +861,7 @@ static void finfo_close(struct finfo *info)
} }
static float *read_closest(struct plugin *pl, char **filenames, float gain, float delay_sec, int offset, static float *read_closest(struct plugin *pl, char **filenames, float gain, float delay_sec, int offset,
int length, int channel, long unsigned *rate, int *n_samples) int length, int channel, long unsigned *rate, int *n_samples, int *latency)
{ {
struct finfo finfo[MAX_RATES]; struct finfo finfo[MAX_RATES];
int res, diff = INT_MAX; int res, diff = INT_MAX;
@ -874,7 +885,7 @@ static float *read_closest(struct plugin *pl, char **filenames, float gain, floa
spa_log_info(pl->log, "loading best rate:%u %s", finfo[best].rate, filenames[best]); spa_log_info(pl->log, "loading best rate:%u %s", finfo[best].rate, filenames[best]);
samples = finfo_read_samples(pl, &finfo[best], gain, samples = finfo_read_samples(pl, &finfo[best], gain,
(int) (delay_sec * finfo[best].rate), offset, length, (int) (delay_sec * finfo[best].rate), offset, length,
channel, rate, n_samples); channel, rate, n_samples, latency);
} else { } else {
char buf[PATH_MAX]; char buf[PATH_MAX];
spa_log_error(pl->log, "Can't open any sample file (CWD %s):", spa_log_error(pl->log, "Can't open any sample file (CWD %s):",
@ -984,7 +995,7 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
char key[256]; char key[256];
char *filenames[MAX_RATES] = { 0 }; char *filenames[MAX_RATES] = { 0 };
int blocksize = 0, tailsize = 0; int blocksize = 0, tailsize = 0;
int resample_quality = RESAMPLE_DEFAULT_QUALITY; int resample_quality = RESAMPLE_DEFAULT_QUALITY, def_latency;
float gain = 1.0f, delay = 0.0f, latency = -1.0f; float gain = 1.0f, delay = 0.0f, latency = -1.0f;
unsigned long rate; unsigned long rate;
@ -1092,7 +1103,7 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
rate = SampleRate; rate = SampleRate;
samples = read_closest(pl, filenames, gain, delay, offset, samples = read_closest(pl, filenames, gain, delay, offset,
length, channel, &rate, &n_samples); length, channel, &rate, &n_samples, &def_latency);
if (samples != NULL && rate != SampleRate) if (samples != NULL && rate != SampleRate)
samples = resample_buffer(pl, samples, &n_samples, samples = resample_buffer(pl, samples, &n_samples,
rate, SampleRate, resample_quality); rate, SampleRate, resample_quality);
@ -1111,8 +1122,8 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
if (tailsize <= 0) if (tailsize <= 0)
tailsize = SPA_CLAMP(4096, blocksize, 32768); tailsize = SPA_CLAMP(4096, blocksize, 32768);
spa_log_info(pl->log, "using n_samples:%u %d:%d blocksize delay:%f", n_samples, spa_log_info(pl->log, "using n_samples:%u %d:%d blocksize delay:%f def-latency:%d", n_samples,
blocksize, tailsize, delay); blocksize, tailsize, delay, def_latency);
impl = calloc(1, sizeof(*impl)); impl = calloc(1, sizeof(*impl));
if (impl == NULL) if (impl == NULL)
@ -1128,7 +1139,7 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
goto error; goto error;
if (latency < 0.0f) if (latency < 0.0f)
impl->latency = n_samples; impl->latency = def_latency;
else else
impl->latency = latency * impl->rate; impl->latency = latency * impl->rate;

View file

@ -401,11 +401,13 @@ extern struct spa_handle_factory spa_filter_graph_factory;
* - `filename` The IR to load or create. Possible values are: * - `filename` The IR to load or create. Possible values are:
* - `/hilbert` creates a [hilbert function](https://en.wikipedia.org/wiki/Hilbert_transform) * - `/hilbert` creates a [hilbert function](https://en.wikipedia.org/wiki/Hilbert_transform)
* that can be used to phase shift the signal by +/-90 degrees. The * that can be used to phase shift the signal by +/-90 degrees. The
* `length` will be used as the number of coefficients. * `length` will be used as the number of coefficients. The default latency
* if the length/2.
* - `/dirac` creates a [Dirac function](https://en.wikipedia.org/wiki/Dirac_delta_function) that * - `/dirac` creates a [Dirac function](https://en.wikipedia.org/wiki/Dirac_delta_function) that
* can be used as gain. * can be used as gain. The default latency is 0.
* - A filename to load as the IR. This needs to be a file format supported * - A filename to load as the IR. This needs to be a file format supported
* by sndfile or be an inline IR with "/ir:<rate>,<value1>,<value2>". * by sndfile or be an inline IR with "/ir:<rate>,<value1>,<value2>". The default
* latency of file IRs is 0.
* - [ filename, ... ] an array of filenames. The file with the closest samplerate match * - [ filename, ... ] an array of filenames. The file with the closest samplerate match
* with the graph samplerate will be used. * with the graph samplerate will be used.
* - `offset` The sample offset in the file as the start of the IR. * - `offset` The sample offset in the file as the start of the IR.
@ -414,7 +416,7 @@ extern struct spa_handle_factory spa_filter_graph_factory;
* - `resample_quality` The resample quality in case the IR does not match the graph * - `resample_quality` The resample quality in case the IR does not match the graph
* samplerate. * samplerate.
* - `latency` The extra latency in seconds to report. When left unspecified (or < 0.0) * - `latency` The extra latency in seconds to report. When left unspecified (or < 0.0)
* the convolver latency will be the length of the IR. * the default IR latency will be used, the the filename argument.
* *
* ### Delay * ### Delay
* *