-
-
Notifications
You must be signed in to change notification settings - Fork 56.4k
Fix frame seeking with negative DTS values in FFMPEG backend #27878
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 4.x
Are you sure you want to change the base?
Conversation
Add test case to verify behavior when seeking in videos that contain negative DTS values in initial fames. Is accompanied by test media "negdts_h264.mp4" in a opencv_extra commit
Implement timestamp normalization to handle videos with negative decoding time stamp values, which are produced by modern encoders like DaVinci Resolve. Calculates a constant timestamp offset once on the first decoded frame, then applies it to all timestamps to shift them to start from 0. The approach reflects what FFMPEG -avoid_negative_ts_make_zero might do. Offset is determined by finding the min timestamp across the container start time, stream start time, or first observed frame timestamp. If the minimum is negative the offset is set to negate it, otherwise is zero (no normalization).
…FFmpeg wrapper update
…n in raw mode The offset needs to be computed for timestamps which are positive as well - this used to be applied in dts_to_sec but now that we normalize in grabFrame it makes sense to do it here. Also changed it so rawMode wont apply the normalization as this doesn't make much sense
cba0185 to
5a48502
Compare
revert change which didn't allow normalization to happen in raw mode, and instead correctly apply the normalization to the dts as well as pts, which should solve problems where the dts < pts because of a dts delay
opencv-alalek
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for contribution!
Perhaps ts_offset_avtb should be also handled in the "seek" operation (for reverting timestamp changes back). Please note that this operation is not accurate on non-key frames (see #9053). Lets ensure that it properly works at least with zero input value (request to seek to start)
|
|
||
| // Compute offset to shift timestamps to zero | ||
| if (min_start_avtb != INT64_MAX) { | ||
| ts_offset_avtb = -min_start_avtb; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (min_start_avtb < 0) is preferable to keep original timestamp on "non-issue" videos with all non-negative timestamps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, thanks for taking a look at this - I'm very happy to contribute.
I agree with the above. The alternative would probably be for CvCapture_FFMPEG::dts_to_sec(int64_t dts) to have a condition which checks if ts_offset_decided is false (no normalization has happened) and take away the stream start_time like it did previously:
double CvCapture_FFMPEG::dts_to_sec(int64_t dts) const
{
const AVStream* st = ic->streams[video_stream];
int64_t ts = dts;
if (ts_offset_avtb == 0 && st->start_time != AV_NOPTS_VALUE_)
ts -= st->start_time;
return ts * r2d(st->time_base);
}
That way positive start time files will still work correctly
And I guess seek will do something like this to revert the timestamps back?
AVStream* st = ic->streams[video_stream];
int64_t time_stamp = st->start_time;
double time_base = r2d(st->time_base);
int64_t ts_norm = (int64_t)(sec / time_base + 0.5);
if (ts_offset_avtb != 0) {
// map normalized target back to original demux timeline
time_stamp += ts_norm - from_avtb(ts_offset_avtb, st->time_base);
} else {
time_stamp += ts_norm;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went ahead and made the changes - the Linux x64 build failed, but seems unrelated.
Some GStreamer test stalling
Keep original timestamp for all non-negative timestamp files by only computing and offset for a negative case + handle this situation in dts_to_sec by explicitly checking if the offset was applied. Additionally, seek operation now considers the offset so that the timestamp is reverted
|
|
||
| if (!cap.isOpened()) | ||
| throw SkipTestException("Video stream is not supported"); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add .grab() + cap.get(CAP_PROP_POS_MSEC) + non-negative check
There is Windows build configuration which doesn't update FFmpeg plugin during PR validation build and this test doesn't fail (passes without fix above).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added those checks into the test now.
Note, this test is within a #ifndef _WIN32 check because I read about some prebuilt binaries not working here.
|
Main problem of this approach that we can't use timestamps received from Also need to check GStreamer backend behavior (users expect the same result from the same videos) |
This is technically true I guess, but the timestamps for the problem files in question weren't correct to begin with. Compared to now: (not using grab in these btw just seek) If you use ffprobe with -frame=best_effort_timestamp_time you also get those results. Behavior for normal files is already the same since we only do this for the negative case. |
Merge with extra: opencv/opencv_extra#1279
Fix for issue #27819.
Accompanied by PR on opencv_extra:
The FFmpeg backend fails to correctly seek in H.264 videos that contain negative DTS values in their initial frames. This is a valid encoding practice used by modern video encoders (such as DaVinci Resolve's current export) where B-frame reordering causes the first few frames to have negative DTS values.
When picture_pts is unavailable (AV_NOPTS_VALUE), the code falls back to using pkt_dts:
picture_pts = packet_raw.pts != AV_NOPTS_VALUE_ ? packet_raw.pts : packet_raw.dts;If this DTS value is negative (which is legal per H.264 spec), it propagates through the frame number calculation:
frame_number = dts_to_frame_number(picture_pts) - first_frame_number;This results in negative frame numbers, messing up seeking operations.
Solution implemented in this branch is a timestamp normalization similar to FFmpegs -avoid_negative_ts_make_zero flag:
start_timesubtractions since timestamps are pre-normalized.This also includes a new test
videoio_ffmpeg.seek_with_negative_dtsThis test verifies that seeking behavior performs as expected on a file which has negative DTS values in the first frames.
A PR on opencv_extra accompanies this one with that testing file: opencv/opencv_extra#1279
Pull Request Readiness Checklist
See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request
Patch to opencv_extra has the same branch name.