From e3ed6931a0ded9c63bac0bc6dc76a14359d7178d Mon Sep 17 00:00:00 2001 From: Gaggery Date: Wed, 1 Apr 2026 05:18:47 -0700 Subject: [PATCH] Merge pull request #67 from TsaiGaggery/fix-camera-v4l2-relayd-v2 intel-ipu7-camera: v4l2-relayd with OBS and Zoom fixes --- .../intel-ipu7-camera/71-ipu7-hide-isys.rules | 8 + .../edge/intel-ipu7-camera/90-ipu7-psys.rules | 5 - pkgbuilds/edge/intel-ipu7-camera/PKGBUILD | 26 ++-- .../edge/intel-ipu7-camera/camera-feed-watch | 144 ------------------ .../camera-feed-watch.service | 12 -- .../intel-ipu7-camera/camera-init.service | 5 +- .../edge/intel-ipu7-camera/camera-sleep-hook | 57 ++----- .../intel-ipu7-camera.install | 52 ++++--- .../v4l2-relayd-ipu7-override.conf | 13 ++ .../intel-ipu7-camera/v4l2-relayd-ipu7.conf | 6 + .../v4l2loopback-modules-load.conf | 1 - 11 files changed, 92 insertions(+), 237 deletions(-) create mode 100644 pkgbuilds/edge/intel-ipu7-camera/71-ipu7-hide-isys.rules delete mode 100644 pkgbuilds/edge/intel-ipu7-camera/camera-feed-watch delete mode 100644 pkgbuilds/edge/intel-ipu7-camera/camera-feed-watch.service create mode 100644 pkgbuilds/edge/intel-ipu7-camera/v4l2-relayd-ipu7-override.conf create mode 100644 pkgbuilds/edge/intel-ipu7-camera/v4l2-relayd-ipu7.conf delete mode 100644 pkgbuilds/edge/intel-ipu7-camera/v4l2loopback-modules-load.conf diff --git a/pkgbuilds/edge/intel-ipu7-camera/71-ipu7-hide-isys.rules b/pkgbuilds/edge/intel-ipu7-camera/71-ipu7-hide-isys.rules new file mode 100644 index 0000000..1a3f49d --- /dev/null +++ b/pkgbuilds/edge/intel-ipu7-camera/71-ipu7-hide-isys.rules @@ -0,0 +1,8 @@ +# Hide raw IPU7 ISYS capture devices from ALL applications. +# These are internal ISP nodes that output raw 12-bit Bayer data and cannot produce usable frames. +# Camera is provided via v4l2loopback (/dev/video50) through v4l2-relayd instead. +# +# This rule MUST run after 70-uaccess.rules (which adds TAG="uaccess" to video4linux) +# but BEFORE 73-seat-late.rules (which applies per-user ACLs based on that tag). +# MODE=0600 + GROUP=root blocks even users in the 'video' group. +SUBSYSTEM=="video4linux", ATTR{name}=="Intel IPU7 ISYS Capture *", TAG-="uaccess", TAG-="seat", MODE="0600", GROUP="root" diff --git a/pkgbuilds/edge/intel-ipu7-camera/90-ipu7-psys.rules b/pkgbuilds/edge/intel-ipu7-camera/90-ipu7-psys.rules index bf62c6d..4fde7b7 100644 --- a/pkgbuilds/edge/intel-ipu7-camera/90-ipu7-psys.rules +++ b/pkgbuilds/edge/intel-ipu7-camera/90-ipu7-psys.rules @@ -1,6 +1 @@ KERNEL=="ipu7-psys0", MODE="0666", SYMLINK+="ipu-psys0" - -# Hide raw IPU7 ISYS capture devices from applications. -# These are internal ISP nodes that cannot produce frames directly. -# Camera is provided via v4l2loopback (/dev/video50) instead. -SUBSYSTEM=="video4linux", ATTR{name}=="Intel IPU7 ISYS Capture *", TAG-="uaccess" diff --git a/pkgbuilds/edge/intel-ipu7-camera/PKGBUILD b/pkgbuilds/edge/intel-ipu7-camera/PKGBUILD index c9c5c17..5b1430a 100644 --- a/pkgbuilds/edge/intel-ipu7-camera/PKGBUILD +++ b/pkgbuilds/edge/intel-ipu7-camera/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Omarchy pkgname=intel-ipu7-camera -pkgver=1.0.2 +pkgver=1.0.3 pkgrel=1 pkgdesc="Intel IPU7 MIPI camera stack for Hurrican/Performance (OV08X40 + hardware ISP)" arch=('x86_64') @@ -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' @@ -48,16 +48,16 @@ source=( "dkms-vision-drivers.conf" "camera-deps.conf" "v4l2loopback-modprobe.conf" - "v4l2loopback-modules-load.conf" "camera-init.service" - "camera-feed-watch" - "camera-feed-watch.service" "hide-ipu7-v4l2.conf" "disable-libcamera.conf" + "71-ipu7-hide-isys.rules" "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' @@ -194,16 +194,10 @@ package() { "${pkgdir}/usr/lib/modprobe.d/camera-deps.conf" install -Dm644 "${srcdir}/v4l2loopback-modprobe.conf" \ "${pkgdir}/usr/lib/modprobe.d/v4l2loopback-camera.conf" - install -Dm644 "${srcdir}/v4l2loopback-modules-load.conf" \ - "${pkgdir}/usr/lib/modules-load.d/v4l2loopback.conf" # 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" \ @@ -211,9 +205,17 @@ package() { install -Dm644 "${srcdir}/disable-libcamera.conf" \ "${pkgdir}/usr/share/wireplumber/wireplumber.conf.d/disable-libcamera.conf" - # udev / tmpfiles / libcamera tuning + # udev rules + install -Dm644 "${srcdir}/71-ipu7-hide-isys.rules" \ + "${pkgdir}/usr/lib/udev/rules.d/71-ipu7-hide-isys.rules" install -Dm644 "${srcdir}/90-ipu7-psys.rules" \ "${pkgdir}/usr/lib/udev/rules.d/90-ipu7-psys.rules" + + # 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" install -Dm644 "${srcdir}/camera-tmpfiles.conf" \ "${pkgdir}/usr/lib/tmpfiles.d/camera.conf" install -Dm644 "${srcdir}/ov08x40.yaml" \ diff --git a/pkgbuilds/edge/intel-ipu7-camera/camera-feed-watch b/pkgbuilds/edge/intel-ipu7-camera/camera-feed-watch deleted file mode 100644 index 5edfef0..0000000 --- a/pkgbuilds/edge/intel-ipu7-camera/camera-feed-watch +++ /dev/null @@ -1,144 +0,0 @@ -#!/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 deleted file mode 100644 index cc0c90b..0000000 --- a/pkgbuilds/edge/intel-ipu7-camera/camera-feed-watch.service +++ /dev/null @@ -1,12 +0,0 @@ -[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 2a1085a..18e844a 100644 --- a/pkgbuilds/edge/intel-ipu7-camera/camera-init.service +++ b/pkgbuilds/edge/intel-ipu7-camera/camera-init.service @@ -1,11 +1,12 @@ [Unit] -Description=Initialize camera (CVS ownership then sensor driver) +Description=Initialize camera (CVS ownership, sensor driver, v4l2loopback) After=systemd-modules-load.service +Before=graphical.target [Service] Type=oneshot RemainAfterExit=yes -ExecStart=/bin/bash -c "modprobe intel_cvs && sleep 2 && modprobe ov08x40" +ExecStart=/bin/bash -c "modprobe intel_cvs && sleep 2 && modprobe ov08x40 && modprobe v4l2loopback" [Install] WantedBy=multi-user.target diff --git a/pkgbuilds/edge/intel-ipu7-camera/camera-sleep-hook b/pkgbuilds/edge/intel-ipu7-camera/camera-sleep-hook index ac2fe2c..0bae2f2 100644 --- a/pkgbuilds/edge/intel-ipu7-camera/camera-sleep-hook +++ b/pkgbuilds/edge/intel-ipu7-camera/camera-sleep-hook @@ -1,52 +1,23 @@ #!/bin/bash -# Re-probe the IPU7 camera sensor after suspend/hibernate resume. +# Handle camera service around suspend/resume. # -# The OV08X40 sensor connects through a USB-to-I2C bridge (usbio-bridge). -# After resume the ipu_bridge software fwnode graph is gone, leaving the -# sensor driver unable to bind. Removing and rescanning the IPU PCI device -# triggers a fresh probe that recreates the fwnode graph. - -IPU_PCI="0000:00:05.0" +# v4l2-relayd must be stopped before suspend to avoid GStreamer crashes, +# then restarted after resume once the camera hardware is ready. +# +# The post-resume restart uses systemd-run to avoid blocking the +# password prompt while waiting for the camera to initialize. case "$1" in + pre) + systemctl stop v4l2-relayd@ipu7.service 2>/dev/null || true + ;; post) - ( - # Wait for the USB-I2C bridge to re-enumerate - for i in $(seq 1 15); do - if grep -q "OVTI08F4" /sys/bus/i2c/devices/*/name 2>/dev/null; then - break - fi - sleep 1 - done - sleep 1 - - # Unload sensor so the IPU device can be cleanly removed - modprobe -r ov08x40 2>/dev/null || true - - # Remove and rescan the IPU PCI device to trigger fresh ipu_bridge_init - echo 1 > "/sys/bus/pci/devices/$IPU_PCI/remove" 2>/dev/null || true - sleep 1 - echo 1 > /sys/bus/pci/rescan 2>/dev/null || true - sleep 3 - - # Reload sensor driver - modprobe intel_cvs 2>/dev/null || true - sleep 2 - modprobe ov08x40 2>/dev/null || true - - # Restart the camera feed service - for uid_dir in /run/user/*; do - uid=$(basename "$uid_dir") - if [[ -S $uid_dir/bus ]]; then - sudo -u "#$uid" env \ - XDG_RUNTIME_DIR="$uid_dir" \ - DBUS_SESSION_BUS_ADDRESS="unix:path=$uid_dir/bus" \ - systemctl --user restart camera-feed-watch.service 2>/dev/null || true - fi - done - ) & - disown + systemd-run --no-block --unit=camera-resume bash -c ' + systemctl restart camera-init.service 2>/dev/null + systemctl reset-failed v4l2-relayd@ipu7.service 2>/dev/null || true + systemctl start v4l2-relayd@ipu7.service 2>/dev/null || true + ' ;; esac exit 0 diff --git a/pkgbuilds/edge/intel-ipu7-camera/intel-ipu7-camera.install b/pkgbuilds/edge/intel-ipu7-camera/intel-ipu7-camera.install index 13d881b..2daa209 100644 --- a/pkgbuilds/edge/intel-ipu7-camera/intel-ipu7-camera.install +++ b/pkgbuilds/edge/intel-ipu7-camera/intel-ipu7-camera.install @@ -1,3 +1,19 @@ +_add_pipewire_camera() { + local conf="$1" + if [ -f "$conf" ] && ! grep -q 'PipeWireCamera' "$conf"; then + if grep -q 'enable-features=' "$conf"; then + sed -i 's/--enable-features=\([^ ]*\)/--enable-features=\1,PipeWireCamera/' "$conf" + else + echo '--enable-features=PipeWireCamera' >> "$conf" + fi + 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 @@ -7,44 +23,44 @@ post_install() { _install_sleep_hook systemctl daemon-reload systemctl enable camera-init.service - systemctl --global enable camera-feed-watch.service + systemctl enable v4l2-relayd@ipu7.service udevadm control --reload-rules 2>/dev/null || true if [ -n "$SUDO_USER" ]; then usermod -aG video "$SUDO_USER" 2>/dev/null || true - # 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 + _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 } 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 - 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 + _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" 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 new file mode 100644 index 0000000..5ec4d0d --- /dev/null +++ b/pkgbuilds/edge/intel-ipu7-camera/v4l2-relayd-ipu7-override.conf @@ -0,0 +1,13 @@ +[Unit] +After=camera-init.service + +[Service] +Environment=GST_PLUGIN_PATH=/usr/lib/gstreamer-1.0 +# Relax sandboxing for icamerasrc hardware access +PrivateNetwork=no +InaccessibleDirectories= +ReadOnlyDirectories= +DevicePolicy=auto +# Give v4l2-relayd time to initialize, then restart WirePlumber +# so PipeWire re-probes the device with the correct format +ExecStartPost=/bin/bash -c 'sleep 2 && systemctl --user -M 1000@ restart wireplumber.service 2>/dev/null || true' diff --git a/pkgbuilds/edge/intel-ipu7-camera/v4l2-relayd-ipu7.conf b/pkgbuilds/edge/intel-ipu7-camera/v4l2-relayd-ipu7.conf new file mode 100644 index 0000000..eb1b6eb --- /dev/null +++ b/pkgbuilds/edge/intel-ipu7-camera/v4l2-relayd-ipu7.conf @@ -0,0 +1,6 @@ +VIDEOSRC="icamerasrc device-name=ov08x40-uf sharpness=80 ev=-1 saturation=10 ! videoflip method=rotate-180" +FORMAT=NV12 +WIDTH=1920 +HEIGHT=1080 +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 deleted file mode 100644 index d394e47..0000000 --- a/pkgbuilds/edge/intel-ipu7-camera/v4l2loopback-modules-load.conf +++ /dev/null @@ -1 +0,0 @@ -v4l2loopback