PR #24739 opened by Kacper Michajłow (kasper93)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24739
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24739.patch

Some small fixes to remixing found when I was testing mpv changes. Each change 
has own FATE test, so it's clear what has changed.


From c4000578dd8ca6a47973753ecbd0ed7aec99a3f1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sun, 27 Sep 2026 16:02:49 +0200
Subject: [PATCH 1/5] swresample/rematrix: treat a single channel next to
 unused ones as mono
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

A layout of one channel is mixed like mono, whatever the channel is.
Unused channels carry no audio, so the same holds for a custom layout
that has a single channel next to them. It was rejected for being
asymmetric.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libswresample/rematrix.c                      | 37 +++++++++++++++----
 tests/fate/libswresample.mak                  |  8 ++++
 tests/ref/fate/swr-rematrix-unused-mono-input |  6 +++
 .../ref/fate/swr-rematrix-unused-mono-output  |  2 +
 4 files changed, 45 insertions(+), 8 deletions(-)
 create mode 100644 tests/ref/fate/swr-rematrix-unused-mono-input
 create mode 100644 tests/ref/fate/swr-rematrix-unused-mono-output

diff --git a/libswresample/rematrix.c b/libswresample/rematrix.c
index 582b2b38e3..fa976b7f7c 100644
--- a/libswresample/rematrix.c
+++ b/libswresample/rematrix.c
@@ -98,17 +98,38 @@ static int even(int64_t layout){
 
 static int clean_layout(AVChannelLayout *out, const AVChannelLayout *in, void 
*s)
 {
-    int ret = 0;
+    int used = in->nb_channels, idx = 0;
 
-    if (av_channel_layout_index_from_channel(in, AV_CHAN_FRONT_CENTER) < 0 && 
in->nb_channels == 1) {
-        char buf[128];
-        av_channel_layout_describe(in, buf, sizeof(buf));
-        av_log(s, AV_LOG_VERBOSE, "Treating %s as mono\n", buf);
+    if (in->order == AV_CHANNEL_ORDER_CUSTOM && in->nb_channels > 1) {
+        used = 0;
+        for (int i = 0; i < in->nb_channels; i++) {
+            if (in->u.map[i].id != AV_CHAN_UNUSED) {
+                idx = i;
+                used++;
+            }
+        }
+    }
+
+    if (used != 1 ||
+        av_channel_layout_channel_from_index(in, idx) == AV_CHAN_FRONT_CENTER)
+        return av_channel_layout_copy(out, in);
+
+    char buf[128];
+    av_channel_layout_describe(in, buf, sizeof(buf));
+    av_log(s, AV_LOG_VERBOSE, "Treating %s as mono\n", buf);
+
+    if (in->nb_channels == 1) {
         *out = (AVChannelLayout)AV_CHANNEL_LAYOUT_MONO;
-    } else
-        ret = av_channel_layout_copy(out, in);
+        return 0;
+    }
 
-    return ret;
+    /* the channel keeps its place among the unused ones */
+    int ret = av_channel_layout_copy(out, in);
+    if (ret < 0)
+        return ret;
+    out->u.map[idx].id = AV_CHAN_FRONT_CENTER;
+
+    return 0;
 }
 
 static int sane_layout(const AVChannelLayout *ch_layout) {
diff --git a/tests/fate/libswresample.mak b/tests/fate/libswresample.mak
index d23525edbc..ca915a3764 100644
--- a/tests/fate/libswresample.mak
+++ b/tests/fate/libswresample.mak
@@ -1146,6 +1146,14 @@ FATE_SWR_REMATRIX-$(CONFIG_SWRESAMPLE) += 
fate-swr-rematrix-unused-output
 fate-swr-rematrix-unused-output: libswresample/tests/rematrix$(EXESUF)
 fate-swr-rematrix-unused-output: CMD = run 
libswresample/tests/rematrix$(EXESUF) 5.1 FL+UNSD+FR+UNSD
 
+FATE_SWR_REMATRIX-$(CONFIG_SWRESAMPLE) += fate-swr-rematrix-unused-mono-input
+fate-swr-rematrix-unused-mono-input: libswresample/tests/rematrix$(EXESUF)
+fate-swr-rematrix-unused-mono-input: CMD = run 
libswresample/tests/rematrix$(EXESUF) BL+UNSD+UNSD 5.1
+
+FATE_SWR_REMATRIX-$(CONFIG_SWRESAMPLE) += fate-swr-rematrix-unused-mono-output
+fate-swr-rematrix-unused-mono-output: libswresample/tests/rematrix$(EXESUF)
+fate-swr-rematrix-unused-mono-output: CMD = run 
libswresample/tests/rematrix$(EXESUF) stereo UNSD+FR
+
 FATE_SWR += $(FATE_SWR_REMATRIX-yes)
 fate-swr-rematrix: $(FATE_SWR_REMATRIX-yes)
 
diff --git a/tests/ref/fate/swr-rematrix-unused-mono-input 
b/tests/ref/fate/swr-rematrix-unused-mono-input
new file mode 100644
index 0000000000..57ca24ebf0
--- /dev/null
+++ b/tests/ref/fate/swr-rematrix-unused-mono-input
@@ -0,0 +1,6 @@
+[FL] = { .BL = 0.000000, .UNSD1 = 0.000000, .UNSD2 = 0.000000, },
+[FR] = { .BL = 0.000000, .UNSD1 = 0.000000, .UNSD2 = 0.000000, },
+[FC] = { .BL = 1.000000, .UNSD1 = 0.000000, .UNSD2 = 0.000000, },
+[LFE] = { .BL = 0.000000, .UNSD1 = 0.000000, .UNSD2 = 0.000000, },
+[BL] = { .BL = 0.000000, .UNSD1 = 0.000000, .UNSD2 = 0.000000, },
+[BR] = { .BL = 0.000000, .UNSD1 = 0.000000, .UNSD2 = 0.000000, },
diff --git a/tests/ref/fate/swr-rematrix-unused-mono-output 
b/tests/ref/fate/swr-rematrix-unused-mono-output
new file mode 100644
index 0000000000..21a67e6d94
--- /dev/null
+++ b/tests/ref/fate/swr-rematrix-unused-mono-output
@@ -0,0 +1,2 @@
+[UNSD0] = { .FL = 0.000000, .FR = 0.000000, },
+[FR] = { .FL = 0.707107, .FR = 0.707107, },
-- 
2.52.0


From 3763c1a5ab4ea7946f73e75f20f93d02cac51088 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sun, 27 Sep 2026 16:03:32 +0200
Subject: [PATCH 2/5] swresample/rematrix: recognize the stereo downmix pair in
 custom layouts
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The pair is mixed like stereo if the other side has no such channels.
It was only recognized as the native layout, so a custom layout with
the pair in another order or next to unused channels was rejected for
the lack of a front channel.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libswresample/rematrix.c                   | 36 ++++++++++++++--------
 tests/fate/libswresample.mak               |  8 +++++
 tests/ref/fate/swr-rematrix-downmix-input  |  6 ++++
 tests/ref/fate/swr-rematrix-downmix-output |  3 ++
 4 files changed, 41 insertions(+), 12 deletions(-)
 create mode 100644 tests/ref/fate/swr-rematrix-downmix-input
 create mode 100644 tests/ref/fate/swr-rematrix-downmix-output

diff --git a/libswresample/rematrix.c b/libswresample/rematrix.c
index fa976b7f7c..7a0ea88682 100644
--- a/libswresample/rematrix.c
+++ b/libswresample/rematrix.c
@@ -132,6 +132,28 @@ static int clean_layout(AVChannelLayout *out, const 
AVChannelLayout *in, void *s
     return 0;
 }
 
+/**
+ * Stereo downmix channels are mixed like stereo, unless the other layout has
+ * such channels as well. The channels keep their places.
+ */
+static void clean_downmix(AVChannelLayout *ch_layout, const AVChannelLayout 
*other)
+{
+    if (av_channel_layout_subset(ch_layout, ~(uint64_t)0) != 
AV_CH_LAYOUT_STEREO_DOWNMIX ||
+        av_channel_layout_subset(other, AV_CH_LAYOUT_STEREO_DOWNMIX))
+        return;
+
+    if (ch_layout->order == AV_CHANNEL_ORDER_NATIVE) {
+        ch_layout->u.mask = AV_CH_LAYOUT_STEREO;
+    } else 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 == AV_CHAN_STEREO_LEFT)
+                ch_layout->u.map[i].id = AV_CHAN_FRONT_LEFT;
+            else if (ch_layout->u.map[i].id == AV_CHAN_STEREO_RIGHT)
+                ch_layout->u.map[i].id = AV_CHAN_FRONT_RIGHT;
+        }
+    }
+}
+
 static int sane_layout(const AVChannelLayout *ch_layout) {
     if(ch_layout->nb_channels >= SWR_CH_MAX)
         return 0;
@@ -605,18 +627,8 @@ av_cold int swr_build_matrix2(const AVChannelLayout 
*in_layout, const AVChannelL
     if (ret < 0)
         goto fail;
 
-    if(   !av_channel_layout_compare(&out_ch_layout, 
&(AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO_DOWNMIX)
-       && !av_channel_layout_subset(&in_ch_layout, AV_CH_LAYOUT_STEREO_DOWNMIX)
-    ) {
-        av_channel_layout_uninit(&out_ch_layout);
-        out_ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO;
-    }
-    if(   !av_channel_layout_compare(&in_ch_layout, 
&(AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO_DOWNMIX)
-       && !av_channel_layout_subset(&out_ch_layout, 
AV_CH_LAYOUT_STEREO_DOWNMIX)
-    ) {
-        av_channel_layout_uninit(&in_ch_layout);
-        in_ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO;
-    }
+    clean_downmix(&out_ch_layout, &in_ch_layout);
+    clean_downmix(&in_ch_layout, &out_ch_layout);
 
     if(!av_channel_layout_check(&in_ch_layout)) {
         av_log(log_context, AV_LOG_ERROR, "Input channel layout is invalid\n");
diff --git a/tests/fate/libswresample.mak b/tests/fate/libswresample.mak
index ca915a3764..c98176a238 100644
--- a/tests/fate/libswresample.mak
+++ b/tests/fate/libswresample.mak
@@ -1154,6 +1154,14 @@ FATE_SWR_REMATRIX-$(CONFIG_SWRESAMPLE) += 
fate-swr-rematrix-unused-mono-output
 fate-swr-rematrix-unused-mono-output: libswresample/tests/rematrix$(EXESUF)
 fate-swr-rematrix-unused-mono-output: CMD = run 
libswresample/tests/rematrix$(EXESUF) stereo UNSD+FR
 
+FATE_SWR_REMATRIX-$(CONFIG_SWRESAMPLE) += fate-swr-rematrix-downmix-input
+fate-swr-rematrix-downmix-input: libswresample/tests/rematrix$(EXESUF)
+fate-swr-rematrix-downmix-input: CMD = run 
libswresample/tests/rematrix$(EXESUF) DR+DL 5.1
+
+FATE_SWR_REMATRIX-$(CONFIG_SWRESAMPLE) += fate-swr-rematrix-downmix-output
+fate-swr-rematrix-downmix-output: libswresample/tests/rematrix$(EXESUF)
+fate-swr-rematrix-downmix-output: CMD = run 
libswresample/tests/rematrix$(EXESUF) 5.1 DR+UNSD+DL
+
 FATE_SWR += $(FATE_SWR_REMATRIX-yes)
 fate-swr-rematrix: $(FATE_SWR_REMATRIX-yes)
 
diff --git a/tests/ref/fate/swr-rematrix-downmix-input 
b/tests/ref/fate/swr-rematrix-downmix-input
new file mode 100644
index 0000000000..254405fb7a
--- /dev/null
+++ b/tests/ref/fate/swr-rematrix-downmix-input
@@ -0,0 +1,6 @@
+[FL] = { .DR = 0.000000, .DL = 1.000000, },
+[FR] = { .DR = 1.000000, .DL = 0.000000, },
+[FC] = { .DR = 0.000000, .DL = 0.000000, },
+[LFE] = { .DR = 0.000000, .DL = 0.000000, },
+[BL] = { .DR = 0.000000, .DL = 0.000000, },
+[BR] = { .DR = 0.000000, .DL = 0.000000, },
diff --git a/tests/ref/fate/swr-rematrix-downmix-output 
b/tests/ref/fate/swr-rematrix-downmix-output
new file mode 100644
index 0000000000..a96b879ca9
--- /dev/null
+++ b/tests/ref/fate/swr-rematrix-downmix-output
@@ -0,0 +1,3 @@
+[DR] = { .FL = 0.000000, .FR = 1.000000, .FC = 0.707107, .LFE = 0.000000, .BL 
= 0.000000, .BR = 0.707107, },
+[UNSD1] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[DL] = { .FL = 1.000000, .FR = 0.000000, .FC = 0.707107, .LFE = 0.000000, .BL 
= 0.707107, .BR = 0.000000, },
-- 
2.52.0


From 3e62290e67bebecdc18afccb1f4aa1ccdc08dfd6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sun, 27 Sep 2026 16:04:09 +0200
Subject: [PATCH 3/5] swresample/rematrix: allow layouts of SWR_CH_MAX channels
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Classic off by one check :)

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libswresample/rematrix.c              |  2 +-
 tests/fate/libswresample.mak          |  7 +++
 tests/ref/fate/swr-rematrix-unused-64 | 64 +++++++++++++++++++++++++++
 3 files changed, 72 insertions(+), 1 deletion(-)
 create mode 100644 tests/ref/fate/swr-rematrix-unused-64

diff --git a/libswresample/rematrix.c b/libswresample/rematrix.c
index 7a0ea88682..252a28603e 100644
--- a/libswresample/rematrix.c
+++ b/libswresample/rematrix.c
@@ -155,7 +155,7 @@ static void clean_downmix(AVChannelLayout *ch_layout, const 
AVChannelLayout *oth
 }
 
 static int sane_layout(const AVChannelLayout *ch_layout) {
-    if(ch_layout->nb_channels >= SWR_CH_MAX)
+    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++) {
diff --git a/tests/fate/libswresample.mak b/tests/fate/libswresample.mak
index c98176a238..82500d1cec 100644
--- a/tests/fate/libswresample.mak
+++ b/tests/fate/libswresample.mak
@@ -1162,6 +1162,13 @@ FATE_SWR_REMATRIX-$(CONFIG_SWRESAMPLE) += 
fate-swr-rematrix-downmix-output
 fate-swr-rematrix-downmix-output: libswresample/tests/rematrix$(EXESUF)
 fate-swr-rematrix-downmix-output: CMD = run 
libswresample/tests/rematrix$(EXESUF) 5.1 DR+UNSD+DL
 
+FATE_SWR_UNUSED_8  = UNSD+UNSD+UNSD+UNSD+UNSD+UNSD+UNSD+UNSD
+FATE_SWR_UNUSED_31 = 
$(FATE_SWR_UNUSED_8)+$(FATE_SWR_UNUSED_8)+$(FATE_SWR_UNUSED_8)+UNSD+UNSD+UNSD+UNSD+UNSD+UNSD+UNSD
+
+FATE_SWR_REMATRIX-$(CONFIG_SWRESAMPLE) += fate-swr-rematrix-unused-64
+fate-swr-rematrix-unused-64: libswresample/tests/rematrix$(EXESUF)
+fate-swr-rematrix-unused-64: CMD = run libswresample/tests/rematrix$(EXESUF) 
5.1 $(FATE_SWR_UNUSED_31)+FL+FR+$(FATE_SWR_UNUSED_31)
+
 FATE_SWR += $(FATE_SWR_REMATRIX-yes)
 fate-swr-rematrix: $(FATE_SWR_REMATRIX-yes)
 
diff --git a/tests/ref/fate/swr-rematrix-unused-64 
b/tests/ref/fate/swr-rematrix-unused-64
new file mode 100644
index 0000000000..242a611f38
--- /dev/null
+++ b/tests/ref/fate/swr-rematrix-unused-64
@@ -0,0 +1,64 @@
+[UNSD0] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD1] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD2] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD3] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD4] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD5] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD6] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD7] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD8] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD9] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD10] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD11] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD12] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD13] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD14] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD15] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD16] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD17] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD18] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD19] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD20] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD21] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD22] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD23] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD24] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD25] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD26] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD27] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD28] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD29] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD30] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[FL] = { .FL = 1.000000, .FR = 0.000000, .FC = 0.707107, .LFE = 0.000000, .BL 
= 0.707107, .BR = 0.000000, },
+[FR] = { .FL = 0.000000, .FR = 1.000000, .FC = 0.707107, .LFE = 0.000000, .BL 
= 0.000000, .BR = 0.707107, },
+[UNSD33] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD34] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD35] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD36] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD37] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD38] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD39] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD40] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD41] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD42] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD43] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD44] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD45] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD46] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD47] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD48] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD49] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD50] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD51] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD52] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD53] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD54] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD55] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD56] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD57] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD58] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD59] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD60] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD61] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD62] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
+[UNSD63] = { .FL = 0.000000, .FR = 0.000000, .FC = 0.000000, .LFE = 0.000000, 
.BL = 0.000000, .BR = 0.000000, },
-- 
2.52.0


