diff --git a/frigate/detectors/plugins/axelera.py b/frigate/detectors/plugins/axelera.py index d83a8629e5..2c6d981ede 100644 --- a/frigate/detectors/plugins/axelera.py +++ b/frigate/detectors/plugins/axelera.py @@ -198,7 +198,8 @@ class _AxeleraInference: self._frame_q: queue.Queue = queue.Queue(maxsize=64) self._cond = threading.Condition() # request ids a caller is still waiting on (guarded by _cond); the pump - # only delivers results whose decoded token is in this set + # only delivers results whose decoded token is in this set. The feeder + # also uses it as the "real frame outstanding" signal. self._waiting: set = set() self._results: dict[int, np.ndarray] = {} self._next_id = 0 @@ -241,9 +242,17 @@ 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. + # A real frame may be outstanding: do NOT inject a keep-alive in + # that window, otherwise the zeros queue in the SDK pipeline + # ahead of the pending frame and delay its result by one slot + # per keep-alive (observed: 3 s real-frame latency under 1 s + # keep-alive spacing vs 14 ms for back-to-back real frames). + with self._cond: + if self._waiting: + continue + # truly idle: keep the SDK stream fed so its inference timeout + # never kills it during quiet periods. The None id means this + # frame's result is dropped by the pump. frame_id, frame = None, np.zeros( (self._height, self._width, 3), np.uint8 )