makefile逻辑分析—FFmpeg makefile源码分析
作者:罗上文,微信:Loken1,公众号:FFmpeg弦外之音
在开始分析之前,讲一个 makefile
的调试技巧,推荐阅读《如何调试MAKEFILE变量》
make -f Makefile -f vars.mk HOSTPROGS
这里我对 vars.mk
做了点修改,因为源 vars.mk
没处理特殊字符,直接 echo
会报错。ffmpeg
的 makefile
的变量有很多特殊字符。我用了 warning
来输出,就不会报错。vars.mk
下载地址:百度网盘,提取码:i59d
先来看 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
比较分散,根目录有一个 makefile
,各个库目录也有一个 makefile
。库目录的 makefile
是通过 DOSUBDIR
函数 包含进来的,如下:
上图代码中,定义了 DOSUBDIR
函数,然后遍历 FFLIBS
数组变量 来调用,FFLIBS
就是 FFmpeg 的那 8个库。
因此,FFmpeg 项目根目录有一个主的 makefile
(MAIN_MAKEFILE),然后各个子目录都有一个各自的 makefile
。代码量有点多。但是我们比较常用的只有 5 个知识点。
提示:makefile 也只是一个构建工具,他最后也是调 gcc 来执行编译。
makefile
里面,我们比较常用的有 5 个知识点。
1,第一个 target
是什么,在哪里定义的?
makefile
的规则是,执行 make
命令的时候要指定一个 target
,就是要编译哪个目标,如果不指定,就是编译 makefile
文件里面的第一个目标,第一个 target
也叫默认target
。那 FFmpeg 的默认目标是什么?如下:
从上图可以看出,默认 target
是 all
,因此,我们在编译 FFmpeg 的时候,无论执行 make
还是 make all
命令,效果是一样的。
2,把 .c
文件 编译成 .o
文件的规则在哪里?
我们知道,在编译阶段,各个 .c
文件都是独立编译的。而 makefile
也是调 gcc
来编译,那是在哪里调的 gcc
呢?
这些规则在 common.mak
文件里面,如下:
COMPILE_C
是一个函数调用,如下:
这里提醒一下,所有的子目录,所有的 .c
文件编译都是这个规则。这是通用的。也就是说,ffmpeg.c
,ffplay.c
文件也是这一个规则。
我们现在来测试一下 是不是这一个规则,看下 ffplay.c
是不是在这里编译的。我们加个东西,如下:
我加了一个无意义的选项 -num666
在后面。然后用 make -n > t33.txt
来查看命令,-n
代表不执行编译只打印信息。如下:
果然,因此所有 的 .c
文件都是在 common.mak
文件里面定义编译规则的。
3,生成 FFmpeg 8个 动态库的规则在哪里?
FFmpeg 里面提供了 8 个库给开发者使用,这些库可以通过打包形成静态库,或者通过链接器来生成动态库。那这些生成静态库,动态库的规则在哪里呢?
以 libavcodec
库为例,libavcodec
目录的 makefile
里面的代码主要有3个重要的变量,HEADERS
,OBJS
,OBJS-YES
,如下:
上面的 OBJ-YES
变量有 x264 的 .o
文件,x264 应该是以动态库或者静态库的方式引用的,这里为什么直接填 .o
,我也不太清楚,后面补充,暂时不管。
上面 allcodecs.o
等等 这些 .o
后缀的文件是目标文件,这些目标文件肯定要传递给 ar
命令打包成静态库,或者传递给 gcc链接器 生成动态库,那在哪里使用这个 OBJS
变量的呢?
在 library.mak
里面,如下:
这里我必须说一下,library.mak
里面的变量 OBJS
, SUBDIR
跟 NAME
等等变量是从其他文件引入的,而且每次循环引入的变量值都不一样,循环发生在 DOSUBDIR
函数里面,如下:
FFmpeg 在各个库目录下的 makefile
文件定义了 OBJS
变量,例如 libavcodec\Makefile
里就定义了一个 OBJS
变量,然后 被 library.mak
使用。实际上我个人觉得 makefile
的语法变量有点乱,不太容易确定是局部变量还是全局,反正逻辑就是我上面说的那样。
现在我们已经找到了 在哪里生成动态库的了,就是 library.mak
文件第 51 行的地方,我讲一下我怎么找到这个地方,我是直接搜 SHFLAGS
变量的,因为这是动态库的 flags
。
现在加一些调试代码,来确认是不是在这个 地方生成 libavcodec.so
文件的,添加的代码,加了个 -num77
如下:
提示:之前 configure 的时候要使用 --enable-shared
,然后才会执行到这条 动态库的 makefile
规则。
执行命令 make -n > t5.txt
,可以看到选项 -num777
加上去了,如下:
可以看到生成 libavcodec.so
依赖的 .o
文件太多了,非常多。
现在来找一下,把 .o
文件打包成 静态库的规则在哪里,猜测静态库跟动态库的规则是同一个文件的。 所以直接 在 library.mak
里面搜索 AR
,因为 config.mak
就是用 AR
这个变量来打包的。
AR
变量 在 Windows 系统是 lib.exe
,在 Linux 系统是 ar
命令,
library.mak
里面生成静态库的代码如下:
上图中的 OBJS
变量是在 libavcodec/makefile
里面定义的。各个库的 makefile
都会定义自己的 OBJS
变量。然后在 DOSUBDIR
函数循环使用
还是惯例,加个 -num888
在后面,如下:
这时候,我们一定要 重新 configure
,不要加 --enable-shared
,这样这条规则才会执行到。他是有其他地方控制 生成动态库还是静态库的,make
只会生成静态库或者动态库,只能二选一。不过层层的 target
依赖找起来太麻烦了,初学者也不需要知道这个。只要知道 静态库,动态库的 makefile
规则在哪里就行。
执行命令 make -n > t6.txt
,可以看到选项 -num88
加上去了,如下:
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
文件。如下:
上面的代码有两个重点, AVPROGS
跟 OBJS-ffmpeg
,生成 ffmpeg.exe
应该会用到这两个变量,只要搜索这两个变量应该就能找到。
我试了一下,根据 AVPROGS
跟 OBJS-ffmpeg
找,有点麻烦,还是直接 搜 _g
就行 因为 FFmpeg 项目是生成 ffmpeg_g.exe
,再生成 ffmpeg.exe
的,
生成 ffmpeg_g.exe
的规则 在根目录的 makefile
里面,如下:
上图用的是 %
模糊匹配所有的有 _g
的文件。 还是老套路,我们在 后面加个 -num999
,执行 make -n
之后如下:
从上图可以看出,有 3个 num999
。证明我们找的地方是对的。
还有一个问题,上面的第 123 行代码, %$(PROGSSUF)_g$(EXESUF): $(FF_DEP_LIBS)
,_g
的规则是一个 target,target 要执行,肯定要被依赖,我们再找一下 这个 _g
在哪里被依赖了。实际上依赖代码就在附近,如下:
上面的规则实际上就是 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
里,如下:
上图里面,$(HEADERS)
与 $(LIBNAME)
这些变量都是在 libxxx
里面定义的,例如 libavutil/makefile
里面定义了自己的 HEADERS
,要安装那些头文件。其他的 libavformat
同理,
ffbuild/library.mak
文件是在一个 foreach
循环里面被引入的,所以每一次循环,他的$(HEADERS)
与 $(LIBNAME)
这些变量都是不同的,如下:
如果你想导出一些新的头文件,例如 libavutil/thread.h
,只需要在 libavutil/makefile
里面给 HEADERS
变量加上对应的头文件就行,如下:
因为默认情况,FFmpeg 是没有导出 thread.h
给外部使用的,这是 FFmpeg 封装的跨平台线程API。
至此,FFmpeg
项目里面比较常用的 makefile
知识,你已经掌握。后面就让我们二次开发 FFmpeg
,加一个自己程序进去。FFmpeg
项目默认有 3 个程序 ffmpeg.exe
,ffplay.exe
,ffprobe.exe
。我们新加的程序就叫 loken.exe
。
欢迎看下一篇文章。
感谢 NETINT(镕铭微电子) 赞助《FFmpeg原理》免费版一书的服务器费用,下面是 VPU 产品介绍