From b1306bac21f3a0f570863952c91e1b5be57bd420 Mon Sep 17 00:00:00 2001 From: mikkeli Date: Thu, 6 Aug 2026 09:21:02 +0900 Subject: [PATCH] Fix V4L2 mmap capture and service configuration - capture.py: use mmap.mmap instead of non-existent os.mmap - capture.py: fix v4l2_fourcc() to pass 4 char arguments - camera-webui.service: add Group=video and PATH env var --- camera-webui.service | 3 ++- camera/capture.py | 16 ++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/camera-webui.service b/camera-webui.service index 1b631fc..4c17395 100644 --- a/camera-webui.service +++ b/camera-webui.service @@ -6,8 +6,9 @@ Wants=network-online.target [Service] Type=exec User=mikkeli +Group=video WorkingDirectory=/home/mikkeli/dev/usb-camera-webui -ExecStartPre=/usr/bin/python3 -c "import v4l2" +Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ExecStart=/usr/bin/python3 -m camera.app --host 0.0.0.0 --port 8080 Restart=on-failure RestartSec=5 diff --git a/camera/capture.py b/camera/capture.py index c4a218f..def72cb 100644 --- a/camera/capture.py +++ b/camera/capture.py @@ -1,16 +1,20 @@ -"""V4L2 MJPEG capture via the v4l2 Python library (mmap). +"""V4L2 MJPEG capture via mmap buffers. -Uses memory-mapped buffers for efficient frame capture without subprocess -overhead. Each frame is a complete JPEG image ready for MJPEG streaming. +Opens /dev/video0 and uses memory-mapped V4L2 buffers for efficient frame +capture. Each frame is a complete JPEG image ready for MJPEG streaming. """ import fcntl import os import time import threading +import mmap import v4l2 +# Fourcc for Motion JPEG +MJPG = v4l2.v4l2_fourcc("M", "J", "P", "G") + # Global camera singleton _camera: "Capture | None" = None _camera_lock = threading.Lock() @@ -79,10 +83,10 @@ class Capture: fmt.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE fmt.fmt.pix.width = self._width fmt.fmt.pix.height = self._height - fmt.fmt.pix.pixelformat = v4l2.v4l2_fourcc("MJPG") + fmt.fmt.pix.pixelformat = MJPG fmt.fmt.pix.field = v4l2.V4L2_FIELD_ANY _ioctl(self._fd, v4l2.VIDIOC_S_FMT, fmt) - print(f"Format set: {fmt.fmt.pix.width}x{fmt.fmt.pix.height} {v4l2.v4l2_fourcc2str(fmt.fmt.pix.pixelformat)}") + print(f"Format: {fmt.fmt.pix.width}x{fmt.fmt.pix.height} {v4l2.v4l2_fourcc2str(fmt.fmt.pix.pixelformat)}") def _allocate_buffers(self) -> None: req = v4l2.v4l2_requestbuffers() @@ -101,7 +105,7 @@ class Capture: length = buf.length offset = buf.m.offset - data = os.mmap(0, length, os.PROT_READ, os.MAP_SHARED, self._fd, offset) + data = mmap.mmap(self._fd, length, mmap.MAP_SHARED, mmap.PROT_READ, 0, offset) self._buffers.append({ "index": i, "length": length,