结论:视频编码链里「录制」和「直播」是两条分流:loggerd 订阅非直播 EncodeData 并转为媒体文件与 EncodeIdx,直播主题由 WebRTC 消费、不按录制媒体流写盘。切段由帧号阈值决定——每路按 SEGMENT_LENGTH × MAIN_FPS 关闭/重开编码器,段长通常 60 秒、20 FPS,但输入缺帧时不能只按墙上时钟推断切段位置。
本文基于固定源码快照的静态阅读,未取得完整 LFS/平台环境,也未在相机或 V4L 编码硬件上运行。采集端与落盘端属于相邻专题,这里聚焦编码进程、多相机同步、后端差异与切段/索引。
进程、相机和消息边界
encoderd 随 onroad 运行;stream_encoderd 用同一可执行文件加 --stream,在直播或非汽车模式运行;部分硬件另有 --clip 离线导出入口,不属于这两个实时进程。三者的输入都是 camerad 通过 VisionIPC 发布的帧。
| 边界 | 已核对行为 |
|---|---|
| 进程选择 | encoderd 随 onroad 运行;stream_encoderd 同可执行文件加 --stream;--clip 是离线导出,不属于实时进程 |
| 相机表 | 窄路面流发主录制流(硬件 HEVC、PC FFVHUFF)与低码率 qNarrowRoadEncodeData;广角、座舱各有主流 |
| 座舱流 | 座舱主流的 record 取决于 RecordFront |
| 输入 | 启动时探测可用 VisionIPC 流,每个可用流起一个编码线程;连接后从帧缓冲及 VisionIpcBufExtra 读取帧号/时间戳 |
| 输出 | 发布编码字节、尺寸、Unix 发布时刻、帧 ID、SOF/EOF、编码 ID、段号/段内 ID 与关键帧标记 |
| 时间域 | logMonoTime、采集 EOF 和 Unix 发布时间是不同时间域 |
| 录制/直播分流 | loggerd 订阅非直播 EncodeData 转媒体文件;直播主题由 WebRTC 消费,不写盘 |
实时调度与切段
sync_encoders() 意图在多编码器起始阶段汇总可用流帧号,把共同起点推到当前帧后两个帧号,每路达到阈值才编码。但父线程在增加 max_waiting 的同时逐个启动子线程,等待数尚未冻结,早启动的线程可能误走单路分支。即使门正常通过,也只同步起始帧 ID,不等于整段硬件时钟同步。
encoder_thread() 检查 VisionIPC 缓冲自身帧 ID 与附加元数据的帧 ID 是否相等,失配则跳过,首次落后时记日志。每路按 SEGMENT_LENGTH × MAIN_FPS 的帧号阈值关闭/重开编码器。
缩略图有独立的节拍:同一窄路面帧会分别送主编码器和 qcamera 编码器;缩略图由 JpegEncoder::pushThumbnail() 在 frame_id % 1200 == 100 时生成,使用 JPEG 消息发布,不是每帧都有缩略图。
loggerd.handle_encoder_msg() 对齐编码器段号与当前日志段,新段创建 writer、等待关键帧后写入媒体,并把 EncodeData.idx 转为 EncodeIdx 写入事件日志。encoderd、loggerd 独立启停,段号对齐依赖首次收到的偏移,不能把编码器内部段号直接当路由段号。
切段与索引的对应关系需要端到端核对:编码器按帧号阈值分段,loggerd 再按段号把媒体写入文件并把 EncodeData.idx 转为 EncodeIdx;两个进程独立启停,因此「编码器内部段号」与「路由段号」不能默认相同。跨段队列、音频等待与段边界处的关键帧对齐,都需要单独验证,不能只凭媒体文件存在就判定编码链完整。
编码后端:硬件与 PC 不等价
V4LEncoder 打开 Qualcomm V4L2 编码设备、提交 NV12 缓冲;后台 dequeue_handler() 接收配置头和编码包,以 EOF 时间戳断言输入/输出配对,关键帧带配置头发布,段结束时等待队列排空及 EOS。这些时序需实板确认。
| 后端 | 行为与限制 |
|---|---|
| 硬件(V4L) | 打开 Qualcomm V4L2 编码设备、提交 NV12;以 EOF 时间戳断言输入/输出配对,关键帧带配置头发布,段结束等待队列排空及 EOS |
| PC(Ffmpeg) | NV12 转 I420、必要时缩放;qcamera 用 H.264,其他流用 FFVHUFF;header 为空,set_bitrate() 和 request_keyframe() 仅记「不支持」 |
| 缩略图 | NV12 下采样经 FFmpeg MJPEG 输出 thumbnail;尺寸与源流比例用断言约束 |
软件后端不是硬件 HEVC 的等价替代。直播时 encoder_set_bitrate() 与 encoder_request_keyframe() 每帧读取参数,WebRTC 侧通过 LivestreamEncoderBitrate 和 LivestreamRequestKeyframe 交互;PC 后端只记录不支持。把直播插件换成独立编码器时,应明确参数所有权、关键帧请求的确认与超时。
故障边界与验证
源码可见的故障路径包括:VisionIPC 尚未可用时重试、帧号失配跳帧、编码失败记录、V4L poll 超时继续等待、V4L 输入/输出时间戳失配断言。一个容易被忽略的边界是:外层 VisionIPC 重连后并未清空已创建的编码器或重做多相机起始同步;若新流尺寸/帧号语义改变,恢复行为需要专门测试,不能从重连循环存在就推断安全恢复。
| 故障路径 | 当前处理 | 未覆盖的部分 |
|---|---|---|
| VisionIPC 不可用 | 重试 | 重连后未清编码器、未重做起始同步 |
| 帧号失配 | 跳过该帧,首次落后时记日志 | 恢复后帧号语义改变的行为待测 |
| V4L poll 超时 | 继续等待 | 无独立截止语义 |
| 输入/输出时间戳失配 | 断言 | 需实板确认 |
test_encoder.py 是硬件测试入口,会检查媒体文件、帧数、编码索引及多相机首帧一致性;本次未运行。面向新硬件,至少应验证缓冲所有权、帧号/时间戳、编码包与索引一一对应、关键帧切段、断流重连和录制/直播互不抢占。
常见问题
录制和直播走的是同一条链路吗?
不是完全同一条。loggerd 订阅非直播 EncodeData 并转为媒体文件与 EncodeIdx;直播主题由 WebRTC 消费,不按录制媒体流写盘。两者共享编码器输出,但下游消费与落盘路径不同。
切段是按墙上时钟还是按帧号?
按帧号阈值。每路按 SEGMENT_LENGTH × MAIN_FPS 关闭/重开编码器,段长通常 60 秒、20 FPS;输入缺帧时不能只按墙上时钟推断切段位置。
VisionIPC 重连后就等于安全恢复了吗?
不能这样推断。源码里外层重连后并未清空已创建的编码器或重做多相机起始同步;若新流尺寸或帧号语义改变,恢复行为需要专门测试。重连循环存在,不等于恢复过程安全。
PC 上的软件编码能替代硬件编码吗?
不能当成等价替代。硬件走 Qualcomm V4L2、关键帧带配置头、段结束等待 EOS;PC 后端 NV12 转 I420、header 为空,set_bitrate() 和 request_keyframe() 仅记「不支持」。直播插件替换编码器时要明确参数所有权与关键帧确认。
延伸阅读
- 相机请求与缓冲生命周期 —— 补齐帧号与缓冲所有权这一输入侧。
- 离线片段导出:硬件解码、转码与任务生命周期 —— 说明
--clip这条离线路径与实时编码的区别。 - WebRTC 码率与关键帧时序 —— 承接直播侧
LivestreamEncoderBitrate/LivestreamRequestKeyframe的交互。 - VisionIPC 缓冲契约:帧号、元数据与可见性 —— 解释帧号失配为何要跳帧处理。
参考资料
- 项目 02 ——
openpilot-research/VIDEO_ENCODING_DEEP_DIVE.md(取用了编码进程与相机表、实时调度与切段、三类编码后端、故障边界与验证范围)。 - 项目 02 ——
openpilot-research/CAMERA_IMU_DEEP_DIVE.md(仅用于确认采集端 VisionIPC 帧边界,未取用其未公开内容)。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11
发表回复