Wayland capture: ashpd portal → gst-launch → ffmpeg → MPEG-TS
Implements the Wayland host pipeline from plan §4.5:
ashpd ScreenCast portal
-> CreateSession + SelectSources + Start + OpenPipeWireRemote
-> (pipewire fd, node_id, width, height)
gst-launch-1.0 pipewiresrc fd=N path=NODE_ID ! videoconvert
! video/x-raw,format=NV12 ! fdsink fd=1
ffmpeg
-f rawvideo -pix_fmt nv12 -video_size WxH -i pipe:0
-f pulse -i default
-c:v libx264 -preset ultrafast -tune zerolatency
-c:a aac -f mpegts -listen 1 http://127.0.0.1:<rand>
Phase 1 ships software x264 per plan §7; VAAPI is Phase 2.
src/host/wayland.rs is the new module. capture.rs becomes a thin
dispatcher with a CaptureHandle enum (Wayland today, X11 next).
host/mod.rs swaps the 150ms sleep for a poll-until-listener-ready
helper, and calls handle.shutdown().await for an orderly SIGTERM /
1s grace / SIGKILL teardown. The Drop impl is the panic backstop.
The pipewire fd handoff clears CLOEXEC before gst-launch spawn and
closes the parent's copy of the raw fd after the child has it.
Also deletes the empty src/host/tunnel.rs and src/viewer/tunnel.rs
placeholder files — the generic bridge in common/tunnel.rs is doing
the work, and there's no host- or viewer-specific tunnel concern
worth a module yet.
This commit is contained in:
+24
-28
@@ -1,43 +1,39 @@
|
||||
//! Capture-and-encode pipeline.
|
||||
//!
|
||||
//! Phase 1 stub: the actual ffmpeg / gst-launch spawn lives here. Returns a
|
||||
//! [`CaptureHandle`] whose `Drop` impl kills the children so a panic on the
|
||||
//! bridge side can't leave a black-hole encoder running.
|
||||
//!
|
||||
//! Phase 2 will fill in X11 (x11grab) and Wayland (ashpd portal +
|
||||
//! pipewiresrc → fdsink | ffmpeg) bodies; per-app PipeWire routing hangs off
|
||||
//! the Wayland branch.
|
||||
//! Capture dispatcher. Selects the per-display-server pipeline and returns its
|
||||
//! [`CaptureHandle`]. Each backend owns its own children and tears them down
|
||||
//! via its own Drop / shutdown.
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
|
||||
use crate::cli::HostOpts;
|
||||
use crate::common::display::DisplayServer;
|
||||
use crate::host::wayland;
|
||||
|
||||
pub struct CaptureHandle {
|
||||
port: u16,
|
||||
// Future: child handles, PipewireState, etc.
|
||||
pub enum CaptureHandle {
|
||||
Wayland(wayland::CaptureHandle),
|
||||
}
|
||||
|
||||
impl CaptureHandle {
|
||||
pub fn local_port(&self) -> u16 {
|
||||
self.port
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CaptureHandle {
|
||||
fn drop(&mut self) {
|
||||
// Future: SIGTERM children with a short grace period, then SIGKILL.
|
||||
// Tear down any PipeWire null sinks / loopbacks created in spawn().
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn spawn(display: DisplayServer, _opts: &HostOpts) -> Result<CaptureHandle> {
|
||||
match display {
|
||||
DisplayServer::X11 => {
|
||||
bail!("X11 capture pipeline not yet implemented (Phase 2)");
|
||||
match self {
|
||||
CaptureHandle::Wayland(h) => h.local_port(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn shutdown(self) {
|
||||
match self {
|
||||
CaptureHandle::Wayland(h) => h.shutdown().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn spawn(display: DisplayServer, opts: &HostOpts) -> Result<CaptureHandle> {
|
||||
match display {
|
||||
DisplayServer::Wayland => {
|
||||
bail!("Wayland capture pipeline not yet implemented (Phase 2)");
|
||||
let h = wayland::start(opts).await?;
|
||||
Ok(CaptureHandle::Wayland(h))
|
||||
}
|
||||
DisplayServer::X11 => {
|
||||
bail!("X11 capture pipeline not yet implemented (Phase 2 follow-up)");
|
||||
}
|
||||
DisplayServer::Unknown => unreachable!("caller guarantees display != Unknown"),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user