Fix AshpdKeeper struct definition and generics in src/media/capture.rs

This commit is contained in:
mixa
2026-02-18 13:04:53 +03:00
parent 65b3f22dae
commit 6f13bb03aa

View File

@@ -15,6 +15,12 @@ use std::process::Stdio;
use tokio::io::{AsyncReadExt, BufReader, AsyncWriteExt, AsyncBufReadExt};
use tokio::process::Command;
use xcap::Monitor;
use ashpd::desktop::{PersistMode, screencast::{CursorMode, Screencast, SourceType}};
struct AshpdKeeper {
_proxy: Screencast<'static>,
_session: ashpd::desktop::Session<'static, Screencast<'static>>,
}
/// Manages a video capture session (camera or screen).
pub struct VideoCapture {
@@ -104,7 +110,17 @@ impl VideoCapture {
tasks.push(tokio::spawn(async move {
let result = if matches!(kind_clone, MediaKind::Screen) {
run_xcap_capture(frame_tx_clone, broadcast_tx_clone, capture_running).await
// Try Wayland Portal first
match select_wayland_source().await {
Ok((node_id, keeper)) => {
tracing::info!("Selected Wayland source node: {}", node_id);
run_pipewire_capture(node_id, keeper, frame_tx_clone, broadcast_tx_clone, capture_running).await
}
Err(e) => {
tracing::warn!("Wayland source selection failed ({}). Falling back to xcap.", e);
run_xcap_capture(frame_tx_clone, broadcast_tx_clone, capture_running).await
}
}
} else {
run_ffmpeg_capture(kind_clone, frame_tx_clone, broadcast_tx_clone, capture_running).await
};
@@ -773,3 +789,185 @@ fn find_start_code_from(data: &[u8], start: usize) -> Option<usize> {
}
None
}
// ---------------------------------------------------------------------------
// Wayland Capture Logic (ashpd)
// ---------------------------------------------------------------------------
async fn select_wayland_source() -> Result<(u32, AshpdKeeper)> {
let proxy = Screencast::new().await?;
let session = proxy.create_session().await?;
proxy
.select_sources(
&session,
CursorMode::Metadata,
SourceType::Monitor | SourceType::Window,
false,
None,
PersistMode::DoNot,
)
.await?;
let request = proxy.start(&session, &ashpd::WindowIdentifier::default()).await?;
let streams = request.response()?;
if let Some(stream) = streams.streams().iter().next() {
Ok((stream.pipe_wire_node_id(), AshpdKeeper { _proxy: proxy, _session: session }))
} else {
Err(anyhow::anyhow!("No pipewire stream returned from portal"))
}
}
async fn run_pipewire_capture(
node_id: u32,
_keeper: AshpdKeeper,
frame_tx: tokio::sync::broadcast::Sender<Vec<u8>>,
broadcast_preview: tokio::sync::broadcast::Sender<WebMediaEvent>,
running: Arc<AtomicBool>,
) -> Result<()> {
tracing::info!("Starting PipeWire capture for node: {}", node_id);
// Encoder Selection (Hardware preferred)
// Note: PipeWire input is raw, so we can encode it.
struct EncoderConfig {
name: &'static str,
codec: &'static str,
opts: Vec<&'static str>,
format: &'static str,
filter: &'static str,
}
let encoders = vec![
EncoderConfig {
name: "hevc_nvenc (Hardware)",
codec: "hevc_nvenc",
opts: vec!["-b:v", "1M", "-g", "30", "-zerolatency", "1", "-preset", "p4"],
format: "hevc",
filter: "hevc_mp4toannexb",
},
EncoderConfig {
name: "h264_nvenc (Hardware Fallback)",
codec: "h264_nvenc",
opts: vec!["-b:v", "1.5M", "-g", "30", "-zerolatency", "1", "-preset", "p4"],
format: "h264",
filter: "h264_mp4toannexb",
},
EncoderConfig {
name: "hevc_vaapi (VAAPI HEVC)",
codec: "hevc_vaapi",
opts: vec![
"-vaapi_device", "/dev/dri/renderD128",
"-vf", "format=nv12,hwupload,scale_vaapi=w=1280:h=720",
"-b:v", "1M", "-g", "30"
],
format: "hevc",
filter: "hevc_mp4toannexb",
},
EncoderConfig {
name: "libx264 (Software Fallback)",
codec: "libx264",
opts: vec!["-preset", "ultrafast", "-tune", "zerolatency", "-b:v", "1M", "-g", "30"],
format: "h264",
filter: "h264_mp4toannexb",
},
];
let mut final_child = None;
let mut chosen_filter = "";
for enc in &encoders {
tracing::info!("Trying encoder: {}", enc.name);
let mut cmd = Command::new("ffmpeg");
cmd.kill_on_drop(true);
cmd.args(&[
"-f", "pipewire",
"-framerate", "30",
"-i", &node_id.to_string(),
]);
if !enc.name.contains("vaapi") {
cmd.args(&["-vf", "scale=1280:720"]);
}
cmd.arg("-c:v").arg(enc.codec);
cmd.args(&enc.opts);
cmd.arg("-bsf:v").arg(enc.filter);
cmd.arg("-f").arg(enc.format);
cmd.arg("-");
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
match cmd.spawn() {
Ok(mut child) => {
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
if let Ok(Some(_)) = child.try_wait() {
continue;
}
final_child = Some(child);
chosen_filter = enc.filter;
break;
}
Err(_) => continue,
}
}
let mut child = match final_child {
Some(c) => c,
None => return Err(anyhow::anyhow!("All encoders failed for PipeWire")),
};
let stdout = child.stdout.take().expect("Failed to open stdout");
let mut reader = BufReader::new(stdout);
let mut buffer = Vec::with_capacity(1024 * 1024);
let mut temp_buf = [0u8; 4096];
while running.load(Ordering::Relaxed) {
let n = match reader.read(&mut temp_buf).await {
Ok(0) => break,
Ok(n) => n,
Err(_) => break,
};
buffer.extend_from_slice(&temp_buf[0..n]);
while let Some(start_idx) = find_start_code(&buffer) {
let end_idx = if let Some(next_start) = find_start_code_from(&buffer, start_idx + 4) {
next_start
} else {
break;
};
let nal_data = buffer.drain(start_idx..end_idx).collect::<Vec<u8>>();
let start_code_len = if nal_data[2] == 1 { 3 } else { 4 };
let nal_header_byte = nal_data[start_code_len];
let is_key = if chosen_filter.contains("hevc") {
let nal_type = (nal_header_byte & 0x7E) >> 1;
(nal_type >= 16 && nal_type <= 21) || (nal_type >= 32 && nal_type <= 34)
} else {
let nal_type = nal_header_byte & 0x1F;
nal_type == 5 || nal_type == 7 || nal_type == 8
};
let frame_type = if is_key { 0u8 } else { 1u8 };
let mut payload = Vec::with_capacity(1 + nal_data.len());
payload.push(frame_type);
payload.extend_from_slice(&nal_data);
let _ = frame_tx.send(payload.clone());
let _ = broadcast_preview.send(WebMediaEvent::Video {
peer_id: "local".to_string(),
kind: MediaKind::Screen,
data: payload,
});
}
}
let _ = child.kill().await;
Ok(())
}