FFmpeg的makefile逻辑分析

时间:2022-07-20 00:35:07

在开始分析之前,讲一个 ​​makefile​​​ 的调试技巧,推荐阅读​​《如何调试MAKEFILE变量》​

make -f Makefile -f vars.mk HOSTPROGS

这里我对 ​​vars.mk​​ 做了点修改,因为源 ​​vars.mk​​ 没处理特殊字符,直接 ​​echo​​ 会报错。​​ffmpeg​​ 的 ​​makefile​​ 的变量有很多特殊字符。我用了 ​​warning​​ 来输出,就不会报错。​​vars.mk​​ 下载地址:​​百度网盘​​,提取码:i59d


先来看 ​​FFmpeg​​ 根目录的 ​​makefile​​ 的代码,如下:

FFmpeg的makefile逻辑分析

上图中的 ​​vpath​​ 是定义搜索目录,因为你可以在其他目录执行 configure 文件,那样 SRC_PATH 变量就不是 ​​./​​ 当前目录了。

FFLIBS-$(CONFIG_AVRESAMPLE) += avresample

这些 ​​CONFIG_AVRESAMPLE​​ 之类额变量都是 yes 或者 no,他就是用 ​​yes/no​​ 来控制要不要某些库或者组件。而这些 ​​yes/no​​ 的变量 是 configure 脚本输出的。


下面的代码 是 检测 configure 有没执行,这个是这样的,configure会 生成 ​​ffbuild/.config​​ 文件,如果你改了 FFmpeg 里面的代码,代码文件就会比 ​​.config​​ 文件更新,如果你不执行 configure , 直接 make ,就会报错。

config.h: ffbuild/.config
ffbuild/.config: $(CONFIGURABLE_COMPONENTS)
@-tput bold 2>/dev/null
@-printf '\nWARNING: $(?) newer than config.h, rerun configure\n\n'
@-tput sgr0 2>/dev/null

到这里,我决定不再一行一行代码解析 FFmpeg 项目的 ​​makefile​​,因为 FFmpeg 的 ​​makefile​​ 代码比较多,而且初学者,通常你不需要看完全部的 ​​makefile​​ 才能进行二次开发。

之前说过,8 千行 configure 最后也只是往编译器,链接器传递了一些参数,这些参数就被定义在 ​​config.mak​​ 文件里面,如下:

FFmpeg的makefile逻辑分析


FFmpeg 的 ​​makefile​​ 比较分散,根目录有一个 ​​makefile​​,各个库目录也有一个 ​​makefile​​。库目录的 ​​makefile​​ 是通过 ​​DOSUBDIR​​ 函数 包含进来的,如下:

FFmpeg的makefile逻辑分析

上图代码中,定义了 ​​DOSUBDIR​​ 函数,然后遍历 ​​FFLIBS​​ 数组变量 来调用,​​FFLIBS​​ 就是 FFmpeg 的那 8个库。


因此,FFmpeg 项目根目录有一个主的 ​​makefile​​ (MAIN_MAKEFILE),然后各个子目录都有一个各自的 ​​makefile​​,代码量有点多。但是我们比较常用的只有 4个 知识点。

提示:makefile 也只是一个构建工具,他最后也是调 gcc 来执行编译。


​makefile​​ 里面,我们比较常用的有 4个 知识点。

1,第一个 ​​target​​ 是什么,在哪里定义的?

​makefile​​ 的规则是,执行 ​​make​​ 命令的时候要指定一个 ​​target​​,就是要编译哪个目标,如果不指定,就是编译 ​​makefile​​ 文件里面的第一个目标,第一个 ​​target​​ 也叫默认​​target​​。那 FFmpeg 的默认目标是什么?如下:

FFmpeg的makefile逻辑分析

从上图可以看出,默认 ​​target​​ 是 ​​all​​,因此,我们在编译 FFmpeg 的时候,无论执行 ​​make​​ 还是 ​​make all​​ 命令,效果是一样的。


2,把 ​​.c​​ 文件 编译成 ​​.o​​ 文件的规则在哪里?

我们知道,在编译阶段,各个 ​​.c​​ 文件都是独立编译的。而 ​​makefile​​ 也是调 ​​gcc​​ 来编译,那是在哪里调的 ​​gcc​​ 呢?

这些规则在 ​​common.mak​​ 文件里面,如下:

FFmpeg的makefile逻辑分析

​COMPILE_C​​ 是一个函数调用,如下:

FFmpeg的makefile逻辑分析

这里提醒一下,所有的子目录,所有的 ​​.c​​ 文件编译都是这个规则。这是通用的。也就是说,​​ffmpeg.c​​,​​ffplay.c​​ 文件也是这一个规则。

我们现在来测试一下 是不是这一个规则,看下 ​​ffplay.c​​ 是不是在这里编译的。我们加个东西,如下:

FFmpeg的makefile逻辑分析

我加了一个无意义的选项 ​​-num666​​ 在后面。然后用 ​​make -n > t33.txt​​ 来查看命令,​​-n​​ 代表不执行编译只打印信息。如下:

