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]
