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

源码树一个库一个目录,编译完一个库变成一堆模块。 要不要碰源码,只取决于一件事:现成的构建能不能满足你。

📝 本节和前面几节的关系

1.3.2 讲的是编好的库,这一节讲源码到编译产物这条路。 1.4 开始讲这些库合起来能干什么。

一、源码树长什么样

解开源码包,第一层目录就是这套 SDK 的目录结构:

目录 编出哪个库 里面是什么


libavformat/ libavformat 各封装格式的读写代码 libavcodec/ libavcodec 各编解码器与通用框架 libavfilter/ libavfilter 每个滤镜一个文件 libavutil/ libavutil 内存、数学、公共结构 libswscale/ libswscale 像素格式转换 libswresample/ libswresample 音频重采样 libavdevice/ libavdevice 各系统的采集接口 fftools/ 三个 exe 三个工具的 main 函数

最后一行是很多人忽略的:ffmpeg、ffplay、ffprobe 的源码就在 fftools 目录里。 也就是说「命令行工具」本身也是这份源码的一部分,不是另外的东西。

想搞清楚某个参数怎么实现的,直接去 fftools 里搜参数名。 这比翻文档快得多,而且是唯一准确的答案。

二、configure 那一步在做什么

它是整个编译流程里唯一需要你拍板的一步,也是最容易失败的一步:

它在做三件事 结果


探测系统里有哪些库 决定哪些开关可以打开 按开关生成 config.h 决定哪些代码被编译进去 生成 Makefile 决定怎么编译、装到哪儿

所以 configure 通过以后,能力就定死了。 后面 make 只是把这个决定执行掉,不会再改变什么。

这也解释了为什么「装完发现缺功能」只能重编,不能补装。 很多新手想「再装一个包补上」,那在 FFmpeg 上是走不通的—— 它不是插件式架构,是编译期静态决定的。

三、编译产物的三个形态

同一份源码,能编出三种东西,用途完全不同:

形态 产物 给谁用


静态库 libav.a 链接进别人的程序 动态库 libav.so 或 .dll 运行时加载,可替换 可执行文件 ffmpeg 等三个 exe 直接敲命令

静态与动态的取舍在 1.1.4 讲过一半:LGPL 下动态链接才能闭源。 所以做商业产品时,这一行不是技术选择,是许可证选择。

还有第四种形态,本课的 Demo 一直在用:语言绑定。 它把动态库再包一层,让你在 Python、Java、C# 里直接调。 包一层的好处是省事,坏处是有些底层能力它没暴露出来。

四、什么时候才需要碰源码

四种情况,只有第一种是必须的:

情况 要不要碰源码


现成构建缺你想要的功能 要,重新 configure 想改某个滤镜的行为 要,改 C 代码 只是想把命令跑起来 不用,下静态包 想读懂某个参数怎么实现 可以只看不编

第二行要提醒一句:改滤镜是最容易踩的坑。 滤镜是并行处理的,看起来一次处理一帧,实际上下面的代码可能在多线程跑。 随手加个全局变量,就会出现「单线程正常、多线程花屏」。

第四行反而是最值得推荐的:源码就在那儿,读它不花钱。 遇到文档说不清的行为,去 fftools 或对应的 libav* 目录里搜关键字, 通常五分钟就能看到答案——而且这个答案不会过时。

五、动手 Demo:源码编完长什么样

沙箱里没有 FFmpeg 源码,但有编译好的 Python 绑定。 它能说明「一个库编出来会变成什么样」——形态是一样的,只是换了语言。

import os, av

d = os.path.dirname(av.__file__)
mods = []
for root, _, fs in os.walk(d):
    for f in fs:
        if f.endswith((u".pyd", u".so")):
            p = os.path.join(root, f)
            mods.append((os.path.relpath(p, d), os.path.getsize(p)))

print(u"模块数", len(mods))
for r, s in sorted(mods, key=lambda x: -x[1])[:5]:
    print(u"%-26s %6.0f KB" % (r, s / 1024.0))

实跑输出:

