PR #24133 opened by AYOUB NABIL BOUBAGRAT (ayoubnabil)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24133
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24133.patch

custom input layouts containing AV_CHAN_UNUSED were rejected by
swr_build_matrix2(), so streams with unused input positions could not
be rematrixed.

allow AV_CHAN_UNUSED in custom input layouts, preserve its physical
matrix position, and clear the corresponding matrix column. output
layouts still reject unused channels.

add a fate test for a FL+FR+UNSD+UNSD to stereo matrix. 
this fixes #24094



>From 15eafdf713823fe652c3facca2db4a0f3f702353 Mon Sep 17 00:00:00 2001
From: Ayoub Nabil Boubagrat <[email protected]>
Date: Thu, 13 Aug 2026 20:23:07 +0200
Subject: [PATCH 1/2] libswresample/tests/rematrix: uninit channel layouts

release parsed channel layouts before exiting.

Signed-off-by: Ayoub Nabil Boubagrat 
<[email protected]>
---
 libswresample/tests/rematrix.c | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/libswresample/tests/rematrix.c b/libswresample/tests/rematrix.c
index eaa78784c6..464da7638b 100644
--- a/libswresample/tests/rematrix.c
+++ b/libswresample/tests/rematrix.c
@@ -82,23 +82,30 @@ int main(int argc, char **argv)
     if (ret < 0) {
         if (ret == AVERROR(EINVAL))
             fprintf(stderr, "Invalid input layout %s\n", in);
-        return 1;
+        ret = 1;
+        goto end;
     }
 
     ret = av_channel_layout_from_string(&out_layout, out);
     if (ret < 0) {
         if (ret == AVERROR(EINVAL))
             fprintf(stderr, "Invalid output layout %s\n", out);
-        return 1;
+        ret = 1;
+        goto end;
     }
 
     if (in_layout.nb_channels > MATRIX_STRIDE ||
         out_layout.nb_channels > MATRIX_STRIDE) {
         fprintf(stderr, "channel layout exceeds matrix capacity\n");
-        return 1;
+        ret = 1;
+        goto end;
     }
 
     ret = print_matrix(&in_layout, &out_layout);
 
+end:
+    av_channel_layout_uninit(&in_layout);
+    av_channel_layout_uninit(&out_layout);
+
     return ret;
 }
-- 
2.52.0


>From a5261e4a1a7fb45a7e9f0b73126faf8fc651372a Mon Sep 17 00:00:00 2001
From: Ayoub Nabil Boubagrat <[email protected]>
Date: Thu, 13 Aug 2026 20:23:19 +0200
Subject: [PATCH 2/2] swresample/rematrix: accept unused input channels

ignore AV_CHAN_UNUSED input channels while preserving their physical
matrix positions. add a fate test covering #24094.

Signed-off-by: Ayoub Nabil Boubagrat 
<[email protected]>
---
 libswresample/rematrix.c           | 23 +++++++++++++++++++----
 libswresample/swresample.h         |  3 +++
 libswresample/tests/rematrix.c     | 26 ++++++++++++++++++++++++++
 tests/fate/libswresample.mak       |  4 ++++
 tests/ref/fate/swr-rematrix-unused |  2 ++
 5 files changed, 54 insertions(+), 4 deletions(-)
 create mode 100644 tests/ref/fate/swr-rematrix-unused

diff --git a/libswresample/rematrix.c b/libswresample/rematrix.c
index 67d9b48339..e3f91aede2 100644
--- a/libswresample/rematrix.c
+++ b/libswresample/rematrix.c
@@ -111,12 +111,16 @@ static int clean_layout(AVChannelLayout *out, const 
AVChannelLayout *in, void *s
     return ret;
 }
 
