From 83f010451ebfe26d8108d0268f12555818984da2 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 26 Mar 2026 09:37:53 +0100 Subject: [PATCH] Revert "intel-ipu7-camera: use v4l2-relayd for on-demand camera feed" --- pkgbuilds/edge/intel-ipu7-camera/PKGBUILD | 18 +-- .../edge/intel-ipu7-camera/camera-feed-watch | 144 ++++++++++++++++++ .../camera-feed-watch.service | 12 ++ .../intel-ipu7-camera/camera-init.service | 5 +- .../intel-ipu7-camera.install | 48 +++--- .../v4l2-relayd-ipu7-override.conf | 7 - .../intel-ipu7-camera/v4l2-relayd-ipu7.conf | 6 - .../v4l2loopback-modules-load.conf | 3 +- 8 files changed, 185 insertions(+), 58 deletions(-) create mode 100644 pkgbuilds/edge/intel-ipu7-camera/camera-feed-watch create mode 100644 pkgbuilds/edge/intel-ipu7-camera/camera-feed-watch.service delete mode 100644 pkgbuilds/edge/intel-ipu7-camera/v4l2-relayd-ipu7-override.conf delete mode 100644 pkgbuilds/edge/intel-ipu7-camera/v4l2-relayd-ipu7.conf diff --git a/pkgbuilds/edge/intel-ipu7-camera/PKGBUILD b/pkgbuilds/edge/intel-ipu7-camera/PKGBUILD index c6cb903..682f297 100644 --- a/pkgbuilds/edge/intel-ipu7-camera/PKGBUILD +++ b/pkgbuilds/edge/intel-ipu7-camera/PKGBUILD @@ -2,7 +2,7 @@ pkgname=intel-ipu7-camera pkgver=1.0.1 -pkgrel=4 +pkgrel=3 pkgdesc="Intel IPU7 MIPI camera stack for Hurrican/Performance (OV08X40 + hardware ISP)" arch=('x86_64') url="https://github.com/TsaiGaggery/hurrican_omarchy_enabling" @@ -11,13 +11,13 @@ depends=( 'dkms' 'linux-headers' 'v4l2loopback-dkms' - 'v4l2-relayd' 'gstreamer' 'gst-plugins-base' 'gst-plugins-good' 'gst-plugins-bad' 'jsoncpp' 'libdrm' + 'python' ) makedepends=( 'git' @@ -50,14 +50,14 @@ source=( "v4l2loopback-modprobe.conf" "v4l2loopback-modules-load.conf" "camera-init.service" + "camera-feed-watch" + "camera-feed-watch.service" "hide-ipu7-v4l2.conf" "disable-libcamera.conf" "90-ipu7-psys.rules" "camera-tmpfiles.conf" "ov08x40.yaml" "camera-sleep-hook" - "v4l2-relayd-ipu7.conf" - "v4l2-relayd-ipu7-override.conf" ) sha256sums=( 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' @@ -200,6 +200,10 @@ package() { # systemd install -Dm644 "${srcdir}/camera-init.service" \ "${pkgdir}/usr/lib/systemd/system/camera-init.service" + install -Dm644 "${srcdir}/camera-feed-watch.service" \ + "${pkgdir}/usr/lib/systemd/user/camera-feed-watch.service" + install -Dm755 "${srcdir}/camera-feed-watch" \ + "${pkgdir}/usr/bin/camera-feed-watch" # WirePlumber install -Dm644 "${srcdir}/hide-ipu7-v4l2.conf" \ @@ -218,10 +222,4 @@ package() { # Sleep hook — staged here, installed to system-sleep by the .install scriptlet install -Dm755 "${srcdir}/camera-sleep-hook" \ "${pkgdir}/usr/share/intel-ipu7-camera/camera-sleep-hook" - - # v4l2-relayd config for IPU7 camera - install -Dm644 "${srcdir}/v4l2-relayd-ipu7.conf" \ - "${pkgdir}/etc/v4l2-relayd.d/ipu7.conf" - install -Dm644 "${srcdir}/v4l2-relayd-ipu7-override.conf" \ - "${pkgdir}/etc/systemd/system/v4l2-relayd@ipu7.service.d/override.conf" } diff --git a/pkgbuilds/edge/intel-ipu7-camera/camera-feed-watch b/pkgbuilds/edge/intel-ipu7-camera/camera-feed-watch new file mode 100644 index 0000000..5edfef0 --- /dev/null +++ b/pkgbuilds/edge/intel-ipu7-camera/camera-feed-watch @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""On-demand camera feed for v4l2loopback. + +Holds /dev/video50 open permanently so v4l2loopback stays in capture mode. +Writes black frames when idle (0% CPU, LED off). Starts the real camera +pipeline when an app opens the device, relaying frames through the same fd. +Stops the camera when all readers close. No fd gap = no stream interruption. +""" + +import fcntl, os, signal, struct, subprocess, sys, threading, time + +DEVICE = "/dev/video50" +W, H = 1920, 1080 +FSIZE = W * H * 3 // 2 +POLL = 2 +STOP_DELAY = 6 + +BLACK = bytes(W * H) + bytes([128] * (W * H // 2)) + +dev = os.open(DEVICE, os.O_WRONLY) +pixfmt = struct.pack("II4sIIII4x188x", W, H, b"NV12", 1, W, FSIZE, 0) +fmt = struct.pack("I4x", 2) + pixfmt +fcntl.ioctl(dev, 0xC0D05605, fmt) +os.write(dev, BLACK) + +# Restart WirePlumber so it sees the capture device +time.sleep(1) +subprocess.run(["systemctl", "--user", "restart", "wireplumber.service"], + capture_output=True, timeout=10) + +cam_proc = None +cam_lock = threading.Lock() +stop_idle = threading.Event() + + +def write_idle(): + while not stop_idle.is_set(): + try: + os.write(dev, BLACK) + except OSError: + pass + stop_idle.wait(2) + + +def relay_camera(): + global cam_proc + env = os.environ.copy() + env["GST_PLUGIN_PATH"] = "/usr/lib/gstreamer-1.0" + cam_proc = subprocess.Popen( + ["gst-launch-1.0", "-q", + "icamerasrc", "device-name=ov08x40-uf", + "sharpness=80", "ev=-1", "saturation=10", + "!", "video/x-raw,format=NV12,width=1920,height=1080", + "!", "videoflip", "method=rotate-180", + "!", "fdsink", "fd=1"], + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, env=env, + ) + buf = b"" + while cam_proc.poll() is None: + data = cam_proc.stdout.read(65536) + if not data: + break + buf += data + while len(buf) >= FSIZE: + try: + os.write(dev, buf[:FSIZE]) + except OSError: + pass + buf = buf[FSIZE:] + cam_proc = None + + +def has_readers(): + try: + r = subprocess.run(["fuser", DEVICE], capture_output=True, text=True, timeout=5) + for pid in r.stdout.strip().split(): + pid = pid.strip() + if not pid: + continue + if int(pid) == os.getpid(): + continue + with cam_lock: + if cam_proc and int(pid) == cam_proc.pid: + continue + return True + except Exception: + pass + return False + + +def start_camera(): + stop_idle.set() + t = threading.Thread(target=relay_camera, daemon=True) + t.start() + return t + + +def stop_camera(): + with cam_lock: + if cam_proc: + cam_proc.terminate() + try: + cam_proc.wait(timeout=5) + except subprocess.TimeoutExpired: + cam_proc.kill() + stop_idle.clear() + t = threading.Thread(target=write_idle, daemon=True) + t.start() + return t + + +def shutdown(sig, frame): + stop_idle.set() + with cam_lock: + if cam_proc: + cam_proc.terminate() + os.close(dev) + sys.exit(0) + + +signal.signal(signal.SIGTERM, shutdown) +signal.signal(signal.SIGINT, shutdown) + +# Start idle +idle_thread = threading.Thread(target=write_idle, daemon=True) +idle_thread.start() + +active = False +idle_count = 0 + +while True: + if has_readers(): + idle_count = 0 + if not active: + start_camera() + active = True + else: + if active: + idle_count += 1 + if idle_count >= STOP_DELAY // POLL: + stop_camera() + active = False + idle_count = 0 + time.sleep(POLL) diff --git a/pkgbuilds/edge/intel-ipu7-camera/camera-feed-watch.service b/pkgbuilds/edge/intel-ipu7-camera/camera-feed-watch.service new file mode 100644 index 0000000..cc0c90b --- /dev/null +++ b/pkgbuilds/edge/intel-ipu7-camera/camera-feed-watch.service @@ -0,0 +1,12 @@ +[Unit] +Description=Camera feed for v4l2loopback (on-demand, LED off when idle) +After=pipewire.service camera-init.service + +[Service] +Type=simple +ExecStart=/usr/bin/camera-feed-watch +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=default.target diff --git a/pkgbuilds/edge/intel-ipu7-camera/camera-init.service b/pkgbuilds/edge/intel-ipu7-camera/camera-init.service index 18e844a..2a1085a 100644 --- a/pkgbuilds/edge/intel-ipu7-camera/camera-init.service +++ b/pkgbuilds/edge/intel-ipu7-camera/camera-init.service @@ -1,12 +1,11 @@ [Unit] -Description=Initialize camera (CVS ownership, sensor driver, v4l2loopback) +Description=Initialize camera (CVS ownership then sensor driver) After=systemd-modules-load.service -Before=graphical.target [Service] Type=oneshot RemainAfterExit=yes -ExecStart=/bin/bash -c "modprobe intel_cvs && sleep 2 && modprobe ov08x40 && modprobe v4l2loopback" +ExecStart=/bin/bash -c "modprobe intel_cvs && sleep 2 && modprobe ov08x40" [Install] WantedBy=multi-user.target diff --git a/pkgbuilds/edge/intel-ipu7-camera/intel-ipu7-camera.install b/pkgbuilds/edge/intel-ipu7-camera/intel-ipu7-camera.install index 797b6d9..13d881b 100644 --- a/pkgbuilds/edge/intel-ipu7-camera/intel-ipu7-camera.install +++ b/pkgbuilds/edge/intel-ipu7-camera/intel-ipu7-camera.install @@ -1,15 +1,3 @@ -_add_pipewire_camera() { - local conf="$1" - if [ -f "$conf" ] && grep -q 'enable-features=' "$conf" && ! grep -q 'PipeWireCamera' "$conf"; then - sed -i 's/--enable-features=\([^ ]*\)/--enable-features=\1,PipeWireCamera/' "$conf" - fi -} - -_remove_pipewire_camera() { - local conf="$1" - [ -f "$conf" ] && sed -i 's/,PipeWireCamera//g' "$conf" -} - _install_sleep_hook() { cp /usr/share/intel-ipu7-camera/camera-sleep-hook /usr/lib/systemd/system-sleep/camera-sleep-hook chmod 755 /usr/lib/systemd/system-sleep/camera-sleep-hook @@ -19,44 +7,44 @@ post_install() { _install_sleep_hook systemctl daemon-reload systemctl enable camera-init.service - systemctl enable v4l2-relayd@ipu7.service + systemctl --global enable camera-feed-watch.service udevadm control --reload-rules 2>/dev/null || true if [ -n "$SUDO_USER" ]; then usermod -aG video "$SUDO_USER" 2>/dev/null || true - _add_pipewire_camera "/home/$SUDO_USER/.config/chromium-flags.conf" - _add_pipewire_camera "/home/$SUDO_USER/.config/brave-flags.conf" - _add_pipewire_camera "/home/$SUDO_USER/.config/chrome-flags.conf" + # Start the service in the current user session if one is active + local uid + uid=$(id -u "$SUDO_USER" 2>/dev/null) + if [ -n "$uid" ] && [ -d "/run/user/$uid" ]; then + XDG_RUNTIME_DIR="/run/user/$uid" \ + DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$uid/bus" \ + runuser -u "$SUDO_USER" -- \ + systemctl --user start camera-feed-watch.service 2>/dev/null || true + fi fi } post_upgrade() { _install_sleep_hook systemctl daemon-reload - systemctl enable v4l2-relayd@ipu7.service - # Disable old camera-feed-watch (replaced by v4l2-relayd) - systemctl --global disable camera-feed-watch.service 2>/dev/null || true udevadm control --reload-rules 2>/dev/null || true - - if [ -n "$SUDO_USER" ]; then - _add_pipewire_camera "/home/$SUDO_USER/.config/chromium-flags.conf" - _add_pipewire_camera "/home/$SUDO_USER/.config/brave-flags.conf" - _add_pipewire_camera "/home/$SUDO_USER/.config/chrome-flags.conf" - fi } pre_remove() { - systemctl disable v4l2-relayd@ipu7.service 2>/dev/null || true - systemctl stop v4l2-relayd@ipu7.service 2>/dev/null || true systemctl --global disable camera-feed-watch.service 2>/dev/null || true systemctl disable camera-init.service 2>/dev/null || true systemctl stop camera-init.service 2>/dev/null || true if [ -n "$SUDO_USER" ]; then - _remove_pipewire_camera "/home/$SUDO_USER/.config/chromium-flags.conf" - _remove_pipewire_camera "/home/$SUDO_USER/.config/brave-flags.conf" - _remove_pipewire_camera "/home/$SUDO_USER/.config/chrome-flags.conf" + local uid + uid=$(id -u "$SUDO_USER" 2>/dev/null) + if [ -n "$uid" ] && [ -d "/run/user/$uid" ]; then + XDG_RUNTIME_DIR="/run/user/$uid" \ + DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$uid/bus" \ + runuser -u "$SUDO_USER" -- \ + systemctl --user stop camera-feed-watch.service 2>/dev/null || true + fi fi } diff --git a/pkgbuilds/edge/intel-ipu7-camera/v4l2-relayd-ipu7-override.conf b/pkgbuilds/edge/intel-ipu7-camera/v4l2-relayd-ipu7-override.conf deleted file mode 100644 index 850e258..0000000 --- a/pkgbuilds/edge/intel-ipu7-camera/v4l2-relayd-ipu7-override.conf +++ /dev/null @@ -1,7 +0,0 @@ -[Service] -Environment=GST_PLUGIN_PATH=/usr/lib/gstreamer-1.0 -# Relax sandboxing for icamerasrc hardware access -PrivateNetwork=no -InaccessibleDirectories= -ReadOnlyDirectories= -DevicePolicy=auto diff --git a/pkgbuilds/edge/intel-ipu7-camera/v4l2-relayd-ipu7.conf b/pkgbuilds/edge/intel-ipu7-camera/v4l2-relayd-ipu7.conf deleted file mode 100644 index 6b99357..0000000 --- a/pkgbuilds/edge/intel-ipu7-camera/v4l2-relayd-ipu7.conf +++ /dev/null @@ -1,6 +0,0 @@ -VIDEOSRC="icamerasrc device-name=ov08x40-uf sharpness=80 ev=-1 saturation=10 ! videoflip method=rotate-180" -FORMAT=NV12 -WIDTH=3840 -HEIGHT=2160 -FRAMERATE=30/1 -CARD_LABEL="Hardware ISP Camera" diff --git a/pkgbuilds/edge/intel-ipu7-camera/v4l2loopback-modules-load.conf b/pkgbuilds/edge/intel-ipu7-camera/v4l2loopback-modules-load.conf index fd1aae3..d394e47 100644 --- a/pkgbuilds/edge/intel-ipu7-camera/v4l2loopback-modules-load.conf +++ b/pkgbuilds/edge/intel-ipu7-camera/v4l2loopback-modules-load.conf @@ -1,2 +1 @@ -# v4l2loopback is loaded by camera-init.service instead of here. -# Loading here causes a race with PipeWire that breaks exclusive_caps. +v4l2loopback