🎬 第一章 FFmpeg入门简介
📄 一句话概括

八类功能,从转封装到推流,全部由那七个库组合出来。 先知道有哪些,后面各章逐个深挖;这一节只做一张地图。

📝 本节和前面几节的关系

1.3 讲的是零件,这一节讲零件拼起来能干什么。 1.5 再往下,讲这些功能在内部是怎么走完的。

一、八类功能,一张地图

FFmpeg 能做的事,其实就八大类,本课程也是按这个顺序展开的:

功能 一句话 本课哪一章


转封装 换个容器,码流不动 第四章 转码 换编码,必须动像素 第五章 加水印 叠一张图或一行字 第六章 特效滤镜 缩放、裁剪、变速、旋转 第七章 流媒体 把流转成网络协议送出去 第八章 设备采集 从摄像头、声卡、屏幕取数据 第九章 探测信息 看清一个文件到底是什么 三个工具章 截图抽帧 把某一刻存成图片 转码章顺带

这张表按「成本」从低到高排的:转封装最便宜,转码最贵。 因为转封装一步都不碰像素,转码要把每一帧拆开再压回去。

记住这个顺序很实用:能转封装解决的事,绝不要转码。 一个 4 GB 的片子,转封装几十秒,转码半小时,画面质量还掉一点。

二、转封装:最快的转换

命令只多一个 -c copy,意义是「不要重新编码」:

做什么 关键参数 耗时量级


mp4 转 mkv -c copy 秒级 提取视频流 -c copy -an 秒级 提取音频流 -c copy -vn 秒级 合并多段 -f concat -c copy 秒级

-c copy 的意思是「码流原样抄过去」,所以画质一点不掉。 代价是「原来怎么编的,转完还是怎么编」—— 想把 H.265 压成 H.264,-c copy 帮不了你。

还有一个坑:不是所有容器都能装所有编码。 比如 mkv 什么都能装,mp4 对某些老编码就装不进去。 装不进去的时候 ffmpeg 会直接报错,不会偷偷帮你转码——这是好事,别嫌它烦。

三、转码:慢,但绕不开

要动像素,就要走解码、滤镜、编码三步,每一步都费时间:

场景 为什么必须转码


H.265 转 H.264 播放器不支持 H.265 4K 转 1080p 分辨率变了,码流必须重算 改码率 码率是编码参数,不是容器属性 改帧率 要重新决定保留哪些帧 剪掉一段 剪点在关键帧之间就得重编

最后一行值得单独说:理论上可以「只保留某几秒」,但剪辑点如果不在关键帧上, 前面的帧解不出来,只能从上一个关键帧开始重编。 这就是为什么精准裁剪比重编快不了多少。

控制转码成本的手段只有一个:选对编码器参数。 预设(preset)从快到慢有七八档,画质差不多,速度能差好几倍。 这条在第五章会展开,这里先记住「慢不等于好」。

四、那些本机做不了的功能

这一节的 Demo 会实测五件事,其中两件在本机做不成。 两件的原因都不在代码,在编译开关:

功能 缺什么 怎么办


文字水印 缺 freetype 与 harfbuzz 换完整构建 字幕烧录 缺 libass 换完整构建 推 RTMP 流 缺 rtmp 协议支持 换完整构建 硬件加速 缺对应 SDK 装显卡厂商的运行库

「换完整构建」不是敷衍的说法。 同一段命令,在缺 freetype 的构建上会直接报「No such filter」, 在完整构建上一个字不改就能跑。

所以遇到「教程里的命令跑不通」,第一件事不是怀疑命令写错了, 是先确认自己的构建有没有那个能力—— ffmpeg -filters | grep 名字 一行就能问清楚。

五、动手 Demo:八件事,本机能做几件,逐件试

先造一段 25 帧的素材,再逐件试:能做的给数字,不能做的说原因。 素材是代码现生成的,所以下面每个数字都能复现。

import av, numpy as np

# 造 25 帧 160x90 的 H.264 素材
c = av.open(u"src.mp4", u"w")
s = c.add_stream(u"libx264", rate=25)
s.width, s.height, s.pix_fmt = 160, 90, u"yuv420p"
for i in range(25):
    a = np.zeros((90, 160, 3), dtype=np.uint8)
    a[:, :, 0] = i * 10
    f = av.VideoFrame.from_ndarray(a, format=u"rgb24")
    for p in s.encode(f):
        c.mux(p)
