[FFmpeg-devel] [PATCH] avfilter: add stereoupmix
Paul B Mahol
onemda at gmail.com
Mon Dec 14 11:50:06 EET 2020
On Mon, Dec 14, 2020 at 5:17 AM Lingjiang Fang <vacingfang at foxmail.com>
wrote:
> On Wed, 9 Dec 2020 18:20:07 +0100
> Paul B Mahol <onemda at gmail.com> wrote:
>
> >Signed-off-by: Paul B Mahol <onemda at gmail.com>
> >---
> > doc/filters.texi | 34 ++++
> > libavfilter/Makefile | 1 +
> > libavfilter/af_stereoupmix.c | 352 +++++++++++++++++++++++++++++++++++
> > libavfilter/allfilters.c | 1 +
> > 4 files changed, 388 insertions(+)
> > create mode 100644 libavfilter/af_stereoupmix.c
> >
> >diff --git a/doc/filters.texi b/doc/filters.texi
> >index 9dfe95f40d..325753c8f4 100644
> >--- a/doc/filters.texi
> >+++ b/doc/filters.texi
> >@@ -5817,6 +5817,40 @@ Convert M/S signal to L/R:
> > @end example
> > @end itemize
> >
> >+ at section stereoupmix
> >+Upmix stereo audio.
> >+
> >+This filter upmixes stereo audio using adaptive panning method.
>
> As far as I know, we have a filter surround has similar function,
> can you describe the difference between these two filter.
>
> sorry if asked stupid question
>
This one is zero latency filter and also is more than 10x faster
and done completely in time-domain.
For surround filter, you give better output only with overlap option set to
higher value >= 0.875
>
> >+
> >+The filter accepts the following options:
> >+
> >+ at table @option
> >+ at item upmix
> >+Set the upmix mode. Can be one of the following:
> >+ at table @samp
> >+ at item 2.1
> >+ at item 3.0
> >+ at item 3.1
> >+ at item 4.0
> >+ at item 4.1
> >+ at item 5.0
> >+ at item 5.1
> >+ at end table
> >+Default value is @var{5.1}.
> >+
> >+ at item center
> >+Set the center audio strength. Allowed range is from 0.0 to 1.0.
> >+Default value is 0.5.
> >+
> >+ at item ambience
> >+Set the ambience audio strength. Allowed range is from 0.0 to 1.0.
> >+Default value is 0.5.
> >+ at end table
> >+
> >+ at subsection Commands
> >+
> >+This filter supports the all above options except @code{upmix} as
> >@ref{commands}. +
> > @section stereowiden
> >
> > This filter enhance the stereo effect by suppressing signal common to
> > both
> >diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> >index 1af85a71a0..a9d76a1eaf 100644
> >--- a/libavfilter/Makefile
> >+++ b/libavfilter/Makefile
> >@@ -145,6 +145,7 @@ OBJS-$(CONFIG_SILENCEREMOVE_FILTER) +=
> >af_silenceremove.o
> > OBJS-$(CONFIG_SOFALIZER_FILTER) += af_sofalizer.o
> > OBJS-$(CONFIG_SPEECHNORM_FILTER) += af_speechnorm.o
> > OBJS-$(CONFIG_STEREOTOOLS_FILTER) += af_stereotools.o
> >+OBJS-$(CONFIG_STEREOUPMIX_FILTER) += af_stereoupmix.o
> > OBJS-$(CONFIG_STEREOWIDEN_FILTER) += af_stereowiden.o
> > OBJS-$(CONFIG_SUPEREQUALIZER_FILTER) += af_superequalizer.o
> > OBJS-$(CONFIG_SURROUND_FILTER) += af_surround.o
> >diff --git a/libavfilter/af_stereoupmix.c
> >b/libavfilter/af_stereoupmix.c new file mode 100644
> >index 0000000000..813f21b088
> >--- /dev/null
> >+++ b/libavfilter/af_stereoupmix.c
> >@@ -0,0 +1,352 @@
> >+/*
> >+ * 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 "libavutil/avassert.h"
> >+#include "libavutil/channel_layout.h"
> >+#include "libavutil/opt.h"
> >+#include "avfilter.h"
> >+#include "audio.h"
> >+#include "formats.h"
> >+
> >+enum UpmixMode {
> >+ UPMIX_2_1,
> >+ UPMIX_3_0,
> >+ UPMIX_3_1,
> >+ UPMIX_4_0,
> >+ UPMIX_4_1,
> >+ UPMIX_5_0,
> >+ UPMIX_5_1,
> >+ NB_UPMIX
> >+};
> >+
> >+typedef struct StereoUpmixContext {
> >+ const AVClass *class;
> >+
> >+ int upmix;
> >+ float center;
> >+ float ambience;
> >+
> >+ uint64_t out_layout;
> >+
> >+ float fl, fr;
> >+ float y;
> >+ float pk;
> >+ float wl, wr;
> >+
> >+ float a[2];
> >+ float b[3];
> >+ float z[2];
> >+} StereoUpmixContext;
> >+
> >+#define OFFSET(x) offsetof(StereoUpmixContext, x)
> >+#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
> >+#define TFLAGS
>
> >AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
> >+ +static const AVOption stereoupmix_options[] = {
> >+ { "upmix", "set upmix mode", OFFSET(upmix),
> >AV_OPT_TYPE_INT, {.i64=UPMIX_5_1}, 0, NB_UPMIX-1, FLAGS, "upmix" },
> >+ { "2.1", NULL, 0,
> >AV_OPT_TYPE_CONST, {.i64=UPMIX_2_1}, 0, 0, FLAGS, "upmix" },
> >+ { "3.0", NULL, 0,
> >AV_OPT_TYPE_CONST, {.i64=UPMIX_3_0}, 0, 0, FLAGS, "upmix" },
> >+ { "3.1", NULL, 0,
> >AV_OPT_TYPE_CONST, {.i64=UPMIX_3_1}, 0, 0, FLAGS, "upmix" },
> >+ { "4.0", NULL, 0,
> >AV_OPT_TYPE_CONST, {.i64=UPMIX_4_0}, 0, 0, FLAGS, "upmix" },
> >+ { "4.1", NULL, 0,
> >AV_OPT_TYPE_CONST, {.i64=UPMIX_4_1}, 0, 0, FLAGS, "upmix" },
> >+ { "5.0", NULL, 0,
> >AV_OPT_TYPE_CONST, {.i64=UPMIX_5_0}, 0, 0, FLAGS, "upmix" },
> >+ { "5.1", NULL, 0,
> >AV_OPT_TYPE_CONST, {.i64=UPMIX_5_1}, 0, 0, FLAGS, "upmix" },
> >+ { "center", "set center strength", OFFSET(center),
> >AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, TFLAGS },
> >+ { "ambience", "set ambience strength", OFFSET(ambience),
> >AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, TFLAGS },
> >+ { NULL }
> >+};
> >+
> >+AVFILTER_DEFINE_CLASS(stereoupmix);
> >+
> >+static av_cold int init(AVFilterContext *ctx)
> >+{
> >+ StereoUpmixContext *s = ctx->priv;
> >+
> >+ s->wl = s->wr = M_SQRT1_2;
> >+
> >+ switch (s->upmix) {
> >+ case UPMIX_2_1:
> >+ s->out_layout = AV_CH_LAYOUT_2POINT1;
> >+ break;
> >+ case UPMIX_3_0:
> >+ s->out_layout = AV_CH_LAYOUT_SURROUND;
> >+ break;
> >+ case UPMIX_3_1:
> >+ s->out_layout = AV_CH_LAYOUT_3POINT1;
> >+ break;
> >+ case UPMIX_4_0:
> >+ s->out_layout = AV_CH_LAYOUT_4POINT0;
> >+ break;
> >+ case UPMIX_4_1:
> >+ s->out_layout = AV_CH_LAYOUT_4POINT1;
> >+ break;
> >+ case UPMIX_5_0:
> >+ s->out_layout = AV_CH_LAYOUT_5POINT0_BACK;
> >+ break;
> >+ case UPMIX_5_1:
> >+ s->out_layout = AV_CH_LAYOUT_5POINT1_BACK;
> >+ break;
> >+ default:
> >+ av_assert0(0);
> >+ }
> >+
> >+ return 0;
> >+}
> >+
> >+static int query_formats(AVFilterContext *ctx)
> >+{
> >+ StereoUpmixContext *s = ctx->priv;
> >+ AVFilterFormats *formats = NULL;
> >+ AVFilterChannelLayouts *layouts = NULL;
> >+ int ret;
> >+
> >+ ret = ff_add_format(&formats, AV_SAMPLE_FMT_FLT);
> >+ if (ret)
> >+ return ret;
> >+ ret = ff_set_common_formats(ctx, formats);
> >+ if (ret)
> >+ return ret;
> >+
> >+ layouts = NULL;
> >+ ret = ff_add_channel_layout(&layouts, s->out_layout);
> >+ if (ret)
> >+ return ret;
> >+
> >+ ret = ff_channel_layouts_ref(layouts,
> >&ctx->outputs[0]->incfg.channel_layouts);
> >+ if (ret)
> >+ return ret;
> >+
> >+ layouts = NULL;
> >+ ret = ff_add_channel_layout(&layouts, AV_CH_LAYOUT_STEREO);
> >+ if (ret)
> >+ return ret;
> >+
> >+ ret = ff_channel_layouts_ref(layouts,
> >&ctx->inputs[0]->outcfg.channel_layouts);
> >+ if (ret)
> >+ return ret;
> >+
> >+ formats = ff_all_samplerates();
> >+ if (!formats)
> >+ return AVERROR(ENOMEM);
> >+ return ff_set_common_samplerates(ctx, formats);
> >+}
> >+
> >+static void upmix(AVFilterContext *ctx, AVFrame *out, AVFrame *in)
> >+{
> >+ StereoUpmixContext *s = ctx->priv;
> >+ const float center = s->center;
> >+ const float ambience = s->ambience;
> >+ const float *src = (const float *)in->data[0];
> >+ const float b0 = s->b[0];
> >+ const float b1 = s->b[1];
> >+ const float b2 = s->b[2];
> >+ const float a1 = s->a[0];
> >+ const float a2 = s->a[1];
> >+ float *dst = (float *)out->data[0];
> >+ float wl = s->wl, wr = s->wr;
> >+ float fl = s->fl, fr = s->fr;
> >+ float pk = s->pk;
> >+ float y = s->y;
> >+ float z0 = s->z[0];
> >+ float z1 = s->z[1];
> >+
> >+ for (int n = 0; n < in->nb_samples; n++) {
> >+ float nwl, nwr;
> >+ float clr, cl, cr;
> >+ float slr, sl, sr;
> >+ float beta, gamma, q;
> >+ float ro = 0.001f;
> >+ float sing, cosb, sinb;
> >+ float FL, FR, FC, LFE, SL, SR, SC;
> >+ float mid, side;
> >+ float g, ig2, gg;
> >+
> >+ nwl = wl + ro * y * (fl - wl * y);
> >+ nwr = wr + ro * y * (fr - wr * y);
> >+ fl = src[n * 2 ];
> >+ fr = src[n * 2 + 1];
> >+ y = nwl * fl + nwr * fr;
> >+ q = nwr * fl - nwl * fr;
> >+ pk = pk + ro * (2.f * fl * fr - (fl * fl + fr * fr) * pk);
> >+ av_assert0(pk >= -1.f && pk <= 1.f);
> >+ beta = asinf(1.f - fmaxf(pk, 0.f));
> >+ gamma = acosf(1.f + fminf(pk, 0.f));
> >+ cosb = cosf(beta);
> >+ sinb = sinf(beta);
> >+ sing = sinf(gamma);
> >+ g = cosb * cosb;
> >+ gg = sinb * sinb;
> >+ ig2 = (1.f - g) * (1.f - g);
> >+
> >+ clr = (nwr * nwr - nwl * nwl) * cosb;
> >+ slr = (nwr * nwr - nwl * nwl) * sing;
> >+ cl = clr < 0 ? -clr : 0.f;
> >+ cr = clr >= 0 ? clr : 0.f;
> >+ sl = slr < 0 ? -slr : 0.f;
> >+ sr = slr >= 0 ? slr : 0.f;
> >+ mid = 0.5f * (fl + fr);
> >+ side = 0.5f * (fr - fl);
> >+ FC = cosb * nwl * nwr * y + gg * mid * center;
> >+ FL = cl * y + g * nwl * q + ig2 * fl;
> >+ FR = cr * y + g * nwr * q + ig2 * fr;
> >+ SC = sinb * q;
> >+ SL = SC + sl * side * ambience;
> >+ SR = SC + sr * side * ambience;
> >+ LFE = FC * b0 + z0;
> >+ z0 = b1 * FC + z1 + a1 * LFE;
> >+ z1 = b2 * FC + a2 * LFE;
> >+
> >+ switch (s->upmix) {
> >+ case UPMIX_2_1:
> >+ dst[n * 3 + 0] = fl;
> >+ dst[n * 3 + 1] = fr;
> >+ dst[n * 3 + 2] = LFE;
> >+ break;
> >+ case UPMIX_3_0:
> >+ dst[n * 3 + 0] = fl;
> >+ dst[n * 3 + 1] = fr;
> >+ dst[n * 3 + 2] = FC;
> >+ break;
> >+ case UPMIX_3_1:
> >+ dst[n * 4 + 0] = fl;
> >+ dst[n * 4 + 1] = fr;
> >+ dst[n * 4 + 2] = FC;
> >+ dst[n * 4 + 3] = LFE;
> >+ break;
> >+ case UPMIX_4_0:
> >+ dst[n * 4 + 0] = FL;
> >+ dst[n * 4 + 1] = FR;
> >+ dst[n * 4 + 2] = FC;
> >+ dst[n * 4 + 3] = SC;
> >+ break;
> >+ case UPMIX_4_1:
> >+ dst[n * 5 + 0] = FL;
> >+ dst[n * 5 + 1] = FR;
> >+ dst[n * 5 + 2] = FC;
> >+ dst[n * 5 + 3] = LFE;
> >+ dst[n * 5 + 4] = SC;
> >+ break;
> >+ case UPMIX_5_0:
> >+ dst[n * 5 + 0] = FL;
> >+ dst[n * 5 + 1] = FR;
> >+ dst[n * 5 + 2] = FC;
> >+ dst[n * 5 + 3] = SL;
> >+ dst[n * 5 + 4] = SR;
> >+ break;
> >+ case UPMIX_5_1:
> >+ dst[n * 6 + 0] = FL;
> >+ dst[n * 6 + 1] = FR;
> >+ dst[n * 6 + 2] = FC;
> >+ dst[n * 6 + 3] = LFE;
> >+ dst[n * 6 + 4] = SL;
> >+ dst[n * 6 + 5] = SR;
> >+ break;
> >+ default:
> >+ av_assert0(0);
> >+ }
> >+
> >+ wl = nwl;
> >+ wr = nwr;
> >+ }
> >+
> >+ s->pk = pk;
> >+ s->y = y;
> >+
> >+ s->fl = fl;
> >+ s->fr = fr;
> >+
> >+ s->wl = wl;
> >+ s->wr = wr;
> >+
> >+ s->z[0] = z0;
> >+ s->z[1] = z1;
> >+}
> >+
> >+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
> >+{
> >+ AVFilterContext *ctx = inlink->dst;
> >+ AVFilterLink *outlink = ctx->outputs[0];
> >+ AVFrame *out;
> >+
> >+ out = ff_get_audio_buffer(outlink, in->nb_samples);
> >+ if (!out) {
> >+ av_frame_free(&in);
> >+ return AVERROR(ENOMEM);
> >+ }
> >+ av_frame_copy_props(out, in);
> >+
> >+ upmix(ctx, out, in);
> >+
> >+ av_frame_free(&in);
> >+ return ff_filter_frame(outlink, out);
> >+}
> >+
> >+static int config_output(AVFilterLink *outlink)
> >+{
> >+ AVFilterContext *ctx = outlink->src;
> >+ StereoUpmixContext *s = ctx->priv;
> >+ double w0 = 2. * M_PI * 120. / outlink->sample_rate;
> >+ double alpha = sin(w0) / 2. * sqrt(2.);
> >+ double a0 = 1. + alpha;
> >+
> >+ s->a[0] = 2. * cos(w0);
> >+ s->a[1] = alpha - 1.;
> >+ s->b[0] = (1. - cos(w0)) / 2.;
> >+ s->b[1] = 1. - cos(w0);
> >+ s->b[2] = (1. - cos(w0)) / 2.;
> >+
> >+ s->a[0] /= a0;
> >+ s->a[1] /= a0;
> >+ s->b[0] /= a0;
> >+ s->b[1] /= a0;
> >+ s->b[2] /= a0;
> >+
> >+ return 0;
> >+}
> >+
> >+static const AVFilterPad inputs[] = {
> >+ {
> >+ .name = "default",
> >+ .type = AVMEDIA_TYPE_AUDIO,
> >+ .filter_frame = filter_frame,
> >+ },
> >+ { NULL }
> >+};
> >+
> >+static const AVFilterPad outputs[] = {
> >+ {
> >+ .name = "default",
> >+ .type = AVMEDIA_TYPE_AUDIO,
> >+ .config_props = config_output,
> >+ },
> >+ { NULL }
> >+};
> >+
> >+AVFilter ff_af_stereoupmix = {
> >+ .name = "stereoupmix",
> >+ .description = NULL_IF_CONFIG_SMALL("Upmix stereo audio."),
> >+ .query_formats = query_formats,
> >+ .priv_size = sizeof(StereoUpmixContext),
> >+ .priv_class = &stereoupmix_class,
> >+ .init = init,
> >+ .inputs = inputs,
> >+ .outputs = outputs,
> >+ .process_command = ff_filter_process_command,
> >+};
> >diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> >index 16e1f08a29..bb96ca88ab 100644
> >--- a/libavfilter/allfilters.c
> >+++ b/libavfilter/allfilters.c
> >@@ -139,6 +139,7 @@ extern AVFilter ff_af_silenceremove;
> > extern AVFilter ff_af_sofalizer;
> > extern AVFilter ff_af_speechnorm;
> > extern AVFilter ff_af_stereotools;
> >+extern AVFilter ff_af_stereoupmix;
> > extern AVFilter ff_af_stereowiden;
> > extern AVFilter ff_af_superequalizer;
> > extern AVFilter ff_af_surround;
>
>
> --
> Best regards,
> Lingjiang Fang
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request at ffmpeg.org with subject "unsubscribe".
More information about the ffmpeg-devel
mailing list