[FFmpeg-devel,2/2] avfilter: add declip audio filter

Submitted by Paul B Mahol on March 25, 2018, 4:41 p.m.

Details

Message ID 20180325164125.22940-1-onemda@gmail.com
State New
Headers show

Commit Message

Paul B Mahol March 25, 2018, 4:41 p.m.
Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
Depends on declick filter.
---
 doc/filters.texi         | 28 +++++++++++++++++++
 libavfilter/Makefile     |  1 +
 libavfilter/af_declick.c | 73 +++++++++++++++++++++++++++++++++++++++++++++---
 libavfilter/allfilters.c |  1 +
 4 files changed, 99 insertions(+), 4 deletions(-)

Patch hide | download patch | download mbox

diff --git a/doc/filters.texi b/doc/filters.texi
index 9a067ba9ea..86cf570ab9 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -2597,6 +2597,34 @@  This controls between how much samples, which are detected as impulsive noise,
 any sample between 2 detected noise samples is considered also as noise sample.
 @end table
 
+@section declip
+Remove clipped samples from input audio.
+
+Samples detected as clipped are replaced by interpolated samples using
+autoregressive modeling.
+
+@table @option
+@item w
+Set window size, in milliseconds. Allowed range is from @code{10} to @code{100}.
+Default value is @code{50} milliseconds.
+This sets size of window which will be processed at once.
+
+@item o
+Set window overlap, in percentage of window size. Allowed range is from @code{50}
+to @code{95}. Default value is @code{75} percent.
+
+@item a
+Set autoregression order, in percentage of window size. Allowed range is from
+@code{1} to @code{50}. Default value is @code{2} percent. This option also controls
+quality of interpolated samples using neighbour good samples.
+
+@item t
+Set threshold value. Allowed range is from @code{0.2} to @code{1.0}.
+Default value is @code{0.98}.
+Any sample which absolute value is equal or higher of this value will be
+detected as clipped and be replaced with interpolated value.
+@end table
+
 @section drmeter
 Measure audio dynamic range.
 
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 978751d2a0..505287b5b4 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -88,6 +88,7 @@  OBJS-$(CONFIG_CROSSFEED_FILTER)              += af_crossfeed.o
 OBJS-$(CONFIG_CRYSTALIZER_FILTER)            += af_crystalizer.o
 OBJS-$(CONFIG_DCSHIFT_FILTER)                += af_dcshift.o
 OBJS-$(CONFIG_DECLICK_FILTER)                += af_declick.o
+OBJS-$(CONFIG_DECLIP_FILTER)                 += af_declick.o
 OBJS-$(CONFIG_DRMETER_FILTER)                += af_drmeter.o
 OBJS-$(CONFIG_DYNAUDNORM_FILTER)             += af_dynaudnorm.o
 OBJS-$(CONFIG_EARWAX_FILTER)                 += af_earwax.o
