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

PyAV 基础实战 (Cookbook: Basics)

1. 保存关键帧 (Saving Keyframes)

通过设置 skip_frame 跳过非关键帧,可大幅提升仅查看/提取关键帧时的处理速度。

import av
import av.datasets

content = av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4")
with av.open(content) as container:
    stream = container.streams.video[0]
    # ✅ 核心:仅解码关键帧
    stream.codec_context.skip_frame = "NONKEY"

    for frame in container.decode(stream):
        print(frame)
        # ⚠️ 注意:skip_frame 模式下 frame.index 无意义,必须使用 frame.pts
        frame.to_image().save(
            "night-sky.{:04d}.jpg".format(frame.pts),
            quality=80,
        )

💡 排查提示:使用 skip_frame 后若发现文件名序号不连续或异常,请确认是否误用了 frame.index,应始终使用 frame.pts

2. 转封装 (Remuxing)

将音视频数据从一个容器复制到另一个容器,不进行重编码,无画质损失且速度极快。

import av
import av.datasets

input_ = av.open(av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4"))
output = av.open("remuxed.mkv", "w")

# 以输入流为模板创建输出流(自动复制编解码参数)
in_stream = input_.streams.video[0]
out_stream = output.add_stream(template=in_stream)

for packet in input_.demux(in_stream):
    print(packet)

    # ⚠️ 必须跳过 demux 产生的 flush 包
    if packet.dts is None:
        continue

    # ✅ 将数据包重新绑定到输出流
    packet.stream = out_stream
    output.mux(packet)

input_.close()
output.close()

Remuxing 关键要点

步骤

说明

add_stream(template=...)

自动复制流的编解码器配置,避免手动设置

跳过 dts is None 的包

flush 包仅用于通知解码器结束,写入输出文件会导致错误

packet.stream = out_stream

必须执行,否则 mux 时无法确定目标流

3. 裸流解析 (Parsing)

当拥有原始字节流(如 Annex B 格式的 H.264)而非完整容器时,使用 CodecContext.parse() 将字节流切分为 Packet。

import av

fh = open("night-sky.h264", "rb")
codec = av.CodecContext.create("h264", "r")

while True:
    chunk = fh.read(1 << 16)  # 每次读取 64KB

    packets = codec.parse(chunk)
    print(f"Parsed {len(packets)} packets from {len(chunk)} bytes:")

    for packet in packets:
        frames = codec.decode(packet)
        for frame in frames:
            print("       ", frame)

    # ⚠️ 必须在读取空 chunk 后再退出,确保 parser 内部缓冲被 flush
    if not chunk:
        break

💡 注意:PyAV 尚未暴露 Bitstream Filter API,若需要将 MP4 中的 H.264 转为 Annex B 格式,仍需借助 ffmpeg -bsf:v h264_mp4toannexb 命令行工具预处理。

4. 多线程解码 (Threading)

PyAV 默认使用 SLICE 线程模式(多线程协作解码单帧)。启用 FRAME/AUTO 模式可实现多线程并行解码多帧,性能提升显著。

线程模式对比

模式

原理

优点

缺点

SLICE (默认)

多线程协作解码同一帧

延迟低,API 行为稳定

并行度有限

AUTO / FRAME

多线程各自解码独立帧

速度大幅提升(实测约 5倍

解码延迟增大,Packet 与 Frame 不再一一对应

启用 AUTO 线程

container = av.open("video.mp4")

# ✅ 仅需一行代码即可切换线程模式
container.streams.video[0].thread_type = "AUTO"

for packet in container.demux():
    for frame in packet.decode():
        process(frame)

container.close()

⚠️ 重要提醒:启用 AUTO 线程后,packet.decode() 返回帧的时序会有明显延迟(即送入多个 Packet 后才开始产出 Frame)。在实时性要求高的场景中需谨慎评估。

💡 开发排查备忘

  1. 关键帧提取慢 → 检查是否设置了 skip_frame = "NONKEY"

  2. Remux 输出文件损坏 → 检查是否跳过了 dts is None 的 flush 包,以及是否正确绑定了 packet.stream

  3. 裸流解析丢帧 → 确认循环是否在 chunk 为空后仍执行了一次 parse() 以 flush 缓冲区。

  4. 解码性能瓶颈 → 优先尝试 thread_type = "AUTO",但需接受延迟增加的代价。

  5. 资源泄漏 → 所有示例均强调显式 close() 或使用 with 语句,务必遵循此规范。


评论