diff mbox series

[FFmpeg-devel] avfilter: add afwtdn filter

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

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 July 10, 2021, 7:20 p.m. UTC
Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 doc/filters.texi         |   60 ++
 libavfilter/Makefile     |    1 +
 libavfilter/af_afwtdn.c  | 1349 ++++++++++++++++++++++++++++++++++++++
 libavfilter/allfilters.c |    1 +
 4 files changed, 1411 insertions(+)
 create mode 100644 libavfilter/af_afwtdn.c

Comments

ffmpegandmahanstreamer@lolcow.email July 11, 2021, 12:36 p.m. UTC | #1
On 2021-07-10 15:20, Paul B Mahol wrote:
> Signed-off-by: Paul B Mahol <onemda@gmail.com>
> ---
>  doc/filters.texi         |   60 ++
>  libavfilter/Makefile     |    1 +
>  libavfilter/af_afwtdn.c  | 1349 ++++++++++++++++++++++++++++++++++++++
>  libavfilter/allfilters.c |    1 +
>  4 files changed, 1411 insertions(+)
>  create mode 100644 libavfilter/af_afwtdn.c
> 
> diff --git a/doc/filters.texi b/doc/filters.texi
> index d991c06628..8c91d49ced 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -1493,6 +1493,66 @@ Default value is 1.0.
> 
>  This filter supports the all above options as @ref{commands}.
> 
> +@section afwtdn
> +Reduce broadband noise from input samples using Wavelets.
> +
> +A description of the accepted options follows.
> +
> +@table @option
> +@item sigma
> +Set the noise sigma, allowed range is from 0 to 1.
> +Default value is 0.
> +This option controls strength of denoising applied to input samples.
> +Most useful way to set this option is via decibels, eg. -45dB.
> +
> +@item levels
> +Set the number of wavelet levels of decomposition.
> +Allowed range is from 1 to 12.
> +Default value is 10.
> +Setting this too low make denoising performance very poor.
> +
> +@item wavet
> +Set wavelet type for decomposition of input frame.
> +They are sorted by number of coefficients, from lowest to highest.
> +More coefficients means worse filtering speed, but overall better 
> quality.
> +Available wavelets are:
> +
> +@table @samp
> +@item sym2
> +@item sym4
> +@item rbior68
> +@item deb10
> +@item sym10
> +@item coif5
> +@item bl3
> +@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 enabled, first input frame will be used as noise profile.
> +If first frame samples contain non-noise performance will be very 
> poor.
> +
> +@item adaptive
> +If enabled, input frames are analyzed for presence of noise.
> +If noise is detected with high possibility then input frame profile 
> will be
> +used for processing following frames, until new noise frame is 
> detected.
> +
> +@item samples
> +Set size of single frame in number of samples. Allowed range is from 
> 512 to
> +65536. Default frame size is 8192 samples.
> +
> +@item softness
> +Set softness applied inside thresholding function. Allowed range is 
> from 0 to
> +10. Default softness is 1.
> +@end table
> +
> +@subsection Commands
> +
> +This filter supports the all above options as @ref{commands}.
> +
>  @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 62ee3d7b67..49c0c8342b 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -53,6 +53,7 @@ OBJS-$(CONFIG_AFFTFILT_FILTER)               += 
> af_afftfilt.o
>  OBJS-$(CONFIG_AFIR_FILTER)                   += af_afir.o
>  OBJS-$(CONFIG_AFORMAT_FILTER)                += af_aformat.o
>  OBJS-$(CONFIG_AFREQSHIFT_FILTER)             += af_afreqshift.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..16195776b4
> --- /dev/null
> +++ b/libavfilter/af_afwtdn.c
> @@ -0,0 +1,1349 @@
> +/*
> + * 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/avstring.h"
> +#include "libavutil/opt.h"
> +#include "avfilter.h"
> +#include "audio.h"
> +#include "filters.h"
> +#include "formats.h"
> +
> +enum WaveletTypes {
> +    SYM2,
> +    SYM4,
> +    RBIOR68,
> +    DEB10,
> +    SYM10,
> +    COIF5,
> +    BL3,
> +    NB_WAVELET_TYPES,
> +};
> +
> +/*
> + * All wavelets coefficients are taken from: 
> http://wavelets.pybytes.com/
> + */
> +
> +static const double bl3_lp[42] = {
> +    0.000146098, -0.000232304, -0.000285414, 0.000462093, 0.000559952,
> +    -0.000927187, -0.001103748, 0.00188212, 0.002186714, -0.003882426,
> +    -0.00435384, 0.008201477, 0.008685294, -0.017982291, -0.017176331,
> +    0.042068328, 0.032080869, -0.110036987, -0.050201753, 0.433923147,
> +    0.766130398, 0.433923147, -0.050201753, -0.110036987, 0.032080869,
> +    0.042068328, -0.017176331, -0.017982291, 0.008685294, 0.008201477,
> +    -0.00435384, -0.003882426, 0.002186714, 0.00188212, -0.001103748,
> +    -0.000927187, 0.000559952, 0.000462093, -0.000285414, 
> -0.000232304,
> +    0.000146098, 0.0,
> +};
> +
> +static const double bl3_hp[42] = {
> +    0.0, 0.000146098, 0.000232304, -0.000285414, -0.000462093, 
> 0.000559952,
> +    0.000927187, -0.001103748, -0.00188212, 0.002186714, 0.003882426,
> +    -0.00435384, -0.008201477, 0.008685294, 0.017982291, -0.017176331,
> +    -0.042068328, 0.032080869, 0.110036987, -0.050201753, 
> -0.433923147,
> +    0.766130398, -0.433923147, -0.050201753, 0.110036987, 0.032080869,
> +    -0.042068328, -0.017176331, 0.017982291, 0.008685294, 
> -0.008201477,
> +    -0.00435384, 0.003882426, 0.002186714, -0.00188212, -0.001103748,
> +    0.000927187, 0.000559952, -0.000462093, -0.000285414, 0.000232304,
> +    0.000146098,
> +};
> +
> +static const double bl3_ilp[42] = {
> +    0.0, 0.000146098, -0.000232304, -0.000285414, 0.000462093, 
> 0.000559952,
> +    -0.000927187, -0.001103748, 0.00188212, 0.002186714, -0.003882426,
> +    -0.00435384, 0.008201477, 0.008685294, -0.017982291, -0.017176331,
> +    0.042068328, 0.032080869, -0.110036987, -0.050201753, 0.433923147,
> +    0.766130398, 0.433923147, -0.050201753, -0.110036987, 0.032080869,
> +    0.042068328, -0.017176331, -0.017982291, 0.008685294, 0.008201477,
> +    -0.00435384, -0.003882426, 0.002186714, 0.00188212, -0.001103748,
> +    -0.000927187, 0.000559952, 0.000462093, -0.000285414, 
> -0.000232304,
> +    0.000146098,
> +};
> +
> +static const double bl3_ihp[42] = {
> +    0.000146098, 0.000232304, -0.000285414, -0.000462093, 0.000559952,
> +    0.000927187, -0.001103748, -0.00188212, 0.002186714, 0.003882426,
> +    -0.00435384, -0.008201477, 0.008685294, 0.017982291, -0.017176331,
> +    -0.042068328, 0.032080869, 0.110036987, -0.050201753, 
> -0.433923147,
> +    0.766130398, -0.433923147, -0.050201753, 0.110036987, 0.032080869,
> +    -0.042068328, -0.017176331, 0.017982291, 0.008685294, 
> -0.008201477,
> +    -0.00435384, 0.003882426, 0.002186714, -0.00188212, -0.001103748,
> +    0.000927187, 0.000559952, -0.000462093, -0.000285414, 0.000232304,
> +    0.000146098,
> +};
> +
> +static const double sym10_lp[20] = {
> +    0.0007701598091144901, 9.563267072289475e-05,
> +    -0.008641299277022422, -0.0014653825813050513,
> +    0.0459272392310922, 0.011609893903711381,
> +    -0.15949427888491757, -0.07088053578324385,
> +    0.47169066693843925, 0.7695100370211071,
> +    0.38382676106708546, -0.03553674047381755,
> +    -0.0319900568824278, 0.04999497207737669,
> +    0.005764912033581909, -0.02035493981231129,
> +    -0.0008043589320165449, 0.004593173585311828,
> +    5.7036083618494284e-05, -0.0004593294210046588,
> +};
> +
> +static const double sym10_hp[20] = {
> +    0.0004593294210046588, 5.7036083618494284e-05,
> +    -0.004593173585311828, -0.0008043589320165449,
> +    0.02035493981231129, 0.005764912033581909,
> +    -0.04999497207737669, -0.0319900568824278,
> +    0.03553674047381755, 0.38382676106708546,
> +    -0.7695100370211071, 0.47169066693843925,
> +    0.07088053578324385, -0.15949427888491757,
> +    -0.011609893903711381, 0.0459272392310922,
> +    0.0014653825813050513, -0.008641299277022422,
> +    -9.563267072289475e-05, 0.0007701598091144901,
> +};
> +
> +static const double sym10_ilp[20] = {
> +    -0.0004593294210046588, 5.7036083618494284e-05,
> +    0.004593173585311828, -0.0008043589320165449,
> +    -0.02035493981231129, 0.005764912033581909,
> +    0.04999497207737669, -0.0319900568824278,
> +    -0.03553674047381755, 0.38382676106708546,
> +    0.7695100370211071, 0.47169066693843925,
> +    -0.07088053578324385, -0.15949427888491757,
> +    0.011609893903711381, 0.0459272392310922,
> +    -0.0014653825813050513, -0.008641299277022422,
> +    9.563267072289475e-05, 0.0007701598091144901,
> +};
> +
> +static const double sym10_ihp[20] = {
> +    0.0007701598091144901, -9.563267072289475e-05,
> +    -0.008641299277022422, 0.0014653825813050513,
> +    0.0459272392310922, -0.011609893903711381,
> +    -0.15949427888491757, 0.07088053578324385,
> +    0.47169066693843925, -0.7695100370211071,
> +    0.38382676106708546, 0.03553674047381755,
> +    -0.0319900568824278, -0.04999497207737669,
> +    0.005764912033581909, 0.02035493981231129,
> +    -0.0008043589320165449, -0.004593173585311828,
> +    5.7036083618494284e-05, 0.0004593294210046588,
> +};
> +
> +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 sym4_lp[8] = {
> +    -0.07576571478927333,
> +    -0.02963552764599851,
> +    0.49761866763201545,
> +    0.8037387518059161,
> +    0.29785779560527736,
> +    -0.09921954357684722,
> +    -0.012603967262037833,
> +    0.0322231006040427,
> +};
> +
> +static const double sym4_hp[8] = {
> +    -0.0322231006040427,
> +    -0.012603967262037833,
> +    0.09921954357684722,
> +    0.29785779560527736,
> +    -0.8037387518059161,
> +    0.49761866763201545,
> +    0.02963552764599851,
> +    -0.07576571478927333,
> +};
> +
> +static const double sym4_ilp[8] = {
> +    0.0322231006040427,
> +    -0.012603967262037833,
> +    -0.09921954357684722,
> +    0.29785779560527736,
> +    0.8037387518059161,
> +    0.49761866763201545,
> +    -0.02963552764599851,
> +    -0.07576571478927333,
> +};
> +
> +static const double sym4_ihp[8] = {
> +    -0.07576571478927333,
> +    0.02963552764599851,
> +    0.49761866763201545,
> +    -0.8037387518059161,
> +    0.29785779560527736,
> +    0.09921954357684722,
> +    -0.012603967262037833,
> +    -0.0322231006040427,
> +};
> +
> +static const double sym2_lp[4] = {
> +    -0.12940952255092145, 0.22414386804185735,
> +    0.836516303737469, 0.48296291314469025,
> +};
> +
> +static const double sym2_hp[4] = {
> +    -0.48296291314469025, 0.836516303737469,
> +    -0.22414386804185735, -0.12940952255092145,
> +};
> +
> +static const double sym2_ilp[4] = {
> +    0.48296291314469025, 0.836516303737469,
> +    0.22414386804185735, -0.12940952255092145,
> +};
> +
> +static const double sym2_ihp[4] = {
> +    -0.12940952255092145, -0.22414386804185735,
> +    0.836516303737469, -0.48296291314469025,
> +};
> +
> +#define MAX_LEVELS 13
> +
> +typedef struct ChannelParams {
> +    int *output_length;
> +    int *filter_length;
> +    double **output_coefs;
> +    double **subbands_to_free;
> +    double **filter_coefs;
> +
> +    int tempa_length;
> +    int tempa_len_max;
> +    int temp_in_length;
> +    int temp_in_max_length;
> +    int buffer_length;
> +    int min_left_ext;
> +    int max_left_ext;
> +
> +    double *tempa;
> +    double *tempd;
> +    double *temp_in;
> +    double *buffer;
> +    double *buffer2;
> +    double *prev;
> +    double *overlap;
> +} ChannelParams;
> +
> +typedef struct AudioFWTDNContext {
> +    const AVClass *class;
> +
> +    double sigma;
> +    double percent;
> +    double softness;
> +
> +    uint64_t sn;
> +    int64_t eof_pts;
> +
> +    int wavelet_type;
> +    int channels;
> +    int nb_samples;
> +    int levels;
> +    int wavelet_length;
> +    int need_profile;
> +    int got_profile;
> +    int adaptive;
> +
> +    int delay;
> +    int drop_samples;
> +    int padd_samples;
> +    int overlap_length;
> +    int prev_length;
> +    ChannelParams *cp;
> +
> +    const double *lp, *hp;
> +    const double *ilp, *ihp;
> +
> +    AVFrame *stddev, *absmean, *filter;
> +    AVFrame *new_stddev, *new_absmean;
> +
> +    int (*filter_channel)(AVFilterContext *ctx, void *arg, int ch,
> int nb_jobs);
> +} AudioFWTDNContext;
> +
> +#define OFFSET(x) offsetof(AudioFWTDNContext, x)
> +#define AF AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
> +#define AFR
> AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
> +
> +static const AVOption afwtdn_options[] = {
> +    { "sigma", "set noise sigma", OFFSET(sigma), AV_OPT_TYPE_DOUBLE,
> {.dbl=0}, 0, 1, AFR },
> +    { "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=SYM10}, 0, NB_WAVELET_TYPES - 1, AF, "wavet" },
> +    { "sym2", "sym2", 0, AV_OPT_TYPE_CONST, {.i64=SYM2}, 0, 0, AF, 
> "wavet" },
> +    { "sym4", "sym4", 0, AV_OPT_TYPE_CONST, {.i64=SYM4}, 0, 0, AF, 
> "wavet" },
> +    { "rbior68", "rbior68", 0, AV_OPT_TYPE_CONST, {.i64=RBIOR68}, 0,
> 0, AF, "wavet" },
> +    { "deb10", "deb10", 0, AV_OPT_TYPE_CONST, {.i64=DEB10}, 0, 0, AF,
> "wavet" },
> +    { "sym10", "sym10", 0, AV_OPT_TYPE_CONST, {.i64=SYM10}, 0, 0, AF,
> "wavet" },
> +    { "coif5", "coif5", 0, AV_OPT_TYPE_CONST, {.i64=COIF5}, 0, 0, AF,
> "wavet" },
> +    { "bl3", "bl3", 0, AV_OPT_TYPE_CONST, {.i64=BL3}, 0, 0, AF, 
> "wavet" },
> +    { "percent", "set percent of full denoising",
> OFFSET(percent),AV_OPT_TYPE_DOUBLE, {.dbl=85}, 0, 100, AFR },
> +    { "profile", "profile noise", OFFSET(need_profile),
> AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, AFR },
> +    { "adaptive", "adaptive profiling of noise", OFFSET(adaptive),
> AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, AFR },
> +    { "samples", "set frame size in number of samples",
> OFFSET(nb_samples), AV_OPT_TYPE_INT, {.i64=8192}, 512, 65536, AF },
> +    { "softness", "set thresholding softness", OFFSET(softness),
> AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0, 10, AFR },
> +    { 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);
> +}
> +
> +#define pow2(x) (1U << (x))
> +#define mod_pow2(x, power_of_two) ((x) & ((power_of_two) - 1))
> +
> +static void conv_down(double *in, int in_length, double *low, double 
> *high,
> +                      int out_length, const double *lp, const double 
> *hp,
> +                      int wavelet_length, int skip,
> +                      double *buffer, int buffer_length)
> +{
> +    double thigh = 0.0, tlow = 0.0;
> +    int buff_idx = 1 + skip;
> +
> +    memcpy(buffer, in, buff_idx * sizeof(*buffer));
> +    memset(buffer + buff_idx, 0, (buffer_length - buff_idx) * 
> sizeof(*buffer));
> +
> +    for (int i = 0; i < out_length - 1; i++) {
> +        double thigh = 0.0, tlow = 0.0;
> +
> +        for (int j = 0; j < wavelet_length; j++) {
> +            const int idx = mod_pow2(-j + buff_idx - 1, 
> buffer_length);
> +            const double btemp = buffer[idx];
> +
> +            thigh += btemp * hp[j];
> +            tlow += btemp * lp[j];
> +        }
> +
> +        high[i] = thigh;
> +        low[i] = tlow;
> +        buffer[buff_idx++] = in[2 * i + 1 + skip];
> +        buffer[buff_idx++] = in[2 * i + 2 + skip];
> +        buff_idx = mod_pow2(buff_idx, buffer_length);
> +    }
> +
> +    for (int i = 0; i < wavelet_length; i++) {
> +        const int idx = mod_pow2(-i + buff_idx - 1, buffer_length);
> +        const double btemp = buffer[idx];
> +
> +        thigh += btemp * hp[i];
> +        tlow += btemp * lp[i];
> +    }
> +
> +    high[out_length - 1] = thigh;
> +    low[out_length - 1] = tlow;
> +}
> +
> +static int left_ext(int wavelet_length, int levels, uint64_t sn)
> +{
> +    if (!sn)
> +        return 0;
> +    return (pow2(levels) - 1) * (wavelet_length - 2) + mod_pow2(sn,
> pow2(levels));
> +}
> +
> +static int nb_coefs(int length, int level, uint64_t sn)
> +{
> +    const int pow2_level = pow2(level);
> +
> +    return (sn + length) / pow2_level - sn / pow2_level;
> +}
> +
> +static int reallocate_inputs(double **out, int *out_length,
> +                             int in_length, int levels, int ch, 
> uint64_t sn)
> +{
> +    const int temp_length = nb_coefs(in_length, levels, sn);
> +
> +    for (int level = 0; level < levels; level++) {
> +        const int temp_length = nb_coefs(in_length, level + 1, sn);
> +
> +        if (temp_length > out_length[level]) {
> +            av_freep(&out[level]);
> +            out_length[level] = 0;
> +
> +            out[level] = av_calloc(temp_length + 1, sizeof(**out));
> +            if (!out[level])
> +                return AVERROR(ENOMEM);
> +            out_length[level] = temp_length + 1;
> +        }
> +
> +        memset(out[level] + temp_length, 0,
> +               (out_length[level] - temp_length) * sizeof(**out));
> +        out_length[level] = temp_length;
> +    }
> +
> +    if (temp_length > out_length[levels]) {
> +        av_freep(&out[levels]);
> +        out_length[levels] = 0;
> +
> +        out[levels] = av_calloc(temp_length + 1, sizeof(**out));
> +        if (!out[levels])
> +            return AVERROR(ENOMEM);
> +        out_length[levels] = temp_length + 1;
> +    }
> +
> +    memset(out[levels] + temp_length, 0,
> +           (out_length[levels] - temp_length) * sizeof(**out));
> +    out_length[levels] = temp_length;
> +
> +    return 0;
> +}
> +
> +static int max_left_zeros_inverse(int levels, int level, int 
> wavelet_length)
> +{
> +    return (pow2(levels - level) - 1) * (wavelet_length - 1);
> +}
> +
> +static int reallocate_outputs(AudioFWTDNContext *s,
> +                              double **out, int *out_length,
> +                              int in_length, int levels, int ch, 
> uint64_t sn)
> +{
> +    ChannelParams *cp = &s->cp[ch];
> +    int temp_length = 0;
> +    int add = 0;
> +
> +    for (int level = 0; level < levels; level++) {
> +        temp_length = nb_coefs(in_length, level + 1, sn);
> +        if (temp_length > out_length[level]) {
> +            av_freep(&cp->subbands_to_free[level]);
> +            out_length[level] = 0;
> +
> +            add = max_left_zeros_inverse(levels, level + 1, 
> s->wavelet_length);
> +            cp->subbands_to_free[level] = av_calloc(add + temp_length
> + 1, sizeof(**out));
> +            if (!cp->subbands_to_free[level])
> +                return AVERROR(ENOMEM);
> +            out_length[level] = add + temp_length + 1;
> +            out[level] = cp->subbands_to_free[level] + add;
> +        }
> +
> +        memset(out[level] + temp_length, 0,
> +               FFMAX(out_length[level] - temp_length - add, 0) *
> sizeof(**out));
> +        out_length[level] = temp_length;
> +    }
> +
> +    temp_length = nb_coefs(in_length, levels, sn);
> +    if (temp_length > out_length[levels]) {
> +        av_freep(&cp->subbands_to_free[levels]);
> +        out_length[levels] = 0;
> +
> +        cp->subbands_to_free[levels] = av_calloc(temp_length + 1,
> sizeof(**out));
> +        if (!cp->subbands_to_free[levels])
> +            return AVERROR(ENOMEM);
> +        out_length[levels] = temp_length + 1;
> +        out[levels] = cp->subbands_to_free[levels];
> +    }
> +
> +    memset(out[levels] + temp_length, 0,
> +           (out_length[levels] - temp_length) * sizeof(**out));
> +    out_length[levels] = temp_length;
> +
> +    return 0;
> +}
> +
> +static int discard_left_ext(int wavelet_length, int levels, int
> level, uint64_t sn)
> +{
> +    if (levels == level || sn == 0)
> +        return 0;
> +    return (pow2(levels - level) - 1) * (wavelet_length - 2) +
> mod_pow2(sn, pow2(levels)) / pow2(level);
> +}
> +
> +static int forward(AudioFWTDNContext *s,
> +                   const double *in, int in_length,
> +                   double **out, int *out_length, int ch, uint64_t sn)
> +{
> +    ChannelParams *cp = &s->cp[ch];
> +    int levels = s->levels;
> +    int skip = sn ? s->wavelet_length - 1 : 1;
> +    int leftext, ret;
> +
> +    ret = reallocate_inputs(out, out_length, in_length, levels, ch, 
> sn);
> +    if (ret < 0)
> +        return ret;
> +    ret = reallocate_outputs(s, cp->filter_coefs, cp->filter_length,
> +                             in_length, levels, ch, sn);
> +    if (ret < 0)
> +        return ret;
> +
> +    leftext = left_ext(s->wavelet_length, levels, sn);
> +
> +    if (cp->temp_in_max_length < in_length + cp->max_left_ext + skip) 
> {
> +        av_freep(&cp->temp_in);
> +        cp->temp_in_max_length = in_length + cp->max_left_ext + skip;
> +        cp->temp_in = av_calloc(cp->temp_in_max_length, 
> sizeof(*cp->temp_in));
> +        if (!cp->temp_in) {
> +            cp->temp_in_max_length = 0;
> +            return AVERROR(ENOMEM);
> +        }
> +    }
> +
> +    memset(cp->temp_in, 0, cp->temp_in_max_length * 
> sizeof(*cp->temp_in));
> +    cp->temp_in_length = in_length + leftext;
> +
> +    if (leftext)
> +        memcpy(cp->temp_in, cp->prev + s->prev_length - leftext,
> leftext * sizeof(*cp->temp_in));
> +    memcpy(cp->temp_in + leftext, in, in_length * sizeof(*in));
> +
> +    if (levels == 1) {
> +        conv_down(cp->temp_in, cp->temp_in_length, out[1], out[0],
> out_length[1],
> +                 s->lp, s->hp, s->wavelet_length, skip,
> +                 cp->buffer, cp->buffer_length);
> +    } else {
> +        int discard = discard_left_ext(s->wavelet_length, levels, 1, 
> sn);
> +        int tempa_length_prev;
> +
> +        if (cp->tempa_len_max < (in_length + cp->max_left_ext +
> s->wavelet_length - 1) / 2) {
> +            av_freep(&cp->tempa);
> +            av_freep(&cp->tempd);
> +            cp->tempa_len_max = (in_length + cp->max_left_ext +
> s->wavelet_length - 1) / 2;
> +            cp->tempa = av_calloc(cp->tempa_len_max, 
> sizeof(*cp->tempa));
> +            cp->tempd = av_calloc(cp->tempa_len_max, 
> sizeof(*cp->tempd));
> +            if (!cp->tempa || !cp->tempd) {
> +                cp->tempa_len_max = 0;
> +                return AVERROR(ENOMEM);
> +            }
> +        }
> +
> +        memset(cp->tempa, 0, cp->tempa_len_max * sizeof(*cp->tempa));
> +        memset(cp->tempd, 0, cp->tempa_len_max * sizeof(*cp->tempd));
> +
> +        cp->tempa_length = out_length[0] + discard;
> +        conv_down(cp->temp_in, cp->temp_in_length,
> +                 cp->tempa, cp->tempd, cp->tempa_length,
> +                 s->lp, s->hp, s->wavelet_length, skip,
> +                 cp->buffer, cp->buffer_length);
> +        memcpy(out[0], cp->tempd + discard, out_length[0] * 
> sizeof(**out));
> +        tempa_length_prev = cp->tempa_length;
> +
> +        for (int level = 1; level < levels - 1; level++) {
> +            if (out_length[level] == 0)
> +                return 0;
> +            discard = discard_left_ext(s->wavelet_length, levels,
> level + 1, sn);
> +            cp->tempa_length = out_length[level] + discard;
> +            conv_down(cp->tempa, tempa_length_prev,
> +                     cp->tempa, cp->tempd, cp->tempa_length,
> +                     s->lp, s->hp, s->wavelet_length, skip,
> +                     cp->buffer, cp->buffer_length);
> +            memcpy(out[level], cp->tempd + discard, out_length[level]
> * sizeof(**out));
> +            tempa_length_prev = cp->tempa_length;
> +        }
> +
> +        if (out_length[levels] == 0)
> +            return 0;
> +        conv_down(cp->tempa, cp->tempa_length, out[levels],
> out[levels - 1], out_length[levels],
> +                 s->lp, s->hp, s->wavelet_length, skip,
> +                 cp->buffer, cp->buffer_length);
> +    }
> +
> +    if (s->prev_length < in_length) {
> +        memcpy(cp->prev, in + in_length - cp->max_left_ext,
> cp->max_left_ext * sizeof(*cp->prev));
> +    } else {
> +        memmove(cp->prev, cp->prev + in_length, (s->prev_length -
> in_length) * sizeof(*cp->prev));
> +        memcpy(cp->prev + s->prev_length - in_length, in, in_length *
> sizeof(*cp->prev));
> +    }
> +
> +    return 0;
> +}
> +
> +static void conv_up(double *low, double *high, int in_length, double
> *out, int out_length,
> +                    const double *lp, const double *hp, int 
> filter_length,
> +                    double *buffer, double *buffer2, int 
> buffer_length)
> +{
> +    int shift = 0, buff_idx = 0, in_idx = 0;
> +
> +    memset(buffer, 0, buffer_length * sizeof(*buffer));
> +    memset(buffer2, 0, buffer_length * sizeof(*buffer2));
> +
> +    for (int i = 0; i < out_length; i++) {
> +        double sum = 0.0;
> +
> +        if ((i & 1) == 0) {
> +            if (in_idx < in_length) {
> +                buffer[buff_idx] = low[in_idx];
> +                buffer2[buff_idx] = high[in_idx++];
> +            } else {
> +                buffer[buff_idx] = 0;
> +                buffer2[buff_idx] = 0;
> +            }
> +            buff_idx++;
> +            if (buff_idx >= buffer_length)
> +                buff_idx = 0;
> +            shift = 0;
> +        }
> +
> +        for (int j = 0; j < (filter_length - shift + 1) / 2; j++) {
> +            const int idx = mod_pow2(-j + buff_idx - 1, 
> buffer_length);
> +
> +            sum += buffer[idx] * lp[j * 2 + shift] + buffer2[idx] *
> hp[j * 2 + shift];
> +        }
> +        out[i] = sum;
> +        shift = 1;
> +    }
> +}
> +
> +static int append_left_ext(int wavelet_length, int levels, int level,
> uint64_t sn)
> +{
> +    if (levels == level)
> +        return 0;
> +
> +    return (pow2(levels - level) - 1) * (wavelet_length - 2) +
> +            mod_pow2(sn, pow2(levels)) / pow2(level);
> +}
> +
> +static int inverse(AudioFWTDNContext *s,
> +                   double **in, int *in_length,
> +                   double *out, int out_length, int ch, uint64_t sn)
> +{
> +    ChannelParams *cp = &s->cp[ch];
> +    const int levels = s->levels;
> +    int leftext = left_ext(s->wavelet_length, levels, sn);
> +    int temp_skip = 0;
> +
> +    if (sn == 0)
> +        temp_skip = cp->min_left_ext;
> +
> +    memset(out, 0, out_length * sizeof(*out));
> +
> +    if (cp->temp_in_max_length < out_length + cp->max_left_ext +
> s->wavelet_length - 1) {
> +        av_freep(&cp->temp_in);
> +        cp->temp_in_max_length = out_length + cp->max_left_ext +
> s->wavelet_length - 1;
> +        cp->temp_in = av_calloc(cp->temp_in_max_length, 
> sizeof(*cp->temp_in));
> +        if (!cp->temp_in) {
> +            cp->temp_in_max_length = 0;
> +            return AVERROR(ENOMEM);
> +        }
> +    }
> +
> +    memset(cp->temp_in, 0, cp->temp_in_max_length * 
> sizeof(*cp->temp_in));
> +    cp->temp_in_length = out_length + cp->max_left_ext;
> +
> +    if (levels == 1) {
> +        conv_up(in[1], in[0], in_length[1], cp->temp_in, 
> cp->temp_in_length,
> +                s->ilp, s->ihp, s->wavelet_length,
> +                cp->buffer, cp->buffer2, cp->buffer_length);
> +        memcpy(out + cp->max_left_ext - leftext, cp->temp_in + 
> temp_skip,
> +               FFMAX(0, out_length - (cp->max_left_ext - leftext)) *
> sizeof(*out));
> +    } else {
> +        double *hp1, *hp2;
> +        int add, add2;
> +
> +        if (cp->tempa_len_max < (out_length + cp->max_left_ext +
> s->wavelet_length - 1) / 2) {
> +            av_freep(&cp->tempa);
> +            cp->tempa_len_max = (out_length + cp->max_left_ext +
> s->wavelet_length - 1) / 2;
> +            cp->tempa = av_calloc(cp->tempa_len_max, 
> sizeof(*cp->tempa));
> +            if (!cp->tempa) {
> +                cp->tempa_len_max = 0;
> +                return AVERROR(ENOMEM);
> +            }
> +        }
> +
> +        memset(cp->tempa, 0, cp->tempa_len_max * sizeof(*cp->tempa));
> +
> +        hp1 = levels & 1 ? cp->temp_in : cp->tempa;
> +        hp2 = levels & 1 ? cp->tempa : cp->temp_in;
> +
> +        add = append_left_ext(s->wavelet_length, levels, levels - 1, 
> sn);
> +        conv_up(in[levels], in[levels - 1], in_length[levels], hp1,
> in_length[levels - 2] + add,
> +               s->ilp, s->ihp, s->wavelet_length, cp->buffer,
> cp->buffer2, cp->buffer_length);
> +
> +        for (int level = levels - 1; level > 1; level--) {
> +            add2 = append_left_ext(s->wavelet_length, levels, level - 
> 1, sn);
> +            add = append_left_ext(s->wavelet_length, levels, level, 
> sn);
> +            conv_up(hp1, in[level - 1] - add, in_length[level - 1] + 
> add,
> +                    hp2, in_length[level - 2] + add2,
> +                    s->ilp, s->ihp, s->wavelet_length,
> +                    cp->buffer, cp->buffer2, cp->buffer_length);
> +            FFSWAP(double *, hp1, hp2);
> +        }
> +
> +        add = append_left_ext(s->wavelet_length, levels, 1, sn);
> +        conv_up(hp1, in[0] - add, in_length[0] + add, cp->temp_in,
> cp->temp_in_length,
> +               s->ilp, s->ihp, s->wavelet_length,
> +               cp->buffer, cp->buffer2, cp->buffer_length);
> +    }
> +
> +    memset(cp->temp_in, 0, temp_skip * sizeof(*cp->temp_in));
> +    if (s->overlap_length <= out_length) {
> +        memcpy(out + cp->max_left_ext - leftext, cp->temp_in + 
> temp_skip,
> +               FFMAX(0, out_length - (cp->max_left_ext - leftext)) *
> sizeof(*out));
> +        for (int i = 0;i < FFMIN(s->overlap_length, out_length); i++)
> +            out[i] += cp->overlap[i];
> +
> +        memcpy(cp->overlap, cp->temp_in + out_length -
> (cp->max_left_ext - leftext),
> +               s->overlap_length * sizeof(*cp->overlap));
> +    } else {
> +        for (int i = 0;i < s->overlap_length - (cp->max_left_ext -
> leftext); i++)
> +            cp->overlap[i + cp->max_left_ext - leftext] += 
> cp->temp_in[i];
> +        memcpy(out, cp->overlap, out_length * sizeof(*out));
> +        memmove(cp->overlap, cp->overlap + out_length,
> +                (s->overlap_length - out_length) * 
> sizeof(*cp->overlap));
> +        memcpy(cp->overlap + s->overlap_length - out_length,
> cp->temp_in + leftext,
> +               out_length * sizeof(*cp->overlap));
> +    }
> +
> +    return 0;
> +}
> +
> +static int next_pow2(int in)
> +{
> +    return 1 << (av_log2(in) + 1);
> +}
> +
> +static void denoise_level(double *out, const double *in,
> +                          const double *filter,
> +                          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] = x * filter[i] + in[i] * y;
> +}
> +
> +static double sqr(double in)
> +{
> +    return in * in;
> +}
> +
> +static double measure_mean(const double *in, int length)
> +{
> +    double sum = 0.0;
> +
> +    for (int i = 0; i < length; i++)
> +        sum += in[i];
> +
> +    return sum / length;
> +}
> +
> +static double measure_absmean(const double *in, int length)
> +{
> +    double sum = 0.0;
> +
> +    for (int i = 0; i < length; i++)
> +        sum += fabs(in[i]);
> +
> +    return sum / length;
> +}
> +
> +static double measure_stddev(const double *in, int length, double 
> mean)
> +{
> +    double sum = 0.;
> +
> +    for (int i = 0; i < length; i++) {
> +        sum += sqr(in[i] - mean);
> +    }
> +
> +    return sqrt(sum / length);
> +}
> +
> +static void noise_filter(const double stddev, const double *in,
> +                         double *out, double absmean, double softness,
> +                         double new_stddev, int length)
> +{
> +    for (int i = 0; i < length; i++) {
> +        if (new_stddev <= stddev)
> +            out[i] = 0.0;
> +        else if (fabs(in[i]) <= absmean)
> +            out[i] = 0.0;
> +        else
> +            out[i] = in[i] - FFSIGN(in[i]) * absmean / exp(3.0 *
> softness * (fabs(in[i]) - absmean) / absmean);
> +    }
> +}
> +
> +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;
> +    ChannelParams *cp = &s->cp[ch];
> +    const double *src = (const double *)(in->extended_data[ch]);
> +    double *dst = (double *)out->extended_data[ch];
> +    double *absmean = (double *)s->absmean->extended_data[ch];
> +    double *new_absmean = (double *)s->new_absmean->extended_data[ch];
> +    double *stddev = (double *)s->stddev->extended_data[ch];
> +    double *new_stddev = (double *)s->new_stddev->extended_data[ch];
> +    double *filter = (double *)s->filter->extended_data[ch];
> +    double is_noise = 0.0;
> +    int ret;
> +
> +    ret = forward(s, src, in->nb_samples, cp->output_coefs,
> cp->output_length, ch, s->sn);
> +    if (ret < 0)
> +        return ret;
> +
> +    if (!s->got_profile && s->need_profile) {
> +        for (int level = 0; level <= s->levels; level++) {
> +            const int length = cp->output_length[level];
> +            const double scale = sqrt(2.0 * log(length));
> +
> +            stddev[level] = measure_stddev(cp->output_coefs[level], 
> length,
> +                            measure_mean(cp->output_coefs[level],
> length)) * scale;
> +            absmean[level] = measure_absmean(cp->output_coefs[level],
> length) * scale;
> +        }
> +    } else if (!s->got_profile && !s->need_profile && !s->adaptive) {
> +        for (int level = 0; level <= s->levels; level++) {
> +            const int length = cp->output_length[level];
> +            const double scale = sqrt(2.0 * log(length));
> +
> +            stddev[level] = 0.5 * s->sigma * scale;
> +            absmean[level] = 0.5 * s->sigma * scale;
> +        }
> +    }
> +
> +    for (int level = 0; level <= s->levels; level++) {
> +        const int length = cp->output_length[level];
> +        double vad;
> +
> +        new_stddev[level] = measure_stddev(cp->output_coefs[level], 
> length,
> +                            measure_mean(cp->output_coefs[level], 
> length));
> +        new_absmean[level] = measure_absmean(cp->output_coefs[level], 
> length);
> +        if (new_absmean[level] <= FLT_EPSILON)
> +            vad = 1.0;
> +        else
> +            vad = new_stddev[level] / new_absmean[level];
> +        if (level < s->levels)
> +            is_noise += sqr(vad - 1.232);
> +    }
> +
> +    is_noise *= in->sample_rate;
> +    is_noise /= s->nb_samples;
> +    for (int level = 0; level <= s->levels; level++) {
> +        const int length = cp->output_length[level];
> +        const double scale = sqrt(2.0 * log(length));
> +
> +        if (is_noise < 0.05 && s->adaptive) {
> +            stddev[level] = new_stddev[level] * scale;
> +            absmean[level] = new_absmean[level] * scale;
> +        }
> +
> +        noise_filter(stddev[level], cp->output_coefs[level], filter,
> absmean[level],
> +                     s->softness, new_stddev[level], length);
> +        denoise_level(cp->filter_coefs[level],
> cp->output_coefs[level], filter, s->percent, length);
> +    }
> +
> +    ret = inverse(s, cp->filter_coefs, cp->filter_length, dst,
> out->nb_samples, ch, s->sn);
> +    if (ret < 0)
> +        return ret;
> +
> +    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;
> +    int eof = in == NULL;
> +
> +    out = ff_get_audio_buffer(outlink, s->nb_samples);
> +    if (!out) {
> +        av_frame_free(&in);
> +        return AVERROR(ENOMEM);
> +    }
> +    if (in) {
> +        av_frame_copy_props(out, in);
> +        s->eof_pts = in->pts + in->nb_samples;
> +    }
> +    if (eof)
> +        out->pts = s->eof_pts - s->padd_samples;
> +
> +    if (!in || in->nb_samples < s->nb_samples) {
> +        AVFrame *new_in = ff_get_audio_buffer(outlink, s->nb_samples);
> +
> +        if (!new_in) {
> +            av_frame_free(&in);
> +            av_frame_free(&out);
> +            return AVERROR(ENOMEM);
> +        }
> +        if (in)
> +            av_frame_copy_props(new_in, in);
> +
> +        s->padd_samples -= s->nb_samples - (in ? in->nb_samples: 0);
> +        if (in)
> +            av_samples_copy(new_in->extended_data, in->extended_data, 
> 0, 0,
> +                            in->nb_samples, in->channels, in->format);
> +        av_frame_free(&in);
> +        in = new_in;
> +    }
> +
> +    td.in  = in;
> +    td.out = out;
> +    ctx->internal->execute(ctx, s->filter_channel, &td, NULL,
> inlink->channels);
> +    if (s->need_profile)
> +        s->got_profile = 1;
> +
> +    s->sn += s->nb_samples;
> +
> +    if (s->drop_samples >= in->nb_samples) {
> +        s->drop_samples -= in->nb_samples;
> +        s->delay += in->nb_samples;
> +        av_frame_free(&in);
> +        av_frame_free(&out);
> +        FF_FILTER_FORWARD_STATUS(inlink, outlink);
> +        FF_FILTER_FORWARD_WANTED(outlink, inlink);
> +        return 0;
> +    } else if (s->drop_samples > 0) {
> +        for (int ch = 0; ch < out->channels; ch++) {
> +            memmove(out->extended_data[ch],
> +                    out->extended_data[ch] + s->drop_samples * 
> sizeof(double),
> +                    (in->nb_samples - s->drop_samples) * 
> sizeof(double));
> +        }
> +
> +        out->nb_samples = in->nb_samples - s->drop_samples;
> +        out->pts = in->pts - av_rescale_q(s->delay, (AVRational){1,
> outlink->sample_rate}, outlink->time_base);
> +        s->delay += s->drop_samples;
> +        s->drop_samples = 0;
> +    } else {
> +        if (s->padd_samples < 0 && eof) {
> +            out->nb_samples += s->padd_samples;
> +            s->padd_samples = 0;
> +        }
> +        if (!eof)
> +            out->pts = in->pts - av_rescale_q(s->delay,
> (AVRational){1, outlink->sample_rate}, outlink->time_base);
> +    }
> +
> +    av_frame_free(&in);
> +    return ff_filter_frame(outlink, out);
> +}
> +
> +static int max_left_ext(int wavelet_length, int levels)
> +{
> +    return (pow2(levels) - 1) * (wavelet_length - 1);
> +}
> +
> +static int min_left_ext(int wavelet_length, int levels)
> +{
> +    return (pow2(levels) - 1) * (wavelet_length - 2);
> +}
> +
> +static int config_output(AVFilterLink *outlink)
> +{
> +    AVFilterContext *ctx = outlink->src;
> +    AudioFWTDNContext *s = ctx->priv;
> +
> +    switch (s->wavelet_type) {
> +    case SYM2:
> +        s->wavelet_length = 4;
> +        s->lp  = sym2_lp;
> +        s->hp  = sym2_hp;
> +        s->ilp = sym2_ilp;
> +        s->ihp = sym2_ihp;
> +        break;
> +    case SYM4:
> +        s->wavelet_length = 8;
> +        s->lp  = sym4_lp;
> +        s->hp  = sym4_hp;
> +        s->ilp = sym4_ilp;
> +        s->ihp = sym4_ihp;
> +        break;
> +    case RBIOR68:
> +        s->wavelet_length = 18;
> +        s->lp  = rbior68_lp;
> +        s->hp  = rbior68_hp;
> +        s->ilp = rbior68_ilp;
> +        s->ihp = rbior68_ihp;
> +        break;
> +    case DEB10:
> +        s->wavelet_length = 20;
> +        s->lp  = deb10_lp;
> +        s->hp  = deb10_hp;
> +        s->ilp = deb10_ilp;
> +        s->ihp = deb10_ihp;
> +    case SYM10:
> +        s->wavelet_length = 20;
> +        s->lp  = sym10_lp;
> +        s->hp  = sym10_hp;
> +        s->ilp = sym10_ilp;
> +        s->ihp = sym10_ihp;
> +        break;
> +    case COIF5:
> +        s->wavelet_length = 30;
> +        s->lp  = coif5_lp;
> +        s->hp  = coif5_hp;
> +        s->ilp = coif5_ilp;
> +        s->ihp = coif5_ihp;
> +        break;
> +    case BL3:
> +        s->wavelet_length = 42;
> +        s->lp  = bl3_lp;
> +        s->hp  = bl3_hp;
> +        s->ilp = bl3_ilp;
> +        s->ihp = bl3_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);
> +    s->filter_channel = filter_channel;
> +
> +    s->stddev = ff_get_audio_buffer(outlink, MAX_LEVELS);
> +    s->new_stddev = ff_get_audio_buffer(outlink, MAX_LEVELS);
> +    s->filter = ff_get_audio_buffer(outlink, s->nb_samples);
> +    s->absmean = ff_get_audio_buffer(outlink, MAX_LEVELS);
> +    s->new_absmean = ff_get_audio_buffer(outlink, MAX_LEVELS);
> +    if (!s->stddev || !s->absmean || !s->filter ||
> +        !s->new_stddev || !s->new_absmean)
> +        return AVERROR(ENOMEM);
> +
> +    s->channels = outlink->channels;
> +    s->overlap_length = max_left_ext(s->wavelet_length, s->levels);
> +    s->prev_length = s->overlap_length;
> +    s->drop_samples = s->overlap_length;
> +    s->padd_samples = s->overlap_length;
> +    s->sn = 1;
> +
> +    s->cp = av_calloc(s->channels, sizeof(*s->cp));
> +    if (!s->cp)
> +        return AVERROR(ENOMEM);
> +
> +    for (int ch = 0; ch < s->channels; ch++) {
> +        ChannelParams *cp = &s->cp[ch];
> +
> +        cp->output_coefs = av_calloc(s->levels + 1, 
> sizeof(*cp->output_coefs));
> +        cp->filter_coefs = av_calloc(s->levels + 1, 
> sizeof(*cp->filter_coefs));
> +        cp->output_length = av_calloc(s->levels + 1,
> sizeof(*cp->output_length));
> +        cp->filter_length = av_calloc(s->levels + 1,
> sizeof(*cp->filter_length));
> +        cp->buffer_length = next_pow2(s->wavelet_length);
> +        cp->buffer = av_calloc(cp->buffer_length, 
> sizeof(*cp->buffer));
> +        cp->buffer2 = av_calloc(cp->buffer_length, 
> sizeof(*cp->buffer2));
> +        cp->subbands_to_free = av_calloc(s->levels + 1,
> sizeof(*cp->subbands_to_free));
> +        cp->prev = av_calloc(s->prev_length, sizeof(*cp->prev));
> +        cp->overlap = av_calloc(s->overlap_length, 
> sizeof(*cp->overlap));
> +        cp->max_left_ext = max_left_ext(s->wavelet_length, s->levels);
> +        cp->min_left_ext = min_left_ext(s->wavelet_length, s->levels);
> +        if (!cp->output_coefs || !cp->filter_coefs || 
> !cp->output_length ||
> +            !cp->filter_length || !cp->subbands_to_free || !cp->prev
> || !cp->overlap ||
> +            !cp->buffer || !cp->buffer2)
> +            return AVERROR(ENOMEM);
> +    }
> +
> +    return 0;
> +}
> +
> +static int activate(AVFilterContext *ctx)
> +{
> +    AVFilterLink *inlink = ctx->inputs[0];
> +    AVFilterLink *outlink = ctx->outputs[0];
> +    AudioFWTDNContext *s = ctx->priv;
> +    AVFrame *in = NULL;
> +    int ret, status;
> +    int64_t pts;
> +
> +    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);
> +
> +    if (ff_inlink_acknowledge_status(inlink, &status, &pts)) {
> +        if (status == AVERROR_EOF) {
> +            while (s->padd_samples != 0) {
> +                ret = filter_frame(inlink, NULL);
> +                if (ret < 0)
> +                    return ret;
> +            }
> +            ff_outlink_set_status(outlink, status, pts);
> +            return ret;
> +        }
> +    }
> +    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->new_stddev);
> +    av_frame_free(&s->stddev);
> +    av_frame_free(&s->new_absmean);
> +    av_frame_free(&s->absmean);
> +
> +    for (int ch = 0; s->cp && ch < s->channels; ch++) {
> +        ChannelParams *cp = &s->cp[ch];
> +
> +        av_freep(&cp->tempa);
> +        av_freep(&cp->tempd);
> +        av_freep(&cp->temp_in);
> +        av_freep(&cp->buffer);
> +        av_freep(&cp->buffer2);
> +        av_freep(&cp->prev);
> +        av_freep(&cp->overlap);
> +
> +        av_freep(&cp->output_length);
> +        av_freep(&cp->filter_length);
> +
> +        if (cp->output_coefs) {
> +            for (int level = 0; level <= s->levels; level++)
> +                av_freep(&cp->output_coefs[level]);
> +        }
> +
> +        if (cp->subbands_to_free) {
> +            for (int level = 0; level <= s->levels; level++)
> +                av_freep(&cp->subbands_to_free[level]);
> +        }
> +
> +        av_freep(&cp->subbands_to_free);
> +        av_freep(&cp->output_coefs);
> +        av_freep(&cp->filter_coefs);
> +    }
> +
> +    av_freep(&s->cp);
> +}
> +
> +static int process_command(AVFilterContext *ctx, const char *cmd,
> const char *args,
> +                           char *res, int res_len, int flags)
> +{
> +    AudioFWTDNContext *s = ctx->priv;
> +    int ret;
> +
> +    ret = ff_filter_process_command(ctx, cmd, args, res, res_len, 
> flags);
> +    if (ret < 0)
> +        return ret;
> +
> +    if (!strcmp(cmd, "profile") && s->need_profile)
> +        s->got_profile = 0;
> +
> +    return 0;
> +}
> +
> +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("Denoise audio stream
> using Wavelets."),
> +    .query_formats   = query_formats,
> +    .priv_size       = sizeof(AudioFWTDNContext),
> +    .priv_class      = &afwtdn_class,
> +    .activate        = activate,
> +    .uninit          = uninit,
> +    .inputs          = inputs,
> +    .outputs         = outputs,
> +    .process_command = process_command,
> +    .flags           = AVFILTER_FLAG_SLICE_THREADS,
> +};
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index c6afef835f..ae74f9c891 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -46,6 +46,7 @@ extern const AVFilter ff_af_afftfilt;
>  extern const AVFilter ff_af_afir;
>  extern const AVFilter ff_af_aformat;
>  extern const AVFilter ff_af_afreqshift;
> +extern const AVFilter ff_af_afwtdn;
>  extern const AVFilter ff_af_agate;
>  extern const AVFilter ff_af_aiir;
>  extern const AVFilter ff_af_aintegral;

