diff mbox series

[FFmpeg-devel] avfilter: add afwtdn filter

Message ID 20200602133254.5732-1-onemda@gmail.com
State New
Headers show
Series [FFmpeg-devel] avfilter: add afwtdn filter | expand

Checks

Context Check Description
andriy/default pending
andriy/make success Make finished
andriy/make_fate success Make fate finished

Commit Message

Paul B Mahol June 2, 2020, 1:32 p.m. UTC
Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 doc/filters.texi         |  41 +++
 libavfilter/Makefile     |   1 +
 libavfilter/af_afwtdn.c  | 653 +++++++++++++++++++++++++++++++++++++++
 libavfilter/allfilters.c |   1 +
 4 files changed, 696 insertions(+)
 create mode 100644 libavfilter/af_afwtdn.c

Comments

Nicolas George June 2, 2020, 1:38 p.m. UTC | #1
Paul B Mahol (12020-06-02):
> Signed-off-by: Paul B Mahol <onemda@gmail.com>
> ---
>  doc/filters.texi         |  41 +++
>  libavfilter/Makefile     |   1 +

>  libavfilter/af_afwtdn.c  | 653 +++++++++++++++++++++++++++++++++++++++

I still oppose strongly to the name of the filter.

>  libavfilter/allfilters.c |   1 +
>  4 files changed, 696 insertions(+)
>  create mode 100644 libavfilter/af_afwtdn.c
> 
> diff --git a/doc/filters.texi b/doc/filters.texi
> index a0b4ab2228..15a8636398 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -1314,6 +1314,47 @@ Force the output to either unsigned 8-bit or signed 16-bit stereo
>  aformat=sample_fmts=u8|s16:channel_layouts=stereo
>  @end example
>  
> +@section afwtdn
> +Reduce broadband noise from input samples using Fast Wavelet Transform.
> +
> +A description of the accepted options follows.
> +
> +@table @option
> +@item sigma
> +Set the noise sigma, allowed range is from 0 to 100.
> +Default value is 0.
> +This option controls strength of denoising applied to input samples.
> +
> +@item levels
> +Set the number of wavelet levels of decomposition.
> +Allowed range is from 1 to 19.
> +Default value is 10.
> +Setting this too low make denoising performance very poor.
> +
> +@item wavet
> +Set wavelet type for decomposition of input frame.
> +Available wavelets are:
> +

> +@table @samp
> +@item deb4
> +@item deb10
> +@item coif5
> +@item rbior68
> +@end table

Insufficient: users have no idea what it means.

> +
> +@item percent
> +Set percent of full denoising. Allowed range is from 0 to 100 percent.
> +Default value is 85 percent or partial denoising.
> +
> +@item profile
> +If set enabled, first input frame will be used as noise profile.
> +If first frame samples contain non-noise performance will be very poor.
> +

> +@item frames
> +Set size of single frame in number of samples. Allowed range is from 1024 to
> +262144. Default frame size is 8192 samples.

If it is the size of a single frame, why is it in plural?

> +@end table
> +
>  @section agate
>  
>  A gate is mainly used to reduce lower parts of a signal. This kind of signal
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 5123540653..191826a622 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -50,6 +50,7 @@ OBJS-$(CONFIG_AFFTDN_FILTER)                 += af_afftdn.o
>  OBJS-$(CONFIG_AFFTFILT_FILTER)               += af_afftfilt.o
>  OBJS-$(CONFIG_AFIR_FILTER)                   += af_afir.o
>  OBJS-$(CONFIG_AFORMAT_FILTER)                += af_aformat.o
> +OBJS-$(CONFIG_AFWTDN_FILTER)                 += af_afwtdn.o
>  OBJS-$(CONFIG_AGATE_FILTER)                  += af_agate.o
>  OBJS-$(CONFIG_AIIR_FILTER)                   += af_aiir.o
>  OBJS-$(CONFIG_AINTEGRAL_FILTER)              += af_aderivative.o
> diff --git a/libavfilter/af_afwtdn.c b/libavfilter/af_afwtdn.c
> new file mode 100644
> index 0000000000..2d51c7bf2d
> --- /dev/null
> +++ b/libavfilter/af_afwtdn.c
> @@ -0,0 +1,653 @@
> +/*
> + * Copyright (c) 2020 Paul B Mahol
> + *
> + * 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
> + */
> +
> +#include <float.h>
> +
> +#include "libavutil/avassert.h"
> +#include "libavutil/audio_fifo.h"
> +#include "libavutil/avstring.h"
> +#include "libavutil/opt.h"
> +#include "avfilter.h"
> +#include "audio.h"
> +#include "filters.h"
> +#include "formats.h"
> +

> +static const double rbior68_lp[18] = {
> +    0.0, 0.0, 0.0, 0.0,
> +    0.014426282505624435, 0.014467504896790148,
> +    -0.07872200106262882, -0.04036797903033992,
> +    0.41784910915027457, 0.7589077294536541,
> +    0.41784910915027457, -0.04036797903033992,
> +    -0.07872200106262882, 0.014467504896790148,
> +    0.014426282505624435, 0.0, 0.0, 0.0,
> +};
> +
> +static const double rbior68_hp[18] = {
> +    -0.0019088317364812906, -0.0019142861290887667,
> +    0.016990639867602342, 0.01193456527972926,
> +    -0.04973290349094079, -0.07726317316720414,
> +    0.09405920349573646, 0.4207962846098268,
> +    -0.8259229974584023, 0.4207962846098268,
> +    0.09405920349573646, -0.07726317316720414,
> +    -0.04973290349094079, 0.01193456527972926,
> +    0.016990639867602342, -0.0019142861290887667,
> +    -0.0019088317364812906, 0.0,
> +};
> +
> +static const double rbior68_ilp[18] = {
> +    0.0019088317364812906, -0.0019142861290887667,
> +    -0.016990639867602342, 0.01193456527972926,
> +    0.04973290349094079, -0.07726317316720414,
> +    -0.09405920349573646, 0.4207962846098268,
> +    0.8259229974584023, 0.4207962846098268,
> +    -0.09405920349573646, -0.07726317316720414,
> +    0.04973290349094079, 0.01193456527972926,
> +    -0.016990639867602342, -0.0019142861290887667,
> +    0.0019088317364812906, 0.0,
> +};
> +
> +static const double rbior68_ihp[18] = {
> +    0.0, 0.0, 0.0, 0.0,
> +    0.014426282505624435, -0.014467504896790148,
> +    -0.07872200106262882, 0.04036797903033992,
> +    0.41784910915027457, -0.7589077294536541,
> +    0.41784910915027457, 0.04036797903033992,
> +    -0.07872200106262882, -0.014467504896790148,
> +    0.014426282505624435, 0.0, 0.0, 0.0,
> +};
> +
> +static const double coif5_lp[30] = {
> +    -9.517657273819165e-08, -1.6744288576823017e-07,
> +    2.0637618513646814e-06, 3.7346551751414047e-06,
> +    -2.1315026809955787e-05, -4.134043227251251e-05,
> +    0.00014054114970203437, 0.00030225958181306315,
> +    -0.0006381313430451114, -0.0016628637020130838,
> +    0.0024333732126576722, 0.006764185448053083,
> +    -0.009164231162481846, -0.01976177894257264,
> +    0.03268357426711183, 0.0412892087501817,
> +    -0.10557420870333893, -0.06203596396290357,
> +    0.4379916261718371, 0.7742896036529562,
> +    0.4215662066908515, -0.05204316317624377,
> +    -0.09192001055969624, 0.02816802897093635,
> +    0.023408156785839195, -0.010131117519849788,
> +    -0.004159358781386048, 0.0021782363581090178,
> +    0.00035858968789573785, -0.00021208083980379827,
> +};
> +
> +static const double coif5_hp[30] = {
> +    0.00021208083980379827, 0.00035858968789573785,
> +    -0.0021782363581090178, -0.004159358781386048,
> +    0.010131117519849788, 0.023408156785839195,
> +    -0.02816802897093635, -0.09192001055969624,
> +    0.05204316317624377, 0.4215662066908515,
> +    -0.7742896036529562, 0.4379916261718371,
> +    0.06203596396290357, -0.10557420870333893,
> +    -0.0412892087501817, 0.03268357426711183,
> +    0.01976177894257264, -0.009164231162481846,
> +    -0.006764185448053083, 0.0024333732126576722,
> +    0.0016628637020130838, -0.0006381313430451114,
> +    -0.00030225958181306315, 0.00014054114970203437,
> +    4.134043227251251e-05, -2.1315026809955787e-05,
> +    -3.7346551751414047e-06, 2.0637618513646814e-06,
> +    1.6744288576823017e-07, -9.517657273819165e-08,
> +};
> +
> +static const double coif5_ilp[30] = {
> +    -0.00021208083980379827, 0.00035858968789573785,
> +    0.0021782363581090178, -0.004159358781386048,
> +    -0.010131117519849788, 0.023408156785839195,
> +    0.02816802897093635, -0.09192001055969624,
> +    -0.05204316317624377, 0.4215662066908515,
> +    0.7742896036529562, 0.4379916261718371,
> +    -0.06203596396290357, -0.10557420870333893,
> +    0.0412892087501817, 0.03268357426711183,
> +    -0.01976177894257264, -0.009164231162481846,
> +    0.006764185448053083, 0.0024333732126576722,
> +    -0.0016628637020130838, -0.0006381313430451114,
> +    0.00030225958181306315, 0.00014054114970203437,
> +    -4.134043227251251e-05, -2.1315026809955787e-05,
> +    3.7346551751414047e-06, 2.0637618513646814e-06,
> +    -1.6744288576823017e-07, -9.517657273819165e-08,
> +};
> +
> +static const double coif5_ihp[30] = {
> +    -9.517657273819165e-08, 1.6744288576823017e-07,
> +    2.0637618513646814e-06, -3.7346551751414047e-06,
> +    -2.1315026809955787e-05, 4.134043227251251e-05,
> +    0.00014054114970203437, -0.00030225958181306315,
> +    -0.0006381313430451114, 0.0016628637020130838,
> +    0.0024333732126576722, -0.006764185448053083,
> +    -0.009164231162481846, 0.01976177894257264,
> +    0.03268357426711183, -0.0412892087501817,
> +    -0.10557420870333893, 0.06203596396290357,
> +    0.4379916261718371, -0.7742896036529562,
> +    0.4215662066908515, 0.05204316317624377,
> +    -0.09192001055969624, -0.02816802897093635,
> +    0.023408156785839195, 0.010131117519849788,
> +    -0.004159358781386048, -0.0021782363581090178,
> +    0.00035858968789573785, 0.00021208083980379827,
> +};
> +
> +static const double deb10_lp[20] = {
> +    -1.326420300235487e-05, 9.358867000108985e-05,
> +    -0.0001164668549943862, -0.0006858566950046825,
> +    0.00199240529499085, 0.0013953517469940798,
> +    -0.010733175482979604, 0.0036065535669883944,
> +    0.03321267405893324, -0.02945753682194567,
> +    -0.07139414716586077, 0.09305736460380659,
> +    0.12736934033574265, -0.19594627437659665,
> +    -0.24984642432648865, 0.2811723436604265,
> +    0.6884590394525921, 0.5272011889309198,
> +    0.18817680007762133, 0.026670057900950818,
> +};
> +
> +static const double deb10_hp[20] = {
> +    -0.026670057900950818, 0.18817680007762133,
> +    -0.5272011889309198, 0.6884590394525921,
> +    -0.2811723436604265, -0.24984642432648865,
> +    0.19594627437659665, 0.12736934033574265,
> +    -0.09305736460380659, -0.07139414716586077,
> +    0.02945753682194567, 0.03321267405893324,
> +    -0.0036065535669883944, -0.010733175482979604,
> +    -0.0013953517469940798, 0.00199240529499085,
> +    0.0006858566950046825, -0.0001164668549943862,
> +    -9.358867000108985e-05, -1.326420300235487e-05,
> +};
> +
> +static const double deb10_ilp[20] = {
> +    0.026670057900950818, 0.18817680007762133,
> +    0.5272011889309198, 0.6884590394525921,
> +    0.2811723436604265, -0.24984642432648865,
> +    -0.19594627437659665, 0.12736934033574265,
> +    0.09305736460380659, -0.07139414716586077,
> +    -0.02945753682194567, 0.03321267405893324,
> +    0.0036065535669883944, -0.010733175482979604,
> +    0.0013953517469940798, 0.00199240529499085,
> +    -0.0006858566950046825, -0.0001164668549943862,
> +    9.358867000108985e-05, -1.326420300235487e-05,
> +};
> +
> +static const double deb10_ihp[20] = {
> +    -1.326420300235487e-05, -9.358867000108985e-05,
> +    -0.0001164668549943862, 0.0006858566950046825,
> +    0.00199240529499085, -0.0013953517469940798,
> +    -0.010733175482979604, -0.0036065535669883944,
> +    0.03321267405893324, 0.02945753682194567,
> +    -0.07139414716586077, -0.09305736460380659,
> +    0.12736934033574265, 0.19594627437659665,
> +    -0.24984642432648865, -0.2811723436604265,
> +    0.6884590394525921, -0.5272011889309198,
> +    0.18817680007762133, -0.026670057900950818,
> +};
> +
> +static const double deb4_lp[8] = {
> +    -0.0105974018, 0.0328830117,
> +     0.0308413818, -0.1870348117,
> +    -0.0279837694, 0.6308807679,
> +     0.7148465706, 0.2303778133,
> +};
> +
> +static const double deb4_hp[8] = {
> +    -0.2303778133, 0.7148465706,
> +    -0.6308807679, -0.0279837694,
> +     0.1870348117, 0.0308413818,
> +    -0.0328830117, -0.0105974018,
> +};
> +
> +static const double deb4_ilp[8] = {
> +     0.23037781330885523, 0.7148465705525415,
> +     0.6308807679295904, -0.02798376941698385,
> +    -0.18703481171888114, 0.030841381835986965,
> +     0.032883011666982945, -0.010597401784997278,
> +};
> +
> +static const double deb4_ihp[8] = {
> +    -0.010597401784997278, -0.032883011666982945,
> +     0.030841381835986965, 0.18703481171888114,
> +    -0.02798376941698385, -0.6308807679295904,
> +     0.7148465705525415, -0.23037781330885523,
> +};

