Commit Graph

36 Commits

Author SHA1 Message Date
郑博文
6e83bda8b8 perf(agent): 关键帧周期从 4s/10s 统一到 1s,缩短丢帧后的兜底恢复窗口
背景
协议 v2 与 vendor nvenc 补丁之后,KeyFrameReq 已成为**真正生效**的恢复主路径
(此前 NVENC 缺 FORCEIDR 标志,请求被静默忽略)。因此周期 IDR 的角色变了:
从「唯一恢复手段」降级为「兜底手段」,只用于防「请求连续丢失」这种极端情况。
既然是兜底,就没必要配得那么长。

问题:三处周期不一致,且都过长
- agent/src/capture.rs NVENC:gop_len = 240 帧 @60fps = 4 秒
- agent/src/capture.rs 软编:    intra_period = 300 帧 @60fps = 5 秒
- agent-core/src/lib.rs Linux:  intra_period = 300 帧 @30fps = **10 秒**

同一产品在三种编码后端上的恢复等待时间差 2.5 倍,且 Linux 端最差。

改动
- 新增 agent/src/capture.rs::INTRA_PERIOD_FRAMES = 60(Windows 两个编码器共用)
- agent-core 的 EncoderSettings::default() 改为按 max_fps 推导(1 秒对应帧数),
  避免以后调帧率时周期又漂移

代价
IDR 是整帧内编码,实测一次约 95-120KB,而 P 帧约 3-10KB。1 秒一次 IDR 约
增加 0.8Mbps 开销,在 5Mbps 码率下可接受。

验证(dump_stream 观察 150 帧)
  [  0] seq=0   IDR   ← 首帧
  [ 60] seq=60  IDR   ← 间隔精确 60 帧
  [120] seq=120 IDR
@60fps 即每 1 秒一个自动 IDR,与 gop_len=60 配置一致。
2026-10-28 16:35:44 +08:00
郑博文
8410a8de2a docs: 记录「不做中继」的最终裁决(2026-10-28)
背景:公司决定不提供中继服务器,中继方案从「暂缓」变为「排除」。

《多平台架构设计.md》
- §6 从「⚠️ 必须现在拍板」改为「✅ 已裁决」,保留原始分析但标注结论
- 新增 §6.1 裁决内容:不做中继、不预留中继接口、IPv6 一等公民、
  连接层边界仍要做
- 新增 §6.2 澄清一件常被混淆的事:**连接层边界 ≠ 中继接口**
  - 连接层边界(把「怎么连」与「连上干什么」分开):要做,与中继无关,
    因为连接方式确定会变(IP→设备码)、安卓端要复用、解码逻辑要可单测
  - 中继接口(Path::Relay 这类类型预留):不做。中继已排除,这些变体
    永远不会被构造,只会产生 dead_code 警告——零收益的预留就是纯负担
- 新增 §6.3 对产品的影响:明确哪些网络环境连不上,并说明这不是 bug
  而是已知边界,UI 必须给出可执行提示
- §10 第 1 条标记为已裁决

《打洞与信令设计.md》
- 状态更新为「已合入 main(703eb64)」
- 补充裁决确认,指向 §6.1/§6.2

补充事实:此前代码中并不存在 PathProvider / Path::Relay,唯一提到 relay 的
signaling.rs:7 是在声明「本协议不含中继」。因此本次是「确认不留」而非
「拆除预留」。
2026-10-28 16:35:32 +08:00
郑博文
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
郑博文
e778ff73cc test(agent): 新增 dump_stream 码流诊断工具
用途:不依赖 viewer 的 Minifb 窗口,直接抓 agent 下发的码流并逐帧打印
NAL 结构与解码结果,用于定位解码恢复类问题。

能力
- 打印每帧的 seq / keyframe / 字节数 / NAL 类型(SPS PPS IDR P)
- --skip-at N:模拟主控端「积压跳帧」——读出但不解码,用于验证跳帧是否
  切断参考链(实测确认:跳掉一帧后所有 P 帧持续解码失败,直到收到 IDR)
