- Add FrameDispatcher for distributing QVideoFrames to subscribers - Implement TelemetryCollector to measure video pipeline performance metrics - Create MainWindow as the main application interface with video rendering - Develop AppMenuBar for camera selection, resolution, and FPS settings - Establish overlay system for displaying telemetry metrics - Set up project structure and configuration files - Add unit tests for FrameDispatcher and TelemetryCollector
81 lines
2.6 KiB
Python
81 lines
2.6 KiB
Python
"""Camera enumeration — discovers available video input devices."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
from PySide6.QtMultimedia import QCameraDevice, QMediaDevices
|
|
|
|
|
|
@dataclass
|
|
class CameraInfo:
|
|
"""Lightweight descriptor of a detected camera."""
|
|
|
|
device: QCameraDevice
|
|
name: str
|
|
id: str
|
|
formats: list[tuple[int, int, float]] = field(default_factory=list)
|
|
# formats: list of (width, height, max_fps)
|
|
|
|
def __str__(self) -> str:
|
|
return f"{self.name} [{self.id}]"
|
|
|
|
|
|
class CameraEnumerator:
|
|
"""Discovers available video input devices via QMediaDevices."""
|
|
|
|
@staticmethod
|
|
def list_cameras() -> list[CameraInfo]:
|
|
"""Return all available camera devices with their supported formats."""
|
|
devices = QMediaDevices.videoInputs()
|
|
cameras: list[CameraInfo] = []
|
|
|
|
for device in devices:
|
|
formats: list[tuple[int, int, float]] = []
|
|
for fmt in device.videoFormats():
|
|
res = fmt.resolution()
|
|
fps = fmt.maxFrameRate()
|
|
formats.append((res.width(), res.height(), fps))
|
|
|
|
# deduplicate and sort: largest resolution first, then fps descending
|
|
seen: set[tuple[int, int, float]] = set()
|
|
unique_formats: list[tuple[int, int, float]] = []
|
|
for f in sorted(formats, key=lambda x: (x[0] * x[1], x[2]), reverse=True):
|
|
if f not in seen:
|
|
seen.add(f)
|
|
unique_formats.append(f)
|
|
|
|
cameras.append(
|
|
CameraInfo(
|
|
device=device,
|
|
name=device.description(),
|
|
id=device.id().toStdString()
|
|
if hasattr(device.id(), "toStdString")
|
|
else device.id().data().decode("utf-8", errors="replace"),
|
|
formats=unique_formats,
|
|
)
|
|
)
|
|
|
|
return cameras
|
|
|
|
@staticmethod
|
|
def default_camera() -> CameraInfo | None:
|
|
"""Return the system default camera, or None if no camera is available."""
|
|
device = QMediaDevices.defaultVideoInput()
|
|
if device.isNull():
|
|
return None
|
|
|
|
cameras = CameraEnumerator.list_cameras()
|
|
# find by id match
|
|
default_id = (
|
|
device.id().toStdString()
|
|
if hasattr(device.id(), "toStdString")
|
|
else device.id().data().decode("utf-8", errors="replace")
|
|
)
|
|
for cam in cameras:
|
|
if cam.id == default_id:
|
|
return cam
|
|
|
|
# fallback: wrap directly
|
|
return cameras[0] if cameras else None
|