refactor: decompose oversized modules into directory form (avhw + state + cap_portal + state_portal + webrtc + bench bins) #26
+17
-19
@@ -113,25 +113,23 @@ fn check_portal_available() -> bool {
|
|||||||
|
|
||||||
// The most likely operation to hang — requires actual Portal-side work.
|
// The most likely operation to hang — requires actual Portal-side work.
|
||||||
// 最可能卡住的操作,需要 Portal 端实际处理。
|
// 最可能卡住的操作,需要 Portal 端实际处理。
|
||||||
let version = match tokio::time::timeout(
|
let version =
|
||||||
PORTAL_DBUS_TIMEOUT,
|
match tokio::time::timeout(PORTAL_DBUS_TIMEOUT, inner.get_property::<u32>("version"))
|
||||||
inner.get_property::<u32>("version"),
|
.await
|
||||||
)
|
{
|
||||||
.await
|
Ok(Ok(version)) => {
|
||||||
{
|
tracing::info!("Portal ScreenCast available (version: {version})");
|
||||||
Ok(Ok(version)) => {
|
true
|
||||||
tracing::info!("Portal ScreenCast available (version: {version})");
|
}
|
||||||
true
|
Ok(Err(e)) => {
|
||||||
}
|
tracing::info!("Portal ScreenCast version query failed: {e}");
|
||||||
Ok(Err(e)) => {
|
false
|
||||||
tracing::info!("Portal ScreenCast version query failed: {e}");
|
}
|
||||||
false
|
Err(_) => {
|
||||||
}
|
log_portal_unresponsive("querying ScreenCast version");
|
||||||
Err(_) => {
|
false
|
||||||
log_portal_unresponsive("querying ScreenCast version");
|
}
|
||||||
false
|
};
|
||||||
}
|
|
||||||
};
|
|
||||||
version
|
version
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -419,9 +419,7 @@ fn import_frame(
|
|||||||
// carries a valid DMA-BUF fd and metadata from PipeWire for the duration of the call.
|
// carries a valid DMA-BUF fd and metadata from PipeWire for the duration of the call.
|
||||||
// SAFETY: frames_ctx is a valid VAAPI frames context; `frame` carries the
|
// SAFETY: frames_ctx is a valid VAAPI frames context; `frame` carries the
|
||||||
// DMA-BUF metadata read by the function.
|
// DMA-BUF metadata read by the function.
|
||||||
unsafe {
|
unsafe { import_dma_buf_to_vaapi(frames_ctx.as_ptr(), frame) }
|
||||||
import_dma_buf_to_vaapi(frames_ctx.as_ptr(), frame)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_gpu_filter_graph(
|
fn build_gpu_filter_graph(
|
||||||
@@ -936,9 +934,7 @@ fn main() -> Result<()> {
|
|||||||
// `first_frame` is the PipeWire-formatted PwDmaBufFrame whose metadata the
|
// `first_frame` is the PipeWire-formatted PwDmaBufFrame whose metadata the
|
||||||
// function reads directly. See that function's own SAFETY contract for the
|
// function reads directly. See that function's own SAFETY contract for the
|
||||||
// full rationale.
|
// full rationale.
|
||||||
let vaapi_frame = unsafe {
|
let vaapi_frame = unsafe { import_dma_buf_to_vaapi(frames_ctx.as_ptr(), &first_frame) };
|
||||||
import_dma_buf_to_vaapi(frames_ctx.as_ptr(), &first_frame)
|
|
||||||
};
|
|
||||||
|
|
||||||
match &vaapi_frame {
|
match &vaapi_frame {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
|||||||
+25
-24
@@ -298,10 +298,7 @@ impl CapPortal {
|
|||||||
/// retry wrapper.
|
/// retry wrapper.
|
||||||
///
|
///
|
||||||
/// `is_retry == true` disables further retry attempts (max 1 retry).
|
/// `is_retry == true` disables further retry attempts (max 1 retry).
|
||||||
async fn _setup_portal_inner(
|
async fn _setup_portal_inner(no_persist: bool, is_retry: bool) -> Result<(OwnedFd, u32)> {
|
||||||
no_persist: bool,
|
|
||||||
is_retry: bool,
|
|
||||||
) -> Result<(OwnedFd, u32)> {
|
|
||||||
use ashpd::desktop::screencast::{
|
use ashpd::desktop::screencast::{
|
||||||
CursorMode, Screencast, SelectSourcesOptions, SourceType,
|
CursorMode, Screencast, SelectSourcesOptions, SourceType,
|
||||||
};
|
};
|
||||||
@@ -371,14 +368,12 @@ impl CapPortal {
|
|||||||
Ok(Err(e)) => return Err(anyhow::anyhow!("Screen sharing permission denied: {e}")),
|
Ok(Err(e)) => return Err(anyhow::anyhow!("Screen sharing permission denied: {e}")),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log_portal_phase_timeout("selecting sources", token_in_use);
|
log_portal_phase_timeout("selecting sources", token_in_use);
|
||||||
return Err(
|
return Err(if token_in_use {
|
||||||
if token_in_use {
|
PortalPhaseTimeout::TokenDependent
|
||||||
PortalPhaseTimeout::TokenDependent
|
} else {
|
||||||
} else {
|
PortalPhaseTimeout::Service
|
||||||
PortalPhaseTimeout::Service
|
}
|
||||||
}
|
.into());
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,14 +394,12 @@ impl CapPortal {
|
|||||||
Ok(Err(e)) => return Err(anyhow::anyhow!("ScreenCast start/response error: {e}")),
|
Ok(Err(e)) => return Err(anyhow::anyhow!("ScreenCast start/response error: {e}")),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log_portal_phase_timeout("starting session", token_in_use);
|
log_portal_phase_timeout("starting session", token_in_use);
|
||||||
return Err(
|
return Err(if token_in_use {
|
||||||
if token_in_use {
|
PortalPhaseTimeout::TokenDependent
|
||||||
PortalPhaseTimeout::TokenDependent
|
} else {
|
||||||
} else {
|
PortalPhaseTimeout::Service
|
||||||
PortalPhaseTimeout::Service
|
}
|
||||||
}
|
.into());
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -470,8 +463,8 @@ fn verify_secure_dir(path: &std::path::Path) -> bool {
|
|||||||
// Must be owned by current user
|
// Must be owned by current user
|
||||||
// SAFETY: libc::getuid has no preconditions and cannot fail; it simply
|
// SAFETY: libc::getuid has no preconditions and cannot fail; it simply
|
||||||
// returns the calling process's real user ID.
|
// returns the calling process's real user ID.
|
||||||
// SAFETY: libc::getuid has no preconditions and cannot fail.
|
// SAFETY: libc::getuid has no preconditions and cannot fail.
|
||||||
if meta.uid() != unsafe { libc::getuid() } {
|
if meta.uid() != unsafe { libc::getuid() } {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"Token parent dir not owned by current user: {}",
|
"Token parent dir not owned by current user: {}",
|
||||||
path.display()
|
path.display()
|
||||||
@@ -588,7 +581,10 @@ fn delete_restore_token() {
|
|||||||
match std::fs::remove_file(&path) {
|
match std::fs::remove_file(&path) {
|
||||||
Ok(()) => tracing::info!("Deleted stale portal restore token at {}", path.display()),
|
Ok(()) => tracing::info!("Deleted stale portal restore token at {}", path.display()),
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
|
||||||
Err(e) => tracing::warn!("Failed to delete stale restore token at {}: {e}", path.display()),
|
Err(e) => tracing::warn!(
|
||||||
|
"Failed to delete stale restore token at {}: {e}",
|
||||||
|
path.display()
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -954,7 +950,12 @@ fn pipewire_thread(ctx: PwThreadCtx) {
|
|||||||
unsafe { stream.queue_raw_buffer(raw_buf) };
|
unsafe { stream.queue_raw_buffer(raw_buf) };
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let PortalFormatInfo { width, height, drm_format: format, modifier } = fmt;
|
let PortalFormatInfo {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
drm_format: format,
|
||||||
|
modifier,
|
||||||
|
} = fmt;
|
||||||
if width == 0 || height == 0 || format == 0 {
|
if width == 0 || height == 0 || format == 0 {
|
||||||
tracing::trace!("process: invalid dimensions {width}x{height} format={format}");
|
tracing::trace!("process: invalid dimensions {width}x{height} format={format}");
|
||||||
// SAFETY: raw_buf still owned, returning it.
|
// SAFETY: raw_buf still owned, returning it.
|
||||||
|
|||||||
+9
-13
@@ -90,7 +90,6 @@ pub struct PartialOutputInfo {
|
|||||||
pub done_count: u32,
|
pub done_count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Marker for wlr-output-management heads seen during probing; tracked by name
|
/// Marker for wlr-output-management heads seen during probing; tracked by name
|
||||||
/// in `EncConstructionStage::ProbingOutputs.wlr_heads`.
|
/// in `EncConstructionStage::ProbingOutputs.wlr_heads`.
|
||||||
// `pub(crate)` (not module-private): exposed via `EncConstructionStage::ProbingOutputs.wlr_heads`
|
// `pub(crate)` (not module-private): exposed via `EncConstructionStage::ProbingOutputs.wlr_heads`
|
||||||
@@ -121,7 +120,10 @@ impl StreamingEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_frame(&mut self, hw_frame: &ffmpeg_next::frame::Video) -> anyhow::Result<crate::avhw::EncodeStages> {
|
fn encode_frame(
|
||||||
|
&mut self,
|
||||||
|
hw_frame: &ffmpeg_next::frame::Video,
|
||||||
|
) -> anyhow::Result<crate::avhw::EncodeStages> {
|
||||||
match self {
|
match self {
|
||||||
StreamingEncoder::Mp4(enc) => enc.encode_frame(hw_frame),
|
StreamingEncoder::Mp4(enc) => enc.encode_frame(hw_frame),
|
||||||
StreamingEncoder::WebRtc(enc) => enc.encode_frame(hw_frame),
|
StreamingEncoder::WebRtc(enc) => enc.encode_frame(hw_frame),
|
||||||
@@ -704,9 +706,7 @@ impl<S: CaptureSource> State<S> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
count += 1;
|
count += 1;
|
||||||
if let Err(e) = wrtc
|
if let Err(e) = wrtc.write_h264_frame(&enc_frame.data, enc_frame.pts_ticks) {
|
||||||
.write_h264_frame(&enc_frame.data, enc_frame.pts_ticks)
|
|
||||||
{
|
|
||||||
tracing::debug!("WebRTC write frame error: {e}");
|
tracing::debug!("WebRTC write frame error: {e}");
|
||||||
}
|
}
|
||||||
self.stats.record_send(0.0, None);
|
self.stats.record_send(0.0, None);
|
||||||
@@ -881,7 +881,8 @@ impl<S: CaptureSource> State<S> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// done_count >= 1 implies transform arrived (Geometry precedes Done).
|
// done_count >= 1 implies transform arrived (Geometry precedes Done).
|
||||||
if info.done_count < 1 || !wlr_manager_done || info.transform.is_none() {
|
if info.done_count < 1 || !wlr_manager_done || info.transform.is_none()
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1154,10 +1155,7 @@ impl<S: CaptureSource> Dispatch<WlOutput, OutputId> for State<S> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
OutputEvent::Geometry {
|
OutputEvent::Geometry { transform, .. } => {
|
||||||
transform,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let t = match transform {
|
let t = match transform {
|
||||||
wayland_client::WEnum::Value(WlTransform::Normal) => Transform::Normal,
|
wayland_client::WEnum::Value(WlTransform::Normal) => Transform::Normal,
|
||||||
wayland_client::WEnum::Value(WlTransform::_90) => Transform::Normal90,
|
wayland_client::WEnum::Value(WlTransform::_90) => Transform::Normal90,
|
||||||
@@ -1527,9 +1525,7 @@ impl<S: CaptureSource> Dispatch<ZwlrOutputHeadV1, ()> for State<S> {
|
|||||||
..
|
..
|
||||||
} = &mut state.stage
|
} = &mut state.stage
|
||||||
{
|
{
|
||||||
wlr_heads
|
wlr_heads.entry(name.clone()).or_insert(WlrHeadInfo {});
|
||||||
.entry(name.clone())
|
|
||||||
.or_insert(WlrHeadInfo {});
|
|
||||||
wlr_head_proxy_to_name.insert(proxy.id(), name);
|
wlr_head_proxy_to_name.insert(proxy.id(), name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-21
@@ -273,9 +273,7 @@ impl StatePortal {
|
|||||||
bitrate_rx,
|
bitrate_rx,
|
||||||
encoder_resolution_rx,
|
encoder_resolution_rx,
|
||||||
)?;
|
)?;
|
||||||
let duplicate_count = std::sync::Arc::new(
|
let duplicate_count = std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0));
|
||||||
std::sync::atomic::AtomicU64::new(0),
|
|
||||||
);
|
|
||||||
let duplicate_count_for_thread = duplicate_count.clone();
|
let duplicate_count_for_thread = duplicate_count.clone();
|
||||||
let handle = std::thread::Builder::new()
|
let handle = std::thread::Builder::new()
|
||||||
.name("wl-webrtc-encode".into())
|
.name("wl-webrtc-encode".into())
|
||||||
@@ -370,11 +368,13 @@ impl StatePortal {
|
|||||||
// capture channel depth. Oracle audit 2026-06-28: previously hardcoded
|
// capture channel depth. Oracle audit 2026-06-28: previously hardcoded
|
||||||
// (0, 0), which silently zeroed two real diagnostic fields.
|
// (0, 0), which silently zeroed two real diagnostic fields.
|
||||||
let total_dropped = self.cap.dropped_count();
|
let total_dropped = self.cap.dropped_count();
|
||||||
self.stats.set_pipewire_dropped(total_dropped, self.pw_dropped_prev);
|
self.stats
|
||||||
|
.set_pipewire_dropped(total_dropped, self.pw_dropped_prev);
|
||||||
self.pw_dropped_prev = total_dropped;
|
self.pw_dropped_prev = total_dropped;
|
||||||
// capture queue depth is real; encoded side has no exposed depth — the
|
// capture queue depth is real; encoded side has no exposed depth — the
|
||||||
// encoder thread publishes timings only, not a frame queue length.
|
// encoder thread publishes timings only, not a frame queue length.
|
||||||
self.stats.set_queue_depths(self.cap.capture_queue_depth(), 0);
|
self.stats
|
||||||
|
.set_queue_depths(self.cap.capture_queue_depth(), 0);
|
||||||
if let Some(ref enc_thread) = self.enc_thread {
|
if let Some(ref enc_thread) = self.enc_thread {
|
||||||
while let Ok(timing) = enc_thread.timing_rx.try_recv() {
|
while let Ok(timing) = enc_thread.timing_rx.try_recv() {
|
||||||
self.stats.record_encode_thread(
|
self.stats.record_encode_thread(
|
||||||
@@ -517,9 +517,8 @@ impl StatePortal {
|
|||||||
// frames_rgb pointer is a valid AVBufferRef owned by enc, and `frame` is the
|
// frames_rgb pointer is a valid AVBufferRef owned by enc, and `frame` is the
|
||||||
// PipeWire-formatted PwDmaBufFrame whose metadata the function reads directly.
|
// PipeWire-formatted PwDmaBufFrame whose metadata the function reads directly.
|
||||||
// See that function's own SAFETY contract.
|
// See that function's own SAFETY contract.
|
||||||
let mut vaapi_frame = unsafe {
|
let mut vaapi_frame =
|
||||||
avhw::import_dma_buf_to_vaapi(enc.frames_rgb().as_ptr(), &frame)
|
unsafe { avhw::import_dma_buf_to_vaapi(enc.frames_rgb().as_ptr(), &frame) }?;
|
||||||
}?;
|
|
||||||
|
|
||||||
let import_us = t_import_start.elapsed().as_micros() as u64;
|
let import_us = t_import_start.elapsed().as_micros() as u64;
|
||||||
|
|
||||||
@@ -550,9 +549,8 @@ impl StatePortal {
|
|||||||
} else if let Some(import) = self.enc_import.as_mut() {
|
} else if let Some(import) = self.enc_import.as_mut() {
|
||||||
// SAFETY: same contract as the enc branch above — frames_rgb owned by
|
// SAFETY: same contract as the enc branch above — frames_rgb owned by
|
||||||
// import, `frame` carries the PipeWire DMA-BUF metadata.
|
// import, `frame` carries the PipeWire DMA-BUF metadata.
|
||||||
let mut vaapi_frame = unsafe {
|
let mut vaapi_frame =
|
||||||
avhw::import_dma_buf_to_vaapi(import.frames_rgb().as_ptr(), &frame)
|
unsafe { avhw::import_dma_buf_to_vaapi(import.frames_rgb().as_ptr(), &frame) }?;
|
||||||
}?;
|
|
||||||
// SAFETY: vaapi_frame is the valid AVFrame returned above; pts is plain i64.
|
// SAFETY: vaapi_frame is the valid AVFrame returned above; pts is plain i64.
|
||||||
unsafe {
|
unsafe {
|
||||||
(*vaapi_frame.as_mut_ptr()).pts = pts;
|
(*vaapi_frame.as_mut_ptr()).pts = pts;
|
||||||
@@ -842,8 +840,7 @@ fn webrtc_thread_loop(
|
|||||||
.unwrap_or(0.0);
|
.unwrap_or(0.0);
|
||||||
// Compute capture-to-send age on the sending thread so the
|
// Compute capture-to-send age on the sending thread so the
|
||||||
// frame_age stat stays accurate when batch-drained later.
|
// frame_age stat stays accurate when batch-drained later.
|
||||||
let age_ms =
|
let age_ms = Some(enc_frame.capture_time.elapsed().as_secs_f64() * 1000.0);
|
||||||
Some(enc_frame.capture_time.elapsed().as_secs_f64() * 1000.0);
|
|
||||||
last_send = Some(std::time::Instant::now());
|
last_send = Some(std::time::Instant::now());
|
||||||
let _ = sent_gap_tx.try_send((gap_ms, age_ms));
|
let _ = sent_gap_tx.try_send((gap_ms, age_ms));
|
||||||
}
|
}
|
||||||
@@ -854,16 +851,14 @@ fn webrtc_thread_loop(
|
|||||||
match webrtc_rx.recv_timeout(timeout) {
|
match webrtc_rx.recv_timeout(timeout) {
|
||||||
Ok(enc_frame) => {
|
Ok(enc_frame) => {
|
||||||
if wrtc.is_connected() {
|
if wrtc.is_connected() {
|
||||||
if let Err(e) = wrtc.write_h264_frame(&enc_frame.data, enc_frame.pts_ticks)
|
if let Err(e) = wrtc.write_h264_frame(&enc_frame.data, enc_frame.pts_ticks) {
|
||||||
{
|
|
||||||
tracing::debug!("WebRTC write frame error: {e}");
|
tracing::debug!("WebRTC write frame error: {e}");
|
||||||
}
|
}
|
||||||
frames_sent = frames_sent.saturating_add(1);
|
frames_sent = frames_sent.saturating_add(1);
|
||||||
let gap_ms = last_send
|
let gap_ms = last_send
|
||||||
.map(|l| l.elapsed().as_secs_f64() * 1000.0)
|
.map(|l| l.elapsed().as_secs_f64() * 1000.0)
|
||||||
.unwrap_or(0.0);
|
.unwrap_or(0.0);
|
||||||
let age_ms =
|
let age_ms = Some(enc_frame.capture_time.elapsed().as_secs_f64() * 1000.0);
|
||||||
Some(enc_frame.capture_time.elapsed().as_secs_f64() * 1000.0);
|
|
||||||
last_send = Some(std::time::Instant::now());
|
last_send = Some(std::time::Instant::now());
|
||||||
let _ = sent_gap_tx.try_send((gap_ms, age_ms));
|
let _ = sent_gap_tx.try_send((gap_ms, age_ms));
|
||||||
}
|
}
|
||||||
@@ -1200,10 +1195,7 @@ mod tests {
|
|||||||
fn select_resolution_keeps_720p_when_bwe_sufficient() {
|
fn select_resolution_keeps_720p_when_bwe_sufficient() {
|
||||||
let fps = 30;
|
let fps = 30;
|
||||||
let bitrate_720 = resolution_bitrate_bps(1280, 720, fps);
|
let bitrate_720 = resolution_bitrate_bps(1280, 720, fps);
|
||||||
assert_eq!(
|
assert_eq!(select_resolution(1280, 720, bitrate_720, fps), (1280, 720));
|
||||||
select_resolution(1280, 720, bitrate_720, fps),
|
|
||||||
(1280, 720)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+3
-2
@@ -201,7 +201,8 @@ impl PipelineStats {
|
|||||||
/// Update duplicate frames skipped counter (absolute value from atomic).
|
/// Update duplicate frames skipped counter (absolute value from atomic).
|
||||||
/// Computes delta from previous value, like set_pipewire_dropped.
|
/// Computes delta from previous value, like set_pipewire_dropped.
|
||||||
pub fn set_duplicate_frames_skipped(&mut self, total_skipped: u64) {
|
pub fn set_duplicate_frames_skipped(&mut self, total_skipped: u64) {
|
||||||
self.duplicate_frames_skipped = total_skipped.saturating_sub(self.prev_duplicate_frames_skipped);
|
self.duplicate_frames_skipped =
|
||||||
|
total_skipped.saturating_sub(self.prev_duplicate_frames_skipped);
|
||||||
self.prev_duplicate_frames_skipped = total_skipped;
|
self.prev_duplicate_frames_skipped = total_skipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,7 +357,7 @@ impl std::fmt::Display for StatsSnapshot {
|
|||||||
// central tendency and tail behaviour in the same glance.
|
// central tendency and tail behaviour in the same glance.
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"elapsed={:.1}s capture_fps={:.1} encoded_fps={:.1} sent_fps={:.1} \
|
"elapsed={:.1}s capture_fps={:.1} encoded_fps={:.1} sent_fps={:.1} \
|
||||||
capture_frames={} encoded_frames={} sent_frames={} \
|
capture_frames={} encoded_frames={} sent_frames={} \
|
||||||
pw_dropped={} duplicate_frames_skipped={} \
|
pw_dropped={} duplicate_frames_skipped={} \
|
||||||
cap_q={} enc_q={} \
|
cap_q={} enc_q={} \
|
||||||
|
|||||||
Reference in New Issue
Block a user