install mic deps

This commit is contained in:
Brad Stein 2025-12-01 16:21:39 -03:00
parent 103220a05a
commit 2e5f162d2c
2 changed files with 77 additions and 10 deletions

38
AGENTS.md Normal file
View File

@ -0,0 +1,38 @@
# Repository Guidelines
## Project Structure & Module Organization
Rust is split into three crates: `server/` (gRPC relay and device control with async tests in `server/tests`), `client/` (GTK + GStreamer desktop agent; `src/input/` and `src/output/` hold HID and media pipelines), and `common/` (shared protobuf schema in `common/proto/lesavka.proto`, compiled via `build.rs`). `scripts/install/*.sh` provision Arch-based hosts, `scripts/manual/*.sh` run hardware diagnostics, and `scripts/daemon/lesavka-core.sh` handles USB gadget bring-up.
## Build, Test, and Development Commands
- `cargo fmt --all` — run rustfmt across every crate before committing.
- `cargo clippy --workspace --all-targets -D warnings` — enforce lint cleanliness against desktop, server, and shared code.
- `cargo build --workspace --all-targets` — compile every binary/lib in debug mode; prefer `--release` for deployment artifacts referenced by install scripts.
- `cargo test --workspace` — executes unit tests plus async scenarios such as `server/tests/hid.rs`.
- `cargo run -p lesavka_server` / `cargo run -p lesavka_client` — start each side locally; set `LESAVKA_SERVER_ADDR` in the environment when pointing the client at a remote relay.
## Coding Style & Naming Conventions
Follow Rust 2024 idioms: four-space indentation, `snake_case` for modules/functions, and `CamelCase` for types and protobuf messages. Keep gRPC traits in `common` authoritative and prefer `tonic` streams over manual channels. Never hand-edit generated files in `target/` or the `OUT_DIR`; update `.proto` files and rebuild instead. Document hardware constants inline.
## Testing Guidelines
Async tests should use `#[tokio::test(flavor = "multi_thread")]` when they spawn background servers, mirroring `hid_roundtrip`. Give tests descriptive names and keep them near the code they exercise (`client/src/tests/`, `server/tests/`). Cover new RPCs end-to-end by instantiating the tonic server in-process and asserting on gRPC clients.
## Commit & Pull Request Guidelines
Recent history favors terse, imperative summaries (“usb fix”), so keep titles under ~50 characters and describe the subsystem. Commits should stay focused (proto update plus regeneration in one commit, client UI tweaks in another). Pull requests must include context, manual verification steps (`cargo test --workspace`, notable hardware checks), relevant logs or screenshots, and linked issues. Call out script or deployment changes so operators know when to re-run `scripts/install/server.sh` or update systemd units.
## Deployment Notes
For reproducible installs, prefer `scripts/install/server.sh --ref <branch>` to provision capture nodes (udev rules, GStreamer stack) and drop `lesavka-core.service`, while `scripts/install/client.sh` handles desktop prerequisites and systemd activation. When editing these scripts, test on an Arch VM. Keep secret material (VPN credentials, Tailscale auth) out of git and load them via environment variables or systemd drop-ins.
Install scripts must stay idempotent and self-contained: add any new runtime dependencies (e.g., audio/ALSA tools needed for mic bring-up) to the respective install script so a rerun on an arbitrary server/client brings the box to a ready state.
## Current Setup
- Server runs on Raspberry Pi 5 host `titan-jh` (ssh alias configured) and is already provisioned; client setup happens on this machine.
- HDMI capture uses two USB AVerMedia/GC311 devices with power/data split; AC relays on GPIO keep them off unless needed (control scripts already on the Pi).
- USB gadget exposes HID/UAC/UVC; webcam feed is expected from the client into the gadget UVC node (default `LESAVKA_UVC_DEV=/dev/video4`).
- Client ↔ server address: `http://38.28.125.112:50051` (was `64.25.10.31`).
- Webcam uplink is the remaining piece to confirm end-to-end after client install; server-side audio/video capture is in place.
## Session Notes (Dec 1, 2025)
- Server change pending deployment: `server/src/main.rs` now auto-picks UVC sink via `LESAVKA_UVC_DEV` or first `:video_output:` node (titan-jh gadget is `/dev/video0`); errors if none. `stream_microphone` honors `LESAVKA_UAC_DEV`.
- Client change: mic capture falls back to first non-`.monitor` Pulse source if `LESAVKA_MIC_SOURCE` missing/not found.
- Action: rerun `scripts/install/server.sh --ref feature/webcam-caps` on titan-jh (optionally set `LESAVKA_UVC_DEV=/dev/video0`, `LESAVKA_UAC_DEV=plughw:UAC2Gadget,0`), then restart service. Check `/tmp/lesavka-server.log` for “stream_camera using UVC sink”.
- Tethys display: SDDM running but greeter not; capture shows black+cursor. Need greeter/desktop on HDMI head (GC311 input) once display/cable is correct; investigate sddm greeter crash after reboot.

