Commit ad74ac4cee for ffmpeg

commit ad74ac4cee9061e618b8c80d8cef8c8f4138c42b
Author: Romain Beauxis <romain.beauxis@gmail.com>
Date:   Thu Oct 15 15:05:12 2026 +0200

    avcodec/aac: export the seek pre-roll

    AAC decoding overlaps each frame with the previous one, so the first
    frame decoded after a seek is only reliable once the frame before it has
    been decoded. Opus declares its pre-roll in AVCodecParameters.seek_preroll,
    AAC did not.

    Have the decoder export its pre-roll, as it knows the frame length: 1024
    or 960 samples, half of it for LD and ELD, and 1024 or 768 for USAC.
    ELD needs three frames, as its low delay filterbank uses a window four
    frames long. SBR keeps the state of the frame before, in its
    filterbanks, its high-frequency generator and its gain smoothing, and
    codes its envelopes and noise floors relative to the frame before, so
    it needs one more frame, of twice as many output samples when it
    doubles the sample rate. Without it, the first 256 samples decoded
    after a seek into HE-AAC differ from continuous decoding by -36.6 dB
    with one frame of pre-roll, and not at all with two.

    Implicit SBR is only known once found in a frame, so the pre-roll is
    raised then, as it is for USAC, whose frame length depends on its SBR
    ratio. Demuxers get it through the stream probing, whatever the
    container. A larger pre-roll declared by the container is kept, and the
    documentation of AVCodecContext.seek_preroll now says a decoder may
    raise it.

    The output of an AAC-LC stream decoded after a seek has an error of
    -9.5 dB in the first 1024 samples, against -85 dB elsewhere, which one
    frame of pre-roll removes.

    The pre-roll tests now show the declared value, for AAC in every
    container, 4096 samples for HE-AAC, explicit or implicit, 2048 for SBR
    that does not double the sample rate, and 1536 for ELD.

diff --git a/doc/APIchanges b/doc/APIchanges
index bd4efbb40d..54350fc633 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-10-xx - xxxxxxxxxx - lavc 63.23.102 - avcodec.h
+  AVCodecContext.seek_preroll may be raised by a decoder to the pre-roll it
+  needs, as the AAC decoder does.
+
 2026-10-xx - xxxxxxxxxx - lavc 63.23.101 - codec_par.h
   avcodec_parameters_to_context() no longer sets AVCodecContext.delay from
   AVCodecParameters.initial_padding.
diff --git a/libavcodec/aac/aacdec.c b/libavcodec/aac/aacdec.c
index 7d90d254ca..d66c363b58 100644
--- a/libavcodec/aac/aacdec.c
+++ b/libavcodec/aac/aacdec.c
@@ -1296,6 +1296,29 @@ static av_cold int init_dsp(AVCodecContext *avctx)
     return 0;
 }

+/**
+ * The samples output after a seek before the output is reliable.
+ * Frames overlap, so the frame before the first one is needed, and the
+ * three before it for the low delay filterbank of ELD. SBR also keeps the
+ * state of the frame before and codes its envelopes relative to it, so one
+ * more frame is needed, of twice as many output samples when SBR doubles
+ * the sample rate.
+ */
+static int seek_preroll(const MPEG4AudioConfig *m4ac)
+{
+    int frame = m4ac->frame_length_short ? 960 : 1024;
+    int frames = 1 + (m4ac->sbr == 1);
+    int upsampled = m4ac->sbr == 1 && m4ac->ext_sample_rate > m4ac->sample_rate;
+
+    if (m4ac->object_type == AOT_USAC)
+        frame = m4ac->frame_length_short ? 768 : 1024;
+    else if (m4ac->object_type == AOT_ER_AAC_LD || m4ac->object_type == AOT_ER_AAC_ELD)
+        frame >>= 1;
+    if (m4ac->object_type == AOT_ER_AAC_ELD)
+        frames += 2;
+    return (frame * frames) << upsampled;
+}
+
 av_cold int ff_aac_decode_init(AVCodecContext *avctx)
 {
     AACDecContext *ac = avctx->priv_data;
@@ -1346,6 +1369,9 @@ av_cold int ff_aac_decode_init(AVCodecContext *avctx)
         }
     }

