郑博文
|
192513dd2a
|
fix(viewer): 跳帧后进入「等待 IDR」状态,不再把必然失败的 P 帧喂给解码器
问题
H.264 的 P 帧依赖前一帧的参考图像。主控端的积压跳帧逻辑(读出但不解码)
会切断参考链,此后所有 P 帧都解不出来,且不会自行恢复——必须收到 IDR。
旧实现跳帧后只是 continue,后续 P 帧仍被送进 decoder,导致:
- 每帧解码失败一次 → stderr 刷屏「decode failed」
- 每次失败都触发一次 KeyFrameReq → 请求风暴
- 解码器反复在错误状态里挣扎
实测(修复前,10 秒会话):decode failed 刷屏数十条。
修复
引入 awaiting_idr 状态,语义是「参考链已断,等 IDR 重新同步」:
- 跳帧时置位,并请求关键帧
- 置位期间跳过所有 P 帧(这是正确性要求,不是优化)
- 收到 hdr.keyframe=true 的帧才清位并恢复解码
- 解码出错时也置位
- 初始值为 true:入会时已发 KeyFrameReq,拿到第一个 IDR 前不该硬解
该实现依赖协议 v2 新增的 hdr.keyframe 字段(从实际码流 NAL 类型判定),
因此必须在协议改动之后合入。
HUD 新增 STATE 行显示 OK / RECOVER。RECOVER 期间画面冻结是**预期行为**,
没有这个指示用户会误以为软件卡死。
验证(同机 agent+viewer,25 秒)
- decode failed:0 次(修复前刷屏)
- stderr 总行数:3(仅首帧日志 + 2 条积压提示)
- 窗口稳定无假死
已知局限(下一步)
本修复解决的是「跳帧后不再做无用功」,但跳帧本身仍会造成画面冻结直到
IDR 到达。NVENC 的 gop_len=240、@60fps 即 4 秒才有一个自动 IDR,恢复窗口
偏长。缩短 GOP 或降低跳帧触发频率是后续优化项。
|
2026-10-28 16:10:40 +08:00 |
|
郑博文
|
9e50bc6237
|
feat(common): 协议 v2——版本号 + 帧头 seq/keyframe + 长度下界校验
为接入安卓主控做准备,先把三端共享契约改到能承载多客户端。改动集中在
common/src/lib.rs,三端适配在同一提交内完成以保证可编译。
协议版本(PROTO_VERSION=2)
- ControlMsg::Auth/AuthOk 增加 proto_version 字段;认证顺序改为「先版本后验证码」:
版本不一致时字段布局可能不同,验证码的解析结果不可信
- 任一端版本不符即 AuthFail 并明确报错,把「连上了但行为诡异」变成「连不上且提示升级」
视频帧头(VideoFrameHeader)
- 新增 seq: u64(每次实际发送递增,含静止桌面重发)与 keyframe: bool
- keyframe 由新增的 common::h264_contains_idr() 从实际 Annex-B 码流判定,
不依赖编码器 API 推断——NVENC 与 openh264 规则不同,实际输出才是唯一可信来源
- 该字段是 MediaCodec 判断解码器重置时机的必要条件,避免客户端自己写 NAL 扫描
不可信输入防护
- read_video_frame 补齐长度下界:total 必须 >= 4,hlen 必须落在 [1, total-4]
- 旧实现只校验上界,构造 total<4 的帧会让接收端在 buf[0..4] 处 panic(进程级DoS)
- 测试从 0 增至 11 个,覆盖序列化往返与 6 类越界/损坏输入
顺带修复(与上述契约直接相关)
- 新增 common::split_video_frame():静止桌面重发时复用载荷但重建帧头,
否则 seq/ts_ms 会停在上次发送时刻,破坏 seq 单调递增契约
- agent-core 与 agent 两端的静止重发路径均改用该函数
验证:cargo test -p common → 20 passed(9 signaling + 11 新增)
|
2026-10-28 16:05:41 +08:00 |
|
dev-team
|
ad022ffcf4
|
fix: v0.8.1 静止桌面黑屏 - 积压跳帧限定突发到达, 空闲重发帧不判陈旧不计E2E
|
2026-10-23 13:36:21 +08:00 |
|
dev-team
|
8dd18a06a1
|
fix: E2E 积压 - 陈旧帧跳过解码排空 QUIC 队列 + rgb 缓冲复用 + 版本号升 v0.8
|
2026-10-23 13:27:18 +08:00 |
|
dev-team
|
04c02d9ced
|
fix: 启动黑屏 - 静止桌面响应KeyFrameReq + 首帧强制IDR + 入会主动请求关键帧
|
2026-10-23 13:12:27 +08:00 |
|
dev-team
|
168f600fe1
|
perf(agent): E2E 排队修复——发送窗口 512KB→64KB + 拥塞反馈自动跳帧
- 实测数据:FPS 61 / RTT 7ms / E2E 618ms → 延迟全在发送侧排队:
512KB 窗口 ÷ WiFi 实际 ~3Mbps 吞吐 ≈ 1.3s 排队上限,latest-wins 管不到已进 QUIC 的字节
- send_window 64KB:排队硬上限 ~170ms@3Mbps;突发吞吐 64KB/7ms≈72Mbps 不受限;
连接级窗口,流 2 输入数据量极小不受影响
- 拥塞反馈跳帧:send write 耗时 >20ms(单帧预算 1.2 倍) → 置位标志 → 采集线程
跳过当前帧(NVENC 少输入一帧,参考链连续无花屏),注入率自适应匹配网络吞吐;
capture 日志新增 skipped 计数
- viewer 标题版本 v0.7-LowLatency
- 回环实测:NVENC 4ms/帧、skipped 0(回环无拥塞符合预期)、零错误
|
2026-10-23 12:48:16 +08:00 |
|
dev-team
|
00b318af8b
|
fix(viewer): HUD 每秒强制呈现(不依赖视频帧)+ 标题栏版本号
- HUD 此前仅在 dirty(收到新帧)时绘制,视频流异常/帧被丢弃时面板
永不出现,且掩盖了排障信息;改为每秒统计刷新时强制 update_with_buffer
- 窗口标题加 v0.6-HUD 版本标记,用于识别误跑旧版 exe(排障关键)
- 回环实测:NVENC 4ms/帧、解码 1920x1080 匹配、14s 零错误
|
2026-10-23 10:45:46 +08:00 |
|
dev-team
|
9c72aecc3b
|
fix(viewer): 帧管线 latest-wins 单槽(修 NVENC 60fps 下积压假死/HUD 停刷)+ GOP 周期自愈
- viewer: 无界 mpsc → Mutex 单槽 latest-wins。根因:NVENC 60fps 到达率 > 渲染
吞吐(~55fps),无界队列无限积压 → 延迟线性增长 + 渲染 drain 阻塞消息泵 →
窗口假死、HUD 停刷(软编 20fps 时代掩盖,NVENC 后暴露)
- viewer: 解码帧尺寸与窗口解耦(blit 逐行拷贝,防 NVENC 对齐尺寸不匹配);
首帧打印解码尺寸诊断
- agent: NVENC gop_len 0xffffffff→240(60fps 下 4s 周期 IDR 自愈恢复点,
配合 KeyFrameReq 即时恢复,消除长时间冻结锯齿)
- 回环实测:NVENC 4ms/帧满速、解码 1920x1080 匹配、14s 零错误
|
2026-10-23 10:31:30 +08:00 |
|
dev-team
|
7dbb04da17
|
feat(hud): B1 基线指标——抖动/帧间隔p95/丢包率/解码失败计数 + 测试脚本
- common: Pong 扩展 lost/sent(agent 发送方向 QUIC 计数,经交叉评审项)
- agent: Pong 附带 conn.stats().path 丢包统计
- viewer: Stats 加 GapRing(64)/jitter EMA/dec_fail;HUD 新增 JIT/P95/LOSS/DECFAIL 四行,% 字形
- tools/bench.md: 三场景基线测试脚本
|
2026-10-23 09:47:09 +08:00 |
|
dev-team
|
61e8a75cdd
|
init: MVP remote desktop (agent/viewer/common) with H.264 pipeline
|
2026-10-23 09:17:55 +08:00 |
|