This is an automated email from the git hooks/post-receive script.

Git pushed a commit to branch master
in repository ffmpeg.

commit 5c395992f99feb47860e4cc99a0cea2009457870
Author:     Ayoub Nabil Boubagrat 
<[email protected]>
AuthorDate: Thu Aug 6 13:46:58 2026 +0200
Commit:     James Almer <[email protected]>
CommitDate: Fri Aug 7 02:33:33 2026 +0000

    swresample/rematrix: handle top-back channels
    
    top-back channels are currently left unaccounted and can be dropped during 
downmixing.
    
    when the output retains top-front channels, follow IAMF 1.1.0 and fold 
top-back into top-front at 0.707. prefer this path over ear-level rear channels 
to preserve the height layer in x.1.4 to x.1.2 downmixes.
    
    when no matching height output remains, map top-back to back or side 
channels, then fall back to front or mono outputs. handle top-back center 
separately and add direct tests for every matrix path.
    
    Signed-off-by: Ayoub Nabil Boubagrat 
<[email protected]>
---
 libswresample/Makefile         |   3 +-
 libswresample/rematrix.c       |  46 +++++++++++
 libswresample/tests/.gitignore |   1 +
 libswresample/tests/rematrix.c | 170 +++++++++++++++++++++++++++++++++++++++++
 tests/fate/libswresample.mak   |   7 ++
 5 files changed, 226 insertions(+), 1 deletion(-)

diff --git a/libswresample/Makefile b/libswresample/Makefile
index 12fbfc35c1..8149de069f 100644
--- a/libswresample/Makefile
+++ b/libswresample/Makefile
@@ -24,5 +24,6 @@ SHLIBOBJS              += log2_tab.o
 # Windows resource file
 SHLIBOBJS-$(HAVE_GNU_WINDRES) += swresampleres.o
 
-TESTPROGS = swresample \
+TESTPROGS = rematrix \
+            swresample \
             swresample_resample_realloc \
diff --git a/libswresample/rematrix.c b/libswresample/rematrix.c
index 77ec2250a6..269849a427 100644
--- a/libswresample/rematrix.c
+++ b/libswresample/rematrix.c
@@ -127,6 +127,8 @@ static int sane_layout(AVChannelLayout *ch_layout) {
         return 0;
     if(!even(av_channel_layout_subset(ch_layout, (AV_CH_TOP_FRONT_LEFT | 
AV_CH_TOP_FRONT_RIGHT))))
         return 0;
+    if(!even(av_channel_layout_subset(ch_layout, (AV_CH_TOP_BACK_LEFT | 
AV_CH_TOP_BACK_RIGHT))))
+        return 0;
 
     return 1;
 }
@@ -297,6 +299,50 @@ static void build_matrix(const AVChannelLayout 
*in_ch_layout, const AVChannelLay
             av_assert0(0);
     }
 
