七个 API 管「开、找、读、跳、写头、写包」,一条流水线走完。 在 PyAV 里有一半看不见——它们被藏进了 av.open。
7.1 讲结构,这一节讲操作这些结构的函数。 下一篇 7.3 拿它们跑一次完整的解封装。
一、七个函数的顺序
按调用顺序排,比按字母排好记。 顺序 C 的 API 干什么
一 avformat_open_input 打开,认容器 二 avformat_find_stream_info 补全流参数 三 av_find_best_stream 挑出要用的流 四 av_read_frame 读包 五 av_seek_frame 跳(按需) 六 avformat_write_header 写文件头 七 av_interleaved_write_frame 写包
前四个是读方向,后两个是写方向,第五个两边都能用。 这个顺序不能颠倒:没 open 就没流,没流就没包,没写头就写不了包。 FFmpeg 的 API 是状态机的形态,不是可以随便排的工具箱。
二、PyAV 藏起来的那三个
藏起来不是消失,是不用你操心。 C 里要手写 PyAV 里
分配 AVFormatContext av.open 内部做 find_stream_info 探一轮 av.open 内部做 write_header 写头 av.open(..., "w") 内部做
省事的代价是看不见。 出问题的时候不知道它探了几轮、读了多少字节、头写成什么样。 要看得见也有办法:拿一个包装器套住输入(第六章那个 LogIO),把每一次 read 都记下来。 这是排查封装问题的第一招。
三、find_stream_info 到底在探什么
打开容器的头时,参数常常不全。 缺什么 为什么要探 探不到会怎样
分辨率 头里可能只写了个大概 拿到 0 帧率 要数几个时间戳才准 算不出 编码参数 SPS/PPS 在码流里 解不了 时长 有的容器不写 读到 None
它的做法就是「先真解一小段试试」。 所以打开一个网络流时会先卡一下,那是在探。
PyAV 里这个动作是自动的,代价同样存在:av.open 比自己以为的要慢。
要精确控时间,就得知道它在探。
四、交错:封装里最容易忽略的一件事
封装不是把包按流分组放,而是按时间穿插放。 排法 播放效果 为什么会这样排
交错 边读边播 音视频包挨着,都够用 先视频后音频 要缓冲很久 要等音轨才开始
av_interleaved_write_frame 名字里那个 interleaved 就是干这个的。 它内部有队列,
按 pts 排好再写。
这也是为什么不能随手用 av_write_frame 代替:那个是「你喂什么就写什么」,交错得你自己保证。
自己写封装时最常见的问题就是音画不同步,十有八九是交错没做对,而不是时间戳算错了。
五、关的时候要成对
开一个关一个,这是硬规矩。 开 关 漏了会怎样
avformat_open_input avformat_close_input 内存泄漏 avformat_alloc_output_context2 avformat_free_context 同上 avio_open avio_closep 输出文件不完整
最后一行最阴:输出没关好,文件头里的索引就没写完,文件看着有、打不开。
这就是「录了半小时,文件坏了」的常见原因。
PyAV 里对应的就是 container.close(),而且要用 with 或者 try 保证它一定被调到。
写输出时尤其不能省。
六、动手 Demo:封装层六个 API,在 PyAV 里长什么样
先把七个 API 一一对照,再拿看得见的那几个真跑一遍。
import av
c = av.open("tiny.mp4")
vs = c.streams.video[0]
print(c.format.name, len(c.streams), vs.time_base, vs.average_rate)
nv = na = 0
for pk in c.demux():
if pk.size == 0:
continue
if pk.stream.type == "video":
nv += 1
else:
na += 1
c.close()
print("视频包", nv, "音频包", na)
实跑输出:
--------------------------------------------------------------------
封装层六个 API,在 PyAV 里长什么样
--------------------------------------------------------------------
**① 一一对照**
C 的 API PyAV 里怎么用 干什么
--------------------------------------------------------------------------
avformat_open_input av.open(path) 打开,认容器
avformat_find_stream_info av.open 里自动做 补全流参数
av_find_best_stream streams.video[0] 挑一条流
av_read_frame container.demux() 读一个包
av_seek_frame container.seek() 跳到某个时间
avformat_write_header av.open(..., "w") 里做 写文件头
av_interleaved_write_frame container.mux(pkt) 写一个包
**七行里有三行在 PyAV 里「看不见」**:打开、找流信息、写文件头,都被包进了 `av.open` 里。
**这是好事,也是坑**——出问题时不知道它替你做了什么。
**② 看得见的那几个,真跑一遍**
认出 **mov,mp4,m4a,3gp,3g2,mj2**,**2 条流**,视频 time_base **1/12800**、音频 **1/44100**,
帧率 **25**。
**一次 demux 循环里一共出来 50 个视频包、88 个音频包**,
它们是被**交错**排好的——一个视频包跟着一两个音频包。
**这就是「封装」两个字的实际含义**:不是把数据打个包,而是**按播放顺序把两条流的包穿插排好**。
**③ 交错排错了会怎样**
**所有视频包排在前面、音频包排在后面**,播放器就得先把视频全缓冲下来,才能配上声音。
**这就是「音画不同步」的一大来源。**
**`av_interleaved_write_frame` 名字里那个 interleaved 就是干这个的**:它内部有个队列,
按 pts 排好再写,不按你喂的顺序写。
一次 demux 出来 50 个视频包、88 个音频包,是交错排好的。 这个「交错」就是封装真正干的活。
30 秒实验
- 把
av_find_best_stream的对应写法找出来(提示:streams.best)。 - 在 demux 循环里打印每个包的 pts,看两条流的时间戳是不是交替上升。
- 故意不 close 就跑第二次,看会不会报资源占用。
常见误区
- 以为 PyAV 少做了事。 它把三步藏进了 av.open。
- 用 av_write_frame 代替 interleaved 那个。 交错得自己保证。
- 输出容器不开 with 直接跑。 中途出错就留下一个坏文件。
自测三问(点开对答案)
1. 七个 API 里哪几个在 PyAV 中看不见? —— open_input、find_stream_info、write_header,都被 av.open 包了。 2. find_stream_info 为什么慢? —— 它会真解一小段来补全参数,网络流上尤其明显。 3. 交错是什么意思? —— 按时间把多条流的包穿插排好,让播放器边读边播。- ☐ (本节完,下一节 7.3 FFmpeg解封装案例实战)