From 527a6b22845bb2990bf3355420ab9213d103255f Mon Sep 17 00:00:00 2001 From: Loic Goossens Date: Mon, 21 Sep 2026 17:00:05 +0200 Subject: [PATCH] videoio(ffmpeg): guard seek() against AV_NOPTS_VALUE start_time CvCapture_FFMPEG::seek() seeds the seek target with the stream start_time without checking for AV_NOPTS_VALUE, while dts_to_sec() right above it does perform that check. Some containers do not let FFmpeg establish a start time, for instance Matroska files whose H.264 track is declared through the legacy VfW wrapper (CodecID V_MS/VFW/FOURCC with a BITMAPINFOHEADER) instead of V_MPEG4/ISO/AVC with an avcC record. For those, start_time is AV_NOPTS_VALUE, the computed target becomes INT64_MIN plus an offset, and av_seek_frame() with AVSEEK_FLAG_BACKWARD lands at position 0. The refinement loop below then decodes every single frame up to the requested one, so every seek degrades to a linear scan. The returned frame is still correct, which makes the failure silent and easy to miss. Such a container is not otherwise malformed: its Cues index is complete and correct, and `ffmpeg -ss` seeks it in constant time at any offset. Measured with OpenCV 4.14.0 on a 15 h H.264 recording (1628443 frames), Windows x64, stock prebuilt FFmpeg wrapper versus the same wrapper rebuilt with this patch: target before after 10 s 0.246 s 0.092 s 60 s 1.494 s 0.039 s 300 s 7.569 s 0.052 s 10000 s ~4 min 0.061 s 54000 s ~22 min 0.051 s Before the patch the cost grew strictly linearly with the target position, at roughly 25 ms per second of video, so the two longest targets were extrapolated rather than waited out. Decoded frames are unchanged for targets that both paths reach. The guard mirrors the one already present in dts_to_sec(). Co-Authored-By: Claude Opus 5 --- modules/videoio/src/cap_ffmpeg_impl.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/videoio/src/cap_ffmpeg_impl.hpp b/modules/videoio/src/cap_ffmpeg_impl.hpp index 6a8543999a..442a051e8d 100644 --- a/modules/videoio/src/cap_ffmpeg_impl.hpp +++ b/modules/videoio/src/cap_ffmpeg_impl.hpp @@ -2325,6 +2325,8 @@ void CvCapture_FFMPEG::seek(int64_t _frame_number) AVStream* st = ic->streams[video_stream]; int64_t time_stamp = st->start_time; + if (time_stamp == AV_NOPTS_VALUE_) // same guard as dts_to_sec(): without it the target is INT64_MIN + offset, + time_stamp = 0; // av_seek_frame() lands at position 0 and the loop below decodes every frame double time_base = r2d(st->time_base); int64_t ts_norm = (int64_t)(sec / time_base + 0.5);