A bunch of magical numbers. We need to know where they come from.

> +
> +#define MAX_LEVELS 20
> +
> +typedef struct AudioFWTDNContext {
> +    const AVClass *class;
> +
> +    double sigma;
> +    double percent;
> +
> +    int wavelet_type;
> +    int thresholding_function;
> +    int nb_samples;
> +    int levels;
> +    int wavelet_length;
> +    int length[MAX_LEVELS];
> +    int out_length;
> +    int need_profile;
> +    int got_profile;
> +
> +    const double *lp, *hp;
> +    const double *ilp, *ihp;
> +
> +    AVFrame *temp0, *temp1;
> +    AVFrame *wtc, *power, *profile, *filter, *signal;
> +} AudioFWTDNContext;
> +
> +#define OFFSET(x) offsetof(AudioFWTDNContext, x)
> +#define AF AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
> +
> +static const AVOption afwtdn_options[] = {
> +    { "sigma", "set noise sigma", OFFSET(sigma), AV_OPT_TYPE_DOUBLE, {.dbl=0}, 0, 100, AF },
> +    { "levels", "set number of wavelet levels", OFFSET(levels), AV_OPT_TYPE_INT, {.i64=10}, 1, MAX_LEVELS-1, AF },
> +    { "wavet", "set wavelet type", OFFSET(wavelet_type), AV_OPT_TYPE_INT, {.i64=0}, 0, 3, AF, "wavet" },
> +    { "deb4",  "deb4", 0, AV_OPT_TYPE_CONST,  {.i64=0}, 0, 0, AF, "wavet" },
> +    { "deb10", "deb10", 0, AV_OPT_TYPE_CONST,  {.i64=1}, 0, 0, AF, "wavet" },
> +    { "coif5", "coif5", 0, AV_OPT_TYPE_CONST,  {.i64=2}, 0, 0, AF, "wavet" },
> +    { "rbior68", "rbior68", 0, AV_OPT_TYPE_CONST,  {.i64=3}, 0, 0, AF, "wavet" },
> +    { "percent", "set percent of full denoising", OFFSET(percent),AV_OPT_TYPE_DOUBLE, {.dbl=85}, 0, 100, AF },
> +    { "profile", "profile noise", OFFSET(need_profile), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, AF },
> +    { "frames", "set frame size", OFFSET(nb_samples), AV_OPT_TYPE_INT, {.i64=8192}, 1024, 262144, AF },
> +    { NULL }
> +};
> +
> +AVFILTER_DEFINE_CLASS(afwtdn);
> +
> +static int query_formats(AVFilterContext *ctx)
> +{
> +    AVFilterFormats *formats = NULL;
> +    AVFilterChannelLayouts *layouts = NULL;
> +    static const enum AVSampleFormat sample_fmts[] = {
> +        AV_SAMPLE_FMT_DBLP,
> +        AV_SAMPLE_FMT_NONE
> +    };
> +    int 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;
> +
> +    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_all_samplerates();
> +    return ff_set_common_samplerates(ctx, formats);
> +}
> +
> +static int config_output(AVFilterLink *outlink)
> +{
> +    AVFilterContext *ctx = outlink->src;
> +    AudioFWTDNContext *s = ctx->priv;
> +    int N, i;
> +
> +    switch (s->wavelet_type) {
> +    case 0:
> +        s->wavelet_length = 8;
> +        s->lp  = deb4_lp;
> +        s->hp  = deb4_hp;
> +        s->ilp = deb4_ilp;
> +        s->ihp = deb4_ihp;
> +        break;
> +    case 1:
> +        s->wavelet_length = 20;
> +        s->lp  = deb10_lp;
> +        s->hp  = deb10_hp;
> +        s->ilp = deb10_ilp;
> +        s->ihp = deb10_ihp;
> +        break;
> +    case 2:
> +        s->wavelet_length = 30;
> +        s->lp  = coif5_lp;
> +        s->hp  = coif5_hp;
> +        s->ilp = coif5_ilp;
> +        s->ihp = coif5_ihp;
> +        break;
> +    case 3:
> +        s->wavelet_length = 18;
> +        s->lp  = rbior68_lp;
> +        s->hp  = rbior68_hp;
> +        s->ilp = rbior68_ilp;
> +        s->ihp = rbior68_ihp;
> +        break;
> +    default:
> +        av_assert0(0);
> +    }
> +
> +    s->levels = FFMIN(s->levels, lrint(log(s->nb_samples / (s->wavelet_length - 1.0)) / M_LN2));
> +    av_log(ctx, AV_LOG_VERBOSE, "levels:%d\n", s->levels);
> +    N = s->nb_samples;
> +    i = s->levels;
> +
> +    while (i > 0) {
> +        N = N + s->wavelet_length - 2;
> +        N = ceil(N / 2.0);
> +        s->length[i] = N;
> +        s->out_length += N;
> +        i--;
> +    }
> +
> +    s->length[0]  = s->length[1];
> +    s->out_length += s->length[0];
> +
> +    s->temp0 = ff_get_audio_buffer(outlink, s->out_length);
> +    s->temp1 = ff_get_audio_buffer(outlink, s->out_length);
> +    s->wtc = ff_get_audio_buffer(outlink, s->out_length);
> +    s->power = ff_get_audio_buffer(outlink, s->out_length);
> +    s->filter = ff_get_audio_buffer(outlink, s->out_length);
> +    s->signal = ff_get_audio_buffer(outlink, s->out_length);
> +    s->profile = ff_get_audio_buffer(outlink, MAX_LEVELS);
> +    if (!s->temp0 || !s->temp1 || !s->wtc ||
> +        !s->power || !s->profile || !s->filter ||
> +        !s->signal)
> +        return AVERROR(ENOMEM);
> +
> +    return 0;
> +}
> +
> +static void dwt(double *src, int N,
> +                const double *lf, const double *hf, int flen,
> +                double *low, double *high, int lhlen)
> +{
> +    for (int i = 0; i < lhlen; i++) {
> +        const int t = 2 * i + 1;
> +
> +        low[i] = 0.0;
> +        high[i] = 0.0;
> +
> +        for (int l = 0; l < flen; l++) {
> +            if (t - l >= 0 && t - l < N) {
> +                const int is = t - l;
> +                low[i] += lf[l] * src[is];
> +                high[i] += hf[l] * src[is];
> +            } else if (t - l < 0) {
> +                const int is = -t + l - 1;
> +                low[i] += lf[l] * src[is];
> +                high[i] += hf[l] * src[is];
> +            } else if (t - l >= N) {
> +                const int is = 2 * N - t + l - 1;
> +                low[i] += lf[l] * src[is];
> +                high[i] += hf[l] * src[is];
> +            }
> +        }
> +    }
> +}
> +
> +static void idwt(double *low, int lhlen, double *high,
> +                 const double *lf, const double *hf,
> +                 int flen, double *dst)
> +{
> +    int m = -2, n = -1;
> +
> +    for (int v = 0; v < lhlen; v++) {
> +        m += 2;
> +        n += 2;
> +        dst[m] = 0.0;
> +        dst[n] = 0.0;
> +        for (int l = 0; l < flen / 2; l++) {
> +            const int t = 2 * l;
> +
> +            if (v - l >= 0 && v - l < lhlen) {
> +                const int is = v - l;
> +
> +                dst[m] += lf[t] * low[is] + hf[t] * high[is];
> +                dst[n] += lf[t + 1] * low[is] + hf[t + 1] * high[is];
> +            }
> +        }
> +    }
> +}
> +
> +static void dwt_levels(AudioFWTDNContext *s, int levels, int inlength, int out_length,
> +                       const double *in, double *temp0, double *temp1, double *wtc)
> +{
> +    int N = out_length;
> +    int temp_len = s->nb_samples;
> +
> +    for (int i = 0; i < inlength; i++)
> +        temp0[i] = in[i];
> +    for (int i = inlength; i < s->nb_samples; i++)
> +        temp0[i] = 0.;
> +
> +    for (int level = 0; level < levels; level++) {
> +        const int level_length = s->length[levels - level];
> +
> +        N -= level_length;
> +        dwt(temp0, temp_len, s->lp, s->hp, s->wavelet_length, temp1, wtc + N, level_length);
> +        temp_len = s->length[levels - level];
> +
> +        if (level == levels - 1) {
> +            for (int i = 0; i < level_length; i++)
> +                wtc[i] = temp1[i];
> +        } else {
> +            for (int i = 0; i < level_length; i++)
> +                temp0[i] = temp1[i];
> +        }
> +    }
> +}
> +
> +static void idwt_levels(AudioFWTDNContext *s, int levels, double *out,
> +                        double *temp, double *wtc)
> +{
> +    const int app_len = s->length[0];
> +    const int lf = s->wavelet_length;
> +    int iter = app_len;
> +
> +    for (int i = 0; i < app_len; i++)
> +        out[i] = wtc[i];
> +
> +    for (int i = 0; i < levels; i++) {
> +        const int det_len = s->length[i + 1];
> +
> +        idwt(out, det_len, wtc + iter, s->ilp, s->ihp, s->wavelet_length, temp);
> +        for (int k = lf - 2; k < 2 * det_len; k++)
> +            out[k - lf + 2] = temp[k];
> +
> +        iter += det_len;
> +    }
> +}
> +
> +static void denoise_level(double *out, const double *in, double *filter, double *signal, double percent, int length)
> +{
> +    const double x = percent * 0.01;
> +    const double y = 1.0 - x;
> +
> +    for (int i = 0; i < length; i++)
> +        out[i] = in[i] * (x * filter[i] * signal[i] + y);
> +}
> +
> +static double sqr(double in)
> +{
> +    return in * in;
> +}
> +
> +static void power_average(const double *in, double *out, int length)
> +{
> +    out[0] = sqr(in[0]);
> +    for (int i = 1; i < length; i++)
> +        out[i] = 0.1 * sqr(in[i]) + 0.9 * out[i - 1];
> +}
> +
> +static void noise_filter(const double *in, double *out, double profile, double ak, int length)
> +{
> +    for (int i = 0; i < length; i++)
> +        out[i] = 1.0 - profile * ak / in[i];
> +}
> +
> +static void signal_filter(const double *in, double *out, double profile, double ak, int length)
> +{
> +    for (int i = 0; i < length; i++)
> +        out[i] = in[i] >= profile * ak;
> +}
> +
> +static void measure_level_profile(AVFilterContext *ctx, const double *wtc, double *profile, int length)
> +{
> +    double sum = 0.;
> +
> +    for (int i = 0; i < length; i++)
> +        sum += sqr(wtc[i]);
> +
> +    *profile = sum / length;
> +}
> +
> +typedef struct ThreadData {
> +    AVFrame *in, *out;
> +} ThreadData;
> +
> +static int filter_channel(AVFilterContext *ctx, void *arg, int ch, int nb_jobs)
> +{
> +    AudioFWTDNContext *s = ctx->priv;
> +    ThreadData *td = arg;
> +    AVFrame *in = td->in;
> +    AVFrame *out = td->out;
> +    const double *src = (const double *)(in->extended_data[ch]);
> +    double *temp0 = (double *)s->temp0->extended_data[ch];
> +    double *temp1 = (double *)s->temp1->extended_data[ch];
> +    double *wtc = (double *)s->wtc->extended_data[ch];
> +    double *dst = (double *)out->extended_data[ch];
> +    double *profile = (double *)s->profile->extended_data[ch];
> +    double *power = (double *)s->power->extended_data[ch];
> +    double *filter = (double *)s->filter->extended_data[ch];
> +    double *signal = (double *)s->signal->extended_data[ch];
> +    int offset = 0;
> +
> +    dwt_levels(s, s->levels, in->nb_samples, s->out_length, src, temp0, temp1, wtc);
> +
> +    if (!s->got_profile && s->need_profile) {
> +        for (int level = 0; level <= s->levels; level++) {
> +            measure_level_profile(ctx, wtc + offset, &profile[level], s->length[level]);
> +            offset += s->length[level];
> +        }
> +        offset = 0;
> +    } else if (!s->got_profile && !s->need_profile) {
> +        for (int level = 0; level <= s->levels; level++)
> +            profile[level] = s->sigma / 1000000.;
> +    }
> +
> +    for (int level = 0; level <= s->levels; level++) {
> +        const double ak = 2.0 + ((4.0 * level) / s->levels);
> +        const int length = s->length[level];
> +
> +        power_average(wtc + offset, power + offset, length);
> +        noise_filter(power + offset, filter + offset, profile[level], ak, length);
> +        signal_filter(power + offset, signal + offset, profile[level], ak, length);
> +        denoise_level(wtc + offset, wtc + offset, filter + offset, signal + offset, s->percent, length);
> +        offset += length;
> +    }
> +
> +    idwt_levels(s, s->levels, dst, temp0, wtc);
> +
> +    return 0;
> +}
> +
> +static int filter_frame(AVFilterLink *inlink, AVFrame *in)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    AudioFWTDNContext *s = ctx->priv;
> +    AVFilterLink *outlink = ctx->outputs[0];
> +    ThreadData td;
> +    AVFrame *out = NULL;
> +
> +    out = ff_get_audio_buffer(outlink, in->nb_samples);
> +    if (!out) {
> +        av_frame_free(&in);
> +        return AVERROR(ENOMEM);
> +    }
> +    out->pts = in->pts;
> +
> +    td.in  = in;
> +    td.out = out;
> +    ctx->internal->execute(ctx, filter_channel, &td, NULL, inlink->channels);
> +    if (s->need_profile)
> +        s->got_profile = 1;
> +
> +    av_frame_free(&in);
> +    return ff_filter_frame(outlink, out);
> +}
> +
> +static int activate(AVFilterContext *ctx)
> +{
> +    AVFilterLink *inlink = ctx->inputs[0];
> +    AVFilterLink *outlink = ctx->outputs[0];
> +    AudioFWTDNContext *s = ctx->priv;
> +    AVFrame *in = NULL;
> +    int ret;
> +
> +    FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink);
> +
> +    ret = ff_inlink_consume_samples(inlink, s->nb_samples, s->nb_samples, &in);
> +    if (ret < 0)
> +        return ret;
> +    if (ret > 0)
> +        return filter_frame(inlink, in);
> +
> +    FF_FILTER_FORWARD_STATUS(inlink, outlink);
> +    FF_FILTER_FORWARD_WANTED(outlink, inlink);
> +
> +    return FFERROR_NOT_READY;
> +}
> +
> +static av_cold void uninit(AVFilterContext *ctx)
> +{
> +    AudioFWTDNContext *s = ctx->priv;
> +
> +    av_frame_free(&s->filter);
> +    av_frame_free(&s->power);
> +    av_frame_free(&s->profile);
> +    av_frame_free(&s->signal);
> +    av_frame_free(&s->temp0);
> +    av_frame_free(&s->temp1);
> +    av_frame_free(&s->wtc);
> +}
> +
> +static const AVFilterPad inputs[] = {
> +    {
> +        .name         = "default",
> +        .type         = AVMEDIA_TYPE_AUDIO,
> +    },
> +    { NULL }
> +};
> +
> +static const AVFilterPad outputs[] = {
> +    {
> +        .name          = "default",
> +        .type          = AVMEDIA_TYPE_AUDIO,
> +        .config_props  = config_output,
> +    },
> +    { NULL }
> +};
> +
> +AVFilter ff_af_afwtdn = {
> +    .name          = "afwtdn",
> +    .description   = NULL_IF_CONFIG_SMALL("Reduce broadband noise from stream using Fast Wavelet Transform."),
> +    .query_formats = query_formats,
> +    .priv_size     = sizeof(AudioFWTDNContext),
> +    .priv_class    = &afwtdn_class,
> +    .activate      = activate,
> +    .uninit        = uninit,
> +    .inputs        = inputs,
> +    .outputs       = outputs,
> +    .flags         = AVFILTER_FLAG_SLICE_THREADS,
> +};
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index 1183e40267..de5884529c 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -43,6 +43,7 @@ extern AVFilter ff_af_afftdn;
>  extern AVFilter ff_af_afftfilt;
>  extern AVFilter ff_af_afir;
>  extern AVFilter ff_af_aformat;
> +extern AVFilter ff_af_afwtdn;
>  extern AVFilter ff_af_agate;
>  extern AVFilter ff_af_aiir;
>  extern AVFilter ff_af_aintegral;
Paul B Mahol June 2, 2020, 1:54 p.m. UTC | #2
On 6/2/20, Nicolas George <george@nsup.org> wrote:
> Paul B Mahol (12020-06-02):
>> Signed-off-by: Paul B Mahol <onemda@gmail.com>
>> ---
>>  doc/filters.texi         |  41 +++
>>  libavfilter/Makefile     |   1 +
>
>>  libavfilter/af_afwtdn.c  | 653 +++++++++++++++++++++++++++++++++++++++
>
> I still oppose strongly to the name of the filter.

