1
0
mirror of https://github.com/FFmpeg/FFmpeg.git synced 2024-12-12 19:18:44 +02:00
FFmpeg/libavfilter/af_acrossover.c
Andreas Rheinhardt b4f5201967 avfilter: Replace query_formats callback with union of list and callback
If one looks at the many query_formats callbacks in existence,
one will immediately recognize that there is one type of default
callback for video and a slightly different default callback for
audio: It is "return ff_set_common_formats_from_list(ctx, pix_fmts);"
for video with a filter-specific pix_fmts list. For audio, it is
the same with a filter-specific sample_fmts list together with
ff_set_common_all_samplerates() and ff_set_common_all_channel_counts().

This commit allows to remove the boilerplate query_formats callbacks
by replacing said callback with a union consisting the old callback
and pointers for pixel and sample format arrays. For the not uncommon
case in which these lists only contain a single entry (besides the
sentinel) enum AVPixelFormat and enum AVSampleFormat fields are also
added to the union to store them directly in the AVFilter,
thereby avoiding a relocation.

The state of said union will be contained in a new, dedicated AVFilter
field (the nb_inputs and nb_outputs fields have been shrunk to uint8_t
in order to create a hole for this new field; this is no problem, as
the maximum of all the nb_inputs is four; for nb_outputs it is only
two).

The state's default value coincides with the earlier default of
query_formats being unset, namely that the filter accepts all formats
(and also sample rates and channel counts/layouts for audio)
provided that these properties agree coincide for all inputs and
outputs.

By using different union members for audio and video filters
the type-unsafety of using the same functions for audio and video
lists will furthermore be more confined to formats.c than before.

When the new fields are used, they will also avoid allocations:
Currently something nearly equivalent to ff_default_query_formats()
is called after every successful call to a query_formats callback;
yet in the common case that the newly allocated AVFilterFormats
are not used at all (namely if there are no free links) these newly
allocated AVFilterFormats are freed again without ever being used.
Filters no longer using the callback will not exhibit this any more.

Reviewed-by: Paul B Mahol <onemda@gmail.com>
Reviewed-by: Nicolas George <george@nsup.org>
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
2021-10-05 17:48:25 +02:00

568 lines
22 KiB
C

