fix/media-looping-boundary#269
Merged
Merged
Conversation
Track LastKeyframeRawPTS and force a fragment flush when two consecutive keyframes on the video track arrive unexpectedly close (<500ms). This detects upstream loop/restart discontinuities (e.g. ffmpeg stream_loop seams) where a fresh IDR would otherwise become a mid-fragment sync sample and cause MSE players to reject the fragment. Emits a warning when triggered and updates LastKeyframeRawPTS for video samples. Also add a sample MP4 file to machinery/data.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the fragmented MP4 writer to detect suspected upstream loop/restart seams by tracking the previous video keyframe PTS and forcing an early fragment flush when two keyframes arrive unusually close together. It affects the recording path that packages RTSP video into browser-playable fragmented MP4 output.
Changes:
- add
LastKeyframeRawPTSto MP4 state - force a fragment flush on video keyframes that arrive within 500ms of the previous keyframe
- emit a warning when that discontinuity heuristic triggers
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+311
to
+331
| // Detect upstream source-loop / restart discontinuity. When an MP4 is | ||
| // looped through virtual-rtsp (ffmpeg `-stream_loop -1 -re`) the loop | ||
| // seam emits a fresh IDR much sooner than a normal GOP would. PTS keeps | ||
| // growing monotonically, so the timing-only `elapsed` check above does | ||
| // not catch it and the seam IDR ends up as a mid-fragment sync sample. | ||
| // MSE-based players (Video.js / Chromium / Firefox) reject the resulting | ||
| // fragment with a "media corruption" error because the inner IDR resets | ||
| // frame_num/POC inside what they expect to be a single GOP. Force a | ||
| // fragment boundary whenever two consecutive keyframes arrive much | ||
| // closer than a normal GOP (here: < 500 ms apart). This isolates the | ||
| // seam IDR into its own fragment so each fragment stays a clean GOP. | ||
| if !shouldFlush && trackID == uint32(mp4.VideoTrack) && mp4.Start && | ||
| mp4.LastKeyframeRawPTS > 0 && pts > mp4.LastKeyframeRawPTS && | ||
| pts-mp4.LastKeyframeRawPTS < 500 { | ||
| log.Log.Warning(fmt.Sprintf("mp4.AddSampleToTrack(): forcing fragment flush at unexpectedly close keyframe (gap=%d ms, fragment elapsed=%d ms) - likely upstream loop/restart discontinuity", pts-mp4.LastKeyframeRawPTS, elapsed)) | ||
| shouldFlush = true | ||
| } | ||
| if trackID == uint32(mp4.VideoTrack) { | ||
| mp4.LastKeyframeRawPTS = pts | ||
| } | ||
|
|
Comment on lines
+318
to
+325
| // frame_num/POC inside what they expect to be a single GOP. Force a | ||
| // fragment boundary whenever two consecutive keyframes arrive much | ||
| // closer than a normal GOP (here: < 500 ms apart). This isolates the | ||
| // seam IDR into its own fragment so each fragment stays a clean GOP. | ||
| if !shouldFlush && trackID == uint32(mp4.VideoTrack) && mp4.Start && | ||
| mp4.LastKeyframeRawPTS > 0 && pts > mp4.LastKeyframeRawPTS && | ||
| pts-mp4.LastKeyframeRawPTS < 500 { | ||
| log.Log.Warning(fmt.Sprintf("mp4.AddSampleToTrack(): forcing fragment flush at unexpectedly close keyframe (gap=%d ms, fragment elapsed=%d ms) - likely upstream loop/restart discontinuity", pts-mp4.LastKeyframeRawPTS, elapsed)) |
Comment on lines
+322
to
+326
| if !shouldFlush && trackID == uint32(mp4.VideoTrack) && mp4.Start && | ||
| mp4.LastKeyframeRawPTS > 0 && pts > mp4.LastKeyframeRawPTS && | ||
| pts-mp4.LastKeyframeRawPTS < 500 { | ||
| log.Log.Warning(fmt.Sprintf("mp4.AddSampleToTrack(): forcing fragment flush at unexpectedly close keyframe (gap=%d ms, fragment elapsed=%d ms) - likely upstream loop/restart discontinuity", pts-mp4.LastKeyframeRawPTS, elapsed)) | ||
| shouldFlush = true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Track LastKeyframeRawPTS and force a fragment flush when two consecutive keyframes on the video track arrive unexpectedly close (<500ms). This detects upstream loop/restart discontinuities (e.g. ffmpeg stream_loop seams) where a fresh IDR would otherwise become a mid-fragment sync sample and cause MSE players to reject the fragment. Emits a warning when triggered and updates LastKeyframeRawPTS for video samples. Also add a sample MP4 file to machinery/data.