refactor: decompose oversized modules into directory form (avhw + state + cap_portal + state_portal + webrtc + bench bins) #26

Merged
dailz merged 16 commits from refactor/split-avhw into master 2026-07-14 11:44:56 +08:00
6 changed files with 69 additions and 85 deletions
Showing only changes of commit 75ad4bba78 - Show all commits
+2 -4
View File
@@ -113,10 +113,8 @@ 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)) => { Ok(Ok(version)) => {
+2 -6
View File
@@ -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(_) => {
+15 -14
View File
@@ -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());
);
} }
}; };
@@ -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
View File
@@ -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
View File
@@ -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]
+2 -1
View File
@@ -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;
} }