「格式转换」其实是两件不同的事:换容器和换编码。 前者加 -c copy 几十秒过,后者必须动像素,耗时差一个量级。
2.1 讲了参数位置,这一节是本章第一条真正干活的命令。 2.3 抽帧、2.4 拼视频,都是从这条命令派生出来的。
一、「格式转换」这四个字底下有四件事
很多人说「帮我转个格式」,其实想要的可能是下面四件里任意一件:
说法 实际做了什么 能不能 -c copy
换容器 码流原样搬进新壳 能 换编码 解成像素再重新压 不能 换分辨率 缩放了再压 不能 换码率 用新码率重压 不能
后三行做不到 -c copy 是硬限制,不是参数没写对:
它们要改的是「像素本身」,而 -c copy 的前提就是「不碰像素」。
所以判断该走哪条,只需要问一句:我要改的东西在壳上还是在画面上?
在壳上就 -c copy,在画面上就只能重编码。
二、-c copy 省掉的到底是哪三步
回到五阶段流水线看,-c copy 把中间三步全剪掉了:
阶段 正常转码 加了 -c copy
解封装 做 做 解码 做 跳过 滤镜 做 跳过 编码 做 跳过 封装 做 做
剩下的两步都只搬字节,不看内容——所以转封装的速度基本由磁盘决定,跟分辨率无关。
这也解释了一个反直觉的现象:4K 片子和 480p 片子,转封装的耗时差不多。
但只要去掉 -c copy,两者的耗时立刻拉开几十倍。
三、三条命令覆盖绝大多数需求
不用记参数表,记住这三条就够开始用了:
要做的事 命令 耗时量级
换容器 ffmpeg -i 输入.mp4 -c copy 输出.mkv 秒级 换编码 ffmpeg -i 输入.mp4 -c:v libx264 输出.mp4 分钟级 只抽音频 ffmpeg -i 输入.mp4 -vn -c copy 输出.m4a 秒级
第一条和第三条都带 -c copy,所以都是秒级。
第三条里的 -vn 是「不要视频」,配上 -c copy 就只是把音轨原样倒出来。
第二条是唯一慢的,因为它真的在重压。这时候才需要考虑编码器选谁、 码率给多少、preset 用哪个——这些都是第五章的内容。
还有一个必须知道的限制:不是所有容器都能装所有编码。 装不进去时 ffmpeg 直接报错,不会偷偷帮你重编码——这是好事,别嫌它烦。
四、动手 Demo:转封装与转码差多少
同一份素材、同一段内容,用两条路线各转一次:一条只搬包,一条解了再编。 两条路线的耗时与输出字节都记在下面,差距比想象中大。
import time
import av
def copy_route(src, dst):
# 转封装:不解码,直接把包搬过去
ic = av.open(src)
oc = av.open(dst, u"w")
ost = oc.add_stream_from_template(ic.streams.video[0])
for pk in ic.demux(video=0):
if pk.dts is None:
continue
pk.stream = ost
oc.mux(pk)
oc.close()
ic.close()
def recode_route(src, dst):
# 转码:解成像素,再重新压一遍
ic = av.open(src)
cc = ic.streams.video[0].codec_context
oc = av.open(dst, u"w")
s = oc.add_stream(u"libx264", rate=25)
s.width, s.height, s.pix_fmt = 160, 90, u"yuv420p"
for pk in ic.demux(video=0):
if not pk.size:
continue
for f in cc.decode(pk):
for o in s.encode(f):
oc.mux(o)
for o in s.encode(None):
oc.mux(o)
oc.close()
ic.close()
for name, fn in ((u"转封装", copy_route), (u"重编码", recode_route)):
t0 = time.perf_counter()
fn(u"src.mp4", u"out_%s" % name)
t = (time.perf_counter() - t0) * 1000
print(u"%s %.1f 毫秒 %d 字节" % (name, t, os.path.getsize(u"out_%s" % name)))
实跑输出:
--------------------------------------------------------------------
音视频格式转换——转封装与转码差多少
--------------------------------------------------------------------
**① 同一个输入,两条命令,成本差一个量级**
做法 耗时(毫秒) 输出字节 动像素吗
-------------------------------------------------------------
转封装 -c copy 5.0 9144 不动
重编码 67.7 9484 动
**源文件 9606 字节、125 帧。转封装只换了外壳,重编码把解出来的 123 帧全拆开又压了一遍。**
**耗时差 13.6 倍,字节数差 340——而画质上,转封装是一点不掉,重编码是掉一点。**
**② 所以「格式转换」这个词其实指两件不同的事**
说法 实际做了什么 什么时候用
--------------------------------------------------------------
换容器 码流不动,只改外面的壳 播放器不认这个后缀
换编码 解成像素再重新压 播放器不认这种编码
换分辨率 必须重编码 大屏转小屏
换码率 必须重编码 压小一点省流量
**后三行都绕不开重编码,因为它们改的是「像素本身」。**
**只有第一行能靠 -c copy 秒过——这也是它最值钱的地方。**
**③ 判断该走哪条,问自己一个问题就够**
**「我要变的东西,是文件里面的排列,还是画面本身?」**
**排列 → 转封装;画面 → 转码。**
注意 copy_route 里一处 decode 都没有——它从头到尾只碰 Packet。
recode_route 里既有 cc.decode 又有 s.encode,中间必然过了一遍像素。
两条路线的输出字节数不一样,而且转封装那一份更小。 码流一个字节没动,少的全是容器自己的开销:mp4 的索引记在文件尾,mkv 是边写边记。
耗时差了一个数量级,而这个差距在真实素材上会更大: 素材分辨率越高、越长,重编码那一路的成本涨得越快,转封装那一路几乎不动。
还要注意一个细节:add_stream_from_template 是「照抄源流的参数」。
这一步是转封装能成立的关键——输出流的所有编码参数必须跟源流完全一致。
30 秒实验
- 比一次:拿一个几十 MB 的 mp4,先
-c copy转 mkv,再去掉这个参数转一次,看耗时差几倍。 - 验一次:转封装后比较输入输出的大小,你会发现码流字节数一样,差的是容器开销。
- 试一次错:把 h264 塞进一个不支持的容器,看它报什么错。这个错以后会经常遇到。
常见误区
- 能转封装却去转码。
-c copy秒级的事,重编码要几分钟,画质还掉一点。先想清楚自己是不是只想换个壳。 - 以为
-c copy能改编码。它只搬码流,编码方式一个字都不变。想换编码必须去掉这个参数。 - 以为所有容器都能装所有编码。mp4 装不进某些老编码,mkv 基本什么都能装。报错是正常的,不是命令写错。
自测三问(点开对答案)
1. 把 mp4 转成 mkv,画质会掉吗? —— 不会,只要加了 `-c copy`。码流原样搬过去,一帧都没重编。掉画质只发生在你去掉这个参数让它重压的时候。 2. 为什么转封装的耗时跟分辨率几乎无关? —— 因为它跳过了解码、滤镜、编码三步,剩下的解封装与封装都只是搬字节。搬字节的成本跟数据量有关,跟画面内容无关。 3. `-vn -c copy` 这条命令做了什么? —— `-vn` 表示不要视频流,`-c copy` 表示音频码流原样搬。合起来就是「只把音轨原封不动倒出来」,所以是秒级。- ☐ (本节完,下一节 2.3 视频缩略图)