fix(webrtc): SO_SNDBUF 2MB + VBV rate limiting + stats integration
P0 - UDP send buffer: set SO_SNDBUF=2MB to prevent EAGAIN on large IDR frames (218KB/256KB keyframes caused 18+ EAGAIN bursts). Actual Linux buffer 4096KB confirmed. P1 - VBV rate limiting: cap rc_max_rate=bitrate and rc_buffer_size= bitrate/4 for WebRTC encode path, preventing oversized IDR frames. Stats: integrate PipelineStats into cap_portal (dropped_count), state.rs (wlroots path), webrtc.rs (browser getStats enhancement + stats panel).
This commit is contained in:
51
src/state.rs
51
src/state.rs
@@ -46,6 +46,7 @@ use crate::args::Args;
|
||||
use crate::avhw::{AvHwDevCtx, EncState, SwEncState};
|
||||
use crate::cap_wlr_screencopy::CapWlrScreencopy;
|
||||
use crate::fps_limit::FpsLimit;
|
||||
use crate::stats::{FrameTimings, PipelineStats};
|
||||
use crate::transform::{transpose_if_transform_transposed, Transform};
|
||||
use crate::webrtc::WebRtcState;
|
||||
|
||||
@@ -213,6 +214,9 @@ pub struct State<S: CaptureSource> {
|
||||
pub stage: EncConstructionStage<S>,
|
||||
pub in_flight_surface: InFlightSurface<S>,
|
||||
pub starting_timestamp: Option<i64>,
|
||||
pub stats_start_time: Option<Instant>,
|
||||
pub stats_last_time: Option<Instant>,
|
||||
pub stats_frames: u64,
|
||||
pub first_frame: bool,
|
||||
pub args: Args,
|
||||
pub errored: bool,
|
||||
@@ -226,6 +230,7 @@ pub struct State<S: CaptureSource> {
|
||||
webrtc_rx: Option<crossbeam_channel::Receiver<Vec<u8>>>,
|
||||
webrtc_frames_sent: u64,
|
||||
webrtc_paused: Option<Arc<AtomicBool>>,
|
||||
stats: PipelineStats,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -302,6 +307,9 @@ impl<S: CaptureSource> State<S> {
|
||||
},
|
||||
in_flight_surface: InFlightSurface::None,
|
||||
starting_timestamp: None,
|
||||
stats_start_time: None,
|
||||
stats_last_time: None,
|
||||
stats_frames: 0,
|
||||
first_frame: true,
|
||||
fps_limit: FpsLimit::new(fps),
|
||||
args,
|
||||
@@ -315,6 +323,7 @@ impl<S: CaptureSource> State<S> {
|
||||
webrtc_rx,
|
||||
webrtc_frames_sent: 0,
|
||||
webrtc_paused,
|
||||
stats: PipelineStats::new(),
|
||||
};
|
||||
|
||||
// registry_queue_init consumes registry events internally during its
|
||||
@@ -492,7 +501,7 @@ impl<S: CaptureSource> State<S> {
|
||||
// is a freshly allocated empty Video frame.
|
||||
let ret = unsafe { ffi::av_hwframe_get_buffer(frames_rgb_ctx, surface.as_mut_ptr(), 0) };
|
||||
if ret < 0 {
|
||||
tracing::error!("av_hwframe_get_buffer failed: error {}", ret);
|
||||
tracing::error!("av_hwframe_get_buffer failed: {}", crate::avhw::ff_err(ret));
|
||||
self.errored = true;
|
||||
return;
|
||||
}
|
||||
@@ -505,7 +514,7 @@ impl<S: CaptureSource> State<S> {
|
||||
}
|
||||
let ret = unsafe { ffi::av_hwframe_map(map_frame.as_mut_ptr(), surface.as_ptr(), 0) };
|
||||
if ret < 0 {
|
||||
tracing::error!("av_hwframe_map failed: error {}", ret);
|
||||
tracing::error!("av_hwframe_map failed: {}", crate::avhw::ff_err(ret));
|
||||
self.errored = true;
|
||||
return;
|
||||
}
|
||||
@@ -530,7 +539,7 @@ impl<S: CaptureSource> State<S> {
|
||||
// takes ownership of the fd, and the original fd is owned by map_frame.
|
||||
let fd_dup = unsafe { libc::dup(obj.fd) };
|
||||
if fd_dup < 0 {
|
||||
tracing::error!("failed to dup dma-buf fd");
|
||||
tracing::error!("failed to dup dma-buf fd: {}", std::io::Error::last_os_error());
|
||||
// wayland-client does not auto-destroy params on Drop.
|
||||
params.destroy();
|
||||
self.errored = true;
|
||||
@@ -574,6 +583,8 @@ impl<S: CaptureSource> State<S> {
|
||||
where
|
||||
S::Frame: Default,
|
||||
{
|
||||
self.stats.record_capture();
|
||||
|
||||
let (mut surface, _drm_map, frame, buffer) =
|
||||
match mem::replace(&mut self.in_flight_surface, InFlightSurface::None) {
|
||||
InFlightSurface::CopyQueued {
|
||||
@@ -614,10 +625,29 @@ impl<S: CaptureSource> State<S> {
|
||||
.is_some()
|
||||
};
|
||||
if should_encode {
|
||||
let encode_start = Instant::now();
|
||||
if let Err(e) = enc.encode_frame(&surface) {
|
||||
tracing::error!("encode_frame failed: {}", e);
|
||||
self.errored = true;
|
||||
}
|
||||
let encode_elapsed = encode_start.elapsed().as_micros() as u64;
|
||||
self.stats.record_encode(&FrameTimings {
|
||||
total_us: encode_elapsed,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
self.stats_frames += 1;
|
||||
if let Some(last) = self.stats_last_time {
|
||||
if last.elapsed() >= std::time::Duration::from_secs(10) {
|
||||
let delta = self.stats_frames;
|
||||
let fps = delta as f64 / last.elapsed().as_secs_f64();
|
||||
tracing::info!(frames = self.stats_frames, fps = format!("{fps:.1}"), "encoding stats");
|
||||
self.stats_last_time = Some(std::time::Instant::now());
|
||||
self.stats_frames = 0;
|
||||
}
|
||||
} else {
|
||||
self.stats_start_time = Some(std::time::Instant::now());
|
||||
self.stats_last_time = Some(std::time::Instant::now());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -670,13 +700,23 @@ impl<S: CaptureSource> State<S> {
|
||||
if let Err(e) = wrtc.write_h264_frame(&data, self.webrtc_frames_sent, self.args.fps) {
|
||||
tracing::debug!("WebRTC write frame error: {e}");
|
||||
}
|
||||
self.stats.record_send(0.0, None);
|
||||
self.webrtc_frames_sent = self.webrtc_frames_sent.saturating_add(1);
|
||||
}
|
||||
if count > 0 {
|
||||
tracing::info!("WebRTC forwarded {count} frames from channel");
|
||||
tracing::debug!("WebRTC forwarded {count} frames from channel");
|
||||
}
|
||||
}
|
||||
|
||||
if self.args.stats && self.stats.should_snapshot() {
|
||||
self.stats.set_queue_depths(
|
||||
0,
|
||||
self.webrtc_rx.as_ref().map(|r| r.len()).unwrap_or(0),
|
||||
);
|
||||
let snap = self.stats.snapshot_and_reset();
|
||||
tracing::info!("stats: {snap}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -995,8 +1035,7 @@ impl<S: CaptureSource> Dispatch<WlRegistry, GlobalListContents> for State<S> {
|
||||
qhandle: &QueueHandle<State<S>>,
|
||||
) {
|
||||
use wayland_client::protocol::wl_registry::Event as RegistryEvent;
|
||||
tracing::debug!("Dispatch<WlRegistry>::event fired: {:?}", event);
|
||||
|
||||
|
||||
match event {
|
||||
RegistryEvent::Global {
|
||||
name,
|
||||
|
||||
Reference in New Issue
Block a user