在 #19、#23、#15、#18 全部修复后,WebRTC 客户端仍有 2-3 秒"操作后延迟":
测试环境:
cargo build --release
src/state_portal.rs:455 (Portal/PipeWire 路径):
src/state_portal.rs:455
let pts = self.frames_encoded as i64; // ← 顺序计数器,0/1/2/3...
对比 src/state.rs:606-607 (wlr-screencopy 路径,正确的实现):
src/state.rs:606-607
let pts = (tv_sec as i64) * fps + (tv_usec as i64) * fps / 1_000_000; // 用真实捕获时间
Portal 路径用帧序号作为 PTS,假设帧率恒定。但 KWin 在 Wayland 下是损伤驱动(damage-driven),帧率从 1.6 fps(静态画面)到 57 fps(活跃画面)剧烈波动。
WebRTC 客户端用 RTP 时间戳(90kHz 时钟,从 PTS 转换)来判断:
静态期间(每 600ms 一帧):
客户端算法推断:"帧到达间隔 600ms,但 RTP 时间戳说应该 33ms 间隔,所以网络抖动 567ms,扩大 jitter buffer 到 567ms"。
几个周期后,jitter buffer 累积到 2-3 秒,且不自动缩小(浏览器保守策略)。
T12:58:36.442 → 36.910 (+468ms) T12:58:36.930 → 37.027 (+97ms) T12:58:37.626 → 38.227 (+599ms) ← 进入 600ms 周期(静态) T12:58:38.825 → 39.425 (+600ms) T12:58:40.025 → 40.624 (+600ms) T12:58:41.225 → 41.824 (+599ms) T12:58:41.915 → 41.932 (+17ms) ← 用户操作,burst 开始 T12:58:42.426 → 43.026 (+600ms) ← 又回到 600ms 周期
每 600ms 一帧 = 1.6 fps(光标闪烁或 widget 刷新触发的损伤)。
total_p95
send_wait_p95
enc_gap_max
frame_age_p95
state.rs:606-607 已经正确使用真实时间。所以这个 bug 是 Portal 路径特有。
state.rs:606-607
src/state_portal.rs:455 修改:
// 旧: let pts = self.frames_encoded as i64; // 新: // PipeWire 通过 SPA_META_Header 提供纳秒级真实捕获时间戳。 // 转换为 encoder time_base 单位(1/fps 秒)。 // 顺序计数器在 damage-driven 可变帧率下让客户端 jitter buffer 误判, // 累积到 2-3 秒。参考 wlr-screencopy 路径(state.rs:606)。 let pts = if frame.pts > 0 { (frame.pts * self.args.fps as i64) / 1_000_000_000 } else { self.frames_encoded as i64 // 后备:PipeWire 未提供 PTS 时 };
PipeWire PTS 已经在 cap_portal.rs:46-47, 688-709 提取并放在 PwDmaBufFrame.pts 字段里,只是被 state_portal 忽略了。
cap_portal.rs:46-47, 688-709
PwDmaBufFrame.pts
src/stats.rs:151-167 的 record_send 已经支持 capture_time 参数,但 WebRTC 路径的 record_send_from_thread 没有传递。把 frame.pts 转换为 Instant,通过 webrtc 线程传给 stats,可以让 frame_age_p95 反映真实延迟。
src/stats.rs:151-167
record_send
capture_time
record_send_from_thread
frame.pts
Instant
create_software_h264_muxer
wl-webrtc --port 56666 -v --stats > /tmp/test.log 2>&1 # 浏览器连接 WebRTC viewer # 让画面保持静态 30 秒(KWin 损伤驱动,光标闪烁触发 ~1.6 fps) # 然后大幅移动鼠标 # 期望:客户端在 2-3 秒后才显示鼠标动作
辅助验证:
chrome://webrtc-internals
jitterBufferDelay
jitterBufferEmittedCount
Critical —— 之前以为 #23 解决了延迟,但用户实测发现仍有 2-3 秒"操作后延迟"。这是产品可用性问题,影响所有 Portal 路径(KWin/KDE/GNOME)用户。
No dependencies set.
The note is not visible to the blocked user.
现象
在 #19、#23、#15、#18 全部修复后,WebRTC 客户端仍有 2-3 秒"操作后延迟":
测试环境:
cargo build --release最新版(test4 日志)触发位置
src/state_portal.rs:455(Portal/PipeWire 路径):对比
src/state.rs:606-607(wlr-screencopy 路径,正确的实现):根因分析
核心错位:PTS 单位 vs 真实时间
Portal 路径用帧序号作为 PTS,假设帧率恒定。但 KWin 在 Wayland 下是损伤驱动(damage-driven),帧率从 1.6 fps(静态画面)到 57 fps(活跃画面)剧烈波动。
客户端 jitter buffer 的误判
WebRTC 客户端用 RTP 时间戳(90kHz 时钟,从 PTS 转换)来判断:
静态期间(每 600ms 一帧):
客户端算法推断:"帧到达间隔 600ms,但 RTP 时间戳说应该 33ms 间隔,所以网络抖动 567ms,扩大 jitter buffer 到 567ms"。
几个周期后,jitter buffer 累积到 2-3 秒,且不自动缩小(浏览器保守策略)。
为什么用户感觉"操作后延迟"
验证证据(test4 日志)
1. 帧发送间隔呈"哑铃型"分布
每 600ms 一帧 = 1.6 fps(光标闪烁或 widget 刷新触发的损伤)。
2. 服务端处理无延迟
total_p95(编码总耗时)send_wait_p95enc_gap_maxframe_age_p953. wlr-screencopy 路径不受影响
state.rs:606-607已经正确使用真实时间。所以这个 bug 是 Portal 路径特有。修复方案
主修复:用 PipeWire 真实 PTS
src/state_portal.rs:455修改:PipeWire PTS 已经在
cap_portal.rs:46-47, 688-709提取并放在PwDmaBufFrame.pts字段里,只是被 state_portal 忽略了。配套修复:打通 frame_age 统计(部分覆盖 #20)
src/stats.rs:151-167的record_send已经支持capture_time参数,但 WebRTC 路径的record_send_from_thread没有传递。把frame.pts转换为Instant,通过 webrtc 线程传给 stats,可以让frame_age_p95反映真实延迟。不在本次修复范围
create_software_h264_muxer)PTS 用顺序计数器是合理的(文件不需要实时播放)复现
辅助验证:
chrome://webrtc-internals看jitterBufferDelay和jitterBufferEmittedCount关联
严重度
Critical —— 之前以为 #23 解决了延迟,但用户实测发现仍有 2-3 秒"操作后延迟"。这是产品可用性问题,影响所有 Portal 路径(KWin/KDE/GNOME)用户。