Fix AshpdKeeper struct definition and generics in src/media/capture.rs
This commit is contained in:
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user