avfilter: add acrossover filter

Signed-off-by: Paul B Mahol <onemda@gmail.com>
pull/296/head
Paul B Mahol 7 years ago
parent 63c69d51c7
commit 5109c38162
  1. 1
      Changelog
  2. 17
      doc/filters.texi
  3. 1
      libavfilter/Makefile
  4. 343
      libavfilter/af_acrossover.c
  5. 1
      libavfilter/allfilters.c
  6. 2
      libavfilter/version.h

@ -29,6 +29,7 @@ version <next>:
- AVS2 video encoder via libxavs2
- amultiply filter
- Block-Matching 3d (bm3d) denoising filter
- acrossover filter
version 4.0:

@ -493,6 +493,23 @@ ffmpeg -i first.flac -i second.flac -filter_complex acrossfade=d=10:o=0:c1=exp:c
@end example
@end itemize
@section acrossover
Split audio stream into several bands.
This filter splits audio stream into two or more frequency ranges.
Summing all streams back will give flat output.
The filter accepts the following options:
@table @option
@item split
Set split frequencies. Those must be positive and increasing.
@item order
Set filter order, can be @var{2nd}, @var{4th} or @var{8th}.
Default is @var{4th}.
@end table
@section acrusher
Reduce audio bit resolution.

@ -35,6 +35,7 @@ OBJS-$(CONFIG_ACOMPRESSOR_FILTER) += af_sidechaincompress.o
OBJS-$(CONFIG_ACONTRAST_FILTER) += af_acontrast.o
OBJS-$(CONFIG_ACOPY_FILTER) += af_acopy.o
OBJS-$(CONFIG_ACROSSFADE_FILTER) += af_afade.o
OBJS-$(CONFIG_ACROSSOVER_FILTER) += af_acrossover.o
OBJS-$(CONFIG_ACRUSHER_FILTER) += af_acrusher.o
OBJS-$(CONFIG_ACUE_FILTER) += f_cue.o
OBJS-$(CONFIG_ADECLICK_FILTER) += af_adeclick.o

