Updating frame handling
This commit is contained in:
@@ -3,6 +3,13 @@ import logging
|
||||
import threading
|
||||
from collections.abc import Iterator
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
|
||||
PIL_AVAILABLE = True
|
||||
except ImportError:
|
||||
PIL_AVAILABLE = False
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
@@ -21,6 +28,7 @@ class Camera:
|
||||
self._frame: bytes = b""
|
||||
self._lock = threading.Lock()
|
||||
self._stop_event = threading.Event()
|
||||
self._frame_event = threading.Event()
|
||||
self.running = False
|
||||
|
||||
def start(self) -> None:
|
||||
@@ -32,12 +40,13 @@ class Camera:
|
||||
return
|
||||
|
||||
self._picam = Picamera2()
|
||||
config = self._picam.create_video_configuration(
|
||||
config = self._picam.create_still_configuration(
|
||||
main={"size": (1280, 720), "format": "RGB888"},
|
||||
)
|
||||
self._picam.configure(config)
|
||||
self._picam.start()
|
||||
self._stop_event.clear()
|
||||
self._frame_event.clear()
|
||||
self._thread = threading.Thread(target=self._capture_loop, daemon=True)
|
||||
self._thread.start()
|
||||
self.running = True
|
||||
@@ -45,22 +54,36 @@ class Camera:
|
||||
|
||||
def _capture_loop(self) -> None:
|
||||
assert self._picam is not None
|
||||
# warm up — let the sensor settle
|
||||
self._stop_event.wait(0.5)
|
||||
logger.info("Capture loop started")
|
||||
|
||||
while not self._stop_event.is_set():
|
||||
buffer = io.BytesIO()
|
||||
self._picam.capture_file(buffer, format="jpeg")
|
||||
with self._lock:
|
||||
self._frame = buffer.getvalue()
|
||||
try:
|
||||
# capture raw numpy array then encode to JPEG manually
|
||||
array = self._picam.capture_array("main")
|
||||
buffer = io.BytesIO()
|
||||
img = Image.fromarray(array)
|
||||
img.save(buffer, format="JPEG", quality=85)
|
||||
with self._lock:
|
||||
self._frame = buffer.getvalue()
|
||||
self._frame_event.set() # signal first frame is ready
|
||||
except Exception as e:
|
||||
logger.error(f"Capture error: {e}")
|
||||
self._stop_event.wait(0.1)
|
||||
|
||||
def stop(self) -> None:
|
||||
if not self.running:
|
||||
return
|
||||
self._stop_event.set()
|
||||
self._frame_event.set() # unblock any waiting frames() calls
|
||||
if self._thread:
|
||||
self._thread.join(timeout=2)
|
||||
self._thread.join(timeout=3)
|
||||
if self._picam:
|
||||
self._picam.stop()
|
||||
self._picam.close()
|
||||
self._picam = None
|
||||
self._frame = b""
|
||||
self.running = False
|
||||
logger.info("Camera stopped")
|
||||
|
||||
@@ -68,12 +91,21 @@ class Camera:
|
||||
with self._lock:
|
||||
return self._frame
|
||||
|
||||
def wait_for_first_frame(self, timeout: float = 5.0) -> bool:
|
||||
"""Block until the first frame is captured, or timeout."""
|
||||
return self._frame_event.wait(timeout)
|
||||
|
||||
def frames(self) -> Iterator[bytes]:
|
||||
"""Yield JPEG frames for multipart streaming."""
|
||||
"""Yield JPEG frames as they are captured."""
|
||||
# wait for first real frame before yielding anything
|
||||
if not self.wait_for_first_frame():
|
||||
logger.error("Timed out waiting for first frame")
|
||||
return
|
||||
while self.running:
|
||||
frame = self.get_frame()
|
||||
if frame:
|
||||
yield frame
|
||||
self._stop_event.wait(0.033) # ~30fps cap
|
||||
|
||||
|
||||
camera = Camera()
|
||||
|
||||
Reference in New Issue
Block a user