diff mbox series

[FFmpeg-devel] avfilter: add audio signal to distortion ratio filter

Message ID 20210912201928.7693-1-onemda@gmail.com
State New
Headers show
Series [FFmpeg-devel] avfilter: add audio signal to distortion ratio filter | expand

Checks

Context Check Description
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished
andriy/make_ppc success Make finished
andriy/make_fate_ppc success Make fate finished

Commit Message

Paul B Mahol Sept. 12, 2021, 8:19 p.m. UTC
Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 doc/filters.texi         |   7 ++
 libavfilter/Makefile     |   1 +
 libavfilter/af_asdr.c    | 197 +++++++++++++++++++++++++++++++++++++++
 libavfilter/allfilters.c |   1 +
 4 files changed, 206 insertions(+)
 create mode 100644 libavfilter/af_asdr.c

Comments

Nicolas George Sept. 12, 2021, 8:49 p.m. UTC | #1
Paul B Mahol (12021-09-12):
> Signed-off-by: Paul B Mahol <onemda@gmail.com>
> ---
>  doc/filters.texi         |   7 ++
>  libavfilter/Makefile     |   1 +
>  libavfilter/af_asdr.c    | 197 +++++++++++++++++++++++++++++++++++++++
>  libavfilter/allfilters.c |   1 +
>  4 files changed, 206 insertions(+)
>  create mode 100644 libavfilter/af_asdr.c
> 
> diff --git a/doc/filters.texi b/doc/filters.texi
> index 8f20ccf8c6..6af7344820 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -2531,6 +2531,13 @@ noise removed from input signal.
>  
>  This filter supports the all above options as @ref{commands}.
>  
> +@section asdr
> +Measure Audio Signal-to-Distortion Ratio.
> +
> +This filter takes two audio streams for input, and outputs first
> +audio stream.
> +Results are in dB per channel at end of either input.
> +
>  @section asetnsamples
>  
>  Set the number of samples per each output audio frame.
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 76c65c3f42..865252ef3f 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -82,6 +82,7 @@ OBJS-$(CONFIG_AREALTIME_FILTER)              += f_realtime.o
>  OBJS-$(CONFIG_ARESAMPLE_FILTER)              += af_aresample.o
>  OBJS-$(CONFIG_AREVERSE_FILTER)               += f_reverse.o
>  OBJS-$(CONFIG_ARNNDN_FILTER)                 += af_arnndn.o
> +OBJS-$(CONFIG_ASDR_FILTER)                   += af_asdr.o
>  OBJS-$(CONFIG_ASEGMENT_FILTER)               += f_segment.o
>  OBJS-$(CONFIG_ASELECT_FILTER)                += f_select.o
>  OBJS-$(CONFIG_ASENDCMD_FILTER)               += f_sendcmd.o
> diff --git a/libavfilter/af_asdr.c b/libavfilter/af_asdr.c
> new file mode 100644
> index 0000000000..25032445cd
> --- /dev/null
> +++ b/libavfilter/af_asdr.c
> @@ -0,0 +1,197 @@
> +/*
> + * Copyright (c) 2021 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 "libavutil/channel_layout.h"
> +#include "libavutil/common.h"
> +#include "libavutil/opt.h"
> +
> +#include "audio.h"
> +#include "avfilter.h"
> +#include "formats.h"
> +#include "filters.h"
> +#include "internal.h"
> +
> +typedef struct AudioSDRContext {
> +    int channels;
> +    int64_t pts;
> +    double *sum_u;
> +    double *sum_uv;
> +
> +    AVFrame *cache[2];
> +} AudioSDRContext;
> +
> +static int query_formats(AVFilterContext *ctx)
> +{
> +    static const enum AVSampleFormat sample_fmts[] = {
> +        AV_SAMPLE_FMT_DBLP,
> +        AV_SAMPLE_FMT_NONE
> +    };
> +    int ret = ff_set_common_all_channel_counts(ctx);
> +    if (ret < 0)
> +        return ret;
> +
> +    ret = ff_set_common_formats_from_list(ctx, sample_fmts);
> +    if (ret < 0)
> +        return ret;
> +
> +    return ff_set_common_all_samplerates(ctx);
> +}
> +
> +static void sdr(AVFilterContext *ctx, const AVFrame *u, const AVFrame *v)
> +{
> +    AudioSDRContext *s = ctx->priv;
> +
> +    for (int ch = 0; ch < u->channels; ch++) {
> +        const double *const us = (double *)u->extended_data[ch];
> +        const double *const vs = (double *)v->extended_data[ch];
> +        double sum_uv = s->sum_uv[ch];
> +        double sum_u = s->sum_u[ch];
> +
> +        for (int n = 0; n < u->nb_samples; n++) {
> +            sum_u  += us[n] * us[n];
> +            sum_uv += (us[n] - vs[n]) * (us[n] - vs[n]);
> +        }
> +
> +        s->sum_uv[ch] = sum_uv;
> +        s->sum_u[ch]  = sum_u;
> +    }
> +}
> +
> +static int activate(AVFilterContext *ctx)
> +{
> +    AudioSDRContext *s = ctx->priv;
> +    int ret, status;
> +    int available;
> +    int64_t pts;
> +
> +    FF_FILTER_FORWARD_STATUS_BACK_ALL(ctx->outputs[0], ctx);
> +
> +    available = FFMIN(ff_inlink_queued_samples(ctx->inputs[0]), ff_inlink_queued_samples(ctx->inputs[1]));
> +    if (available > 0) {
> +        AVFrame *out;
> +
> +        for (int i = 0; i < 2; i++) {
> +            ret = ff_inlink_consume_samples(ctx->inputs[i], available, available, &s->cache[i]);
> +            if (ret > 0) {
> +                if (s->pts == AV_NOPTS_VALUE)
> +                    s->pts = s->cache[i]->pts;
> +            }
> +        }
> +
> +        sdr(ctx, s->cache[0], s->cache[1]);
> +
> +        av_frame_free(&s->cache[1]);
> +        out = s->cache[0];
> +        out->nb_samples = available;
> +        out->pts = s->pts;
> +        s->pts += available;
> +        s->cache[0] = NULL;
> +
> +        return ff_filter_frame(ctx->outputs[0], out);
> +    }

Here, you need an else for the case where one input has samples, to call
ff_inlink_request_frame().

> +
> +    for (int i = 0; i < 2; i++) {
> +        if (ff_inlink_acknowledge_status(ctx->inputs[i], &status, &pts)) {
> +            ff_outlink_set_status(ctx->outputs[0], status, pts);
> +            return 0;
> +        }
> +    }
> +

> +    if (ff_inlink_queued_samples(ctx->inputs[0]) > 0 &&
> +        ff_inlink_queued_samples(ctx->inputs[1]) > 0) {

This condition can never be true, since you just consumed all the
samples from one of the inputs.

> +        ff_filter_set_ready(ctx, 10);
> +        return 0;
> +    }
> +
> +    if (ff_outlink_frame_wanted(ctx->outputs[0])) {
> +        for (int i = 0; i < 2; i++) {
> +            if (ff_inlink_queued_samples(ctx->inputs[i]) > 0)
> +                continue;
> +            ff_inlink_request_frame(ctx->inputs[i]);
> +        }
> +        return 0;
> +    }
> +
> +    return FFERROR_NOT_READY;
> +}
> +
> +static int config_output(AVFilterLink *outlink)
> +{
> +    AVFilterContext *ctx = outlink->src;
> +    AVFilterLink *inlink = ctx->inputs[0];
> +    AudioSDRContext *s = ctx->priv;
> +
> +    s->pts = AV_NOPTS_VALUE;
> +
> +    s->channels = inlink->channels;
> +    outlink->format = inlink->format;
> +    outlink->channels = inlink->channels;
> +
> +    s->sum_u  = av_calloc(outlink->channels, sizeof(*s->sum_u));
> +    s->sum_uv = av_calloc(outlink->channels, sizeof(*s->sum_uv));
> +    if (!s->sum_u || !s->sum_uv)
> +        return AVERROR(ENOMEM);
> +
> +    return 0;
> +}
> +
> +static av_cold void uninit(AVFilterContext *ctx)
> +{
> +    AudioSDRContext *s = ctx->priv;
> +
> +    for (int ch = 0; ch < s->channels; ch++)
> +        av_log(ctx, AV_LOG_INFO, "SDR ch%d: %g dB\n", ch, 20. * log10(s->sum_u[ch] / s->sum_uv[ch]));
> +
> +    av_frame_free(&s->cache[0]);
> +    av_frame_free(&s->cache[1]);
> +
> +    av_freep(&s->sum_u);
> +    av_freep(&s->sum_uv);
> +}
> +
> +static const AVFilterPad inputs[] = {
> +    {
> +        .name = "input0",
> +        .type = AVMEDIA_TYPE_AUDIO,
> +    },
> +    {
> +        .name = "input1",
> +        .type = AVMEDIA_TYPE_AUDIO,
> +    },
> +};
> +
> +static const AVFilterPad outputs[] = {
> +    {
> +        .name         = "default",
> +        .type         = AVMEDIA_TYPE_AUDIO,
> +        .config_props = config_output,
> +    },
> +};
> +
> +const AVFilter ff_af_asdr = {
> +    .name           = "asdr",
> +    .description    = NULL_IF_CONFIG_SMALL("Measure Audio Signal-to-Distortion Ratio."),
> +    .priv_size      = sizeof(AudioSDRContext),
> +    .query_formats  = query_formats,
> +    .activate       = activate,
> +    .uninit         = uninit,
> +    FILTER_INPUTS(inputs),
> +    FILTER_OUTPUTS(outputs),
> +};
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index 73a0bf9c44..7234ca6dbe 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -75,6 +75,7 @@ extern const AVFilter ff_af_arealtime;
>  extern const AVFilter ff_af_aresample;
>  extern const AVFilter ff_af_areverse;
>  extern const AVFilter ff_af_arnndn;
> +extern const AVFilter ff_af_asdr;
>  extern const AVFilter ff_af_asegment;
>  extern const AVFilter ff_af_aselect;
>  extern const AVFilter ff_af_asendcmd;

Regards,
Paul B Mahol Sept. 12, 2021, 8:55 p.m. UTC | #2
On Sun, Sep 12, 2021 at 10:49 PM Nicolas George <george@nsup.org> wrote:

> Paul B Mahol (12021-09-12):
> > Signed-off-by: Paul B Mahol <onemda@gmail.com>
> > ---
> >  doc/filters.texi         |   7 ++
> >  libavfilter/Makefile     |   1 +
> >  libavfilter/af_asdr.c    | 197 +++++++++++++++++++++++++++++++++++++++
> >  libavfilter/allfilters.c |   1 +
> >  4 files changed, 206 insertions(+)
> >  create mode 100644 libavfilter/af_asdr.c
> >
> > diff --git a/doc/filters.texi b/doc/filters.texi
> > index 8f20ccf8c6..6af7344820 100644
> > --- a/doc/filters.texi
> > +++ b/doc/filters.texi
> > @@ -2531,6 +2531,13 @@ noise removed from input signal.
> >
> >  This filter supports the all above options as @ref{commands}.
> >
> > +@section asdr
> > +Measure Audio Signal-to-Distortion Ratio.
> > +
> > +This filter takes two audio streams for input, and outputs first
> > +audio stream.
> > +Results are in dB per channel at end of either input.
> > +
> >  @section asetnsamples
> >
> >  Set the number of samples per each output audio frame.
> > diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> > index 76c65c3f42..865252ef3f 100644
> > --- a/libavfilter/Makefile
> > +++ b/libavfilter/Makefile
> > @@ -82,6 +82,7 @@ OBJS-$(CONFIG_AREALTIME_FILTER)              +=
> f_realtime.o
> >  OBJS-$(CONFIG_ARESAMPLE_FILTER)              += af_aresample.o
> >  OBJS-$(CONFIG_AREVERSE_FILTER)               += f_reverse.o
> >  OBJS-$(CONFIG_ARNNDN_FILTER)                 += af_arnndn.o
> > +OBJS-$(CONFIG_ASDR_FILTER)                   += af_asdr.o
> >  OBJS-$(CONFIG_ASEGMENT_FILTER)               += f_segment.o
> >  OBJS-$(CONFIG_ASELECT_FILTER)                += f_select.o
> >  OBJS-$(CONFIG_ASENDCMD_FILTER)               += f_sendcmd.o
> > diff --git a/libavfilter/af_asdr.c b/libavfilter/af_asdr.c
> > new file mode 100644
> > index 0000000000..25032445cd
> > --- /dev/null
> > +++ b/libavfilter/af_asdr.c
> > @@ -0,0 +1,197 @@
> > +/*
> > + * Copyright (c) 2021 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 "libavutil/channel_layout.h"
> > +#include "libavutil/common.h"
> > +#include "libavutil/opt.h"
> > +
> > +#include "audio.h"
> > +#include "avfilter.h"
> > +#include "formats.h"
> > +#include "filters.h"
> > +#include "internal.h"
> > +
> > +typedef struct AudioSDRContext {
> > +    int channels;
> > +    int64_t pts;
> > +    double *sum_u;
> > +    double *sum_uv;
> > +
> > +    AVFrame *cache[2];
> > +} AudioSDRContext;
> > +
> > +static int query_formats(AVFilterContext *ctx)
> > +{
> > +    static const enum AVSampleFormat sample_fmts[] = {
> > +        AV_SAMPLE_FMT_DBLP,
> > +        AV_SAMPLE_FMT_NONE
> > +    };
> > +    int ret = ff_set_common_all_channel_counts(ctx);
> > +    if (ret < 0)
> > +        return ret;
> > +
> > +    ret = ff_set_common_formats_from_list(ctx, sample_fmts);
> > +    if (ret < 0)
> > +        return ret;
> > +
> > +    return ff_set_common_all_samplerates(ctx);
> > +}
> > +
> > +static void sdr(AVFilterContext *ctx, const AVFrame *u, const AVFrame
> *v)
> > +{
> > +    AudioSDRContext *s = ctx->priv;
> > +
> > +    for (int ch = 0; ch < u->channels; ch++) {
> > +        const double *const us = (double *)u->extended_data[ch];
> > +        const double *const vs = (double *)v->extended_data[ch];
> > +        double sum_uv = s->sum_uv[ch];
> > +        double sum_u = s->sum_u[ch];
> > +
> > +        for (int n = 0; n < u->nb_samples; n++) {
> > +            sum_u  += us[n] * us[n];
> > +            sum_uv += (us[n] - vs[n]) * (us[n] - vs[n]);
> > +        }
> > +
> > +        s->sum_uv[ch] = sum_uv;
> > +        s->sum_u[ch]  = sum_u;
> > +    }
> > +}
> > +
> > +static int activate(AVFilterContext *ctx)
> > +{
> > +    AudioSDRContext *s = ctx->priv;
> > +    int ret, status;
> > +    int available;
> > +    int64_t pts;
> > +
> > +    FF_FILTER_FORWARD_STATUS_BACK_ALL(ctx->outputs[0], ctx);
> > +
> > +    available = FFMIN(ff_inlink_queued_samples(ctx->inputs[0]),
> ff_inlink_queued_samples(ctx->inputs[1]));
> > +    if (available > 0) {
> > +        AVFrame *out;
> > +
> > +        for (int i = 0; i < 2; i++) {
> > +            ret = ff_inlink_consume_samples(ctx->inputs[i], available,
> available, &s->cache[i]);
> > +            if (ret > 0) {
> > +                if (s->pts == AV_NOPTS_VALUE)
> > +                    s->pts = s->cache[i]->pts;
> > +            }
> > +        }
> > +
> > +        sdr(ctx, s->cache[0], s->cache[1]);
> > +
> > +        av_frame_free(&s->cache[1]);
> > +        out = s->cache[0];
> > +        out->nb_samples = available;
> > +        out->pts = s->pts;
> > +        s->pts += available;
> > +        s->cache[0] = NULL;
> > +
> > +        return ff_filter_frame(ctx->outputs[0], out);
> > +    }
>
> Here, you need an else for the case where one input has samples, to call
> ff_inlink_request_frame().
>

Yes, it is called down bellow few lines.


>
> > +
> > +    for (int i = 0; i < 2; i++) {
> > +        if (ff_inlink_acknowledge_status(ctx->inputs[i], &status,
> &pts)) {
> > +            ff_outlink_set_status(ctx->outputs[0], status, pts);
> > +            return 0;
> > +        }
> > +    }
> > +
>
> > +    if (ff_inlink_queued_samples(ctx->inputs[0]) > 0 &&
> > +        ff_inlink_queued_samples(ctx->inputs[1]) > 0) {
>
> This condition can never be true, since you just consumed all the
> samples from one of the inputs.
>
> > +        ff_filter_set_ready(ctx, 10);
> > +        return 0;
> > +    }
> > +
> > +    if (ff_outlink_frame_wanted(ctx->outputs[0])) {
> > +        for (int i = 0; i < 2; i++) {
> > +            if (ff_inlink_queued_samples(ctx->inputs[i]) > 0)
> > +                continue;
> > +            ff_inlink_request_frame(ctx->inputs[i]);
> > +        }
> > +        return 0;
> > +    }
> > +
> > +    return FFERROR_NOT_READY;
> > +}
> > +
> > +static int config_output(AVFilterLink *outlink)
> > +{
> > +    AVFilterContext *ctx = outlink->src;
> > +    AVFilterLink *inlink = ctx->inputs[0];
> > +    AudioSDRContext *s = ctx->priv;
> > +
> > +    s->pts = AV_NOPTS_VALUE;
> > +
> > +    s->channels = inlink->channels;
> > +    outlink->format = inlink->format;
> > +    outlink->channels = inlink->channels;
> > +
> > +    s->sum_u  = av_calloc(outlink->channels, sizeof(*s->sum_u));
> > +    s->sum_uv = av_calloc(outlink->channels, sizeof(*s->sum_uv));
> > +    if (!s->sum_u || !s->sum_uv)
> > +        return AVERROR(ENOMEM);
> > +
> > +    return 0;
> > +}
> > +
> > +static av_cold void uninit(AVFilterContext *ctx)
> > +{
> > +    AudioSDRContext *s = ctx->priv;
> > +
> > +    for (int ch = 0; ch < s->channels; ch++)
> > +        av_log(ctx, AV_LOG_INFO, "SDR ch%d: %g dB\n", ch, 20. *
> log10(s->sum_u[ch] / s->sum_uv[ch]));
> > +
> > +    av_frame_free(&s->cache[0]);
> > +    av_frame_free(&s->cache[1]);
> > +
> > +    av_freep(&s->sum_u);
> > +    av_freep(&s->sum_uv);
> > +}
> > +
> > +static const AVFilterPad inputs[] = {
> > +    {
> > +        .name = "input0",
> > +        .type = AVMEDIA_TYPE_AUDIO,
> > +    },
> > +    {
> > +        .name = "input1",
> > +        .type = AVMEDIA_TYPE_AUDIO,
> > +    },
> > +};
> > +
> > +static const AVFilterPad outputs[] = {
> > +    {
> > +        .name         = "default",
> > +        .type         = AVMEDIA_TYPE_AUDIO,
> > +        .config_props = config_output,
> > +    },
> > +};
> > +
> > +const AVFilter ff_af_asdr = {
> > +    .name           = "asdr",
> > +    .description    = NULL_IF_CONFIG_SMALL("Measure Audio
> Signal-to-Distortion Ratio."),
> > +    .priv_size      = sizeof(AudioSDRContext),
> > +    .query_formats  = query_formats,
> > +    .activate       = activate,
> > +    .uninit         = uninit,
> > +    FILTER_INPUTS(inputs),
> > +    FILTER_OUTPUTS(outputs),
> > +};
> > diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> > index 73a0bf9c44..7234ca6dbe 100644
> > --- a/libavfilter/allfilters.c
> > +++ b/libavfilter/allfilters.c
> > @@ -75,6 +75,7 @@ extern const AVFilter ff_af_arealtime;
> >  extern const AVFilter ff_af_aresample;
> >  extern const AVFilter ff_af_areverse;
> >  extern const AVFilter ff_af_arnndn;
> > +extern const AVFilter ff_af_asdr;
> >  extern const AVFilter ff_af_asegment;
> >  extern const AVFilter ff_af_aselect;
> >  extern const AVFilter ff_af_asendcmd;
>
> Regards,
>
> --
>   Nicolas George
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
Nicolas George Sept. 12, 2021, 9:05 p.m. UTC | #3
Paul B Mahol (12021-09-12):
> Yes, it is called down bellow few lines.

Indeed, but only if frame_wanted is set on the output. And it is conform
with all that is currently done, but there is something missing in all
filters with multiple inputs to make them work with a purely
input-driven scheme. I need to think about it. Do not consider this
blocking.

Regards,
Paul B Mahol Sept. 12, 2021, 9:16 p.m. UTC | #4
On Sun, Sep 12, 2021 at 11:06 PM Nicolas George <george@nsup.org> wrote:

> Paul B Mahol (12021-09-12):
> > Yes, it is called down bellow few lines.
>
> Indeed, but only if frame_wanted is set on the output. And it is conform
> with all that is currently done, but there is something missing in all
> filters with multiple inputs to make them work with a purely
> input-driven scheme. I need to think about it. Do not consider this
> blocking.
>

Why purely input-driven scheme is wanted/needed to be supported?


>
> Regards,
>
> --
>   Nicolas George
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
Nicolas George Sept. 13, 2021, 6:31 a.m. UTC | #5
Paul B Mahol (12021-09-12):
> Why purely input-driven scheme is wanted/needed to be supported?

Consider this:

input -----> scale -----> sink

If you add frames to the input, they will reach the sink. It can even be
something else than a buffersink and still work. No need to request a
frame on the sink.

OTOH:

input -----> vstack -----> sink
               /
    testsrc --/

If you add frames to the input, they will accumulate in the link before
vstack, because no frame will be requested from the sink.

I think I forgot one rule for filters with multiple inputs: having
enough frames on one input should be considered a sign output is wanted,
and trigger requesting frames on other outputs.

But I am not 100% sure I did not already consider this and discard it
for a reason I forgot, so I need to think about it.

Regards,
Paul B Mahol Oct. 9, 2021, 11:18 a.m. UTC | #6
Will apply improved version with documentation additions shortly.
diff mbox series

Patch

diff --git a/doc/filters.texi b/doc/filters.texi
index 8f20ccf8c6..6af7344820 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -2531,6 +2531,13 @@  noise removed from input signal.
 
 This filter supports the all above options as @ref{commands}.
 
+@section asdr
+Measure Audio Signal-to-Distortion Ratio.
+
+This filter takes two audio streams for input, and outputs first
+audio stream.
+Results are in dB per channel at end of either input.
+
 @section asetnsamples
 
 Set the number of samples per each output audio frame.
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 76c65c3f42..865252ef3f 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -82,6 +82,7 @@  OBJS-$(CONFIG_AREALTIME_FILTER)              += f_realtime.o
 OBJS-$(CONFIG_ARESAMPLE_FILTER)              += af_aresample.o
 OBJS-$(CONFIG_AREVERSE_FILTER)               += f_reverse.o
 OBJS-$(CONFIG_ARNNDN_FILTER)                 += af_arnndn.o
+OBJS-$(CONFIG_ASDR_FILTER)                   += af_asdr.o
 OBJS-$(CONFIG_ASEGMENT_FILTER)               += f_segment.o
 OBJS-$(CONFIG_ASELECT_FILTER)                += f_select.o
 OBJS-$(CONFIG_ASENDCMD_FILTER)               += f_sendcmd.o
diff --git a/libavfilter/af_asdr.c b/libavfilter/af_asdr.c
new file mode 100644
index 0000000000..25032445cd
--- /dev/null
+++ b/libavfilter/af_asdr.c
@@ -0,0 +1,197 @@ 
+/*
+ * Copyright (c) 2021 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 "libavutil/channel_layout.h"
+#include "libavutil/common.h"
+#include "libavutil/opt.h"
+
+#include "audio.h"
+#include "avfilter.h"
+#include "formats.h"
+#include "filters.h"
+#include "internal.h"
+
+typedef struct AudioSDRContext {
+    int channels;
+    int64_t pts;
+    double *sum_u;
+    double *sum_uv;
+
+    AVFrame *cache[2];
+} AudioSDRContext;
+
+static int query_formats(AVFilterContext *ctx)
+{
+    static const enum AVSampleFormat sample_fmts[] = {
+        AV_SAMPLE_FMT_DBLP,
+        AV_SAMPLE_FMT_NONE
+    };
+    int ret = ff_set_common_all_channel_counts(ctx);
+    if (ret < 0)
+        return ret;
+
+    ret = ff_set_common_formats_from_list(ctx, sample_fmts);
+    if (ret < 0)
+        return ret;
+
+    return ff_set_common_all_samplerates(ctx);
+}
+
+static void sdr(AVFilterContext *ctx, const AVFrame *u, const AVFrame *v)
+{
+    AudioSDRContext *s = ctx->priv;
+
+    for (int ch = 0; ch < u->channels; ch++) {
+        const double *const us = (double *)u->extended_data[ch];
+        const double *const vs = (double *)v->extended_data[ch];
+        double sum_uv = s->sum_uv[ch];
+        double sum_u = s->sum_u[ch];
+
+        for (int n = 0; n < u->nb_samples; n++) {
+            sum_u  += us[n] * us[n];
+            sum_uv += (us[n] - vs[n]) * (us[n] - vs[n]);
+        }
+
+        s->sum_uv[ch] = sum_uv;
+        s->sum_u[ch]  = sum_u;
+    }
+}
+
+static int activate(AVFilterContext *ctx)
+{
+    AudioSDRContext *s = ctx->priv;
+    int ret, status;
+    int available;
+    int64_t pts;
+
+    FF_FILTER_FORWARD_STATUS_BACK_ALL(ctx->outputs[0], ctx);
+
+    available = FFMIN(ff_inlink_queued_samples(ctx->inputs[0]), ff_inlink_queued_samples(ctx->inputs[1]));
+    if (available > 0) {
+        AVFrame *out;
+
+        for (int i = 0; i < 2; i++) {
+            ret = ff_inlink_consume_samples(ctx->inputs[i], available, available, &s->cache[i]);
+            if (ret > 0) {
+                if (s->pts == AV_NOPTS_VALUE)
+                    s->pts = s->cache[i]->pts;
+            }
+        }
+
+        sdr(ctx, s->cache[0], s->cache[1]);
+
+        av_frame_free(&s->cache[1]);
+        out = s->cache[0];
+        out->nb_samples = available;
+        out->pts = s->pts;
+        s->pts += available;
+        s->cache[0] = NULL;
+
+        return ff_filter_frame(ctx->outputs[0], out);
+    }
+
+    for (int i = 0; i < 2; i++) {
+        if (ff_inlink_acknowledge_status(ctx->inputs[i], &status, &pts)) {
+            ff_outlink_set_status(ctx->outputs[0], status, pts);
+            return 0;
+        }
+    }
+
+    if (ff_inlink_queued_samples(ctx->inputs[0]) > 0 &&
+        ff_inlink_queued_samples(ctx->inputs[1]) > 0) {
+        ff_filter_set_ready(ctx, 10);
+        return 0;
+    }
+
+    if (ff_outlink_frame_wanted(ctx->outputs[0])) {
+        for (int i = 0; i < 2; i++) {
+            if (ff_inlink_queued_samples(ctx->inputs[i]) > 0)
+                continue;
+            ff_inlink_request_frame(ctx->inputs[i]);
+        }
+        return 0;
+    }
+
+    return FFERROR_NOT_READY;
+}
+
+static int config_output(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    AVFilterLink *inlink = ctx->inputs[0];
+    AudioSDRContext *s = ctx->priv;
+
+    s->pts = AV_NOPTS_VALUE;
+
+    s->channels = inlink->channels;
+    outlink->format = inlink->format;
+    outlink->channels = inlink->channels;
+
+    s->sum_u  = av_calloc(outlink->channels, sizeof(*s->sum_u));
+    s->sum_uv = av_calloc(outlink->channels, sizeof(*s->sum_uv));
+    if (!s->sum_u || !s->sum_uv)
+        return AVERROR(ENOMEM);
+
+    return 0;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    AudioSDRContext *s = ctx->priv;
+
+    for (int ch = 0; ch < s->channels; ch++)
+        av_log(ctx, AV_LOG_INFO, "SDR ch%d: %g dB\n", ch, 20. * log10(s->sum_u[ch] / s->sum_uv[ch]));
+
+    av_frame_free(&s->cache[0]);
+    av_frame_free(&s->cache[1]);
+
+    av_freep(&s->sum_u);
+    av_freep(&s->sum_uv);
+}
+
+static const AVFilterPad inputs[] = {
+    {
+        .name = "input0",
+        .type = AVMEDIA_TYPE_AUDIO,
+    },
+    {
+        .name = "input1",
+        .type = AVMEDIA_TYPE_AUDIO,
+    },
+};
+
+static const AVFilterPad outputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_AUDIO,
+        .config_props = config_output,
+    },
+};
+
+const AVFilter ff_af_asdr = {
+    .name           = "asdr",
+    .description    = NULL_IF_CONFIG_SMALL("Measure Audio Signal-to-Distortion Ratio."),
+    .priv_size      = sizeof(AudioSDRContext),
+    .query_formats  = query_formats,
+    .activate       = activate,
+    .uninit         = uninit,
+    FILTER_INPUTS(inputs),
+    FILTER_OUTPUTS(outputs),
+};
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 73a0bf9c44..7234ca6dbe 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -75,6 +75,7 @@  extern const AVFilter ff_af_arealtime;
 extern const AVFilter ff_af_aresample;
 extern const AVFilter ff_af_areverse;
 extern const AVFilter ff_af_arnndn;
+extern const AVFilter ff_af_asdr;
 extern const AVFilter ff_af_asegment;
 extern const AVFilter ff_af_aselect;
 extern const AVFilter ff_af_asendcmd;