Your opinion is not technical one.

>
>>  libavfilter/allfilters.c |   1 +
>>  4 files changed, 696 insertions(+)
>>  create mode 100644 libavfilter/af_afwtdn.c
>>
>> diff --git a/doc/filters.texi b/doc/filters.texi
>> index a0b4ab2228..15a8636398 100644
>> --- a/doc/filters.texi
>> +++ b/doc/filters.texi
>> @@ -1314,6 +1314,47 @@ Force the output to either unsigned 8-bit or signed
>> 16-bit stereo
>>  aformat=sample_fmts=u8|s16:channel_layouts=stereo
>>  @end example
>>
>> +@section afwtdn
>> +Reduce broadband noise from input samples using Fast Wavelet Transform.
>> +
>> +A description of the accepted options follows.
>> +
>> +@table @option
>> +@item sigma
>> +Set the noise sigma, allowed range is from 0 to 100.
>> +Default value is 0.
>> +This option controls strength of denoising applied to input samples.
>> +
>> +@item levels
>> +Set the number of wavelet levels of decomposition.
>> +Allowed range is from 1 to 19.
>> +Default value is 10.
>> +Setting this too low make denoising performance very poor.
>> +
>> +@item wavet
>> +Set wavelet type for decomposition of input frame.
>> +Available wavelets are:
>> +
>
>> +@table @samp
>> +@item deb4
>> +@item deb10
>> +@item coif5
>> +@item rbior68
>> +@end table
>
> Insufficient: users have no idea what it means.

They need only to experiment, we can not teach them whole wavelet
theory in 5 minutes.

>
>> +
>> +@item percent
>> +Set percent of full denoising. Allowed range is from 0 to 100 percent.
>> +Default value is 85 percent or partial denoising.
>> +
>> +@item profile
>> +If set enabled, first input frame will be used as noise profile.
>> +If first frame samples contain non-noise performance will be very poor.
>> +
>
>> +@item frames
>> +Set size of single frame in number of samples. Allowed range is from 1024
>> to
>> +262144. Default frame size is 8192 samples.
>
> If it is the size of a single frame, why is it in plural?

frames stands for _frame_   _s_ize.

