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

PyAV 时间系统 (Time) 核心速查手册

以下是基于 PyAV Time 模块整理的时间系统 (Time) 核心速查手册。该文档专为视频开发者设计,聚焦于 time_base 的上下文差异、编解码时序转换及安全同步计算,是避免音画不同步、转码花屏和封装失败的关键。


1. 核心概念:Time Base 三重语境

PyAV/FFmpeg 中不存在“绝对秒数时间戳”,所有时间均为 整数 × time_base。不同对象拥有独立的 time_base,跨对象运算必须先 rescale

对象

time_base 来源

典型值

用途

Stream

stream.time_base

1/25, 1/90000

Packet PTS/DTS、Stream duration/start_time

CodecContext

codec_context.time_base

1/fps, 1/sample_rate

编码时 Frame PTS;解码时通常与 Stream 一致

Container

av.TIME_BASE

1/1000000 (微秒)

容器级 duration、无显式 time_base 时的隐式基准

Frame

动态继承

解码=Stream;编码=CodecContext

Frame.pts 所属基准

Packet

动态继承

默认=Stream

Packet.pts/dts 所属基准

💡 黄金法则永远不要直接加减不同对象的 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)

⚠️ 解码时序陷阱

陷阱

说明

解决方案

frame.pts 为 None

B帧重排/损坏包/某些直播流

frame.dts 降级或维护单调递增计数器

start_time != 0

编辑点/延迟头导致首帧非零

同步计算时必须减去 stream.start_time

time_base 为 None

极少数异常容器

回退到 codec_context.time_baseav.TIME_BASE

直接用 pts 比较跨流

音视频 time_base 不同

av.rescale_q() 到统一基准再比较


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()
写入文件

编码时序规范

步骤

操作

关键点

1. 设置编码器

codec_context.time_base = Fraction(1, fps)

固定帧率设为帧率倒数;VFR 设为合理高精度如 1/1000

2. 准备 Frame

frame.pts = next_pts

CodecContext.time_base 单位下赋值

3. 编码

packets = codec_context.encode(frame)

输出 Packet.pts 仍在 CodecContext.time_base

4. Rescale

packet.rescale_ts(codec_context, stream)

必须! 转为 Stream.time_base

5. Mux

container.mux(packet)

此时 Packet 已在正确基准下

⚠️ 关键警告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. 开发排查备忘

症状

可能原因

诊断步骤

播放速度异常快/慢

time_base 设错或未 rescale

打印 codec/stream time_base;确认 rescale_ts 调用

音画逐渐漂移

VFR 编码用固定增量 pts

用实际帧间隔累加 pts;提高 time_base 精度

转码后首帧黑屏/跳帧

忽略 start_time 偏移

打印 stream.start_time;解码时减去偏移

mux 报错 "Invalid timestamp"

Packet pts 未在 stream.time_base

确认 rescale_ts(codec, stream) 在 write_header 之后

frame.time 返回 None

pts 为 None

检查解码错误;用 dts 或计数器降级

编码输出时长不对

codec.time_base ≠ 1/fps 且 pts 按帧号递增

VFR 场景改用高精度 time_base + 真实时间累加

跨流比较结果荒谬

直接减不同 time_base 的 pts

先 rescale_q 到统一基准

💡 核心记忆点

  • 解码看 Stream,编码看 Codec,Mux 前必 Rescale

  • write_header 会改 stream.time_base:rescale 必须在之后进行

  • Frame.time 是安全快捷方式:优先使用,避免手动换算出错

  • start_time 不是 0:同步/裁剪/seek 时必须补偿

  • VFR 用高精度 time_base1/1000 或更高,不要用 1/fps

  • None 是常态:pts/duration/time_base 均可能为 None,防御性编程必不可少


评论