视频编码:录制与直播分流,切段由帧号阈值决定

作者:

在

结论:视频编码链里「录制」和「直播」是两条分流: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() 仅记「不支持」。直播插件替换编码器时要明确参数所有权与关键帧确认。

延伸阅读

参考资料

  • 项目 02 —— openpilot-research/VIDEO_ENCODING_DEEP_DIVE.md(取用了编码进程与相机表、实时调度与切段、三类编码后端、故障边界与验证范围)。
  • 项目 02 —— openpilot-research/CAMERA_IMU_DEEP_DIVE.md(仅用于确认采集端 VisionIPC 帧边界,未取用其未公开内容)。

说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

更新记录

首次发布 2026-10-11 最近核对:2026-10-11

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注