Compare commits
37 Commits
feature/we
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f629facca | |||
| 7844ec9870 | |||
| 17b70bec5e | |||
| 0615992d9b | |||
| 29933a685e | |||
| 7222020631 | |||
| f04fc1c02a | |||
| a1d6728c59 | |||
| 29c51fe694 | |||
| 28f564c8d6 | |||
| 3aca228498 | |||
| 927a776d13 | |||
| 6b1a7d1963 | |||
| 394b842e49 | |||
| cd56d6f1be | |||
| 21d4057a6d | |||
| 70be697f8c | |||
| 5bd4b6155d | |||
| 5b272448a6 | |||
| b6aae91a52 | |||
| a2d07ef1e5 | |||
| fc92d0321b | |||
| e7bff41c0c | |||
| 120424925b | |||
| 2088cdddec | |||
| ca3c8168e4 | |||
| 4b1eca6c0d | |||
| d167ed8511 | |||
| 6934bf8645 | |||
| 07dbe15d9f | |||
| 8b2336e753 | |||
| 590344df41 | |||
| 46c5190fb0 | |||
| 23c25297bc | |||
| 41a64664e0 | |||
| 86b6e79e33 | |||
| 34030aa8b6 |
@ -26,16 +26,23 @@ impl CameraCapture {
|
||||
// (NVIDIA → VA-API → software x264).
|
||||
let (enc, kf_prop, kf_val) = Self::choose_encoder();
|
||||
tracing::info!("📸 using encoder element: {enc}");
|
||||
let have_nvvidconv = gst::ElementFactory::find("nvvidconv").is_some();
|
||||
let (src_caps, preenc) = match enc {
|
||||
"nvh264enc" => (
|
||||
"video/x-raw(memory:NVMM),format=NV12,width=1280,height=720",
|
||||
"nvvidconv !"
|
||||
),
|
||||
"vaapih264enc" => (
|
||||
"video/x-raw,format=NV12,width=1280,height=720",
|
||||
"videoconvert !"
|
||||
),
|
||||
_ => ("video/x-raw,width=1280,height=720", "videoconvert !"),
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// Jetson (has nvvidconv) Desktop (falls back to videoconvert)
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
"nvh264enc" if have_nvvidconv =>
|
||||
("video/x-raw(memory:NVMM),format=NV12,width=1280,height=720",
|
||||
"nvvidconv !"),
|
||||
"nvh264enc" /* else */ =>
|
||||
("video/x-raw,format=NV12,width=1280,height=720",
|
||||
"videoconvert !"),
|
||||
"vaapih264enc" =>
|
||||
("video/x-raw,format=NV12,width=1280,height=720",
|
||||
"videoconvert !"),
|
||||
_ =>
|
||||
("video/x-raw,width=1280,height=720",
|
||||
"videoconvert !"),
|
||||
};
|
||||
|
||||
// let desc = format!(
|
||||
|
||||
@ -6,6 +6,11 @@
|
||||
set -euo pipefail
|
||||
|
||||
log() { printf '[lesavka-core] %s\n' "$*"; }
|
||||
cleanup() { echo "" >"$G/UDC" 2>/dev/null || true; }
|
||||
|
||||
exec 2> >(tee -a /tmp/lesavka-core.debug.$(date +%s).log)
|
||||
set -x
|
||||
echo "[lesavka-core] running: $0 (sha1sum=$(sha1sum "$0" | cut -d' ' -f1))"
|
||||
|
||||
#──────────────────────────────────────────────────
|
||||
# 1. Ensure overlay + kernel modules
|
||||
@ -114,10 +119,86 @@ echo 32 >"$U/req_number" 2>/dev/null || true
|
||||
|
||||
# ----------------------- UVC function (usb‑video) ------------------
|
||||
mkdir -p "$G/functions/uvc.usb0"
|
||||
mkdir -p "$G/functions/uvc.usb0/control/strings/0x409"
|
||||
echo "Lesavka UVC" >"$G/functions/uvc.usb0/control/strings/0x409/label"
|
||||
# Simple 720p MJPEG + 720p H.264 alt‑setting
|
||||
printf '\x50\x00\x00\x00' >"$G/functions/uvc.usb0/control/header/h_video"
|
||||
F="$G/functions/uvc.usb0"
|
||||
|
||||
# ── 1. FORMAT DESCRIPTOR (uncompressed YUY2, 16 bpp) ──────────────
|
||||
mkdir -p "$F/streaming/uncompressed/u"
|
||||
# GUID = {59555932-0000-0010-8000-00aa00389b71} (“YUY2”) little‑endian
|
||||
printf '\x59\x55\x59\x32\x00\x00\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71' \
|
||||
>"$F/streaming/uncompressed/u/guidFormat"
|
||||
echo 16 >"$F/streaming/uncompressed/u/bBitsPerPixel"
|
||||
|
||||
# ── 2. FRAME DESCRIPTOR (index 1 @ 30 fps) ────────────────────────
|
||||
mkdir -p "$F/streaming/uncompressed/u/f1"
|
||||
echo 1280 >"$F/streaming/uncompressed/u/f1/wWidth"
|
||||
echo 720 >"$F/streaming/uncompressed/u/f1/wHeight"
|
||||
echo 1843200 >"$F/streaming/uncompressed/u/f1/dwMaxVideoFrameBufferSize"
|
||||
echo 333333 >"$F/streaming/uncompressed/u/f1/dwDefaultFrameInterval" # 30 fps
|
||||
echo 333333 >"$F/streaming/uncompressed/u/f1/dwFrameInterval"
|
||||
|
||||
# ── 3. REQUIRED HEADER LINKS (absolute‑paths, no “1”) ──────────────
|
||||
header_h="$F/streaming/header/h" # convenience variables
|
||||
fmt_dir="$F/streaming/uncompressed/u"
|
||||
|
||||
mkdir -p "$header_h"
|
||||
|
||||
# wait until the kernel has rebuilt the directory and added its attribute
|
||||
# files (bmInfo is always created by the driver)
|
||||
for _ in {1..50}; do
|
||||
[ -e "$header_h/bmInfo" ] && break
|
||||
sleep 0.010 # max 0.5 s total
|
||||
done
|
||||
|
||||
# ABSOLUTE symlink → no relative elements, name is “fmt” (not “1”)
|
||||
ln -sf "$fmt_dir" "$header_h/fmt"
|
||||
# echo 1 >"$header_h/bNumFormats"
|
||||
# echo 0 >"$header_h/bmInfo"
|
||||
|
||||
# per‑speed class directories (absolute links)
|
||||
for s in fs hs ss; do
|
||||
mkdir -p "$F/streaming/class/$s"
|
||||
ln -sf "$header_h" "$F/streaming/class/$s/h"
|
||||
done
|
||||
|
||||
# ── 4. Video‑Control interface ─────────────────────────────────────
|
||||
mkdir -p "$F/control/header/h" # real dir – mandatory
|
||||
mkdir -p "$F/control/class" # parent once
|
||||
|
||||
echo "[lesavka-core] ★ directory tree just before links:"
|
||||
tree -L 3 "$F/control" | sed 's/^/[lesavka-core] /'
|
||||
|
||||
for s in fs hs ss; do
|
||||
# ensure the per‑speed dir exists (created by kernel)
|
||||
mkdir -p "$F/control/class/$s" # harmless if already there
|
||||
|
||||
# create the mandatory *symlink inside* that directory:
|
||||
ln -snf ../../header/h "$F/control/class/$s/h"
|
||||
done
|
||||
|
||||
for s in fs hs ss; do
|
||||
[ -L "$F/control/class/$s/h" ] || {
|
||||
echo "[lesavka‑core] ❌ $s/h link missing, aborting" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
echo "[lesavka-core] ★ directory tree just before bind:"
|
||||
tree -L 3 "$F/control" | sed 's/^/[lesavka-core] /'
|
||||
|
||||
for s in fs hs ss; do
|
||||
[ -L "$F/control/class/$s" ] || {
|
||||
echo "[lesavka-core] ❌ $s link missing, gadget aborting" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
# 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
|
||||
|
||||
# friendly label
|
||||
mkdir -p "$F/control/header/strings/0x409"
|
||||
echo "Lesavka UVC" >"$F/control/header/strings/0x409/label"
|
||||
|
||||
# ----------------------- configuration -----------------------------
|
||||
mkdir -p "$G/configs/c.1/strings/0x409"
|
||||
|
||||
@ -3,6 +3,18 @@
|
||||
set -euo pipefail
|
||||
ORIG_USER=${SUDO_USER:-$(id -un)}
|
||||
|
||||
REF=${LESAVKA_REF:-master} # fallback
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-r|--ref) REF="$2"; shift 2 ;;
|
||||
-h|--help)
|
||||
echo "Usage: $0 [--ref <branch|commit>]"; exit 0 ;;
|
||||
*) echo "Unknown option: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
echo "==> Using git ref: $REF"
|
||||
|
||||
echo "==> 1a. Base packages"
|
||||
sudo pacman -Syq --needed --noconfirm git \
|
||||
rustup \
|
||||
@ -83,11 +95,17 @@ if [[ ! -d $SRC_DIR ]]; then
|
||||
sudo chown "$ORIG_USER":"$ORIG_USER" /var/src
|
||||
fi
|
||||
if [[ -d $SRC_DIR/.git ]]; then
|
||||
sudo -u "$ORIG_USER" git -C "$SRC_DIR" pull --ff-only
|
||||
sudo -u "$ORIG_USER" git -C "$SRC_DIR" fetch --all --tags --prune
|
||||
else
|
||||
sudo -u "$ORIG_USER" git clone "$REPO_URL" "$SRC_DIR"
|
||||
fi
|
||||
|
||||
if sudo -u "$ORIG_USER" git -C "$SRC_DIR" rev-parse --verify --quiet "origin/$REF" >/dev/null; then
|
||||
sudo -u "$ORIG_USER" git -C "$SRC_DIR" checkout -B "$REF" "origin/$REF"
|
||||
else
|
||||
sudo -u "$ORIG_USER" git -C "$SRC_DIR" checkout --force "$REF"
|
||||
fi
|
||||
|
||||
echo "==> 4b. Source build"
|
||||
sudo -u "$ORIG_USER" bash -c "cd '$SRC_DIR/server' && cargo clean && cargo build --release"
|
||||
|
||||
@ -122,7 +140,7 @@ After=network.target lesavka-core.service
|
||||
[Service]
|
||||
ExecStart=/usr/local/bin/lesavka-server
|
||||
Restart=always
|
||||
Environment=RUST_LOG=lesavka_server=info,lesavka_server::audio=trace,lesavka_server::video=trace,lesavka_server::gadget=info
|
||||
Environment=RUST_LOG=lesavka_server=info,lesavka_server::audio=info,lesavka_server::video=debug,lesavka_server::gadget=info
|
||||
Environment=RUST_BACKTRACE=1
|
||||
Environment=GST_DEBUG="*:2,alsasink:6,alsasrc:6"
|
||||
Restart=always
|
||||
|
||||
@ -124,6 +124,14 @@ impl Handler {
|
||||
did_cycle: AtomicBool::new(false),
|
||||
})
|
||||
}
|
||||
|
||||
async fn reopen_hid(&self) -> anyhow::Result<()> {
|
||||
let kb_new = open_with_retry("/dev/hidg0").await?;
|
||||
let ms_new = open_with_retry("/dev/hidg1").await?;
|
||||
*self.kb.lock().await = kb_new;
|
||||
*self.ms.lock().await = ms_new;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/*──────────────── gRPC service ─────────────*/
|
||||
@ -280,7 +288,13 @@ impl Relay for Handler {
|
||||
) -> Result<Response<ResetUsbReply>, Status> {
|
||||
info!("🔴 explicit ResetUsb() called");
|
||||
match self.gadget.cycle() {
|
||||
Ok(_) => Ok(Response::new(ResetUsbReply { ok: true })),
|
||||
Ok(_) => {
|
||||
if let Err(e) = self.reopen_hid().await {
|
||||
error!("💥 reopen HID failed: {e:#}");
|
||||
return Err(Status::internal(e.to_string()));
|
||||
}
|
||||
Ok(Response::new(ResetUsbReply { ok: true }))
|
||||
}
|
||||
Err(e) => {
|
||||
error!("💥 cycle failed: {e:#}");
|
||||
Err(Status::internal(e.to_string()))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user