PR #25837 opened by Romain Beauxis (toots)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25837
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25837.patch

Since 843e83ed2d, a seek decided on a video stream also moves back by the audio 
pre-roll, so it lands on the previous keyframe. A concat `inpoint` on a 
keyframe then keeps the GOP before it in stream copy. The pre-roll now applies 
only when the seek is decided on an audio stream.

The first commit adds a test with the current output, and the fix updates it. 
`release/9.1` needs a backport.

Fixes #25672.


>From a9c13846ca83ed25752f375203a7a486d41a8da7 Mon Sep 17 00:00:00 2001
From: Romain Beauxis <[email protected]>
Date: Sat, 7 Nov 2026 09:32:45 -0600
Subject: [PATCH 1/2] tests/fate/preroll: check a concat inpoint on a video
 keyframe

An inpoint on a keyframe of a Matroska file with AAC must start the cut on that 
keyframe.

The reference shows the current behaviour, which starts on the keyframe before.
---
 tests/fate/preroll.mak                    | 4 ++++
 tests/ref/fate/preroll-aac-concat-inpoint | 2 ++
 2 files changed, 6 insertions(+)
 create mode 100644 tests/ref/fate/preroll-aac-concat-inpoint

diff --git a/tests/fate/preroll.mak b/tests/fate/preroll.mak
index 243b9d07317..4a040b4e02d 100644
--- a/tests/fate/preroll.mak
+++ b/tests/fate/preroll.mak
@@ -66,6 +66,10 @@ FATE_PREROLL_SEEK-$(call ALLYES, CONCAT_DEMUXER MOV_DEMUXER 
AAC_DECODER FILE_PRO
 fate-preroll-aac-concat-seek: 
tests/data/samples/gapless/102400samples_qt-lc-aac.m4a
 fate-preroll-aac-concat-seek: CMD = seek_concat 
$(SRC_PATH)/tests/preroll.ffconcat 
../samples/gapless_102400samples_qt-lc-aac.m4a -seekback 530000 -frames 3 
-duration 3
 
+# An inpoint on a video keyframe: the cut starts at that keyframe.
+FATE_PREROLL_FFPROBE-$(call ALLYES, CONCAT_DEMUXER MATROSKA_DEMUXER 
H264_DECODER AAC_DECODER FILE_PROTOCOL PIPE_PROTOCOL) += 
fate-preroll-aac-concat-inpoint
+fate-preroll-aac-concat-inpoint: CMD = printf "ffconcat version 1.0\nfile 
file:%s\ninpoint 3\n" $(TARGET_SAMPLES)/mkv/test7_cut.mkv | run 
ffprobe$(PROGSSUF)$(EXESUF) -protocol_whitelist pipe,file -safe 0 -f concat -i 
pipe: -select_streams v -read_intervals %+\#2 -show_entries packet=pts,flags 
-print_format compact
+
 # A seek close to the start of a stream whose start time is unknown.
 FATE_PREROLL_SEEK-$(call DEMDEC, AAC, AAC) += fate-preroll-aac-adts-seek-start
 fate-preroll-aac-adts-seek-start: CMD = run libavformat/tests/seek$(EXESUF) 
$(TARGET_SAMPLES)/aac/id3v2.aac -seekback 10000 -frames 2 -duration 3
diff --git a/tests/ref/fate/preroll-aac-concat-inpoint 
b/tests/ref/fate/preroll-aac-concat-inpoint
new file mode 100644
index 00000000000..6cd382212e7
--- /dev/null
+++ b/tests/ref/fate/preroll-aac-concat-inpoint
@@ -0,0 +1,2 @@
+packet|pts=-1000|flags=K__
+packet|pts=-916|flags=___
-- 
2.54.0


>From f688aebdfa1f956b686d388f104f79b213629814 Mon Sep 17 00:00:00 2001
From: Romain Beauxis <[email protected]>
Date: Sat, 7 Nov 2026 09:32:45 -0600
Subject: [PATCH 2/2] avformat/seek: apply the audio pre-roll only to seeks on
 an audio stream

A seek decided on a video stream lands on a keyframe, so moving its target back 
by the audio pre-roll lands a whole GOP earlier.

This broke stream copy from a concat inpoint, as used by yt-dlp to cut 
SponsorBlock segments (#25672).
---
 libavformat/avformat.h                    | 7 ++++---
 libavformat/seek.c                        | 9 +++++++--
 tests/fate/preroll.mak                    | 3 +--
 tests/ref/fate/preroll-aac-concat-inpoint | 4 ++--
 tests/ref/fate/preroll-aac-mov-seek-video | 8 ++++----
 5 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/libavformat/avformat.h b/libavformat/avformat.h
index 679d15676c5..3557b4ebc00 100644
--- a/libavformat/avformat.h
+++ b/libavformat/avformat.h
@@ -2566,9 +2566,10 @@ int av_seek_frame(AVFormatContext *s, int stream_index, 
int64_t timestamp,
  *
  * A seek by timestamp moves ts back by the largest
  * AVCodecParameters.seek_preroll of the audio streams that are not discarded,
- * whatever stream_index is, so that the decoders get their declared pre-roll
- * before ts. The target stays within min_ts and after the start of the
- * stream, so less room may be left. max_ts moves back as much, unless it is
+ * so that the decoders get their declared pre-roll before ts. This only
+ * applies when stream_index, or the default stream if it is -1, is audio.
+ * The target stays within min_ts and after the start of the stream, so less
+ * room may be left. max_ts moves back as much, unless it is
  * INT64_MAX. If that seek fails, the seek is done to ts. This also applies to
  * av_seek_frame() with a demuxer that only implements this function.
  *
diff --git a/libavformat/seek.c b/libavformat/seek.c
index 4a09d013353..f8a04c9badb 100644
--- a/libavformat/seek.c
+++ b/libavformat/seek.c
@@ -669,14 +669,19 @@ int av_seek_frame(AVFormatContext *s, int stream_index,
  * units of the seek: the time base of the stream, or AV_TIME_BASE when no
  * stream is given. Audio decoders need the frames before the target to
  * produce reliable output at it, and declare how many samples in
- * AVCodecParameters.seek_preroll.
+ * AVCodecParameters.seek_preroll. None when the seek is decided on a stream
+ * that is not audio, as its keyframes set where the seek lands.
  */
-static int64_t seek_preroll(const AVFormatContext *s, int stream_index)
+static int64_t seek_preroll(AVFormatContext *s, int stream_index)
 {
+    int ref = stream_index >= 0 ? stream_index : 
av_find_default_stream_index(s);
     AVRational unit = stream_index >= 0 ? s->streams[stream_index]->time_base
                                         : AV_TIME_BASE_Q;
     int64_t preroll = 0;
 
+    if (ref < 0 || s->streams[ref]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO)
+        return 0;
+
     for (unsigned i = 0; i < s->nb_streams; i++) {
         const AVStream *st = s->streams[i];
         const AVCodecParameters *par = st->codecpar;
diff --git a/tests/fate/preroll.mak b/tests/fate/preroll.mak
index 4a040b4e02d..238da2b7075 100644
--- a/tests/fate/preroll.mak
+++ b/tests/fate/preroll.mak
@@ -51,8 +51,7 @@ fate-preroll-aac-caf-landing-end: CMD = run 
ffprobe$(PROGSSUF)$(EXESUF) -select_
 FATE_PREROLL_FFPROBE-$(call DEMDEC, MATROSKA, OPUS) += 
fate-preroll-opus-mkv-landing-end
 fate-preroll-opus-mkv-landing-end: CMD = run ffprobe$(PROGSSUF)$(EXESUF) 
-select_streams a:0 -read_intervals 1.1%+\#2 -show_entries 
packet=pts,duration:packet_side_data=skip_samples,discard_padding -print_format 
compact $(TARGET_SAMPLES)/mkv/codec_delay_opus.mkv
 
-# A seek on the video stream, just after a keyframe: the audio pre-roll
-# still needs room.
+# A seek on the video stream, just after a keyframe, lands on that keyframe.
 FATE_PREROLL_SEEK-$(call DEMDEC, MOV, AAC H264) += 
fate-preroll-aac-mov-seek-video
 fate-preroll-aac-mov-seek-video: CMD = run libavformat/tests/seek$(EXESUF) 
$(TARGET_SAMPLES)/h264/twofields_packet.mp4 -stream_id 0 -seekback 182000 
-frames 4 -duration 3
 
diff --git a/tests/ref/fate/preroll-aac-concat-inpoint 
b/tests/ref/fate/preroll-aac-concat-inpoint
index 6cd382212e7..bd7f4c9e3dc 100644
--- a/tests/ref/fate/preroll-aac-concat-inpoint
+++ b/tests/ref/fate/preroll-aac-concat-inpoint
@@ -1,2 +1,2 @@
-packet|pts=-1000|flags=K__
-packet|pts=-916|flags=___
+packet|pts=0|flags=K__
+packet|pts=84|flags=___
diff --git a/tests/ref/fate/preroll-aac-mov-seek-video 
b/tests/ref/fate/preroll-aac-mov-seek-video
index 8964c18cc5a..10608044fac 100644
--- a/tests/ref/fate/preroll-aac-mov-seek-video
+++ b/tests/ref/fate/preroll-aac-mov-seek-video
@@ -1,7 +1,7 @@
-ret: 0         st: 1 flags:1 dts: 0.960000 pts: 0.960000 pos:1364132 size:   
341 skip:1024 discard:0
-ret: 0         st: 1 flags:1 dts: 0.981333 pts: 0.981333 pos:1385069 size:   
341
-ret: 0         st: 0 flags:1 dts: 0.967633 pts: 1.067733 pos:1385410 
size:225704
-ret: 0         st: 1 flags:1 dts: 1.002667 pts: 1.002667 pos:1611114 size:   
342
+ret: 0         st: 1 flags:1 dts: 1.962667 pts: 1.962667 pos:2810490 size:   
342 skip:1024 discard:0
+ret: 0         st: 1 flags:1 dts: 1.984000 pts: 1.984000 pos:2829340 size:   
341
+ret: 0         st: 0 flags:1 dts: 1.968633 pts: 2.068733 pos:2829681 
size:149436
+ret: 0         st: 1 flags:1 dts: 2.005333 pts: 2.005333 pos:2979117 size:   
341
 ret: 0         st:-1 flags:0  ts:-1.000000
 ret: 0         st: 0 flags:1 dts:-0.033367 pts: 0.066733 pos:  32776 
size:198404
 ret: 0         st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 231180 size:   
341
-- 
2.54.0

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

Reply via email to