FFmpeg的makefile逻辑分析

时间:2022-11-27 08:55:40

在开始分析之前,讲一个 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.cffplay.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个重要的变量,HEADERSOBJSOBJS-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 变量,然后 被 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.cffmpeg_opt.c ,cmdutils.cffmpeg_filter.cffmpeg_hw.cffmpeg_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


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

欢迎看下一篇文章。


 推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:

Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习