PR #24521 opened by Romain Beauxis (toots) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24521 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24521.patch
This PR reworks the Xing header parsing/rendering logic: * Finish parsing all available fields * Add a generic packer API + tests * Use the generic packer API to enhance mp3 encoding round trips: validates inconsistent values, properly signal joint stereo etc. Fixes #24129 and its duplicate https://trac.ffmpeg.org/ticket/4954 >From 7cd7b243e937b2591461261f4c7ae89fdecc1498 Mon Sep 17 00:00:00 2001 From: Romain Beauxis <[email protected]> Date: Tue, 15 Sep 2026 10:41:39 -0500 Subject: [PATCH 1/3] avcodec/mpegaudiodecheader: parse all remaining header fields Parse the bitrate index, padding, private bit, copyright, original and emphasis fields, so that a parsed header holds every field of the bitstream header. --- doc/APIchanges | 4 ++++ libavcodec/mpegaudiodecheader.c | 10 ++++++---- libavcodec/mpegaudiodecheader.h | 8 +++++++- libavcodec/version.h | 2 +- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/doc/APIchanges b/doc/APIchanges index 5d81cd92d7..b67e71d317 100644 --- a/doc/APIchanges +++ b/doc/APIchanges @@ -2,6 +2,10 @@ The last version increases of all libraries were on 2026-06-23. API changes, most recent first: +2026-09-15 - xxxxxxxxxx - lavc 63.14.100 - mpegaudiodecheader.h + Add copyright, original, emphasis, bitrate_index, padding and private_bit + to MPADecodeHeader, filled by avpriv_mpegaudio_decode_header(). + 2026-09-12 - xxxxxxxxxx - lavc 63.13.100 - codec_id.h, defs.h Add AV_CODEC_ID_ASTC and the AV_PROFILE_ASTC_* profile values. diff --git a/libavcodec/mpegaudiodecheader.c b/libavcodec/mpegaudiodecheader.c index ef63befbf4..8a86d85500 100644 --- a/libavcodec/mpegaudiodecheader.c +++ b/libavcodec/mpegaudiodecheader.c @@ -62,12 +62,14 @@ int avpriv_mpegaudio_decode_header(MPADecodeHeader *s, uint32_t header) bitrate_index = (header >> 12) & 0xf; padding = (header >> 9) & 1; - //extension = (header >> 8) & 1; + s->bitrate_index = bitrate_index; + s->padding = padding; + s->private_bit = (header >> 8) & 1; s->mode = (header >> 6) & 3; s->mode_ext = (header >> 4) & 3; - //copyright = (header >> 3) & 1; - //original = (header >> 2) & 1; - //emphasis = header & 3; + s->copyright = (header >> 3) & 1; + s->original = (header >> 2) & 1; + s->emphasis = header & 3; if (s->mode == MPA_MONO) s->nb_channels = 1; diff --git a/libavcodec/mpegaudiodecheader.h b/libavcodec/mpegaudiodecheader.h index ed5d1f3b33..42c8736fa6 100644 --- a/libavcodec/mpegaudiodecheader.h +++ b/libavcodec/mpegaudiodecheader.h @@ -42,7 +42,13 @@ int nb_channels; \ int mode; \ int mode_ext; \ - int lsf; + int lsf; \ + int copyright; \ + int original; \ + int emphasis; \ + int bitrate_index; \ + int padding; \ + int private_bit; typedef struct MPADecodeHeader { MPA_DECODE_HEADER diff --git a/libavcodec/version.h b/libavcodec/version.h index 755c90bbc1..7531c6c42a 100644 --- a/libavcodec/version.h +++ b/libavcodec/version.h @@ -29,7 +29,7 @@ #include "version_major.h" -#define LIBAVCODEC_VERSION_MINOR 13 +#define LIBAVCODEC_VERSION_MINOR 14 #define LIBAVCODEC_VERSION_MICRO 100 #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ -- 2.52.0 >From 1dbcd5e46c19d10515cf27f44b84160aa07be500 Mon Sep 17 00:00:00 2001 From: Romain Beauxis <[email protected]> Date: Tue, 15 Sep 2026 10:41:39 -0500 Subject: [PATCH 2/3] avcodec/mpegaudiodecheader: add a header packer ff_mpa_encode_header() is the inverse of avpriv_mpegaudio_decode_header(). A test program walks the frames of a file, checks that every header encodes back to its original bits and prints the header fields. --- libavcodec/Makefile | 1 + libavcodec/mpegaudiodecheader.h | 19 +++ libavcodec/tests/.gitignore | 1 + libavcodec/tests/mpegaudiodecheader.c | 120 ++++++++++++++++++ tests/fate/mp3.mak | 38 ++++++ tests/ref/fate/mpegaudiodecheader-bitrates | 15 +++ tests/ref/fate/mpegaudiodecheader-hecommon | 10 ++ .../ref/fate/mpegaudiodecheader-joint-stereo | 2 + tests/ref/fate/mpegaudiodecheader-libmp3lame | 3 + tests/ref/fate/mpegaudiodecheader-mp2 | 2 + tests/ref/fate/mpegaudiodecheader-mp3-remux | 3 + tests/ref/fate/mpegaudiodecheader-mpeg25 | 3 + tests/ref/fate/mpegaudiodecheader-xing | 3 + 13 files changed, 220 insertions(+) create mode 100644 libavcodec/tests/mpegaudiodecheader.c create mode 100644 tests/ref/fate/mpegaudiodecheader-bitrates create mode 100644 tests/ref/fate/mpegaudiodecheader-hecommon create mode 100644 tests/ref/fate/mpegaudiodecheader-joint-stereo create mode 100644 tests/ref/fate/mpegaudiodecheader-libmp3lame create mode 100644 tests/ref/fate/mpegaudiodecheader-mp2 create mode 100644 tests/ref/fate/mpegaudiodecheader-mp3-remux create mode 100644 tests/ref/fate/mpegaudiodecheader-mpeg25 create mode 100644 tests/ref/fate/mpegaudiodecheader-xing diff --git a/libavcodec/Makefile b/libavcodec/Makefile index a5c71995ea..e2c60e9051 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -1393,6 +1393,7 @@ TESTPROGS-$(CONFIG_GOLOMB) += golomb TESTPROGS-$(CONFIG_IDCTDSP) += dct TESTPROGS-$(CONFIG_DXV_ENCODER) += hashtable TESTPROGS-$(CONFIG_MJPEG_ENCODER) += mjpegenc_huffman +TESTPROGS-$(CONFIG_MPEGAUDIOHEADER) += mpegaudiodecheader TESTPROGS-$(CONFIG_MPEGVIDEO) += mpeg12framerate TESTPROGS-$(CONFIG_H264_METADATA_BSF) += h264_levels TESTPROGS-$(CONFIG_HEVC_METADATA_BSF) += h265_levels diff --git a/libavcodec/mpegaudiodecheader.h b/libavcodec/mpegaudiodecheader.h index 42c8736fa6..d4802c2bb9 100644 --- a/libavcodec/mpegaudiodecheader.h +++ b/libavcodec/mpegaudiodecheader.h @@ -84,4 +84,23 @@ static inline int ff_mpa_check_header(uint32_t header){ return 0; } +static inline uint32_t ff_mpa_encode_header(const MPADecodeHeader *s) +{ + int version = s->sample_rate_index < 6 ? 3 - s->sample_rate_index / 3 : 0; + + return 0xffeU << 20 | + version << 19 | + (4 - s->layer) << 17 | + !s->error_protection << 16 | + s->bitrate_index << 12 | + s->sample_rate_index % 3 << 10 | + s->padding << 9 | + s->private_bit << 8 | + s->mode << 6 | + s->mode_ext << 4 | + s->copyright << 3 | + s->original << 2 | + s->emphasis; +} + #endif /* AVCODEC_MPEGAUDIODECHEADER_H */ diff --git a/libavcodec/tests/.gitignore b/libavcodec/tests/.gitignore index 4977c63e00..8e4f26c2ad 100644 --- a/libavcodec/tests/.gitignore +++ b/libavcodec/tests/.gitignore @@ -18,5 +18,6 @@ /mathops /mjpegenc_huffman /mpeg12framerate +/mpegaudiodecheader /rangecoder /snowenc diff --git a/libavcodec/tests/mpegaudiodecheader.c b/libavcodec/tests/mpegaudiodecheader.c new file mode 100644 index 0000000000..cf711bb835 --- /dev/null +++ b/libavcodec/tests/mpegaudiodecheader.c @@ -0,0 +1,120 @@ +/* + * 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 + */ + +/** + * Walks the MPEG audio frames of a file, checks that every frame header + * encodes back to its original bits, and prints each distinct header. + */ + +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include "libavutil/file.h" +#include "libavutil/intreadwrite.h" + +#include "libavcodec/mpegaudiodecheader.h" + +#define PADDING_BIT (1U << 9) + +static size_t id3v2_size(const uint8_t *buf, size_t size) +{ + if (size < 10 || memcmp(buf, "ID3", 3)) + return 0; + + return 10 + (buf[5] & 0x10 ? 10 : 0) + + ((buf[6] & 0x7f) << 21 | (buf[7] & 0x7f) << 14 | + (buf[8] & 0x7f) << 7 | (buf[9] & 0x7f)); +} + +static void print_header(size_t offset, unsigned frames, uint32_t bits, + const MPADecodeHeader *header) +{ + printf("offset=%zu frames=%u header=%08"PRIX32" layer=%d sample_rate=%d " + "bit_rate=%d crc=%d mode=%d mode_ext=%d copyright=%d original=%d " + "emphasis=%d private_bit=%d\n", + offset, frames, bits, header->layer, header->sample_rate, + header->bit_rate, header->error_protection, header->mode, + header->mode_ext, header->copyright, header->original, + header->emphasis, header->private_bit); +} + +int main(int argc, char *argv[]) +{ + MPADecodeHeader run_header; + uint8_t *buf; + size_t size, pos, run_offset = 0; + uint32_t run_bits = 0; + unsigned run_frames = 0, total_frames = 0; + int ret = 1; + + if (argc != 2) { + fprintf(stderr, "Usage: %s <file>\n", argv[0]); + return 1; + } + + if (av_file_map(argv[1], &buf, &size, 0, NULL) < 0) { + fprintf(stderr, "Failed to open '%s'\n", argv[1]); + return 1; + } + + pos = id3v2_size(buf, size); + while (pos + 4 <= size) { + uint32_t bits = AV_RB32(buf + pos); + MPADecodeHeader header; + uint32_t encoded; + int decoded; + + if (ff_mpa_check_header(bits) < 0) { + pos++; + continue; + } + + decoded = avpriv_mpegaudio_decode_header(&header, bits); + encoded = ff_mpa_encode_header(&header); + if (encoded != bits) { + printf("offset=%zu header=%08"PRIX32" encoded back as %08"PRIX32"\n", + pos, bits, encoded); + goto end; + } + + if (run_frames && (bits & ~PADDING_BIT) != (run_bits & ~PADDING_BIT)) { + print_header(run_offset, run_frames, run_bits, &run_header); + run_frames = 0; + } + if (!run_frames) { + run_offset = pos; + run_bits = bits; + run_header = header; + } + run_frames++; + total_frames++; + + pos += decoded == 0 ? header.frame_size : 1; + } + if (run_frames) + print_header(run_offset, run_frames, run_bits, &run_header); + + printf("frames=%u\n", total_frames); + ret = !total_frames; + +end: + av_file_unmap(buf, size); + return ret; +} diff --git a/tests/fate/mp3.mak b/tests/fate/mp3.mak index 145ce72a6e..edce1f98e6 100644 --- a/tests/fate/mp3.mak +++ b/tests/fate/mp3.mak @@ -40,3 +40,41 @@ FATE_MP3-$(call DEMDEC, MP3, MP3FLOAT, ARESAMPLE_FILTER PIPE_PROTOCOL) += $(FATE FATE_SAMPLES_AVCONV += $(FATE_MP3-yes) fate-mp3: $(FATE_MP3-yes) + +MPA_HEADER_TESTBIN = libavcodec/tests/mpegaudiodecheader$(EXESUF) + +FATE_MPA_HEADER-$(CONFIG_MPEGAUDIOHEADER) += fate-mpegaudiodecheader-hecommon +fate-mpegaudiodecheader-hecommon: $(MPA_HEADER_TESTBIN) +fate-mpegaudiodecheader-hecommon: CMD = run "$(MPA_HEADER_TESTBIN)" "$(TARGET_SAMPLES)/mp3-conformance/hecommon.bit" + +FATE_MPA_HEADER-$(CONFIG_MPEGAUDIOHEADER) += fate-mpegaudiodecheader-bitrates +fate-mpegaudiodecheader-bitrates: $(MPA_HEADER_TESTBIN) +fate-mpegaudiodecheader-bitrates: CMD = run "$(MPA_HEADER_TESTBIN)" "$(TARGET_SAMPLES)/mp3-conformance/he_32khz.bit" + +FATE_MPA_HEADER-$(CONFIG_MPEGAUDIOHEADER) += fate-mpegaudiodecheader-mpeg25 +fate-mpegaudiodecheader-mpeg25: $(MPA_HEADER_TESTBIN) +fate-mpegaudiodecheader-mpeg25: CMD = run "$(MPA_HEADER_TESTBIN)" "$(TARGET_SAMPLES)/id3v2/lang_xxx.mp3" + +FATE_MPA_HEADER-$(CONFIG_MPEGAUDIOHEADER) += fate-mpegaudiodecheader-joint-stereo +fate-mpegaudiodecheader-joint-stereo: $(MPA_HEADER_TESTBIN) +fate-mpegaudiodecheader-joint-stereo: CMD = run "$(MPA_HEADER_TESTBIN)" "$(TARGET_SAMPLES)/mp3-conformance/sin1k0db.bit" + +FATE_MPA_HEADER-$(CONFIG_MPEGAUDIOHEADER) += fate-mpegaudiodecheader-xing +fate-mpegaudiodecheader-xing: $(MPA_HEADER_TESTBIN) +fate-mpegaudiodecheader-xing: CMD = run "$(MPA_HEADER_TESTBIN)" "$(TARGET_SAMPLES)/exif/embedded_small.mp3" + +FATE_MPA_HEADER-$(call ALLYES, MPEGAUDIOHEADER MP3_DEMUXER MP3FLOAT_DECODER ARESAMPLE_FILTER MP2_ENCODER MP2_MUXER FILE_PROTOCOL) += fate-mpegaudiodecheader-mp2 +fate-mpegaudiodecheader-mp2: $(MPA_HEADER_TESTBIN) +fate-mpegaudiodecheader-mp2: CMD = run_with_temp "$(FFMPEG) -nostdin -hide_banner -loglevel error -i $(TARGET_SAMPLES)/mp3-conformance/compl.bit -ar 24000 -c:a mp2 -fflags +bitexact -f mp2 -y" "$(MPA_HEADER_TESTBIN)" mp2 + +FATE_MPA_HEADER-$(call REMUX, MP3, MPEGAUDIOHEADER) += fate-mpegaudiodecheader-mp3-remux +fate-mpegaudiodecheader-mp3-remux: $(MPA_HEADER_TESTBIN) +fate-mpegaudiodecheader-mp3-remux: CMD = run_with_temp "$(FFMPEG) -nostdin -hide_banner -loglevel error -i $(TARGET_SAMPLES)/exif/embedded_small.mp3 -map 0:a -c copy -fflags +bitexact -f mp3 -y" "$(MPA_HEADER_TESTBIN)" mp3 + +FATE_MPA_HEADER_LAME-$(call ENCDEC, LIBMP3LAME PCM_S16LE, MP3 WAV, MPEGAUDIOHEADER PAN_FILTER ARESAMPLE_FILTER) += fate-mpegaudiodecheader-libmp3lame +fate-mpegaudiodecheader-libmp3lame: tests/data/asynth-44100-2.wav $(MPA_HEADER_TESTBIN) +fate-mpegaudiodecheader-libmp3lame: CMD = run_with_temp "$(FFMPEG) -nostdin -hide_banner -loglevel error -i $(TARGET_PATH)/tests/data/asynth-44100-2.wav -af pan=stereo|c0=c0|c1=c0 -c:a libmp3lame -b:a 128k -fflags +bitexact -f mp3 -y" "$(MPA_HEADER_TESTBIN)" mp3 + +FATE_SAMPLES_FFMPEG += $(FATE_MPA_HEADER-yes) +FATE_FFMPEG += $(FATE_MPA_HEADER_LAME-yes) +fate-mpegaudiodecheader: $(FATE_MPA_HEADER-yes) $(FATE_MPA_HEADER_LAME-yes) diff --git a/tests/ref/fate/mpegaudiodecheader-bitrates b/tests/ref/fate/mpegaudiodecheader-bitrates new file mode 100644 index 0000000000..2b41fc573b --- /dev/null +++ b/tests/ref/fate/mpegaudiodecheader-bitrates @@ -0,0 +1,15 @@ +offset=0 frames=10 header=FFFB18C0 layer=3 sample_rate=32000 bit_rate=32000 crc=0 mode=3 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=1440 frames=10 header=FFFB28C0 layer=3 sample_rate=32000 bit_rate=40000 crc=0 mode=3 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=3240 frames=10 header=FFFB38C0 layer=3 sample_rate=32000 bit_rate=48000 crc=0 mode=3 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=5400 frames=10 header=FFFB48C0 layer=3 sample_rate=32000 bit_rate=56000 crc=0 mode=3 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=7920 frames=10 header=FFFB58C0 layer=3 sample_rate=32000 bit_rate=64000 crc=0 mode=3 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=10800 frames=10 header=FFFB68C0 layer=3 sample_rate=32000 bit_rate=80000 crc=0 mode=3 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=14400 frames=10 header=FFFB78C0 layer=3 sample_rate=32000 bit_rate=96000 crc=0 mode=3 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=18720 frames=10 header=FFFB88C0 layer=3 sample_rate=32000 bit_rate=112000 crc=0 mode=3 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=23760 frames=10 header=FFFB98C0 layer=3 sample_rate=32000 bit_rate=128000 crc=0 mode=3 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=29520 frames=10 header=FFFBA8C0 layer=3 sample_rate=32000 bit_rate=160000 crc=0 mode=3 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=36720 frames=10 header=FFFBB8C0 layer=3 sample_rate=32000 bit_rate=192000 crc=0 mode=3 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=45360 frames=10 header=FFFBC8C0 layer=3 sample_rate=32000 bit_rate=224000 crc=0 mode=3 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=55440 frames=10 header=FFFBD8C0 layer=3 sample_rate=32000 bit_rate=256000 crc=0 mode=3 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=66960 frames=20 header=FFFBE8C0 layer=3 sample_rate=32000 bit_rate=320000 crc=0 mode=3 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +frames=150 diff --git a/tests/ref/fate/mpegaudiodecheader-hecommon b/tests/ref/fate/mpegaudiodecheader-hecommon new file mode 100644 index 0000000000..4ea48dced2 --- /dev/null +++ b/tests/ref/fate/mpegaudiodecheader-hecommon @@ -0,0 +1,10 @@ +offset=0 frames=5 header=FFFB9000 layer=3 sample_rate=44100 bit_rate=128000 crc=0 mode=0 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=2089 frames=1 header=FFFA9200 layer=3 sample_rate=44100 bit_rate=128000 crc=1 mode=0 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=2507 frames=1 header=FFFA9300 layer=3 sample_rate=44100 bit_rate=128000 crc=1 mode=0 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=1 +offset=2925 frames=1 header=FFFA9208 layer=3 sample_rate=44100 bit_rate=128000 crc=1 mode=0 mode_ext=0 copyright=1 original=0 emphasis=0 private_bit=0 +offset=3343 frames=1 header=FFFA9204 layer=3 sample_rate=44100 bit_rate=128000 crc=1 mode=0 mode_ext=0 copyright=0 original=1 emphasis=0 private_bit=0 +offset=3761 frames=1 header=FFFA9201 layer=3 sample_rate=44100 bit_rate=128000 crc=1 mode=0 mode_ext=0 copyright=0 original=0 emphasis=1 private_bit=0 +offset=4179 frames=1 header=FFFA9202 layer=3 sample_rate=44100 bit_rate=128000 crc=1 mode=0 mode_ext=0 copyright=0 original=0 emphasis=2 private_bit=0 +offset=4597 frames=1 header=FFFA9203 layer=3 sample_rate=44100 bit_rate=128000 crc=1 mode=0 mode_ext=0 copyright=0 original=0 emphasis=3 private_bit=0 +offset=5015 frames=18 header=FFFA9200 layer=3 sample_rate=44100 bit_rate=128000 crc=1 mode=0 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +frames=30 diff --git a/tests/ref/fate/mpegaudiodecheader-joint-stereo b/tests/ref/fate/mpegaudiodecheader-joint-stereo new file mode 100644 index 0000000000..b813cad988 --- /dev/null +++ b/tests/ref/fate/mpegaudiodecheader-joint-stereo @@ -0,0 +1,2 @@ +offset=215 frames=318 header=FFFB9260 layer=3 sample_rate=44100 bit_rate=128000 crc=0 mode=1 mode_ext=2 copyright=0 original=0 emphasis=0 private_bit=0 +frames=318 diff --git a/tests/ref/fate/mpegaudiodecheader-libmp3lame b/tests/ref/fate/mpegaudiodecheader-libmp3lame new file mode 100644 index 0000000000..3955a43426 --- /dev/null +++ b/tests/ref/fate/mpegaudiodecheader-libmp3lame @@ -0,0 +1,3 @@ +offset=20 frames=1 header=FFFB9000 layer=3 sample_rate=44100 bit_rate=128000 crc=0 mode=0 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=437 frames=231 header=FFFB9064 layer=3 sample_rate=44100 bit_rate=128000 crc=0 mode=1 mode_ext=2 copyright=0 original=1 emphasis=0 private_bit=0 +frames=232 diff --git a/tests/ref/fate/mpegaudiodecheader-mp2 b/tests/ref/fate/mpegaudiodecheader-mp2 new file mode 100644 index 0000000000..df9e9ff8d7 --- /dev/null +++ b/tests/ref/fate/mpegaudiodecheader-mp2 @@ -0,0 +1,2 @@ +offset=0 frames=109 header=FFF5E4C4 layer=2 sample_rate=24000 bit_rate=160000 crc=0 mode=3 mode_ext=0 copyright=0 original=1 emphasis=0 private_bit=0 +frames=109 diff --git a/tests/ref/fate/mpegaudiodecheader-mp3-remux b/tests/ref/fate/mpegaudiodecheader-mp3-remux new file mode 100644 index 0000000000..c5d4d5cdd7 --- /dev/null +++ b/tests/ref/fate/mpegaudiodecheader-mp3-remux @@ -0,0 +1,3 @@ +offset=301 frames=1 header=FFFB9000 layer=3 sample_rate=44100 bit_rate=128000 crc=0 mode=0 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=718 frames=21 header=FFFB9064 layer=3 sample_rate=44100 bit_rate=128000 crc=0 mode=1 mode_ext=2 copyright=0 original=1 emphasis=0 private_bit=0 +frames=22 diff --git a/tests/ref/fate/mpegaudiodecheader-mpeg25 b/tests/ref/fate/mpegaudiodecheader-mpeg25 new file mode 100644 index 0000000000..c3a11dcd37 --- /dev/null +++ b/tests/ref/fate/mpegaudiodecheader-mpeg25 @@ -0,0 +1,3 @@ +offset=1062 frames=1 header=FFE338C0 layer=3 sample_rate=8000 bit_rate=24000 crc=0 mode=3 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=1278 frames=4 header=FFE318C4 layer=3 sample_rate=8000 bit_rate=8000 crc=0 mode=3 mode_ext=0 copyright=0 original=1 emphasis=0 private_bit=0 +frames=5 diff --git a/tests/ref/fate/mpegaudiodecheader-xing b/tests/ref/fate/mpegaudiodecheader-xing new file mode 100644 index 0000000000..fbaafedc48 --- /dev/null +++ b/tests/ref/fate/mpegaudiodecheader-xing @@ -0,0 +1,3 @@ +offset=16110 frames=1 header=FFFB4000 layer=3 sample_rate=44100 bit_rate=56000 crc=0 mode=0 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 +offset=16292 frames=21 header=FFFB9064 layer=3 sample_rate=44100 bit_rate=128000 crc=0 mode=1 mode_ext=2 copyright=0 original=1 emphasis=0 private_bit=0 +frames=22 -- 2.52.0 >From e2104b5b9f669678e146060c8f065fc7ad51097a Mon Sep 17 00:00:00 2001 From: Romain Beauxis <[email protected]> Date: Tue, 15 Sep 2026 10:41:39 -0500 Subject: [PATCH 3/3] avformat/mp3enc: build the Xing frame header from the first audio frame Some hardware decoders reject files whose Xing frame header differs from the audio frames, e.g. joint stereo from libmp3lame. Fail when the first frame does not match the Xing frame layout. Fixes #24129. --- libavformat/mp3enc.c | 61 +++++++++++++------- tests/ref/fate/gapless-mp3-remux | 2 +- tests/ref/fate/mpegaudiodecheader-libmp3lame | 3 +- tests/ref/fate/mpegaudiodecheader-mp3-remux | 3 +- 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/libavformat/mp3enc.c b/libavformat/mp3enc.c index 6d42feb1ff..c11f5daa9e 100644 --- a/libavformat/mp3enc.c +++ b/libavformat/mp3enc.c @@ -126,6 +126,9 @@ typedef struct MP3Context { uint64_t bag[XING_NUM_BAGS]; int initial_bitrate; int has_variable_bitrate; + MPADecodeHeader xing_header; + MPADecodeHeader first_frame; + int first_frame_seen; int delay; int64_t padding; @@ -149,14 +152,13 @@ static int mp3_write_xing(AVFormatContext *s) AVCodecParameters *par = s->streams[mp3->audio_stream_idx]->codecpar; AVDictionaryEntry *enc = av_dict_get(s->streams[mp3->audio_stream_idx]->metadata, "encoder", NULL, 0); AVIOContext *dyn_ctx; - int32_t header; - MPADecodeHeader mpah; - int srate_idx, i, channels; + uint32_t header; + MPADecodeHeader mpah = { .layer = 3 }; + int srate_idx, i; int bitrate_idx; int best_bitrate_idx = -1; int best_bitrate_error = INT_MAX; int ret; - int ver = 0; int bytes_needed; if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL) || !mp3->write_xing) @@ -165,12 +167,11 @@ static int mp3_write_xing(AVFormatContext *s) for (i = 0; i < FF_ARRAY_ELEMS(ff_mpa_freq_tab); i++) { const uint16_t base_freq = ff_mpa_freq_tab[i]; - if (par->sample_rate == base_freq) ver = 0x3; // MPEG 1 - else if (par->sample_rate == base_freq / 2) ver = 0x2; // MPEG 2 - else if (par->sample_rate == base_freq / 4) ver = 0x0; // MPEG 2.5 + if (par->sample_rate == base_freq) srate_idx = i; // MPEG 1 + else if (par->sample_rate == base_freq / 2) srate_idx = i + 3; // MPEG 2 + else if (par->sample_rate == base_freq / 4) srate_idx = i + 6; // MPEG 2.5 else continue; - srate_idx = i; break; } if (i == FF_ARRAY_ELEMS(ff_mpa_freq_tab)) { @@ -179,21 +180,17 @@ static int mp3_write_xing(AVFormatContext *s) } switch (par->ch_layout.nb_channels) { - case 1: channels = MPA_MONO; break; - case 2: channels = MPA_STEREO; break; + case 1: mpah.mode = MPA_MONO; break; + case 2: mpah.mode = MPA_STEREO; break; default: av_log(s, AV_LOG_WARNING, "Unsupported number of channels, " "not writing Xing header.\n"); return -1; } - /* dummy MPEG audio header */ - header = 0xffU << 24; // sync - header |= (0x7 << 5 | ver << 3 | 0x1 << 1 | 0x1) << 16; // sync/audio-version/layer 3/no crc*/ - header |= (srate_idx << 2) << 8; - header |= channels << 6; + mpah.sample_rate_index = srate_idx; for (bitrate_idx = 1; bitrate_idx < 15; bitrate_idx++) { - int bit_rate = 1000 * ff_mpa_bitrate_tab[ver != 3][3 - 1][bitrate_idx]; + int bit_rate = 1000 * ff_mpa_bitrate_tab[srate_idx >= 3][3 - 1][bitrate_idx]; int error = FFABS(bit_rate - par->bit_rate); if (error < best_bitrate_error) { @@ -204,10 +201,10 @@ static int mp3_write_xing(AVFormatContext *s) av_assert0(best_bitrate_idx >= 0); for (bitrate_idx = best_bitrate_idx; ; bitrate_idx++) { - int32_t mask = bitrate_idx << (4 + 8); if (15 == bitrate_idx) return -1; - header |= mask; + mpah.bitrate_index = bitrate_idx; + header = ff_mpa_encode_header(&mpah); ret = avpriv_mpegaudio_decode_header(&mpah, header); av_assert0(ret >= 0); @@ -216,9 +213,8 @@ static int mp3_write_xing(AVFormatContext *s) if (bytes_needed <= mpah.frame_size) break; - - header &= ~mask; } + mp3->xing_header = mpah; ret = avio_open_dyn_buf(&dyn_ctx); if (ret < 0) @@ -332,6 +328,22 @@ static int mp3_write_audio_packet(AVFormatContext *s, AVPacket *pkt) mp3->initial_bitrate = mpah.bit_rate; if ((mpah.bit_rate == 0) || (mp3->initial_bitrate != mpah.bit_rate)) mp3->has_variable_bitrate = 1; + if (!mp3->first_frame_seen) { + const MPADecodeHeader *xing = &mp3->xing_header; + + if (mp3->xing_offset && + (mpah.layer != xing->layer || + mpah.sample_rate != xing->sample_rate || + mpah.nb_channels != xing->nb_channels)) { + av_log(s, AV_LOG_ERROR, "First audio frame (layer %d, %d Hz, %d channels) " + "does not match the stream (layer %d, %d Hz, %d channels).\n", + mpah.layer, mpah.sample_rate, mpah.nb_channels, + xing->layer, xing->sample_rate, xing->nb_channels); + return AVERROR_INVALIDDATA; + } + mp3->first_frame = mpah; + mp3->first_frame_seen = 1; + } } else { av_log(s, AV_LOG_WARNING, "Audio packet of size %d (starting with %08"PRIX32"...) " "is invalid, writing it anyway.\n", pkt->size, h); @@ -441,6 +453,15 @@ static void mp3_update_xing(AVFormatContext *s) if (!mp3->has_variable_bitrate) AV_WL32(mp3->xing_frame + mp3->xing_offset, MKTAG('I', 'n', 'f', 'o')); + if (mp3->first_frame_seen) { + MPADecodeHeader header = mp3->first_frame; + + header.error_protection = mp3->xing_header.error_protection; + header.bitrate_index = mp3->xing_header.bitrate_index; + header.padding = mp3->xing_header.padding; + AV_WB32(mp3->xing_frame, ff_mpa_encode_header(&header)); + } + AV_WB32(mp3->xing_frame + mp3->xing_offset + 8, mp3->frames); AV_WB32(mp3->xing_frame + mp3->xing_offset + 12, mp3->size); diff --git a/tests/ref/fate/gapless-mp3-remux b/tests/ref/fate/gapless-mp3-remux index 0d3d625e33..6d56043a7f 100644 --- a/tests/ref/fate/gapless-mp3-remux +++ b/tests/ref/fate/gapless-mp3-remux @@ -1,3 +1,3 @@ -4458dffaa3fd294d708f05965e1a3186 *tests/data/fate/gapless-mp3-remux.mp3 +a490eb522f8fd2c772d1e27a1b5054a2 *tests/data/fate/gapless-mp3-remux.mp3 249138 tests/data/fate/gapless-mp3-remux.mp3 stream|start_pts=353600|duration_ts=218521600 diff --git a/tests/ref/fate/mpegaudiodecheader-libmp3lame b/tests/ref/fate/mpegaudiodecheader-libmp3lame index 3955a43426..ddf18712e5 100644 --- a/tests/ref/fate/mpegaudiodecheader-libmp3lame +++ b/tests/ref/fate/mpegaudiodecheader-libmp3lame @@ -1,3 +1,2 @@ -offset=20 frames=1 header=FFFB9000 layer=3 sample_rate=44100 bit_rate=128000 crc=0 mode=0 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 -offset=437 frames=231 header=FFFB9064 layer=3 sample_rate=44100 bit_rate=128000 crc=0 mode=1 mode_ext=2 copyright=0 original=1 emphasis=0 private_bit=0 +offset=20 frames=232 header=FFFB9064 layer=3 sample_rate=44100 bit_rate=128000 crc=0 mode=1 mode_ext=2 copyright=0 original=1 emphasis=0 private_bit=0 frames=232 diff --git a/tests/ref/fate/mpegaudiodecheader-mp3-remux b/tests/ref/fate/mpegaudiodecheader-mp3-remux index c5d4d5cdd7..4a7602dc83 100644 --- a/tests/ref/fate/mpegaudiodecheader-mp3-remux +++ b/tests/ref/fate/mpegaudiodecheader-mp3-remux @@ -1,3 +1,2 @@ -offset=301 frames=1 header=FFFB9000 layer=3 sample_rate=44100 bit_rate=128000 crc=0 mode=0 mode_ext=0 copyright=0 original=0 emphasis=0 private_bit=0 -offset=718 frames=21 header=FFFB9064 layer=3 sample_rate=44100 bit_rate=128000 crc=0 mode=1 mode_ext=2 copyright=0 original=1 emphasis=0 private_bit=0 +offset=301 frames=22 header=FFFB9064 layer=3 sample_rate=44100 bit_rate=128000 crc=0 mode=1 mode_ext=2 copyright=0 original=1 emphasis=0 private_bit=0 frames=22 -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
