以下是基于 PyAV Time 模块整理的时间系统 (Time) 核心速查手册。该文档专为视频开发者设计,聚焦于 time_base 的上下文差异、编解码时序转换及安全同步计算,是避免音画不同步、转码花屏和封装失败的关键。
1. 核心概念:Time Base 三重语境
PyAV/FFmpeg 中不存在“绝对秒数时间戳”,所有时间均为 整数 × time_base。不同对象拥有独立的 time_base,跨对象运算必须先 rescale。
💡 黄金法则:永远不要直接加减不同对象的 pts/duration;永远不要假设 time_base = 1/fps;跨域操作必 rescale。
2. 解码时序模型(最常用)
解码时,所有时间戳统一在 Stream.time_base 下:
stream = container.streams.video[0]
for frame in container.decode(stream):
# ✅ 安全获取秒级时间
time_sec = frame.time # float, 已自动换算
# ✅ 手动精确计算(考虑 start_time 偏移)
if frame.pts is not None and stream.time_base is not None:
adjusted_pts = frame.pts - (stream.start_time or 0)
time_sec = float(adjusted_pts * stream.time_base)
⚠️ 解码时序陷阱
3. 编码时序模型(易错区)
编码涉及 两次 time_base 切换,流程严格固定:
Frame.pts (CodecContext.time_base)
↓ encoder.encode()
Packet.pts (仍为 CodecContext.time_base) ← ⚠️ 库自动复制,未转换!
↓ packet.rescale_ts(codec_ctx, stream)
Packet.pts (Stream.time_base)
↓ muxer.mux()
写入文件
编码时序规范
⚠️ 关键警告:
avformat_write_header()后stream.time_base可能被 muxer 修改。必须在 write_header 之后、mux 之前读取最终 stream.time_base 用于 rescale。
4. 时间转换工具
rescale_q:跨基准转换
# ✅ 将 Packet 从 codec time_base 转到 stream time_base
packet.rescale_ts(codec_context, stream)
# ✅ 手动 rescale(等价于 FFmpeg av_rescale_q)
new_pts = av.rescale_q(old_pts, old_tb, new_tb)
秒 ↔ 时间戳互转
# 秒 → 时间戳
pts = int(time_sec / stream.time_base)
# 时间戳 → 秒
time_sec = float(pts * stream.time_base)
# ✅ 推荐:Frame 直接用 .time 属性
time_sec = frame.time # 自动处理 None 和 time_base
5. 常见工作流示例
音视频同步检查
audio_tb = audio_stream.time_base
video_tb = video_stream.time_base
# ✅ 统一到微秒基准比较
audio_us = av.rescale_q(audio_frame.pts, audio_tb, av.TIME_BASE)
video_us = av.rescale_q(video_frame.pts, video_tb, av.TIME_BASE)
drift_ms = abs(audio_us - video_us) / 1000
if drift_ms > 100:
logger.warning(f"A/V drift: {drift_ms:.1f}ms")
VFR 编码安全实践
codec_ctx.time_base = Fraction(1, 1000) # 高精度基准
stream.time_base = Fraction(1, 1000) # hint to muxer
container.open() # write_header 可能修改 stream.time_base
next_pts = 0
for frame in generate_frames():
frame.pts = next_pts
frame.time_base = codec_ctx.time_base
for pkt in codec_ctx.encode(frame):
pkt.rescale_ts(codec_ctx, stream) # ✅ 使用 write_header 后的实际 stream.tb
container.mux(pkt)
next_pts += int(frame_duration_sec / codec_ctx.time_base)
6. 开发排查备忘
💡 核心记忆点
解码看 Stream,编码看 Codec,Mux 前必 Rescale
write_header 会改 stream.time_base:rescale 必须在之后进行
Frame.time 是安全快捷方式:优先使用,避免手动换算出错
start_time 不是 0:同步/裁剪/seek 时必须补偿
VFR 用高精度 time_base:
1/1000或更高,不要用1/fpsNone 是常态:pts/duration/time_base 均可能为 None,防御性编程必不可少