CD Code Digest Hub /CS:GO

Demo 录制回放与 GOTV

7 分钟阅读 7 个章节

Demo 与 GOTV(代码中称 HLTV)都建立在同一个事实上:客户端收到的网络包已经包含重建该视角画面所需的状态。Demo 把这些包写进文件;GOTV 则在服务器内部挂一个“特殊客户端”,把它收到的快照延迟后再转播给观众。

相关页面:网络通道与协议 · 实体复制 · 主循环与 Tick

相关源码文件#

  • public/demofile/demoformat.h:demo 命令类型
  • engine/cl_demo.cpp:CDemoRecorder、CDemoPlayer
  • engine/hltvserver.cpp:CHLTVServer
  • game/server/hltvdirector.cpp:导播与广播延迟

1. Demo 文件#

Demo 由一串带 tick 的命令组成,命令类型包括签入数据、网络包、同步 tick、控制台命令、用户命令、数据表、停止、自定义数据、字符串表:public/demofile/demoformat.h:28-47。

录制:

  • 网络通道在每个包处理完毕后调用录制器 RecordPacket:engine/net_chan.cpp:2946-2951。
  • WriteMessages 在签入期间写 dem_signon,之后写 dem_packet,每条记录包含命令头、玩家视角信息 democmdinfo_t、网络通道序号与原始消息字节:engine/cl_demo.cpp:875-926。
  • 客户端生成的每条 CUserCmd 也会被录制:engine/cl_main.cpp:2795-2800、engine/cl_demo.cpp:526。

回放:StartPlayback 打开文件后,ReadPacket 以 netpacket_t 的形式把记录交还给客户端网络层:engine/cl_demo.cpp:2157、engine/cl_demo.cpp:1480。回放 HLTV/Replay demo 时 CL_Move 不发送命令:engine/cl_main.cpp:2749-2765;timedemo 或跳转期间即使本帧没有 tick 也会处理客户端数据:engine/host.cpp:4315-4321。

2. GOTV 的数据来源#

GOTV 主代理在服务器内部表现为一个 m_bIsHLTV 客户端。它的快照不走网络发送,而是:打包声音与临时实体到 HLTV 缓冲,把 CClientFrame 交给 CHLTVServer::AddNewDeltaFrame,并立即伪确认该帧:engine/sv_client.cpp:1619-1652、engine/hltvserver.cpp:1822。为了让 GOTV 能看到所有本应只发给本人的数据,本地玩家专属发送表也会把 GOTV 客户端加入接收者:game/server/player.cpp:8218-8227。打包阶段也会为 HLTV 保存每个实体的 PVS 信息:engine/sv_packedentities.cpp:561-562。

3. 广播延迟#

  • tv_delay 默认 10 秒:game/server/hltvdirector.cpp:24。
  • 导播每次 UpdateSettings 计算广播 tick = 当前 tick − TIME_TO_TICKS(tv_delay)(低于最小延迟时视为 0):game/server/hltvdirector.cpp:214-249;GetDirectorTick 返回该值:game/server/hltvdirector.cpp:198-202。
  • CHLTVServer::UpdateTick 在主代理上以导播 tick 为准,展开累积的增量帧,取最接近的帧作为当前帧,并恢复该时刻的字符串表:engine/hltvserver.cpp:2785-2830。

4. 每帧运行#

引擎在每帧的 tick 循环之后运行所有 HLTV 服务器:engine/host.cpp:4289-4293。CHLTVServer::RunFrame 先处理作为中继(relay)时与上游的连接,再更新 tick、处理观众连接、统计并给观众发送消息:engine/hltvserver.cpp:2738-2783。主代理与中继分别由 StartMaster 与 StartRelay 启动:engine/hltvserver.cpp:605、engine/hltvserver.cpp:875。服务器发送快照时若存在有观众或正在录制的 GOTV,就不会因为“只有 GOTV”而跳过拍快照:engine/sv_main.cpp:2151-2161。

5. 击杀回放(HLTV Replay)#

服务器允许普通客户端切换到“HLTV 回放”状态,此时它直接使用 HLTV 已经预打包的流:打包阶段跳过这些客户端,发送阶段改调 SendHltvReplaySnapshot:engine/sv_packedentities.cpp:576-577、engine/sv_packedentities.cpp:627-631、engine/sv_client.cpp:1614-1617。客户端通过 clc_HltvReplay 请求:engine/sv_client.cpp:360。

约束与边界#

  • 未分析 HLTV 的观众连接/中继协议、广播到 HTTP(m_bBroadcastActive)及 hltvdemo.cpp 的服务器端录制细节。
  • REPLAY_ENABLED 宏下的 Replay 系统与 HLTV 并列存在:engine/sv_client.cpp:1654-1685、engine/baseclient.h:121-125。但全仓检索不到任何 #define REPLAY_ENABLED,VPC 工程中也没有这个宏,所以本快照的构建不会编译这些分支(基于检索)。它与第 5 节的 CS:GO 击杀回放(HLTV Replay)是两套不同机制。