From 6e107bb136cc4001834a24daf0a074ac12c72ebe Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Sun, 29 Jun 2025 17:24:19 -0500 Subject: [PATCH] AV Desc fix --- client/src/output/video.rs | 13 +++++-- scripts/install/server.sh | 2 + server/src/audio.rs | 78 +++++++++++++++++++++----------------- server/src/main.rs | 17 +++++---- server/src/video.rs | 30 ++++++++++++--- 5 files changed, 88 insertions(+), 52 deletions(-) diff --git a/client/src/output/video.rs b/client/src/output/video.rs index 8ade65c..589da49 100644 --- a/client/src/output/video.rs +++ b/client/src/output/video.rs @@ -6,6 +6,7 @@ use gstreamer_app as gst_app; use gst::prelude::*; use lesavka_common::lesavka::VideoPacket; use tracing::{error, info, warn, debug}; +use gstreamer_video::glib::Type; /* ---------- pipeline ---------------------------------------------------- * ┌────────────┐ H.264/AU ┌─────────┐ Decoded ┌─────────────┐ @@ -39,9 +40,14 @@ impl MonitorWindow { if let Some(sink) = pipeline.by_name("sink") { // glimagesink: title / fullscreen / force‑aspect‑ratio let title = format!("Lesavka‑eye‑{_id}"); - let _ = sink.set_property("title", &title); // only if supported - if fullscreen { - let _ = sink.set_property("fullscreen", &true); // ditto + if sink.has_property("title", None::) { + sink.set_property("title", &title); + } + if sink.has_property("window-title", None::) { + sink.set_property("window-title", &title); + } + if sink.has_property("fullscreen", None::) && fullscreen { + let _ = sink.set_property("fullscreen", &true); } let _ = sink.set_property("force-aspect-ratio", &true); } @@ -61,7 +67,6 @@ impl MonitorWindow { pipeline.set_state(gst::State::Playing)?; if let Some(sink) = pipeline.by_name("sink") { let title = format!("Lesavka‑eye‑{_id}"); - sink.set_property_from_str("window-title", &title); sink.set_property("force-aspect-ratio", &true); } diff --git a/scripts/install/server.sh b/scripts/install/server.sh index 90cb9dd..f9d6058 100755 --- a/scripts/install/server.sh +++ b/scripts/install/server.sh @@ -107,6 +107,8 @@ ExecStart=/usr/local/bin/lesavka-server Restart=always Environment=RUST_LOG=lesavka_server=trace,lesavka_server::audio=trace,lesavka_server::video=trace,lesavka_server::usb_gadget=info Environment=RUST_BACKTRACE=1 +Environment=GST_DEBUG=3,v4l2src:6,tsdemux:6,parsebin:5 +Environment=GST_DEBUG_DUMP_DOT_DIR=/tmp Restart=always RestartSec=5 StandardError=append:/tmp/lesavka-server.stderr diff --git a/server/src/audio.rs b/server/src/audio.rs index f707177..8aadad7 100644 --- a/server/src/audio.rs +++ b/server/src/audio.rs @@ -26,41 +26,51 @@ impl Stream for AudioStream { } } -pub async fn eye_ear( - dev: &str, - id: u32, -) -> anyhow::Result { - let ear = EAR_ID[id as usize]; - gst::init().context("gst init")?; - let desc = format!( - "v4l2src device=\"{dev}\" io-mode=mmap do-timestamp=true ! queue ! \ - tsdemux name=d d.audio_0 ! queue ! \ - aacparse ! queue ! \ - appsink name=asink emit-signals=true max-buffers=64 drop=true" - ); - let pipe: gst::Pipeline = gst::parse::launch(&desc)? - .downcast() - .expect("pipeline"); +// pub async fn eye_ear( +// dev: &str, +// id: u32, +// ) -> anyhow::Result { +// let ear = EAR_ID[id as usize]; +// gst::init().context("gst init")?; +// let desc = format!( +// "v4l2src device=\"{dev}\" io-mode=mmap do-timestamp=true ! \ +// video/mpegts,systemstream=true,packetsize=188 ! \ +// tsdemux name=d d.audio_0 ! queue ! \ +// aacparse ! queue ! \ +// appsink name=asink emit-signals=true max-buffers=64 drop=true" +// ); +// let pipe: gst::Pipeline = gst::parse::launch(&desc)? +// .downcast() +// .expect("pipeline"); - let sink = pipe.by_name("asink").expect("asink").downcast::().unwrap(); - let (tx, rx) = tokio::sync::mpsc::channel(8192); +// let sink = pipe.by_name("asink").expect("asink").downcast::().unwrap(); +// let (tx, rx) = tokio::sync::mpsc::channel(8192); - sink.set_callbacks( - gst_app::AppSinkCallbacks::builder() - .new_sample(move |s| { - let samp = s.pull_sample().map_err(|_| gst::FlowError::Eos)?; - let buf = samp.buffer().ok_or(gst::FlowError::Error)?; - let map = buf.map_readable().map_err(|_| gst::FlowError::Error)?; - let pts = buf.pts().unwrap_or(gst::ClockTime::ZERO).nseconds()/1_000; - let pkt = AudioPacket { id, pts, data: map.as_slice().to_vec() }; - debug!(target:"lesavka_server::audio", - eye = id, bytes = pkt.data.len(), pts = pkt.pts, - "⬆️ pushed audio sample ear-{ear}"); - let _ = tx.try_send(Ok(pkt)); - Ok(gst::FlowSuccess::Ok) - }).build() - ); +// sink.set_callbacks( +// gst_app::AppSinkCallbacks::builder() +// .new_sample(move |s| { +// let samp = s.pull_sample().map_err(|_| gst::FlowError::Eos)?; +// let buf = samp.buffer().ok_or(gst::FlowError::Error)?; +// let map = buf.map_readable().map_err(|_| gst::FlowError::Error)?; +// let pts = buf.pts().unwrap_or(gst::ClockTime::ZERO).nseconds()/1_000; +// let pkt = AudioPacket { id, pts, data: map.as_slice().to_vec() }; +// debug!(target:"lesavka_server::audio", +// eye = id, bytes = pkt.data.len(), pts = pkt.pts, +// "⬆️ pushed audio sample ear-{ear}"); +// let _ = tx.try_send(Ok(pkt)); +// Ok(gst::FlowSuccess::Ok) +// }).build() +// ); - pipe.set_state(gst::State::Playing)?; - Ok(AudioStream{ _pipe: pipe, inner: ReceiverStream::new(rx) }) +// pipe.set_state(gst::State::Playing)?; +// Ok(AudioStream{ _pipe: pipe, inner: ReceiverStream::new(rx) }) +// } + +pub async fn eye_ear(_dev: &str, _id: u32) -> anyhow::Result { + use tokio_stream::StreamExt; + let (_tx, rx) = tokio::sync::mpsc::channel(1); + Ok(AudioStream { + _pipe: gst::Pipeline::new(), + inner: ReceiverStream::new(rx), // always empty + }) } diff --git a/server/src/main.rs b/server/src/main.rs index 65da0c2..eebc1f1 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -186,14 +186,15 @@ impl Relay for Handler { &self, req: Request, ) -> Result, Status> { - let id = req.into_inner().id; - let dev = match id { 0 => "/dev/lesavka_l_eye", - 1 => "/dev/lesavka_r_eye", - _ => return Err(Status::invalid_argument("monitor id must be 0 or 1")) }; - let s = audio::eye_ear(dev, id) - .await - .map_err(|e| Status::internal(format!("{e:#}")))?; - Ok(Response::new(Box::pin(s))) + // let id = req.into_inner().id; + // let dev = match id { 0 => "/dev/lesavka_l_eye", + // 1 => "/dev/lesavka_r_eye", + // _ => return Err(Status::invalid_argument("monitor id must be 0 or 1")) }; + // let s = audio::eye_ear(dev, id) + // .await + // .map_err(|e| Status::internal(format!("{e:#}")))?; + // Ok(Response::new(Box::pin(s))) + Err(Status::unimplemented("GC311 has no embedded audio")) } /*────────────── USB-reset RPC ───────────*/ diff --git a/server/src/video.rs b/server/src/video.rs index 45499d1..1f1248f 100644 --- a/server/src/video.rs +++ b/server/src/video.rs @@ -45,8 +45,10 @@ pub async fn eye_ball( gst::init().context("gst init")?; let desc = format!( - "v4l2src device=\"{dev}\" io-mode=mmap do-timestamp=true ! queue ! \ - tsdemux name=d d.video_0 ! queue ! \ + // Elementary H‑264 stream coming straight from the UVC device –‑ + // no MPEG‑TS container, so no tsdemux. + "v4l2src device=\"{dev}\" io-mode=mmap do-timestamp=true ! \ + queue ! \ h264parse disable-passthrough=true config-interval=-1 ! \ video/x-h264,stream-format=byte-stream,alignment=au ! \ appsink name=sink emit-signals=true max-buffers=32 drop=true" @@ -78,31 +80,47 @@ pub async fn eye_ball( /* ----- BUS WATCH: show errors & warnings immediately --------------- */ let bus = pipeline.bus().expect("bus"); + let src_pad = pipeline + .by_name("v4l2src1") // adapt: eye‑specific element name + .unwrap() + .static_pad("src") + .unwrap(); + + src_pad.add_probe(gst::PadProbeType::EVENT_DOWNSTREAM, |pad, info| { + if let Some(gst::PadProbeData::Event(ref ev)) = info.data { + if let gst::EventView::Caps(c) = ev.view() { + trace!(target:"lesavka_server::video", + ?c, "🔍 new caps on {}", pad.name()); + } + } + gst::PadProbeReturn::Ok + }); + let eye_clone = eye.to_owned(); std::thread::spawn(move || { for msg in bus.iter_timed(gst::ClockTime::NONE) { use gst::MessageView::*; match msg.view() { Error(err) => { - tracing::error!(target:"lesavka_server::video", + error!(target:"lesavka_server::video", eye = %eye_clone, "💥 pipeline error: {} ({})", err.error(), err.debug().unwrap_or_default()); } Warning(w) => { - tracing::warn!(target:"lesavka_server::video", + warn!(target:"lesavka_server::video", eye = %eye_clone, "⚠️ pipeline warning: {} ({})", w.error(), w.debug().unwrap_or_default()); } Info(i) => { - tracing::info!(target:"lesavka_server::video", + info!(target:"lesavka_server::video", eye = %eye_clone, "📌 pipeline info: {} ({})", i.error(), i.debug().unwrap_or_default()); } StateChanged(s) if s.current() == gst::State::Playing => { - tracing::info!(target:"lesavka_server::video", + info!(target:"lesavka_server::video", eye = %eye_clone, "🎬 pipeline PLAYING"); }