首页
/ openpilot 日志体系详解:rlog/qlog 分段记录与摄像头视频存储机制

openpilot 日志体系详解:rlog/qlog 分段记录与摄像头视频存储机制

2026-09-04 15:05:28作者:邵娇湘

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.cclogger_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.ccrotate_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 哨兵消息并删除锁文件,再为新段写入 InitDataSTART_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.hLOG_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.ccloggerd_thread):凡 should_log 为真、或非直播的编码器数据流,或开启了 RecordAudiorawAudioData,都会建立 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 映射为 -1build_header 中的 decimation = -1 if v.decimation is None else v.decimation),即永不入 qlog。

降采样策略体现了取舍:carState 保留 10Hz(100Hz 十分之一),足以重建车辆行为;can 降到一整段仅约 3 条;onroadEventspandaStatesgpsLocation 等低频服务则是 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_inforecord 字段取自 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.pyuploader.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 标识跨段并行处理。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384