>
>> +@end table
>> +
>>  @section agate
>>
>>  A gate is mainly used to reduce lower parts of a signal. This kind of
>> signal
>> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
>> index 5123540653..191826a622 100644
>> --- a/libavfilter/Makefile
>> +++ b/libavfilter/Makefile
>> @@ -50,6 +50,7 @@ OBJS-$(CONFIG_AFFTDN_FILTER)                 +=
>> af_afftdn.o
>>  OBJS-$(CONFIG_AFFTFILT_FILTER)               += af_afftfilt.o
>>  OBJS-$(CONFIG_AFIR_FILTER)                   += af_afir.o
>>  OBJS-$(CONFIG_AFORMAT_FILTER)                += af_aformat.o
>> +OBJS-$(CONFIG_AFWTDN_FILTER)                 += af_afwtdn.o
>>  OBJS-$(CONFIG_AGATE_FILTER)                  += af_agate.o
>>  OBJS-$(CONFIG_AIIR_FILTER)                   += af_aiir.o
>>  OBJS-$(CONFIG_AINTEGRAL_FILTER)              += af_aderivative.o
>> diff --git a/libavfilter/af_afwtdn.c b/libavfilter/af_afwtdn.c
>> new file mode 100644
>> index 0000000000..2d51c7bf2d
>> --- /dev/null
>> +++ b/libavfilter/af_afwtdn.c
>> @@ -0,0 +1,653 @@
>> +/*
>> + * Copyright (c) 2020 Paul B Mahol
>> + *
>> + * 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
>> + */
>> +
>> +#include <float.h>
>> +
>> +#include "libavutil/avassert.h"
>> +#include "libavutil/audio_fifo.h"
>> +#include "libavutil/avstring.h"
>> +#include "libavutil/opt.h"
>> +#include "avfilter.h"
>> +#include "audio.h"
>> +#include "filters.h"
>> +#include "formats.h"
>> +
>
>> +static const double rbior68_lp[18] = {
>> +    0.0, 0.0, 0.0, 0.0,
>> +    0.014426282505624435, 0.014467504896790148,
>> +    -0.07872200106262882, -0.04036797903033992,
>> +    0.41784910915027457, 0.7589077294536541,
>> +    0.41784910915027457, -0.04036797903033992,
>> +    -0.07872200106262882, 0.014467504896790148,
>> +    0.014426282505624435, 0.0, 0.0, 0.0,
>> +};
>> +
>> +static const double rbior68_hp[18] = {
>> +    -0.0019088317364812906, -0.0019142861290887667,
>> +    0.016990639867602342, 0.01193456527972926,
>> +    -0.04973290349094079, -0.07726317316720414,
>> +    0.09405920349573646, 0.4207962846098268,
>> +    -0.8259229974584023, 0.4207962846098268,
>> +    0.09405920349573646, -0.07726317316720414,
>> +    -0.04973290349094079, 0.01193456527972926,
>> +    0.016990639867602342, -0.0019142861290887667,
>> +    -0.0019088317364812906, 0.0,
>> +};
>> +
>> +static const double rbior68_ilp[18] = {
>> +    0.0019088317364812906, -0.0019142861290887667,
>> +    -0.016990639867602342, 0.01193456527972926,
>> +    0.04973290349094079, -0.07726317316720414,
>> +    -0.09405920349573646, 0.4207962846098268,
>> +    0.8259229974584023, 0.4207962846098268,
>> +    -0.09405920349573646, -0.07726317316720414,
>> +    0.04973290349094079, 0.01193456527972926,
>> +    -0.016990639867602342, -0.0019142861290887667,
>> +    0.0019088317364812906, 0.0,
>> +};
>> +
>> +static const double rbior68_ihp[18] = {
>> +    0.0, 0.0, 0.0, 0.0,
>> +    0.014426282505624435, -0.014467504896790148,
>> +    -0.07872200106262882, 0.04036797903033992,
>> +    0.41784910915027457, -0.7589077294536541,
>> +    0.41784910915027457, 0.04036797903033992,
>> +    -0.07872200106262882, -0.014467504896790148,
>> +    0.014426282505624435, 0.0, 0.0, 0.0,
>> +};
>> +
>> +static const double coif5_lp[30] = {
>> +    -9.517657273819165e-08, -1.6744288576823017e-07,
>> +    2.0637618513646814e-06, 3.7346551751414047e-06,
>> +    -2.1315026809955787e-05, -4.134043227251251e-05,
>> +    0.00014054114970203437, 0.00030225958181306315,
>> +    -0.0006381313430451114, -0.0016628637020130838,
>> +    0.0024333732126576722, 0.006764185448053083,
>> +    -0.009164231162481846, -0.01976177894257264,
>> +    0.03268357426711183, 0.0412892087501817,
>> +    -0.10557420870333893, -0.06203596396290357,
>> +    0.4379916261718371, 0.7742896036529562,
>> +    0.4215662066908515, -0.05204316317624377,
>> +    -0.09192001055969624, 0.02816802897093635,
>> +    0.023408156785839195, -0.010131117519849788,
>> +    -0.004159358781386048, 0.0021782363581090178,
>> +    0.00035858968789573785, -0.00021208083980379827,
>> +};
>> +
>> +static const double coif5_hp[30] = {
>> +    0.00021208083980379827, 0.00035858968789573785,
>> +    -0.0021782363581090178, -0.004159358781386048,
>> +    0.010131117519849788, 0.023408156785839195,
>> +    -0.02816802897093635, -0.09192001055969624,
>> +    0.05204316317624377, 0.4215662066908515,
>> +    -0.7742896036529562, 0.4379916261718371,
>> +    0.06203596396290357, -0.10557420870333893,
>> +    -0.0412892087501817, 0.03268357426711183,
>> +    0.01976177894257264, -0.009164231162481846,
>> +    -0.006764185448053083, 0.0024333732126576722,
>> +    0.0016628637020130838, -0.0006381313430451114,
>> +    -0.00030225958181306315, 0.00014054114970203437,
>> +    4.134043227251251e-05, -2.1315026809955787e-05,
>> +    -3.7346551751414047e-06, 2.0637618513646814e-06,
>> +    1.6744288576823017e-07, -9.517657273819165e-08,
>> +};
>> +
>> +static const double coif5_ilp[30] = {
>> +    -0.00021208083980379827, 0.00035858968789573785,
>> +    0.0021782363581090178, -0.004159358781386048,
>> +    -0.010131117519849788, 0.023408156785839195,
>> +    0.02816802897093635, -0.09192001055969624,
>> +    -0.05204316317624377, 0.4215662066908515,
>> +    0.7742896036529562, 0.4379916261718371,
>> +    -0.06203596396290357, -0.10557420870333893,
>> +    0.0412892087501817, 0.03268357426711183,
>> +    -0.01976177894257264, -0.009164231162481846,
>> +    0.006764185448053083, 0.0024333732126576722,
>> +    -0.0016628637020130838, -0.0006381313430451114,
>> +    0.00030225958181306315, 0.00014054114970203437,
>> +    -4.134043227251251e-05, -2.1315026809955787e-05,
>> +    3.7346551751414047e-06, 2.0637618513646814e-06,
>> +    -1.6744288576823017e-07, -9.517657273819165e-08,
>> +};
>> +
>> +static const double coif5_ihp[30] = {
>> +    -9.517657273819165e-08, 1.6744288576823017e-07,
>> +    2.0637618513646814e-06, -3.7346551751414047e-06,
>> +    -2.1315026809955787e-05, 4.134043227251251e-05,
>> +    0.00014054114970203437, -0.00030225958181306315,
>> +    -0.0006381313430451114, 0.0016628637020130838,
>> +    0.0024333732126576722, -0.006764185448053083,
>> +    -0.009164231162481846, 0.01976177894257264,
>> +    0.03268357426711183, -0.0412892087501817,
>> +    -0.10557420870333893, 0.06203596396290357,
>> +    0.4379916261718371, -0.7742896036529562,
>> +    0.4215662066908515, 0.05204316317624377,
>> +    -0.09192001055969624, -0.02816802897093635,
>> +    0.023408156785839195, 0.010131117519849788,
>> +    -0.004159358781386048, -0.0021782363581090178,
>> +    0.00035858968789573785, 0.00021208083980379827,
>> +};
>> +
>> +static const double deb10_lp[20] = {
>> +    -1.326420300235487e-05, 9.358867000108985e-05,
>> +    -0.0001164668549943862, -0.0006858566950046825,
>> +    0.00199240529499085, 0.0013953517469940798,
>> +    -0.010733175482979604, 0.0036065535669883944,
>> +    0.03321267405893324, -0.02945753682194567,
>> +    -0.07139414716586077, 0.09305736460380659,
>> +    0.12736934033574265, -0.19594627437659665,
>> +    -0.24984642432648865, 0.2811723436604265,
>> +    0.6884590394525921, 0.5272011889309198,
>> +    0.18817680007762133, 0.026670057900950818,
>> +};
>> +
>> +static const double deb10_hp[20] = {
>> +    -0.026670057900950818, 0.18817680007762133,
>> +    -0.5272011889309198, 0.6884590394525921,
>> +    -0.2811723436604265, -0.24984642432648865,
>> +    0.19594627437659665, 0.12736934033574265,
>> +    -0.09305736460380659, -0.07139414716586077,
>> +    0.02945753682194567, 0.03321267405893324,
>> +    -0.0036065535669883944, -0.010733175482979604,
>> +    -0.0013953517469940798, 0.00199240529499085,
>> +    0.0006858566950046825, -0.0001164668549943862,
>> +    -9.358867000108985e-05, -1.326420300235487e-05,
>> +};
>> +
>> +static const double deb10_ilp[20] = {
>> +    0.026670057900950818, 0.18817680007762133,
>> +    0.5272011889309198, 0.6884590394525921,
>> +    0.2811723436604265, -0.24984642432648865,
>> +    -0.19594627437659665, 0.12736934033574265,
>> +    0.09305736460380659, -0.07139414716586077,
>> +    -0.02945753682194567, 0.03321267405893324,
>> +    0.0036065535669883944, -0.010733175482979604,
>> +    0.0013953517469940798, 0.00199240529499085,
>> +    -0.0006858566950046825, -0.0001164668549943862,
>> +    9.358867000108985e-05, -1.326420300235487e-05,
>> +};
>> +
>> +static const double deb10_ihp[20] = {
>> +    -1.326420300235487e-05, -9.358867000108985e-05,
>> +    -0.0001164668549943862, 0.0006858566950046825,
>> +    0.00199240529499085, -0.0013953517469940798,
>> +    -0.010733175482979604, -0.0036065535669883944,
>> +    0.03321267405893324, 0.02945753682194567,
>> +    -0.07139414716586077, -0.09305736460380659,
>> +    0.12736934033574265, 0.19594627437659665,
>> +    -0.24984642432648865, -0.2811723436604265,
>> +    0.6884590394525921, -0.5272011889309198,
>> +    0.18817680007762133, -0.026670057900950818,
>> +};
>> +
>> +static const double deb4_lp[8] = {
>> +    -0.0105974018, 0.0328830117,
>> +     0.0308413818, -0.1870348117,
>> +    -0.0279837694, 0.6308807679,
>> +     0.7148465706, 0.2303778133,
>> +};
>> +
>> +static const double deb4_hp[8] = {
>> +    -0.2303778133, 0.7148465706,
>> +    -0.6308807679, -0.0279837694,
>> +     0.1870348117, 0.0308413818,
>> +    -0.0328830117, -0.0105974018,
>> +};
>> +
>> +static const double deb4_ilp[8] = {
>> +     0.23037781330885523, 0.7148465705525415,
>> +     0.6308807679295904, -0.02798376941698385,
>> +    -0.18703481171888114, 0.030841381835986965,
>> +     0.032883011666982945, -0.010597401784997278,
>> +};
>> +
>> +static const double deb4_ihp[8] = {
>> +    -0.010597401784997278, -0.032883011666982945,
>> +     0.030841381835986965, 0.18703481171888114,
>> +    -0.02798376941698385, -0.6308807679295904,
>> +     0.7148465705525415, -0.23037781330885523,
>> +};
>
> A bunch of magical numbers. We need to know where they come from.
>

Get back to math class, those number are not magical at all.

Seriously there are other wavelet non-audio filters in libavfilter or
even libavcodec already, and they do not show from where numbers come.

