intel-ipu7-camera: use v4l2-relayd for on-demand camera feed
Replace camera-feed-watch and camera-session with v4l2-relayd, a single-process daemon that manages the icamerasrc -> v4l2loopback feed. v4l2-relayd uses V4L2_EVENT_PRI_CLIENT_USAGE kernel events to detect when an app starts/stops streaming, providing zero-polling on-demand camera activation with LED control. This is a generic solution that works with all PipeWire apps (Snapshot, Chromium, Firefox, etc.) without per-app wrappers or .desktop patching. The key advantage over the previous camera-feed-watch approach: v4l2-relayd keeps a single persistent v4l2sink writer, switching only the data source (splash vs icamerasrc) via appsrc. This avoids the PipeWire stale mmap buffer issue that caused degraded framerate on subsequent camera sessions when the writer process changed. Changes: - Add v4l2-relayd as package dependency - Add /etc/v4l2-relayd.d/ipu7.conf with icamerasrc pipeline config - Add systemd service override for GST_PLUGIN_PATH and device access - Remove camera-feed-watch, camera-feed-watch.service, camera-session, camera-feed.service, and Snapshot .desktop patching - Restore PipeWireCamera feature flag for Chromium/Brave/Chrome - Enable v4l2-relayd@ipu7.service at install time
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user