From 057148b6005c39e3fe946603ec856edb7552cb1a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sun, 27 Sep 2026 16:04:59 +0200
Subject: [PATCH 4/5] swresample/rematrix: don't check channels that are only
 reordered
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Layouts with the same channels in another order need a matrix, but
nothing is mixed. The checks for channels that can be mixed rejected
them anyway, for example BL+BR to BR+BL.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libswresample/rematrix.c                   | 14 ++++++++++----
 tests/fate/libswresample.mak               |  8 ++++++++
 tests/ref/fate/swr-rematrix-reorder        |  2 ++
 tests/ref/fate/swr-rematrix-reorder-unused |  3 +++
 4 files changed, 23 insertions(+), 4 deletions(-)
 create mode 100644 tests/ref/fate/swr-rematrix-reorder
 create mode 100644 tests/ref/fate/swr-rematrix-reorder-unused

diff --git a/libswresample/rematrix.c b/libswresample/rematrix.c
index 252a28603e..3bec10360e 100644
--- a/libswresample/rematrix.c
+++ b/libswresample/rematrix.c
@@ -154,7 +154,7 @@ static void clean_downmix(AVChannelLayout *ch_layout, const 
AVChannelLayout *oth
     }
 }
 
-static int sane_layout(const AVChannelLayout *ch_layout) {
+static int sane_layout(const AVChannelLayout *ch_layout, int mixed) {
     if(ch_layout->nb_channels > SWR_CH_MAX)
         return 0;
     if(ch_layout->order == AV_CHANNEL_ORDER_CUSTOM)
@@ -168,6 +168,8 @@ static int sane_layout(const AVChannelLayout *ch_layout) {
         }
     else if (ch_layout->order != AV_CHANNEL_ORDER_NATIVE)
         return 0;
+    if (!mixed)
+        return 1;
     uint64_t mask = av_channel_layout_subset(ch_layout, ~(uint64_t)0);
     if(!(mask & AV_CH_LAYOUT_SURROUND)) // at least 1 front speaker
         return 0;
@@ -618,7 +620,7 @@ av_cold int swr_build_matrix2(const AVChannelLayout 
*in_layout, const AVChannelL
                               double rematrix_volume, double *matrix_param,
                               ptrdiff_t stride, enum AVMatrixEncoding 
matrix_encoding, void *log_context)
 {
-    int i, j, ret;
+    int i, j, ret, mixed;
     AVChannelLayout in_ch_layout = { 0 }, out_ch_layout = { 0 };
     char buf[128];
 
@@ -630,12 +632,16 @@ av_cold int swr_build_matrix2(const AVChannelLayout 
*in_layout, const AVChannelL
     clean_downmix(&out_ch_layout, &in_ch_layout);
     clean_downmix(&in_ch_layout, &out_ch_layout);
 
+    /* the same channels on both sides change their places at most */
+    mixed = av_channel_layout_subset(&in_ch_layout,  ~(uint64_t)0) !=
+            av_channel_layout_subset(&out_ch_layout, ~(uint64_t)0);
+
     if(!av_channel_layout_check(&in_ch_layout)) {
         av_log(log_context, AV_LOG_ERROR, "Input channel layout is invalid\n");
         ret = AVERROR(EINVAL);
         goto fail;
     }
-    if(!sane_layout(&in_ch_layout)) {
+    if(!sane_layout(&in_ch_layout, mixed)) {
         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);
@@ -647,7 +653,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, mixed)) {
         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/tests/fate/libswresample.mak b/tests/fate/libswresample.mak
index 82500d1cec..335750b552 100644
--- a/tests/fate/libswresample.mak
+++ b/tests/fate/libswresample.mak
@@ -1169,6 +1169,14 @@ FATE_SWR_REMATRIX-$(CONFIG_SWRESAMPLE) += 
fate-swr-rematrix-unused-64
 fate-swr-rematrix-unused-64: libswresample/tests/rematrix$(EXESUF)
 fate-swr-rematrix-unused-64: CMD = run libswresample/tests/rematrix$(EXESUF) 
5.1 $(FATE_SWR_UNUSED_31)+FL+FR+$(FATE_SWR_UNUSED_31)
 
+FATE_SWR_REMATRIX-$(CONFIG_SWRESAMPLE) += fate-swr-rematrix-reorder
+fate-swr-rematrix-reorder: libswresample/tests/rematrix$(EXESUF)
+fate-swr-rematrix-reorder: CMD = run libswresample/tests/rematrix$(EXESUF) 
BL+BR BR+BL
+
+FATE_SWR_REMATRIX-$(CONFIG_SWRESAMPLE) += fate-swr-rematrix-reorder-unused
+fate-swr-rematrix-reorder-unused: libswresample/tests/rematrix$(EXESUF)
+fate-swr-rematrix-reorder-unused: CMD = run 
libswresample/tests/rematrix$(EXESUF) FC+UNSD+FL FL+FC+UNSD
+
 FATE_SWR += $(FATE_SWR_REMATRIX-yes)
 fate-swr-rematrix: $(FATE_SWR_REMATRIX-yes)
 
diff --git a/tests/ref/fate/swr-rematrix-reorder 
b/tests/ref/fate/swr-rematrix-reorder
new file mode 100644
index 0000000000..b80b9f3c05
--- /dev/null
+++ b/tests/ref/fate/swr-rematrix-reorder
@@ -0,0 +1,2 @@
+[BR] = { .BL = 0.000000, .BR = 1.000000, },
+[BL] = { .BL = 1.000000, .BR = 0.000000, },
diff --git a/tests/ref/fate/swr-rematrix-reorder-unused 
b/tests/ref/fate/swr-rematrix-reorder-unused
new file mode 100644
index 0000000000..2db5a874a8
--- /dev/null
+++ b/tests/ref/fate/swr-rematrix-reorder-unused
@@ -0,0 +1,3 @@
+[FL] = { .FC = 0.000000, .UNSD1 = 0.000000, .FL = 1.000000, },
+[FC] = { .FC = 1.000000, .UNSD1 = 0.000000, .FL = 0.000000, },
+[UNSD2] = { .FC = 0.000000, .UNSD1 = 0.000000, .FL = 0.000000, },
-- 
2.52.0


From 87c02d2ed062870013197e732dfd9e78b93ae00f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sun, 27 Sep 2026 16:05:52 +0200
Subject: [PATCH 5/5] swresample: keep integer samples if channels are only
 copied
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

With a matrix, samples of more than 16 bits are mixed in floating
point, which takes the lowest bits of s32. A matrix that only copies
channels computes nothing, so select the internal format like without
a matrix. Layouts that differ in the order of their channels or in
unused ones get such a matrix.

The matrix is built before the format is selected for this. The limit
for its coefficients no longer looks at the selected format. For
mixing that is an integer format only if the output has one or if it
was set, which is what the limit looks at now.

s64 still goes through double, the matrix has no functions for it.

Bump the micro version, as this and the previous commits change which
custom layouts can be used.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libswresample/rematrix.c            | 36 +++++++++++++++++++++++++----
 libswresample/swresample.c          | 34 ++++++++++++++++-----------
 libswresample/swresample_internal.h |  2 ++
 libswresample/version.h             |  2 +-
 tests/fate/libswresample.mak        |  8 +++++++
 tests/filtergraphs/custom_reorder   |  1 +
 6 files changed, 65 insertions(+), 18 deletions(-)
 create mode 100644 tests/filtergraphs/custom_reorder

diff --git a/libswresample/rematrix.c b/libswresample/rematrix.c
index 3bec10360e..31b1078bcc 100644
--- a/libswresample/rematrix.c
+++ b/libswresample/rematrix.c
@@ -697,7 +697,8 @@ av_cold static int auto_matrix(SwrContext *s)
     if (s->rematrix_maxval > 0) {
         maxval = s->rematrix_maxval;
     } else if (   av_get_packed_sample_fmt(s->out_sample_fmt) < 
AV_SAMPLE_FMT_FLT
-               || av_get_packed_sample_fmt(s->int_sample_fmt) < 
AV_SAMPLE_FMT_FLT) {
+               || (s->user_int_sample_fmt != AV_SAMPLE_FMT_NONE &&
+                   av_get_packed_sample_fmt(s->user_int_sample_fmt) < 
AV_SAMPLE_FMT_FLT)) {
         maxval = 1.0;
     } else
         maxval = INT_MAX;
@@ -709,13 +710,12 @@ av_cold static int auto_matrix(SwrContext *s)
                              s->matrix[1] - s->matrix[0], s->matrix_encoding, 
s);
 }
 
-av_cold int swri_rematrix_init(SwrContext *s){
+av_cold int swri_rematrix_build(SwrContext *s)
+{
     int i, j;
     int nb_in  = s->used_ch_layout.nb_channels;
     int nb_out = s->out.ch_count;
 
-    s->mix_any_f = NULL;
-
     if (!s->rematrix_custom) {
         int r = auto_matrix(s);
         if (r)
@@ -735,6 +735,34 @@ av_cold int swri_rematrix_init(SwrContext *s){
             av_log(s, AV_LOG_DEBUG, "\n");
         }
     }
+
+    /* a channel that is one channel of the input, or silent, is not mixed */
+    for (i = 0; i < nb_out; i++) {
+        int sources = 0;
+        for (j = 0; j < nb_in; j++) {
+            if (s->matrix[i][j] == 0.0)
+                continue;
+            if (s->matrix[i][j] != 1.0 || sources++)
+                return 1;
+        }
+    }
+
+    return 0;
+}
+
+av_cold int swri_rematrix_init(SwrContext *s){
+    int i, j;
+    int nb_in  = s->used_ch_layout.nb_channels;
+    int nb_out = s->out.ch_count;
+
+    s->mix_any_f = NULL;
+
+    /* a matrix that is applied was built by swri_rematrix_build() */
+    if (!s->rematrix) {
+        int r = auto_matrix(s);
+        if (r)
+            return r;
+    }
     if (s->midbuf.fmt == AV_SAMPLE_FMT_S16P){
         int maxsum = 0;
         s->native_matrix = av_calloc(nb_in * nb_out, sizeof(int));
diff --git a/libswresample/swresample.c b/libswresample/swresample.c
index 3fa2f3bf6c..cd84c731d9 100644
--- a/libswresample/swresample.c
+++ b/libswresample/swresample.c
@@ -154,7 +154,7 @@ av_cold void swr_close(SwrContext *s){
 }
 
 av_cold int swr_init(struct SwrContext *s){
-    int ret;
+    int ret, mixing;
     char l1[1024], l2[1024];
 
     clear_context(s);
@@ -232,6 +232,24 @@ av_cold int swr_init(struct SwrContext *s){
                  s->rematrix_volume!=1.0 ||
                  s->rematrix_custom;
 
+    av_channel_layout_describe(&s->out_ch_layout, l2, sizeof(l2));
+    av_channel_layout_describe(&s->in_ch_layout, l1, sizeof(l1));
+    if ((   s->out_ch_layout.order == AV_CHANNEL_ORDER_UNSPEC
+         || s-> in_ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) && 
s->used_ch_layout.nb_channels != s->out.ch_count && !s->rematrix_custom) {
+        av_log(s, AV_LOG_ERROR, "Rematrix is needed between %s and %s "
+               "but there is not enough information to do it\n", l1, l2);
+        return AVERROR(EINVAL);
+    }
+
+    /* a matrix that only copies channels leaves the samples as they are,
+     * so the format in between is selected like without a matrix */
+    mixing = 0;
+    if (s->rematrix) {
+        mixing = swri_rematrix_build(s);
+        if (mixing < 0)
+            return mixing;
+    }
+
     if(s->int_sample_fmt == AV_SAMPLE_FMT_NONE){
         // DSD to PCM conversion is done in floating point
         if(   s->in_sample_fmt == AV_SAMPLE_FMT_DSD
@@ -247,13 +265,13 @@ av_cold int swr_init(struct SwrContext *s){
                     +av_get_bytes_per_sample(s->out_sample_fmt) <= 3 ) {
             s->int_sample_fmt= AV_SAMPLE_FMT_S16P;
         }else if(   av_get_bytes_per_sample(s-> in_sample_fmt) <= 2
-           && !s->rematrix
+           && !mixing
            && s->out_sample_rate==s->in_sample_rate
            && !(s->flags & SWR_FLAG_RESAMPLE)){
             s->int_sample_fmt= AV_SAMPLE_FMT_S16P;
         }else if(   av_get_planar_sample_fmt(s-> in_sample_fmt) == 
AV_SAMPLE_FMT_S32P
                  && av_get_planar_sample_fmt(s->out_sample_fmt) == 
AV_SAMPLE_FMT_S32P
-                 && !s->rematrix
+                 && !mixing
                  && s->out_sample_rate == s->in_sample_rate
                  && !(s->flags & SWR_FLAG_RESAMPLE)
                  && s->engine != SWR_ENGINE_SOXR){
@@ -328,22 +346,12 @@ av_cold int swr_init(struct SwrContext *s){
         goto fail;
     }
 
-    av_channel_layout_describe(&s->out_ch_layout, l2, sizeof(l2));
-    av_channel_layout_describe(&s->in_ch_layout, l1, sizeof(l1));
     if (s->in_ch_layout.order != AV_CHANNEL_ORDER_UNSPEC && 
s->used_ch_layout.nb_channels != s->in_ch_layout.nb_channels) {
         av_log(s, AV_LOG_ERROR, "Input channel layout %s mismatches specified 
channel count %d\n", l1, s->used_ch_layout.nb_channels);
         ret = AVERROR(EINVAL);
         goto fail;
     }
 
-    if ((   s->out_ch_layout.order == AV_CHANNEL_ORDER_UNSPEC
-         || s-> in_ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) && 
s->used_ch_layout.nb_channels != s->out.ch_count && !s->rematrix_custom) {
-        av_log(s, AV_LOG_ERROR, "Rematrix is needed between %s and %s "
-               "but there is not enough information to do it\n", l1, l2);
-        ret = AVERROR(EINVAL);
-        goto fail;
-    }
-
 av_assert0(s->used_ch_layout.nb_channels);
 av_assert0(s->out.ch_count);
     s->resample_first= RSC*s->out.ch_count/s->used_ch_layout.nb_channels - RSC 
< s->out_sample_rate/(float)s-> in_sample_rate - 1.0;
diff --git a/libswresample/swresample_internal.h 
b/libswresample/swresample_internal.h
index 0cde2760bb..dcfb5335e7 100644
--- a/libswresample/swresample_internal.h
+++ b/libswresample/swresample_internal.h
@@ -207,6 +207,8 @@ void swri_noise_shaping_int32 (SwrContext *s, AudioData 
*dsts, const AudioData *
 void swri_noise_shaping_float (SwrContext *s, AudioData *dsts, const AudioData 
*srcs, const AudioData *noises, int count);
 void swri_noise_shaping_double(SwrContext *s, AudioData *dsts, const AudioData 
*srcs, const AudioData *noises, int count);
 
+av_warn_unused_result
+int swri_rematrix_build(SwrContext *s);
 av_warn_unused_result
 int swri_rematrix_init(SwrContext *s);
 void swri_rematrix_free(SwrContext *s);
diff --git a/libswresample/version.h b/libswresample/version.h
index d1795b5545..403be6227a 100644
--- a/libswresample/version.h
+++ b/libswresample/version.h
@@ -31,7 +31,7 @@
 #include "version_major.h"
 
 #define LIBSWRESAMPLE_VERSION_MINOR   3
-#define LIBSWRESAMPLE_VERSION_MICRO 100
+#define LIBSWRESAMPLE_VERSION_MICRO 101
 
 #define LIBSWRESAMPLE_VERSION_INT  AV_VERSION_INT(LIBSWRESAMPLE_VERSION_MAJOR, 
\
                                                   LIBSWRESAMPLE_VERSION_MINOR, 
\
diff --git a/tests/fate/libswresample.mak b/tests/fate/libswresample.mak
index 335750b552..f09f06b5d3 100644
--- a/tests/fate/libswresample.mak
+++ b/tests/fate/libswresample.mak
@@ -1106,6 +1106,14 @@ fate-swr-custom-rematrix: REF = 
2a14a44deb4ae26e3b474ddbfbc048f8
 
 FATE_SWR += $(FATE_SWR_CUSTOM_REMATRIX-yes)
 
+FATE_SWR_CUSTOM_REORDER-$(call FILTERDEMDECENCMUX, ARESAMPLE AFORMAT, 
PCM_S32LE, PCM_S32LE, PCM_S32LE, PCM_S32LE) += fate-swr-custom-reorder
+fate-swr-custom-reorder: tests/data/asynth-44100-8.wav 
tests/data/filtergraphs/custom_reorder
+fate-swr-custom-reorder: CMD = md5 -f s32le -ar 44100 -ch_layout 7.1 -i 
$(TARGET_PATH)/tests/data/asynth-44100-8.wav -/filter_complex 
$(TARGET_PATH)/tests/data/filtergraphs/custom_reorder -map [OUT] -f s32le
+fate-swr-custom-reorder: CMP = oneline
+fate-swr-custom-reorder: REF = e9518a955a68bde2945dfd1994675101
+
+FATE_SWR += $(FATE_SWR_CUSTOM_REORDER-yes)
+
 FATE_SWR_22_2_LAYOUTS   = 9.1.6 9.1.4 7.2.3 7.1.4 7.1.2 5.1.4 5.1.2 7.1 5.1 
stereo mono
 FATE_SWR_9_1_6_LAYOUTS  = 9.1.4 7.2.3 7.1.4 7.1.2 5.1.4 5.1.2 7.1 5.1 stereo 
mono
 FATE_SWR_9_1_4_LAYOUTS  = 7.2.3 7.1.4 7.1.2 5.1.4 5.1.2 7.1 5.1 stereo mono
diff --git a/tests/filtergraphs/custom_reorder 
b/tests/filtergraphs/custom_reorder
new file mode 100644
index 0000000000..8f8a6b92cf
--- /dev/null
+++ b/tests/filtergraphs/custom_reorder
@@ -0,0 +1 @@
+[0:a]aresample,aformat=f=s32:cl=FR+FL+FC+LFE+SL+SR+BL+BR[OUT];
-- 
2.52.0

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

Reply via email to