mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-17 07:00:03 -05:00
filter-graph: support inline convolver IRs
Refactor the IR loading code, Make some generic open/load/close functions and handle the different filenames in the open call. This makes it possible to reuse some of the delay and alloc code. It also makes it easier to add new IRs loading code. Support /IR:<rate>,<value1>,<value2>,... as an inline IR definition.
This commit is contained in:
parent
f322a8b159
commit
fb20b96024
2 changed files with 147 additions and 111 deletions
|
|
@ -704,11 +704,78 @@ struct convolver_impl {
|
|||
struct convolver *conv;
|
||||
};
|
||||
|
||||
struct finfo {
|
||||
#define TYPE_INVALID 0
|
||||
#define TYPE_SNDFILE 1
|
||||
#define TYPE_HILBERT 2
|
||||
#define TYPE_DIRAC 3
|
||||
#define TYPE_IR 4
|
||||
uint32_t type;
|
||||
|
||||
const char *filename;
|
||||
#ifdef HAVE_SNDFILE
|
||||
static float *read_samples_from_sf(SNDFILE *f, const SF_INFO *info, float gain, int delay,
|
||||
int offset, int length, int channel, long unsigned *rate, int *n_samples) {
|
||||
float *samples;
|
||||
int i, n;
|
||||
SF_INFO info;
|
||||
SNDFILE *fs;
|
||||
#endif
|
||||
int channels;
|
||||
int frames;
|
||||
uint32_t rate;
|
||||
const char *error;
|
||||
};
|
||||
|
||||
static int finfo_open(const char *filename, struct finfo *info, int rate)
|
||||
{
|
||||
info->filename = filename;
|
||||
if (spa_strstartswith(filename, "/hilbert")) {
|
||||
info->channels = 1;
|
||||
info->rate = rate;
|
||||
info->frames = 64;
|
||||
info->type = TYPE_HILBERT;
|
||||
}
|
||||
else if (spa_strstartswith(filename, "/dirac")) {
|
||||
info->channels = 1;
|
||||
info->frames = 1;
|
||||
info->rate = rate;
|
||||
info->type = TYPE_DIRAC;
|
||||
}
|
||||
else if (spa_strstartswith(filename, "/ir:")) {
|
||||
struct spa_json it[1];
|
||||
float v;
|
||||
int rate;
|
||||
info->channels = 1;
|
||||
info->type = TYPE_IR;
|
||||
info->frames = 0;
|
||||
if (spa_json_begin_array_relax(&it[0], filename+4, strlen(filename+4)) <= 0)
|
||||
return -EINVAL;
|
||||
if (spa_json_get_int(&it[0], &rate) <= 0)
|
||||
return -EINVAL;
|
||||
info->rate = rate;
|
||||
while (spa_json_get_float(&it[0], &v) > 0)
|
||||
info->frames++;
|
||||
} else {
|
||||
#ifdef HAVE_SNDFILE
|
||||
info->fs = sf_open(filename, SFM_READ, &info->info);
|
||||
if (info->fs == NULL) {
|
||||
info->error = sf_strerror(NULL);
|
||||
return -ENOENT;
|
||||
}
|
||||
info->channels = info->info.channels;
|
||||
info->frames = info->info.frames;
|
||||
info->rate = info->info.samplerate;
|
||||
info->type = TYPE_SNDFILE;
|
||||
#else
|
||||
info->error = "compiled without sndfile support, can't load samples";
|
||||
return -ENOTSUP;
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
float *samples, v;
|
||||
int i, n, h;
|
||||
|
||||
if (length <= 0)
|
||||
length = info->frames;
|
||||
|
|
@ -725,128 +792,106 @@ static float *read_samples_from_sf(SNDFILE *f, const SF_INFO *info, float gain,
|
|||
if (samples == NULL)
|
||||
return NULL;
|
||||
|
||||
if (offset > 0)
|
||||
sf_seek(f, offset, SEEK_SET);
|
||||
sf_readf_float(f, samples + (delay * info->channels), length);
|
||||
|
||||
channel = channel % info->channels;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
samples[i] = samples[info->channels * i + channel] * gain;
|
||||
|
||||
switch (info->type) {
|
||||
case TYPE_SNDFILE:
|
||||
#ifdef HAVE_SNDFILE
|
||||
if (offset > 0)
|
||||
sf_seek(info->fs, offset, SEEK_SET);
|
||||
sf_readf_float(info->fs, samples + (delay * info->channels), length);
|
||||
for (i = 0; i < n; i++)
|
||||
samples[i] = samples[info->channels * i + channel] * gain;
|
||||
#endif
|
||||
break;
|
||||
case TYPE_HILBERT:
|
||||
gain *= 2 / (float)M_PI;
|
||||
h = length / 2;
|
||||
for (i = 1; i < h; i += 2) {
|
||||
v = (gain / i) * (0.43f + 0.57f * cosf(i * (float)M_PI / h));
|
||||
samples[delay + h + i] = -v;
|
||||
samples[delay + h - i] = v;
|
||||
}
|
||||
spa_log_info(pl->log, "created hilbert function length %d", length);
|
||||
break;
|
||||
case TYPE_DIRAC:
|
||||
samples[delay] = gain;
|
||||
spa_log_info(pl->log, "created dirac function");
|
||||
break;
|
||||
case TYPE_IR:
|
||||
{
|
||||
struct spa_json it[1];
|
||||
float v;
|
||||
if (spa_json_begin_array_relax(&it[0], info->filename+4, strlen(info->filename+4)) <= 0)
|
||||
return NULL;
|
||||
if (spa_json_get_int(&it[0], &h) <= 0)
|
||||
return NULL;
|
||||
info->rate = h;
|
||||
i = 0;
|
||||
while (spa_json_get_float(&it[0], &v) > 0) {
|
||||
samples[delay + i] = v * gain;
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
*n_samples = n;
|
||||
*rate = info->samplerate;
|
||||
*rate = info->rate;
|
||||
return samples;
|
||||
}
|
||||
|
||||
|
||||
static void finfo_close(struct finfo *info)
|
||||
{
|
||||
#ifdef HAVE_SNDFILE
|
||||
if (info->type == TYPE_SNDFILE && info->fs != NULL)
|
||||
sf_close(info->fs);
|
||||
#endif
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
#ifdef HAVE_SNDFILE
|
||||
SF_INFO infos[MAX_RATES];
|
||||
SNDFILE *fs[MAX_RATES];
|
||||
|
||||
spa_zero(infos);
|
||||
spa_zero(fs);
|
||||
|
||||
int diff = INT_MAX;
|
||||
uint32_t best = 0, i;
|
||||
struct finfo finfo[MAX_RATES];
|
||||
int res, diff = INT_MAX;
|
||||
uint32_t best = SPA_ID_INVALID, i;
|
||||
float *samples = NULL;
|
||||
|
||||
spa_zero(finfo);
|
||||
|
||||
for (i = 0; i < MAX_RATES && filenames[i] && filenames[i][0]; i++) {
|
||||
fs[i] = sf_open(filenames[i], SFM_READ, &infos[i]);
|
||||
if (fs[i] == NULL)
|
||||
res = finfo_open(filenames[i], &finfo[i], *rate);
|
||||
if (res < 0)
|
||||
continue;
|
||||
|
||||
if (labs((long)infos[i].samplerate - (long)*rate) < diff) {
|
||||
if (labs((long)finfo[i].rate - (long)*rate) < diff) {
|
||||
best = i;
|
||||
diff = labs((long)infos[i].samplerate - (long)*rate);
|
||||
spa_log_debug(pl->log, "new closest match: %d", infos[i].samplerate);
|
||||
diff = labs((long)finfo[i].rate - (long)*rate);
|
||||
spa_log_debug(pl->log, "new closest match: %d", finfo[i].rate);
|
||||
}
|
||||
}
|
||||
if (fs[best] != NULL) {
|
||||
spa_log_info(pl->log, "loading best rate:%u %s", infos[best].samplerate, filenames[best]);
|
||||
samples = read_samples_from_sf(fs[best], &infos[best], gain,
|
||||
(int) (delay_sec * infos[best].samplerate), offset, length,
|
||||
if (best != SPA_ID_INVALID) {
|
||||
spa_log_info(pl->log, "loading best rate:%u %s", finfo[best].rate, filenames[best]);
|
||||
samples = finfo_read_samples(pl, &finfo[best], gain,
|
||||
(int) (delay_sec * finfo[best].rate), offset, length,
|
||||
channel, rate, n_samples);
|
||||
} else {
|
||||
char buf[PATH_MAX];
|
||||
spa_log_error(pl->log, "Can't open any sample file (CWD %s):",
|
||||
getcwd(buf, sizeof(buf)));
|
||||
|
||||
for (i = 0; i < MAX_RATES && filenames[i] && filenames[i][0]; i++) {
|
||||
fs[i] = sf_open(filenames[i], SFM_READ, &infos[i]);
|
||||
if (fs[i] == NULL)
|
||||
spa_log_error(pl->log, " failed file %s: %s", filenames[i], sf_strerror(fs[i]));
|
||||
res = finfo_open(filenames[i], &finfo[i], *rate);
|
||||
if (res < 0)
|
||||
spa_log_error(pl->log, " failed file %s: %s", filenames[i], finfo[i].error);
|
||||
else
|
||||
spa_log_warn(pl->log, " unexpectedly opened file %s", filenames[i]);
|
||||
}
|
||||
}
|
||||
for (i = 0; i < MAX_RATES; i++)
|
||||
if (fs[i] != NULL)
|
||||
sf_close(fs[i]);
|
||||
finfo_close(&finfo[i]);
|
||||
|
||||
return samples;
|
||||
#else
|
||||
spa_log_error(pl->log, "compiled without sndfile support, can't load samples: "
|
||||
"using dirac impulse");
|
||||
float *samples = calloc(1, sizeof(float));
|
||||
samples[0] = gain;
|
||||
*n_samples = 1;
|
||||
return samples;
|
||||
#endif
|
||||
}
|
||||
|
||||
static float *create_hilbert(struct plugin *pl, const char *filename, float gain, int rate, float delay_sec, int offset,
|
||||
int length, int *n_samples)
|
||||
{
|
||||
float *samples, v;
|
||||
int i, n, h;
|
||||
int delay = (int) (delay_sec * rate);
|
||||
|
||||
if (length <= 0)
|
||||
length = 64;
|
||||
|
||||
length -= SPA_MIN(offset, length);
|
||||
|
||||
n = delay + length;
|
||||
if (n == 0)
|
||||
return NULL;
|
||||
|
||||
samples = calloc(n, sizeof(float));
|
||||
if (samples == NULL)
|
||||
return NULL;
|
||||
|
||||
gain *= 2 / (float)M_PI;
|
||||
h = length / 2;
|
||||
for (i = 1; i < h; i += 2) {
|
||||
v = (gain / i) * (0.43f + 0.57f * cosf(i * (float)M_PI / h));
|
||||
samples[delay + h + i] = -v;
|
||||
samples[delay + h - i] = v;
|
||||
}
|
||||
*n_samples = n;
|
||||
spa_log_info(pl->log, "created hilbert function");
|
||||
return samples;
|
||||
}
|
||||
|
||||
static float *create_dirac(struct plugin *pl, const char *filename, float gain, int rate, float delay_sec, int offset,
|
||||
int length, int *n_samples)
|
||||
{
|
||||
float *samples;
|
||||
int delay = (int) (delay_sec * rate);
|
||||
int n;
|
||||
|
||||
n = delay + 1;
|
||||
|
||||
samples = calloc(n, sizeof(float));
|
||||
if (samples == NULL)
|
||||
return NULL;
|
||||
|
||||
samples[delay] = gain;
|
||||
|
||||
spa_log_info(pl->log, "created dirac function");
|
||||
*n_samples = n;
|
||||
return samples;
|
||||
}
|
||||
|
||||
static float *resample_buffer(struct plugin *pl, float *samples, int *n_samples,
|
||||
|
|
@ -1045,21 +1090,12 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
|
|||
if (offset < 0)
|
||||
offset = 0;
|
||||
|
||||
if (spa_streq(filenames[0], "/hilbert")) {
|
||||
samples = create_hilbert(pl, filenames[0], gain, SampleRate, delay, offset,
|
||||
length, &n_samples);
|
||||
} else if (spa_streq(filenames[0], "/dirac")) {
|
||||
samples = create_dirac(pl, filenames[0], gain, SampleRate, delay, offset,
|
||||
length, &n_samples);
|
||||
} else {
|
||||
rate = SampleRate;
|
||||
samples = read_closest(pl, filenames, gain, delay, offset,
|
||||
length, channel, &rate, &n_samples);
|
||||
if (samples != NULL && rate != SampleRate) {
|
||||
samples = resample_buffer(pl, samples, &n_samples,
|
||||
rate, SampleRate, resample_quality);
|
||||
}
|
||||
}
|
||||
rate = SampleRate;
|
||||
samples = read_closest(pl, filenames, gain, delay, offset,
|
||||
length, channel, &rate, &n_samples);
|
||||
if (samples != NULL && rate != SampleRate)
|
||||
samples = resample_buffer(pl, samples, &n_samples,
|
||||
rate, SampleRate, resample_quality);
|
||||
|
||||
for (i = 0; i < MAX_RATES; i++)
|
||||
if (filenames[i])
|
||||
|
|
|
|||
|
|
@ -405,7 +405,7 @@ extern struct spa_handle_factory spa_filter_graph_factory;
|
|||
* - `/dirac` creates a [Dirac function](https://en.wikipedia.org/wiki/Dirac_delta_function) that
|
||||
* can be used as gain.
|
||||
* - A filename to load as the IR. This needs to be a file format supported
|
||||
* by sndfile.
|
||||
* by sndfile or be an inline IR with "/ir:<rate>,<value1>,<value2>".
|
||||
* - [ filename, ... ] an array of filenames. The file with the closest samplerate match
|
||||
* with the graph samplerate will be used.
|
||||
* - `offset` The sample offset in the file as the start of the IR.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue