# 生产架构说明 ## 目标 本项目用于把一个逻辑尺寸为 `14880 x 3510` 的 LED 大屏内容,拆分到两台 GPU 输出服务器本地渲染。 生产形态: - 控制服务运行 FastAPI,负责场景、动作、时间同步、ACK、状态监控。 - 左 GPU 服务器打开 `/output/left`,只显示左半屏 tile。 - 右 GPU 服务器打开 `/output/right`,只显示右半屏 tile。 - 每台 GPU 服务器输出 4 路 4K 到拼控或 LED 控制器。 - 拼控或 LED 控制器完成最终物理拼接。 WebSocket 不传大视频流,只传状态、命令、时间、ACK 和性能指标。画面由两台 GPU 服务器本地渲染。 ## 逻辑拓扑 ```text 控制台 / http://control:8000/ | v FastAPI 控制服务 场景管理 / WebSocket Hub / SyncCoordinator / 状态接口 | | prepare/commit/action status/telemetry/ACK | | ------------------------------- | | v v 左 GPU 输出服务器 右 GPU 输出服务器 /output/left /output/right left tile 本地渲染 right tile 本地渲染 4 路 4K 输出 4 路 4K 输出 | | ---------> 拼控 / LED 控制器 <- 14880 x 3510 ``` ## Tile 切分 完整逻辑画面: ```text |-------------------------- 14880 --------------------------| |------------ left 7440 ------------|-------- right 7440 ----| ``` `config/tiles.json` 定义: ```text left: x=0 y=0 width=7440 height=3510 right: x=7440 y=0 width=7440 height=3510 ``` 输出端页面使用完整逻辑大屏坐标系,再根据自己的 tile 做视口偏移。Three.js 项目中建议使用: ```js camera.setViewOffset( fullWidth, fullHeight, tile.x, tile.y, tile.width, tile.height, ); ``` ## 每台服务器 4 路 4K 每台 GPU 服务器建议配置为 `2 x 2` 逻辑桌面: ```text 7680 x 4320 ``` 四路输出映射: ```text 1: x=0, y=0, 3840 x 2160 2: x=3840, y=0, 3840 x 2160 3: x=0, y=2160, 3840 x 2160 4: x=3840, y=2160, 3840 x 2160 ``` 单侧业务 tile 是 `7440 x 3510`,可以放入该逻辑桌面。 ## 同步协议 ### 场景切换 控制台调用: ```text POST /api/scenes/{scene_id}/switch ``` 服务端生成同一个 `command_id`,先广播: ```text prepare_scene(command_id, apply_at_ms, state, scene) ``` 输出端收到 `prepare_scene` 后: 1. 预加载资源。 2. 等待至少两个 RAF,让浏览器完成布局和首帧准备。 3. 如果存在 `window.ledPlatformPrepareScene`,等待业务方的真实预渲染/超分完成。 4. 回 ACK:`status=prepared`。 服务端 `SyncCoordinator` 等 `target_tiles` 中的 `left` 和 `right` 都返回 `prepared` 后,才广播: ```text commit_scene(command_id, apply_at_ms, state, scene) ``` 输出端收到 `commit_scene` 后,等到统一的 `apply_at_ms` 再切换画面,并回 ACK: ```text status=committed ``` 这样谁渲染慢就等谁,两个输出端都准备好后才同步放行。 ### 局部动作 控制台调用: ```text POST /api/actions ``` 支持动作: ```text page.next page.prev energy.mode security.alert timeline.pause timeline.resume ``` 局部动作目前直接按 `apply_at_ms` 调度并回 ACK: ```text status=action_committed ``` 如果后续某些局部动作也需要超分或重资源准备,可以复用场景切换的 prepare/barrier/commit 模式。 ## 画面回显 控制台内置两个只读预览 iframe,并把它们无缝拼成一块完整画面: ```text /output/left?preview=1 /output/right?preview=1 ``` 预览模式通过 `/ws/admin` 接收状态和命令,不连接 `/ws/output/{tile_id}`,因此: - 不注册为真实输出节点。 - 不发送 `prepared`、`committed`、`telemetry` ACK。 - 不会提前释放同步 barrier。 - 只用于控制台观察画面。 真实生产输出端仍然打开: ```text /output/left /output/right ``` ## 时间同步 输出端通过 `clock_ping` / `clock_pong` 估算服务端时间: ```text serverNowMs = Date.now() + serverOffsetFromDateMs ``` RTT 使用 `performance.now()` 估算,服务端时间偏移使用客户端 wall clock 的发送时间和返回时间中点估算。 渲染和动画应基于统一时间轴: ```js const t = serverNowMs() - sceneStartedAtMs; renderSceneAt(t); ``` 不要让左右输出端各自累计本地 delta: ```js // 不推荐 animation += localDeltaTime; ``` ## 状态观测 控制台显示: - 输出节点数量。 - 每个输出节点 FPS、frame time、RTT、clock offset。 - 最近命令回显。 - 每个 tile 的 `prepared`、`committed`、`late`、`error` 状态。 - barrier 放行耗时。 接口: ```text GET /api/sync/status GET /healthz ``` ## 为什么不传视频流 单侧半屏未压缩数据量约为: ```text 7440 x 3510 x 4 bytes x 60 fps ~= 6.3 GB/s ``` 左右两侧合计超过 `12 GB/s`。普通网络视频流还需要编码、传输、解码,会带来延迟、画质损失、文字细线压缩失真,以及新的编码/解码同步问题。 本系统选择“控制服务发命令,输出端本地渲染”,更适合高分辨率 LED 墙。 ## 生产验收指标 建议关注 P95/P99: ```text 60 FPS: P95 frame time < 16.67ms P99 frame time < 20ms 30 FPS: P95 frame time < 33.33ms P99 frame time < 40ms ``` 同步指标: ```text WebSocket RTT < 10ms clock offset < 5ms left/right 都返回 prepared left/right 都返回 committed 或 action_committed 不得频繁出现 late ACK ``` 硬件建议: - 两台输出服务器尽量使用同型号 GPU、同驱动、同浏览器版本。 - 电源策略设置为高性能。 - 关闭系统休眠、屏保、自动更新弹窗。 - 拼控侧开启输入缓存、帧同步或延迟对齐能力。 - 生产环境使用 PTP 或内网 NTP。 ## 边界 能保证: - 两边业务状态一致。 - 两边都准备好后才释放场景提交。 - 两边按统一服务端时间点提交。 - 动画长期不漂移。 - 命令和性能可观测。 不能单独保证: - 两张不同 GPU 的物理扫描完全同相。 如果必须达到广播级帧同步,需要硬件层支持 Genlock / Frame Lock / 专业视频墙控制器。