| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
The snapshot URL requested cache=30s, which makes go2rtc hand back byte-identical JPEG data for up to 30 seconds. go2rtc's cache is an unconditional TTL cache checked before the stream is touched at all (internal/mjpeg/mjpeg.go): on a hit it writes the stored payload and returns, so a healthy, actively producing stream is served a stale frame exactly as a stalled one is. The comment here claimed the cache only came into play when the producer was unavailable, which is not what go2rtc implements. API detection calls detect_objects_api_snapshot() ahead of any frame decode, so every detection ran against that cached frame. A stream at detection_interval=1 therefore re-ran detection once a second on one frozen image, and an object entering the scene stayed invisible until the cache expired - up to 30s later. Observed on a live 1 Hz stream as 28 consecutive person detections carrying a single distinct confidence value and a single distinct bounding box. Measured against a running instance: six requests with cache=30s interleaved with six without, over the same 12s window, while the stream was demonstrably healthy. The cached requests returned 1 distinct frame (~0.001s each), the uncached ones 6 distinct frames (~1s each). 1s preserves the reconnect tolerance the parameter was added for while matching the shortest interval lightNVR can express, since both detection gates compare whole seconds.
|
Confirmed on bench cam running light-object-detect with tiled detection (openvino backend igpu execution) |
Sorry, something went wrong.
… cost The rewritten comment still opened with the original framing -- that cache= exists so an unavailable producer does not stall the caller. That is the behaviour the old comment claimed, not the behaviour go2rtc implements: the TTL is checked unconditionally, ahead of any stream access, so stream health never enters into it. Also document the cost this shifts onto the caller. On a cache miss go2rtc waits for the next keyframe, so the fetch takes up to one GOP (~1s measured on a 1s GOP) against ~1ms for a hit, and that wait happens inline on the UDT main loop between av_read_frame() calls.
There was a problem hiding this comment.
This PR updates the go2rtc snapshot request behavior to reduce stale-frame reuse so API-based detection can operate on fresher frames when snapshots are polled frequently.
Changes:
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Sorry, something went wrong.
| // detection freshness for less stalling. 1s bounds staleness at roughly one detection | ||
| // interval, which is as fine as lightNVR can express since both detection gates | ||
| // compare whole seconds. | ||
| snprintf(url, sizeof(url), "http://localhost:1984" GO2RTC_BASE_PATH "/api/frame.jpeg?src=%s&cache=1s", encoded_name); |
| // On a miss go2rtc waits for the stream's *next* keyframe, so the fetch costs up to | ||
| // one GOP (measured ~1s on a 1s GOP; a cache hit is ~1ms). That cost lands on this | ||
| // thread: the UDT main loop calls run_detection_on_frame() between av_read_frame() | ||
| // calls, so a slow fetch stalls packet reading for the stream. A larger value trades | ||
| // detection freshness for less stalling. 1s bounds staleness at roughly one detection | ||
| // interval, which is as fine as lightNVR can express since both detection gates | ||
| // compare whole seconds. |
| Back | FazBrowse Home | New Git URL |
go2rtc_snapshot.c requested snapshots with cache=30s. That parameter does not do what the comment beside it claimed, and the consequence is that API detection runs on frames up to 30 seconds old.
What cache= actually does
The comment asserted the cache only comes into play when the producer is unavailable:
go2rtc implements it as an unconditional TTL cache, checked before the stream is touched at all — handlerKeyframe in internal/mjpeg/mjpeg.go:
There is no stream-health condition anywhere on that path. A healthy, actively producing stream is served a stale frame exactly as a stalled one is.
Impact
run_detection_on_frame() calls detect_objects_api_snapshot() ahead of any frame decode, so with go2rtc enabled every API detection uses this cached JPEG. A stream at detection_interval=1 therefore re-runs detection once a second against one frozen image, and an object entering the scene stays invisible until the cache expires — up to 30 s later.
Observed on a live 1 Hz 2560×1440 stream: 28 consecutive person rows carrying a single distinct confidence value and a single distinct bounding box.
In effect the 30 s cache silently defeats any detection_interval shorter than 30 s.
Measurement
Six requests with cache=30s interleaved with six without, over the same 12 s window, with the stream demonstrably healthy throughout — the uncached requests interleaved between the cached ones returned a fresh frame every time:
With cache=1s, polling at 1 Hz for 10 requests: 10 of 10 distinct frames. The staleness is gone.
Side effects of cache=1s
This is not free, and the cost is worth stating plainly.
Fetch latency rises. On a miss go2rtc waits for the stream's next keyframe, so a fetch costs up to one GOP. Measured at 1 Hz polling against a 1 s GOP, latency alternates cleanly between a hit and a miss:
That wait is paid on the UDT main loop. The loop is av_read_frame() (line 1726) → process_packet() (line 1746) → run_detection_on_frame(), single-threaded, so a slow snapshot fetch stalls packet reading for that stream. This repo already treats that as a hazard — the ONVIF path was moved to its own thread for exactly this reason, and the comment introducing it notes that "SOD and API detection paths are completely unaffected", i.e. still inline.
So on a 1 s GOP at detection_interval=1, the reader goes from stalling ~170 ms per cycle (the detect API POST alone) to roughly 170 ms + up to 1 s on the cycles that miss. I have not measured the downstream effect on recording continuity or on the effective detection rate; both could plausibly degrade, and the rate is partly self-limiting since a longer cycle means fewer detections. Flagging it rather than claiming it is benign.
What is not affected:
Alternatives, if the stall is judged too expensive
Both are larger changes than this PR and I have not implemented either:
The value could also be made configurable rather than hardcoded, so deployments can pick their own point on the freshness/stall trade.
The change
cache=30s → cache=1s, plus the comment rewritten to describe the actual TTL behaviour and to record the keyframe-wait cost it shifts onto the caller.
1 s bounds staleness at roughly one detection interval, which is as fine as lightNVR can express — both detection gates compare whole seconds in unified_detection_thread.c, so detection_interval cannot go below 1.
Draft because the fix is verified against go2rtc's source and by direct measurement of the live endpoint, but I have not yet run a rebuilt image end to end, and the packet-reader stall above deserves a maintainer's judgement.