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 关键要点
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 模式可实现多线程并行解码多帧,性能提升显著。
线程模式对比
启用 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)。在实时性要求高的场景中需谨慎评估。
💡 开发排查备忘
关键帧提取慢 → 检查是否设置了
skip_frame = "NONKEY"。Remux 输出文件损坏 → 检查是否跳过了
dts is None的 flush 包,以及是否正确绑定了packet.stream。裸流解析丢帧 → 确认循环是否在
chunk为空后仍执行了一次parse()以 flush 缓冲区。解码性能瓶颈 → 优先尝试
thread_type = "AUTO",但需接受延迟增加的代价。资源泄漏 → 所有示例均强调显式
close()或使用with语句,务必遵循此规范。