SuperAPI AI 工具集

rerun-mp4

数据与分析

查看项目

更新于 10-16 02:18

技能介绍

用途

Mp4Reader 将一个 .mp4 文件转换为同一实体上的惰性 chunk 流:压缩视频采样,不解码为像素,除非必要不会重新编码。所有配置在构造函数中完成,stream() 不接受参数。

from rerun.experimental import Mp4Reader, Mp4TranscodeOptions

reader = Mp4Reader(video_path, entity_path="/camera/front")  # mode="stream" by default
stream = reader.stream()  # lazy: nothing is decoded yet

路径之后的所有参数均为仅关键字参数。一个 reader 处理一个文件;多摄像头需为每个文件各建一个 reader,再用 LazyChunkStream.merge(...) 合并。

两种模式

mode 输出 适用时机
"stream"(默认) 一个 codec chunk、每个 GOP 的 sample chunk、一个关键帧标记 几乎总是 —— 每帧都有时间索引且可查询
"asset" 整个文件作为一个 blob,外加帧索引 编解码器无法作为 VideoStream,或源文件从 GOP 中间开始

asset 模式将整个文件作为一个 blob 复制进记录,下游无法在不获取整个资产的情况下查看单帧。它是 VideoStream 无法承载的编解码器(mp4v、图像序列 mp4)的回退方案。

stream 模式输出内容

以 tests/assets/video/Big_Buck_Bunny_1080_1s_h264_nobframes.mp4(30 帧、单 GOP)为例,entity_path="/camera/front",完整输出为三个 chunk:

  • 一个静态 chunk,一行,持有 VideoStream:codec,无时间线(代码遍历时需处理:if chunk.is_static: continue)。
  • 每个 GOP 一个时间 chunk:关键帧及其所有依赖采样,直到(不含)下一个关键帧。
  • 一个尾随的 is_keyframe 标记 chunk,在每个关键帧时间持有稀疏 True 行。它是逐行列表,读回为 [True] 而非 True。

时间线名为 video,duration[ns],值为从视频开始的 mp4 PTS。所有 chunk 在同一实体上。entity_path=None(默认)从文件绝对路径推导。短片段常为单个 GOP,因此“共三个 chunk”是正常形态。chunk_by_gop=False 改为每个采样一个 Rerun chunk(标记 chunk 不受影响),这是调试形态,存储布局差且后续 optimize 开销显著更高。

区分三种 chunk 应看列而非位置或 is_static:codec chunk 是静态的,sample chunk 携带 VideoStream:sample,标记 chunk 只携带 VideoStream:is_keyframe。

支持的 stream 模式编解码器恰为五个 VideoCodec 值:H264、H265、AV1、VP8、VP9。

通过 FFmpeg 转码

VideoStream 尚无法建模 DTS != PTS,因此含容器级 B 帧重排序的 H.264/H.265 源无法直接输出。reader 自行处理:通过 FFmpeg 以 -bf 0 重新编码,将结果作为分片 mp4 流回,并把每个分片转为一个 GOP chunk,因此同一时刻只驻留一个 GOP。此过程自动且对 API 不可见,需要 ffmpeg 可执行文件。asset 模式不受 B 帧影响。

Mp4TranscodeOptions(仅 stream 模式)可额外请求转码:

字段 作用
output_codec 重新编码为另一个 VideoCodec
gop_size 强制每 N 帧一个关键帧
try_gpu 尽力硬件编码,仅 NVENC / VideoToolbox
ffmpeg_override 使用指定的 ffmpeg 而非 PATH 中的

请求与源相同的 output_codec 是空操作。gop_size=N 配合 chunk_by_gop=True 时,除最后一个外的每个 GOP chunk 恰好持有 N 个采样。try_gpu 实际覆盖较新 NVIDIA 上的 H264/H265 及 AV1;VP8/VP9 始终回退到软件。

Mp4Reader(
    video_path,
    entity_path="/camera/front",
    transcode=Mp4TranscodeOptions(gop_size=64),
).stream()

时间线

输出的时间始终是 PTS——从该视频开始的经过纳秒数。多传感器录制中通常不是要对齐的时间线。timeline_name="real_time" 重命名时间线;timeline_type="timestamp" 仅将相同 PTS 值重新类型化为自 Unix 纪元起的纳秒。reader 不偏移它们,单独使用会将视频渲染在约 1970 年,只有与 retag 步骤配对才有意义。

采样按呈现顺序到达(B 帧已被剥离),因此游标将采样 i 映射到 capture_times_ns[i],但游标必须跳过不含采样的关键帧标记 chunk。

该游标使 retag 依赖顺序,因此 retag 应保持在单个 reader 的 stream 上,在任何 merge 之前。

多摄像头合并

每个文件一个 reader、不同实体路径,然后合并:

streams = [
    Mp4Reader(path, entity_path=f"/camera/{name}", timeline_name="real_time", timeline_type="timestamp").stream()
    for name, path in cameras.items()
]
(
    LazyChunkStream
    .merge(*streams)
    .collect(optimize=OptimizationProfile.OBJECT_STORE)
    .write_rrd(out_path, application_id=app_id, recording_id=segment_id)
)

OBJECT_STORE 会重新分批视频并原样保留 reader 的关键帧标记,无需 fix_keyframe=True。

reader chunk 与 optimize 的关系

reader 输出最细的 GOP 对齐分区:每个 GOP 恰好一个 chunk。optimize 随后应用尺寸策略,按 profile 的 max_bytes / max_rows 合并连续 GOP,从不跨 chunk 拆分 GOP,因此 optimize 的分区是 reader 分区的纯粗化。不要预先合并或重新分块 reader 输出以“帮助” optimize。

optimize 在 GOP 分块输入上开销很低

在 89 MB / 1800 帧 / 60 GOP 的 H.264 文件上:collect() 11.1 ms;collect(optimize=OBJECT_STORE) 16.3 ms;增量为 +5.2 ms。检测 GOP 起始只读头部,不解码;重建 chunk 不复制数据(taken(0..n) 在索引从零连续时返回未复制数组)。chunk_by_gop=False 会丧失该快速路径。

注意事项

  • 错误在首次拉取时而非构造时浮现;应包裹消费(to_chunks()、send_chunks、迭代)而非 Mp4Reader(...) 调用。
  • 五个 VideoCodec 之外的编解码器无法作为 VideoStream;mp4v 会抛出 RuntimeError。asset 模式只部分接受该文件:blob chunk 输出,帧索引 chunk 被跳过并警告。
  • 重写时间列的 map/flat_map 必须携带字段元数据,否则会静默变为静态。
  • 尾随标记 chunk 是时间性的但不含采样,应基于 VideoStream:sample 列判断。
  • asset 模式因 Arrow 的 i32 偏移上限约 2 GiB,并将文件字节重复进 RRD。
  • mode="asset" 会以 ValueError 拒绝 chunk_by_gop=False 和 transcode=。
  • reader 输出压缩采样,从不输出像素;缩略图、CLIP 嵌入等需要 RGB 的内容须单独解码。
  • 每次 stream() 调用都会从头重新解码文件,每次终端调用都会重跑管道;需要多遍时 collect() 一次。
  • 在任何 map/flat_map 中显式处理静态 codec chunk。
  • stream 模式拒绝第一个关键帧之前的采样;此类文件请用 asset 模式。

参考资料

canonical 示例:rerun_py/tests/integration/test_mp4_reader.py。Rust 核心:crates/data_flow/re_mp4_reader/(stream.rs 为 GOP/转码路径,asset.rs 为 blob+索引路径)。