>> +
>> +#define MAX_LEVELS 20
>> +
>> +typedef struct AudioFWTDNContext {
>> +    const AVClass *class;
>> +
>> +    double sigma;
>> +    double percent;
>> +
>> +    int wavelet_type;
>> +    int thresholding_function;
>> +    int nb_samples;
>> +    int levels;
>> +    int wavelet_length;
>> +    int length[MAX_LEVELS];
>> +    int out_length;
>> +    int need_profile;
>> +    int got_profile;
>> +
>> +    const double *lp, *hp;
>> +    const double *ilp, *ihp;
>> +
>> +    AVFrame *temp0, *temp1;
>> +    AVFrame *wtc, *power, *profile, *filter, *signal;
>> +} AudioFWTDNContext;
>> +
>> +#define OFFSET(x) offsetof(AudioFWTDNContext, x)
>> +#define AF AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
>> +
>> +static const AVOption afwtdn_options[] = {
>> +    { "sigma", "set noise sigma", OFFSET(sigma), AV_OPT_TYPE_DOUBLE,
>> {.dbl=0}, 0, 100, AF },
>> +    { "levels", "set number of wavelet levels", OFFSET(levels),
>> AV_OPT_TYPE_INT, {.i64=10}, 1, MAX_LEVELS-1, AF },
>> +    { "wavet", "set wavelet type", OFFSET(wavelet_type), AV_OPT_TYPE_INT,
>> {.i64=0}, 0, 3, AF, "wavet" },
>> +    { "deb4",  "deb4", 0, AV_OPT_TYPE_CONST,  {.i64=0}, 0, 0, AF, "wavet"
>> },
>> +    { "deb10", "deb10", 0, AV_OPT_TYPE_CONST,  {.i64=1}, 0, 0, AF,
>> "wavet" },
>> +    { "coif5", "coif5", 0, AV_OPT_TYPE_CONST,  {.i64=2}, 0, 0, AF,
>> "wavet" },
>> +    { "rbior68", "rbior68", 0, AV_OPT_TYPE_CONST,  {.i64=3}, 0, 0, AF,
>> "wavet" },
>> +    { "percent", "set percent of full denoising",
>> OFFSET(percent),AV_OPT_TYPE_DOUBLE, {.dbl=85}, 0, 100, AF },
>> +    { "profile", "profile noise", OFFSET(need_profile), AV_OPT_TYPE_BOOL,
>> {.i64=0}, 0, 1, AF },
>> +    { "frames", "set frame size", OFFSET(nb_samples), AV_OPT_TYPE_INT,
>> {.i64=8192}, 1024, 262144, AF },
>> +    { NULL }
>> +};
>> +
>> +AVFILTER_DEFINE_CLASS(afwtdn);
>> +
>> +static int query_formats(AVFilterContext *ctx)
>> +{
>> +    AVFilterFormats *formats = NULL;
>> +    AVFilterChannelLayouts *layouts = NULL;
>> +    static const enum AVSampleFormat sample_fmts[] = {
>> +        AV_SAMPLE_FMT_DBLP,
>> +        AV_SAMPLE_FMT_NONE
>> +    };
>> +    int 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;
>> +
>> +    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_all_samplerates();
>> +    return ff_set_common_samplerates(ctx, formats);
>> +}
>> +
>> +static int config_output(AVFilterLink *outlink)
>> +{
>> +    AVFilterContext *ctx = outlink->src;
>> +    AudioFWTDNContext *s = ctx->priv;
>> +    int N, i;
>> +
>> +    switch (s->wavelet_type) {
>> +    case 0:
>> +        s->wavelet_length = 8;
>> +        s->lp  = deb4_lp;
>> +        s->hp  = deb4_hp;
>> +        s->ilp = deb4_ilp;
>> +        s->ihp = deb4_ihp;
>> +        break;
>> +    case 1:
>> +        s->wavelet_length = 20;
>> +        s->lp  = deb10_lp;
>> +        s->hp  = deb10_hp;
>> +        s->ilp = deb10_ilp;
>> +        s->ihp = deb10_ihp;
>> +        break;
>> +    case 2:
>> +        s->wavelet_length = 30;
>> +        s->lp  = coif5_lp;
>> +        s->hp  = coif5_hp;
>> +        s->ilp = coif5_ilp;
>> +        s->ihp = coif5_ihp;
>> +        break;
>> +    case 3:
>> +        s->wavelet_length = 18;
>> +        s->lp  = rbior68_lp;
>> +        s->hp  = rbior68_hp;
>> +        s->ilp = rbior68_ilp;
>> +        s->ihp = rbior68_ihp;
>> +        break;
>> +    default:
>> +        av_assert0(0);
>> +    }
>> +
>> +    s->levels = FFMIN(s->levels, lrint(log(s->nb_samples /
>> (s->wavelet_length - 1.0)) / M_LN2));
>> +    av_log(ctx, AV_LOG_VERBOSE, "levels:%d\n", s->levels);
>> +    N = s->nb_samples;
>> +    i = s->levels;
>> +
>> +    while (i > 0) {
>> +        N = N + s->wavelet_length - 2;
>> +        N = ceil(N / 2.0);
>> +        s->length[i] = N;
>> +        s->out_length += N;
>> +        i--;
>> +    }
>> +
>> +    s->length[0]  = s->length[1];
>> +    s->out_length += s->length[0];
>> +
>> +    s->temp0 = ff_get_audio_buffer(outlink, s->out_length);
>> +    s->temp1 = ff_get_audio_buffer(outlink, s->out_length);
>> +    s->wtc = ff_get_audio_buffer(outlink, s->out_length);
>> +    s->power = ff_get_audio_buffer(outlink, s->out_length);
>> +    s->filter = ff_get_audio_buffer(outlink, s->out_length);
>> +    s->signal = ff_get_audio_buffer(outlink, s->out_length);
>> +    s->profile = ff_get_audio_buffer(outlink, MAX_LEVELS);
>> +    if (!s->temp0 || !s->temp1 || !s->wtc ||
>> +        !s->power || !s->profile || !s->filter ||
>> +        !s->signal)
>> +        return AVERROR(ENOMEM);
>> +
>> +    return 0;
>> +}
>> +
>> +static void dwt(double *src, int N,
>> +                const double *lf, const double *hf, int flen,
>> +                double *low, double *high, int lhlen)
>> +{
>> +    for (int i = 0; i < lhlen; i++) {
>> +        const int t = 2 * i + 1;
>> +
>> +        low[i] = 0.0;
>> +        high[i] = 0.0;
>> +
>> +        for (int l = 0; l < flen; l++) {
>> +            if (t - l >= 0 && t - l < N) {
>> +                const int is = t - l;
>> +                low[i] += lf[l] * src[is];
>> +                high[i] += hf[l] * src[is];
>> +            } else if (t - l < 0) {
>> +                const int is = -t + l - 1;
>> +                low[i] += lf[l] * src[is];
>> +                high[i] += hf[l] * src[is];
>> +            } else if (t - l >= N) {
>> +                const int is = 2 * N - t + l - 1;
>> +                low[i] += lf[l] * src[is];
>> +                high[i] += hf[l] * src[is];
>> +            }
>> +        }
>> +    }
>> +}
>> +
>> +static void idwt(double *low, int lhlen, double *high,
>> +                 const double *lf, const double *hf,
>> +                 int flen, double *dst)
>> +{
>> +    int m = -2, n = -1;
>> +
>> +    for (int v = 0; v < lhlen; v++) {
>> +        m += 2;
>> +        n += 2;
>> +        dst[m] = 0.0;
>> +        dst[n] = 0.0;
>> +        for (int l = 0; l < flen / 2; l++) {
>> +            const int t = 2 * l;
>> +
>> +            if (v - l >= 0 && v - l < lhlen) {
>> +                const int is = v - l;
>> +
>> +                dst[m] += lf[t] * low[is] + hf[t] * high[is];
>> +                dst[n] += lf[t + 1] * low[is] + hf[t + 1] * high[is];
>> +            }
>> +        }
>> +    }
>> +}
>> +
>> +static void dwt_levels(AudioFWTDNContext *s, int levels, int inlength,
>> int out_length,
>> +                       const double *in, double *temp0, double *temp1,
>> double *wtc)
>> +{
>> +    int N = out_length;
>> +    int temp_len = s->nb_samples;
>> +
>> +    for (int i = 0; i < inlength; i++)
>> +        temp0[i] = in[i];
>> +    for (int i = inlength; i < s->nb_samples; i++)
>> +        temp0[i] = 0.;
>> +
>> +    for (int level = 0; level < levels; level++) {
>> +        const int level_length = s->length[levels - level];
>> +
>> +        N -= level_length;
>> +        dwt(temp0, temp_len, s->lp, s->hp, s->wavelet_length, temp1, wtc
>> + N, level_length);
>> +        temp_len = s->length[levels - level];
>> +
>> +        if (level == levels - 1) {
>> +            for (int i = 0; i < level_length; i++)
>> +                wtc[i] = temp1[i];
>> +        } else {
>> +            for (int i = 0; i < level_length; i++)
>> +                temp0[i] = temp1[i];
>> +        }
>> +    }
>> +}
>> +
>> +static void idwt_levels(AudioFWTDNContext *s, int levels, double *out,
>> +                        double *temp, double *wtc)
>> +{
>> +    const int app_len = s->length[0];
>> +    const int lf = s->wavelet_length;
>> +    int iter = app_len;
>> +
>> +    for (int i = 0; i < app_len; i++)
>> +        out[i] = wtc[i];
>> +
>> +    for (int i = 0; i < levels; i++) {
>> +        const int det_len = s->length[i + 1];
>> +
>> +        idwt(out, det_len, wtc + iter, s->ilp, s->ihp, s->wavelet_length,
>> temp);
>> +        for (int k = lf - 2; k < 2 * det_len; k++)
>> +            out[k - lf + 2] = temp[k];
>> +
>> +        iter += det_len;
>> +    }
>> +}
>> +
>> +static void denoise_level(double *out, const double *in, double *filter,
>> double *signal, double percent, int length)
>> +{
>> +    const double x = percent * 0.01;
>> +    const double y = 1.0 - x;
>> +
>> +    for (int i = 0; i < length; i++)
>> +        out[i] = in[i] * (x * filter[i] * signal[i] + y);
>> +}
>> +
>> +static double sqr(double in)
>> +{
>> +    return in * in;
>> +}
>> +
>> +static void power_average(const double *in, double *out, int length)
>> +{
>> +    out[0] = sqr(in[0]);
>> +    for (int i = 1; i < length; i++)
>> +        out[i] = 0.1 * sqr(in[i]) + 0.9 * out[i - 1];
>> +}
>> +
>> +static void noise_filter(const double *in, double *out, double profile,
>> double ak, int length)
>> +{
>> +    for (int i = 0; i < length; i++)
>> +        out[i] = 1.0 - profile * ak / in[i];
>> +}
>> +
>> +static void signal_filter(const double *in, double *out, double profile,
>> double ak, int length)
>> +{
>> +    for (int i = 0; i < length; i++)
>> +        out[i] = in[i] >= profile * ak;
>> +}
>> +
>> +static void measure_level_profile(AVFilterContext *ctx, const double
>> *wtc, double *profile, int length)
>> +{
>> +    double sum = 0.;
>> +
>> +    for (int i = 0; i < length; i++)
>> +        sum += sqr(wtc[i]);
>> +
>> +    *profile = sum / length;
>> +}
>> +
>> +typedef struct ThreadData {
>> +    AVFrame *in, *out;
>> +} ThreadData;
>> +
>> +static int filter_channel(AVFilterContext *ctx, void *arg, int ch, int
>> nb_jobs)
>> +{
>> +    AudioFWTDNContext *s = ctx->priv;
>> +    ThreadData *td = arg;
>> +    AVFrame *in = td->in;
>> +    AVFrame *out = td->out;
>> +    const double *src = (const double *)(in->extended_data[ch]);
>> +    double *temp0 = (double *)s->temp0->extended_data[ch];
>> +    double *temp1 = (double *)s->temp1->extended_data[ch];
>> +    double *wtc = (double *)s->wtc->extended_data[ch];
>> +    double *dst = (double *)out->extended_data[ch];
>> +    double *profile = (double *)s->profile->extended_data[ch];
>> +    double *power = (double *)s->power->extended_data[ch];
>> +    double *filter = (double *)s->filter->extended_data[ch];
>> +    double *signal = (double *)s->signal->extended_data[ch];
>> +    int offset = 0;
>> +
>> +    dwt_levels(s, s->levels, in->nb_samples, s->out_length, src, temp0,
>> temp1, wtc);
>> +
>> +    if (!s->got_profile && s->need_profile) {
>> +        for (int level = 0; level <= s->levels; level++) {
>> +            measure_level_profile(ctx, wtc + offset, &profile[level],
>> s->length[level]);
>> +            offset += s->length[level];
>> +        }
>> +        offset = 0;
>> +    } else if (!s->got_profile && !s->need_profile) {
>> +        for (int level = 0; level <= s->levels; level++)
>> +            profile[level] = s->sigma / 1000000.;
>> +    }
>> +
>> +    for (int level = 0; level <= s->levels; level++) {
>> +        const double ak = 2.0 + ((4.0 * level) / s->levels);
>> +        const int length = s->length[level];
>> +
>> +        power_average(wtc + offset, power + offset, length);
>> +        noise_filter(power + offset, filter + offset, profile[level], ak,
>> length);
>> +        signal_filter(power + offset, signal + offset, profile[level],
>> ak, length);
>> +        denoise_level(wtc + offset, wtc + offset, filter + offset, signal
>> + offset, s->percent, length);
>> +        offset += length;
>> +    }
>> +
>> +    idwt_levels(s, s->levels, dst, temp0, wtc);
>> +
>> +    return 0;
>> +}
>> +
>> +static int filter_frame(AVFilterLink *inlink, AVFrame *in)
>> +{
>> +    AVFilterContext *ctx = inlink->dst;
>> +    AudioFWTDNContext *s = ctx->priv;
>> +    AVFilterLink *outlink = ctx->outputs[0];
>> +    ThreadData td;
>> +    AVFrame *out = NULL;
>> +
>> +    out = ff_get_audio_buffer(outlink, in->nb_samples);
>> +    if (!out) {
>> +        av_frame_free(&in);
>> +        return AVERROR(ENOMEM);
>> +    }
>> +    out->pts = in->pts;
>> +
>> +    td.in  = in;
>> +    td.out = out;
>> +    ctx->internal->execute(ctx, filter_channel, &td, NULL,
>> inlink->channels);
>> +    if (s->need_profile)
>> +        s->got_profile = 1;
>> +
>> +    av_frame_free(&in);
>> +    return ff_filter_frame(outlink, out);
>> +}
>> +
>> +static int activate(AVFilterContext *ctx)
>> +{
>> +    AVFilterLink *inlink = ctx->inputs[0];
>> +    AVFilterLink *outlink = ctx->outputs[0];
>> +    AudioFWTDNContext *s = ctx->priv;
>> +    AVFrame *in = NULL;
>> +    int ret;
>> +
>> +    FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink);
>> +
>> +    ret = ff_inlink_consume_samples(inlink, s->nb_samples, s->nb_samples,
>> &in);
>> +    if (ret < 0)
>> +        return ret;
>> +    if (ret > 0)
>> +        return filter_frame(inlink, in);
>> +
>> +    FF_FILTER_FORWARD_STATUS(inlink, outlink);
>> +    FF_FILTER_FORWARD_WANTED(outlink, inlink);
>> +
>> +    return FFERROR_NOT_READY;
>> +}
>> +
>> +static av_cold void uninit(AVFilterContext *ctx)
>> +{
>> +    AudioFWTDNContext *s = ctx->priv;
>> +
>> +    av_frame_free(&s->filter);
>> +    av_frame_free(&s->power);
>> +    av_frame_free(&s->profile);
>> +    av_frame_free(&s->signal);
>> +    av_frame_free(&s->temp0);
>> +    av_frame_free(&s->temp1);
>> +    av_frame_free(&s->wtc);
>> +}
>> +
>> +static const AVFilterPad inputs[] = {
>> +    {
>> +        .name         = "default",
>> +        .type         = AVMEDIA_TYPE_AUDIO,
>> +    },
>> +    { NULL }
>> +};
>> +
>> +static const AVFilterPad outputs[] = {
>> +    {
>> +        .name          = "default",
>> +        .type          = AVMEDIA_TYPE_AUDIO,
>> +        .config_props  = config_output,
>> +    },
>> +    { NULL }
>> +};
>> +
>> +AVFilter ff_af_afwtdn = {
>> +    .name          = "afwtdn",
>> +    .description   = NULL_IF_CONFIG_SMALL("Reduce broadband noise from
>> stream using Fast Wavelet Transform."),
>> +    .query_formats = query_formats,
>> +    .priv_size     = sizeof(AudioFWTDNContext),
>> +    .priv_class    = &afwtdn_class,
>> +    .activate      = activate,
>> +    .uninit        = uninit,
>> +    .inputs        = inputs,
>> +    .outputs       = outputs,
>> +    .flags         = AVFILTER_FLAG_SLICE_THREADS,
>> +};
>> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
>> index 1183e40267..de5884529c 100644
>> --- a/libavfilter/allfilters.c
>> +++ b/libavfilter/allfilters.c
>> @@ -43,6 +43,7 @@ extern AVFilter ff_af_afftdn;
>>  extern AVFilter ff_af_afftfilt;
>>  extern AVFilter ff_af_afir;
>>  extern AVFilter ff_af_aformat;
>> +extern AVFilter ff_af_afwtdn;
>>  extern AVFilter ff_af_agate;
>>  extern AVFilter ff_af_aiir;
>>  extern AVFilter ff_af_aintegral;
>
> --
>   Nicolas George
>
Nicolas George June 2, 2020, 1:57 p.m. UTC | #3
Paul B Mahol (12020-06-02):
> Your opinion is not technical one.

It pertains to usability.

> > Insufficient: users have no idea what it means.
> They need only to experiment, we can not teach them whole wavelet
> theory in 5 minutes.

You need to explain the practical facts, not the theory.

> frames stands for _frame_   _s_ize.

Not good, needs to be fixed.

> Seriously there are other wavelet non-audio filters in libavfilter or
> even libavcodec already, and they do not show from where numbers come.

They should.
Paul B Mahol June 2, 2020, 2:15 p.m. UTC | #4
Hi,

On 6/2/20, Nicolas George <george@nsup.org> wrote:
> Paul B Mahol (12020-06-02):
>> Your opinion is not technical one.
>
> It pertains to usability.

Filter is already very usable.

>
>> > Insufficient: users have no idea what it means.
>> They need only to experiment, we can not teach them whole wavelet
>> theory in 5 minutes.
>
> You need to explain the practical facts, not the theory.

Practical facts is that they have different length, thus slower processing.

>
>> frames stands for _frame_   _s_ize.
>
> Not good, needs to be fixed.

What you propose as an alternative ?

>
>> Seriously there are other wavelet non-audio filters in libavfilter or
>> even libavcodec already, and they do not show from where numbers come.
>
> They should.

They can not put math theory into source code.
Nicolas George June 2, 2020, 2:18 p.m. UTC | #5
Paul B Mahol (12020-06-02):
<snip>

I have nothing to add to what I have already written. This filter is
rejected until the problem I have pointed are resolved: better name,
better doc, better options name, comments on the magic numbers.

Regards,
James Almer June 2, 2020, 2:40 p.m. UTC | #6
On 6/2/2020 11:18 AM, Nicolas George wrote:
> Paul B Mahol (12020-06-02):
> <snip>
> 
> I have nothing to add to what I have already written. This filter is
> rejected until the problem I have pointed are resolved: better name,
> better doc, better options name, comments on the magic numbers.

Suggesting a name for both the filter and the options would help quite a
bit. It would for starters prevent him from coming up with new ones that
you may also end up not liking.
Nicolas George June 2, 2020, 2:42 p.m. UTC | #7
James Almer (12020-06-02):
> Suggesting a name for both the filter and the options would help quite a
> bit. It would for starters prevent him from coming up with new ones that
> you may also end up not liking.

For the option, the correct name is obvious: frame_size.

For the filter, I would need to know what this filter does, what benefit
it brings to the user. Let me see a user-oriented doc, and I can suggest
a name. But the user-oriented doc is missing too.

Regards,
Paul B Mahol June 2, 2020, 2:55 p.m. UTC | #8
On 6/2/20, Nicolas George <george@nsup.org> wrote:
> James Almer (12020-06-02):
>> Suggesting a name for both the filter and the options would help quite a
>> bit. It would for starters prevent him from coming up with new ones that
>> you may also end up not liking.
>
> For the option, the correct name is obvious: frame_size.
>
> For the filter, I would need to know what this filter does, what benefit
> it brings to the user. Let me see a user-oriented doc, and I can suggest
> a name. But the user-oriented doc is missing too.
>

It is clear that your reasoning is heavily biased also looks like
you have personal vendetta against me.

You have not vote after all any more, so your comments are hereby ignored.

You are so ignorant being thus I will from now on ignore all your comments.
diff mbox series

Patch

diff --git a/doc/filters.texi b/doc/filters.texi
index a0b4ab2228..15a8636398 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -1314,6 +1314,47 @@  Force the output to either unsigned 8-bit or signed 16-bit stereo
 aformat=sample_fmts=u8|s16:channel_layouts=stereo
 @end example
 
+@section afwtdn
+Reduce broadband noise from input samples using Fast Wavelet Transform.
+
+A description of the accepted options follows.
+
+@table @option
+@item sigma
+Set the noise sigma, allowed range is from 0 to 100.
+Default value is 0.
+This option controls strength of denoising applied to input samples.
+
+@item levels
+Set the number of wavelet levels of decomposition.
+Allowed range is from 1 to 19.
+Default value is 10.
+Setting this too low make denoising performance very poor.
+
+@item wavet
+Set wavelet type for decomposition of input frame.
+Available wavelets are:
+
+@table @samp
+@item deb4
+@item deb10
+@item coif5
+@item rbior68
+@end table
+
+@item percent
+Set percent of full denoising. Allowed range is from 0 to 100 percent.
+Default value is 85 percent or partial denoising.
+
+@item profile
+If set enabled, first input frame will be used as noise profile.
+If first frame samples contain non-noise performance will be very poor.
+
+@item frames
+Set size of single frame in number of samples. Allowed range is from 1024 to
+262144. Default frame size is 8192 samples.
+@end table
+
 @section agate
 
 A gate is mainly used to reduce lower parts of a signal. This kind of signal
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 5123540653..191826a622 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -50,6 +50,7 @@  OBJS-$(CONFIG_AFFTDN_FILTER)                 += af_afftdn.o
 OBJS-$(CONFIG_AFFTFILT_FILTER)               += af_afftfilt.o
 OBJS-$(CONFIG_AFIR_FILTER)                   += af_afir.o
 OBJS-$(CONFIG_AFORMAT_FILTER)                += af_aformat.o
+OBJS-$(CONFIG_AFWTDN_FILTER)                 += af_afwtdn.o
 OBJS-$(CONFIG_AGATE_FILTER)                  += af_agate.o
 OBJS-$(CONFIG_AIIR_FILTER)                   += af_aiir.o
 OBJS-$(CONFIG_AINTEGRAL_FILTER)              += af_aderivative.o
diff --git a/libavfilter/af_afwtdn.c b/libavfilter/af_afwtdn.c
new file mode 100644
index 0000000000..2d51c7bf2d
--- /dev/null
+++ b/libavfilter/af_afwtdn.c
@@ -0,0 +1,653 @@ 
+/*
+ * Copyright (c) 2020 Paul B Mahol
+ *
+ * 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
+ */
+
+#include <float.h>
+
+#include "libavutil/avassert.h"
+#include "libavutil/audio_fifo.h"
+#include "libavutil/avstring.h"
+#include "libavutil/opt.h"
+#include "avfilter.h"
+#include "audio.h"
+#include "filters.h"
+#include "formats.h"
+
+static const double rbior68_lp[18] = {
+    0.0, 0.0, 0.0, 0.0,
+    0.014426282505624435, 0.014467504896790148,
+    -0.07872200106262882, -0.04036797903033992,
+    0.41784910915027457, 0.7589077294536541,
+    0.41784910915027457, -0.04036797903033992,
+    -0.07872200106262882, 0.014467504896790148,
+    0.014426282505624435, 0.0, 0.0, 0.0,
+};
+
+static const double rbior68_hp[18] = {
+    -0.0019088317364812906, -0.0019142861290887667,
+    0.016990639867602342, 0.01193456527972926,
+    -0.04973290349094079, -0.07726317316720414,
+    0.09405920349573646, 0.4207962846098268,
+    -0.8259229974584023, 0.4207962846098268,
+    0.09405920349573646, -0.07726317316720414,
+    -0.04973290349094079, 0.01193456527972926,
+    0.016990639867602342, -0.0019142861290887667,
+    -0.0019088317364812906, 0.0,
+};
+
+static const double rbior68_ilp[18] = {
+    0.0019088317364812906, -0.0019142861290887667,
+    -0.016990639867602342, 0.01193456527972926,
+    0.04973290349094079, -0.07726317316720414,
+    -0.09405920349573646, 0.4207962846098268,
+    0.8259229974584023, 0.4207962846098268,
+    -0.09405920349573646, -0.07726317316720414,
+    0.04973290349094079, 0.01193456527972926,
+    -0.016990639867602342, -0.0019142861290887667,
+    0.0019088317364812906, 0.0,
+};
+
+static const double rbior68_ihp[18] = {
+    0.0, 0.0, 0.0, 0.0,
+    0.014426282505624435, -0.014467504896790148,
+    -0.07872200106262882, 0.04036797903033992,
+    0.41784910915027457, -0.7589077294536541,
+    0.41784910915027457, 0.04036797903033992,
+    -0.07872200106262882, -0.014467504896790148,
+    0.014426282505624435, 0.0, 0.0, 0.0,
+};
+
+static const double coif5_lp[30] = {
+    -9.517657273819165e-08, -1.6744288576823017e-07,
+    2.0637618513646814e-06, 3.7346551751414047e-06,
+    -2.1315026809955787e-05, -4.134043227251251e-05,
+    0.00014054114970203437, 0.00030225958181306315,
+    -0.0006381313430451114, -0.0016628637020130838,
+    0.0024333732126576722, 0.006764185448053083,
+    -0.009164231162481846, -0.01976177894257264,
+    0.03268357426711183, 0.0412892087501817,
+    -0.10557420870333893, -0.06203596396290357,
+    0.4379916261718371, 0.7742896036529562,
+    0.4215662066908515, -0.05204316317624377,
+    -0.09192001055969624, 0.02816802897093635,
+    0.023408156785839195, -0.010131117519849788,
+    -0.004159358781386048, 0.0021782363581090178,
+    0.00035858968789573785, -0.00021208083980379827,
+};
+
+static const double coif5_hp[30] = {
+    0.00021208083980379827, 0.00035858968789573785,
+    -0.0021782363581090178, -0.004159358781386048,
+    0.010131117519849788, 0.023408156785839195,
+    -0.02816802897093635, -0.09192001055969624,
+    0.05204316317624377, 0.4215662066908515,
+    -0.7742896036529562, 0.4379916261718371,
+    0.06203596396290357, -0.10557420870333893,
+    -0.0412892087501817, 0.03268357426711183,
+    0.01976177894257264, -0.009164231162481846,
+    -0.006764185448053083, 0.0024333732126576722,
+    0.0016628637020130838, -0.0006381313430451114,
+    -0.00030225958181306315, 0.00014054114970203437,
+    4.134043227251251e-05, -2.1315026809955787e-05,
+    -3.7346551751414047e-06, 2.0637618513646814e-06,
+    1.6744288576823017e-07, -9.517657273819165e-08,
+};
+
+static const double coif5_ilp[30] = {
+    -0.00021208083980379827, 0.00035858968789573785,
+    0.0021782363581090178, -0.004159358781386048,
+    -0.010131117519849788, 0.023408156785839195,
+    0.02816802897093635, -0.09192001055969624,
+    -0.05204316317624377, 0.4215662066908515,
+    0.7742896036529562, 0.4379916261718371,
+    -0.06203596396290357, -0.10557420870333893,
+    0.0412892087501817, 0.03268357426711183,
+    -0.01976177894257264, -0.009164231162481846,
+    0.006764185448053083, 0.0024333732126576722,
+    -0.0016628637020130838, -0.0006381313430451114,
+    0.00030225958181306315, 0.00014054114970203437,
+    -4.134043227251251e-05, -2.1315026809955787e-05,
+    3.7346551751414047e-06, 2.0637618513646814e-06,
+    -1.6744288576823017e-07, -9.517657273819165e-08,
+};
+
+static const double coif5_ihp[30] = {
+    -9.517657273819165e-08, 1.6744288576823017e-07,
+    2.0637618513646814e-06, -3.7346551751414047e-06,
+    -2.1315026809955787e-05, 4.134043227251251e-05,
+    0.00014054114970203437, -0.00030225958181306315,
+    -0.0006381313430451114, 0.0016628637020130838,
+    0.0024333732126576722, -0.006764185448053083,
+    -0.009164231162481846, 0.01976177894257264,
+    0.03268357426711183, -0.0412892087501817,
+    -0.10557420870333893, 0.06203596396290357,
+    0.4379916261718371, -0.7742896036529562,
+    0.4215662066908515, 0.05204316317624377,
+    -0.09192001055969624, -0.02816802897093635,
+    0.023408156785839195, 0.010131117519849788,
+    -0.004159358781386048, -0.0021782363581090178,
+    0.00035858968789573785, 0.00021208083980379827,
+};
+
+static const double deb10_lp[20] = {
+    -1.326420300235487e-05, 9.358867000108985e-05,
+    -0.0001164668549943862, -0.0006858566950046825,
+    0.00199240529499085, 0.0013953517469940798,
+    -0.010733175482979604, 0.0036065535669883944,
+    0.03321267405893324, -0.02945753682194567,
+    -0.07139414716586077, 0.09305736460380659,
+    0.12736934033574265, -0.19594627437659665,
+    -0.24984642432648865, 0.2811723436604265,
+    0.6884590394525921, 0.5272011889309198,
+    0.18817680007762133, 0.026670057900950818,
+};
+
+static const double deb10_hp[20] = {
+    -0.026670057900950818, 0.18817680007762133,
+    -0.5272011889309198, 0.6884590394525921,
+    -0.2811723436604265, -0.24984642432648865,
+    0.19594627437659665, 0.12736934033574265,
+    -0.09305736460380659, -0.07139414716586077,
+    0.02945753682194567, 0.03321267405893324,
+    -0.0036065535669883944, -0.010733175482979604,
+    -0.0013953517469940798, 0.00199240529499085,
+    0.0006858566950046825, -0.0001164668549943862,
+    -9.358867000108985e-05, -1.326420300235487e-05,
+};
+
+static const double deb10_ilp[20] = {
+    0.026670057900950818, 0.18817680007762133,
+    0.5272011889309198, 0.6884590394525921,
+    0.2811723436604265, -0.24984642432648865,
+    -0.19594627437659665, 0.12736934033574265,
+    0.09305736460380659, -0.07139414716586077,
+    -0.02945753682194567, 0.03321267405893324,
+    0.0036065535669883944, -0.010733175482979604,
+    0.0013953517469940798, 0.00199240529499085,
+    -0.0006858566950046825, -0.0001164668549943862,
+    9.358867000108985e-05, -1.326420300235487e-05,
+};
+
+static const double deb10_ihp[20] = {
+    -1.326420300235487e-05, -9.358867000108985e-05,
+    -0.0001164668549943862, 0.0006858566950046825,
+    0.00199240529499085, -0.0013953517469940798,
+    -0.010733175482979604, -0.0036065535669883944,
+    0.03321267405893324, 0.02945753682194567,
+    -0.07139414716586077, -0.09305736460380659,
+    0.12736934033574265, 0.19594627437659665,
+    -0.24984642432648865, -0.2811723436604265,
+    0.6884590394525921, -0.5272011889309198,
+    0.18817680007762133, -0.026670057900950818,
+};
+
+static const double deb4_lp[8] = {
+    -0.0105974018, 0.0328830117,
+     0.0308413818, -0.1870348117,
+    -0.0279837694, 0.6308807679,
+     0.7148465706, 0.2303778133,
+};
+
+static const double deb4_hp[8] = {
+    -0.2303778133, 0.7148465706,
+    -0.6308807679, -0.0279837694,
+     0.1870348117, 0.0308413818,
+    -0.0328830117, -0.0105974018,
+};
+
+static const double deb4_ilp[8] = {
+     0.23037781330885523, 0.7148465705525415,
+     0.6308807679295904, -0.02798376941698385,
+    -0.18703481171888114, 0.030841381835986965,
+     0.032883011666982945, -0.010597401784997278,
+};
+
+static const double deb4_ihp[8] = {
+    -0.010597401784997278, -0.032883011666982945,
+     0.030841381835986965, 0.18703481171888114,
+    -0.02798376941698385, -0.6308807679295904,
+     0.7148465705525415, -0.23037781330885523,
+};
+
+#define MAX_LEVELS 20
+
+typedef struct AudioFWTDNContext {
+    const AVClass *class;
+
+    double sigma;
+    double percent;
+
+    int wavelet_type;
+    int thresholding_function;
+    int nb_samples;
+    int levels;
+    int wavelet_length;
+    int length[MAX_LEVELS];
+    int out_length;
+    int need_profile;
+    int got_profile;
+
+    const double *lp, *hp;
+    const double *ilp, *ihp;
+
+    AVFrame *temp0, *temp1;
+    AVFrame *wtc, *power, *profile, *filter, *signal;
+} AudioFWTDNContext;
+
+#define OFFSET(x) offsetof(AudioFWTDNContext, x)
+#define AF AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
+
+static const AVOption afwtdn_options[] = {
+    { "sigma", "set noise sigma", OFFSET(sigma), AV_OPT_TYPE_DOUBLE, {.dbl=0}, 0, 100, AF },
+    { "levels", "set number of wavelet levels", OFFSET(levels), AV_OPT_TYPE_INT, {.i64=10}, 1, MAX_LEVELS-1, AF },
+    { "wavet", "set wavelet type", OFFSET(wavelet_type), AV_OPT_TYPE_INT, {.i64=0}, 0, 3, AF, "wavet" },
+    { "deb4",  "deb4", 0, AV_OPT_TYPE_CONST,  {.i64=0}, 0, 0, AF, "wavet" },
+    { "deb10", "deb10", 0, AV_OPT_TYPE_CONST,  {.i64=1}, 0, 0, AF, "wavet" },
+    { "coif5", "coif5", 0, AV_OPT_TYPE_CONST,  {.i64=2}, 0, 0, AF, "wavet" },
+    { "rbior68", "rbior68", 0, AV_OPT_TYPE_CONST,  {.i64=3}, 0, 0, AF, "wavet" },
+    { "percent", "set percent of full denoising", OFFSET(percent),AV_OPT_TYPE_DOUBLE, {.dbl=85}, 0, 100, AF },
+    { "profile", "profile noise", OFFSET(need_profile), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, AF },
+    { "frames", "set frame size", OFFSET(nb_samples), AV_OPT_TYPE_INT, {.i64=8192}, 1024, 262144, AF },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(afwtdn);
+
+static int query_formats(AVFilterContext *ctx)
+{
+    AVFilterFormats *formats = NULL;
+    AVFilterChannelLayouts *layouts = NULL;
+    static const enum AVSampleFormat sample_fmts[] = {
+        AV_SAMPLE_FMT_DBLP,
+        AV_SAMPLE_FMT_NONE
+    };
+    int 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;
+
+    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_all_samplerates();
+    return ff_set_common_samplerates(ctx, formats);
+}
+
+static int config_output(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    AudioFWTDNContext *s = ctx->priv;
+    int N, i;
+
+    switch (s->wavelet_type) {
+    case 0:
+        s->wavelet_length = 8;
+        s->lp  = deb4_lp;
+        s->hp  = deb4_hp;
+        s->ilp = deb4_ilp;
+        s->ihp = deb4_ihp;
+        break;
+    case 1:
+        s->wavelet_length = 20;
+        s->lp  = deb10_lp;
+        s->hp  = deb10_hp;
+        s->ilp = deb10_ilp;
+        s->ihp = deb10_ihp;
+        break;
+    case 2:
+        s->wavelet_length = 30;
+        s->lp  = coif5_lp;
+        s->hp  = coif5_hp;
+        s->ilp = coif5_ilp;
+        s->ihp = coif5_ihp;
+        break;
+    case 3:
+        s->wavelet_length = 18;
+        s->lp  = rbior68_lp;
+        s->hp  = rbior68_hp;
+        s->ilp = rbior68_ilp;
+        s->ihp = rbior68_ihp;
+        break;
+    default:
+        av_assert0(0);
+    }
+
+    s->levels = FFMIN(s->levels, lrint(log(s->nb_samples / (s->wavelet_length - 1.0)) / M_LN2));
+    av_log(ctx, AV_LOG_VERBOSE, "levels:%d\n", s->levels);
+    N = s->nb_samples;
+    i = s->levels;
+
+    while (i > 0) {
+        N = N + s->wavelet_length - 2;
+        N = ceil(N / 2.0);
+        s->length[i] = N;
+        s->out_length += N;
+        i--;
+    }
+
+    s->length[0]  = s->length[1];
+    s->out_length += s->length[0];
+
+    s->temp0 = ff_get_audio_buffer(outlink, s->out_length);
+    s->temp1 = ff_get_audio_buffer(outlink, s->out_length);
+    s->wtc = ff_get_audio_buffer(outlink, s->out_length);
+    s->power = ff_get_audio_buffer(outlink, s->out_length);
+    s->filter = ff_get_audio_buffer(outlink, s->out_length);
+    s->signal = ff_get_audio_buffer(outlink, s->out_length);
+    s->profile = ff_get_audio_buffer(outlink, MAX_LEVELS);
+    if (!s->temp0 || !s->temp1 || !s->wtc ||
+        !s->power || !s->profile || !s->filter ||
+        !s->signal)
+        return AVERROR(ENOMEM);
+
+    return 0;
+}
+
+static void dwt(double *src, int N,
+                const double *lf, const double *hf, int flen,
+                double *low, double *high, int lhlen)
+{
+    for (int i = 0; i < lhlen; i++) {
+        const int t = 2 * i + 1;
+
+        low[i] = 0.0;
+        high[i] = 0.0;
+
+        for (int l = 0; l < flen; l++) {
+            if (t - l >= 0 && t - l < N) {
+                const int is = t - l;
+                low[i] += lf[l] * src[is];
+                high[i] += hf[l] * src[is];
+            } else if (t - l < 0) {
+                const int is = -t + l - 1;
+                low[i] += lf[l] * src[is];
+                high[i] += hf[l] * src[is];
+            } else if (t - l >= N) {
+                const int is = 2 * N - t + l - 1;
+                low[i] += lf[l] * src[is];
+                high[i] += hf[l] * src[is];
+            }
+        }
+    }
+}
+
+static void idwt(double *low, int lhlen, double *high,
+                 const double *lf, const double *hf,
+                 int flen, double *dst)
+{
+    int m = -2, n = -1;
+
+    for (int v = 0; v < lhlen; v++) {
+        m += 2;
+        n += 2;
+        dst[m] = 0.0;
+        dst[n] = 0.0;
+        for (int l = 0; l < flen / 2; l++) {
+            const int t = 2 * l;
+
+            if (v - l >= 0 && v - l < lhlen) {
+                const int is = v - l;
+
+                dst[m] += lf[t] * low[is] + hf[t] * high[is];
+                dst[n] += lf[t + 1] * low[is] + hf[t + 1] * high[is];
+            }
+        }
+    }
+}
+
+static void dwt_levels(AudioFWTDNContext *s, int levels, int inlength, int out_length,
+                       const double *in, double *temp0, double *temp1, double *wtc)
+{
+    int N = out_length;
+    int temp_len = s->nb_samples;
+
+    for (int i = 0; i < inlength; i++)
+        temp0[i] = in[i];
+    for (int i = inlength; i < s->nb_samples; i++)
+        temp0[i] = 0.;
+
+    for (int level = 0; level < levels; level++) {
+        const int level_length = s->length[levels - level];
+
+        N -= level_length;
+        dwt(temp0, temp_len, s->lp, s->hp, s->wavelet_length, temp1, wtc + N, level_length);
+        temp_len = s->length[levels - level];
+
+        if (level == levels - 1) {
+            for (int i = 0; i < level_length; i++)
+                wtc[i] = temp1[i];
+        } else {
+            for (int i = 0; i < level_length; i++)
+                temp0[i] = temp1[i];
+        }
+    }
+}
+
+static void idwt_levels(AudioFWTDNContext *s, int levels, double *out,
+                        double *temp, double *wtc)
+{
+    const int app_len = s->length[0];
+    const int lf = s->wavelet_length;
+    int iter = app_len;
+
+    for (int i = 0; i < app_len; i++)
+        out[i] = wtc[i];
+
+    for (int i = 0; i < levels; i++) {
+        const int det_len = s->length[i + 1];
+
+        idwt(out, det_len, wtc + iter, s->ilp, s->ihp, s->wavelet_length, temp);
+        for (int k = lf - 2; k < 2 * det_len; k++)
+            out[k - lf + 2] = temp[k];
+
+        iter += det_len;
+    }
+}
+
+static void denoise_level(double *out, const double *in, double *filter, double *signal, double percent, int length)
+{
+    const double x = percent * 0.01;
+    const double y = 1.0 - x;
+
+    for (int i = 0; i < length; i++)
+        out[i] = in[i] * (x * filter[i] * signal[i] + y);
+}
+
+static double sqr(double in)
+{
+    return in * in;
+}
+
+static void power_average(const double *in, double *out, int length)
+{
+    out[0] = sqr(in[0]);
+    for (int i = 1; i < length; i++)
+        out[i] = 0.1 * sqr(in[i]) + 0.9 * out[i - 1];
+}
+
+static void noise_filter(const double *in, double *out, double profile, double ak, int length)
+{
+    for (int i = 0; i < length; i++)
+        out[i] = 1.0 - profile * ak / in[i];
+}
+
+static void signal_filter(const double *in, double *out, double profile, double ak, int length)
+{
+    for (int i = 0; i < length; i++)
+        out[i] = in[i] >= profile * ak;
+}
+
+static void measure_level_profile(AVFilterContext *ctx, const double *wtc, double *profile, int length)
+{
+    double sum = 0.;
+
+    for (int i = 0; i < length; i++)
+        sum += sqr(wtc[i]);
+
+    *profile = sum / length;
+}
+
+typedef struct ThreadData {
+    AVFrame *in, *out;
+} ThreadData;
+
+static int filter_channel(AVFilterContext *ctx, void *arg, int ch, int nb_jobs)
+{
+    AudioFWTDNContext *s = ctx->priv;
+    ThreadData *td = arg;
+    AVFrame *in = td->in;
+    AVFrame *out = td->out;
+    const double *src = (const double *)(in->extended_data[ch]);
+    double *temp0 = (double *)s->temp0->extended_data[ch];
+    double *temp1 = (double *)s->temp1->extended_data[ch];
+    double *wtc = (double *)s->wtc->extended_data[ch];
+    double *dst = (double *)out->extended_data[ch];
+    double *profile = (double *)s->profile->extended_data[ch];
+    double *power = (double *)s->power->extended_data[ch];
+    double *filter = (double *)s->filter->extended_data[ch];
+    double *signal = (double *)s->signal->extended_data[ch];
+    int offset = 0;
+
+    dwt_levels(s, s->levels, in->nb_samples, s->out_length, src, temp0, temp1, wtc);
+
+    if (!s->got_profile && s->need_profile) {
+        for (int level = 0; level <= s->levels; level++) {
+            measure_level_profile(ctx, wtc + offset, &profile[level], s->length[level]);
+            offset += s->length[level];
+        }
+        offset = 0;
+    } else if (!s->got_profile && !s->need_profile) {
+        for (int level = 0; level <= s->levels; level++)
+            profile[level] = s->sigma / 1000000.;
+    }
+
+    for (int level = 0; level <= s->levels; level++) {
+        const double ak = 2.0 + ((4.0 * level) / s->levels);
+        const int length = s->length[level];
+
+        power_average(wtc + offset, power + offset, length);
+        noise_filter(power + offset, filter + offset, profile[level], ak, length);
+        signal_filter(power + offset, signal + offset, profile[level], ak, length);
+        denoise_level(wtc + offset, wtc + offset, filter + offset, signal + offset, s->percent, length);
+        offset += length;
+    }
+
+    idwt_levels(s, s->levels, dst, temp0, wtc);
+
+    return 0;
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
+{
+    AVFilterContext *ctx = inlink->dst;
+    AudioFWTDNContext *s = ctx->priv;
+    AVFilterLink *outlink = ctx->outputs[0];
+    ThreadData td;
+    AVFrame *out = NULL;
+
+    out = ff_get_audio_buffer(outlink, in->nb_samples);
+    if (!out) {
+        av_frame_free(&in);
+        return AVERROR(ENOMEM);
+    }
+    out->pts = in->pts;
+
+    td.in  = in;
+    td.out = out;
+    ctx->internal->execute(ctx, filter_channel, &td, NULL, inlink->channels);
+    if (s->need_profile)
+        s->got_profile = 1;
+
+    av_frame_free(&in);
+    return ff_filter_frame(outlink, out);
+}
+
+static int activate(AVFilterContext *ctx)
+{
+    AVFilterLink *inlink = ctx->inputs[0];
+    AVFilterLink *outlink = ctx->outputs[0];
+    AudioFWTDNContext *s = ctx->priv;
+    AVFrame *in = NULL;
+    int ret;
+
+    FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink);
+
+    ret = ff_inlink_consume_samples(inlink, s->nb_samples, s->nb_samples, &in);
+    if (ret < 0)
+        return ret;
+    if (ret > 0)
+        return filter_frame(inlink, in);
+
+    FF_FILTER_FORWARD_STATUS(inlink, outlink);
+    FF_FILTER_FORWARD_WANTED(outlink, inlink);
+
+    return FFERROR_NOT_READY;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    AudioFWTDNContext *s = ctx->priv;
+
+    av_frame_free(&s->filter);
+    av_frame_free(&s->power);
+    av_frame_free(&s->profile);
+    av_frame_free(&s->signal);
+    av_frame_free(&s->temp0);
+    av_frame_free(&s->temp1);
+    av_frame_free(&s->wtc);
+}
+
+static const AVFilterPad inputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_AUDIO,
+    },
+    { NULL }
+};
+
+static const AVFilterPad outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_AUDIO,
+        .config_props  = config_output,
+    },
+    { NULL }
+};
+
+AVFilter ff_af_afwtdn = {
+    .name          = "afwtdn",
+    .description   = NULL_IF_CONFIG_SMALL("Reduce broadband noise from stream using Fast Wavelet Transform."),
+    .query_formats = query_formats,
+    .priv_size     = sizeof(AudioFWTDNContext),
+    .priv_class    = &afwtdn_class,
+    .activate      = activate,
+    .uninit        = uninit,
+    .inputs        = inputs,
+    .outputs       = outputs,
+    .flags         = AVFILTER_FLAG_SLICE_THREADS,
+};
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 1183e40267..de5884529c 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -43,6 +43,7 @@  extern AVFilter ff_af_afftdn;
 extern AVFilter ff_af_afftfilt;
 extern AVFilter ff_af_afir;
 extern AVFilter ff_af_aformat;
+extern AVFilter ff_af_afwtdn;
 extern AVFilter ff_af_agate;
 extern AVFilter ff_af_aiir;
 extern AVFilter ff_af_aintegral;