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:
dailz
2026-06-07 16:55:07 +08:00
parent 029fe13e37
commit aae030f309
3 changed files with 231 additions and 39 deletions
+33 -5
View File
@@ -73,6 +73,7 @@ pub struct CapPortal {
event_rx: Receiver<PwCtrlEvent>,
pw_thread: Option<JoinHandle<()>>,
rt: Runtime,
pw_dropped: Arc<AtomicU64>,
}
/// PipeWire 捕获线程的上下文数据
@@ -149,6 +150,7 @@ impl CapPortal {
event_rx,
pw_thread: Some(pw_thread),
rt,
pw_dropped,
})
}
@@ -160,6 +162,16 @@ impl CapPortal {
&self.event_rx
}
/// Returns the total number of PipeWire frames dropped due to channel backlog.
pub fn dropped_count(&self) -> u64 {
self.pw_dropped.load(Ordering::Relaxed)
}
/// Returns the number of frames currently waiting in the capture channel.
pub fn capture_queue_depth(&self) -> usize {
self.frame_rx.len()
}
/// 通过 XDG Desktop Portal 建立屏幕录制会话
///
/// 与桌面环境的 D-Bus 服务交互,请求用户授权屏幕录制。
@@ -476,7 +488,9 @@ fn pipewire_thread(ctx: PwThreadCtx) {
let mainloop = match pw::main_loop::MainLoopBox::new(None) {
Ok(ml) => ml,
Err(e) => {
let _ = event_tx.try_send(PwCtrlEvent::Error(format!("MainLoop::new failed: {e}")));
if let Err(e) = event_tx.try_send(PwCtrlEvent::Error(format!("MainLoop::new failed: {e}"))) {
tracing::error!("MainLoop::new failed and error channel also failed: {e}");
}
return;
}
};
@@ -484,7 +498,9 @@ fn pipewire_thread(ctx: PwThreadCtx) {
let context = match pw::context::ContextBox::new(mainloop.loop_(), None) {
Ok(c) => c,
Err(e) => {
let _ = event_tx.try_send(PwCtrlEvent::Error(format!("Context::new failed: {e}")));
if let Err(e) = event_tx.try_send(PwCtrlEvent::Error(format!("Context::new failed: {e}"))) {
tracing::error!("Context::new failed and error channel also failed: {e}");
}
return;
}
};
@@ -492,7 +508,9 @@ fn pipewire_thread(ctx: PwThreadCtx) {
let core = match context.connect_fd(pw_fd, None) {
Ok(c) => c,
Err(e) => {
let _ = event_tx.try_send(PwCtrlEvent::Error(format!("connect_fd failed: {e}")));
if let Err(e) = event_tx.try_send(PwCtrlEvent::Error(format!("connect_fd failed: {e}"))) {
tracing::error!("connect_fd failed and error channel also failed: {e}");
}
return;
}
};
@@ -514,7 +532,9 @@ fn pipewire_thread(ctx: PwThreadCtx) {
) {
Ok(s) => s,
Err(e) => {
let _ = event_tx.try_send(PwCtrlEvent::Error(format!("Stream::new failed: {e}")));
if let Err(e) = event_tx.try_send(PwCtrlEvent::Error(format!("Stream::new failed: {e}"))) {
tracing::error!("Stream::new failed and error channel also failed: {e}");
}
return;
}
};
@@ -584,12 +604,14 @@ fn pipewire_thread(ctx: PwThreadCtx) {
let raw_buf = unsafe { stream.dequeue_raw_buffer() };
if raw_buf.is_null() {
tracing::trace!("process: null raw_buf");
return;
}
// 获取 SPA buffer 结构体,包含数据数组、元数据等
let spa_buf = unsafe { (*raw_buf).buffer };
if spa_buf.is_null() {
tracing::trace!("process: null spa_buf");
unsafe { stream.queue_raw_buffer(raw_buf) };
return;
}
@@ -599,6 +621,7 @@ fn pipewire_thread(ctx: PwThreadCtx) {
let n_datas = unsafe { (*spa_buf).n_datas };
let datas_ptr = unsafe { (*spa_buf).datas };
if n_datas == 0 || datas_ptr.is_null() {
tracing::trace!("process: no data (n_datas={n_datas})");
unsafe { stream.queue_raw_buffer(raw_buf) };
return;
}
@@ -609,11 +632,13 @@ fn pipewire_thread(ctx: PwThreadCtx) {
unsafe { &*(datas_ptr as *const pw::spa::buffer::Data) };
let fd = data_ref.fd();
if fd < 0 {
tracing::trace!("process: invalid fd={fd}");
unsafe { stream.queue_raw_buffer(raw_buf) };
return;
}
if data_ref.as_raw().chunk.is_null() {
tracing::trace!("process: null chunk");
unsafe { stream.queue_raw_buffer(raw_buf) };
return;
}
@@ -651,6 +676,7 @@ fn pipewire_thread(ctx: PwThreadCtx) {
return;
};
if width == 0 || height == 0 || format == 0 {
tracing::trace!("process: invalid dimensions {width}x{height} format={format}");
unsafe { stream.queue_raw_buffer(raw_buf) };
return;
}
@@ -695,7 +721,9 @@ fn pipewire_thread(ctx: PwThreadCtx) {
StreamFlags::AUTOCONNECT | StreamFlags::MAP_BUFFERS,
&mut params,
) {
let _ = event_tx.try_send(PwCtrlEvent::Error(format!("stream.connect failed: {e}")));
if let Err(e) = event_tx.try_send(PwCtrlEvent::Error(format!("stream.connect failed: {e}"))) {
tracing::error!("stream.connect failed and error channel also failed: {e}");
}
return;
}