1. 核心定位:Packet vs Frame
Packet 是 压缩后、解码前 的数据单元。理解它与 Frame 的区别是正确处理视频流的前提。
💡 黄金法则:转发/录制用 Packet(零拷贝);渲染/处理用 Frame。永远不要假设一个 Packet 对应一个完整画面。
2. 时间戳系统
PTS / DTS / Duration
pkt.pts # int: 显示时间戳(time_base 单位),可能为 None
pkt.dts # int: 解码时间戳(time_base 单位),通常不为 None
pkt.duration # int: 持续时间(time_base 单位),可能为 None
pkt.time_base # Fraction: 时间基准(如 1/90000)
DTS 必须单调递增:这是 FFmpeg 解复用器的硬性要求,B 帧重排导致 PTS 可能乱序,但 DTS 永远有序
PTS 可能缺失:某些容器(如裸 H.264)不提供 PTS,需通过 DTS + 帧类型推算或由解码器补全
Duration 可能为 None:可变帧率 (VFR) 流中常见,不可用于精确时长计算
⚠️ 时间戳陷阱
3. 关键帧与标志位检测
核心布尔属性
实用模式
# ✅ 仅在关键帧处提取缩略图
for pkt in container.demux(video=0):
if pkt.is_keyframe:
frames = pkt.decode()
save_thumbnail(frames[0])
# ✅ 自适应码率:拥塞时丢弃非参考帧
if network_congested and pkt.is_disposable:
continue # 跳过该包,不影响后续解码
# ✅ 损坏包统计
corrupt_count = sum(1 for p in packets if p.is_corrupt)
4. 解码操作
decode() 方法
frames = pkt.decode() # 返回 List[VideoFrame | AudioFrame | SubtitleSet]
返回值是列表:一个 Packet 可能产出 0、1 或多帧
空列表是正常的:B 帧重排缓冲、编码器延迟都会导致暂时无输出
异常传播:内部调用解码器,可能抛出
InvalidDataError等 FFmpeg 异常
⚠️ decode() vs Container.decode()
💡 建议:除非需要在 Packet 层做过滤/转发后再选择性解码,否则优先使用
container.decode()迭代器。
5. 元数据与定位
码率分析示例
bitrate_samples = []
for pkt in container.demux(video=0):
if pkt.duration and pkt.size:
bps = pkt.size * 8 / (pkt.duration * pkt.time_base)
bitrate_samples.append(bps)
avg_bitrate = sum(bitrate_samples) / len(bitrate_samples)
6. 视频 Packet 处理最佳实践
7. 开发排查备忘
💡 核心记忆点
转发用 Packet,渲染用 Frame,中间不落地
DTS 管解码顺序(必递增),PTS 管显示时机(可乱序)
decode() 返回空列表是常态,不是错误
is_keyframe 是 Seek 和缩略图的唯一可靠锚点
size/duration/pos 都可能为 None,防御性编程必不可少