鴥彼晚风
发布于 2026-07-01 / 2 阅读
0
0

PyAV 视频帧 (Frames) 核心速查手册

1. 核心概念:Frame vs Packet

在 PyAV 中,Frame 是解码后的原始像素数据载体,而 Packet 是压缩码流。理解两者的时间戳映射是视频同步的关键。

属性

Frame (解码后)

Packet (压缩码流)

PTS

✅ 显示时间(何时展示给用户)

✅ 显示时间(可能缺失)

DTS

⚠️ 仅在非帧线程模式下等于解码时间

✅ 解码时间(必须单调递增)

Time

✅ 浮点秒数(便于业务逻辑)

❌ 无直接秒数属性

数据状态

原始像素(YUV/RGB)

压缩比特流

💡 黄金法则视频渲染/同步永远使用 frame.ptsframe.timeframe.dts 仅在分析解码延迟或调试时参考。


2. 时间戳系统详解

PTS (Presentation Timestamp)

frame.pts       # int: time_base 单位下的显示时间戳
frame.time      # float: 以秒为单位的显示时间(= pts * time_base)
frame.time_base # Fraction: 时间基准(如 1/30, 1/90000)
  • 用途:音视频同步、字幕对齐、帧率计算、Seek 目标定位

  • 注意:PTS 可能不连续(丢帧、可变帧率 VFR),不要假设 pts[n+1] = pts[n] + 1

DTS (Decoding Timestamp)

frame.dts  # int: time_base 单位下的解码时间戳
  • 来源:从触发该帧输出的 Packet.dts 复制而来

  • 特殊行为:当未启用帧级多线程时,DTS 也作为仅基于 Packet.dts 计算的显示时间(无 PTS 时的降级方案)

  • 用途:调试解码顺序、检测 B 帧重排异常

⚠️ 时间戳陷阱

场景

问题

解决方案

PTS 为 None

容器未提供时间戳

dts 降级;或根据帧序号 × 帧率手动生成

time 精度丢失

float 在长时间视频中累积误差

关键计算用 pts (int) + time_base (Fraction)

负时间戳

某些容器允许负 PTS

检查 ContainerFormat.flags.TS_NEGATIVE;必要时偏移

时间戳跳变

拼接/裁剪导致不连续

重新归一化 PTS 或使用滤镜 setpts


3. 数据安全:make_writable()

为什么需要它?

FFmpeg 内部广泛使用引用计数缓冲区。多个 Frame 可能共享同一块内存(如解码器输出帧被滤镜引用)。直接修改共享数据会导致:

  • 其他引用者看到意外修改

  • 内存损坏 / Segfault

  • 编码器/滤镜内部状态污染

正确用法

frame = next(container.decode(video=0))

# ❌ 危险:直接修改可能共享的缓冲区
frame.planes[0].update(new_data)

# ✅ 安全:确保独占可写副本
frame.make_writable()
frame.planes[0].update(new_data)

性能提示

  • make_writable() 仅在数据不可写时才复制,已独占时为零开销

  • 如果你确定帧是新创建的(如 VideoFrame(width, height, fmt)),无需调用

  • 来自 decode() / filter.vpull() 的帧始终应调用后再修改

  • 底层封装自 av_frame_make_writable()


4. 帧完整性校验

is_corrupt

if frame.is_corrupt:
    logger.warning(f"Corrupt frame at PTS={frame.pts}, skipping")
    continue
  • 含义:FFmpeg 解码器标记该帧数据不完整或损坏

  • 触发条件:码流截断、传输错误、编码器 bug

  • 处理策略

    • 实时播放:跳过该帧,用上一帧替代或黑屏过渡

    • 转码/分析:记录位置,决定是否终止或容错继续

    • 配合标志:设置 CodecContext.flags |= 'OUTPUT_CORRUPT' 才能收到损坏帧(否则被静默丢弃)

side_data

for sd in frame.side_data:
    print(sd.type, sd.data)
  • 携带额外元数据:HDR 信息、ROI 区域、运动向量、SEI 消息等

  • 视频 HDR 处理中常用于提取 MASTERING_DISPLAY_METADATACONTENT_LIGHT_LEVEL


5. 视频帧处理最佳实践清单

操作

推荐做法

反模式

获取显示时间

frame.time (float) 或 frame.pts * frame.time_base

frame.dts 做同步

修改像素数据

make_writable() 再写入

直接操作 planes/buffers

处理损坏帧

检查 is_corrupt + 降级策略

假设所有帧都完好

时间比较

用整数 pts + 相同 time_base

用 float time 做精确相等判断

跨容器传递帧

保留原始 time_base 或在 mux 时 rescale

硬编码 1/90000

释放帧内存

with 或及时 del;避免长期持有引用

在全局列表缓存所有帧

调试时间戳

同时打印 pts, dts, time, time_base

只看 time 忽略单位


6. 开发排查备忘

症状

可能原因

诊断步骤

画面闪烁/撕裂

修改了共享帧数据

确认修改前调用了 make_writable()

音画不同步

使用了 dts 而非 pts

改为 frame.time;检查 time_base 是否正确

播放卡顿但 CPU 低

大量 corrupt 帧被反复重试

打印 is_corrupt 统计比例;检查源文件

时间戳溢出

float 精度不足

改用 pts (int) 运算;检查 time_base 分母

HDR 色调映射失败

未读取 side_data

遍历 frame.side_data 检查 HDR metadata

滤镜输出全黑

输入帧 time_base 与 Graph 不匹配

在 add_buffer 时显式指定 time_base

内存持续增长

Frame 引用未释放

检查是否有循环引用;用 tracemalloc 定位

💡 核心记忆点

  • 显示看 PTS,调试看 DTS,业务用 time,精度靠 Fraction

  • 改数据前必 make_writable(),零开销保安全

  • is_corrupt 不是异常而是信号,优雅降级胜过崩溃

  • side_data 是 HDR/元数据的唯一可靠来源


评论