AV Desc fix

This commit is contained in:
Brad Stein 2025-06-29 17:24:19 -05:00
parent 14bad67a04
commit 6e107bb136
5 changed files with 88 additions and 52 deletions

View File

@ -6,6 +6,7 @@ use gstreamer_app as gst_app;
use gst::prelude::*; use gst::prelude::*;
use lesavka_common::lesavka::VideoPacket; use lesavka_common::lesavka::VideoPacket;
use tracing::{error, info, warn, debug}; use tracing::{error, info, warn, debug};
use gstreamer_video::glib::Type;
/* ---------- pipeline ---------------------------------------------------- /* ---------- pipeline ----------------------------------------------------
* H.264/AU Decoded * H.264/AU Decoded
@ -39,9 +40,14 @@ impl MonitorWindow {
if let Some(sink) = pipeline.by_name("sink") { if let Some(sink) = pipeline.by_name("sink") {
// glimagesink: title / fullscreen / forceaspectratio // glimagesink: title / fullscreen / forceaspectratio
let title = format!("Lesavkaeye{_id}"); let title = format!("Lesavkaeye{_id}");
let _ = sink.set_property("title", &title); // only if supported if sink.has_property("title", None::<Type>) {
if fullscreen { sink.set_property("title", &title);
let _ = sink.set_property("fullscreen", &true); // ditto }
if sink.has_property("window-title", None::<Type>) {
sink.set_property("window-title", &title);
}
if sink.has_property("fullscreen", None::<Type>) && fullscreen {
let _ = sink.set_property("fullscreen", &true);
} }
let _ = sink.set_property("force-aspect-ratio", &true); let _ = sink.set_property("force-aspect-ratio", &true);
} }
@ -61,7 +67,6 @@ impl MonitorWindow {
pipeline.set_state(gst::State::Playing)?; pipeline.set_state(gst::State::Playing)?;
if let Some(sink) = pipeline.by_name("sink") { if let Some(sink) = pipeline.by_name("sink") {
let title = format!("Lesavkaeye{_id}"); let title = format!("Lesavkaeye{_id}");
sink.set_property_from_str("window-title", &title);
sink.set_property("force-aspect-ratio", &true); sink.set_property("force-aspect-ratio", &true);
} }

View File

@ -107,6 +107,8 @@ ExecStart=/usr/local/bin/lesavka-server
Restart=always Restart=always
Environment=RUST_LOG=lesavka_server=trace,lesavka_server::audio=trace,lesavka_server::video=trace,lesavka_server::usb_gadget=info Environment=RUST_LOG=lesavka_server=trace,lesavka_server::audio=trace,lesavka_server::video=trace,lesavka_server::usb_gadget=info
Environment=RUST_BACKTRACE=1 Environment=RUST_BACKTRACE=1
Environment=GST_DEBUG=3,v4l2src:6,tsdemux:6,parsebin:5
Environment=GST_DEBUG_DUMP_DOT_DIR=/tmp
Restart=always Restart=always
RestartSec=5 RestartSec=5
StandardError=append:/tmp/lesavka-server.stderr StandardError=append:/tmp/lesavka-server.stderr

View File

@ -26,41 +26,51 @@ impl Stream for AudioStream {
} }
} }
pub async fn eye_ear( // pub async fn eye_ear(
dev: &str, // dev: &str,
id: u32, // id: u32,
) -> anyhow::Result<AudioStream> { // ) -> anyhow::Result<AudioStream> {
let ear = EAR_ID[id as usize]; // let ear = EAR_ID[id as usize];
gst::init().context("gst init")?; // gst::init().context("gst init")?;
let desc = format!( // let desc = format!(
"v4l2src device=\"{dev}\" io-mode=mmap do-timestamp=true ! queue ! \ // "v4l2src device=\"{dev}\" io-mode=mmap do-timestamp=true ! \
tsdemux name=d d.audio_0 ! queue ! \ // video/mpegts,systemstream=true,packetsize=188 ! \
aacparse ! queue ! \ // tsdemux name=d d.audio_0 ! queue ! \
appsink name=asink emit-signals=true max-buffers=64 drop=true" // aacparse ! queue ! \
); // appsink name=asink emit-signals=true max-buffers=64 drop=true"
let pipe: gst::Pipeline = gst::parse::launch(&desc)? // );
.downcast() // let pipe: gst::Pipeline = gst::parse::launch(&desc)?
.expect("pipeline"); // .downcast()
// .expect("pipeline");
let sink = pipe.by_name("asink").expect("asink").downcast::<gst_app::AppSink>().unwrap(); // let sink = pipe.by_name("asink").expect("asink").downcast::<gst_app::AppSink>().unwrap();
let (tx, rx) = tokio::sync::mpsc::channel(8192); // let (tx, rx) = tokio::sync::mpsc::channel(8192);
sink.set_callbacks( // sink.set_callbacks(
gst_app::AppSinkCallbacks::builder() // gst_app::AppSinkCallbacks::builder()
.new_sample(move |s| { // .new_sample(move |s| {
let samp = s.pull_sample().map_err(|_| gst::FlowError::Eos)?; // let samp = s.pull_sample().map_err(|_| gst::FlowError::Eos)?;
let buf = samp.buffer().ok_or(gst::FlowError::Error)?; // let buf = samp.buffer().ok_or(gst::FlowError::Error)?;
let map = buf.map_readable().map_err(|_| 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 pts = buf.pts().unwrap_or(gst::ClockTime::ZERO).nseconds()/1_000;
let pkt = AudioPacket { id, pts, data: map.as_slice().to_vec() }; // let pkt = AudioPacket { id, pts, data: map.as_slice().to_vec() };
debug!(target:"lesavka_server::audio", // debug!(target:"lesavka_server::audio",
eye = id, bytes = pkt.data.len(), pts = pkt.pts, // eye = id, bytes = pkt.data.len(), pts = pkt.pts,
"⬆️ pushed audio sample ear-{ear}"); // "⬆️ pushed audio sample ear-{ear}");
let _ = tx.try_send(Ok(pkt)); // let _ = tx.try_send(Ok(pkt));
Ok(gst::FlowSuccess::Ok) // Ok(gst::FlowSuccess::Ok)
}).build() // }).build()
); // );
pipe.set_state(gst::State::Playing)?; // pipe.set_state(gst::State::Playing)?;
Ok(AudioStream{ _pipe: pipe, inner: ReceiverStream::new(rx) }) // Ok(AudioStream{ _pipe: pipe, inner: ReceiverStream::new(rx) })
// }
pub async fn eye_ear(_dev: &str, _id: u32) -> anyhow::Result<AudioStream> {
use tokio_stream::StreamExt;
let (_tx, rx) = tokio::sync::mpsc::channel(1);
Ok(AudioStream {
_pipe: gst::Pipeline::new(),
inner: ReceiverStream::new(rx), // always empty
})
} }

View File

@ -186,14 +186,15 @@ impl Relay for Handler {
&self, &self,
req: Request<MonitorRequest>, req: Request<MonitorRequest>,
) -> Result<Response<Self::CaptureAudioStream>, Status> { ) -> Result<Response<Self::CaptureAudioStream>, Status> {
let id = req.into_inner().id; // let id = req.into_inner().id;
let dev = match id { 0 => "/dev/lesavka_l_eye", // let dev = match id { 0 => "/dev/lesavka_l_eye",
1 => "/dev/lesavka_r_eye", // 1 => "/dev/lesavka_r_eye",
_ => return Err(Status::invalid_argument("monitor id must be 0 or 1")) }; // _ => return Err(Status::invalid_argument("monitor id must be 0 or 1")) };
let s = audio::eye_ear(dev, id) // let s = audio::eye_ear(dev, id)
.await // .await
.map_err(|e| Status::internal(format!("{e:#}")))?; // .map_err(|e| Status::internal(format!("{e:#}")))?;
Ok(Response::new(Box::pin(s))) // Ok(Response::new(Box::pin(s)))
Err(Status::unimplemented("GC311 has no embedded audio"))
} }
/*────────────── USB-reset RPC ───────────*/ /*────────────── USB-reset RPC ───────────*/

