From d3f53e4b6bc73fa8646404168a67a20a9cef057f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Tue, 12 May 2026 13:56:07 +0000 Subject: [PATCH] Adjust MinNormalGOPMs threshold to prevent false positives on loop seams --- machinery/src/video/mp4.go | 25 ++++++++++++++++-------- machinery/src/video/mp4_loopseam_test.go | 7 +++++-- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/machinery/src/video/mp4.go b/machinery/src/video/mp4.go index faaa783..7dee969 100644 --- a/machinery/src/video/mp4.go +++ b/machinery/src/video/mp4.go @@ -32,14 +32,23 @@ const MacEpochOffset uint64 = 2082844800 // resulting in ~3 second fragments (assuming a typical GOP interval). const FragmentDurationMs = 3000 -// MinNormalGOPMs is the minimum spacing we expect between two consecutive -// IDRs of a healthy source (typical encoders produce IDRs every 1000ms or -// more). When two keyframes arrive closer than this, we treat the second one -// as an upstream restart/loop-seam and force a fresh fragment so the seam -// IDR cannot end up as a mid-fragment sync sample. The check only runs when -// the current fragment has not yet reached FragmentDurationMs, so it never -// fires during normal multi-GOP fragments at intended GOP boundaries. -const MinNormalGOPMs = 950 +// MinNormalGOPMs is the maximum spacing between two consecutive IDRs that +// we still consider an anomalous "loop/restart seam". When two keyframes +// arrive closer than this, we treat the second one as an upstream +// restart/loop-seam and force a fresh fragment so the seam IDR cannot end +// up as a mid-fragment sync sample. The check only runs when the current +// fragment has not yet reached FragmentDurationMs. +// +// This must be set well below the smallest plausible *legitimate* GOP +// length. Typical IP cameras use GOP intervals of 1000-2000 ms, and the +// arrival timing of consecutive IDRs can jitter by a few hundred ms due to +// network/RTSP buffering. A threshold close to 1 s (e.g. 950) caused +// false positives on cameras with ~1 s GOPs (warnings like +// "gap=800 ms / 300 ms / 200 ms" while the stream itself was healthy). +// 400 ms is comfortably below any realistic GOP yet still catches the +// virtual-rtsp / ffmpeg loop-seam pattern (seam IDRs typically arrive +// 100-200 ms after the prior IDR). +const MinNormalGOPMs = 400 type MP4 struct { // FileName is the name of the file diff --git a/machinery/src/video/mp4_loopseam_test.go b/machinery/src/video/mp4_loopseam_test.go index 6c24812..2a69444 100644 --- a/machinery/src/video/mp4_loopseam_test.go +++ b/machinery/src/video/mp4_loopseam_test.go @@ -51,8 +51,11 @@ func TestMP4LoopSeamIsolation(t *testing.T) { // 17 seconds of normal content (last "good" IDR at sec 17). emit(17*30, 30) - // Seam: IDR arrives ~867ms after previous (vs normal 1000ms). - pts -= 100 + // Seam: IDR arrives ~150ms after previous (vs normal ~1000ms). + // This matches the realistic virtual-rtsp / ffmpeg `-stream_loop` + // loop boundary, where the new clip's first IDR is emitted shortly + // after the previous clip's final IDR. + pts -= 820 emit(13*30, 30) mp4Video.Close(&models.Config{Signing: &models.Signing{PrivateKey: ""}})