From b7c37b84e4f361bd86964209a0c6638b5e5bc664 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 15 Feb 2026 15:22:11 -0600 Subject: [PATCH] - Fix FFmpegReader first-frame black regression by anchoring PTS offset to video start (and correcting video offset to use packet PTS), so AAC preroll/skip-samples no longer shifts frame 1. - Add examples/test_aac_preroll.mp4 fixture (small, deterministic AAC-preroll sample) for reproducing and testing the regression. --- examples/test_aac_preroll.mp4 | Bin 0 -> 18106 bytes src/FFmpegReader.cpp | 27 ++++++++++++--------------- tests/FFmpegReader.cpp | 2 +- 3 files changed, 13 insertions(+), 16 deletions(-) create mode 100644 examples/test_aac_preroll.mp4 diff --git a/examples/test_aac_preroll.mp4 b/examples/test_aac_preroll.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..d46ffb8882dfa071bd93eb236211cdbcd4516a2c GIT binary patch literal 18106 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 MUV0t8BQN!T0EiYz#sB~S literal 0 HcmV?d00001 diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 18a9084c1..2ffa75c01 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -2263,7 +2263,7 @@ void FFmpegReader::UpdatePTSOffset() { // Video packet if (!has_video_pts && packet->stream_index == videoStream) { // Get the video packet start time (in seconds) - video_pts_offset_seconds = 0.0 - (video_pts * info.video_timebase.ToDouble()); + video_pts_offset_seconds = 0.0 - (pts * info.video_timebase.ToDouble()); // Is timestamp close to zero (within X seconds) // Ignore wildly invalid timestamps (i.e. -234923423423) @@ -2283,20 +2283,17 @@ void FFmpegReader::UpdatePTSOffset() { } } - // Do we have all valid timestamps to determine PTS offset? - if (has_video_pts && has_audio_pts) { - // Set PTS Offset to the smallest offset - // [ video timestamp ] - // [ audio timestamp ] - // - // ** SHIFT TIMESTAMPS TO ZERO ** - // - //[ video timestamp ] - // [ audio timestamp ] - // - // Since all offsets are negative at this point, we want the max value, which - // represents the closest to zero - pts_offset_seconds = std::max(video_pts_offset_seconds, audio_pts_offset_seconds); + // Choose timestamp origin: + // - If video exists, anchor timeline frame mapping to video start. + // This avoids AAC priming / audio preroll shifting video frame 1 to frame 2. + // - If no video exists (audio-only readers), use audio start. + if (info.has_video && has_video_pts) { + pts_offset_seconds = video_pts_offset_seconds; + } else if (!info.has_video && has_audio_pts) { + pts_offset_seconds = audio_pts_offset_seconds; + } else if (has_video_pts && has_audio_pts) { + // Fallback when stream flags are unusual but both timestamps exist. + pts_offset_seconds = video_pts_offset_seconds; } } diff --git a/tests/FFmpegReader.cpp b/tests/FFmpegReader.cpp index 1b1ff2526..0542df6b8 100644 --- a/tests/FFmpegReader.cpp +++ b/tests/FFmpegReader.cpp @@ -476,4 +476,4 @@ TEST_CASE( "Decoding AV1 Video", "[libopenshot][ffmpegreader]" ) } catch (const InvalidFile & e) { // Ignore older FFmpeg versions which don't support AV1 } -} \ No newline at end of file +}