openpilot 日志体系详解:rlog/qlog 分段记录与摄像头视频存储机制
openpilot 将每段行驶(route)按一分钟一个的 segment 切分,逐段落盘 rlog/qlog 消息日志和摄像头视频。理解这套日志结构,是回放(replay)、分析驾驶数据和定位 openpilot 缺陷的前提。读完本文,你将掌握 segment 的生命周期与轮转机制、rlog.zst/qlog.zst/.hevc/qcamera.ts 四类文件的生成原理,以及用 LogReader 解析日志的完整方法。
Route 与 Segment:日志的基本组织单位
openpilot 以 segment(分段) 为单位记录一次 route(行车记录)。一条 route 在点火信号上升沿(ignition rising edge)开始,在下降沿结束;每个 segment 时长固定为 60 秒。该常量定义在 config.py 中:
CAMERA_FPS = 20
SEGMENT_LENGTH = 60
loggerd.h 中还有对应的兜底逻辑:测试环境下可通过环境变量 LOGGERD_SEGMENT_LENGTH 覆盖,正常运行时恒为 60 秒。
目录与文件命名规则
LoggerState 构造时会生成 route 标识。从 logger.cc 的 logger_get_identifier 可以看到,标识由一个 32 位递增计数器加 10 位随机十六进制组成:
// a log identifier is a 32 bit counter, plus a 10 character unique ID.
// e.g. 000001a3--c20ba54385
计数器存储在 Params 的 RouteCount 中,因此同一路由的目录名形如 000001a3--c20ba54385。每个 segment 的目录名在 route 名后追加 --段号(如 000001a3--c20ba54385--3),由 LoggerState::next() 创建,并写入 rlog.lock 作为该段正在录制的锁文件。每个 segment 目录下包含:
rlog.zst:全量消息日志;qlog.zst:降采样后的精简日志;fcamera.hevc/ecamera.hevc/dcamera.hevc:三路摄像头视频;qcamera.ts:低码率流媒体版前视视频。
Segment 轮转机制
日志轮转不是简单的定时器触发,而是由 loggerd.cc 的 rotate_if_needed 协调所有编码器共同完成:
- 正常轮转:所有正在录制的编码器(
ready_to_rotate == max_waiting)都报告"本段帧已发完"时才切段,保证 rlog 与视频帧在同一秒边界对齐; - 超时兜底:超过
SEGMENT_LENGTH且 500ms 内没收到任何摄像头数据(NO_CAMERA_PATIENCE = 500),或段长超过SEGMENT_LENGTH * 1.2(72 秒),则强制切段,避免摄像头/编码器故障导致单段无限拉长。
每次切段时,LoggerState::next() 会先向旧文件写入 END_OF_SEGMENT 哨兵消息并删除锁文件,再为新段写入 InitData 和 START_OF_SEGMENT(第一段为 START_OF_ROUTE)。析构时写入 END_OF_ROUTE。这些哨兵让解析器能明确判断 route/segment 的起止与完整性。
InitData 本身也写入了丰富的环境元数据(见 logger_build_init_data):wall time、openpilot 版本、设备类型、内核参数与内核版本、Git commit/branch、全部 Params 键值(带 DONT_LOG 标记的除外)、df -h 磁盘占用输出等。也就是说,rlog 的第一个消息就是这台设备当时的完整运行画像,排障时可以直接对照。
rlog.zst:全量进程间消息日志
rlog 记录了 openpilot 各进程之间传递的全部消息,是 zstd 压缩的 Cap'n Proto 序列化消息序列。压缩级别定义在 logger.h:LOG_COMPRESSION_LEVEL = 10。
被记录哪些服务、频率多少,由 services.py 统一声明。每个服务是一条四元组:
# service: (should_log, frequency, qlog decimation (optional))
"can": (True, 100., 2053, QueueSize.BIG), # decimation gives ~3 msgs in a full segment
"controlsState": (True, 100., 10, QueueSize.MEDIUM),
"carState": (True, 100., 10),
"modelV2": (True, 20., None, QueueSize.BIG),
"logMessage": (True, 0., None, QueueSize.BIG),
- should_log:是否写入 rlog。注意大量
*EncodeData服务(如narrowRoadEncodeData)标记为False——视频帧本体不进入日志流,而是直接写视频文件;但对应的*EncodeIdx索引包仍会入日志(文件顶部注释明确说明),用于把日志事件与视频帧对齐; - frequency:该服务的预期发送频率(Hz),供订阅方做限频判断;
- decimation:qlog 降采样倍数,
None表示不写入 qlog; - queue_size:消息队列缓冲,分
BIG(10MB,视频帧/大 AI 输出)、MEDIUM(2MB,高频 CAN 与直播)、SMALL(250KB,多数服务) 三档。
典型服务一览(摘自 services.py):
| 服务 | 频率 (Hz) | qlog 降采样 | 说明 |
|---|---|---|---|
carState / carControl / carOutput |
100 | 10 | 车辆状态与控制,qlog 保留 10Hz |
can |
100 | 2053 | CAN 总线原始帧,qlog 中一段约 3 条 |
sendcan |
100 | 139 | openpilot 发送的 CAN 帧 |
controlsState / selfdriveState |
100 | 10 | 纵向/横向控制状态 |
modelV2 |
20 | 不记录 | 驾驶模型输出,仅入 rlog |
driverStateV2 / driverMonitoringState |
20 | 10 | 驾驶员监控 |
gpsLocationExternal |
10 | 10 | 外部 GPS |
onroadEvents |
1 | 1 | 行车事件,全量入 qlog |
userBookmark / bookmarkButton |
0(事件型) | 1 | 用户标记,触发段保留 |
procLog |
0.5 | 15 | 进程级系统信息 |
services.py 末尾的 build_header() 会把同一张表生成为 C++ 头文件 services.h,供 loggerd 等原生进程编译期直接使用——这就是 rlog 订阅列表与 Python 侧保持一致的原因。
loggerd 对每个服务的订阅逻辑(loggerd.cc 的 loggerd_thread):凡 should_log 为真、或非直播的编码器数据流,或开启了 RecordAudio 的 rawAudioData,都会建立 SubSocket 订阅。非编码器消息调用 s.logger.write(data, size, in_qlog),同时写入 rlog,并视情况写入 qlog。
qlog.zst:为快速上云设计的降采样日志
qlog 是 rlog 的降采样子集:loggerd 对每个服务维护一个计数器,按该服务在 services.py 中声明的 decimation 值取模,命中的消息才写入 qlog:
const bool in_qlog = service.freq != -1 && (service.counter++ % service.freq == 0);
services.h 生成时把 decimation = None 映射为 -1(build_header 中的 decimation = -1 if v.decimation is None else v.decimation),即永不入 qlog。
降采样策略体现了取舍:carState 保留 10Hz(100Hz 十分之一),足以重建车辆行为;can 降到一整段仅约 3 条;onroadEvents、pandaStates、gpsLocation 等低频服务则是 1:1 全量保留。而 modelV2(模型原始输出)、radarTracks 这类大包只留 rlog。
qlog 与 qcamera 的设计目标是:在慢网络下能即时上传,同时满足大多数分析与调试需求。这解释了为什么日常远程排障优先用 qlog,复现底层问题才需要完整 rlog。
摄像头视频文件:三路 H.265 主视频 + qcamera.ts
每个摄像头流的编码参数由 loggerd.h 中的 EncoderInfo 结构声明,视频帧由 loggerd 从编码器消息中提取并落盘:
| 文件 | 来源服务 | 说明 |
|---|---|---|
fcamera.hevc |
narrowRoadEncodeData |
窄视场前视摄像头(主摄像头) |
ecamera.hevc |
wideRoadEncodeData |
宽视场前视摄像头 |
dcamera.hevc |
cabinEncodeData |
车内摄像头 |
qcamera.ts |
qNarrowRoadEncodeData |
fcamera 的低分辨率 H.264 版本 |
主视频为 H.265 编码(FULL_H_E_V_C 类型;PC 上降级为无损 BIG_BOX_LOSSLESS),20 fps。码率按输入宽度自适应(MainEncoderSettings):
if (in_width <= 1344) {
return EncoderSettings{.encode_type = MAIN_ENCODE_TYPE, .bitrate = 5'000'000, .gop_size = 20};
} else {
return EncoderSettings{.encode_type = MAIN_ENCODE_TYPE, .bitrate = 10'000'000, .gop_size = 30};
}
几个值得注意的实现细节:
- 车内视频是否录制由 Params 决定:
main_cabin_encoder_info的record字段取自Params().getBool("RecordFront"),即dcamera.hevc的落盘是可配置的; - qcamera 参数极小:
QcamEncoderSettings固定 H.264、526x330 分辨率、256 kbps 码率、GOP 15;若 Params 中RecordAudio开启,qcamera 还会混入音频轨(loggerd 会把rawAudioData的 PCM 数据写入对应VideoWriter)。逗号 connect 网页里播放的视频正是来自 qcamera; - 关键帧才开新文件:loggerd 收到编码消息时,只有在收到
V4L2_BUF_FLAG_KEYFRAME标记的 iframe 后才开始写新段的视频文件(write_encode_data),此前收到的非关键帧会被丢弃并计数告警; - EncodeIdx 对齐:每个视频帧对应的
EncodeIdx包会以in_qlog = true写入日志流(rlog 和 qlog 都有),使日志事件能与视频帧按时间戳精确对齐。
此外还有三组 livestream* 编码流(1152x720/1280x720,H.264,默认 5Mbps、可用 STREAM_BITRATE 覆盖),record = false,只用于实时推流,不落盘。
用 LogReader 读取日志
读取日志的官方入口是 logreader.py 中的 LogReader,这也是 openpilot 自身调试和复现问题时使用的工具链基础。给定一个本地路径、route 标识或下载 URL,LogReader 会解析为具体文件列表并按 Event 迭代器逐条产出 Cap'n Proto 消息:
from openpilot.tools.lib.logreader import LogReader
# 读取本地 rlog/qlog(.zst 或旧格式 .bz2 均可)
lr = LogReader("000001a3--c20ba54385--3/rlog.zst")
# 过滤某一类消息,例如车辆状态
for car_state in lr.filter("carState"):
print(car_state.vEgo, car_state.steeringAngle)
# 取第一条某类消息(如 route 起始的 carParams)
cp = lr.first("carParams")
几个实用特性(均可在源码中确认):
- 自动识别压缩格式:
_LogFileReader按扩展名或魔数判断,.zst走 zstandard 流解压(魔数\x28\xB5\x2F\xFD),.bz2/BZh9走 bz2;不带扩展名的旧 rlog 直接按裸 Cap'n Proto 流处理; - ReadMode 选择:
LogReader(default_mode=ReadMode.QLOG)只读 qlog,RLOG只读 rlog,AUTO优先 rlog、缺失段自动回退 qlog;route 标识末尾也可加/a选择器强制回退(见auto_source中的处理); - 时间序列视图:
lr.time_series属性把消息流转换为按服务分组的时间序列(委托给 log_time_series.py),便于直接画曲线; - 跨段并行处理:
run_across_segments(num_processes, func)用进程池对 route 的每个 segment 并行执行同一个函数,适合全 route 统计。
日志文件本身还可作为 CLI 直接打印:python openpilot/tools/lib/logreader.py <path> 会按时间排序逐条输出所有消息。围绕同一套日志的更完整工作流(回放、可视化)见 replay 工具文档。
段保留与上传
两个与日志生命周期相关的机制值得了解:
- 用户书签保留段:收到
userBookmark消息时,loggerd 调用handle_preserve_segment,用setxattr给当前 segment 目录打上user.preserve=1扩展属性,并把 route 名追加到 Params 的AthenadRecentlyViewedRoutes,供上传逻辑优先处理(loggerd.cc); - 清理与上云:deleter.py 与 uploader.py 负责按磁盘余量(
get_available_bytes/get_available_percent,见 config.py)淘汰旧 route、把 qlog/qcamera 快速上传云端;qlog+qcamera 体积小的设计正是为了让这一步在慢网络上也能即时完成。
小结
openpilot 的日志体系围绕"一分钟 segment"这一粒度组织:loggerd 订阅全部服务消息,按 services.py 声明同步写出全量 rlog 与降采样 qlog,并按关键帧边界把三路摄像头写入 fcamera.hevc/ecamera.hevc/dcamera.hevc 和轻量 qcamera.ts。哨兵消息(route/segment 起止)、InitData 环境快照与 EncodeIdx 帧对齐包共同保证了日志可被离线完整复现。基于 LogReader,同一份数据既可本地分析,也可按 route 标识跨段并行处理。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00