- 交叉校验帧头 keyframe 标志与码流实际 NAL 类型是否一致
- 末尾补发 KeyFrameReq 并观察 IDR 是否真的回来

用法
  cargo run --release -p agent --bin dump_stream -- --port 47800 --code 123456
  cargo run --release -p agent --bin dump_stream -- --port 47800 --code 123456 --skip-at 1

注意:KeyFrameReq 必须写在流 2(isend)——agent 的读循环只监听流 2,
写到流 1 会被静默忽略(本工具第一版踩过,误判「修复无效」)。

这个工具是发现 nvenc FORCEIDR 缺陷的直接手段,保留以备回归排查。
2026-10-28 16:05:41 +08:00
郑博文
e814cb64e7 fix(agent): vendor nvenc 并补 FORCEIDR 标志——修复运行期关键帧请求被静默忽略
根因(实测确认,非推测)
上游 nvenc 0.1.0 的 encode_picture 只设置 picture_type=NV_ENC_PIC_TYPE_IDR,
却从未设置 encode_pic_flags=NV_ENC_PIC_FLAG_FORCEIDR。NVENC 会据此忽略请求、
继续输出 P 帧。首帧之所以「看起来正常」,是因为 frame_idx==0 本来就强制 IDR,
与该字段无关——这也是该缺陷长期未被发现的原因。

证据(同一 frame_count=4,修复前后)
- 修复前:输出 NAL 首字节 0x61 → 0x61 & 0x1F = 1 = 普通 P 片
- 修复后:输出 NAL 首字节 0x65 → 0x65 & 0x1F = 5 = IDR 片

影响
这解释了构建运行手册 §6.1 记录的「静止桌面黑屏」:NVENC 路径的重编 IDR 从未
真正生效,KeyFrameReq 拿不到 IDR 就无法从解码错误中恢复。

改动
- 新增 vendor/nvenc(上游为 MIT 许可,可自由修改分发,符合商用要求)
- vendor/nvenc/src/safe/encoder.rs:picture_type=IDR 时置 ForceIDR;
  IntraRefresh 时置 ForceIntra。改动处有「本项目补丁」注释标记
- agent/Cargo.toml:nvenc 改为 path 依赖指向 vendor 版本

建议后续向上游提 PR(github.com/AlsoSylv/nvenc),以降低长期维护成本。

验证:跳帧后发 KeyFrameReq,第 3 帧即收到 IDR 并解码成功
2026-10-28 16:05:41 +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
郑博文
703eb64fc0 merge: 合入 feature/common-punch-signaling(P2P 信令协议 + 打洞探针 + 4 份架构设计文档) 2026-10-28 15:14:56 +08:00
郑博文
4533968057 merge: 合入 feature/agent-linux(测试期望值修正 + 冒烟脚本纳入单测 + cargo 网络配置文档) 2026-10-28 15:14:51 +08:00
yu
18d12baba1 fix(tools): 探针区分信令回复与打洞包,修掉打洞假阳性
问题:客户端反复注册引来的 PEER/WAIT 回复在 discover_peer 返回后才到达,
被接收线程当成「对方打进来了」,输出假的「✅ 打洞成功」。
实测形态:报告的对方地址实为信令服务器 47.98.245.66:47999,包长 22 字节
(真打洞包固定 16 字节 = PUNCHv1| + 8 字节时间戳)。

