在 #24 修复后(PTS 已正确传播),浏览器 jitter buffer 在活跃期间仍然累积,导致:
jitterBufferDelay(chrome://webrtc-internals):
jitterBufferDelay
10 秒尾随现象(关键新症状):
之前 #24 只解决了静态期 PTS,活跃期问题仍在。
WebRTC encoder 初始化用 enc.set_time_base(ff::Rational::new(1, fps as i32))(src/avhw.rs:1810 等)。30fps 时每个 PTS tick = 33ms。
enc.set_time_base(ff::Rational::new(1, fps as i32))
src/avhw.rs:1810
src/state_portal.rs:581:
src/state_portal.rs:581
let ticks_i128 = (relative_ns.saturating_mul(i128::from(self.args.fps))) / NS_PER_SEC;
KWin 在活跃期间以 60fps 送帧(每 16.7ms):
多帧映射到同一 tick。state_portal.rs:584-588 的 monotonicity guard 强制顺序化:
state_portal.rs:584-588
if pts <= last { pts = last.checked_add(1).unwrap_or(last); }
结果:0, 1, 2, 3, 4, 5, ...(顺序)。
1 秒真实时间 @ 60fps 捕获 = 60 帧 60 帧 sequential RTP(每帧间隔 3000 = 33ms) RTP 时间跨度:60 × 33ms = 1.98 秒 RTP 时间
1 真实秒变成 2 RTP 秒。
浏览器按 RTP 速率(半速)播放 → 帧到达比播放快 → 缓冲累积。
用户移鼠标 10 秒(60fps 捕获): 真实时间:10 秒 RTP 时间:20 秒 浏览器累积缓冲:10 秒
用户停止后,服务端立即停止发送(damage 停止 + hash dedup),但浏览器慢慢排空累积的 10 秒缓冲。这正是用户报告的现象。
静态期间每 600ms 一帧,远超 33ms 粒度。compute_capture_pts 返回准确的 18 ticks 跳变,RTP 跳变 54000,精确反映 600ms 间隔。浏览器看到真实时间 = RTP 时间,jitter 极小。
compute_capture_pts
如果 50% 时间活跃(60fps 捕获) + 50% 时间静态(1.6fps):
活跃期间每帧 RTP 间隔 3000(33ms),但真实到达间隔是 33ms(因编码器 30fps 上限)。 等等,这看起来 RTP 和真实时间匹配?
不对。问题在于单帧内的 PTS 计算:
具体:test8 有 2067 个 RTP=3000 跳变,61 个 RTP=54000 跳变。
RTP 时间 > 真实时间 = 浏览器播放慢于真实 = 缓冲累积。验证假设。
(误差可能因为部分帧的 PTS 计算确实接近真实,但整体趋势 RTP 时间膨胀约 7%。长时间活跃会话会放大这个差异。)
src/avhw.rs:1810(WebRTC 路径的 create_software_h264_encoder):
create_software_h264_encoder
// 旧: enc.set_time_base(ff::Rational::new(1, fps as i32)); // 新: // 90kHz 时钟与 RTP 直接对齐,避免 1/fps 粒度量化。 // 在 60fps 捕获下,1/fps time_base 会让 16.7ms 间隔的两帧映射到同一 tick, // 被 monotonicity guard 强制顺序化,导致 RTP 时间膨胀、浏览器缓冲累积。 // 90kHz 提供 11μs 粒度,完全足够 WebRTC 可变帧率。 enc.set_time_base(ff::Rational::new(1, 90_000));
// 旧: let ticks_i128 = (relative_ns.saturating_mul(i128::from(self.args.fps))) / NS_PER_SEC; // 新: // 90kHz 时钟:1 秒 = 90000 ticks,1 ns = 90/1e9 ticks const RTP_CLOCK_HZ: i128 = 90_000; let ticks_i128 = (relative_ns.saturating_mul(RTP_CLOCK_HZ)) / NS_PER_SEC;
src/webrtc.rs 现有实现:
src/webrtc.rs
// 旧: pub fn rtp_timestamp_from_pts_ticks(pts_ticks: i64, fps: u32) -> u32 { let fps_safe = (fps.max(1) as u64).max(1); let pts_u64 = (pts_ticks.max(0) as u64).min(u64::MAX / TICKS_PER_SECOND); pts_u64.saturating_mul(TICKS_PER_SECOND) / fps_safe as u32 }
新签名(去掉 fps 参数,因为 PTS 已经是 90kHz):
// 新: pub fn rtp_timestamp_from_pts_ticks(pts_ticks: i64) -> u32 { pts_ticks.max(0) as u32 // 直接是 RTP 时间戳 }
或者直接 inline 到 write_h264_frame,删除独立函数。
MP4 用 create_software_h264_muxer(另一个 encoder 构造器),保持 1/fps time_base。理由:
create_software_h264_muxer
1/fps
wl-webrtc --port 56666 -v --stats > /tmp/test.log 2>&1 # 浏览器连接,chrome://webrtc-internals 观察 # 1. 静态 30 秒(jitterBufferDelay 应该 10-20 ms) # 2. 大幅移动鼠标 10 秒(jitterBufferDelay 飙到 1000+ ms) # 3. 停止移动(客户端继续显示鼠标 10 秒)
Critical —— 即使所有之���的修复都生效,产品在用户活跃操作时仍有 1-2 秒延迟和 10 秒尾随。严重影响可用性。
No dependencies set.
The note is not visible to the blocked user.
现象
在 #24 修复后(PTS 已正确传播),浏览器 jitter buffer 在活跃期间仍然累积,导致:
jitterBufferDelay(chrome://webrtc-internals):10 秒尾随现象(关键新症状):
之前 #24 只解决了静态期 PTS,活跃期问题仍在。
根因分析
1. encoder time_base = 1/fps 的精度限制
WebRTC encoder 初始化用
enc.set_time_base(ff::Rational::new(1, fps as i32))(src/avhw.rs:1810等)。30fps 时每个 PTS tick = 33ms。2. compute_capture_pts 的整数除法量化
src/state_portal.rs:581:KWin 在活跃期间以 60fps 送帧(每 16.7ms):
多帧映射到同一 tick。
state_portal.rs:584-588的 monotonicity guard 强制顺序化:结果:0, 1, 2, 3, 4, 5, ...(顺序)。
3. 致命错位:真实时间 vs RTP 时间
1 真实秒变成 2 RTP 秒。
浏览器按 RTP 速率(半速)播放 → 帧到达比播放快 → 缓冲累积。
用户停止后,服务端立即停止发送(damage 停止 + hash dedup),但浏览器慢慢排空累积的 10 秒缓冲。这正是用户报告的现象。
4. 为什么静态期没问题
静态期间每 600ms 一帧,远超 33ms 粒度。
compute_capture_pts返回准确的 18 ticks 跳变,RTP 跳变 54000,精确反映 600ms 间隔。浏览器看到真实时间 = RTP 时间,jitter 极小。数学验证(test8 数据)
如果 50% 时间活跃(60fps 捕获) + 50% 时间静态(1.6fps):
活跃期间每帧 RTP 间隔 3000(33ms),但真实到达间隔是 33ms(因编码器 30fps 上限)。
等等,这看起来 RTP 和真实时间匹配?
不对。问题在于单帧内的 PTS 计算:
具体:test8 有 2067 个 RTP=3000 跳变,61 个 RTP=54000 跳变。
RTP 时间 > 真实时间 = 浏览器播放慢于真实 = 缓冲累积。验证假设。
(误差可能因为部分帧的 PTS 计算确实接近真实,但整体趋势 RTP 时间膨胀约 7%。长时间活跃会话会放大这个差异。)
修复方案
主修复:encoder time_base 改成 90kHz
src/avhw.rs:1810(WebRTC 路径的create_software_h264_encoder):compute_capture_pts 返回 90kHz ticks
src/state_portal.rs:581:rtp_timestamp_from_pts_ticks 简化
src/webrtc.rs现有实现:新签名(去掉 fps 参数,因为 PTS 已经是 90kHz):
或者直接 inline 到 write_h264_frame,删除独立函数。
MP4 路径不动
MP4 用
create_software_h264_muxer(另一个 encoder 构造器),保持1/fpstime_base。理由:期望效果
复现
关联
严重度
Critical —— 即使所有之���的修复都生效,产品在用户活跃操作时仍有 1-2 秒延迟和 10 秒尾随。严重影响可用性。