diff mbox series

[FFmpeg-devel] avfilter: add stereoupmix

Message ID 20201209172007.14867-1-onemda@gmail.com
State New
Headers show
Series [FFmpeg-devel] avfilter: add stereoupmix
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 warning Make fate failed

Commit Message

Paul B Mahol Dec. 9, 2020, 5:20 p.m. UTC
Signed-off-by: Paul B Mahol <onemda@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

Comments

Lingjiang Fang Dec. 14, 2020, 4:13 a.m. UTC | #1
On Wed,  9 Dec 2020 18:20:07 +0100
Paul B Mahol <onemda@gmail.com> wrote:

>Signed-off-by: Paul B Mahol <onemda@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
> 
>+@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

>+
>+The filter accepts the following options:
>+
>+@table @option
>+@item upmix
>+Set the upmix mode. Can be one of the following:
>+@table @samp
>+@item 2.1
>+@item 3.0
>+@item 3.1
>+@item 4.0
>+@item 4.1
>+@item 5.0
>+@item 5.1
>+@end table
>+Default value is @var{5.1}.
>+
>+@item center
>+Set the center audio strength. Allowed range is from 0.0 to 1.0.
>+Default value is 0.5.
>+
>+@item ambience
>+Set the ambience audio strength. Allowed range is from 0.0 to 1.0.
>+Default value is 0.5.
>+@end table
>+
>+@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
Paul B Mahol Dec. 14, 2020, 9:50 a.m. UTC | #2
On Mon, Dec 14, 2020 at 5:17 AM Lingjiang Fang <vacingfang@foxmail.com>
wrote:

> On Wed,  9 Dec 2020 18:20:07 +0100
> Paul B Mahol <onemda@gmail.com> wrote:
>
> >Signed-off-by: Paul B Mahol <onemda@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
> >
> >+@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:
> >+
> >+@table @option
> >+@item upmix
> >+Set the upmix mode. Can be one of the following:
> >+@table @samp
> >+@item 2.1
> >+@item 3.0
> >+@item 3.1
> >+@item 4.0
> >+@item 4.1
> >+@item 5.0
> >+@item 5.1
> >+@end table
> >+Default value is @var{5.1}.
> >+
> >+@item center
> >+Set the center audio strength. Allowed range is from 0.0 to 1.0.
> >+Default value is 0.5.
> >+
> >+@item ambience
> >+Set the ambience audio strength. Allowed range is from 0.0 to 1.0.
> >+Default value is 0.5.
> >+@end table
> >+
> >+@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@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
diff mbox series

Patch

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
 
+@section stereoupmix
+Upmix stereo audio.
+
+This filter upmixes stereo audio using adaptive panning method.
+
+The filter accepts the following options:
+
+@table @option
+@item upmix
+Set the upmix mode. Can be one of the following:
+@table @samp
+@item 2.1
+@item 3.0
+@item 3.1
+@item 4.0
+@item 4.1
+@item 5.0
+@item 5.1
+@end table
+Default value is @var{5.1}.
+
+@item center
+Set the center audio strength. Allowed range is from 0.0 to 1.0.
+Default value is 0.5.
+
+@item ambience
+Set the ambience audio strength. Allowed range is from 0.0 to 1.0.
+Default value is 0.5.
+@end table
+
+@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;