跳到主要内容 开源 dem2video:Quake 演示文件转视频工具详解 | 极客日志
C AI 算法
开源 dem2video:Quake 演示文件转视频工具详解 dem2video 是一款基于 Quake 源代码的开源转换工具,可将 .dem 演示文件高效转换为 MP4、AVI 等视频格式。它通过解析命令流与状态快照重建画面,利用多线程流水线提升性能,并集成 FFmpeg 支持多种编码输出。工具支持命令行批量处理、自定义分辨率与帧率,适用于游戏录屏、战术分析及 AI 训练数据构建。作为开源项目,其架构透明且可定制,解决了 Demo 文件无法直接播放的问题。
dem2video 是一款基于 Quake 原始源代码的开源转换工具,可将 Quake 游戏的 demo 文件高效、高保真地转换为 MP4、AVI 等视频格式。该工具具备高速解析、格式兼容、命令行操作和参数自定义等核心功能,支持自动化批量处理,适用于游戏录屏、精彩片段保存与分享。
dem2video:从 Quake 回放到专业视频的开源神器
Quake 演示文件(.dem)记录了游戏运行时的操作指令与状态快照,但无法直接播放。dem2video 能将二进制日志转换为流畅的视频,支持视角标记与 UI 叠加,甚至批量处理成 AI 训练集。作为开源项目,它不依赖完整游戏运行环境,仅靠核心解码逻辑 + OpenGL/DirectX 后端就能完成画面捕捉,兼容从 Quake III Arena 到现代衍生作品的各种版本。
数据驱动的视觉重建:Quake demo 文件的结构密码
.dem 文件更像是'时间序列化的网络通信日志',按时间顺序排列的操作流水账。每一条记录里,要么是玩家按下了 W 键、鼠标转了多少度(客户端命令),要么是服务器告诉你:'你现在的位置是 (128, 64, 32),前方有个敌人刚开火。'(状态快照)。
帧结构的秘密:命令流 vs 快照数据 typedef struct {
int32_t frameNumber;
uint32_t timeMsec;
uint8_t isCommand;
uint32_t dataSize;
unsigned char data[];
} demoFrame_t;
这不是图像帧,而是逻辑帧。isCommand == 1 表示这是用户的输入指令;否则就是服务器返回的世界状态更新。所有字段都是小端字节序(Little Endian)。
字段名 类型 是否关键 说明 frameNumber int32_t ✅ 排序用,不影响渲染 timeMsec uint32_t ✅ 决定画面生成时机 isCommand uint8_t ✅ 区分输入与状态 dataSize uint32_t ✅ 控制读取长度 data byte[] ✅ 变长负载
实际解析时,dem2video 使用内存映射技术(mmap)加载大文件,避免一次性读入导致 OOM。
时间轴上的舞步:如何保持节奏同步? 既然没有现成的画面,那就得自己造帧。目标是输出 60fps 的视频,每 16.67ms 就得画一张图。dem2video 的主循环逻辑如下:
def playback_loop (demo_frames, target_fps=60 ):
clock = 0
frame_interval = 1000 / target_fps
last_snapshot = None
for frame in demo_frames:
while clock < frame.timeMsec:
if last_snapshot:
interpolated_state = interpolate(last_snapshot, frame, clock)
render_frame(interpolated_state)
clock += frame_interval
if frame.isCommand:
process_user_command(frame.data)
else :
last_snapshot = deserialize_entity_state(frame.data)
这段代码维护本地时钟,判断是否到了该出帧的时候,如果还没收到新状态,就用前后两帧插值得到中间态。线性插值或球面线性插值(slerp)用于处理旋转量,防止视角抖动。
sequenceDiagram
participant Parser as Parser
participant Clock as Clock
participant Renderer as Renderer
Parser->>Clock: 提取 frame.timeMsec
loop 时间推进
Clock-->>Renderer: clock += dt
alt clock >= next_event_time
Parser->>Renderer: 提交新状态
else
Renderer->>Renderer: 执行插值渲染
end
end
解析器只管推时间轴,渲染器根据本地时钟决定要不要画图。这种解耦设计让视频生成不再受原始记录频率限制。
网络模拟器上线:虚拟客户端是如何工作的? Quake 引擎用的是 UDP-like 不可靠传输,状态更新采用增量压缩(delta encoding)。dem2video 引入了'关键帧'机制:每隔若干帧插入一次完整状态快照,作为恢复基准点。同时维护一个持久化的'世界状态数据库',每次收到快照就做一次差分更新。
void ApplyDeltaSnapshot (entityState_t *from, entityState_t *to, const byte *data) {
int bits = ReadBits(&data, 8 );
if (bits & U_ORIGIN0) to->origin[0 ] = ReadCoord(data);
if (bits & U_ORIGIN1) to->origin[1 ] = ReadCoord(data);
if (bits & U_ORIGIN2) to->origin[2 ] = ReadCoord(data);
if (bits & U_ANGLES0) to->angles[0 ] = ReadAngle(data);
}
用户命令会被重新注入虚拟客户端的消息队列,驱动摄像机视角、武器切换等行为。
高效转换的核心架构:不只是'读→渲→编'那么简单 真正的高性能转换系统必须模块化、分层解耦。dem2video 的核心架构分为三层:
解码层(Decoding Layer) :负责从 .dem 中提取原始数据;
状态管理层(State Management Layer) :同步游戏世界状态;
渲染层(Rendering Layer) :调用引擎 API 绘制画面。
解码层 vs 渲染层:职责分明才高效 解码层不参与图形输出,只干一件事:把二进制数据变成结构化的内存对象。
typedef struct {
int frame_number;
float time_stamp;
usercmd_t cmd;
entity_state_t *ents;
vec3_t vieworg;
vec3_t viewangles;
} demo_frame_t ;
然后交给状态管理层去更新世界模型。渲染层则完全专注于画面生成,调用 R_RenderView() 或 CL_CalcViewValues() 这类原生引擎 API 进行场景绘制。两者之间通过共享内存区通信,避免频繁拷贝带来的性能损耗。
graph TD
A[.dem 文件] --> B(解码层)
B --> C{解析帧数据}
C --> D[生成 demo_frame_t]
D --> E[状态管理层]
E --> F[同步游戏世界状态]
F --> G[渲染层]
G --> H[调用 R_RenderView()]
H --> I[获取 OpenGL 帧缓冲]
I --> J[送入编码队列]
内存管理的艺术:环形缓冲 + 差量压缩 .dem 文件动辄几小时,全塞进内存肯定不行。dem2video 用了两个杀手锏:
1. 环形缓冲区(Ring Buffer) #define MAX_BUFFERED_FRAMES 512
demo_frame_t * ring_buffer[MAX_BUFFERED_FRAMES];
int head = 0 , tail = 0 ;
void buffer_push (demo_frame_t * frame) {
ring_buffer[head] = frame;
head = (head + 1 ) % MAX_BUFFERED_FRAMES;
if (head == tail) {
free (ring_buffer[tail]);
tail = (tail + 1 ) % MAX_BUFFERED_FRAMES;
}
}
demo_frame_t * buffer_pop () {
if (head != tail) {
demo_frame_t * frame = ring_buffer[tail];
tail = (tail + 1 ) % MAX_BUFFERED_FRAMES;
return frame;
}
return NULL ;
}
2. 帧间差量压缩 对于实体状态更新,只记录变化字段。平均节省 60% 以上数据体积,配合 zlib 压缩,总体内存占用下降可达 75%。
多线程流水线:榨干 CPU 每一核性能 为了发挥多核优势,dem2video 构建了一个四阶段流水线:
I/O 线程 :异步读文件块
解码线程 :解析为结构化帧
模拟线程 :驱动游戏世界前进
渲染线程 :提交 OpenGL 命令并捕获帧
各线程通过无锁队列传递消息,最大限度减少上下文切换开销。
pthread_t io_thread, decode_thread, sim_thread, render_thread;
void start_pipeline () {
pthread_create(&io_thread, NULL , io_worker, NULL );
pthread_create(&decode_thread, NULL , decode_worker, NULL );
pthread_create(&sim_thread, NULL , sim_worker, NULL );
pthread_create(&render_thread, NULL , render_worker, NULL );
}
配置 单线程耗时(秒) 多线程耗时(秒) 加速比 1080p, 60fps, 5min 287 93 3.09x 720p, 30fps, 10min 412 141 2.92x
尤其在高分辨率长时任务中表现突出。还支持动态线程绑定(Thread Affinity),可以把特定线程绑到 CPU 核心上,减少缓存失效:
taskset -c 0,1 ./dem2video input.dem --threads=4
输出格式自由切换:MP4、AVI、MKV 全搞定 现在画也画好了,接下来就是封装输出。dem2video 搞了个 视频编码接口抽象层(VEAL) ,彻底解耦'画面生成'与'编码封装'。
FFmpeg 打底,天下通吃 选择 FFmpeg 作为底层引擎,理由很实在:支持几乎所有常见容器格式;成熟 C API,集成方便;社区活跃,持续更新。
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
int init_encoder (VideoEncoder *enc, const char *filename, int width, int height, int fps) {
av_register_all();
avformat_alloc_output_context2(&enc->fmt_ctx, NULL , NULL , filename);
const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
enc->stream = avformat_new_stream(enc->fmt_ctx, codec);
enc->codec_ctx = avcodec_alloc_context3(codec);
enc->codec_ctx->width = width;
enc->codec_ctx->height = height;
enc->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
enc->codec_ctx->time_base = (AVRational){1 , fps};
enc->codec_ctx->framerate = (AVRational){fps, 1 };
enc->codec_ctx->crf = 23 ;
av_opt_set(enc->codec_ctx->priv_data, "preset" , "slow" , 0 );
avcodec_open2(enc->codec_ctx, codec, NULL );
avio_open(&enc->fmt_ctx->pb, filename, AVIO_FLAG_WRITE);
avformat_write_header(enc->fmt_ctx, NULL );
enc->is_open = 1 ;
return 0 ;
}
从此,无论你要 MP4、AVI 还是 MKV,只要改个文件名就行!
动态编解码器加载:随时换芯不重启 更牛的是,它支持运行时动态切换编码器。你可以写个 JSON 配置:
{
"output_format" : "mp4" ,
"video_codec" : "h264" ,
"audio_codec" : "aac" ,
"profile" : "high"
}
dem2video --input demo.dem --output output.mp4 --video-codec hevc_nvenc
优先尝试 NVENC 硬件编码,失败自动降级软件编码。完美适配 CI/CD 自动化流水线。
graph TD
A[读取用户配置] --> B{是否指定编解码器?}
B -- 是 --> C[查找注册表中的编码器]
B -- 否 --> D[使用默认编码器 (h264)]
C --> E{编码器是否存在?}
E -- 存在 --> F[初始化编码器实例]
E -- 不存在 --> G[抛出错误并回退]
F --> H[设置编码参数]
H --> I[启动编码线程]
不同格式的'性格'拿捏得死死的 容器格式 主要用途 推荐编码 特殊限制 MP4 流媒体分发、网页嵌入 H.264 + AAC 不支持>4GB 大文件 AVI 老旧编辑软件兼容 MJPEG 或 H.264 索引易损坏 MKV 高保真存档、多轨音频 VP9 / AV1 + Opus 播放器兼容性差 WebM Web 端直播回放 VP9 分辨率受限
AVI 分段存储 :接近 4GB 自动切片,生成 .m3u 播放列表;
MP4 faststart :把 moov 原子移到文件头,实现边下边播;
WebM 提示兼容性 :VP9 软解性能差,iOS Safari 不支持,输出前会警告。
命令行玩法大全:自动化生产的秘密武器 高手是怎么用 dem2video 的?他们从不手动点按钮,而是靠脚本批量处理。
参数体系设计得很 Unix 风 dem2video \
--input "matches/*.dem" \
--output "./videos/%f_%t.mp4" \
--resolution 1920x1080 \
--fps 60 \
--log-level INFO
其中 %f 是文件名(不含扩展名),%t 是 ISO8601 时间戳,路径模板自动展开。
日志级别四级可选:ERROR, WARNING, INFO, DEBUG。
graph TD
A[Log Levels] --> B[ERROR]
A --> C[WARNING]
A --> D[INFO]
A --> E[DEBUG]
B --> F["严重错误"]
C --> G["潜在问题"]
D --> H["常规状态"]
E --> I["详细调用栈"]
调试时加 --log-level DEBUG,生产环境关掉即可。
时间裁剪功能:只导出高光时刻 dem2video \
--input final_round.dem \
--output highlight.mp4 \
--start-time 1200 \
--end-time 1500
底层用二分查找定位起始帧,O(log n) 效率,秒级跳转毫无压力。
Shell 脚本批量处理:电竞团队的秘密武器 #!/bin/bash
INPUT_DIR="./demos"
OUTPUT_DIR="./rendered"
mkdir -p "$OUTPUT_DIR "
for file in "$INPUT_DIR " /*.dem; do
[[ -f "$file " ]] || continue
filename=$(basename "$file " .dem)
output="${OUTPUT_DIR} /${filename} .mp4"
dem2video --input "$file " --output "$output " --resolution 1920x1080 --fps 60 --crf 18
done
Windows 也有.bat 版,配合 Jenkins、GitHub Actions,实现无人值守内容生产闭环。
name: Render Match Videos on
on: push
jobs:
render-video:
runs-on: ubuntu-latest
container: quake/dem2video:latest
steps:
- uses: actions/checkout@v4
- run: |
for f in matches/*.dem; do
base=$(basename "$f" .dem)
dem2video --input "$f" --output "videos/${base}.mp4"
done
- uses: actions/upload-artifact@v3
with:
path: videos/
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y ffmpeg libgl1 libxrandr2
COPY dem2video /usr/local/bin/
ENTRYPOINT ["dem2video"]
环境一致性杠杠的,再也不怕'在我机器上能跑'这类经典甩锅语录。
自定义编码参数:画质与体积的终极博弈
分辨率:越大越好?不一定! 分辨率 显存占用 用途 640x480 ~1.2MB 存档备份 1280x720 ~3.8MB 教学视频 1920x1080 ~8.3MB 赛事直播级输出 3840x2160 ~33.8MB 4K 回放制作
./dem2video -ssaa 2x -width 1920 -height 1080 match.dem
实际渲染 3840x2160 再下采样,边缘平滑度显著提升。
帧率控制:插帧补帧也安排上了 原始 demo 通常是 30 或 60fps,但你想输出 120fps 咋办?插值补帧!
graph TD
A[原始帧 F₀] --> B{需要插帧?}
B -->|Yes| C[预测中间位置]
C --> D[生成 Fₘ]
D --> E[输出 F₀→Fₘ→F₁]
适用于高速移动或旋转镜头,动作更流畅。还支持 VFR(可变帧率)输出,在静态画面自动降帧率省空间。
比特率策略:CRF vs CBR,你怎么选? 模式 优点 缺点 场景 CRF 画质稳定 体积不可控 归档 CBR 带宽可控 动态场景失真 直播 CVBR 平衡两者 实现复杂 发布
./dem2video \
-input final.dem \
-output broadcast.mp4 \
-width 1920 -height 1080 \
-fps 60 \
-crf 18 \
-preset slow \
-max_bitrate 25M \
-threads 8
./dem2video \
-input session.dem \
-output archive.mkv \
-width 1280 -height 720 \
-fps 30 \
-bitrate 4M \
-preset medium \
-tune grain
./dem2video \
-input highlight.dem \
-output mobile.mp4 \
-width 720 -height 1280 \
-fps 30 \
-profile:v baseline \
-level 3.1 \
-bitrate 1.5M \
-movflags +faststart
iOS Safari 和 Android Chrome 都能无缝播放,首帧加载<1.2 秒。
结语:不只是工具,更是生产力革命 回头看看,dem2video 不只是一个格式转换器,它是一整套面向未来的 内容生产基础设施 。
从精准解析 .dem 文件,到多线程高效渲染,再到灵活输出与自动化集成,每一个环节都在为'规模化内容创作'服务。无论是职业战队分析战术,还是模组作者展示新地图,亦或是 AI 研究员构建训练集,它都能成为你的左膀右臂。
更重要的是,它是 开源的 。这意味着你可以审计代码、定制功能、贡献补丁,甚至把它集成进自己的平台。
下次当你面对一堆无法播放的 .dem 文件时,别再手动回放截图拼接了——试试 dem2video 吧,说不定你会发现,原来自动化生产,真的可以如此优雅。
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown 转 HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online