You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: lazy-start picamera2 encoders to save idle CPU
Previously, csi.py started both the MJPEG encoder and (when WebRTC was
enabled) the H264 encoder unconditionally at server startup, regardless
of whether any client was connected. Both encoders ran continuously,
burning CPU on every frame even with zero consumers.
This change introduces LazyEncoder, a small ref-counted wrapper around
picamera2's start_encoder/stop_encoder. Encoders are now started on the
first consumer (MJPEG /stream or /snapshot, WebRTC WHEP POST) and
stopped when the last consumer disconnects.
Measured CPU impact on a Raspberry Pi 4B with a Camera Module 3
(hardware encoders enabled, sampled with `top`, single python process,
percentages are of one core; the kernel reports values >100% when the
process spans multiple cores):
Default config (640x480 @ 15 FPS):
Idle (no clients): ~13-15% -> ~5%
Live MJPEG / WebRTC client: unchanged
Heavier config (1920x1080 @ 30 FPS):
Idle (no clients): ~95-100% -> ~15%
Live WebRTC only: ~120-135% -> ~60%
Live MJPEG only: ~95-104% -> ~50-60%
The biggest wins come from two effects: idle is no longer paying for
either encoder, and a live WebRTC-only client no longer pays for the
unused MJPEG encoder running in parallel (and vice versa).
The remaining idle CPU is the picamera2 capture loop itself, which
still runs continuously; pausing the camera entirely is left for a
follow-up commit.
Implementation notes:
- LazyEncoder.acquire/release are protected by a threading.Lock; a
failed start_encoder rolls back the refcount so callers can retry.
- WebRTC release is wired to RTCPeerConnection 'closed' state with an
idempotent guard, plus a try/except around setup so a failure during
SDP exchange or ICE gathering does not leak an encoder.
- USB camera path is unaffected: it never used encoders, and the new
code uses getattr(handler, ..., None) so missing attributes fall
back to no-op.
All 129 existing unit tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
0 commit comments