controller: persistent ffmpeg snapshot keeper — /snapshot latency 5s → 4ms
SnapshotKeeper class — single long-running ffmpeg subprocess на каждый instance, читает output_rtsp_url непрерывно и dumps latest frame в файл (/tmp/snapshot-keeper/<instance>.png) каждые 500 ms через ffmpeg fps=2 -update 1. /snapshot endpoint просто serves файл — disk read ~4 ms (было ~5 sec на cold ffmpeg start + RTSP negotiate + keyframe wait). Auto-restart с exponential backoff при exit (RTSP source перезапустился, network glitch). Cold ffmpeg fallback остаётся в endpoint — если keeper ещё не успел dump первый PNG (первые ~1-2 sec после controller start). UI: snapshot poll interval 700 → 250 ms (4 fps preview, было ~1.4 fps). Keeper dump rate сейчас 2 fps — practical limit. При желании поднять до 4 fps — fps=4 в snapshot_keeper.py (~5% доп CPU на ffmpeg PNG encode). Применение: основной user-facing path = visual overlay editor http://controller:8083/. Раньше polling 700 ms показывал тот же frame несколько раз пока ffmpeg запускался. Сейчас preview почти real-time, drag-and-drop reference точный. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,7 @@ class AudioOutputReq(BaseModel):
|
||||
def create_app(
|
||||
cfg: Config, state: ControllerState, dispatcher: CommandDispatcher,
|
||||
snapshot_history: SnapshotHistory | None = None,
|
||||
snapshot_keeper=None,
|
||||
frigate_bridge=None,
|
||||
) -> FastAPI:
|
||||
app = FastAPI(
|
||||
@@ -221,11 +222,23 @@ def create_app(
|
||||
},
|
||||
)
|
||||
async def snapshot(instance: str) -> Response:
|
||||
"""Capture single frame от output_rtsp_url. Returns PNG bytes (image/png)."""
|
||||
"""Capture single frame от output_rtsp_url. Returns PNG bytes (image/png).
|
||||
|
||||
Fast path: persistent ffmpeg-keeper subprocess дампит latest frame в
|
||||
файл каждые 500 ms — endpoint просто читает (latency ~10 ms). Cold path:
|
||||
fork ffmpeg на каждый request (~5 sec) — fallback если keeper ещё не
|
||||
создал файл (первые 1-2 sec после controller start) или у инстанса
|
||||
нет output_rtsp_url.
|
||||
"""
|
||||
inst = _check_instance(instance)
|
||||
if not inst.output_rtsp_url:
|
||||
raise HTTPException(400, f"instance '{instance}' has no output_rtsp_url configured")
|
||||
|
||||
if snapshot_keeper:
|
||||
path = snapshot_keeper.path(instance)
|
||||
if path:
|
||||
return FileResponse(path, media_type="image/png")
|
||||
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"ffmpeg", "-hide_banner", "-loglevel", "error",
|
||||
"-rtsp_transport", "tcp",
|
||||
|
||||
Reference in New Issue
Block a user