FFmpeg的makefile逻辑分析

果然,因此所有 的 ​​.c​​ 文件都是在 ​​common.mak​​ 文件里面定义编译规则的。


3,生成 FFmpeg 8个 动态库的规则在哪里?

FFmpeg 里面提供了 8 个库给开发者使用,这些库可以通过打包形成静态库,或者通过链接器来生成动态库。那这些生成静态库,动态库的规则在哪里呢?

以 ​​libavcodec​​ 库为例,​​libavcodec​​ 目录的 ​​makefile​​ 里面的代码主要有3个重要的变量,​​HEADERS​​,​​OBJS​​,​​OBJS-YES​​,如下:

FFmpeg的makefile逻辑分析

FFmpeg的makefile逻辑分析

上面的 ​​OBJ-YES​​ 变量有 x264 的 ​​.o​​ 文件,x264 应该是以动态库或者静态库的方式引用的,这里为什么直接填 ​​.o​​ ,我也不太清楚,后面补充,暂时不管。

上面 ​​allcodecs.o​​ 等等 这些 ​​.o​​ 后缀的文件是目标文件,这些目标文件肯定要传递给 ​​ar​​ 命令打包成静态库,或者传递给 gcc链接器 生成动态库,那在哪里使用这个 ​​OBJS​​ 变量的呢?

在 ​​library.mak​​ 里面,如下:

FFmpeg的makefile逻辑分析

这里我必须说一下,​​library.mak​​ 里面的变量 ​​OBJS​​, ​​SUBDIR​​ 跟 ​​NAME​​ 等等变量是从其他文件引入的,而且每次循环引入的变量值都不一样,循环发生在 ​​DOSUBDIR​​ 函数里面,如下:

FFmpeg的makefile逻辑分析

FFmpeg 在各个库目录下的 ​​makefile​​ 文件定义了 ​​OBJS​​ 变量,例如 ​​libavcodec\Makefile​​ 里就定义了一个 ​​OBJS​​ 变量,然后 被 ​​library.mak​​ 使用。实际上我个人觉得 ​​makefile​​ 的语法变量有点乱,不太容易确定是局部变量还是全局,反正逻辑就是我上面说的那样。

现在我们已经找到了 在哪里生成动态库的了,就是 ​​library.mak​​ 文件第 51 行的地方,我讲一下我怎么找到这个地方,我是直接搜 ​​SHFLAGS​​ 变量的,因为这是动态库的 ​​flags​​。

现在加一些调试代码,来确认是不是在这个 地方生成 ​​libavcodec.so​​ 文件的,添加的代码,加了个 ​​-num77​​ 如下:

FFmpeg的makefile逻辑分析

提示:之前 configure 的时候要使用 ​​--enable-shared​​,然后才会执行到这条 动态库的 ​​makefile​​ 规则。

执行命令 ​​make -n > t5.txt​​ ,可以看到选项 ​​-num777​​ 加上去了,如下:

FFmpeg的makefile逻辑分析

可以看到生成 ​​libavcodec.so​​ 依赖的 ​​.o​​ 文件太多了,非常多。


现在来找一下,把 ​​.o​​ 文件打包成 静态库的规则在哪里,猜测静态库跟动态库的规则是同一个文件的。 所以直接 在 ​​library.mak​​ 里面搜索 ​​AR​​ ,因为 ​​config.mak​​ 就是用 ​​AR​​ 这个变量来打包的。

​AR​​ 变量 在 Windows 系统是 ​​lib.exe​​ ,在 Linux 系统是 ​​ar​​ 命令,

​library.mak​​ 里面生成静态库的代码如下:

FFmpeg的makefile逻辑分析

上图中的 ​​OBJS​​ 变量是在 ​​libavcodec/makefile​​ 里面定义的。各个库的 ​​makefile​​ 都会定义自己的 ​​OBJS​​ 变量。然后在 ​​DOSUBDIR​​ 函数循环使用

还是惯例,加个 ​​-num888​​ 在后面,如下:

FFmpeg的makefile逻辑分析

这时候,我们一定要 重新 ​​configure​​ ,不要加 ​​--enable-shared​​,这样这条规则才会执行到。他是有其他地方控制 生成动态库还是静态库的,​​make​​ 只会生成静态库或者动态库,只能二选一。不过层层的 ​​target​​ 依赖找起来太麻烦了,初学者也不需要知道这个。只要知道 静态库,动态库的 ​​makefile​​ 规则在哪里就行。

执行命令 ​​make -n > t6.txt​​ ,可以看到选项 ​​-num88​​ 加上去了,如下:

FFmpeg的makefile逻辑分析


4,生成 ​​ffmpeg.exe​​ ,​​ffplay.exe​​ 的规则在哪里?

​ffmpeg.exe​​ 实际上也是调 那 8 个库实现的,​​ffmpeg.exe​​ 相关的代码在 ​​fftools​​ 目录里面,​​ffmpeg.exe​​ 相关的代码只有几个文件,如下:

