diff mbox series

[FFmpeg-devel] avfilter: add asubcut filter

Message ID 20201130124906.31725-1-onemda@gmail.com
State Accepted
Headers show
Series [FFmpeg-devel] avfilter: add asubcut filter | expand

Checks

Context Check Description
andriy/x86_make success Make finished
andriy/x86_make_fate success Make fate finished
andriy/PPC64_make success Make finished
andriy/PPC64_make_fate success Make fate finished

Commit Message

Paul B Mahol Nov. 30, 2020, 12:49 p.m. UTC
Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 doc/filters.texi           | 25 +++++++++++
 libavfilter/Makefile       |  1 +
 libavfilter/af_asupercut.c | 89 ++++++++++++++++++++++++++++++--------
 libavfilter/allfilters.c   |  1 +
 4 files changed, 97 insertions(+), 19 deletions(-)

Comments

Marton Balint Nov. 30, 2020, 1:58 p.m. UTC | #1
On Mon, 30 Nov 2020, Paul B Mahol wrote:

> Signed-off-by: Paul B Mahol <onemda@gmail.com>
> ---
> doc/filters.texi           | 25 +++++++++++
> libavfilter/Makefile       |  1 +
> libavfilter/af_asupercut.c | 89 ++++++++++++++++++++++++++++++--------
> libavfilter/allfilters.c   |  1 +
> 4 files changed, 97 insertions(+), 19 deletions(-)
>
> diff --git a/doc/filters.texi b/doc/filters.texi
> index 06ed28f3f9..348cdae4e9 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -2636,6 +2636,28 @@ Default value is 20.
> 
> This filter supports the all above options as @ref{commands}.
> 
> +@section asubcut
> +Cut subwoofer frequencies.

Please explain in the documentation how is this different from a simple 
highpass filter, and when it is preferable to use this and when to use 
highpass.

Thanks,
Marton