diff --git a/libavfilter/af_declick.c b/libavfilter/af_declick.c
index 0de4c35c95..658dae420b 100644
--- a/libavfilter/af_declick.c
+++ b/libavfilter/af_declick.c
@@ -29,9 +29,11 @@  typedef struct DeclickContext {
     double w;
     double overlap;
     double threshold;
+    double clip_threshold;
     double ar;
     double burst;
 
+    int is_declip;
     int ar_order;
     int nb_burst_samples;
     int window_size;
@@ -68,6 +70,10 @@  typedef struct DeclickContext {
 
     AVAudioFifo *fifo;
     double *window_func_lut;
+
+    int (*detector)(struct DeclickContext *s, double sigmae, double *detection,
+                    double *acoefficients, uint8_t *click, int *index,
+                    const double *src, double *dst);
 } DeclickContext;
 
 #define OFFSET(x) offsetof(DeclickContext, x)
@@ -354,6 +360,28 @@  static int interpolation(DeclickContext *s, const double *src, int ar_order,
     return cholesky_decomposition(s, matrix, vector, nb_clicks, interpolated);
 }
 
+static int detect_clips(DeclickContext *s, double unused0, double *unused1, double *unused2,
+                        uint8_t *clips, int *index,
+                        const double *src, double *dst)
+{
+    const double threshold = s->clip_threshold;
+    int i, nb_clips = 0;
+
+    for (i = 0; i < s->window_size; i++) {
+        clips[i] = fabs(src[i]) >= threshold;
+        dst[i] = src[i];
+    }
+
+    memset(clips, 0, s->ar_order * sizeof(*clips));
+    memset(clips + (s->window_size - s->ar_order), 0, s->ar_order * sizeof(*clips));
+
+    for (i = s->ar_order; i < s->window_size - s->ar_order; i++)
+        if (clips[i])
+            index[nb_clips++] = i;
+
+    return nb_clips;
+}
+
 static int detect_clicks(DeclickContext *s, double sigmae, double *detection, double *acoefficients,
                          uint8_t *click, int *index,
                          const double *src, double *dst)
@@ -441,8 +469,8 @@  static int filter_frame(AVFilterLink *inlink, AVFrame *in)
                 int *index = s->index;
                 int nb_clicks;
 
-                nb_clicks = detect_clicks(s, sigmae, s->detection, s->acoefficients,
-                                          s->click, index, src, dst);
+                nb_clicks = s->detector(s, sigmae, s->detection, s->acoefficients,
+                                        s->click, index, src, dst);
                 if (nb_clicks > 0) {
                     ret = interpolation(s, src, s->ar_order, s->acoefficients, index,
                                         nb_clicks, s->auxiliary, interpolated);
@@ -521,13 +549,27 @@  static int request_frame(AVFilterLink *outlink)
     return ret;
 }
 
+static av_cold int init(AVFilterContext *ctx)
+{
+    DeclickContext *s = ctx->priv;
+
+    s->is_declip = !strcmp(ctx->filter->name, "declip");
+    if (s->is_declip) {
+        s->detector = detect_clips;
+    } else {
+        s->detector = detect_clicks;
+    }
+
+    return 0;
+}
 
 static av_cold void uninit(AVFilterContext *ctx)
 {
     DeclickContext *s = ctx->priv;
 
-    av_log(ctx, AV_LOG_INFO, "Detected clicks in %"PRId64" of %"PRId64" samples (%g%%).\n",
-           s->detected_clicks, s->nb_samples, 100. * s->detected_clicks / s->nb_samples);
+    av_log(ctx, AV_LOG_INFO, "Detected %s in %"PRId64" of %"PRId64" samples (%g%%).\n",
+           s->is_declip ? "clips" : "clicks", s->detected_clicks,
+           s->nb_samples, 100. * s->detected_clicks / s->nb_samples);
 
     av_audio_fifo_free(s->fifo);
     av_freep(&s->window_func_lut);
@@ -580,6 +622,29 @@  AVFilter ff_af_declick = {
     .query_formats = query_formats,
     .priv_size     = sizeof(DeclickContext),
     .priv_class    = &declick_class,
+    .init          = init,
+    .uninit        = uninit,
+    .inputs        = inputs,
+    .outputs       = outputs,
+};
+
+static const AVOption declip_options[] = {
+    { "w", "set window size",          OFFSET(w),              AV_OPT_TYPE_DOUBLE, {.dbl=50},     10, 100, AF },
+    { "o", "set window overlap",       OFFSET(overlap),        AV_OPT_TYPE_DOUBLE, {.dbl=75},     50,  95, AF },
+    { "a", "set autoregression order", OFFSET(ar),             AV_OPT_TYPE_DOUBLE, {.dbl=8},       1,  50, AF },
+    { "t", "set threshold",            OFFSET(clip_threshold), AV_OPT_TYPE_DOUBLE, {.dbl=0.98},  0.2,  1., AF },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(declip);
+
+AVFilter ff_af_declip = {
+    .name          = "declip",
+    .description   = NULL_IF_CONFIG_SMALL("Remove clipping from input audio."),
+    .query_formats = query_formats,
+    .priv_size     = sizeof(DeclickContext),
+    .priv_class    = &declip_class,
+    .init          = init,
     .uninit        = uninit,
     .inputs        = inputs,
     .outputs       = outputs,
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index cf5016f2c1..4d18e243ab 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -99,6 +99,7 @@  static void register_all(void)
     REGISTER_FILTER(CRYSTALIZER,    crystalizer,    af);
     REGISTER_FILTER(DCSHIFT,        dcshift,        af);
     REGISTER_FILTER(DECLICK,        declick,        af);
+    REGISTER_FILTER(DECLIP,         declip,         af);
     REGISTER_FILTER(DRMETER,        drmeter,        af);
     REGISTER_FILTER(DYNAUDNORM,     dynaudnorm,     af);
     REGISTER_FILTER(EARWAX,         earwax,         af);