From 368319af631f287c36df708779d1e2ed80806b99 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Tue, 6 Jan 2026 09:36:55 -0300 Subject: [PATCH] uvc: default to 720p gadget settings --- scripts/daemon/lesavka-core.sh | 52 +++++++++++++++++++++++++++------- server/src/bin/lesavka-uvc.rs | 6 ++-- server/src/video.rs | 20 +++++++++++-- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/scripts/daemon/lesavka-core.sh b/scripts/daemon/lesavka-core.sh index 5d77587..46ea308 100644 --- a/scripts/daemon/lesavka-core.sh +++ b/scripts/daemon/lesavka-core.sh @@ -11,6 +11,21 @@ cleanup() { echo "" >"$G/UDC" 2>/dev/null || true; } DISABLE_UAC=${LESAVKA_DISABLE_UAC:-} DISABLE_UVC=${LESAVKA_DISABLE_UVC:-} UVC_FALLBACK=${LESAVKA_UVC_FALLBACK:-1} +UVC_STREAMING_INTERVAL=${LESAVKA_UVC_STREAMING_INTERVAL:-1} +UVC_MAXPACKET=${LESAVKA_UVC_MAXPACKET:-1024} +UVC_MAXBURST=${LESAVKA_UVC_MAXBURST:-1} +UVC_INTERVAL=${LESAVKA_UVC_INTERVAL:-} +UVC_WIDTH=${LESAVKA_UVC_WIDTH:-1280} +UVC_HEIGHT=${LESAVKA_UVC_HEIGHT:-720} +UVC_FPS=${LESAVKA_UVC_FPS:-30} +UVC_DISABLE_IRQ=${LESAVKA_UVC_DISABLE_IRQ:-} +UVC_BULK=${LESAVKA_UVC_BULK:-} +MAX_SPEED=${LESAVKA_MAX_SPEED:-high-speed} + +if [[ -z $UVC_INTERVAL ]]; then + UVC_INTERVAL=$((10000000 / UVC_FPS)) +fi +UVC_FRAME_SIZE=$((UVC_WIDTH * UVC_HEIGHT * 2)) wait_for_enum() { local tries=${1:-50} # 50 x 100ms = 5s @@ -86,15 +101,19 @@ G=/sys/kernel/config/usb_gadget/lesavka if [[ -d $G ]]; then echo '' >"$G/UDC" 2>/dev/null || true sleep 0.2 - find "$G/configs" -type l -delete 2>/dev/null || true - rm -rf "$G" 2>/dev/null || true + # configfs doesn't allow unlinking attribute files; remove links then rmdir. + find "$G" -type l -delete 2>/dev/null || true + for dir in "$G/functions" "$G/configs" "$G/strings" "$G/os_desc" "$G/webusb"; do + [[ -d $dir ]] || continue + find "$dir" -mindepth 1 -depth -type d -exec rmdir {} \; 2>/dev/null || true + done fi mkdir -p "$G" echo 0x1d6b >"$G/idVendor" # Linux Foundation echo 0x0104 >"$G/idProduct" # Multifunction Composite Gadget echo 0x0200 >"$G/bcdUSB" -echo high-speed >"$G/max_speed" +echo "$MAX_SPEED" >"$G/max_speed" mkdir -p "$G/strings/0x409" echo "$(cat /proc/sys/kernel/random/uuid)" >"$G/strings/0x409/serialnumber" @@ -145,6 +164,12 @@ if [[ -z $DISABLE_UVC ]]; then # ----------------------- UVC function (usb‑video) ------------------ mkdir -p "$G/functions/uvc.usb0" F="$G/functions/uvc.usb0" + echo "$UVC_STREAMING_INTERVAL" >"$F/streaming_interval" + echo "$UVC_MAXPACKET" >"$F/streaming_maxpacket" + echo "$UVC_MAXBURST" >"$F/streaming_maxburst" + if [[ -n $UVC_BULK ]]; then + echo 1 >"$F/streaming_bulk" 2>/dev/null || true + fi # ── 1. FORMAT DESCRIPTOR (uncompressed YUY2, 16 bpp) ────────────── mkdir -p "$F/streaming/uncompressed/yuyv" @@ -153,14 +178,15 @@ if [[ -z $DISABLE_UVC ]]; then >"$F/streaming/uncompressed/yuyv/guidFormat" echo 16 >"$F/streaming/uncompressed/yuyv/bBitsPerPixel" -# ── 2. FRAME DESCRIPTOR (720p @ 30 fps) ─────────────────────────── - mkdir -p "$F/streaming/uncompressed/yuyv/720p" - echo 1280 >"$F/streaming/uncompressed/yuyv/720p/wWidth" - echo 720 >"$F/streaming/uncompressed/yuyv/720p/wHeight" - echo 1843200 >"$F/streaming/uncompressed/yuyv/720p/dwMaxVideoFrameBufferSize" - echo 333333 >"$F/streaming/uncompressed/yuyv/720p/dwDefaultFrameInterval" - cat <<'EOF' >"$F/streaming/uncompressed/yuyv/720p/dwFrameInterval" -333333 +# ── 2. FRAME DESCRIPTOR (480p @ 30 fps) ─────────────────────────── + mkdir -p "$F/streaming/uncompressed/yuyv/480p" + echo "$UVC_WIDTH" >"$F/streaming/uncompressed/yuyv/480p/wWidth" + echo "$UVC_HEIGHT" >"$F/streaming/uncompressed/yuyv/480p/wHeight" + echo "$UVC_FRAME_SIZE" >"$F/streaming/uncompressed/yuyv/480p/dwMaxVideoFrameBufferSize" + echo "$UVC_INTERVAL" >"$F/streaming/uncompressed/yuyv/480p/dwDefaultFrameInterval" + cat <"$F/streaming/uncompressed/yuyv/480p/dwFrameInterval" +${UVC_INTERVAL} +$((UVC_INTERVAL * 2)) EOF # ── 3. REQUIRED HEADER LINKS (per UVC gadget docs) ──────────────── @@ -187,6 +213,10 @@ EOF done set -e + if [[ -n $UVC_DISABLE_IRQ ]]; then + echo 0 >"$F/control/enable_interrupt_ep" 2>/dev/null || true + fi + # optional: hide unsupported controls echo 0 >"$F/control/terminal/camera/default/bmControls" 2>/dev/null || true echo 0 >"$F/control/processing/default/bmControls" 2>/dev/null || true diff --git a/server/src/bin/lesavka-uvc.rs b/server/src/bin/lesavka-uvc.rs index d4ce397..29585a4 100644 --- a/server/src/bin/lesavka-uvc.rs +++ b/server/src/bin/lesavka-uvc.rs @@ -229,11 +229,11 @@ fn parse_args() -> Result<(String, UvcConfig)> { impl UvcConfig { fn from_env() -> Self { - let width = env_u32("LESAVKA_UVC_WIDTH", 640); - let height = env_u32("LESAVKA_UVC_HEIGHT", 480); + let width = env_u32("LESAVKA_UVC_WIDTH", 1280); + let height = env_u32("LESAVKA_UVC_HEIGHT", 720); let fps = env_u32("LESAVKA_UVC_FPS", 30).max(1); let interval = env_u32("LESAVKA_UVC_INTERVAL", 0); - let mut max_packet = env_u32("LESAVKA_UVC_MAXPACKET", 3072); + let mut max_packet = env_u32("LESAVKA_UVC_MAXPACKET", 1024); if env::var("LESAVKA_UVC_BULK").is_ok() { max_packet = max_packet.min(512); } else { diff --git a/server/src/video.rs b/server/src/video.rs index 90e9cea..b059f44 100644 --- a/server/src/video.rs +++ b/server/src/video.rs @@ -16,6 +16,13 @@ const EYE_ID: [&str; 2] = ["l", "r"]; static START: std::sync::OnceLock = std::sync::OnceLock::new(); static DEV_MODE: std::sync::OnceLock = std::sync::OnceLock::new(); +fn env_u32(name: &str, default: u32) -> u32 { + std::env::var(name) + .ok() + .and_then(|v| v.parse::().ok()) + .unwrap_or(default) +} + fn dev_mode_enabled() -> bool { *DEV_MODE .get_or_init(|| std::env::var("LESAVKA_DEV_MODE").is_ok()) @@ -271,15 +278,19 @@ impl WebcamSink { let pipeline = gst::Pipeline::new(); + let width = env_u32("LESAVKA_UVC_WIDTH", 1280) as i32; + let height = env_u32("LESAVKA_UVC_HEIGHT", 720) as i32; + let fps = env_u32("LESAVKA_UVC_FPS", 30).max(1) as i32; + let caps_h264 = gst::Caps::builder("video/x-h264") .field("stream-format", "byte-stream") .field("alignment", "au") .build(); let raw_caps = gst::Caps::builder("video/x-raw") .field("format", "YUY2") - .field("width", 1280i32) - .field("height", 720i32) - .field("framerate", gst::Fraction::new(30, 1)) + .field("width", width) + .field("height", height) + .field("framerate", gst::Fraction::new(fps, 1)) .build(); let src = gst::ElementFactory::make("appsrc") @@ -297,6 +308,7 @@ impl WebcamSink { .build() .with_context(|| format!("building decoder element {decoder_name}"))?; let convert = gst::ElementFactory::make("videoconvert").build()?; + let scale = gst::ElementFactory::make("videoscale").build()?; let caps = gst::ElementFactory::make("capsfilter") .property("caps", &raw_caps) .build()?; @@ -311,6 +323,7 @@ impl WebcamSink { &h264parse, &decoder, &convert, + &scale, &caps, &sink, ])?; @@ -319,6 +332,7 @@ impl WebcamSink { &h264parse, &decoder, &convert, + &scale, &caps, &sink, ])?;