> +
> +The filter accepts the following options:
> +
> +@table @option
> +@item cutoff
> +Set cutoff frequency in Hertz. Allowed range is 2 to 200.
> +Default value is 20.
> +
> +@item order
> +Set filter order. Available values are from 3 to 20.
> +Default value is 10.
> +
> +@item level
> +Set input gain level. Allowed range is from 0 to 1. Default value is 1.
> +@end table
> +
> +@subsection Commands
> +
> +This filter supports the all above options as @ref{commands}.
> +
> @section asupercut
> Cut super frequencies.
> 
> @@ -2649,6 +2671,9 @@ Default value is 20000.
> @item order
> Set filter order. Available values are from 3 to 20.
> Default value is 10.
> +
> +@item level
> +Set input gain level. Allowed range is from 0 to 1. Default value is 1.
> @end table
> 
> @subsection Commands
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index cff9402989..03998dc064 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -90,6 +90,7 @@ OBJS-$(CONFIG_ASR_FILTER)                    += af_asr.o
> OBJS-$(CONFIG_ASTATS_FILTER)                 += af_astats.o
> OBJS-$(CONFIG_ASTREAMSELECT_FILTER)          += f_streamselect.o framesync.o
> OBJS-$(CONFIG_ASUBBOOST_FILTER)              += af_asubboost.o
> +OBJS-$(CONFIG_ASUBCUT_FILTER)                += af_asupercut.o
> OBJS-$(CONFIG_ASUPERCUT_FILTER)              += af_asupercut.o
> OBJS-$(CONFIG_ATEMPO_FILTER)                 += af_atempo.o
> OBJS-$(CONFIG_ATRIM_FILTER)                  += trim.o
> diff --git a/libavfilter/af_asupercut.c b/libavfilter/af_asupercut.c
> index ef241405d8..6d0a2b79a9 100644
> --- a/libavfilter/af_asupercut.c
> +++ b/libavfilter/af_asupercut.c
> @@ -32,6 +32,7 @@ typedef struct ASuperCutContext {
>     const AVClass *class;
>
>     double cutoff;
> +    double level;
>     int order;
>
>     int filter_count;
> @@ -95,27 +96,52 @@ static int get_coeffs(AVFilterContext *ctx)
>     s->filter_count = s->order / 2 + (s->order & 1);
>     calc_q_factors(s->order, q);
> 
> -    if (s->order & 1) {
> -        BiquadCoeffs *coeffs = &s->coeffs[0];
> -        double omega = 2. * tan(M_PI * w0);
> +    if (!strcmp(ctx->filter->name, "asubcut")) {
> +        if (s->order & 1) {
> +            BiquadCoeffs *coeffs = &s->coeffs[0];
> +            double omega = 2. * tan(M_PI * w0);
> 
> -        coeffs->b0 = omega / (2. + omega);
> -        coeffs->b1 = coeffs->b0;
> -        coeffs->b2 = 0.;
> -        coeffs->a1 = -(omega - 2.) / (2. + omega);
> -        coeffs->a2 = 0.;
> -    }
> +            coeffs->b0 = 2. / (2. + omega);
> +            coeffs->b1 = -coeffs->b0;
> +            coeffs->b2 = 0.;
> +            coeffs->a1 = -(omega - 2.) / (2. + omega);
> +            coeffs->a2 = 0.;
> +        }
> +
> +        for (int b = (s->order & 1); b < s->filter_count; b++) {
> +            BiquadCoeffs *coeffs = &s->coeffs[b];
> +            const int idx = b - (s->order & 1);
> +            double norm = 1.0 / (1.0 + K / q[idx] + K * K);
> 
> -    for (int b = (s->order & 1); b < s->filter_count; b++) {
> -        BiquadCoeffs *coeffs = &s->coeffs[b];
> -        const int idx = b - (s->order & 1);
> -        double norm = 1.0 / (1.0 + K / q[idx] + K * K);
> +            coeffs->b0 = norm;
> +            coeffs->b1 = -2.0 * coeffs->b0;
> +            coeffs->b2 = coeffs->b0;
> +            coeffs->a1 = -2.0 * (K * K - 1.0) * norm;
> +            coeffs->a2 = -(1.0 - K / q[idx] + K * K) * norm;
> +        }
> +    } else {
> +        if (s->order & 1) {
> +            BiquadCoeffs *coeffs = &s->coeffs[0];
> +            double omega = 2. * tan(M_PI * w0);
> +
> +            coeffs->b0 = omega / (2. + omega);
> +            coeffs->b1 = coeffs->b0;
> +            coeffs->b2 = 0.;
> +            coeffs->a1 = -(omega - 2.) / (2. + omega);
> +            coeffs->a2 = 0.;
> +        }
> 
> -        coeffs->b0 = K * K * norm;
> -        coeffs->b1 = 2.0 * coeffs->b0;
> -        coeffs->b2 = coeffs->b0;
> -        coeffs->a1 = -2.0 * (K * K - 1.0) * norm;
> -        coeffs->a2 = -(1.0 - K / q[idx] + K * K) * norm;
> +        for (int b = (s->order & 1); b < s->filter_count; b++) {
> +            BiquadCoeffs *coeffs = &s->coeffs[b];
> +            const int idx = b - (s->order & 1);
> +            double norm = 1.0 / (1.0 + K / q[idx] + K * K);
> +
> +            coeffs->b0 = K * K * norm;
> +            coeffs->b1 = 2.0 * coeffs->b0;
> +            coeffs->b2 = coeffs->b0;
> +            coeffs->a1 = -2.0 * (K * K - 1.0) * norm;
> +            coeffs->a2 = -(1.0 - K / q[idx] + K * K) * norm;
> +        }
>     }
>
>     return 0;
> @@ -135,6 +161,7 @@ static int filter_channels_## name(AVFilterContext *ctx, void *arg, \
>     AVFrame *in = td->in;                                           \
>     const int start = (in->channels * jobnr) / nb_jobs;             \
>     const int end = (in->channels * (jobnr+1)) / nb_jobs;           \
> +    const double level = s->level;                                  \
>                                                                     \
>     for (int ch = start; ch < end; ch++) {                          \
>         const type *src = (const type *)in->extended_data[ch];      \
> @@ -150,7 +177,7 @@ static int filter_channels_## name(AVFilterContext *ctx, void *arg, \
>             type *w = ((type *)s->w->extended_data[ch]) + b * 2;    \
>                                                                     \
>             for (int n = 0; n < in->nb_samples; n++) {              \
> -                type sin = b ? dst[n] : src[n];                     \
> +                type sin = b ? dst[n] : src[n] * level;             \
>                 type sout = sin * b0 + w[0];                        \
>                                                                     \
>                 w[0] = b1 * sin + w[1] + a1 * sout;                 \
> @@ -240,6 +267,7 @@ static av_cold void uninit(AVFilterContext *ctx)
> static const AVOption asupercut_options[] = {
>     { "cutoff", "set cutoff frequency", OFFSET(cutoff), AV_OPT_TYPE_DOUBLE, {.dbl=20000}, 20000, 192000, FLAGS },
>     { "order",  "set filter order",     OFFSET(order),  AV_OPT_TYPE_INT,    {.i64=10},        3,     20, FLAGS },
> +    { "level",  "set input level",      OFFSET(level),  AV_OPT_TYPE_DOUBLE, {.dbl=1.},        0.,    1., FLAGS },
>     { NULL }
> };
> 
> @@ -276,3 +304,26 @@ AVFilter ff_af_asupercut = {
>     .flags           = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC |
>                        AVFILTER_FLAG_SLICE_THREADS,
> };
> +
> +static const AVOption asubcut_options[] = {
> +    { "cutoff", "set cutoff frequency", OFFSET(cutoff), AV_OPT_TYPE_DOUBLE, {.dbl=20},  2, 200, FLAGS },
> +    { "order",  "set filter order",     OFFSET(order),  AV_OPT_TYPE_INT,    {.i64=10},  3,  20, FLAGS },
> +    { "level",  "set input level",      OFFSET(level),  AV_OPT_TYPE_DOUBLE, {.dbl=1.}, 0.,  1., FLAGS },
> +    { NULL }
> +};
> +
> +AVFILTER_DEFINE_CLASS(asubcut);
> +
> +AVFilter ff_af_asubcut = {
> +    .name            = "asubcut",
> +    .description     = NULL_IF_CONFIG_SMALL("Cut subwoofer frequencies."),
> +    .query_formats   = query_formats,
> +    .priv_size       = sizeof(ASuperCutContext),
> +    .priv_class      = &asubcut_class,
> +    .uninit          = uninit,
> +    .inputs          = inputs,
> +    .outputs         = outputs,
> +    .process_command = process_command,
> +    .flags           = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC |
> +                       AVFILTER_FLAG_SLICE_THREADS,
> +};
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index 83f434bc27..d0fb7bfe9d 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -83,6 +83,7 @@ extern AVFilter ff_af_asr;
> extern AVFilter ff_af_astats;
> extern AVFilter ff_af_astreamselect;
> extern AVFilter ff_af_asubboost;
> +extern AVFilter ff_af_asubcut;
> extern AVFilter ff_af_asupercut;
> extern AVFilter ff_af_atempo;
> extern AVFilter ff_af_atrim;
> -- 
> 2.17.1
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Paul B Mahol Nov. 30, 2020, 2:25 p.m. UTC | #2
On Mon, Nov 30, 2020 at 2:58 PM Marton Balint <cus@passwd.hu> wrote:

>
>
> On Mon, 30 Nov 2020, Paul B Mahol wrote:
>
> > Signed-off-by: Paul B Mahol <onemda@gmail.com>
> > ---
> > doc/filters.texi           | 25 +++++++++++
> > libavfilter/Makefile       |  1 +
> > libavfilter/af_asupercut.c | 89 ++++++++++++++++++++++++++++++--------
> > libavfilter/allfilters.c   |  1 +
> > 4 files changed, 97 insertions(+), 19 deletions(-)
> >
> > diff --git a/doc/filters.texi b/doc/filters.texi
> > index 06ed28f3f9..348cdae4e9 100644
> > --- a/doc/filters.texi
> > +++ b/doc/filters.texi
> > @@ -2636,6 +2636,28 @@ Default value is 20.
> >
> > This filter supports the all above options as @ref{commands}.
> >
> > +@section asubcut
> > +Cut subwoofer frequencies.
>
> Please explain in the documentation how is this different from a simple
> highpass filter, and when it is preferable to use this and when to use
> highpass.
>

This runs internally several cascaded filters to give a butterworth response
of given order. Also odd orders are butterworth as they use single pole
highpass
which is not available at all in a high pass filter (its single pole has
different characteristics).
This makes sense to use to cut-off unwanted low frequencies more
efficiently than single high pass filter.
Thus it has an order option to control curve steepness.
Simple highpass is just meant for custom usage when one needs more fine
grained control at expense of a huge filter graph for doing anything not
trivial.
Note the extra level option which is needed if one wants to avoid clipping
and do not want to use other filters for that.
In a perfect world, clipping would never happen as phase response would
remain zero, but in practice that is impossible to do with IIR filters.
There is little hack for that scenario, but it is not always 100% effective
and also it is not realtime, as it needs two areverse filters to do the job
and full input signal.


>
> Thanks,
> Marton
>
> > +
> > +The filter accepts the following options:
> > +
> > +@table @option
> > +@item cutoff
> > +Set cutoff frequency in Hertz. Allowed range is 2 to 200.
> > +Default value is 20.
> > +
> > +@item order
> > +Set filter order. Available values are from 3 to 20.
> > +Default value is 10.
> > +
> > +@item level
> > +Set input gain level. Allowed range is from 0 to 1. Default value is 1.
> > +@end table
> > +
> > +@subsection Commands
> > +
> > +This filter supports the all above options as @ref{commands}.
> > +
> > @section asupercut
> > Cut super frequencies.
> >
> > @@ -2649,6 +2671,9 @@ Default value is 20000.
> > @item order
> > Set filter order. Available values are from 3 to 20.
> > Default value is 10.
> > +
> > +@item level
> > +Set input gain level. Allowed range is from 0 to 1. Default value is 1.
> > @end table
> >
> > @subsection Commands
> > diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> > index cff9402989..03998dc064 100644
> > --- a/libavfilter/Makefile
> > +++ b/libavfilter/Makefile
> > @@ -90,6 +90,7 @@ OBJS-$(CONFIG_ASR_FILTER)                    +=
> af_asr.o
> > OBJS-$(CONFIG_ASTATS_FILTER)                 += af_astats.o
> > OBJS-$(CONFIG_ASTREAMSELECT_FILTER)          += f_streamselect.o
> framesync.o
> > OBJS-$(CONFIG_ASUBBOOST_FILTER)              += af_asubboost.o
> > +OBJS-$(CONFIG_ASUBCUT_FILTER)                += af_asupercut.o
> > OBJS-$(CONFIG_ASUPERCUT_FILTER)              += af_asupercut.o
> > OBJS-$(CONFIG_ATEMPO_FILTER)                 += af_atempo.o
> > OBJS-$(CONFIG_ATRIM_FILTER)                  += trim.o
> > diff --git a/libavfilter/af_asupercut.c b/libavfilter/af_asupercut.c
> > index ef241405d8..6d0a2b79a9 100644
> > --- a/libavfilter/af_asupercut.c
> > +++ b/libavfilter/af_asupercut.c
> > @@ -32,6 +32,7 @@ typedef struct ASuperCutContext {
> >     const AVClass *class;
> >
> >     double cutoff;
> > +    double level;
> >     int order;
> >
> >     int filter_count;
> > @@ -95,27 +96,52 @@ static int get_coeffs(AVFilterContext *ctx)
> >     s->filter_count = s->order / 2 + (s->order & 1);
> >     calc_q_factors(s->order, q);
> >
> > -    if (s->order & 1) {
> > -        BiquadCoeffs *coeffs = &s->coeffs[0];
> > -        double omega = 2. * tan(M_PI * w0);
> > +    if (!strcmp(ctx->filter->name, "asubcut")) {
> > +        if (s->order & 1) {
> > +            BiquadCoeffs *coeffs = &s->coeffs[0];
> > +            double omega = 2. * tan(M_PI * w0);
> >
> > -        coeffs->b0 = omega / (2. + omega);
> > -        coeffs->b1 = coeffs->b0;
> > -        coeffs->b2 = 0.;
> > -        coeffs->a1 = -(omega - 2.) / (2. + omega);
> > -        coeffs->a2 = 0.;
> > -    }
> > +            coeffs->b0 = 2. / (2. + omega);
> > +            coeffs->b1 = -coeffs->b0;
> > +            coeffs->b2 = 0.;
> > +            coeffs->a1 = -(omega - 2.) / (2. + omega);
> > +            coeffs->a2 = 0.;
> > +        }
> > +
> > +        for (int b = (s->order & 1); b < s->filter_count; b++) {
> > +            BiquadCoeffs *coeffs = &s->coeffs[b];
> > +            const int idx = b - (s->order & 1);
> > +            double norm = 1.0 / (1.0 + K / q[idx] + K * K);
> >
> > -    for (int b = (s->order & 1); b < s->filter_count; b++) {
> > -        BiquadCoeffs *coeffs = &s->coeffs[b];
> > -        const int idx = b - (s->order & 1);
> > -        double norm = 1.0 / (1.0 + K / q[idx] + K * K);
> > +            coeffs->b0 = norm;
> > +            coeffs->b1 = -2.0 * coeffs->b0;
> > +            coeffs->b2 = coeffs->b0;
> > +            coeffs->a1 = -2.0 * (K * K - 1.0) * norm;
> > +            coeffs->a2 = -(1.0 - K / q[idx] + K * K) * norm;
> > +        }
> > +    } else {
> > +        if (s->order & 1) {
> > +            BiquadCoeffs *coeffs = &s->coeffs[0];
> > +            double omega = 2. * tan(M_PI * w0);
> > +
> > +            coeffs->b0 = omega / (2. + omega);
> > +            coeffs->b1 = coeffs->b0;
> > +            coeffs->b2 = 0.;
> > +            coeffs->a1 = -(omega - 2.) / (2. + omega);
> > +            coeffs->a2 = 0.;
> > +        }
> >
> > -        coeffs->b0 = K * K * norm;
> > -        coeffs->b1 = 2.0 * coeffs->b0;
> > -        coeffs->b2 = coeffs->b0;
> > -        coeffs->a1 = -2.0 * (K * K - 1.0) * norm;
> > -        coeffs->a2 = -(1.0 - K / q[idx] + K * K) * norm;
> > +        for (int b = (s->order & 1); b < s->filter_count; b++) {
> > +            BiquadCoeffs *coeffs = &s->coeffs[b];
> > +            const int idx = b - (s->order & 1);
> > +            double norm = 1.0 / (1.0 + K / q[idx] + K * K);
> > +
> > +            coeffs->b0 = K * K * norm;
> > +            coeffs->b1 = 2.0 * coeffs->b0;
> > +            coeffs->b2 = coeffs->b0;
> > +            coeffs->a1 = -2.0 * (K * K - 1.0) * norm;
> > +            coeffs->a2 = -(1.0 - K / q[idx] + K * K) * norm;
> > +        }
> >     }
> >
> >     return 0;
> > @@ -135,6 +161,7 @@ static int filter_channels_## name(AVFilterContext
> *ctx, void *arg, \
> >     AVFrame *in = td->in;                                           \
> >     const int start = (in->channels * jobnr) / nb_jobs;             \
> >     const int end = (in->channels * (jobnr+1)) / nb_jobs;           \
> > +    const double level = s->level;                                  \
> >                                                                     \
> >     for (int ch = start; ch < end; ch++) {                          \
> >         const type *src = (const type *)in->extended_data[ch];      \
> > @@ -150,7 +177,7 @@ static int filter_channels_## name(AVFilterContext
> *ctx, void *arg, \
> >             type *w = ((type *)s->w->extended_data[ch]) + b * 2;    \
> >                                                                     \
> >             for (int n = 0; n < in->nb_samples; n++) {              \
> > -                type sin = b ? dst[n] : src[n];                     \
> > +                type sin = b ? dst[n] : src[n] * level;             \
> >                 type sout = sin * b0 + w[0];                        \
> >                                                                     \
> >                 w[0] = b1 * sin + w[1] + a1 * sout;                 \
> > @@ -240,6 +267,7 @@ static av_cold void uninit(AVFilterContext *ctx)
> > static const AVOption asupercut_options[] = {
> >     { "cutoff", "set cutoff frequency", OFFSET(cutoff),
> AV_OPT_TYPE_DOUBLE, {.dbl=20000}, 20000, 192000, FLAGS },
> >     { "order",  "set filter order",     OFFSET(order),
> AV_OPT_TYPE_INT,    {.i64=10},        3,     20, FLAGS },
> > +    { "level",  "set input level",      OFFSET(level),
> AV_OPT_TYPE_DOUBLE, {.dbl=1.},        0.,    1., FLAGS },
> >     { NULL }
> > };
> >
> > @@ -276,3 +304,26 @@ AVFilter ff_af_asupercut = {
> >     .flags           = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC |
> >                        AVFILTER_FLAG_SLICE_THREADS,
> > };
> > +
> > +static const AVOption asubcut_options[] = {
> > +    { "cutoff", "set cutoff frequency", OFFSET(cutoff),
> AV_OPT_TYPE_DOUBLE, {.dbl=20},  2, 200, FLAGS },
> > +    { "order",  "set filter order",     OFFSET(order),
> AV_OPT_TYPE_INT,    {.i64=10},  3,  20, FLAGS },
> > +    { "level",  "set input level",      OFFSET(level),
> AV_OPT_TYPE_DOUBLE, {.dbl=1.}, 0.,  1., FLAGS },
> > +    { NULL }
> > +};
> > +
> > +AVFILTER_DEFINE_CLASS(asubcut);
> > +
> > +AVFilter ff_af_asubcut = {
> > +    .name            = "asubcut",
> > +    .description     = NULL_IF_CONFIG_SMALL("Cut subwoofer
> frequencies."),
> > +    .query_formats   = query_formats,
> > +    .priv_size       = sizeof(ASuperCutContext),
> > +    .priv_class      = &asubcut_class,
> > +    .uninit          = uninit,
> > +    .inputs          = inputs,
> > +    .outputs         = outputs,
> > +    .process_command = process_command,
> > +    .flags           = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC |
> > +                       AVFILTER_FLAG_SLICE_THREADS,
> > +};
> > diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> > index 83f434bc27..d0fb7bfe9d 100644
> > --- a/libavfilter/allfilters.c
> > +++ b/libavfilter/allfilters.c
> > @@ -83,6 +83,7 @@ extern AVFilter ff_af_asr;
> > extern AVFilter ff_af_astats;
> > extern AVFilter ff_af_astreamselect;
> > extern AVFilter ff_af_asubboost;
> > +extern AVFilter ff_af_asubcut;
> > extern AVFilter ff_af_asupercut;
> > extern AVFilter ff_af_atempo;
> > extern AVFilter ff_af_atrim;
> > --
> > 2.17.1
> >
> > _______________________________________________
> > ffmpeg-devel mailing list
> > ffmpeg-devel@ffmpeg.org
> > https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> >
> > To unsubscribe, visit link above, or email
> > ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Paul B Mahol Dec. 4, 2020, 8:52 p.m. UTC | #3
Will apply shortly.
diff mbox series

Patch

diff --git a/doc/filters.texi b/doc/filters.texi
index 06ed28f3f9..348cdae4e9 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -2636,6 +2636,28 @@  Default value is 20.
 
 This filter supports the all above options as @ref{commands}.
 
+@section asubcut
+Cut subwoofer frequencies.
+
+The filter accepts the following options:
+
+@table @option
+@item cutoff
+Set cutoff frequency in Hertz. Allowed range is 2 to 200.
+Default value is 20.
+
+@item order
+Set filter order. Available values are from 3 to 20.
+Default value is 10.
+
+@item level
+Set input gain level. Allowed range is from 0 to 1. Default value is 1.
+@end table
+
+@subsection Commands
+
+This filter supports the all above options as @ref{commands}.
+
 @section asupercut
 Cut super frequencies.
 
@@ -2649,6 +2671,9 @@  Default value is 20000.
 @item order
 Set filter order. Available values are from 3 to 20.
 Default value is 10.
+
+@item level
+Set input gain level. Allowed range is from 0 to 1. Default value is 1.
 @end table
 
 @subsection Commands
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index cff9402989..03998dc064 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -90,6 +90,7 @@  OBJS-$(CONFIG_ASR_FILTER)                    += af_asr.o
 OBJS-$(CONFIG_ASTATS_FILTER)                 += af_astats.o
 OBJS-$(CONFIG_ASTREAMSELECT_FILTER)          += f_streamselect.o framesync.o
 OBJS-$(CONFIG_ASUBBOOST_FILTER)              += af_asubboost.o
+OBJS-$(CONFIG_ASUBCUT_FILTER)                += af_asupercut.o
 OBJS-$(CONFIG_ASUPERCUT_FILTER)              += af_asupercut.o
 OBJS-$(CONFIG_ATEMPO_FILTER)                 += af_atempo.o
 OBJS-$(CONFIG_ATRIM_FILTER)                  += trim.o
diff --git a/libavfilter/af_asupercut.c b/libavfilter/af_asupercut.c
index ef241405d8..6d0a2b79a9 100644
--- a/libavfilter/af_asupercut.c
+++ b/libavfilter/af_asupercut.c
@@ -32,6 +32,7 @@  typedef struct ASuperCutContext {
     const AVClass *class;
 
     double cutoff;
+    double level;
     int order;
 
     int filter_count;
@@ -95,27 +96,52 @@  static int get_coeffs(AVFilterContext *ctx)
     s->filter_count = s->order / 2 + (s->order & 1);
     calc_q_factors(s->order, q);
 
-    if (s->order & 1) {
-        BiquadCoeffs *coeffs = &s->coeffs[0];
-        double omega = 2. * tan(M_PI * w0);
+    if (!strcmp(ctx->filter->name, "asubcut")) {
+        if (s->order & 1) {
+            BiquadCoeffs *coeffs = &s->coeffs[0];
+            double omega = 2. * tan(M_PI * w0);
 
-        coeffs->b0 = omega / (2. + omega);
-        coeffs->b1 = coeffs->b0;
-        coeffs->b2 = 0.;
-        coeffs->a1 = -(omega - 2.) / (2. + omega);
-        coeffs->a2 = 0.;
-    }
+            coeffs->b0 = 2. / (2. + omega);
+            coeffs->b1 = -coeffs->b0;
+            coeffs->b2 = 0.;
+            coeffs->a1 = -(omega - 2.) / (2. + omega);
+            coeffs->a2 = 0.;
+        }
+
+        for (int b = (s->order & 1); b < s->filter_count; b++) {
+            BiquadCoeffs *coeffs = &s->coeffs[b];
+            const int idx = b - (s->order & 1);
+            double norm = 1.0 / (1.0 + K / q[idx] + K * K);
 
-    for (int b = (s->order & 1); b < s->filter_count; b++) {
-        BiquadCoeffs *coeffs = &s->coeffs[b];
-        const int idx = b - (s->order & 1);
-        double norm = 1.0 / (1.0 + K / q[idx] + K * K);
+            coeffs->b0 = norm;
+            coeffs->b1 = -2.0 * coeffs->b0;
+            coeffs->b2 = coeffs->b0;
+            coeffs->a1 = -2.0 * (K * K - 1.0) * norm;
+            coeffs->a2 = -(1.0 - K / q[idx] + K * K) * norm;
+        }
+    } else {
+        if (s->order & 1) {
+            BiquadCoeffs *coeffs = &s->coeffs[0];
+            double omega = 2. * tan(M_PI * w0);
+
+            coeffs->b0 = omega / (2. + omega);
+            coeffs->b1 = coeffs->b0;
+            coeffs->b2 = 0.;
+            coeffs->a1 = -(omega - 2.) / (2. + omega);
+            coeffs->a2 = 0.;
+        }
 
-        coeffs->b0 = K * K * norm;
-        coeffs->b1 = 2.0 * coeffs->b0;
-        coeffs->b2 = coeffs->b0;
-        coeffs->a1 = -2.0 * (K * K - 1.0) * norm;
-        coeffs->a2 = -(1.0 - K / q[idx] + K * K) * norm;
+        for (int b = (s->order & 1); b < s->filter_count; b++) {
+            BiquadCoeffs *coeffs = &s->coeffs[b];
+            const int idx = b - (s->order & 1);
+            double norm = 1.0 / (1.0 + K / q[idx] + K * K);
+
+            coeffs->b0 = K * K * norm;
+            coeffs->b1 = 2.0 * coeffs->b0;
+            coeffs->b2 = coeffs->b0;
+            coeffs->a1 = -2.0 * (K * K - 1.0) * norm;
+            coeffs->a2 = -(1.0 - K / q[idx] + K * K) * norm;
+        }
     }
 
     return 0;
@@ -135,6 +161,7 @@  static int filter_channels_## name(AVFilterContext *ctx, void *arg, \
     AVFrame *in = td->in;                                           \
     const int start = (in->channels * jobnr) / nb_jobs;             \
     const int end = (in->channels * (jobnr+1)) / nb_jobs;           \
+    const double level = s->level;                                  \
                                                                     \
     for (int ch = start; ch < end; ch++) {                          \
         const type *src = (const type *)in->extended_data[ch];      \
@@ -150,7 +177,7 @@  static int filter_channels_## name(AVFilterContext *ctx, void *arg, \
             type *w = ((type *)s->w->extended_data[ch]) + b * 2;    \
                                                                     \
             for (int n = 0; n < in->nb_samples; n++) {              \
-                type sin = b ? dst[n] : src[n];                     \
+                type sin = b ? dst[n] : src[n] * level;             \
                 type sout = sin * b0 + w[0];                        \
                                                                     \
                 w[0] = b1 * sin + w[1] + a1 * sout;                 \
@@ -240,6 +267,7 @@  static av_cold void uninit(AVFilterContext *ctx)
 static const AVOption asupercut_options[] = {
     { "cutoff", "set cutoff frequency", OFFSET(cutoff), AV_OPT_TYPE_DOUBLE, {.dbl=20000}, 20000, 192000, FLAGS },
     { "order",  "set filter order",     OFFSET(order),  AV_OPT_TYPE_INT,    {.i64=10},        3,     20, FLAGS },
+    { "level",  "set input level",      OFFSET(level),  AV_OPT_TYPE_DOUBLE, {.dbl=1.},        0.,    1., FLAGS },
     { NULL }
 };
 
@@ -276,3 +304,26 @@  AVFilter ff_af_asupercut = {
     .flags           = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC |
                        AVFILTER_FLAG_SLICE_THREADS,
 };
+
+static const AVOption asubcut_options[] = {
+    { "cutoff", "set cutoff frequency", OFFSET(cutoff), AV_OPT_TYPE_DOUBLE, {.dbl=20},  2, 200, FLAGS },
+    { "order",  "set filter order",     OFFSET(order),  AV_OPT_TYPE_INT,    {.i64=10},  3,  20, FLAGS },
+    { "level",  "set input level",      OFFSET(level),  AV_OPT_TYPE_DOUBLE, {.dbl=1.}, 0.,  1., FLAGS },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(asubcut);
+
+AVFilter ff_af_asubcut = {
+    .name            = "asubcut",
+    .description     = NULL_IF_CONFIG_SMALL("Cut subwoofer frequencies."),
+    .query_formats   = query_formats,
+    .priv_size       = sizeof(ASuperCutContext),
+    .priv_class      = &asubcut_class,
+    .uninit          = uninit,
+    .inputs          = inputs,
+    .outputs         = outputs,
+    .process_command = process_command,
+    .flags           = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC |
+                       AVFILTER_FLAG_SLICE_THREADS,
+};
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 83f434bc27..d0fb7bfe9d 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -83,6 +83,7 @@  extern AVFilter ff_af_asr;
 extern AVFilter ff_af_astats;
 extern AVFilter ff_af_astreamselect;
 extern AVFilter ff_af_asubboost;
+extern AVFilter ff_af_asubcut;
 extern AVFilter ff_af_asupercut;
 extern AVFilter ff_af_atempo;
 extern AVFilter ff_af_atrim;