@ -0,0 +1,343 @@
/*
* 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/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
typedef struct BiquadContext {
double a0, a1, a2;
double b1, b2;
double i1, i2;
double o1, o2;
} BiquadContext;
typedef struct CrossoverChannel {
BiquadContext lp[MAX_BANDS][4];
BiquadContext hp[MAX_BANDS][4];
} CrossoverChannel;
typedef struct AudioCrossoverContext {
const AVClass *class;
char *splits_str;
int order;
int filter_count;
int nb_splits;
float *splits;
CrossoverChannel *xover;
} 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 order", OFFSET(order), AV_OPT_TYPE_INT, {.i64=1}, 0, 2, AF, "m" },
{ "2nd", "2nd order", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, AF, "m" },
{ "4th", "4th order", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, AF, "m" },
{ "8th", "8th order", 0, AV_OPT_TYPE_CONST, {.i64=2}, 0, 0, AF, "m" },
{ NULL }
};
AVFILTER_DEFINE_CLASS(acrossover);
static av_cold int init(AVFilterContext *ctx)
{
AudioCrossoverContext *s = ctx->priv;
char *p, *arg, *saveptr = NULL;
int i, ret = 0;
s->splits = av_calloc(MAX_SPLITS, sizeof(*s->splits));
if (!s->splits)
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;
ret = sscanf(arg, "%f", &freq);
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;
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_insert_outpad(ctx, i, &pad)) < 0) {
av_freep(&pad.name);
return ret;
}
}
return ret;
}
static void set_lp(BiquadContext *b, float fc, float q, float sr)
{
double omega = (2.0 * M_PI * fc / sr);
double sn = sin(omega);
double cs = cos(omega);
double alpha = (sn / (2 * q));
double inv = (1.0 / (1.0 + alpha));
b->a2 = b->a0 = (inv * (1.0 - cs) * 0.5);
b->a1 = b->a0 + b->a0;
b->b1 = -2. * cs * inv;
b->b2 = (1. - alpha) * inv;
}
static void set_hp(BiquadContext *b, float fc, float q, float sr)
{
double omega = 2 * M_PI * fc / sr;
double sn = sin(omega);
double cs = cos(omega);
double alpha = sn / (2 * q);
double inv = 1.0 / (1.0 + alpha);
b->a0 = inv * (1. + cs) / 2.;
b->a1 = -2. * b->a0;
b->a2 = b->a0;
b->b1 = -2. * cs * inv;
b->b2 = (1. - alpha) * inv;
}
static int config_input(AVFilterLink *inlink)
{
AVFilterContext *ctx = inlink->dst;
AudioCrossoverContext *s = ctx->priv;
int ch, band, sample_rate = inlink->sample_rate;
double q;
s->xover = av_calloc(inlink->channels, sizeof(*s->xover));
if (!s->xover)
return AVERROR(ENOMEM);
switch (s->order) {
case 0:
q = 0.5;
s->filter_count = 1;
break;
case 1:
q = M_SQRT1_2;
s->filter_count = 2;
break;
case 2:
q = 0.54;
s->filter_count = 4;
break;
}
for (ch = 0; ch < inlink->channels; ch++) {
for (band = 0; band <= s->nb_splits; band++) {
set_lp(&s->xover[ch].lp[band][0], s->splits[band], q, sample_rate);
set_hp(&s->xover[ch].hp[band][0], s->splits[band], q, sample_rate);
if (s->order > 1) {
set_lp(&s->xover[ch].lp[band][1], s->splits[band], 1.34, sample_rate);
set_hp(&s->xover[ch].hp[band][1], s->splits[band], 1.34, sample_rate);
set_lp(&s->xover[ch].lp[band][2], s->splits[band], q, sample_rate);
set_hp(&s->xover[ch].hp[band][2], s->splits[band], q, sample_rate);
set_lp(&s->xover[ch].lp[band][3], s->splits[band], 1.34, sample_rate);
set_hp(&s->xover[ch].hp[band][3], s->splits[band], 1.34, sample_rate);
} else {
set_lp(&s->xover[ch].lp[band][1], s->splits[band], q, sample_rate);
set_hp(&s->xover[ch].hp[band][1], s->splits[band], q, sample_rate);
}
}
}
return 0;
}
static int query_formats(AVFilterContext *ctx)
{
AVFilterFormats *formats;
AVFilterChannelLayouts *layouts;
static const enum AVSampleFormat sample_fmts[] = {
AV_SAMPLE_FMT_DBLP,
AV_SAMPLE_FMT_NONE
};
int ret;
layouts = ff_all_channel_counts();
if (!layouts)
return AVERROR(ENOMEM);
ret = ff_set_common_channel_layouts(ctx, layouts);
if (ret < 0)
return ret;
formats = ff_make_format_list(sample_fmts);
if (!formats)
return AVERROR(ENOMEM);
ret = ff_set_common_formats(ctx, formats);
if (ret < 0)
return ret;
formats = ff_all_samplerates();
if (!formats)
return AVERROR(ENOMEM);
return ff_set_common_samplerates(ctx, formats);
}
static double biquad_process(BiquadContext *b, double in)
{
double out = in * b->a0 + b->i1 * b->a1 + b->i2 * b->a2 - b->o1 * b->b1 - b->o2 * b->b2;
b->i2 = b->i1;
b->o2 = b->o1;
b->i1 = in;
b->o1 = out;
return out;
}
static int filter_frame(AVFilterLink *inlink, AVFrame *in)
{
AVFilterContext *ctx = inlink->dst;
AudioCrossoverContext *s = ctx->priv;
AVFrame *frames[MAX_BANDS] = { NULL };
int i, f, ch, band, 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;
for (ch = 0; ch < inlink->channels; ch++) {
const double *src = (const double *)in->extended_data[ch];
CrossoverChannel *xover = &s->xover[ch];
for (band = 0; band < ctx->nb_outputs; band++) {
double *dst = (double *)frames[band]->extended_data[ch];
for (i = 0; i < in->nb_samples; i++) {
dst[i] = src[i];
for (f = 0; f < s->filter_count; f++) {
if (band + 1 < ctx->nb_outputs) {
BiquadContext *lp = &xover->lp[band][f];
dst[i] = biquad_process(lp, dst[i]);
}
if (band - 1 >= 0) {
BiquadContext *hp = &xover->hp[band - 1][f];
dst[i] = biquad_process(hp, dst[i]);
}
}
}
}
}
for (i = 0; i < ctx->nb_outputs; i++) {
ret = ff_filter_frame(ctx->outputs[i], frames[i]);
if (ret < 0)
break;
}
fail:
av_frame_free(&in);
return ret;
}
static av_cold void uninit(AVFilterContext *ctx)
{
AudioCrossoverContext *s = ctx->priv;
int i;
av_freep(&s->splits);
for (i = 0; i < ctx->nb_outputs; i++)
av_freep(&ctx->output_pads[i].name);
}
static const AVFilterPad inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_AUDIO,
.filter_frame = filter_frame,
.config_props = config_input,
},
{ NULL }
};
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,
.query_formats = query_formats,
.inputs = inputs,
.outputs = NULL,
.flags = AVFILTER_FLAG_DYNAMIC_OUTPUTS,
};

@ -29,6 +29,7 @@ extern AVFilter ff_af_acontrast;
extern AVFilter ff_af_acopy;
extern AVFilter ff_af_acue;
extern AVFilter ff_af_acrossfade;
extern AVFilter ff_af_acrossover;
extern AVFilter ff_af_acrusher;
extern AVFilter ff_af_adeclick;
extern AVFilter ff_af_adeclip;

@ -30,7 +30,7 @@
#include "libavutil/version.h"
#define LIBAVFILTER_VERSION_MAJOR 7
#define LIBAVFILTER_VERSION_MINOR 31
#define LIBAVFILTER_VERSION_MINOR 32
#define LIBAVFILTER_VERSION_MICRO 100
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \

Loading…
Cancel
Save