test on mac
This commit is contained in:
363
test_qcamera.py
Normal file
363
test_qcamera.py
Normal file
@@ -0,0 +1,363 @@
|
||||
import sys
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import Qt, QTimer
|
||||
from PySide6.QtGui import QAction
|
||||
from PySide6.QtWidgets import QApplication, QMainWindow, QLabel
|
||||
from PySide6.QtMultimedia import (
|
||||
QMediaDevices,
|
||||
QCamera,
|
||||
QCameraDevice,
|
||||
QMediaFormat,
|
||||
QVideoFrameFormat,
|
||||
QMediaCaptureSession
|
||||
)
|
||||
from PySide6.QtMultimediaWidgets import QVideoWidget
|
||||
|
||||
|
||||
# ============================================================
|
||||
# LOGGER
|
||||
# ============================================================
|
||||
|
||||
LOG_DIR = Path("logs")
|
||||
LOG_DIR.mkdir(exist_ok=True)
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||
handlers=[
|
||||
logging.FileHandler(LOG_DIR / "camera_debug.log", encoding="utf-8"),
|
||||
logging.StreamHandler(sys.stdout),
|
||||
],
|
||||
)
|
||||
|
||||
logger = logging.getLogger("camera_app")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# CAMERA WINDOW
|
||||
# ============================================================
|
||||
|
||||
class CameraWindow(QMainWindow):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.setWindowTitle("Camera Preview")
|
||||
|
||||
# Widget wyświetlający wyłącznie obraz
|
||||
self.video_widget = QVideoWidget()
|
||||
self.setCentralWidget(self.video_widget)
|
||||
|
||||
self.camera = None
|
||||
self.camera_device = None
|
||||
self.capture_session = QMediaCaptureSession()
|
||||
|
||||
self.init_camera()
|
||||
|
||||
# ========================================================
|
||||
# CAMERA INIT
|
||||
# ========================================================
|
||||
|
||||
def init_camera(self):
|
||||
|
||||
devices = QMediaDevices.videoInputs()
|
||||
|
||||
logger.info("==========================================")
|
||||
logger.info("VIDEO DEVICES ENUMERATION")
|
||||
logger.info("==========================================")
|
||||
|
||||
if not devices:
|
||||
logger.error("Nie znaleziono kamer.")
|
||||
return
|
||||
|
||||
for idx, device in enumerate(devices):
|
||||
self.log_camera_device(device, idx)
|
||||
|
||||
# wybór pierwszej kamery
|
||||
self.camera_device = devices[0]
|
||||
|
||||
logger.info(f"Wybrano kamerę: {self.camera_device.description()}")
|
||||
|
||||
self.camera = QCamera(self.camera_device)
|
||||
|
||||
# sygnały
|
||||
self.camera.errorOccurred.connect(self.on_camera_error)
|
||||
self.camera.activeChanged.connect(self.on_active_changed)
|
||||
|
||||
# video sink
|
||||
# self.camera.setVideoOutput(self.video_widget)
|
||||
self.capture_session.setCamera(self.camera)
|
||||
self.capture_session.setVideoOutput(self.video_widget)
|
||||
|
||||
# przykładowe ustawienia
|
||||
self.set_camera_format(
|
||||
width=1280,
|
||||
height=720,
|
||||
fps=30,
|
||||
preferred_pixel_format=QVideoFrameFormat.PixelFormat.Format_Jpeg
|
||||
)
|
||||
|
||||
self.log_runtime_camera_capabilities()
|
||||
|
||||
self.camera.start()
|
||||
|
||||
logger.info("Kamera uruchomiona.")
|
||||
|
||||
# ========================================================
|
||||
# FORMAT SELECTION
|
||||
# ========================================================
|
||||
def set_camera_format(
|
||||
self,
|
||||
width: int,
|
||||
height: int,
|
||||
fps: int,
|
||||
preferred_pixel_format=QVideoFrameFormat.PixelFormat.Format_Jpeg,
|
||||
):
|
||||
# def set_camera_format(self, width: int, height: int, fps: int):
|
||||
"""
|
||||
Prototyp funkcji ustawiającej:
|
||||
- rozdzielczość
|
||||
- FPS
|
||||
|
||||
Wybiera najbliższy pasujący format.
|
||||
"""
|
||||
|
||||
logger.info(
|
||||
f"Próba ustawienia formatu: {width}x{height} @ {fps} FPS"
|
||||
)
|
||||
|
||||
best_match = None
|
||||
|
||||
for fmt in self.camera_device.videoFormats():
|
||||
|
||||
resolution = fmt.resolution()
|
||||
min_fps = fmt.minFrameRate()
|
||||
max_fps = fmt.maxFrameRate()
|
||||
pixel_format = fmt.pixelFormat()
|
||||
|
||||
logger.debug(
|
||||
f"Sprawdzam format -> "
|
||||
f"{resolution.width()}x{resolution.height()} "
|
||||
f"FPS:{min_fps}-{max_fps} "
|
||||
f"PIX:{pixel_format}"
|
||||
)
|
||||
|
||||
if (
|
||||
resolution.width() == width
|
||||
and resolution.height() == height
|
||||
and min_fps <= fps <= max_fps
|
||||
and pixel_format == preferred_pixel_format
|
||||
):
|
||||
best_match = fmt
|
||||
break
|
||||
|
||||
if best_match:
|
||||
self.camera.setCameraFormat(best_match)
|
||||
|
||||
logger.info("Ustawiono format kamery:")
|
||||
logger.info(
|
||||
f"Resolution: "
|
||||
f"{best_match.resolution().width()}x"
|
||||
f"{best_match.resolution().height()}"
|
||||
)
|
||||
logger.info(
|
||||
f"FPS range: "
|
||||
f"{best_match.minFrameRate()} - "
|
||||
f"{best_match.maxFrameRate()}"
|
||||
)
|
||||
logger.info(
|
||||
f"Pixel format: {best_match.pixelFormat()}"
|
||||
)
|
||||
|
||||
else:
|
||||
logger.warning("Nie znaleziono pasującego formatu.")
|
||||
|
||||
# ========================================================
|
||||
# DEVICE LOGGER
|
||||
# ========================================================
|
||||
|
||||
def log_camera_device(self, device: QCameraDevice, idx: int):
|
||||
|
||||
logger.info("------------------------------------------")
|
||||
logger.info(f"KAMERA #{idx}")
|
||||
logger.info("------------------------------------------")
|
||||
|
||||
logger.info(f"Description: {device.description()}")
|
||||
logger.info(f"ID: {device.id().data().decode(errors='ignore')}")
|
||||
|
||||
try:
|
||||
logger.info(f"Is default: {device.isDefault()}")
|
||||
except Exception as e:
|
||||
logger.warning(f"isDefault() unsupported: {e}")
|
||||
|
||||
formats = device.videoFormats()
|
||||
|
||||
logger.info(f"Liczba formatów: {len(formats)}")
|
||||
|
||||
for i, fmt in enumerate(formats):
|
||||
|
||||
resolution = fmt.resolution()
|
||||
|
||||
logger.info(f"")
|
||||
logger.info(f"FORMAT #{i}")
|
||||
|
||||
logger.info(
|
||||
f"Resolution: "
|
||||
f"{resolution.width()}x{resolution.height()}"
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"FPS min/max: "
|
||||
f"{fmt.minFrameRate()} / {fmt.maxFrameRate()}"
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Pixel format enum: {fmt.pixelFormat()}"
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Pixel format name: "
|
||||
f"{self.pixel_format_to_string(fmt.pixelFormat())}"
|
||||
)
|
||||
|
||||
# ========================================================
|
||||
# RUNTIME CAMERA CAPABILITIES
|
||||
# ========================================================
|
||||
|
||||
def log_runtime_camera_capabilities(self):
|
||||
|
||||
logger.info("")
|
||||
logger.info("==========================================")
|
||||
logger.info("QCAMERA RUNTIME CAPABILITIES")
|
||||
logger.info("==========================================")
|
||||
|
||||
try:
|
||||
logger.info(f"Camera active: {self.camera.isActive()}")
|
||||
except Exception as e:
|
||||
logger.warning(e)
|
||||
|
||||
# Dostępne w zależności od backendu/platformy
|
||||
properties = [
|
||||
"focusMode",
|
||||
"exposureMode",
|
||||
"whiteBalanceMode",
|
||||
"flashMode",
|
||||
"torchMode",
|
||||
]
|
||||
|
||||
for prop in properties:
|
||||
|
||||
try:
|
||||
value = getattr(self.camera, prop)()
|
||||
logger.info(f"{prop}: {value}")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"{prop} unsupported: {e}")
|
||||
|
||||
# backend/platform info
|
||||
logger.info("")
|
||||
logger.info("QT MULTIMEDIA INFO")
|
||||
|
||||
try:
|
||||
logger.info(f"Qt version: {QApplication.qtVersion()}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logger.info(
|
||||
"Backend zależy od platformy:"
|
||||
)
|
||||
logger.info(
|
||||
"- Windows -> MediaFoundation"
|
||||
)
|
||||
logger.info(
|
||||
"- Linux -> GStreamer / PipeWire / V4L2"
|
||||
)
|
||||
logger.info(
|
||||
"- macOS -> AVFoundation"
|
||||
)
|
||||
|
||||
# ========================================================
|
||||
# PIXEL FORMAT NAME
|
||||
# ========================================================
|
||||
|
||||
def pixel_format_to_string(self, pixel_format):
|
||||
|
||||
mapping = {
|
||||
QVideoFrameFormat.PixelFormat.Format_YUV420P: "YUV420P",
|
||||
QVideoFrameFormat.PixelFormat.Format_NV12: "NV12",
|
||||
QVideoFrameFormat.PixelFormat.Format_NV21: "NV21",
|
||||
QVideoFrameFormat.PixelFormat.Format_UYVY: "UYVY",
|
||||
QVideoFrameFormat.PixelFormat.Format_YUYV: "YUYV",
|
||||
QVideoFrameFormat.PixelFormat.Format_Jpeg: "MJPG/JPEG",
|
||||
}
|
||||
|
||||
return mapping.get(pixel_format, str(pixel_format))
|
||||
|
||||
# ========================================================
|
||||
# SIGNALS
|
||||
# ========================================================
|
||||
|
||||
def on_camera_error(self, error, error_string):
|
||||
logger.error(f"CAMERA ERROR: {error}")
|
||||
logger.error(f"ERROR STRING: {error_string}")
|
||||
|
||||
def on_active_changed(self, active):
|
||||
logger.info(f"Camera active changed: {active}")
|
||||
|
||||
# ========================================================
|
||||
# CLEANUP
|
||||
# ========================================================
|
||||
|
||||
# def closeEvent(self, event):
|
||||
|
||||
# logger.info("Zamykanie aplikacji.")
|
||||
|
||||
# if self.camera:
|
||||
# self.camera.stop()
|
||||
|
||||
# super().closeEvent(event)
|
||||
|
||||
def closeEvent(self, event):
|
||||
|
||||
logger.info("Zamykanie aplikacji.")
|
||||
|
||||
try:
|
||||
if self.camera:
|
||||
logger.info("Stopping camera...")
|
||||
self.camera.stop()
|
||||
|
||||
if self.capture_session:
|
||||
logger.info("Detaching camera from capture session...")
|
||||
self.capture_session.setCamera(None)
|
||||
self.capture_session.setVideoOutput(None)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
super().closeEvent(event)
|
||||
|
||||
# ============================================================
|
||||
# MAIN
|
||||
# ============================================================
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
window = CameraWindow()
|
||||
|
||||
# tylko obraz
|
||||
window.resize(1280, 720)
|
||||
|
||||
# fullscreen:
|
||||
# window.showFullScreen()
|
||||
|
||||
window.show()
|
||||
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user