LGTM.

But maybe add a comment near "sn" variable as it meaning may not be 
immediately obvious to outsiders.
Paul B Mahol July 15, 2021, 5:57 p.m. UTC | #2
Will apply soon.
diff mbox series

Patch

diff --git a/doc/filters.texi b/doc/filters.texi
index d991c06628..8c91d49ced 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -1493,6 +1493,66 @@  Default value is 1.0.
 
 This filter supports the all above options as @ref{commands}.
 
+@section afwtdn
+Reduce broadband noise from input samples using Wavelets.
+
+A description of the accepted options follows.
+
+@table @option
+@item sigma
+Set the noise sigma, allowed range is from 0 to 1.
+Default value is 0.
+This option controls strength of denoising applied to input samples.
+Most useful way to set this option is via decibels, eg. -45dB.
+
+@item levels
+Set the number of wavelet levels of decomposition.
+Allowed range is from 1 to 12.
+Default value is 10.
+Setting this too low make denoising performance very poor.
+
+@item wavet
+Set wavelet type for decomposition of input frame.
+They are sorted by number of coefficients, from lowest to highest.
+More coefficients means worse filtering speed, but overall better quality.
+Available wavelets are:
+
+@table @samp
+@item sym2
+@item sym4
+@item rbior68
+@item deb10
+@item sym10
+@item coif5
+@item bl3
+@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 enabled, first input frame will be used as noise profile.
+If first frame samples contain non-noise performance will be very poor.
+
+@item adaptive
+If enabled, input frames are analyzed for presence of noise.
+If noise is detected with high possibility then input frame profile will be
+used for processing following frames, until new noise frame is detected.
+
+@item samples
+Set size of single frame in number of samples. Allowed range is from 512 to
+65536. Default frame size is 8192 samples.
+
+@item softness
+Set softness applied inside thresholding function. Allowed range is from 0 to
+10. Default softness is 1.
+@end table
+
+@subsection Commands
+
+This filter supports the all above options as @ref{commands}.
+
 @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 62ee3d7b67..49c0c8342b 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -53,6 +53,7 @@  OBJS-$(CONFIG_AFFTFILT_FILTER)               += af_afftfilt.o
 OBJS-$(CONFIG_AFIR_FILTER)                   += af_afir.o
 OBJS-$(CONFIG_AFORMAT_FILTER)                += af_aformat.o
 OBJS-$(CONFIG_AFREQSHIFT_FILTER)             += af_afreqshift.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..16195776b4
