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:
+33
-5
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user