+    if (unaccounted & AV_CH_TOP_BACK_LEFT) {
+        if (av_channel_layout_index_from_channel(out_ch_layout, 
AV_CHAN_TOP_BACK_CENTER) >= 0) {
+            matrix[TOP_BACK_CENTER][TOP_BACK_LEFT ] += M_SQRT1_2;
+            matrix[TOP_BACK_CENTER][TOP_BACK_RIGHT] += M_SQRT1_2;
+        } else if (av_channel_layout_index_from_channel(out_ch_layout, 
AV_CHAN_TOP_FRONT_LEFT) >= 0) {
+            /* IAMF v1.1.0, Section 7.3.2.1.1. */
+            matrix[TOP_FRONT_LEFT ][TOP_BACK_LEFT ] += M_SQRT1_2;
+            matrix[TOP_FRONT_RIGHT][TOP_BACK_RIGHT] += M_SQRT1_2;
+        } else if (av_channel_layout_index_from_channel(out_ch_layout, 
AV_CHAN_BACK_LEFT) >= 0) {
+            matrix[BACK_LEFT ][TOP_BACK_LEFT ] += 1.0;
+            matrix[BACK_RIGHT][TOP_BACK_RIGHT] += 1.0;
+        } else if (av_channel_layout_index_from_channel(out_ch_layout, 
AV_CHAN_SIDE_LEFT) >= 0) {
+            matrix[SIDE_LEFT ][TOP_BACK_LEFT ] += 1.0;
+            matrix[SIDE_RIGHT][TOP_BACK_RIGHT] += 1.0;
+        } else if (av_channel_layout_index_from_channel(out_ch_layout, 
AV_CHAN_FRONT_LEFT) >= 0) {
+            matrix[FRONT_LEFT ][TOP_BACK_LEFT ] += surround_mix_level;
+            matrix[FRONT_RIGHT][TOP_BACK_RIGHT] += surround_mix_level;
+        } else if (av_channel_layout_index_from_channel(out_ch_layout, 
AV_CHAN_FRONT_CENTER) >= 0) {
+            matrix[FRONT_CENTER][TOP_BACK_LEFT ] += M_SQRT1_2;
+            matrix[FRONT_CENTER][TOP_BACK_RIGHT] += M_SQRT1_2;
+        } else
+            av_assert0(0);
+    }
+
+    /* BS.2127-1 maps U+180 to rear outputs before front outputs. */
+    if (unaccounted & AV_CH_TOP_BACK_CENTER) {
+        if (av_channel_layout_index_from_channel(out_ch_layout, 
AV_CHAN_TOP_BACK_LEFT) >= 0) {
+            matrix[TOP_BACK_LEFT ][TOP_BACK_CENTER] += M_SQRT1_2;
+            matrix[TOP_BACK_RIGHT][TOP_BACK_CENTER] += M_SQRT1_2;
+        } else if (av_channel_layout_index_from_channel(out_ch_layout, 
AV_CHAN_BACK_LEFT) >= 0) {
+            matrix[BACK_LEFT ][TOP_BACK_CENTER] += M_SQRT1_2;
+            matrix[BACK_RIGHT][TOP_BACK_CENTER] += M_SQRT1_2;
+        } else if (av_channel_layout_index_from_channel(out_ch_layout, 
AV_CHAN_SIDE_LEFT) >= 0) {
+            matrix[SIDE_LEFT ][TOP_BACK_CENTER] += M_SQRT1_2;
+            matrix[SIDE_RIGHT][TOP_BACK_CENTER] += M_SQRT1_2;
+        } else if (av_channel_layout_index_from_channel(out_ch_layout, 
AV_CHAN_FRONT_LEFT) >= 0) {
+            matrix[FRONT_LEFT ][TOP_BACK_CENTER] += 0.5;
+            matrix[FRONT_RIGHT][TOP_BACK_CENTER] += 0.5;
+        } else if (av_channel_layout_index_from_channel(out_ch_layout, 
AV_CHAN_FRONT_CENTER) >= 0)
+            matrix[FRONT_CENTER][TOP_BACK_CENTER] += 0.5;
+        else
+            av_assert0(0);
+    }
+
     /* mix LFE into front left/right or center */
     if (unaccounted & AV_CH_LOW_FREQUENCY) {
         if (av_channel_layout_index_from_channel(out_ch_layout, 
AV_CHAN_FRONT_CENTER) >= 0) {
diff --git a/libswresample/tests/.gitignore b/libswresample/tests/.gitignore
index 2dc986bd0e..e156fc35de 100644
--- a/libswresample/tests/.gitignore
+++ b/libswresample/tests/.gitignore
@@ -1 +1,2 @@
+/rematrix
 /swresample
diff --git a/libswresample/tests/rematrix.c b/libswresample/tests/rematrix.c
new file mode 100644
index 0000000000..4caf6d9678
--- /dev/null
+++ b/libswresample/tests/rematrix.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2026 Ayoub Nabil Boubagrat
+ *
+ * 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 <limits.h>
+#include <stdio.h>
+
+#include "libavutil/channel_layout.h"
+#include "libavutil/mathematics.h"
+#include "libswresample/swresample.h"
+
+/* swr_build_matrix2() accesses an internal SWR_CH_MAX by SWR_CH_MAX matrix. */
+#define MATRIX_STRIDE 64
+
+static int check_coefficient_with_slev(const AVChannelLayout *in_layout,
+                                       const AVChannelLayout *out_layout,
+                                       enum AVChannel in_channel,
+                                       enum AVChannel out_channel,
+                                       double surround_mix_level,
+                                       double expected)
+{
+    double matrix[MATRIX_STRIDE * MATRIX_STRIDE] = { 0 };
+    char in_name[16], out_name[16];
+    int in, out, ret;
+
+    av_channel_name(in_name, sizeof(in_name), in_channel);
+    av_channel_name(out_name, sizeof(out_name), out_channel);
+
+    if (in_layout->nb_channels > MATRIX_STRIDE ||
+        out_layout->nb_channels > MATRIX_STRIDE) {
+        fprintf(stderr, "channel layout exceeds matrix capacity\n");
+        return 1;
+    }
+
+    in  = av_channel_layout_index_from_channel(in_layout,  in_channel);
+    out = av_channel_layout_index_from_channel(out_layout, out_channel);
+    if (in < 0) {
+        fprintf(stderr, "input channel %s is not in the input layout\n", 
in_name);
+        return 1;
+    }
+    if (out < 0) {
+        fprintf(stderr, "output channel %s is not in the output layout\n", 
out_name);
+        return 1;
+    }
+
+    /* Disable normalization so the raw downmix gains can be checked. */
+    ret = swr_build_matrix2(in_layout, out_layout, M_SQRT1_2,
+                            surround_mix_level,
+                            0.0, INT_MAX, 1.0, matrix, MATRIX_STRIDE,
+                            AV_MATRIX_ENCODING_NONE, NULL);
+    if (ret < 0) {
+        fprintf(stderr, "swr_build_matrix2 failed with error %d\n", ret);
+        return 1;
+    }
+
+    if (fabs(matrix[out * MATRIX_STRIDE + in] - expected) > 1e-12) {
+        fprintf(stderr, "%s -> %s: expected %.12f, got %.12f\n",
+                in_name, out_name, expected,
+                matrix[out * MATRIX_STRIDE + in]);
+        return 1;
+    }
+
+    return 0;
+}
+
+static int check_coefficient(const AVChannelLayout *in_layout,
+                             const AVChannelLayout *out_layout,
+                             enum AVChannel in_channel,
+                             enum AVChannel out_channel, double expected)
+{
+    return check_coefficient_with_slev(in_layout, out_layout, in_channel,
+                                       out_channel, M_SQRT1_2, expected);
+}
+
+int main(void)
+{
+    const AVChannelLayout mono          = AV_CHANNEL_LAYOUT_MONO;
+    const AVChannelLayout stereo        = AV_CHANNEL_LAYOUT_STEREO;
+    const AVChannelLayout surround      = AV_CHANNEL_LAYOUT_5POINT1;
+    const AVChannelLayout surround_back = AV_CHANNEL_LAYOUT_5POINT1_BACK;
+    const AVChannelLayout surround_2    = AV_CHANNEL_LAYOUT_5POINT1POINT2;
+    const AVChannelLayout surround_4    = AV_CHANNEL_LAYOUT_5POINT1POINT4_BACK;
+    const AVChannelLayout surround_tbc  = AV_CHANNEL_LAYOUT_7POINT2POINT3;
+    int ret = 0;
+
+    ret |= check_coefficient(&surround_2, &stereo,
+                             AV_CHAN_TOP_FRONT_LEFT, AV_CHAN_FRONT_LEFT, 1.0);
+    ret |= check_coefficient(&surround_2, &stereo,
+                             AV_CHAN_TOP_FRONT_RIGHT, AV_CHAN_FRONT_RIGHT, 
1.0);
+    ret |= check_coefficient(&surround_4, &surround_2,
+                             AV_CHAN_TOP_BACK_LEFT, AV_CHAN_TOP_FRONT_LEFT,
+                             M_SQRT1_2);
+    ret |= check_coefficient(&surround_4, &surround_2,
+                             AV_CHAN_TOP_BACK_RIGHT, AV_CHAN_TOP_FRONT_RIGHT,
+                             M_SQRT1_2);
+    ret |= check_coefficient(&surround_4, &surround_tbc,
+                             AV_CHAN_TOP_BACK_LEFT, AV_CHAN_TOP_BACK_CENTER,
+                             M_SQRT1_2);
+    ret |= check_coefficient(&surround_4, &surround_tbc,
+                             AV_CHAN_TOP_BACK_RIGHT, AV_CHAN_TOP_BACK_CENTER,
+                             M_SQRT1_2);
+    ret |= check_coefficient(&surround_4, &surround,
+                             AV_CHAN_TOP_BACK_LEFT, AV_CHAN_SIDE_LEFT, 1.0);
+    ret |= check_coefficient(&surround_4, &surround,
+                             AV_CHAN_TOP_BACK_RIGHT, AV_CHAN_SIDE_RIGHT, 1.0);
+    ret |= check_coefficient(&surround_4, &surround_back,
+                             AV_CHAN_TOP_BACK_LEFT, AV_CHAN_BACK_LEFT, 1.0);
+    ret |= check_coefficient(&surround_4, &surround_back,
+                             AV_CHAN_TOP_BACK_RIGHT, AV_CHAN_BACK_RIGHT, 1.0);
+    ret |= check_coefficient(&surround_4, &stereo,
+                             AV_CHAN_TOP_BACK_LEFT, AV_CHAN_FRONT_LEFT,
+                             M_SQRT1_2);
+    ret |= check_coefficient(&surround_4, &stereo,
+                             AV_CHAN_TOP_BACK_RIGHT, AV_CHAN_FRONT_RIGHT,
+                             M_SQRT1_2);
+    ret |= check_coefficient_with_slev(&surround_4, &stereo,
+                                       AV_CHAN_TOP_BACK_LEFT,
+                                       AV_CHAN_FRONT_LEFT, 0.5, 0.5);
+    ret |= check_coefficient_with_slev(&surround_4, &stereo,
+                                       AV_CHAN_TOP_BACK_RIGHT,
+                                       AV_CHAN_FRONT_RIGHT, 0.5, 0.5);
+    ret |= check_coefficient(&surround_4, &mono,
+                             AV_CHAN_TOP_BACK_LEFT, AV_CHAN_FRONT_CENTER,
+                             M_SQRT1_2);
+    ret |= check_coefficient(&surround_4, &mono,
+                             AV_CHAN_TOP_BACK_RIGHT, AV_CHAN_FRONT_CENTER,
+                             M_SQRT1_2);
+    ret |= check_coefficient(&surround_tbc, &surround,
+                             AV_CHAN_TOP_BACK_CENTER, AV_CHAN_SIDE_LEFT,
+                             M_SQRT1_2);
+    ret |= check_coefficient(&surround_tbc, &surround,
+                             AV_CHAN_TOP_BACK_CENTER, AV_CHAN_SIDE_RIGHT,
+                             M_SQRT1_2);
+    ret |= check_coefficient(&surround_tbc, &surround_4,
+                             AV_CHAN_TOP_BACK_CENTER, AV_CHAN_TOP_BACK_LEFT,
+                             M_SQRT1_2);
+    ret |= check_coefficient(&surround_tbc, &surround_4,
+                             AV_CHAN_TOP_BACK_CENTER, AV_CHAN_TOP_BACK_RIGHT,
+                             M_SQRT1_2);
+    ret |= check_coefficient(&surround_tbc, &surround_back,
+                             AV_CHAN_TOP_BACK_CENTER, AV_CHAN_BACK_LEFT,
+                             M_SQRT1_2);
+    ret |= check_coefficient(&surround_tbc, &surround_back,
+                             AV_CHAN_TOP_BACK_CENTER, AV_CHAN_BACK_RIGHT,
+                             M_SQRT1_2);
+    ret |= check_coefficient(&surround_tbc, &stereo,
+                             AV_CHAN_TOP_BACK_CENTER, AV_CHAN_FRONT_LEFT, 0.5);
+    ret |= check_coefficient(&surround_tbc, &stereo,
+                             AV_CHAN_TOP_BACK_CENTER, AV_CHAN_FRONT_RIGHT, 
0.5);
+    ret |= check_coefficient(&surround_tbc, &mono,
+                             AV_CHAN_TOP_BACK_CENTER, AV_CHAN_FRONT_CENTER, 
0.5);
+
+    return ret;
+}
diff --git a/tests/fate/libswresample.mak b/tests/fate/libswresample.mak
index 25073923b5..9d933c4887 100644
--- a/tests/fate/libswresample.mak
+++ b/tests/fate/libswresample.mak
@@ -1106,6 +1106,13 @@ fate-swr-custom-rematrix: REF = 
2a14a44deb4ae26e3b474ddbfbc048f8
 
 FATE_SWR += $(FATE_SWR_CUSTOM_REMATRIX-yes)
 
+FATE_SWR_REMATRIX-$(CONFIG_SWRESAMPLE) += fate-swr-rematrix
+fate-swr-rematrix: libswresample/tests/rematrix$(EXESUF)
+fate-swr-rematrix: CMD = run libswresample/tests/rematrix$(EXESUF)
+fate-swr-rematrix: CMP = null
+
+FATE_SWR += $(FATE_SWR_REMATRIX-yes)
+
 FATE_SWR_REALLOC-$(CONFIG_SWRESAMPLE) += fate-swr-resample-realloc
 fate-swr-resample-realloc: 
libswresample/tests/swresample_resample_realloc$(EXESUF)
 fate-swr-resample-realloc: CMD = run 
libswresample/tests/swresample_resample_realloc$(EXESUF)

_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to