一次改四个参数:容器、分辨率、帧率、声道。 丢帧必须重排 pts,这一条在这里第一次真用到。
4.5.4 把账算清了,这一节拿一次实战收尾。 下一章 5.1 开始拆 libavutil。
一、案例的四个要求
这道题把转码链的三个环全用上了。 要求 落在哪一环
容器换成 mkv 封装 分辨率减半 处理 帧率 25 改 15 处理,且要重排时间戳 声音改单声道 处理,用重采样
四个要求里只有第一个不碰像素,其余三个都得走完整的解码与编码。 所以这道题的耗时必定是转码量级。 如果只提第一个要求,那就该走转封装,时间差十倍。需求决定路线,这一点比背参数重要。
二、改帧率为什么最麻烦
因为帧率一变,每一帧的时间坐标就全变了。 25 帧降到 15 帧,两秒的内容从 50 帧变 30 帧。 如果你只是把 50 帧里丢掉 20 帧、其余原样送进编码器,pts 还是按 25 帧的网格算的。 做法 结果
只丢帧,不改 pts 时长不变,画面卡顿 丢帧并重排 pts 时长不变,播放流畅 丢帧且改帧率参数 时长正确,但可能音画不同步
这里的做法是「按新帧率画网格,同一个网格只留第一帧」: 把原来每一帧的 pts 换算成 15 帧率下的格子号,格子没变就跳过。 这样丢帧与重排一次完成,不会出现空洞。比「先丢再排序再重编号」简单,也不容易错。
三、改声道要走重采样
立体声变单声道不是「取一路扔一路」那么简单。
直接扔会损失一半信息,响度掉下来。
正确做法是把两路混合,这属于重采样的活儿,交给 AudioResampler。
采样率一起改了(44100 降到 22050),因为单声道通常配低采样率,既省体积又能保持听感。
重采样也要冲空包:resample(None) 把缓冲里剩下的吐出来。
忘了这一步,结尾会少一小段声音——这就是 4.5.4 说的第一条规矩。
四、跑完之后要核对什么
转码最容易出的问题是「跑完了但不对」,所以要逐项核对。 核对项 该对上什么
容器 matroska 分辨率 80×60 帧率 15 时长 与原文件接近 采样率 22050
时长这一项最该看。 改了帧率之后时长跑了,说明时间戳算错了; 时长对了但画面卡,说明帧没丢干净。 「时长对、画面顺、参数对」三条都过,这次转码才算成。 只看「没报错」是不够的。
五、动手 Demo:一次改四个参数,前后对照
四个改动落在三个环上,跑完逐项核对。
import av
with av.open("_media32/tiny.mp4") as ic:
with av.open("_media32/case_out.mkv", "w") as oc:
vs = oc.add_stream("libx264", rate=15)
vs.width, vs.height, vs.pix_fmt = 80, 60, "yuv420p"
aus = oc.add_stream("aac", rate=22050)
aus.layout = "mono"
rs = av.AudioResampler(format="fltp", layout="mono", rate=22050)
last = -1
for frame in ic.decode(ic.streams.video[0]):
g = int(float(frame.pts) / 12800.0 * 15)
if g == last:
continue
last = g
nf = frame.reformat(width=80, height=60, format="yuv420p")
for pkt in vs.encode(nf):
oc.mux(pkt)
实跑输出:
--------------------------------------------------------------------
一次改四个参数,前后对照
--------------------------------------------------------------------
**① 案例要求:容器换成 mkv、分辨率减半、帧率 25 改 15、声音改单声道**
四个改动落在三个环上,正好把转码链用满。
**② 前后对照**
项 原文件 产物
--------------------------------------------
容器 mov matroska
大小 46137 字节 10293 字节
时长 2.02 秒 2.00 秒
分辨率 160×120 80×60
帧率 25 15
视频帧数 50 0(没写)
音频采样率 44100 22050
音频声道 stereo mono
耗时 **86.3 毫秒**,视频送进编码器 **30 帧**(原 50 帧,按 15 帧率抽掉了一部分)。
**③ 三处值得注意的地方**
**第一,帧数是 50 抽成 30,不是整除。** 25 帧降到 15 帧,两秒的内容从 50 帧变 30 帧,
比例正好是 15/25。
**第二,丢帧必须重排 pts。** 这里用「pts 乘 15 取整当网格号」的办法,同一个网格只留第一帧。
不重排就会留下空洞,播出来卡顿。
**第三,MKV 不报每路码率,也不报总帧数。** 产物那两路的 bit_rate 都是 None,视频帧数那一栏是 0,
**都不是编码失败,是容器没把这些数写进头里。**
**同一个文件,MP4 里能读到的字段,MKV 里可能读不到。** 查字段前先确认容器支持不支持,
别把「没有」当成「零」。
**④ 这一章到此为止**
五环的链、两个方向的缓冲、两条转码路、一次四参数实战,这些是后面每一章的操作底座。
第五章开始拆「处理」这一环里的各个库。
g 就是新帧率下的格子号,同一格只留一帧,丢帧与重排一步做完。
产物是 matroska,两路的 bit_rate 读出来都是 None,不是失败。
30 秒实验
- 把
rate=15改成rate=10,数一数留下多少帧。 - 把
layout="mono"改成"stereo",看音频体积差多少。 - 把重采样的
resample(None)删掉,听结尾少了多少。
常见误区
- 丢帧不重排 pts。 时长不变但画面卡顿,因为时间坐标有空洞。
- 立体声转单声道直接扔一路。 响度掉一半,正确做法是混合。
- 只看没报错就以为成功。 要核时长、分辨率、帧率、采样率四项。
自测三问(点开对答案)
1. 改帧率为什么必须重排时间戳? —— 帧率变了时间网格就变了。只丢帧不改 pts,时间坐标会出现空洞,播出来卡顿。 2. 立体声变单声道该怎么做? —— 交给重采样器混合两路,不能直接扔掉一路,否则响度掉一半。 3. 转码跑完最该核对哪一项? —— 时长。时长跑了说明时间戳算错;时长对但画面卡说明帧没丢干净。- ☐ (本章完,下一节 5.1 libavutil 的数据结构与日志)