修复:
- 接收线程只认以 MAGIC 开头、且长度正确的包
- 显式排除信令服务器的 IP:端口
- 失败结论里报告被忽略的非打洞包数量,避免再次误读
- 验证记录补 4.5 节,写明 Rust 实现的对应判据
2026-10-23 18:02:14 +08:00
yu
2487e616c2 docs: 打洞设计/多平台架构/借鉴映射/验证记录
- 打洞与信令设计.md: 无中继方案的流程、成功率边界、Rust 实现约束
- 多平台架构设计.md: 6 层分层 + 平台矩阵 + 六项关键决策 + 分期路线
- 借鉴映射与落地规范.md: 每层看谁/抄什么/不抄什么 + 许可证红线(AGPL 只读)
- 打洞验证记录.md: POC 实测结论(锥形 NAT + 端口保留)与 4 个坑
- README: 文档索引补充
2026-10-23 17:32:17 +08:00
yu
d659a4e7f1 feat(tools): 打洞探针与极简 rendezvous 服务器(Python POC)
- punch_probe.py: UDP 打洞探针,支持 STUN 查映射、--peer 手工配对、--server/--room 信令配对
- rendezvous.py: 极简 UDP 信令服务器,只做房间配对与地址回推,不承载媒体
- 已在 47.98.245.66 实测跨公网注册/配对可用
2026-10-23 17:32:17 +08:00
yu
bb392507bd feat(common): 新增 P2P 打洞信令协议(无中继)
- signaling.rs: 信令消息契约 + NAT 画像/打洞前景诊断 + 证书指纹
- 明确不含 relay/TURN:打洞失败必须给出可执行原因(PunchFailReason::user_hint)
- 时间对齐不做时钟同步:服务器按两侧 RTT 下发不同 start_in_ms
- 保活按 binding_timeout_s 折半(keepalive_secs)
2026-10-23 17:32:17 +08:00
yu
bae871c78c fix(linux-agent): 修正坐标换算单测期望值 + 冒烟脚本纳入单测
- scale(32768,1920) 期望值应为 959(32768×1919÷65535=959.49 向下取整),
  原写成 960 —— 是**测试期望值错**,实现本身正确(0→0、65535→extents-1 均通过);
  集成测试因带 ±2 像素容差未能暴露,是跑单测才抓到的
- scripts/linux-smoke.sh 原先只跑 --test smoke,漏掉 --bins 单测;现两层都跑
- 手册 §8.4 说明补充单测这一层及其必要性

验证:bash scripts/linux-smoke.sh → 单测 4 passed + 端到端 2 passed
2026-10-23 15:04:56 +08:00
yu
49f85db9f9 docs: 修正 cargo 网络配置建议(代理而非镜像源)+ 更新构建命令路径
- 实测 USTC 镜像索引 cksum 与下发的 .crate 字节不一致(nvenc 0.1.0),
  源替换会触发 checksum 校验失败并改变 registry 身份;改为 [http] proxy 走官方源
- 构建命令里 D:\\wordkb\\... 的失效路径替换为可用写法
- 已实测:Windows cargo build --release 通过(agent.exe/viewer.exe/encbench.exe)
  且 cargo test -p linux-agent 在容器内 2 passed
2026-10-23 14:10:20 +08:00
yu
9fda49d9c9 Merge branch 'main' of http://43.140.215.244:7040/wangyukun/remote into feature/agent-linux 2026-10-23 14:02:44 +08:00
dev-team
ad022ffcf4 fix: v0.8.1 静止桌面黑屏 - 积压跳帧限定突发到达, 空闲重发帧不判陈旧不计E2E 2026-10-23 13:36:21 +08:00
yu
9c340813a8 feat(agent): 新增 agent-core 与 Linux 被控端(X11 + XTest,纯新增不动 agent/)
本期只在新增文件里做,**未改动 Windows agent/ 一个字节**(与 NVENC 线并行,避免争抢)。

- 新增 agent-core:平台无关会话层 —— QUIC/TLS 握手、传输调优(沿用 512KB 发送窗口
  结论)、验证码认证、会话/select 循环、latest-wins 单槽、输入分发,以及软件编码
  视频管线(openh264)。同步跟进协议新增的 Pong{lost,sent} 下行丢包统计字段
- 新增 linux-agent:X11 GetImage 采集 + 整帧变更检测;XTest 注入 + 扫描码→X11
  keycode 映射(主键区 = scancode+8,E0 扩展键必须查表,含单元测试)