--- /dev/null
+++ b/libavfilter/af_afwtdn.c
@@ -0,0 +1,1349 @@ 
+/*
+ * 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/avstring.h"
+#include "libavutil/opt.h"
+#include "avfilter.h"
+#include "audio.h"
+#include "filters.h"
+#include "formats.h"
+
+enum WaveletTypes {
+    SYM2,
+    SYM4,
+    RBIOR68,
+    DEB10,
+    SYM10,
+    COIF5,
+    BL3,
+    NB_WAVELET_TYPES,
+};
+
+/*
+ * All wavelets coefficients are taken from: http://wavelets.pybytes.com/
+ */
+
+static const double bl3_lp[42] = {
+    0.000146098, -0.000232304, -0.000285414, 0.000462093, 0.000559952,
+    -0.000927187, -0.001103748, 0.00188212, 0.002186714, -0.003882426,
+    -0.00435384, 0.008201477, 0.008685294, -0.017982291, -0.017176331,
+    0.042068328, 0.032080869, -0.110036987, -0.050201753, 0.433923147,
+    0.766130398, 0.433923147, -0.050201753, -0.110036987, 0.032080869,
+    0.042068328, -0.017176331, -0.017982291, 0.008685294, 0.008201477,
+    -0.00435384, -0.003882426, 0.002186714, 0.00188212, -0.001103748,
+    -0.000927187, 0.000559952, 0.000462093, -0.000285414, -0.000232304,
+    0.000146098, 0.0,
+};
+
+static const double bl3_hp[42] = {
+    0.0, 0.000146098, 0.000232304, -0.000285414, -0.000462093, 0.000559952,
+    0.000927187, -0.001103748, -0.00188212, 0.002186714, 0.003882426,
+    -0.00435384, -0.008201477, 0.008685294, 0.017982291, -0.017176331,
+    -0.042068328, 0.032080869, 0.110036987, -0.050201753, -0.433923147,
+    0.766130398, -0.433923147, -0.050201753, 0.110036987, 0.032080869,
+    -0.042068328, -0.017176331, 0.017982291, 0.008685294, -0.008201477,
+    -0.00435384, 0.003882426, 0.002186714, -0.00188212, -0.001103748,
+    0.000927187, 0.000559952, -0.000462093, -0.000285414, 0.000232304,
+    0.000146098,
+};
+
+static const double bl3_ilp[42] = {
+    0.0, 0.000146098, -0.000232304, -0.000285414, 0.000462093, 0.000559952,
+    -0.000927187, -0.001103748, 0.00188212, 0.002186714, -0.003882426,
+    -0.00435384, 0.008201477, 0.008685294, -0.017982291, -0.017176331,
+    0.042068328, 0.032080869, -0.110036987, -0.050201753, 0.433923147,
+    0.766130398, 0.433923147, -0.050201753, -0.110036987, 0.032080869,
+    0.042068328, -0.017176331, -0.017982291, 0.008685294, 0.008201477,
+    -0.00435384, -0.003882426, 0.002186714, 0.00188212, -0.001103748,
+    -0.000927187, 0.000559952, 0.000462093, -0.000285414, -0.000232304,
+    0.000146098,
+};
+
+static const double bl3_ihp[42] = {
+    0.000146098, 0.000232304, -0.000285414, -0.000462093, 0.000559952,
+    0.000927187, -0.001103748, -0.00188212, 0.002186714, 0.003882426,
+    -0.00435384, -0.008201477, 0.008685294, 0.017982291, -0.017176331,
+    -0.042068328, 0.032080869, 0.110036987, -0.050201753, -0.433923147,
+    0.766130398, -0.433923147, -0.050201753, 0.110036987, 0.032080869,
+    -0.042068328, -0.017176331, 0.017982291, 0.008685294, -0.008201477,
+    -0.00435384, 0.003882426, 0.002186714, -0.00188212, -0.001103748,
+    0.000927187, 0.000559952, -0.000462093, -0.000285414, 0.000232304,
+    0.000146098,
+};
+
+static const double sym10_lp[20] = {
+    0.0007701598091144901, 9.563267072289475e-05,
+    -0.008641299277022422, -0.0014653825813050513,
+    0.0459272392310922, 0.011609893903711381,
+    -0.15949427888491757, -0.07088053578324385,
+    0.47169066693843925, 0.7695100370211071,
+    0.38382676106708546, -0.03553674047381755,
+    -0.0319900568824278, 0.04999497207737669,
+    0.005764912033581909, -0.02035493981231129,
+    -0.0008043589320165449, 0.004593173585311828,
+    5.7036083618494284e-05, -0.0004593294210046588,
+};
+
+static const double sym10_hp[20] = {
+    0.0004593294210046588, 5.7036083618494284e-05,
+    -0.004593173585311828, -0.0008043589320165449,
+    0.02035493981231129, 0.005764912033581909,
+    -0.04999497207737669, -0.0319900568824278,
+    0.03553674047381755, 0.38382676106708546,
+    -0.7695100370211071, 0.47169066693843925,
+    0.07088053578324385, -0.15949427888491757,
+    -0.011609893903711381, 0.0459272392310922,
+    0.0014653825813050513, -0.008641299277022422,
+    -9.563267072289475e-05, 0.0007701598091144901,
+};
+
+static const double sym10_ilp[20] = {
+    -0.0004593294210046588, 5.7036083618494284e-05,
+    0.004593173585311828, -0.0008043589320165449,
+    -0.02035493981231129, 0.005764912033581909,
+    0.04999497207737669, -0.0319900568824278,
+    -0.03553674047381755, 0.38382676106708546,
+    0.7695100370211071, 0.47169066693843925,
+    -0.07088053578324385, -0.15949427888491757,
+    0.011609893903711381, 0.0459272392310922,
+    -0.0014653825813050513, -0.008641299277022422,
+    9.563267072289475e-05, 0.0007701598091144901,
+};
+
+static const double sym10_ihp[20] = {
+    0.0007701598091144901, -9.563267072289475e-05,
+    -0.008641299277022422, 0.0014653825813050513,
+    0.0459272392310922, -0.011609893903711381,
+    -0.15949427888491757, 0.07088053578324385,
+    0.47169066693843925, -0.7695100370211071,
+    0.38382676106708546, 0.03553674047381755,
+    -0.0319900568824278, -0.04999497207737669,
+    0.005764912033581909, 0.02035493981231129,
+    -0.0008043589320165449, -0.004593173585311828,
+    5.7036083618494284e-05, 0.0004593294210046588,
+};
+
+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 sym4_lp[8] = {
+    -0.07576571478927333,
+    -0.02963552764599851,
+    0.49761866763201545,
+    0.8037387518059161,
+    0.29785779560527736,
+    -0.09921954357684722,
+    -0.012603967262037833,
+    0.0322231006040427,
+};
+
+static const double sym4_hp[8] = {
+    -0.0322231006040427,
+    -0.012603967262037833,
+    0.09921954357684722,
+    0.29785779560527736,
+    -0.8037387518059161,
+    0.49761866763201545,
+    0.02963552764599851,
+    -0.07576571478927333,
+};
+
+static const double sym4_ilp[8] = {
+    0.0322231006040427,
+    -0.012603967262037833,
+    -0.09921954357684722,
+    0.29785779560527736,
+    0.8037387518059161,
+    0.49761866763201545,
+    -0.02963552764599851,
+    -0.07576571478927333,
+};
+
+static const double sym4_ihp[8] = {
+    -0.07576571478927333,
+    0.02963552764599851,
+    0.49761866763201545,
+    -0.8037387518059161,
+    0.29785779560527736,
+    0.09921954357684722,
+    -0.012603967262037833,
+    -0.0322231006040427,
+};
+
+static const double sym2_lp[4] = {
+    -0.12940952255092145, 0.22414386804185735,
+    0.836516303737469, 0.48296291314469025,
+};
+
+static const double sym2_hp[4] = {
+    -0.48296291314469025, 0.836516303737469,
+    -0.22414386804185735, -0.12940952255092145,
+};
+
+static const double sym2_ilp[4] = {
+    0.48296291314469025, 0.836516303737469,
+    0.22414386804185735, -0.12940952255092145,
+};
+
+static const double sym2_ihp[4] = {
+    -0.12940952255092145, -0.22414386804185735,
+    0.836516303737469, -0.48296291314469025,
+};
+
+#define MAX_LEVELS 13
+
+typedef struct ChannelParams {
+    int *output_length;
+    int *filter_length;
+    double **output_coefs;
+    double **subbands_to_free;
+    double **filter_coefs;
+
+    int tempa_length;
+    int tempa_len_max;
+    int temp_in_length;
+    int temp_in_max_length;
+    int buffer_length;
+    int min_left_ext;
+    int max_left_ext;
+
+    double *tempa;
+    double *tempd;
+    double *temp_in;
+    double *buffer;
+    double *buffer2;
+    double *prev;
+    double *overlap;
+} ChannelParams;
+
+typedef struct AudioFWTDNContext {
+    const AVClass *class;
+
+    double sigma;
+    double percent;
+    double softness;
+
+    uint64_t sn;
+    int64_t eof_pts;
+
+    int wavelet_type;
+    int channels;
+    int nb_samples;
+    int levels;
+    int wavelet_length;
+    int need_profile;
+    int got_profile;
+    int adaptive;
+
+    int delay;
+    int drop_samples;
+    int padd_samples;
+    int overlap_length;
+    int prev_length;
+    ChannelParams *cp;
+
+    const double *lp, *hp;
+    const double *ilp, *ihp;
+
+    AVFrame *stddev, *absmean, *filter;
+    AVFrame *new_stddev, *new_absmean;
+
+    int (*filter_channel)(AVFilterContext *ctx, void *arg, int ch, int nb_jobs);
+} AudioFWTDNContext;
+
+#define OFFSET(x) offsetof(AudioFWTDNContext, x)
+#define AF AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
+#define AFR AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
+
+static const AVOption afwtdn_options[] = {
+    { "sigma", "set noise sigma", OFFSET(sigma), AV_OPT_TYPE_DOUBLE, {.dbl=0}, 0, 1, AFR },
+    { "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=SYM10}, 0, NB_WAVELET_TYPES - 1, AF, "wavet" },
+    { "sym2", "sym2", 0, AV_OPT_TYPE_CONST, {.i64=SYM2}, 0, 0, AF, "wavet" },
+    { "sym4", "sym4", 0, AV_OPT_TYPE_CONST, {.i64=SYM4}, 0, 0, AF, "wavet" },
+    { "rbior68", "rbior68", 0, AV_OPT_TYPE_CONST, {.i64=RBIOR68}, 0, 0, AF, "wavet" },
+    { "deb10", "deb10", 0, AV_OPT_TYPE_CONST, {.i64=DEB10}, 0, 0, AF, "wavet" },
+    { "sym10", "sym10", 0, AV_OPT_TYPE_CONST, {.i64=SYM10}, 0, 0, AF, "wavet" },
+    { "coif5", "coif5", 0, AV_OPT_TYPE_CONST, {.i64=COIF5}, 0, 0, AF, "wavet" },
+    { "bl3", "bl3", 0, AV_OPT_TYPE_CONST, {.i64=BL3}, 0, 0, AF, "wavet" },
+    { "percent", "set percent of full denoising", OFFSET(percent),AV_OPT_TYPE_DOUBLE, {.dbl=85}, 0, 100, AFR },
+    { "profile", "profile noise", OFFSET(need_profile), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, AFR },
+    { "adaptive", "adaptive profiling of noise", OFFSET(adaptive), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, AFR },
+    { "samples", "set frame size in number of samples", OFFSET(nb_samples), AV_OPT_TYPE_INT, {.i64=8192}, 512, 65536, AF },
+    { "softness", "set thresholding softness", OFFSET(softness), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0, 10, AFR },
+    { 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);
+}
+
+#define pow2(x) (1U << (x))
+#define mod_pow2(x, power_of_two) ((x) & ((power_of_two) - 1))
+
+static void conv_down(double *in, int in_length, double *low, double *high,
+                      int out_length, const double *lp, const double *hp,
+                      int wavelet_length, int skip,
+                      double *buffer, int buffer_length)
+{
+    double thigh = 0.0, tlow = 0.0;
+    int buff_idx = 1 + skip;
+
+    memcpy(buffer, in, buff_idx * sizeof(*buffer));
+    memset(buffer + buff_idx, 0, (buffer_length - buff_idx) * sizeof(*buffer));
+
+    for (int i = 0; i < out_length - 1; i++) {
+        double thigh = 0.0, tlow = 0.0;
+
+        for (int j = 0; j < wavelet_length; j++) {
+            const int idx = mod_pow2(-j + buff_idx - 1, buffer_length);
+            const double btemp = buffer[idx];
+
+            thigh += btemp * hp[j];
+            tlow += btemp * lp[j];
+        }
+
+        high[i] = thigh;
+        low[i] = tlow;
+        buffer[buff_idx++] = in[2 * i + 1 + skip];
+        buffer[buff_idx++] = in[2 * i + 2 + skip];
+        buff_idx = mod_pow2(buff_idx, buffer_length);
+    }
+
+    for (int i = 0; i < wavelet_length; i++) {
+        const int idx = mod_pow2(-i + buff_idx - 1, buffer_length);
+        const double btemp = buffer[idx];
+
+        thigh += btemp * hp[i];
+        tlow += btemp * lp[i];
+    }
+
+    high[out_length - 1] = thigh;
+    low[out_length - 1] = tlow;
+}
+
+static int left_ext(int wavelet_length, int levels, uint64_t sn)
+{
+    if (!sn)
+        return 0;
+    return (pow2(levels) - 1) * (wavelet_length - 2) + mod_pow2(sn, pow2(levels));
+}
+
+static int nb_coefs(int length, int level, uint64_t sn)
+{
+    const int pow2_level = pow2(level);
+
+    return (sn + length) / pow2_level - sn / pow2_level;
+}
+
+static int reallocate_inputs(double **out, int *out_length,
+                             int in_length, int levels, int ch, uint64_t sn)
+{
+    const int temp_length = nb_coefs(in_length, levels, sn);
+
+    for (int level = 0; level < levels; level++) {
+        const int temp_length = nb_coefs(in_length, level + 1, sn);
+
+        if (temp_length > out_length[level]) {
+            av_freep(&out[level]);
+            out_length[level] = 0;
+
+            out[level] = av_calloc(temp_length + 1, sizeof(**out));
+            if (!out[level])
+                return AVERROR(ENOMEM);
+            out_length[level] = temp_length + 1;
+        }
+
+        memset(out[level] + temp_length, 0,
+               (out_length[level] - temp_length) * sizeof(**out));
+        out_length[level] = temp_length;
+    }
+
+    if (temp_length > out_length[levels]) {
+        av_freep(&out[levels]);
+        out_length[levels] = 0;
+
+        out[levels] = av_calloc(temp_length + 1, sizeof(**out));
+        if (!out[levels])
+            return AVERROR(ENOMEM);
+        out_length[levels] = temp_length + 1;
+    }
+
+    memset(out[levels] + temp_length, 0,
+           (out_length[levels] - temp_length) * sizeof(**out));
+    out_length[levels] = temp_length;
+
+    return 0;
+}
+
+static int max_left_zeros_inverse(int levels, int level, int wavelet_length)
+{
+    return (pow2(levels - level) - 1) * (wavelet_length - 1);
+}
+
+static int reallocate_outputs(AudioFWTDNContext *s,
+                              double **out, int *out_length,
+                              int in_length, int levels, int ch, uint64_t sn)
+{
+    ChannelParams *cp = &s->cp[ch];
+    int temp_length = 0;
+    int add = 0;
+
+    for (int level = 0; level < levels; level++) {
+        temp_length = nb_coefs(in_length, level + 1, sn);
+        if (temp_length > out_length[level]) {
+            av_freep(&cp->subbands_to_free[level]);
+            out_length[level] = 0;
+
+            add = max_left_zeros_inverse(levels, level + 1, s->wavelet_length);
+            cp->subbands_to_free[level] = av_calloc(add + temp_length + 1, sizeof(**out));
+            if (!cp->subbands_to_free[level])
+                return AVERROR(ENOMEM);
+            out_length[level] = add + temp_length + 1;
+            out[level] = cp->subbands_to_free[level] + add;
+        }
+
+        memset(out[level] + temp_length, 0,
+               FFMAX(out_length[level] - temp_length - add, 0) * sizeof(**out));
+        out_length[level] = temp_length;
+    }
+
+    temp_length = nb_coefs(in_length, levels, sn);
+    if (temp_length > out_length[levels]) {
+        av_freep(&cp->subbands_to_free[levels]);
+        out_length[levels] = 0;
+
+        cp->subbands_to_free[levels] = av_calloc(temp_length + 1, sizeof(**out));
+        if (!cp->subbands_to_free[levels])
+            return AVERROR(ENOMEM);
+        out_length[levels] = temp_length + 1;
+        out[levels] = cp->subbands_to_free[levels];
+    }
+
+    memset(out[levels] + temp_length, 0,
+           (out_length[levels] - temp_length) * sizeof(**out));
+    out_length[levels] = temp_length;
+
+    return 0;
+}
+
+static int discard_left_ext(int wavelet_length, int levels, int level, uint64_t sn)
+{
+    if (levels == level || sn == 0)
+        return 0;
+    return (pow2(levels - level) - 1) * (wavelet_length - 2) + mod_pow2(sn, pow2(levels)) / pow2(level);
+}
+
+static int forward(AudioFWTDNContext *s,
+                   const double *in, int in_length,
+                   double **out, int *out_length, int ch, uint64_t sn)
+{
+    ChannelParams *cp = &s->cp[ch];
+    int levels = s->levels;
+    int skip = sn ? s->wavelet_length - 1 : 1;
+    int leftext, ret;
+
+    ret = reallocate_inputs(out, out_length, in_length, levels, ch, sn);
+    if (ret < 0)
+        return ret;
+    ret = reallocate_outputs(s, cp->filter_coefs, cp->filter_length,
+                             in_length, levels, ch, sn);
+    if (ret < 0)
+        return ret;
+
+    leftext = left_ext(s->wavelet_length, levels, sn);
+
+    if (cp->temp_in_max_length < in_length + cp->max_left_ext + skip) {
+        av_freep(&cp->temp_in);
+        cp->temp_in_max_length = in_length + cp->max_left_ext + skip;
+        cp->temp_in = av_calloc(cp->temp_in_max_length, sizeof(*cp->temp_in));
+        if (!cp->temp_in) {
+            cp->temp_in_max_length = 0;
+            return AVERROR(ENOMEM);
+        }
+    }
+
+    memset(cp->temp_in, 0, cp->temp_in_max_length * sizeof(*cp->temp_in));
+    cp->temp_in_length = in_length + leftext;
+
+    if (leftext)
+        memcpy(cp->temp_in, cp->prev + s->prev_length - leftext, leftext * sizeof(*cp->temp_in));
+    memcpy(cp->temp_in + leftext, in, in_length * sizeof(*in));
+
+    if (levels == 1) {
+        conv_down(cp->temp_in, cp->temp_in_length, out[1], out[0], out_length[1],
+                 s->lp, s->hp, s->wavelet_length, skip,
+                 cp->buffer, cp->buffer_length);
+    } else {
+        int discard = discard_left_ext(s->wavelet_length, levels, 1, sn);
+        int tempa_length_prev;
+
+        if (cp->tempa_len_max < (in_length + cp->max_left_ext + s->wavelet_length - 1) / 2) {
+            av_freep(&cp->tempa);
+            av_freep(&cp->tempd);
+            cp->tempa_len_max = (in_length + cp->max_left_ext + s->wavelet_length - 1) / 2;
+            cp->tempa = av_calloc(cp->tempa_len_max, sizeof(*cp->tempa));
+            cp->tempd = av_calloc(cp->tempa_len_max, sizeof(*cp->tempd));
+            if (!cp->tempa || !cp->tempd) {
+                cp->tempa_len_max = 0;
+                return AVERROR(ENOMEM);
+            }
+        }
+
+        memset(cp->tempa, 0, cp->tempa_len_max * sizeof(*cp->tempa));
+        memset(cp->tempd, 0, cp->tempa_len_max * sizeof(*cp->tempd));
+
+        cp->tempa_length = out_length[0] + discard;
+        conv_down(cp->temp_in, cp->temp_in_length,
+                 cp->tempa, cp->tempd, cp->tempa_length,
+                 s->lp, s->hp, s->wavelet_length, skip,
+                 cp->buffer, cp->buffer_length);
+        memcpy(out[0], cp->tempd + discard, out_length[0] * sizeof(**out));
+        tempa_length_prev = cp->tempa_length;
+
+        for (int level = 1; level < levels - 1; level++) {
+            if (out_length[level] == 0)
+                return 0;
+            discard = discard_left_ext(s->wavelet_length, levels, level + 1, sn);
+            cp->tempa_length = out_length[level] + discard;
+            conv_down(cp->tempa, tempa_length_prev,
+                     cp->tempa, cp->tempd, cp->tempa_length,
+                     s->lp, s->hp, s->wavelet_length, skip,
+                     cp->buffer, cp->buffer_length);
+            memcpy(out[level], cp->tempd + discard, out_length[level] * sizeof(**out));
+            tempa_length_prev = cp->tempa_length;
+        }
+
+        if (out_length[levels] == 0)
+            return 0;
+        conv_down(cp->tempa, cp->tempa_length, out[levels], out[levels - 1], out_length[levels],
+                 s->lp, s->hp, s->wavelet_length, skip,
+                 cp->buffer, cp->buffer_length);
+    }
+
+    if (s->prev_length < in_length) {
+        memcpy(cp->prev, in + in_length - cp->max_left_ext, cp->max_left_ext * sizeof(*cp->prev));
+    } else {
+        memmove(cp->prev, cp->prev + in_length, (s->prev_length - in_length) * sizeof(*cp->prev));
+        memcpy(cp->prev + s->prev_length - in_length, in, in_length * sizeof(*cp->prev));
+    }
+
+    return 0;
+}
+
+static void conv_up(double *low, double *high, int in_length, double *out, int out_length,
+                    const double *lp, const double *hp, int filter_length,
+                    double *buffer, double *buffer2, int buffer_length)
+{
+    int shift = 0, buff_idx = 0, in_idx = 0;
+
+    memset(buffer, 0, buffer_length * sizeof(*buffer));
+    memset(buffer2, 0, buffer_length * sizeof(*buffer2));
+
+    for (int i = 0; i < out_length; i++) {
+        double sum = 0.0;
+
+        if ((i & 1) == 0) {
+            if (in_idx < in_length) {
+                buffer[buff_idx] = low[in_idx];
+                buffer2[buff_idx] = high[in_idx++];
+            } else {
+                buffer[buff_idx] = 0;
+                buffer2[buff_idx] = 0;
+            }
+            buff_idx++;
+            if (buff_idx >= buffer_length)
+                buff_idx = 0;
+            shift = 0;
+        }
+
+        for (int j = 0; j < (filter_length - shift + 1) / 2; j++) {
+            const int idx = mod_pow2(-j + buff_idx - 1, buffer_length);
+
+            sum += buffer[idx] * lp[j * 2 + shift] + buffer2[idx] * hp[j * 2 + shift];
+        }
+        out[i] = sum;
+        shift = 1;
+    }
+}
+
+static int append_left_ext(int wavelet_length, int levels, int level, uint64_t sn)
+{
+    if (levels == level)
+        return 0;
+
+    return (pow2(levels - level) - 1) * (wavelet_length - 2) +
+            mod_pow2(sn, pow2(levels)) / pow2(level);
+}
+
+static int inverse(AudioFWTDNContext *s,
+                   double **in, int *in_length,
+                   double *out, int out_length, int ch, uint64_t sn)
+{
+    ChannelParams *cp = &s->cp[ch];
+    const int levels = s->levels;
+    int leftext = left_ext(s->wavelet_length, levels, sn);
+    int temp_skip = 0;
+
+    if (sn == 0)
+        temp_skip = cp->min_left_ext;
+
+    memset(out, 0, out_length * sizeof(*out));
+
+    if (cp->temp_in_max_length < out_length + cp->max_left_ext + s->wavelet_length - 1) {
+        av_freep(&cp->temp_in);
+        cp->temp_in_max_length = out_length + cp->max_left_ext + s->wavelet_length - 1;
+        cp->temp_in = av_calloc(cp->temp_in_max_length, sizeof(*cp->temp_in));
+        if (!cp->temp_in) {
+            cp->temp_in_max_length = 0;
+            return AVERROR(ENOMEM);
+        }
+    }
+
+    memset(cp->temp_in, 0, cp->temp_in_max_length * sizeof(*cp->temp_in));
+    cp->temp_in_length = out_length + cp->max_left_ext;
+
+    if (levels == 1) {
+        conv_up(in[1], in[0], in_length[1], cp->temp_in, cp->temp_in_length,
+                s->ilp, s->ihp, s->wavelet_length,
+                cp->buffer, cp->buffer2, cp->buffer_length);
+        memcpy(out + cp->max_left_ext - leftext, cp->temp_in + temp_skip,
+               FFMAX(0, out_length - (cp->max_left_ext - leftext)) * sizeof(*out));
+    } else {
+        double *hp1, *hp2;
+        int add, add2;
+
+        if (cp->tempa_len_max < (out_length + cp->max_left_ext + s->wavelet_length - 1) / 2) {
+            av_freep(&cp->tempa);
+            cp->tempa_len_max = (out_length + cp->max_left_ext + s->wavelet_length - 1) / 2;
+            cp->tempa = av_calloc(cp->tempa_len_max, sizeof(*cp->tempa));
+            if (!cp->tempa) {
+                cp->tempa_len_max = 0;
+                return AVERROR(ENOMEM);
+            }
+        }
+
+        memset(cp->tempa, 0, cp->tempa_len_max * sizeof(*cp->tempa));
+
+        hp1 = levels & 1 ? cp->temp_in : cp->tempa;
+        hp2 = levels & 1 ? cp->tempa : cp->temp_in;
+
+        add = append_left_ext(s->wavelet_length, levels, levels - 1, sn);
+        conv_up(in[levels], in[levels - 1], in_length[levels], hp1, in_length[levels - 2] + add,
+               s->ilp, s->ihp, s->wavelet_length, cp->buffer, cp->buffer2, cp->buffer_length);
+
+        for (int level = levels - 1; level > 1; level--) {
+            add2 = append_left_ext(s->wavelet_length, levels, level - 1, sn);
+            add = append_left_ext(s->wavelet_length, levels, level, sn);
+            conv_up(hp1, in[level - 1] - add, in_length[level - 1] + add,
+                    hp2, in_length[level - 2] + add2,
+                    s->ilp, s->ihp, s->wavelet_length,
+                    cp->buffer, cp->buffer2, cp->buffer_length);
+            FFSWAP(double *, hp1, hp2);
+        }
+
+        add = append_left_ext(s->wavelet_length, levels, 1, sn);
+        conv_up(hp1, in[0] - add, in_length[0] + add, cp->temp_in, cp->temp_in_length,
+               s->ilp, s->ihp, s->wavelet_length,
+               cp->buffer, cp->buffer2, cp->buffer_length);
+    }
+
+    memset(cp->temp_in, 0, temp_skip * sizeof(*cp->temp_in));
+    if (s->overlap_length <= out_length) {
+        memcpy(out + cp->max_left_ext - leftext, cp->temp_in + temp_skip,
+               FFMAX(0, out_length - (cp->max_left_ext - leftext)) * sizeof(*out));
+        for (int i = 0;i < FFMIN(s->overlap_length, out_length); i++)
+            out[i] += cp->overlap[i];
+
+        memcpy(cp->overlap, cp->temp_in + out_length - (cp->max_left_ext - leftext),
+               s->overlap_length * sizeof(*cp->overlap));
+    } else {
+        for (int i = 0;i < s->overlap_length - (cp->max_left_ext - leftext); i++)
+            cp->overlap[i + cp->max_left_ext - leftext] += cp->temp_in[i];
+        memcpy(out, cp->overlap, out_length * sizeof(*out));
+        memmove(cp->overlap, cp->overlap + out_length,
+                (s->overlap_length - out_length) * sizeof(*cp->overlap));
+        memcpy(cp->overlap + s->overlap_length - out_length, cp->temp_in + leftext,
+               out_length * sizeof(*cp->overlap));
+    }
+
+    return 0;
+}
+
+static int next_pow2(int in)
+{
+    return 1 << (av_log2(in) + 1);
+}
+
+static void denoise_level(double *out, const double *in,
+                          const double *filter,
+                          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] = x * filter[i] + in[i] * y;
+}
+
+static double sqr(double in)
+{
+    return in * in;
+}
+
+static double measure_mean(const double *in, int length)
+{
+    double sum = 0.0;
+
+    for (int i = 0; i < length; i++)
+        sum += in[i];
+
+    return sum / length;
+}
+
+static double measure_absmean(const double *in, int length)
+{
+    double sum = 0.0;
+
+    for (int i = 0; i < length; i++)
+        sum += fabs(in[i]);
+
+    return sum / length;
+}
+
+static double measure_stddev(const double *in, int length, double mean)
+{
+    double sum = 0.;
+
+    for (int i = 0; i < length; i++) {
+        sum += sqr(in[i] - mean);
+    }
+
+    return sqrt(sum / length);
+}
+
+static void noise_filter(const double stddev, const double *in,
+                         double *out, double absmean, double softness,
+                         double new_stddev, int length)
+{
+    for (int i = 0; i < length; i++) {
+        if (new_stddev <= stddev)
+            out[i] = 0.0;
+        else if (fabs(in[i]) <= absmean)
+            out[i] = 0.0;
+        else
+            out[i] = in[i] - FFSIGN(in[i]) * absmean / exp(3.0 * softness * (fabs(in[i]) - absmean) / absmean);
+    }
+}
+
+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;
+    ChannelParams *cp = &s->cp[ch];
+    const double *src = (const double *)(in->extended_data[ch]);
+    double *dst = (double *)out->extended_data[ch];
+    double *absmean = (double *)s->absmean->extended_data[ch];
+    double *new_absmean = (double *)s->new_absmean->extended_data[ch];
+    double *stddev = (double *)s->stddev->extended_data[ch];
+    double *new_stddev = (double *)s->new_stddev->extended_data[ch];
+    double *filter = (double *)s->filter->extended_data[ch];
+    double is_noise = 0.0;
+    int ret;
+
+    ret = forward(s, src, in->nb_samples, cp->output_coefs, cp->output_length, ch, s->sn);
+    if (ret < 0)
+        return ret;
+
+    if (!s->got_profile && s->need_profile) {
+        for (int level = 0; level <= s->levels; level++) {
+            const int length = cp->output_length[level];
+            const double scale = sqrt(2.0 * log(length));
+
+            stddev[level] = measure_stddev(cp->output_coefs[level], length,
+                            measure_mean(cp->output_coefs[level], length)) * scale;
+            absmean[level] = measure_absmean(cp->output_coefs[level], length) * scale;
+        }
+    } else if (!s->got_profile && !s->need_profile && !s->adaptive) {
+        for (int level = 0; level <= s->levels; level++) {
+            const int length = cp->output_length[level];
+            const double scale = sqrt(2.0 * log(length));
+
+            stddev[level] = 0.5 * s->sigma * scale;
+            absmean[level] = 0.5 * s->sigma * scale;
+        }
+    }
+
+    for (int level = 0; level <= s->levels; level++) {
+        const int length = cp->output_length[level];
+        double vad;
+
+        new_stddev[level] = measure_stddev(cp->output_coefs[level], length,
+                            measure_mean(cp->output_coefs[level], length));
+        new_absmean[level] = measure_absmean(cp->output_coefs[level], length);
+        if (new_absmean[level] <= FLT_EPSILON)
+            vad = 1.0;
+        else
+            vad = new_stddev[level] / new_absmean[level];
+        if (level < s->levels)
+            is_noise += sqr(vad - 1.232);
+    }
+
+    is_noise *= in->sample_rate;
+    is_noise /= s->nb_samples;
+    for (int level = 0; level <= s->levels; level++) {
+        const int length = cp->output_length[level];
+        const double scale = sqrt(2.0 * log(length));
+
+        if (is_noise < 0.05 && s->adaptive) {
+            stddev[level] = new_stddev[level] * scale;
+            absmean[level] = new_absmean[level] * scale;
+        }
+
+        noise_filter(stddev[level], cp->output_coefs[level], filter, absmean[level],
+                     s->softness, new_stddev[level], length);
+        denoise_level(cp->filter_coefs[level], cp->output_coefs[level], filter, s->percent, length);
+    }
+
+    ret = inverse(s, cp->filter_coefs, cp->filter_length, dst, out->nb_samples, ch, s->sn);
+    if (ret < 0)
+        return ret;
+
+    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;
+    int eof = in == NULL;
+
+    out = ff_get_audio_buffer(outlink, s->nb_samples);
+    if (!out) {
+        av_frame_free(&in);
+        return AVERROR(ENOMEM);
+    }
+    if (in) {
+        av_frame_copy_props(out, in);
+        s->eof_pts = in->pts + in->nb_samples;
+    }
+    if (eof)
+        out->pts = s->eof_pts - s->padd_samples;
+
+    if (!in || in->nb_samples < s->nb_samples) {
+        AVFrame *new_in = ff_get_audio_buffer(outlink, s->nb_samples);
+
+        if (!new_in) {
+            av_frame_free(&in);
+            av_frame_free(&out);
+            return AVERROR(ENOMEM);
+        }
+        if (in)
+            av_frame_copy_props(new_in, in);
+
+        s->padd_samples -= s->nb_samples - (in ? in->nb_samples: 0);
+        if (in)
+            av_samples_copy(new_in->extended_data, in->extended_data, 0, 0,
+                            in->nb_samples, in->channels, in->format);
+        av_frame_free(&in);
+        in = new_in;
+    }
+
+    td.in  = in;
+    td.out = out;
+    ctx->internal->execute(ctx, s->filter_channel, &td, NULL, inlink->channels);
+    if (s->need_profile)
+        s->got_profile = 1;
+
+    s->sn += s->nb_samples;
+
+    if (s->drop_samples >= in->nb_samples) {
+        s->drop_samples -= in->nb_samples;
+        s->delay += in->nb_samples;
+        av_frame_free(&in);
+        av_frame_free(&out);
+        FF_FILTER_FORWARD_STATUS(inlink, outlink);
+        FF_FILTER_FORWARD_WANTED(outlink, inlink);
+        return 0;
+    } else if (s->drop_samples > 0) {
+        for (int ch = 0; ch < out->channels; ch++) {
+            memmove(out->extended_data[ch],
+                    out->extended_data[ch] + s->drop_samples * sizeof(double),
+                    (in->nb_samples - s->drop_samples) * sizeof(double));
+        }
+
+        out->nb_samples = in->nb_samples - s->drop_samples;
+        out->pts = in->pts - av_rescale_q(s->delay, (AVRational){1, outlink->sample_rate}, outlink->time_base);
+        s->delay += s->drop_samples;
+        s->drop_samples = 0;
+    } else {
+        if (s->padd_samples < 0 && eof) {
+            out->nb_samples += s->padd_samples;
+            s->padd_samples = 0;
+        }
+        if (!eof)
+            out->pts = in->pts - av_rescale_q(s->delay, (AVRational){1, outlink->sample_rate}, outlink->time_base);
+    }
+
+    av_frame_free(&in);
+    return ff_filter_frame(outlink, out);
+}
+
+static int max_left_ext(int wavelet_length, int levels)
+{
+    return (pow2(levels) - 1) * (wavelet_length - 1);
+}
+
+static int min_left_ext(int wavelet_length, int levels)
+{
+    return (pow2(levels) - 1) * (wavelet_length - 2);
+}
+
+static int config_output(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    AudioFWTDNContext *s = ctx->priv;
+
+    switch (s->wavelet_type) {
+    case SYM2:
+        s->wavelet_length = 4;
+        s->lp  = sym2_lp;
+        s->hp  = sym2_hp;
+        s->ilp = sym2_ilp;
+        s->ihp = sym2_ihp;
+        break;
+    case SYM4:
+        s->wavelet_length = 8;
+        s->lp  = sym4_lp;
+        s->hp  = sym4_hp;
+        s->ilp = sym4_ilp;
+        s->ihp = sym4_ihp;
+        break;
+    case RBIOR68:
+        s->wavelet_length = 18;
+        s->lp  = rbior68_lp;
+        s->hp  = rbior68_hp;
+        s->ilp = rbior68_ilp;
+        s->ihp = rbior68_ihp;
+        break;
+    case DEB10:
+        s->wavelet_length = 20;
+        s->lp  = deb10_lp;
+        s->hp  = deb10_hp;
+        s->ilp = deb10_ilp;
+        s->ihp = deb10_ihp;
+    case SYM10:
+        s->wavelet_length = 20;
+        s->lp  = sym10_lp;
+        s->hp  = sym10_hp;
+        s->ilp = sym10_ilp;
+        s->ihp = sym10_ihp;
+        break;
+    case COIF5:
+        s->wavelet_length = 30;
+        s->lp  = coif5_lp;
+        s->hp  = coif5_hp;
+        s->ilp = coif5_ilp;
+        s->ihp = coif5_ihp;
+        break;
+    case BL3:
+        s->wavelet_length = 42;
+        s->lp  = bl3_lp;
+        s->hp  = bl3_hp;
+        s->ilp = bl3_ilp;
+        s->ihp = bl3_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);
+    s->filter_channel = filter_channel;
+
+    s->stddev = ff_get_audio_buffer(outlink, MAX_LEVELS);
+    s->new_stddev = ff_get_audio_buffer(outlink, MAX_LEVELS);
+    s->filter = ff_get_audio_buffer(outlink, s->nb_samples);
+    s->absmean = ff_get_audio_buffer(outlink, MAX_LEVELS);
+    s->new_absmean = ff_get_audio_buffer(outlink, MAX_LEVELS);
+    if (!s->stddev || !s->absmean || !s->filter ||
+        !s->new_stddev || !s->new_absmean)
+        return AVERROR(ENOMEM);
+
+    s->channels = outlink->channels;
+    s->overlap_length = max_left_ext(s->wavelet_length, s->levels);
+    s->prev_length = s->overlap_length;
+    s->drop_samples = s->overlap_length;
+    s->padd_samples = s->overlap_length;
+    s->sn = 1;
+
+    s->cp = av_calloc(s->channels, sizeof(*s->cp));
+    if (!s->cp)
+        return AVERROR(ENOMEM);
+
+    for (int ch = 0; ch < s->channels; ch++) {
+        ChannelParams *cp = &s->cp[ch];
+
+        cp->output_coefs = av_calloc(s->levels + 1, sizeof(*cp->output_coefs));
+        cp->filter_coefs = av_calloc(s->levels + 1, sizeof(*cp->filter_coefs));
+        cp->output_length = av_calloc(s->levels + 1, sizeof(*cp->output_length));
+        cp->filter_length = av_calloc(s->levels + 1, sizeof(*cp->filter_length));
+        cp->buffer_length = next_pow2(s->wavelet_length);
+        cp->buffer = av_calloc(cp->buffer_length, sizeof(*cp->buffer));
+        cp->buffer2 = av_calloc(cp->buffer_length, sizeof(*cp->buffer2));
+        cp->subbands_to_free = av_calloc(s->levels + 1, sizeof(*cp->subbands_to_free));
+        cp->prev = av_calloc(s->prev_length, sizeof(*cp->prev));
+        cp->overlap = av_calloc(s->overlap_length, sizeof(*cp->overlap));
+        cp->max_left_ext = max_left_ext(s->wavelet_length, s->levels);
+        cp->min_left_ext = min_left_ext(s->wavelet_length, s->levels);
+        if (!cp->output_coefs || !cp->filter_coefs || !cp->output_length ||
+            !cp->filter_length || !cp->subbands_to_free || !cp->prev || !cp->overlap ||
+            !cp->buffer || !cp->buffer2)
+            return AVERROR(ENOMEM);
+    }
+
+    return 0;
+}
+
+static int activate(AVFilterContext *ctx)
+{
+    AVFilterLink *inlink = ctx->inputs[0];
+    AVFilterLink *outlink = ctx->outputs[0];
+    AudioFWTDNContext *s = ctx->priv;
+    AVFrame *in = NULL;
+    int ret, status;
+    int64_t pts;
+
+    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);
+
+    if (ff_inlink_acknowledge_status(inlink, &status, &pts)) {
+        if (status == AVERROR_EOF) {
+            while (s->padd_samples != 0) {
+                ret = filter_frame(inlink, NULL);
+                if (ret < 0)
+                    return ret;
+            }
+            ff_outlink_set_status(outlink, status, pts);
+            return ret;
+        }
+    }
+    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->new_stddev);
+    av_frame_free(&s->stddev);
+    av_frame_free(&s->new_absmean);
+    av_frame_free(&s->absmean);
+
+    for (int ch = 0; s->cp && ch < s->channels; ch++) {
+        ChannelParams *cp = &s->cp[ch];
+
+        av_freep(&cp->tempa);
+        av_freep(&cp->tempd);
+        av_freep(&cp->temp_in);
+        av_freep(&cp->buffer);
+        av_freep(&cp->buffer2);
+        av_freep(&cp->prev);
+        av_freep(&cp->overlap);
+
+        av_freep(&cp->output_length);
+        av_freep(&cp->filter_length);
+
+        if (cp->output_coefs) {
+            for (int level = 0; level <= s->levels; level++)
+                av_freep(&cp->output_coefs[level]);
+        }
+
+        if (cp->subbands_to_free) {
+            for (int level = 0; level <= s->levels; level++)
+                av_freep(&cp->subbands_to_free[level]);
+        }
+
+        av_freep(&cp->subbands_to_free);
+        av_freep(&cp->output_coefs);
+        av_freep(&cp->filter_coefs);
+    }
+
+    av_freep(&s->cp);
+}
+
+static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
+                           char *res, int res_len, int flags)
+{
+    AudioFWTDNContext *s = ctx->priv;
+    int ret;
+
+    ret = ff_filter_process_command(ctx, cmd, args, res, res_len, flags);
+    if (ret < 0)
+        return ret;
+
+    if (!strcmp(cmd, "profile") && s->need_profile)
+        s->got_profile = 0;
+
+    return 0;
+}
+
+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("Denoise audio stream using Wavelets."),
+    .query_formats   = query_formats,
+    .priv_size       = sizeof(AudioFWTDNContext),
+    .priv_class      = &afwtdn_class,
+    .activate        = activate,
+    .uninit          = uninit,
+    .inputs          = inputs,
+    .outputs         = outputs,
+    .process_command = process_command,
+    .flags           = AVFILTER_FLAG_SLICE_THREADS,
+};
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index c6afef835f..ae74f9c891 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -46,6 +46,7 @@  extern const AVFilter ff_af_afftfilt;
 extern const AVFilter ff_af_afir;
 extern const AVFilter ff_af_aformat;
 extern const AVFilter ff_af_afreqshift;
+extern const AVFilter ff_af_afwtdn;
 extern const AVFilter ff_af_agate;
 extern const AVFilter ff_af_aiir;
 extern const AVFilter ff_af_aintegral;