+    /* A larger pre-roll declared by the container is kept. */
+    avctx->seek_preroll = FFMAX(avctx->seek_preroll, seek_preroll(&ac->oc[1].m4ac));
+
     if (avctx->ch_layout.nb_channels > MAX_CHANNELS) {
         av_log(avctx, AV_LOG_ERROR, "Too many channels\n");
         return AVERROR_INVALIDDATA;
@@ -2468,6 +2494,8 @@ static int decode_frame_ga(AVCodecContext *avctx, AACDecContext *ac,
     if (ac->oc[1].status && audio_found) {
         avctx->sample_rate = ac->oc[1].m4ac.sample_rate << multiplier;
         avctx->frame_size = samples;
+        // Implicit SBR is only known once found in a frame.
+        avctx->seek_preroll = FFMAX(avctx->seek_preroll, seek_preroll(&ac->oc[1].m4ac));
         ac->oc[1].status = OC_LOCKED;
     }

diff --git a/libavcodec/aac/aacdec_usac.c b/libavcodec/aac/aacdec_usac.c
index 4d41a24f8d..e29457a1bd 100644
--- a/libavcodec/aac/aacdec_usac.c
+++ b/libavcodec/aac/aacdec_usac.c
@@ -2177,6 +2177,7 @@ int ff_aac_usac_decode_frame(AVCodecContext *avctx, AACDecContext *ac,
     if (ac->oc[1].status && audio_found) {
         avctx->sample_rate = ac->oc[1].m4ac.ext_sample_rate;
         avctx->frame_size = samples;
+        avctx->seek_preroll = FFMAX(avctx->seek_preroll, samples);
         ac->oc[1].status = OC_LOCKED;
     }

diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index 8dde6d9f6c..bb654ec5a6 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -1127,7 +1127,8 @@ typedef struct AVCodecContext {

     /**
      * Number of samples to skip after a discontinuity
-     * - decoding: unused
+     * - decoding: may be set by the user, a decoder may raise it to the
+     *             pre-roll it needs
      * - encoding: set by libavcodec
      */
     int seek_preroll;
diff --git a/libavcodec/version.h b/libavcodec/version.h
index 3af30cdc3b..9d910cf848 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -30,7 +30,7 @@
 #include "version_major.h"

 #define LIBAVCODEC_VERSION_MINOR  23
-#define LIBAVCODEC_VERSION_MICRO 101
+#define LIBAVCODEC_VERSION_MICRO 102

 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
                                                LIBAVCODEC_VERSION_MINOR, \
diff --git a/tests/ref/fate/aac-seek-preroll-eld b/tests/ref/fate/aac-seek-preroll-eld
index a491c65571..6ef90c3197 100644
--- a/tests/ref/fate/aac-seek-preroll-eld
+++ b/tests/ref/fate/aac-seek-preroll-eld
@@ -1 +1 @@
-stream|profile=ELD|sample_rate=48000|seek_preroll=0
+stream|profile=ELD|sample_rate=48000|seek_preroll=1536
diff --git a/tests/ref/fate/aac-seek-preroll-frame-length-short b/tests/ref/fate/aac-seek-preroll-frame-length-short
index f749ee3324..b7ad1ef762 100644
--- a/tests/ref/fate/aac-seek-preroll-frame-length-short
+++ b/tests/ref/fate/aac-seek-preroll-frame-length-short
@@ -1 +1 @@
-stream|profile=LC|sample_rate=48000|seek_preroll=0
+stream|profile=LC|sample_rate=48000|seek_preroll=960
diff --git a/tests/ref/fate/aac-seek-preroll-sbr b/tests/ref/fate/aac-seek-preroll-sbr
index 184174cf41..94656dc48b 100644
--- a/tests/ref/fate/aac-seek-preroll-sbr
+++ b/tests/ref/fate/aac-seek-preroll-sbr
@@ -1 +1 @@
-stream|profile=HE-AAC|sample_rate=48000|seek_preroll=0
+stream|profile=HE-AAC|sample_rate=48000|seek_preroll=4096
diff --git a/tests/ref/fate/aac-seek-preroll-sbr-no-doubling b/tests/ref/fate/aac-seek-preroll-sbr-no-doubling
index 184174cf41..77aba65d62 100644
--- a/tests/ref/fate/aac-seek-preroll-sbr-no-doubling
+++ b/tests/ref/fate/aac-seek-preroll-sbr-no-doubling
@@ -1 +1 @@
-stream|profile=HE-AAC|sample_rate=48000|seek_preroll=0
+stream|profile=HE-AAC|sample_rate=48000|seek_preroll=2048
diff --git a/tests/ref/fate/adts-noparse-demux b/tests/ref/fate/adts-noparse-demux
index f0a763525e..b1bfdd41b2 100644
--- a/tests/ref/fate/adts-noparse-demux
+++ b/tests/ref/fate/adts-noparse-demux
@@ -3,5 +3,5 @@ packet|codec_type=audio|stream_index=0|pts=602112|pts_time=0.021333|dts=602112|d
 packet|codec_type=audio|stream_index=0|pts=1204224|pts_time=0.042667|dts=1204224|dts_time=0.042667|duration=602112|duration_time=0.021333|size=502|pos=1198|flags=K__|data_hash=CRC32:b614d97e
 packet|codec_type=audio|stream_index=0|pts=1806336|pts_time=0.064000|dts=1806336|dts_time=0.064000|duration=602112|duration_time=0.021333|size=495|pos=1700|flags=K__|data_hash=CRC32:dffd387b
 packet|codec_type=audio|stream_index=0|pts=2408448|pts_time=0.085333|dts=2408448|dts_time=0.085333|duration=602112|duration_time=0.021333|size=493|pos=2195|flags=K__|data_hash=CRC32:119f413d
-stream|index=0|codec_name=aac|profile=1|level=-99|codec_type=audio|codec_tag_string=[0][0][0][0]|codec_tag=0x0000|mime_codec_string=mp4a.40.2|sample_fmt=fltp|sample_rate=48000|channels=2|channel_layout=stereo|bits_per_sample=0|initial_padding=0|seek_preroll=0|id=N/A|r_frame_rate=0/0|avg_frame_rate=0/0|time_base=1/28224000|start_pts=N/A|start_time=N/A|duration_ts=N/A|duration=N/A|bit_rate=N/A|max_bit_rate=N/A|bits_per_raw_sample=N/A|nb_frames=N/A|nb_read_frames=N/A|nb_read_packets=5|disposition:default=0|disposition:dub=0|disposition:original=0|disposition:comment=0|disposition:lyrics=0|disposition:karaoke=0|disposition:forced=0|disposition:hearing_impaired=0|disposition:visual_impaired=0|disposition:clean_effects=0|disposition:attached_pic=0|disposition:timed_thumbnails=0|disposition:non_diegetic=0|disposition:captions=0|disposition:descriptions=0|disposition:metadata=0|disposition:dependent=0|disposition:still_image=0|disposition:multilayer=0
+stream|index=0|codec_name=aac|profile=1|level=-99|codec_type=audio|codec_tag_string=[0][0][0][0]|codec_tag=0x0000|mime_codec_string=mp4a.40.2|sample_fmt=fltp|sample_rate=48000|channels=2|channel_layout=stereo|bits_per_sample=0|initial_padding=0|seek_preroll=1024|id=N/A|r_frame_rate=0/0|avg_frame_rate=0/0|time_base=1/28224000|start_pts=N/A|start_time=N/A|duration_ts=N/A|duration=N/A|bit_rate=N/A|max_bit_rate=N/A|bits_per_raw_sample=N/A|nb_frames=N/A|nb_read_frames=N/A|nb_read_packets=5|disposition:default=0|disposition:dub=0|disposition:original=0|disposition:comment=0|disposition:lyrics=0|disposition:karaoke=0|disposition:forced=0|disposition:hearing_impaired=0|disposition:visual_impaired=0|disposition:clean_effects=0|disposition:attached_pic=0|disposition:timed_thumbnails=0|disposition:non_diegetic=0|disposition:captions=0|disposition:descriptions=0|disposition:metadata=0|disposition:dependent=0|disposition:still_image=0|disposition:multilayer=0
 format|filename=id3v1.aac|nb_streams=1|nb_programs=0|nb_stream_groups=0|format_name=aac|start_time=N/A|duration=N/A|size=2816|bit_rate=N/A|probe_score=0|tag:title=Title|tag:artist=Artist|tag:genre=Blues
diff --git a/tests/ref/fate/flv-demux b/tests/ref/fate/flv-demux
index 6d6ff54b30..c8c70f8787 100644
--- a/tests/ref/fate/flv-demux
+++ b/tests/ref/fate/flv-demux
@@ -602,5 +602,5 @@ packet|codec_type=video|stream_index=0|pts=11645|pts_time=11.645000|dts=11645|dt
 packet|codec_type=audio|stream_index=1|pts=11656|pts_time=11.656000|dts=11656|dts_time=11.656000|duration=46|duration_time=0.046000|size=346|pos=510431|flags=K__|data_hash=CRC32:4e6b44cb
 packet|codec_type=video|stream_index=0|pts=11678|pts_time=11.678000|dts=11678|dts_time=11.678000|duration=33|duration_time=0.033000|size=1190|pos=510794|flags=__C|data_hash=CRC32:a0206c90
 stream|index=0|codec_name=h264|profile=77|level=21|codec_type=video|codec_tag_string=[0][0][0][0]|codec_tag=0x0000|mime_codec_string=avc1.4d4015|width=426|height=240|coded_width=426|coded_height=240|has_b_frames=1|sample_aspect_ratio=1:1|display_aspect_ratio=71:40|pix_fmt=yuv420p|color_range=unknown|color_space=unknown|color_transfer=unknown|color_primaries=unknown|chroma_location=left|field_order=progressive|is_avc=true|nal_length_size=4|id=N/A|r_frame_rate=30000/1001|avg_frame_rate=30/1|time_base=1/1000|start_pts=0|start_time=0.000000|duration_ts=N/A|duration=N/A|bit_rate=393929|max_bit_rate=N/A|bits_per_raw_sample=8|nb_frames=N/A|nb_read_frames=N/A|nb_read_packets=351|extradata_size=39|extradata_hash=CRC32:07b85ca9|disposition:default=0|disposition:dub=0|disposition:original=0|disposition:comment=0|disposition:lyrics=0|disposition:karaoke=0|disposition:forced=0|disposition:hearing_impaired=0|disposition:visual_impaired=0|disposition:clean_effects=0|disposition:attached_pic=0|disposition:timed_thumbnails=0|disposition:non_diegetic=0|disposition:captions=0|disposition:descriptions=0|disposition:metadata=0|disposition:dependent=0|disposition:still_image=0|disposition:multilayer=0
-stream|index=1|codec_name=aac|profile=1|level=-99|codec_type=audio|codec_tag_string=[0][0][0][0]|codec_tag=0x0000|mime_codec_string=mp4a.40.2|sample_fmt=fltp|sample_rate=22050|channels=2|channel_layout=stereo|bits_per_sample=0|initial_padding=0|seek_preroll=0|id=N/A|r_frame_rate=0/0|avg_frame_rate=0/0|time_base=1/1000|start_pts=0|start_time=0.000000|duration_ts=N/A|duration=N/A|bit_rate=67874|max_bit_rate=N/A|bits_per_raw_sample=N/A|nb_frames=N/A|nb_read_frames=N/A|nb_read_packets=252|extradata_size=2|extradata_hash=CRC32:d039c029|disposition:default=0|disposition:dub=0|disposition:original=0|disposition:comment=0|disposition:lyrics=0|disposition:karaoke=0|disposition:forced=0|disposition:hearing_impaired=0|disposition:visual_impaired=0|disposition:clean_effects=0|disposition:attached_pic=0|disposition:timed_thumbnails=0|disposition:non_diegetic=0|disposition:captions=0|disposition:descriptions=0|disposition:metadata=0|disposition:dependent=0|disposition:still_image=0|disposition:multilayer=0
+stream|index=1|codec_name=aac|profile=1|level=-99|codec_type=audio|codec_tag_string=[0][0][0][0]|codec_tag=0x0000|mime_codec_string=mp4a.40.2|sample_fmt=fltp|sample_rate=22050|channels=2|channel_layout=stereo|bits_per_sample=0|initial_padding=0|seek_preroll=1024|id=N/A|r_frame_rate=0/0|avg_frame_rate=0/0|time_base=1/1000|start_pts=0|start_time=0.000000|duration_ts=N/A|duration=N/A|bit_rate=67874|max_bit_rate=N/A|bits_per_raw_sample=N/A|nb_frames=N/A|nb_read_frames=N/A|nb_read_packets=252|extradata_size=2|extradata_hash=CRC32:d039c029|disposition:default=0|disposition:dub=0|disposition:original=0|disposition:comment=0|disposition:lyrics=0|disposition:karaoke=0|disposition:forced=0|disposition:hearing_impaired=0|disposition:visual_impaired=0|disposition:clean_effects=0|disposition:attached_pic=0|disposition:timed_thumbnails=0|disposition:non_diegetic=0|disposition:captions=0|disposition:descriptions=0|disposition:metadata=0|disposition:dependent=0|disposition:still_image=0|disposition:multilayer=0
 format|filename=Enigma_Principles_of_Lust-part.flv|nb_streams=2|nb_programs=0|nb_stream_groups=0|format_name=flv|start_time=0.000000|duration=210.209999|size=512000|bit_rate=19485|probe_score=100|tag:hasKeyframes=true|tag:hasMetadata=true|tag:datasize=11970544|tag:hasVideo=true|tag:canSeekToEnd=false|tag:lasttimestamp=210|tag:lastkeyframetimestamp=210|tag:audiosize=1791332|tag:hasAudio=true|tag:audiodelay=0|tag:videosize=10176110|tag:metadatadate=2011-02-27T11:00:33.125000Z|tag:metadatacreator=inlet media FLVTool2 v1.0.6 - http://www.inlet-media.de/flvtool2|tag:hasCuePoints=false
diff --git a/tests/ref/fate/mov-aac-2048-priming-preroll b/tests/ref/fate/mov-aac-2048-priming-preroll
index 27fc25da22..0726b0cc2c 100644
--- a/tests/ref/fate/mov-aac-2048-priming-preroll
+++ b/tests/ref/fate/mov-aac-2048-priming-preroll
@@ -1 +1 @@
-stream|initial_padding=2048|seek_preroll=0
+stream|initial_padding=2048|seek_preroll=1024
diff --git a/tests/ref/fate/preroll-aac-adts-declared b/tests/ref/fate/preroll-aac-adts-declared
index ff5c5aab2a..59696b7b45 100644
--- a/tests/ref/fate/preroll-aac-adts-declared
+++ b/tests/ref/fate/preroll-aac-adts-declared
@@ -1 +1 @@
-stream|codec_name=aac|sample_rate=48000|initial_padding=0|seek_preroll=0
+stream|codec_name=aac|sample_rate=48000|initial_padding=0|seek_preroll=1024
diff --git a/tests/ref/fate/preroll-aac-caf-declared b/tests/ref/fate/preroll-aac-caf-declared
index 444e0046b3..e73865c4b0 100644
--- a/tests/ref/fate/preroll-aac-caf-declared
+++ b/tests/ref/fate/preroll-aac-caf-declared
@@ -1 +1 @@
-stream|codec_name=aac|sample_rate=48000|initial_padding=2112|seek_preroll=0
+stream|codec_name=aac|sample_rate=48000|initial_padding=2112|seek_preroll=1024
diff --git a/tests/ref/fate/preroll-aac-flv-declared b/tests/ref/fate/preroll-aac-flv-declared
index ff5c5aab2a..59696b7b45 100644
--- a/tests/ref/fate/preroll-aac-flv-declared
+++ b/tests/ref/fate/preroll-aac-flv-declared
@@ -1 +1 @@
-stream|codec_name=aac|sample_rate=48000|initial_padding=0|seek_preroll=0
+stream|codec_name=aac|sample_rate=48000|initial_padding=0|seek_preroll=1024
diff --git a/tests/ref/fate/preroll-aac-he-adts-declared b/tests/ref/fate/preroll-aac-he-adts-declared
index 4428dd300e..1a9b5fc781 100644
--- a/tests/ref/fate/preroll-aac-he-adts-declared
+++ b/tests/ref/fate/preroll-aac-he-adts-declared
@@ -1 +1 @@
-stream|codec_name=aac|sample_rate=44100|initial_padding=0|seek_preroll=0
+stream|codec_name=aac|sample_rate=44100|initial_padding=0|seek_preroll=4096
diff --git a/tests/ref/fate/preroll-aac-he-implicit-mov-declared b/tests/ref/fate/preroll-aac-he-implicit-mov-declared
index 4428dd300e..1a9b5fc781 100644
--- a/tests/ref/fate/preroll-aac-he-implicit-mov-declared
+++ b/tests/ref/fate/preroll-aac-he-implicit-mov-declared
@@ -1 +1 @@
-stream|codec_name=aac|sample_rate=44100|initial_padding=0|seek_preroll=0
+stream|codec_name=aac|sample_rate=44100|initial_padding=0|seek_preroll=4096
diff --git a/tests/ref/fate/preroll-aac-mkv-declared b/tests/ref/fate/preroll-aac-mkv-declared
index ff5c5aab2a..59696b7b45 100644
--- a/tests/ref/fate/preroll-aac-mkv-declared
+++ b/tests/ref/fate/preroll-aac-mkv-declared
@@ -1 +1 @@
-stream|codec_name=aac|sample_rate=48000|initial_padding=0|seek_preroll=0
+stream|codec_name=aac|sample_rate=48000|initial_padding=0|seek_preroll=1024
diff --git a/tests/ref/fate/preroll-aac-mov-declared b/tests/ref/fate/preroll-aac-mov-declared
index 373bee20c9..8e6ffc6576 100644
--- a/tests/ref/fate/preroll-aac-mov-declared
+++ b/tests/ref/fate/preroll-aac-mov-declared
@@ -1 +1 @@
-stream|codec_name=aac|sample_rate=44100|initial_padding=2112|seek_preroll=0
+stream|codec_name=aac|sample_rate=44100|initial_padding=2112|seek_preroll=1024
diff --git a/tests/ref/lavf-fate/adts b/tests/ref/lavf-fate/adts
index e32a375bbc..56bf8c9241 100644
--- a/tests/ref/lavf-fate/adts
+++ b/tests/ref/lavf-fate/adts
@@ -16,7 +16,7 @@ channels=8
 channel_layout=5.1.2(back)
 bits_per_sample=0
 initial_padding=0
-seek_preroll=0
+seek_preroll=1024
 id=N/A
 r_frame_rate=0/0
 avg_frame_rate=0/0