- 协议零改动:复用 Set-1 扫描码与虚拟桌面归一化坐标,viewer 无需任何修改
- 新增端到端冒烟测试 tests/smoke.rs 与 scripts/linux-smoke.sh(自建 Xvfb,无需
  图形桌面):采集→编码→QUIC 解帧、认证(含错误码拒绝)、Ping/Pong(含丢包字段)、
  静止桌面关键帧恢复、输入注入实证(注入后从另一条 X11 连接读回指针真实坐标)
- 该关键帧用例做过变异测试:把修复退回会失败、恢复后通过,确认断言有效
- .gitattributes:shell 脚本强制 LF(Windows checkout 成 CRLF 会导致容器内无法执行)
- 手册:修正已失效的工具链路径;补充 cargo 镜像;新增第 8 节(Linux)与 6.1 节
  (三条待修问题,含两条 Windows 侧仍存在的关键帧缺陷)

验证:
- Linux(容器, USTC 源 + Xvfb): cargo test -p linux-agent 通过,2 passed
- Windows: 本期未改 agent/,default-members 未变,`cargo build --release` 行为不变
2026-10-23 13:31:08 +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
68b2ddc59a merge: E2E 排队修复(64KB 窗口 + 拥塞跳帧) 2026-10-23 12:48:17 +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
bd7ca29945 merge: HUD 常显修复 2026-10-23 10:45:47 +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
e00175385d merge: 修 HUD 停刷与帧率不稳(单槽 latest-wins + GOP 自愈) 2026-10-23 10:31:31 +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
82ec166f23 merge: NVENC 硬编(4ms/帧@1080p,编码瓶颈解除) 2026-10-23 10:18:29 +08:00
dev-team
ddbb53d41a feat(agent): NVENC 硬编接入(GPU 零拷贝 ARGB 直通,软编自动兜底)
- 编码器自动选择:NVENC(H264 High/CBR 5Mbps/UltraLowLatency/无B帧/按需IDR)→ openh264 兜底
- Duplication 增加 GPU 中间纹理(B8G8R8A8 DEFAULT),NVENC register_resource_dx11 直读,颜色转换完全省略
- KeyFrameReq → encode_picture(picture_type=IDR),静止 500ms 重编码 P 帧
- windows 0.58→0.62(nvenc crate 依赖 0.62,运行时 dlopen nvEncodeAPI64.dll 零 SDK 依赖)
- 实测(GTX 1650 Ti 回环):encode 41ms→4ms/帧(10x),convert 0ms,viewer 解码正常
2026-10-23 10:18:29 +08:00
dev-team
94c45aff3f merge: FPS 调优(60fps上限/零拷贝/E2E 修复) 2026-10-23 10:02:42 +08:00
dev-team
e7472fa12f perf(agent): 60fps上限/零拷贝转换/多线程编码/发送缓冲512KB + encbench基准
- capture: min_frame 33→16ms,RC 60fps;with_mapped 映射窗口内直接 BGRA→YUV(行距连续零拷贝)
- capture: num_threads(4) + 每120帧输出 convert/encode 耗时(实测:convert 2ms,encode 41ms/帧真实工况)
- agent: send_window 2MB→512KB,E2E 排队天花板 ~0.15s(修复 E2E 633ms 红)
- encbench: 1080p 编码基准(合成 5ms/帧,真实工况 41ms/帧 → 软编 1080p 上限 ~24fps,60fps 需 NVENC)
- tools/bench.md
2026-10-23 10:02:41 +08:00
dev-team
881341be3a merge: B1 联调基线(HUD 扩展 + bench 脚本) 2026-10-23 09:47:10 +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
95049b6642 docs: B侧设计文档(联调基线/码率自适应/打洞中继/多屏文件) 2026-10-23 09:34:21 +08:00
dev-team
e0a076d570 docs: 两人分工方案定稿 2026-10-23 09:31:52 +08:00
dev-team
155405fbcc docs: README 2026-10-23 09:21:07 +08:00
dev-team
4ebc5c1602 docs: 团队协作规范(分支策略/分工/合并规则) 2026-10-23 09:18:41 +08:00
dev-team
61e8a75cdd init: MVP remote desktop (agent/viewer/common) with H.264 pipeline 2026-10-23 09:17:55 +08:00