fix(axelera): stop keep-alive frames after pipeline init

Misattribution reproduced at night: when Frigate stops sending frames
during quiet periods the queue empties and keep-alive frames are emitted;
a pending real-frame result then races the keep-alive's (empty) result,
which steals the real id (frame/detections shown under the WRONG camera).

Fix: synthesize keep-alive frames ONLY until the first stream result
(_primed), then never again - no unregistered result can exist in the
pipeline afterwards. Queue-empty after init just waits; under Frigate's
continuous feed this branch is only hit during true quiet periods, where
there is no pending request to misattribute anyway.
This commit is contained in:
patrykorwat 2026-08-21 06:08:17 +00:00
parent 9d8a1a10a4
commit 6fd5b136b6

View File

@ -153,6 +153,11 @@ class _AxeleraInference:
# flight the FIFO pop can misattribute a result to the wrong camera.
# With at most one frame in flight the result can only belong to it.
self._run_lock = threading.Lock()
# Set on the FIRST stream result. Until then the feed may synthesize
# keep-alive frames so the SDK pipeline finishes initializing; after
# this NO synthetic frames are ever emitted, so an unregistered result
# can never arrive while a real request id is pending.
self._primed = threading.Event()
if stream_factory is None:
# lazy import: never import the SDK unless the detector is used
@ -187,9 +192,16 @@ class _AxeleraInference:
try:
frame_id, frame = self._frame_q.get(timeout=1.0)
except queue.Empty:
# keep-alive: no request pending, keep the SDK stream fed so the
# inference timeout never kills it during quiet periods. The
# None id means this frame's result is dropped by the pump.
if self._primed.is_set():
# After init NEVER synthesize frames: a keep-alive result
# can arrive while a real id is pending and steal it
# (misattribution across cameras, observed at night when
# Frigate stops sending frames during quiet periods).
time.sleep(0.2)
continue
# During pipeline init the SDK needs a sustained source; these
# zero frames prime it and are drained before the first real
# request is registered.
frame_id, frame = None, np.zeros(
(self._height, self._width, 3), np.uint8
)
@ -255,8 +267,11 @@ class _AxeleraInference:
logger.info("axelera: inference stream started")
try:
for frame_result in self._stream:
if not self._primed.is_set():
self._primed.set()
logger.info("axelera: pipeline initialized (first result)")
out = _pad_rows(self._detection_rows(frame_result))
if self._pending_ids:
if self._primed.is_set() and self._pending_ids:
# FIFO: the stream returns results in submission order, so
# the oldest pending id belongs to this result.
frame_id = self._pending_ids.popleft()