​ffmpeg.c​​,​​ffmpeg_opt.c​​ ,​​cmdutils.c​​,​​ffmpeg_filter.c​​,​​ffmpeg_hw.c​​,​​ffmpeg_qsv.c​​ ,​​ffmpeg_videotoolbox.c​

后面的 ​​ffmpeg_qsv.c​​ 跟​​ffmpeg_videotoolbox.c​​ 文件不是必须的,是可选的。也就是 生成 ​​ffmpeg.exe​​ 只需要 5个 C 文件。这 5个文件加起来只有 1万多行代码。

看完 这 1万多行代码,就能学会使用 FFmpeg API 函数了,是不是很简单。

回到 原来的问题。生成 ​​ffmpeg.exe​​ 的 ​​makefile​​ 规则在哪里?这个问题实际上要分析 ​​fftools​​ 目录的 ​​makefile​​ 文件。如下:

FFmpeg的makefile逻辑分析

上面的代码有两个重点, ​​AVPROGS​​ 跟 ​​OBJS-ffmpeg​​,生成 ​​ffmpeg.exe​​ 应该会用到这两个变量,只有搜索这两个变量应该就能找到。

我试了一下,根据 ​​AVPROGS​​ 跟 ​​OBJS-ffmpeg​​找,有点麻烦,还是直接 搜 ​​_g​​ 就行 因为 FFmpeg 项目是生成 ​​ffmpeg_g.exe​​,再生成 ​​ffmpeg.exe​​ 的,

生成 ​​ffmpeg_g.exe​​ 的规则 在根目录的 ​​makefile​​ 里面,如下:

FFmpeg的makefile逻辑分析

上图用的是 ​​%​​ 模糊匹配所有的有 ​​_g​​ 的文件。 还是老套路,我们在 后面加个 ​​-num999​​ ,执行 ​​make -n​​ 之后如下:

FFmpeg的makefile逻辑分析

从上图可以看出,有 3个 ​​num999​​ 。证明我们找的地方是对的。


还有一个问题,上面的第 123 行代码, ​​%$(PROGSSUF)_g$(EXESUF): $(FF_DEP_LIBS)​​ ,​​_g​​ 的规则是一个 target,target 要执行,肯定要被依赖,我们再找一下 这个 ​​_g​​ 在哪里被依赖了。实际上依赖代码就在附近,如下:

FFmpeg的makefile逻辑分析

上面的规则实际上就是 ​​ffmpeg.exe​​ 依赖 ​​ffmpeg_g.exe​​ ,所以依赖 ​​_g​​ 的地方找到了。我们看到这个地方干了什么,可以看到 用了 strip,再看回去上面的打印日志,有一个关键的地方,如下:

strip -o ffmpeg ffmpeg_g

原来 ​​ffmpeg_g.exe​​ 就是带调试信息的可执行文件,而 ​​ffmpeg.exe​​ 是经过 ​​strip​​ 去掉调试信息的可执行文件。

提示:我为什么加 ​​exe​​,其实 Linux 环境是没有 ​​exe​​ 后缀,但是为了区分我是在说 ​​ffmpeg​​ 可执行文件,还是指 ​​FFmpeg​​ 整个项目,所以我加了 ​​exe​​。


5, make install 的逻辑在哪里

编译完 FFmpeg 之后,会生成 8 个 ​​dll​​ 跟 相关的 ​​exe​​ 文件,然后你只要执行 ​​make install​​ 命令,就会把这些 ​​dll​​,头文件,​​exe​​ 安装到 ​​prefix​​ 的目录。

那这个安装的逻辑是在 ​​makefile​​ 的那个地方呢?如下:

答:在 ​​ffbuild/library.mak​​ 里,如下:

FFmpeg的makefile逻辑分析

上图里面,​​$(HEADERS)​​ 与 ​​$(LIBNAME)​​ 这些变量都是在 ​​libxxx​​ 里面定义的,例如 ​​libavutil/makefile​​ 里面定义了自己的 ​​HEADERS​​,要安装那些头文件。其他的 ​​libavformat​​ 同理,

​ffbuild/library.mak​​ 文件是在一个 ​​foreach​​ 循环里面被引入的,所以每一次循环,他的​​$(HEADERS)​​ 与 ​​$(LIBNAME)​​ 这些变量都是不同的,如下:

FFmpeg的makefile逻辑分析

如果你想导出一些新的头文件,例如 ​​libavutil/thread.h​​,只需要在 ​​libavutil/makefile​​ 里面给 ​​HEADERS​​ 变量加上对应的头文件就行,如下:

FFmpeg的makefile逻辑分析

因为默认情况,FFmpeg 是没有导出 ​​thread.h​​ 给外部使用的,这是 FFmpeg 封装的跨平台线程API。


至此,​​FFmpeg​​ 项目里面比较常用的 ​​makefile​​ 知识,你已经掌握。后面就让我们二次开发 ​​FFmpeg​​,加一个自己程序进去。​​FFmpeg​​ 项目默认有 3 个程序 ​​ffmpeg.exe​​ ,​​ffplay.exe​​ ,​​ffprobe.exe​​ 。我们新加的程序就叫 ​​loken.exe​​。

欢迎看下一篇文章。