diff --git a/pkgbuilds/edge/intel-ipu7-camera/PKGBUILD b/pkgbuilds/edge/intel-ipu7-camera/PKGBUILD index 682f297..c410635 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=3 +pkgrel=4 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,20 +50,19 @@ 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' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' - 'SKIP' 'SKIP' ) prepare() { @@ -200,10 +199,6 @@ 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" \ @@ -222,4 +217,10 @@ 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 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/intel-ipu7-camera.install b/pkgbuilds/edge/intel-ipu7-camera/intel-ipu7-camera.install index 13d881b..797b6d9 100644 --- a/pkgbuilds/edge/intel-ipu7-camera/intel-ipu7-camera.install +++ b/pkgbuilds/edge/intel-ipu7-camera/intel-ipu7-camera.install @@ -1,3 +1,15 @@ +_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 @@ -7,44 +19,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..850e258 --- /dev/null +++ b/pkgbuilds/edge/intel-ipu7-camera/v4l2-relayd-ipu7-override.conf @@ -0,0 +1,7 @@ +[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 new file mode 100644 index 0000000..6b99357 --- /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=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 d394e47..fd1aae3 100644 --- a/pkgbuilds/edge/intel-ipu7-camera/v4l2loopback-modules-load.conf +++ b/pkgbuilds/edge/intel-ipu7-camera/v4l2loopback-modules-load.conf @@ -1 +1,2 @@ -v4l2loopback +# v4l2loopback is loaded by camera-init.service instead of here. +# Loading here causes a race with PipeWire that breaks exclusive_caps.