--------------------------------------------------------------------
源码编完长什么样——49 个扩展模块,各管一块
--------------------------------------------------------------------

**① 源码树是一个库一个目录,编完是一个库一堆模块**

  编译产物                 大小(KB)     里面是什么
  ----------------------------------------------------------------------
  video/frame.pyd          290            AVFrame 的实现,最重的一个
  filter/context.pyd       132            滤镜实例,参数解析都在这儿
  codec/context.pyd        120            AVCodecContext 的实现
  container/core.pyd       94             AVFormatContext 的实现
  error.pyd                92             错误码与异常映射
  container/output.pyd     88             封装写出那一半

**一共 49 个扩展模块。它们在 C 里是七个库,在 Python 里被拆成按对象分的模块:**
**一个库里的东西太多,绑定时按「一个结构体一个模块」切开,用起来才顺手。**

**② 最大的那个是 AVFrame,不是巧合**

**AVFrame 要管像素平面、行宽对齐、时间戳、色彩信息、附属数据……**
**它是全流程里被摸次数最多的结构,所以实现也最厚。**
**这一点在下一节的流程图里会更明显:五个阶段里有四个都在动它。**

**③ 从源码到能用的 exe,四步**

  步     做什么                         产物
  -----------------------------------------------------------------
  1      ./configure 决定开哪些开关     config.h 与 Makefile
  2      make 编译七个库                libav*.so 或 .dll
  3      make 编译三个工具              ffmpeg、ffplay、ffprobe
  4      make install 装到位            可执行文件加头文件

**第 1 步决定后面所有事。第 2、3 步只是把决定执行掉。**
**所以「我这个 ffmpeg 怎么没有某某功能」的答案,永远在第 1 步。**

第一张表挑出最大的六个模块,第一个是 AVFrame。 它不是巧合——AVFrame 要管像素平面、行宽对齐、时间戳、色彩信息、附属数据, 是全流程里被摸次数最多的结构。

第二张表说明一件事:源码里七个库,编出来是几十个模块。 因为一个库里的东西太多,绑定时按「一个结构体一个模块」切开,用起来才顺手。

第三张表把「从源码到能用的 exe」压成四步。 第 1 步决定后面所有事,第 2、3 步只是执行。

所以「我这个 ffmpeg 怎么没有某某功能」的答案,永远在第 1 步。 想补,只能回到第 1 步重来——FFmpeg 不是插件式的,装不上去。

30 秒实验

  1. 搜参数:下源码包,在 fftools/ffmpeg_opt.c 里搜 drawtext 之类不会命中,要搜 -vf 的处理逻辑。源码里全是参数解析表,比文档全。
  2. 看滤镜:libavfilter/ 下每个滤镜一个 vf_*.c 文件(视频)或 af_*.c(音频)。想看 scale 怎么算的,就打开 vf_scale.c。
  3. 验形态:找到你机器上的 libavcodec.so 或 avcodec-*.dll,看它多大。几百 KB 到几十 MB 都正常,取决于开了多少编解码器。

常见误区

  1. 以为缺功能可以「补装一个包」。FFmpeg 的能力在 configure 时就定死了,运行时不能加载新编解码器。要补只能重编。
  2. 以为命令行工具是另一份代码。它就在源码树的 fftools 目录里,和库是同一份源码编出来的。
  3. 以为改滤镜很简单。滤镜内部可能多线程执行,随手加全局状态就会导致单线程正常、多线程花屏。改之前先看它是怎么被调度的。
自测三问(点开对答案) 1. 源码树里 fftools 目录编出什么? —— 编出 ffmpeg、ffplay、ffprobe 三个可执行文件。它们只是库的调用者,本身不含编解码逻辑。 2. 为什么 configure 之后就不能再补功能了? —— 因为 configure 生成 config.h 决定哪些代码参与编译,Makefile 也按那个决定生成。没编进去的代码根本不在二进制里,运行期无从加载。 3. 静态库和动态库在许可证上有什么差别? —— LGPL 下动态链接通常不构成衍生作品,静态链接通常算。所以做闭源产品时,选动态库不是性能考虑,是合规考虑。

相关笔记