for p in s.encode(None):
    c.mux(p)
c.close()

# 抽一帧存图
c = av.open(u"src.mp4")
next(c.decode(video=0)).to_image().save(u"f0.png")
c.close()

实跑输出:

--------------------------------------------------------------------
常用功能八件事——本机能做几件,逐件试
--------------------------------------------------------------------

**① 先造一段素材:25 帧、160×90、H.264**

  项           值
  -------------------------------
  文件大小     2800 字节
  帧数         25
  分辨率       160×90
  编码         H.264,yuv420p

**这段素材是代码现生成的,不是拷来的文件——所以下面每个数字都可以复现。**

**② 逐件试,做得成的给数字,做不成的说原因**

  功能               本机能做吗     结果或原因
  -----------------------------------------------------------
  转封装 mp4→mkv     能             2425 字节,比源小 375
  抽一帧存 PNG       能             289 字节
  缩放到一半         能             80×45
  文字水印           不能           drawtext 没编进来
  RTMP 推流          不能           rtmp 协议没编进来

**③ 两件「不能」的,原因都在编译开关上,不在代码上**

**drawtext 要 freetype 与 harfbuzz,这个构建没编;**
**rtmp 要 librtmp 或自带的 rtmp 协议支持,这个构建也没编。**
**换一个官方完整版 exe,同一段 Python 代码一个字不改就能跑通——**
**这就是「命令行与库是同一份东西」的最直接证据。**

**④ 转封装为什么字节数会变**

**码流一个字节没动,变的只是外面那层「文件怎么排」的说明。**
**mp4 有索引表在文件末尾,mkv 的索引是边写边记的,所以同样一段流装进去不一样大。**
**这一条在第四章讲转封装时会正式展开,这里先记住:容器换、码流不动。**

第一张表先交素材:25 帧、160×90、H.264,2800 字节。 小得离谱,但结构完整——有序列头、有关键帧、有普通帧。

第二张表是逐件试的结果:转封装、抽帧、缩放都能做, 文字水印和 RTMP 推流不能。

注意转封装那一行:2800 字节变成 2425,少了 375 字节。 码流一个字节没动,少的是容器自己的开销—— mp4 的索引在文件末尾,mkv 的索引边写边记,所以同样一段流装进去不一样大。

两件「不能」的原因都在编译开关上,不在代码上。 换一个官方完整版 exe,同一段 Python 代码一个字不改就能跑通—— 这就是「命令行与库是同一份东西」最直接的证据。

30 秒实验

  1. 比速度:拿一个几十 MB 的 mp4,先 -c copy 转一次,再重编码转一次,看两次耗时差多少。这个差距就是「碰不碰像素」的代价。
  2. 查能力:ffmpeg -filters | grep drawtext,有输出才能做文字水印。没有就别浪费时间试参数了。
  3. 抽一帧:ffmpeg -i 文件 -ss 00:00:05 -vframes 1 out.jpg。-ss 放在 -i 前面比放后面快得多,因为它会先跳到关键帧。

常见误区

  1. 能转封装却去转码。-c copy 几十秒的事,重编码要半小时,画质还下降。先想「我只是想换个容器吗」,再决定要不要动编码。
  2. 以为 -c copy 能改编码。它只搬码流,编码方式一点不变。想换编码就得去掉这个参数,让 ffmpeg 重新编。
  3. 以为所有容器都能装所有编码。mp4 装不进某些老编码,mkv 基本什么都能装。装不进去时 ffmpeg 会报错,别以为是命令写错了。
自测三问(点开对答案) 1. 把 mp4 转成 mkv,画质会掉吗? —— 不会,只要加了 `-c copy`。码流原样搬过去,一帧都没重编。掉画质的情况只发生在你去掉 `-c copy` 让它重新编码时。 2. 为什么「精准裁剪」往往比重编快不了多少? —— 因为要裁的位置通常不在关键帧上,前面的帧解不出来,只能从上一个关键帧开始整段重编,再把不要的部分丢掉。 3. 同样一条命令在别人机器上能跑,在你这里报 No such filter,最可能是什么原因? —— 你的构建没编那个滤镜。用 `ffmpeg -filters | grep 名字` 确认,再用 `-version` 看 compile 开关。换一个完整构建就能跑。

相关笔记