View File

@ -45,8 +45,10 @@ pub async fn eye_ball(
gst::init().context("gst init")?; gst::init().context("gst init")?;
let desc = format!( let desc = format!(
"v4l2src device=\"{dev}\" io-mode=mmap do-timestamp=true ! queue ! \ // Elementary H264 stream coming straight from the UVC device
tsdemux name=d d.video_0 ! queue ! \ // no MPEGTS container, so no tsdemux.
"v4l2src device=\"{dev}\" io-mode=mmap do-timestamp=true ! \
queue ! \
h264parse disable-passthrough=true config-interval=-1 ! \ h264parse disable-passthrough=true config-interval=-1 ! \
video/x-h264,stream-format=byte-stream,alignment=au ! \ video/x-h264,stream-format=byte-stream,alignment=au ! \
appsink name=sink emit-signals=true max-buffers=32 drop=true" 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 --------------- */ /* ----- BUS WATCH: show errors & warnings immediately --------------- */
let bus = pipeline.bus().expect("bus"); let bus = pipeline.bus().expect("bus");
let src_pad = pipeline
.by_name("v4l2src1") // adapt: eyespecific 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(); let eye_clone = eye.to_owned();
std::thread::spawn(move || { std::thread::spawn(move || {
for msg in bus.iter_timed(gst::ClockTime::NONE) { for msg in bus.iter_timed(gst::ClockTime::NONE) {
use gst::MessageView::*; use gst::MessageView::*;
match msg.view() { match msg.view() {
Error(err) => { Error(err) => {
tracing::error!(target:"lesavka_server::video", error!(target:"lesavka_server::video",
eye = %eye_clone, eye = %eye_clone,
"💥 pipeline error: {} ({})", "💥 pipeline error: {} ({})",
err.error(), err.debug().unwrap_or_default()); err.error(), err.debug().unwrap_or_default());
} }
Warning(w) => { Warning(w) => {
tracing::warn!(target:"lesavka_server::video", warn!(target:"lesavka_server::video",
eye = %eye_clone, eye = %eye_clone,
"⚠️ pipeline warning: {} ({})", "⚠️ pipeline warning: {} ({})",
w.error(), w.debug().unwrap_or_default()); w.error(), w.debug().unwrap_or_default());
} }
Info(i) => { Info(i) => {
tracing::info!(target:"lesavka_server::video", info!(target:"lesavka_server::video",
eye = %eye_clone, eye = %eye_clone,
"📌 pipeline info: {} ({})", "📌 pipeline info: {} ({})",
i.error(), i.debug().unwrap_or_default()); i.error(), i.debug().unwrap_or_default());
} }
StateChanged(s) if s.current() == gst::State::Playing => { StateChanged(s) if s.current() == gst::State::Playing => {
tracing::info!(target:"lesavka_server::video", info!(target:"lesavka_server::video",
eye = %eye_clone, eye = %eye_clone,
"🎬 pipeline PLAYING"); "🎬 pipeline PLAYING");
} }