Compare commits
No commits in common. "master" and "feature/webcam" have entirely different histories.
master
...
feature/we
@ -26,24 +26,17 @@ impl CameraCapture {
|
|||||||
// (NVIDIA → VA-API → software x264).
|
// (NVIDIA → VA-API → software x264).
|
||||||
let (enc, kf_prop, kf_val) = Self::choose_encoder();
|
let (enc, kf_prop, kf_val) = Self::choose_encoder();
|
||||||
tracing::info!("📸 using encoder element: {enc}");
|
tracing::info!("📸 using encoder element: {enc}");
|
||||||
let have_nvvidconv = gst::ElementFactory::find("nvvidconv").is_some();
|
|
||||||
let (src_caps, preenc) = match enc {
|
let (src_caps, preenc) = match enc {
|
||||||
// ───────────────────────────────────────────────────────────────────
|
"nvh264enc" => (
|
||||||
// Jetson (has nvvidconv) Desktop (falls back to videoconvert)
|
"video/x-raw(memory:NVMM),format=NV12,width=1280,height=720",
|
||||||
// ───────────────────────────────────────────────────────────────────
|
"nvvidconv !"
|
||||||
"nvh264enc" if have_nvvidconv =>
|
),
|
||||||
("video/x-raw(memory:NVMM),format=NV12,width=1280,height=720",
|
"vaapih264enc" => (
|
||||||
"nvvidconv !"),
|
"video/x-raw,format=NV12,width=1280,height=720",
|
||||||
"nvh264enc" /* else */ =>
|
"videoconvert !"
|
||||||
("video/x-raw,format=NV12,width=1280,height=720",
|
),
|
||||||
"videoconvert !"),
|
_ => ("video/x-raw,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!(
|
// let desc = format!(
|
||||||
// "v4l2src device={dev} do-timestamp=true ! {raw_caps},width=1280,height=720 ! \
|
// "v4l2src device={dev} do-timestamp=true ! {raw_caps},width=1280,height=720 ! \
|
||||||
|
|||||||
@ -6,11 +6,6 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
log() { printf '[lesavka-core] %s\n' "$*"; }
|
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
|
# 1. Ensure overlay + kernel modules
|
||||||
@ -119,86 +114,10 @@ echo 32 >"$U/req_number" 2>/dev/null || true
|
|||||||
|
|
||||||
# ----------------------- UVC function (usb‑video) ------------------
|
# ----------------------- UVC function (usb‑video) ------------------
|
||||||
mkdir -p "$G/functions/uvc.usb0"
|
mkdir -p "$G/functions/uvc.usb0"
|
||||||
F="$G/functions/uvc.usb0"
|
mkdir -p "$G/functions/uvc.usb0/control/strings/0x409"
|
||||||
|
echo "Lesavka UVC" >"$G/functions/uvc.usb0/control/strings/0x409/label"
|
||||||
# ── 1. FORMAT DESCRIPTOR (uncompressed YUY2, 16 bpp) ──────────────
|
# Simple 720p MJPEG + 720p H.264 alt‑setting
|
||||||
mkdir -p "$F/streaming/uncompressed/u"
|
printf '\x50\x00\x00\x00' >"$G/functions/uvc.usb0/control/header/h_video"
|
||||||
# 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 -----------------------------
|
# ----------------------- configuration -----------------------------
|
||||||
mkdir -p "$G/configs/c.1/strings/0x409"
|
mkdir -p "$G/configs/c.1/strings/0x409"
|
||||||
|
|||||||
@ -3,18 +3,6 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ORIG_USER=${SUDO_USER:-$(id -un)}
|
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"
|
echo "==> 1a. Base packages"
|
||||||
sudo pacman -Syq --needed --noconfirm git \
|
sudo pacman -Syq --needed --noconfirm git \
|
||||||
rustup \
|
rustup \
|
||||||
@ -95,17 +83,11 @@ if [[ ! -d $SRC_DIR ]]; then
|
|||||||
sudo chown "$ORIG_USER":"$ORIG_USER" /var/src
|
sudo chown "$ORIG_USER":"$ORIG_USER" /var/src
|
||||||
fi
|
fi
|
||||||
if [[ -d $SRC_DIR/.git ]]; then
|
if [[ -d $SRC_DIR/.git ]]; then
|
||||||
sudo -u "$ORIG_USER" git -C "$SRC_DIR" fetch --all --tags --prune
|
sudo -u "$ORIG_USER" git -C "$SRC_DIR" pull --ff-only
|
||||||
else
|
else
|
||||||
sudo -u "$ORIG_USER" git clone "$REPO_URL" "$SRC_DIR"
|
sudo -u "$ORIG_USER" git clone "$REPO_URL" "$SRC_DIR"
|
||||||
fi
|
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"
|
echo "==> 4b. Source build"
|
||||||
sudo -u "$ORIG_USER" bash -c "cd '$SRC_DIR/server' && cargo clean && cargo build --release"
|
sudo -u "$ORIG_USER" bash -c "cd '$SRC_DIR/server' && cargo clean && cargo build --release"
|
||||||
|
|
||||||
@ -140,7 +122,7 @@ After=network.target lesavka-core.service
|
|||||||
[Service]
|
[Service]
|
||||||
ExecStart=/usr/local/bin/lesavka-server
|
ExecStart=/usr/local/bin/lesavka-server
|
||||||
Restart=always
|
Restart=always
|
||||||
Environment=RUST_LOG=lesavka_server=info,lesavka_server::audio=info,lesavka_server::video=debug,lesavka_server::gadget=info
|
Environment=RUST_LOG=lesavka_server=info,lesavka_server::audio=trace,lesavka_server::video=trace,lesavka_server::gadget=info
|
||||||
Environment=RUST_BACKTRACE=1
|
Environment=RUST_BACKTRACE=1
|
||||||
Environment=GST_DEBUG="*:2,alsasink:6,alsasrc:6"
|
Environment=GST_DEBUG="*:2,alsasink:6,alsasrc:6"
|
||||||
Restart=always
|
Restart=always
|
||||||
|
|||||||
@ -124,14 +124,6 @@ impl Handler {
|
|||||||
did_cycle: AtomicBool::new(false),
|
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 ─────────────*/
|
/*──────────────── gRPC service ─────────────*/
|
||||||
@ -288,13 +280,7 @@ impl Relay for Handler {
|
|||||||
) -> Result<Response<ResetUsbReply>, Status> {
|
) -> Result<Response<ResetUsbReply>, Status> {
|
||||||
info!("🔴 explicit ResetUsb() called");
|
info!("🔴 explicit ResetUsb() called");
|
||||||
match self.gadget.cycle() {
|
match self.gadget.cycle() {
|
||||||
Ok(_) => {
|
Ok(_) => Ok(Response::new(ResetUsbReply { ok: true })),
|
||||||
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) => {
|
Err(e) => {
|
||||||
error!("💥 cycle failed: {e:#}");
|
error!("💥 cycle failed: {e:#}");
|
||||||
Err(Status::internal(e.to_string()))
|
Err(Status::internal(e.to_string()))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user