-static int sane_layout(AVChannelLayout *ch_layout) {
+static int sane_layout(const AVChannelLayout *ch_layout, int allow_unused) {
     if(ch_layout->nb_channels >= SWR_CH_MAX)
         return 0;
     if(ch_layout->order == AV_CHANNEL_ORDER_CUSTOM)
         for (int i = 0; i < ch_layout->nb_channels; i++) {
-            if (ch_layout->u.map[i].id >= 64)
+            enum AVChannel id = ch_layout->u.map[i].id;
+
+            if (id == AV_CHAN_UNUSED && allow_unused)
+                continue;
+            if (id >= 64)
                 return 0;
         }
     else if (ch_layout->order != AV_CHANNEL_ORDER_NATIVE)
@@ -156,6 +160,17 @@ static void build_matrix(const AVChannelLayout 
*in_ch_layout, const AVChannelLay
     double maxcoef=0;
     int i, j;
 
+    if (in_ch_layout->order == AV_CHANNEL_ORDER_CUSTOM) {
+        for (j = 0; j < in_ch_layout->nb_channels; j++) {
+            if (in_ch_layout->u.map[j].id == AV_CHAN_UNUSED) {
+                /* the named-channel loop below cannot visit AV_CHAN_UNUSED.
+                 * explicitly clear its column so callers may reuse a matrix. 
*/
+                for (i = 0; i < out_ch_layout->nb_channels; i++)
+                    matrix_param[stride * i + j] = 0.0;
+            }
+        }
+    }
+
     for(i=0; i<FF_ARRAY_ELEMS(matrix); i++){
         if (in_mask & out_mask & (1ULL << i))
             matrix[i][i]= 1.0;
@@ -576,7 +591,7 @@ av_cold int swr_build_matrix2(const AVChannelLayout 
*in_layout, const AVChannelL
         ret = AVERROR(EINVAL);
         goto fail;
     }
-    if(!sane_layout(&in_ch_layout)) {
+    if(!sane_layout(&in_ch_layout, 1)) {
         av_channel_layout_describe(&in_ch_layout, buf, sizeof(buf));
         av_log(log_context, AV_LOG_ERROR, "Input channel layout '%s' is not 
supported\n", buf);
         ret = AVERROR(EINVAL);
@@ -588,7 +603,7 @@ av_cold int swr_build_matrix2(const AVChannelLayout 
*in_layout, const AVChannelL
         ret = AVERROR(EINVAL);
         goto fail;
     }
-    if(!sane_layout(&out_ch_layout)) {
+    if(!sane_layout(&out_ch_layout, 0)) {
         av_channel_layout_describe(&out_ch_layout, buf, sizeof(buf));
         av_log(log_context, AV_LOG_ERROR, "Output channel layout '%s' is not 
supported\n", buf);
         ret = AVERROR(EINVAL);
diff --git a/libswresample/swresample.h b/libswresample/swresample.h
index 052089acca..9b70719b48 100644
--- a/libswresample/swresample.h
+++ b/libswresample/swresample.h
@@ -377,6 +377,9 @@ int swr_set_channel_mapping(struct SwrContext *s, const int 
*channel_map);
  * default mixing matrix. It is made public just as a utility function for
  * building custom matrices.
  *
+ * AV_CHAN_UNUSED entries in custom input layouts are ignored and their matrix
+ * columns are set to zero.
+ *
  * @param in_layout           input channel layout
  * @param out_layout          output channel layout
  * @param center_mix_level    mix level for the center channel
diff --git a/libswresample/tests/rematrix.c b/libswresample/tests/rematrix.c
index 464da7638b..c8d3f285d4 100644
--- a/libswresample/tests/rematrix.c
+++ b/libswresample/tests/rematrix.c
@@ -28,6 +28,11 @@
 /* swr_build_matrix2() accesses an internal SWR_CH_MAX by SWR_CH_MAX matrix. */
 #define MATRIX_STRIDE 64
 
+static int channel_is_unused(const AVChannelLayout *layout, int index)
+{
+    return av_channel_layout_channel_from_index(layout, index) == 
AV_CHAN_UNUSED;
+}
+
 static int print_matrix(const AVChannelLayout *in_layout,
                         const AVChannelLayout *out_layout)
 {
@@ -35,6 +40,12 @@ static int print_matrix(const AVChannelLayout *in_layout,
     char in_name[16], out_name[16];
     int ret;
 
+    /* ensure swr_build_matrix2() overwrites unused input columns with zero. */
+    for (int out = 0; out < out_layout->nb_channels; out++)
+        for (int in = 0; in < in_layout->nb_channels; in++)
+            if (channel_is_unused(in_layout, in))
+                matrix[out * MATRIX_STRIDE + in] = 1.0;
+
     /* Disable normalization so the raw downmix gains can be checked. */
     ret = swr_build_matrix2(in_layout, out_layout, M_SQRT1_2,
                             M_SQRT1_2,
@@ -45,6 +56,16 @@ static int print_matrix(const AVChannelLayout *in_layout,
         return 1;
     }
 
+    for (int out = 0; out < out_layout->nb_channels; out++) {
+        for (int in = 0; in < in_layout->nb_channels; in++) {
+            if (channel_is_unused(in_layout, in) &&
+                matrix[out * MATRIX_STRIDE + in] != 0.0) {
+                fprintf(stderr, "unused input %d has a non-zero 
coefficient\n", in);
+                return 1;
+            }
+        }
+    }
+
     for (int i = 0; i < 64; i++) {
         int out_i = av_channel_layout_index_from_channel(out_layout, i);
         if (out_i < 0)
@@ -58,6 +79,11 @@ static int print_matrix(const AVChannelLayout *in_layout,
             av_channel_name(in_name, sizeof(in_name), j);
             printf(".%s = %f, ", in_name, matrix[out_i * MATRIX_STRIDE + 
in_i]);
         }
+        for (int in_i = 0; in_i < in_layout->nb_channels; in_i++) {
+            if (channel_is_unused(in_layout, in_i))
+                printf(".UNSD%d = %f, ", in_i,
+                       matrix[out_i * MATRIX_STRIDE + in_i]);
+        }
         printf("},\n");
     }
 
diff --git a/tests/fate/libswresample.mak b/tests/fate/libswresample.mak
index 7959aa87cd..315c05b5be 100644
--- a/tests/fate/libswresample.mak
+++ b/tests/fate/libswresample.mak
@@ -1138,6 +1138,10 @@ $(call SWR_REMATRIX_TEST,$(FATE_SWR_7_1_LAYOUTS),7.1)
 $(call SWR_REMATRIX_TEST,$(FATE_SWR_5_1_LAYOUTS),5.1)
 $(call SWR_REMATRIX_TEST,$(FATE_SWR_STEREO_LAYOUTS),stereo)
 
+FATE_SWR_REMATRIX-$(CONFIG_SWRESAMPLE) += fate-swr-rematrix-unused
+fate-swr-rematrix-unused: libswresample/tests/rematrix$(EXESUF)
+fate-swr-rematrix-unused: CMD = run libswresample/tests/rematrix$(EXESUF) 
FL+FR+UNSD+UNSD stereo
+
 FATE_SWR += $(FATE_SWR_REMATRIX-yes)
 fate-swr-rematrix: $(FATE_SWR_REMATRIX-yes)
 
diff --git a/tests/ref/fate/swr-rematrix-unused 
b/tests/ref/fate/swr-rematrix-unused
new file mode 100644
index 0000000000..605caf3b70
--- /dev/null
+++ b/tests/ref/fate/swr-rematrix-unused
@@ -0,0 +1,2 @@
+[FL] = { .FL = 1.000000, .FR = 0.000000, .UNSD2 = 0.000000, .UNSD3 = 0.000000, 
},
+[FR] = { .FL = 0.000000, .FR = 1.000000, .UNSD2 = 0.000000, .UNSD3 = 0.000000, 
},
-- 
2.52.0

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

Reply via email to