ADR-009: Support Dual Camera Sources with a Latest-Frame Pipeline
ID: ADR-009 Status: Accepted
Context
A browser may have its own camera, but a remote administration browser cannot access the webcam attached to the Raspberry Pi. Sending/capturing faster than inference creates stale-frame latency if frames are queued. Multiple capture loops or concurrent enrollment/recognition can also contend for a single device and non-thread-safe model state.
Decision
Support two configured camera sources behind one frame contract:
browser: capture in the SPA, send only when no response is outstanding, and have the backend drain already-queued WebSocket messages before decoding the newest one;backend: oneLatestFrameCameraworker owns one V4L2VideoCapture; successful captures replace one latest slot and consumers request a sequence newer than the one just processed.
LatestFrameCamera uses a process-wide non-blocking device lease, configures a one-frame OpenCV/V4L2 buffer where supported, copies a delivered frame, and releases capture in all stop/error paths. FastAPI's shared operation lock serializes recognition and enrollment. Each response correlates box, frame_width, frame_height, frame_sequence, and (in backend mode) the exact JPEG.
This decision governs the deployed FastAPI runtime. The older maintenance CLIs under backend/faceguard/ retain direct VideoCapture calls and therefore must be run only while the service is stopped; they are not a second production capture path.
Considered alternatives
- FIFO frame queue: rejected because increasing backlog increases decision age rather than usefulness.
- One capture loop per WebSocket: rejected because a physical camera has one owner and model operations are not intended to overlap.
- Browser-only capture: rejected for remote Pi-camera use.
- Backend-only capture: rejected because browser mode is useful for local development and client cameras.
- Native libcamera adapter: not selected in the current implementation; the backend adapter requires V4L2.
Consequences
- Slow inference drops intermediate frames by design and acts on current input.
- There is one backend camera capture loop in the FastAPI runtime and bounded memory rather than a backlog.
- A busy second operation receives a bounded
operation_busyresult. - Camera cleanup is part of WebSocket
finallyhandling and application shutdown. - CI can prove software ownership/replacement/cleanup with fake captures, but camera-driver behavior and Raspberry Pi image quality still require hardware verification.
Quality requirements addressed
- QR-001, alongside ADR-003 and ADR-005.