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 <noreply@anthropic.com>
This commit is contained in:
Loic Goossens
2026-09-22 10:05:58 +02:00
co-authored by Claude Opus 5
parent a91ff70ceb
commit 527a6b2284
+2
View File
@@ -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);