/*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file
* Crossover filter
*
* Split an audio stream into several bands.
*/
#include "libavutil/attributes.h"
#include "libavutil/avstring.h"
#include "libavutil/channel_layout.h"
#include "libavutil/eval.h"
#include "libavutil/float_dsp.h"
#include "libavutil/internal.h"
#include "libavutil/opt.h"
#include "audio.h"
#include "avfilter.h"
#include "formats.h"
#include "internal.h"
#define MAX_SPLITS 16
#define MAX_BANDS MAX_SPLITS + 1
#define B0 0
#define B1 1
#define B2 2
#define A1 3
#define A2 4
typedef struct BiquadCoeffs {
double cd[5];
float cf[5];
} BiquadCoeffs;
typedef struct AudioCrossoverContext {
const AVClass *class;
char *splits_str;
char *gains_str;
int order_opt;
float level_in;
int order;
int filter_count;
int first_order;
int ap_filter_count;
int nb_splits;
float splits[MAX_SPLITS];
float gains[MAX_BANDS];
BiquadCoeffs lp[MAX_BANDS][20];
BiquadCoeffs hp[MAX_BANDS][20];
BiquadCoeffs ap[MAX_BANDS][20];
AVFrame *xover;
AVFrame *input_frame;
AVFrame *frames[MAX_BANDS];
int (*filter_channels)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs);
AVFloatDSPContext *fdsp;
} AudioCrossoverContext;
#define OFFSET(x) offsetof(AudioCrossoverContext, x)
#define AF AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
static const AVOption acrossover_options[] = {
{ "split", "set split frequencies", OFFSET(splits_str), AV_OPT_TYPE_STRING, {.str="500"}, 0, 0, AF },
{ "order", "set filter order", OFFSET(order_opt), AV_OPT_TYPE_INT, {.i64=1}, 0, 9, AF, "m" },
{ "2nd", "2nd order (12 dB/8ve)", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, AF, "m" },
{ "4th", "4th order (24 dB/8ve)", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, AF, "m" },
{ "6th", "6th order (36 dB/8ve)", 0, AV_OPT_TYPE_CONST, {.i64=2}, 0, 0, AF, "m" },
{ "8th", "8th order (48 dB/8ve)", 0, AV_OPT_TYPE_CONST, {.i64=3}, 0, 0, AF, "m" },
{ "10th", "10th order (60 dB/8ve)",0, AV_OPT_TYPE_CONST, {.i64=4}, 0, 0, AF, "m" },
{ "12th", "12th order (72 dB/8ve)",0, AV_OPT_TYPE_CONST, {.i64=5}, 0, 0, AF, "m" },
{ "14th", "14th order (84 dB/8ve)",0, AV_OPT_TYPE_CONST, {.i64=6}, 0, 0, AF, "m" },
{ "16th", "16th order (96 dB/8ve)",0, AV_OPT_TYPE_CONST, {.i64=7}, 0, 0, AF, "m" },
{ "18th", "18th order (108 dB/8ve)",0, AV_OPT_TYPE_CONST, {.i64=8}, 0, 0, AF, "m" },
{ "20th", "20th order (120 dB/8ve)",0, AV_OPT_TYPE_CONST, {.i64=9}, 0, 0, AF, "m" },
{ "level", "set input gain", OFFSET(level_in), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 1, AF },
{ "gain", "set output bands gain", OFFSET(gains_str), AV_OPT_TYPE_STRING, {.str="1.f"}, 0, 0, AF },
{ NULL }
};
AVFILTER_DEFINE_CLASS(acrossover);
static int parse_gains(AVFilterContext *ctx)
{
AudioCrossoverContext *s = ctx->priv;
char *p, *arg, *saveptr = NULL;
int i, ret = 0;
saveptr = NULL;
p = s->gains_str;
for (i = 0; i < MAX_BANDS; i++) {
float gain;
char c[3] = { 0 };
if (!(arg = av_strtok(p, " |", &saveptr)))
break;
p = NULL;
if (av_sscanf(arg, "%f%2s", &gain, c) < 1) {
av_log(ctx, AV_LOG_ERROR, "Invalid syntax for gain[%d].\n", i);
ret = AVERROR(EINVAL);
break;
}
if (c[0] == 'd' && c[1] == 'B')
s->gains[i] = expf(gain * M_LN10 / 20.f);
else
s->gains[i] = gain;
}
for (; i < MAX_BANDS; i++)
s->gains[i] = 1.f;
return ret;
}
static av_cold int init(AVFilterContext *ctx)
{
AudioCrossoverContext *s = ctx->priv;
char *p, *arg, *saveptr = NULL;
int i, ret = 0;
s->fdsp = avpriv_float_dsp_alloc(0);
if (!s->fdsp)
return AVERROR(ENOMEM);
p = s->splits_str;
for (i = 0; i < MAX_SPLITS; i++) {
float freq;
if (!(arg = av_strtok(p, " |", &saveptr)))
break;
p = NULL;
if (av_sscanf(arg, "%f", &freq) != 1) {
av_log(ctx, AV_LOG_ERROR, "Invalid syntax for frequency[%d].\n", i);
return AVERROR(EINVAL);
}
if (freq <= 0) {
av_log(ctx, AV_LOG_ERROR, "Frequency %f must be positive number.\n", freq);
return AVERROR(EINVAL);
}
if (i > 0 && freq <= s->splits[i-1]) {
av_log(ctx, AV_LOG_ERROR, "Frequency %f must be in increasing order.\n", freq);
return AVERROR(EINVAL);
}
s->splits[i] = freq;
}
s->nb_splits = i;
ret = parse_gains(ctx);
if (ret < 0)
return ret;
for (i = 0; i <= s->nb_splits; i++) {
AVFilterPad pad = { 0 };
char *name;
pad.type = AVMEDIA_TYPE_AUDIO;
name = av_asprintf("out%d", ctx->nb_outputs);
if (!name)
return AVERROR(ENOMEM);
pad.name = name;
if ((ret = ff_append_outpad_free_name(ctx, &pad)) < 0)
return ret;
}
return ret;
}
static void set_lp(BiquadCoeffs *b, double fc, double q, double sr)
{
double omega = 2. * M_PI * fc / sr;
double cosine = cos(omega);
double alpha = sin(omega) / (2. * q);
double b0 = (1. - cosine) / 2.;
double b1 = 1. - cosine;
double b2 = (1. - cosine) / 2.;
double a0 = 1. + alpha;
double a1 = -2. * cosine;
double a2 = 1. - alpha;
b->cd[B0] = b0 / a0;
b->cd[B1] = b1 / a0;
b->cd[B2] = b2 / a0;
b->cd[A1] = -a1 / a0;
b->cd[A2] = -a2 / a0;
b->cf[B0] = b->cd[B0];
b->cf[B1] = b->cd[B1];
b->cf[B2] = b->cd[B2];
b->cf[A1] = b->cd[A1];
b->cf[A2] = b->cd[A2];
}
static void set_hp(BiquadCoeffs *b, double fc, double q, double sr)
{
double omega = 2. * M_PI * fc / sr;
double cosine = cos(omega);
double alpha = sin(omega) / (2. * q);
double b0 = (1. + cosine) / 2.;
double b1 = -1. - cosine;
double b2 = (1. + cosine) / 2.;
double a0 = 1. + alpha;
double a1 = -2. * cosine;
double a2 = 1. - alpha;
b->cd[B0] = b0 / a0;
b->cd[B1] = b1 / a0;
b->cd[B2] = b2 / a0;
b->cd[A1] = -a1 / a0;
b->cd[A2] = -a2 / a0;
b->cf[B0] = b->cd[B0];
b->cf[B1] = b->cd[B1];
b->cf[B2] = b->cd[B2];
b->cf[A1] = b->cd[A1];
b->cf[A2] = b->cd[A2];
}
static void set_ap(BiquadCoeffs *b, double fc, double q, double sr)
{
double omega = 2. * M_PI * fc / sr;
double cosine = cos(omega);
double alpha = sin(omega) / (2. * q);
double a0 = 1. + alpha;
double a1 = -2. * cosine;
double a2 = 1. - alpha;
double b0 = a2;
double b1 = a1;
double b2 = a0;
b->cd[B0] = b0 / a0;
b->cd[B1] = b1 / a0;
b->cd[B2] = b2 / a0;
b->cd[A1] = -a1 / a0;
b->cd[A2] = -a2 / a0;
b->cf[B0] = b->cd[B0];
b->cf[B1] = b->cd[B1];
b->cf[B2] = b->cd[B2];
b->cf[A1] = b->cd[A1];
b->cf[A2] = b->cd[A2];
}
static void set_ap1(BiquadCoeffs *b, double fc, double sr)
{
double omega = 2. * M_PI * fc / sr;
b->cd[A1] = exp(-omega);
b->cd[A2] = 0.;
b->cd[B0] = -b->cd[A1];
b->cd[B1] = 1.;
b->cd[B2] = 0.;
b->cf[B0] = b->cd[B0];
b->cf[B1] = b->cd[B1];
b->cf[B2] = b->cd[B2];
b->cf[A1] = b->cd[A1];
b->cf[A2] = b->cd[A2];
}
static void calc_q_factors(int order, double *q)
{
double n = order / 2.;
for (int i = 0; i < n / 2; i++)
q[i] = 1. / (-2. * cos(M_PI * (2. * (i + 1) + n - 1.) / (2. * n)));
}
static int query_formats(AVFilterContext *ctx)
{
static const enum AVSampleFormat sample_fmts[] = {
AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_DBLP,
AV_SAMPLE_FMT_NONE
};
int ret;
ret = ff_set_common_all_channel_counts(ctx);
if (ret < 0)
return ret;
ret = ff_set_common_formats_from_list(ctx, sample_fmts);
if (ret < 0)
return ret;
return ff_set_common_all_samplerates(ctx);
}
#define BIQUAD_PROCESS(name, type) \
static void biquad_process_## name(const type *const c, \
type *b, \
type *dst, const type *src, \
int nb_samples) \
{ \
const type b0 = c[B0]; \
const type b1 = c[B1]; \
const type b2 = c[B2]; \
const type a1 = c[A1]; \
const type a2 = c[A2]; \
type z1 = b[0]; \
type z2 = b[1]; \
\
for (int n = 0; n + 1 < nb_samples; n++) { \
type in = src[n]; \
type out; \
\
out = in * b0 + z1; \
z1 = b1 * in + z2 + a1 * out; \
z2 = b2 * in + a2 * out; \
dst[n] = out; \
\
n++; \
in = src[n]; \
out = in * b0 + z1; \
z1 = b1 * in + z2 + a1 * out; \
z2 = b2 * in + a2 * out; \
dst[n] = out; \
} \
\
if (nb_samples & 1) { \
const int n = nb_samples - 1; \
const type in = src[n]; \
type out; \
\
out = in * b0 + z1; \
z1 = b1 * in + z2 + a1 * out; \
z2 = b2 * in + a2 * out; \
dst[n] = out; \
} \
\
b[0] = z1; \
b[1] = z2; \
}
BIQUAD_PROCESS(fltp, float)
BIQUAD_PROCESS(dblp, double)
#define XOVER_PROCESS(name, type, one, ff) \
static int filter_channels_## name(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) \
{ \
AudioCrossoverContext *s = ctx->priv; \
AVFrame *in = s->input_frame; \
AVFrame **frames = s->frames; \
const int start = (in->channels * jobnr) / nb_jobs; \
const int end = (in->channels * (jobnr+1)) / nb_jobs; \
const int nb_samples = in->nb_samples; \
const int nb_outs = ctx->nb_outputs; \
const int first_order = s->first_order; \
\
for (int ch = start; ch < end; ch++) { \
const type *src = (const type *)in->extended_data[ch]; \
type *xover = (type *)s->xover->extended_data[ch]; \
\
s->fdsp->vector_## ff ##mul_scalar((type *)frames[0]->extended_data[ch], src, \
s->level_in, FFALIGN(nb_samples, sizeof(type))); \
\
for (int band = 0; band < nb_outs; band++) { \
for (int f = 0; band + 1 < nb_outs && f < s->filter_count; f++) { \
const type *prv = (const type *)frames[band]->extended_data[ch]; \
type *dst = (type *)frames[band + 1]->extended_data[ch]; \
const type *hsrc = f == 0 ? prv : dst; \
type *hp = xover + nb_outs * 20 + band * 20 + f * 2; \
const type *const hpc = (type *)&s->hp[band][f].c ## ff; \
\
biquad_process_## name(hpc, hp, dst, hsrc, nb_samples); \
} \
\
for (int f = 0; band + 1 < nb_outs && f < s->filter_count; f++) { \
type *dst = (type *)frames[band]->extended_data[ch]; \
const type *lsrc = dst; \
type *lp = xover + band * 20 + f * 2; \
const type *const lpc = (type *)&s->lp[band][f].c ## ff; \
\
biquad_process_## name(lpc, lp, dst, lsrc, nb_samples); \
} \
\
for (int aband = band + 1; aband + 1 < nb_outs; aband++) { \
if (first_order) { \
const type *asrc = (const type *)frames[band]->extended_data[ch]; \
type *dst = (type *)frames[band]->extended_data[ch]; \
type *ap = xover + nb_outs * 40 + (aband * nb_outs + band) * 20; \
const type *const apc = (type *)&s->ap[aband][0].c ## ff; \
\
biquad_process_## name(apc, ap, dst, asrc, nb_samples); \
} \
\
for (int f = first_order; f < s->ap_filter_count; f++) { \
const type *asrc = (const type *)frames[band]->extended_data[ch]; \
type *dst = (type *)frames[band]->extended_data[ch]; \
type *ap = xover + nb_outs * 40 + (aband * nb_outs + band) * 20 + f * 2;\
const type *const apc = (type *)&s->ap[aband][f].c ## ff; \
\
biquad_process_## name(apc, ap, dst, asrc, nb_samples); \
} \
} \
} \
\
for (int band = 0; band < nb_outs; band++) { \
const type gain = s->gains[band] * ((band & 1 && first_order) ? -one : one); \
type *dst = (type *)frames[band]->extended_data[ch]; \
\
s->fdsp->vector_## ff ##mul_scalar(dst, dst, gain, \
FFALIGN(nb_samples, sizeof(type))); \
} \
} \
\
return 0; \
}
XOVER_PROCESS(fltp, float, 1.f, f)
XOVER_PROCESS(dblp, double, 1.0, d)
static int config_input(AVFilterLink *inlink)
{
AVFilterContext *ctx = inlink->dst;
AudioCrossoverContext *s = ctx->priv;
int sample_rate = inlink->sample_rate;
double q[16];
s->order = (s->order_opt + 1) * 2;
s->filter_count = s->order / 2;
s->first_order = s->filter_count & 1;
s->ap_filter_count = s->filter_count / 2 + s->first_order;
calc_q_factors(s->order, q);
for (int band = 0; band <= s->nb_splits; band++) {
if (s->first_order) {
set_lp(&s->lp[band][0], s->splits[band], 0.5, sample_rate);
set_hp(&s->hp[band][0], s->splits[band], 0.5, sample_rate);
}
for (int n = s->first_order; n < s->filter_count; n++) {
const int idx = s->filter_count / 2 - ((n + s->first_order) / 2 - s->first_order) - 1;
set_lp(&s->lp[band][n], s->splits[band], q[idx], sample_rate);
set_hp(&s->hp[band][n], s->splits[band], q[idx], sample_rate);
}
if (s->first_order)
set_ap1(&s->ap[band][0], s->splits[band], sample_rate);
for (int n = s->first_order; n < s->ap_filter_count; n++) {
const int idx = (s->filter_count / 2 - ((n * 2 + s->first_order) / 2 - s->first_order) - 1);
set_ap(&s->ap[band][n], s->splits[band], q[idx], sample_rate);
}
}
switch (inlink->format) {
case AV_SAMPLE_FMT_FLTP: s->filter_channels = filter_channels_fltp; break;
case AV_SAMPLE_FMT_DBLP: s->filter_channels = filter_channels_dblp; break;
default: return AVERROR_BUG;
}
s->xover = ff_get_audio_buffer(inlink, 2 * (ctx->nb_outputs * 10 + ctx->nb_outputs * 10 +
ctx->nb_outputs * ctx->nb_outputs * 10));
if (!s->xover)
return AVERROR(ENOMEM);
return 0;
}
static int filter_frame(AVFilterLink *inlink, AVFrame *in)
{
AVFilterContext *ctx = inlink->dst;
AudioCrossoverContext *s = ctx->priv;
AVFrame **frames = s->frames;
int i, ret = 0;
for (i = 0; i < ctx->nb_outputs; i++) {
frames[i] = ff_get_audio_buffer(ctx->outputs[i], in->nb_samples);
if (!frames[i]) {
ret = AVERROR(ENOMEM);
break;
}
frames[i]->pts = in->pts;
}
if (ret < 0)
goto fail;
s->input_frame = in;
ff_filter_execute(ctx, s->filter_channels, NULL, NULL,
FFMIN(inlink->channels, ff_filter_get_nb_threads(ctx)));
for (i = 0; i < ctx->nb_outputs; i++) {
ret = ff_filter_frame(ctx->outputs[i], frames[i]);
frames[i] = NULL;
if (ret < 0)
break;
}
fail:
for (i = 0; i < ctx->nb_outputs; i++)
av_frame_free(&frames[i]);
av_frame_free(&in);
s->input_frame = NULL;
return ret;
}
static av_cold void uninit(AVFilterContext *ctx)
{
AudioCrossoverContext *s = ctx->priv;
av_freep(&s->fdsp);
av_frame_free(&s->xover);
}
static const AVFilterPad inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_AUDIO,
.filter_frame = filter_frame,
.config_props = config_input,
},
};
const AVFilter ff_af_acrossover = {
.name = "acrossover",
.description = NULL_IF_CONFIG_SMALL("Split audio into per-bands streams."),
.priv_size = sizeof(AudioCrossoverContext),
.priv_class = &acrossover_class,
.init = init,
.uninit = uninit,
FILTER_QUERY_FUNC(query_formats),
FILTER_INPUTS(inputs),
.outputs = NULL,
.flags = AVFILTER_FLAG_DYNAMIC_OUTPUTS |
AVFILTER_FLAG_SLICE_THREADS,
};