[PR] avformat/mp3enc: keep trailing padding spanning several packets (PR #23993)

Romain Beauxis via ffmpeg-devel <[email protected]> Mon, 03 Aug 2026 05:16:27 -0000
Newsgroups gmane.comp.video.ffmpeg.devel
Message-ID <178573418843.59.8994461824117073912@29965ddac10e>
PR #23993 opened by Romain Beauxis (toots)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23993
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23993.patch

The trailing padding is read from the AV_PKT_DATA_SKIP_SAMPLES side data of
every packet, overwriting the previous value, so only the last packet was
ever accounted for. A single packet holds at most one frame, which caps the
padding that can be written at 1152 + 528 + 1 samples.

LAME regularly reports more than that: gapless/gapless.mp3 carries 1984 and
comes out of a stream copy with 1681, decoding to 303 samples more than the
file it was copied from.

Accumulate instead, and add the decoder delay once the total is known.

Fixes: https://trac.ffmpeg.org/ticket/9755



>From 30b680889fb08fedd2bd8055d6b95220e6a7907a Mon Sep 17 00:00:00 2001
From: Romain Beauxis <[email protected]>
Date: Sun, 2 Aug 2026 13:17:48 -0500
Subject: [PATCH] avformat/mp3enc: keep trailing padding spanning several
 packets

The trailing padding is read from the AV_PKT_DATA_SKIP_SAMPLES side data of
every packet, overwriting the previous value, so only the last packet was
ever accounted for. A single packet holds at most one frame, which caps the
padding that can be written at 1152 + 528 + 1 samples.

LAME regularly reports more than that: gapless/gapless.mp3 carries 1984 and
comes out of a stream copy with 1681, decoding to 303 samples more than the
file it was copied from.

Accumulate instead, and add the decoder delay once the total is known.

Fixes: https://trac.ffmpeg.org/ticket/9755
---
 libavformat/mp3enc.c             | 8 ++++++--
 tests/fate/gapless.mak           | 5 +++++
 tests/ref/fate/gapless-mp3-remux | 3 +++
 3 files changed, 14 insertions(+), 2 deletions(-)
 create mode 100644 tests/ref/fate/gapless-mp3-remux

diff --git a/libavformat/mp3enc.c b/libavformat/mp3enc.c
index 131fb231fc..14eac3dab6 100644
--- a/libavformat/mp3enc.c
+++ b/libavformat/mp3enc.c
@@ -126,7 +126,7 @@ typedef struct MP3Context {
     int initial_bitrate;
     int has_variable_bitrate;
     int delay;
-    int padding;
+    int64_t padding;
 
     /* index of the audio stream */
     int audio_stream_idx;
@@ -367,7 +367,9 @@ static int mp3_write_audio_packet(AVFormatContext *s, AVPacket *pkt)
                                                 AV_PKT_DATA_SKIP_SAMPLES,
                                                 &side_data_size);
             if (side_data && side_data_size >= 10) {
-                mp3->padding = FFMAX((int64_t)AV_RL32(side_data + 4) + 528 + 1, 0);
+                uint32_t discard_padding = AV_RL32(side_data + 4);
+                /* Padding longer than a frame is spread over several packets. */
+                mp3->padding = discard_padding ? mp3->padding + discard_padding : 0;
                 if (!mp3->delay)
                     mp3->delay =  FFMAX((int64_t)AV_RL32(side_data) - 528 - 1, 0);
             } else {
@@ -449,6 +451,8 @@ static void mp3_update_xing(AVFormatContext *s)
     }
 
     /* write encoder delay/padding */
+    if (mp3->padding)
+        mp3->padding += 528 + 1;
     if (mp3->delay >= 1 << 12) {
         mp3->delay = (1 << 12) - 1;
         av_log(s, AV_LOG_WARNING, "Too many samples of initial padding.\n");
diff --git a/tests/fate/gapless.mak b/tests/fate/gapless.mak
index c5addeebba..5672824a44 100644
--- a/tests/fate/gapless.mak
+++ b/tests/fate/gapless.mak
@@ -4,6 +4,11 @@ fate-gapless-mp3: CMD = gapless $(TARGET_SAMPLES)/gapless/gapless.mp3 "-c:a mp3"
 FATE_GAPLESSINFO_PROBE-$(CONFIG_MP3_DEMUXER) += fate-gapless-mp3-side-data
 fate-gapless-mp3-side-data: CMD = ffprobe_demux $(TARGET_SAMPLES)/gapless/gapless.mp3
 
+# The trailing padding of this sample spans two packets. The remuxed file has to
+# decode to the very samples fate-gapless-mp3 decodes the source to.
+FATE_GAPLESS-$(call FRAMECRC, MP3, MP3, ARESAMPLE_FILTER MP3_MUXER MD5_MUXER) += fate-gapless-mp3-remux
+fate-gapless-mp3-remux: CMD = transcode mp3 $(TARGET_SAMPLES)/gapless/gapless.mp3 mp3 "-c copy" "" "" "" "-auto_conversion_filters" "" md5
+
 FATE_GAPLESS-$(call DEMDEC, MP3, MP3, ARESAMPLE_FILTER WAV_MUXER) += fate-audiomatch-square-mp3
 fate-audiomatch-square-mp3: CMD = audio_match $(TARGET_SAMPLES)/audiomatch/square3.mp3 $(SAMPLES)/audiomatch/square3.wav
 
diff --git a/tests/ref/fate/gapless-mp3-remux b/tests/ref/fate/gapless-mp3-remux
new file mode 100644
index 0000000000..8346ec0b57
--- /dev/null
+++ b/tests/ref/fate/gapless-mp3-remux
@@ -0,0 +1,3 @@
+4458dffaa3fd294d708f05965e1a3186 *tests/data/fate/gapless-mp3-remux.mp3
+249138 tests/data/fate/gapless-mp3-remux.mp3
+MD5=d6c8c874457e5a655af8e7cbb0a3e201
-- 
2.52.0

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