View File

@ -3,7 +3,8 @@
use anyhow::Context;
use futures_util::Stream;
use gst::prelude::*;
use gst::{MessageView, log};
use gst::MessageView::*;
use gst::MessageView;
use gstreamer as gst;
use gstreamer_app as gst_app;
use lesavka_common::lesavka::VideoPacket;
@ -20,6 +21,33 @@ fn dev_mode_enabled() -> bool {
.get_or_init(|| std::env::var("LESAVKA_DEV_MODE").is_ok())
}
fn contains_idr(h264: &[u8]) -> bool {
// naive AnnexB scan for H.264 IDR (NAL type 5)
let mut i = 0;
while i + 4 < h264.len() {
// find start code 0x000001 or 0x00000001
if h264[i] == 0 && h264[i + 1] == 0 {
let offset = if h264[i + 2] == 1 {
3
} else if h264[i + 2] == 0 && h264[i + 3] == 1 {
4
} else {
i += 1;
continue;
};
let nal_idx = i + offset;
if nal_idx < h264.len() {
let nal = h264[nal_idx] & 0x1F;
if nal == 5 {
return true;
}
}
}
i += 1;
}
false
}
pub struct VideoStream {
_pipeline: gst::Pipeline,
inner: ReceiverStream<Result<VideoPacket, Status>>,
@ -144,7 +172,7 @@ pub async fn eye_ball(dev: &str, id: u32, _max_bitrate_kbit: u32) -> anyhow::Res
/* -------- basic counters ------ */
static FRAME: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
let n = FRAME.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if n % 120 == 0 {
if n % 120 == 0 && contains_idr(map.as_slice()) {
trace!(target: "lesavka_server::video", "eye-{eye}: delivered {n} frames");
if enabled!(Level::TRACE) {
let path = format!("/tmp/eye-{eye}-srv-{:05}.h264", n);
@ -361,14 +389,15 @@ impl CameraRelay {
"📸📥 srv pkt");
}
if dev_mode_enabled() && (cfg!(debug_assertions) || tracing::enabled!(tracing::Level::TRACE)) {
if n % 120 == 0 {
let path = format!("/tmp/eye3-cli-{n:05}.h264");
if let Err(e) = std::fs::write(&path, &pkt.data) {
tracing::warn!("📸💾 dump failed: {e}");
} else {
tracing::debug!("📸💾 wrote {}", path);
}
if dev_mode_enabled()
&& (cfg!(debug_assertions) || tracing::enabled!(tracing::Level::TRACE))
&& contains_idr(&pkt.data)
{
let path = format!("/tmp/eye3-cli-{n:05}.h264");
if let Err(e) = std::fs::write(&path, &pkt.data) {
tracing::warn!("📸💾 dump failed: {e}");
} else {
tracing::debug!("📸💾 wrote {}", path);
}
}