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]
