FFmpeg与Linux编译:流程浅谈
Linux下的编译和链接一直是一件令人头疼的事,繁琐零碎的关系与变量很难在上手时总会造成不少的麻烦。作为Linux下非常成熟的应用之一,FFmpeg (Homepage | Wiki) 在这方面很具有代表性,本次便以Ubuntu与FFmpeg为例介绍一下Linux下最常见的编译流程。
0 - 概述
Linux 最令人欲罢不能的特性之一就是库的安装,如果仅仅为了使用FFmpeg,那么你只需要一行命令:
sudo apt-get install ffmpeg
但是apt
安装的库是么得灵魂的,大多数情况下(使用者并没有root
权限或是有更多配置需求的时候)往往会采用另一种安装方式:
$ ./configure [options]
$ make [-jx]
$ [sudo] make install
上述命令中[]
内的部分不是必须的,执行的位置一般是在FFmpeg Source Code的根目录下,有关可选的部分:
-
configure [options]
configure是此类项目常见的配置脚本,主要用途是检测依赖环境是否满足要求、生成编译所需的宏定义、追加必要的flags,使用者一般通过向configure中传入预留的options来控制后续的编译过程,options因项目而异,后面会详细讨论 -
make [-jx]
make命令几乎是编译中最频繁的命令,它的功能主要是执行Makefile中的各个命令,生成与链接往往在这一步完成。他允许使用者通过-j
命令指定执行时的线程数量,-jx
即同时使用x
个线程运行,如果不指定x
,直接使用-j
即占用全部线程(会很嗨哟,小心死机) -
[sudo] make install
make install 是执行Makefile中的install部分,大多数项目会实现这个命令,并且有着一套约定俗成的安装规则。默认情况下的安装往往是在/usr
或/usr/local
路径下,以便确保可以被PATH
包含而供使用者直接调用,向其中写入文件当然是需要root
权限的,不过不必担心,这个是可以更改的
除了提到的Linux编译三部曲,常见的其他编译模块还有automake
、cmake
等等用于自动生成configure或者Makefile文件的,他们的流程实际都大同小异,无非以下几个步骤:
- 确定目标平台和这个平台对应的编译链接工具链(以生成可供该平台运行的程序)
- 确定项目生成的规则(不同的平台、实际环境、配置需求等)
- 如果满足基本要求,继续使用工具链将代码编译、链接成可执行文件或者依赖库(或者生成可供编译的项目)
- 如果需要,将生成的文件安装或者打包,有时候也会顺便帮你改改环境变量
那么,下面就一起看看这几个步骤在FFmpeg中是如何进行的吧~
1 - 配置 · configure
不是每个使用者都具有直接更改代码的能力,对FFmpeg这种拥有很多很多很多可选模块的项目,很重要的一点就是应该有一套完整的开关以及变量供使用者方便的配置。常用的选项也是这两类:
1.1 开关 [ --enable-xxx | --disable-xxx ]
开关比较容易理解,通过添加类似选项指定某个模块或者某类模块开启或关闭,以定义自己实际需要的可执行文件。如开启avs2
解码器的选项为--enable-libdavs2
或者--enable-decoder=libdavs2
,也可以关闭所有解码器--disable-decoders
。通过不断组合实现自己需要的FFmpeg定义。
但是在开启或关闭的过程中,configure要确保整个工程的合法性。
比如上述提到的--enable-libdavs2
,在configure中davs2开发者规定需要遵守的规则有以下几点:
- 默认关闭,开启时要求GPL许可环境
- 要求使用
pkg-config
检查依赖是否完整,包括:头文件davs2.h
、依赖库davs2
(版本应至少为1.6.0)、davs2要求包含的其他需求
第一步GPL许可环境要求编译时添加--enable-gpl
选项。
第二步的检查方式是读取pkg-config的检测输出,生成一个包含指定头文件、库和含davs2提供的“检测函数”的临时文件,尝试编译这个测试文件,如果成功生成则认为依赖完整(日志可见~/ffbuild/config.log)
这个过程中可能还得了解了解下面几位老哥:
-
pkg-config
pkg-config是Linux下依赖检测的贴心助手,环境管理的金牌保姆。他的核心是通过读取*.pc
文件来获得某个依赖的头文件目录、依赖库目录、可执行文件目录、版本信息、描述、必要的附加库 等信息,这些信息都被记录在依赖库自己的pc
文件中,当然这个pc文件不是藏哪他都能找见的,pkg-config的搜索范围仅限于默认目录(/usr/lib/pkgconfig
,有时候也包括/usr/local/lib/pkgconfig
或者其他的系统目录)和PKG_CONFIG_PATH
中指定的目录,一般PKG_CONFIG_PATH
都是空的,如果我们有需要pkg-config搜寻的范围(一般是xxx/lib/pkgconfig
)只需要扔给这个变量:
$ export PKG_CONFIG_PATH=/path/to/lib/pkgconfig/
-
ld
Linux最常用也是最基础的链接工具就是ld
(是L),一般见到的使用形式都是-ldavs2
(也是L)或者-L/path/to/libdavs2
(pkg-config也需要使用ld进行链接)。它的主要工作是找到指定的静态库并把它放进被链接的项目里以备使用,这里也有一个类似的用户路径LD_LIBRARY_PATH
用于扩展搜索范围,使用时只需要把对应lib路径加进去
$ export LD_LIBRARY_PATH=/path/to/lib/
-
ldconfig
顺便说一下ldconfig
(还是L),乍看之下和ld有点很像,但它的主要功能是在运行时搜索需要的动态库,同样他提供了另一个变量LD_CONFIG_PATH
,使用(并刷新缓存)的方法是:
$ export LD_CONFIG_PATH=/path/to/lib/
$ [sudo] ldconfig
-
C_INCLUDE_PATH
除了库有的时候我们还需要自己指定一下头文件所在的路径,同理,扔进C_INCLUDE_PATH
$ export C_INCLUDE_PATH=/path/to/include/
1.2 变量 [ xxx=xxx ]
除了开与关之外,FFmpeg也允许用户直接指定一些变量的值,比如最常见的prefix
,他的作用就是控制最终的安装目录,使用方法也很简单,指定--prefix=/path/to/xxx
之后configure会把它记录进配置文件,install的时候项目输出就会有新家啦
除此之外还有大家喜闻乐见的FLAGS
,主要有--extra-cflags
、--extra-cxxflags
、--extra-ldflags
、--pkg-config-flags
等,功能和前面直接指定环境变量类似,缺哪个加哪个。只不过是在FFmpeg局部添加,仅在编译的时候用用,不会影响configure结束之后terminal的后续功能
除了FLAGS
还有很多可定制的参数,比如编译相关的CC
、NM
、AS
还有平台相关的arch
、toolchains
、target-os
,生成相关的build-suffix
以及asm
、pic
等各式各样你不懂没关系的变量设置,只需要知道一点:当你想改某个变量的时候,先在configure里找找;当你环境很别扭的时候,多去configure里看看;毕竟这是项目的大总管
有关编译参数的部分之前整理过一小部分:FFmpeg 编译参数简单整理,仅供参考
只有在所有条件都符合的情况下configure才会完成配置,并生成两个很重要的文件:config.h 和 config.mak(config.asm视为汇编版的config.h),前者包含了FFmpeg各种重要的宏定义,后者包含了配置过程中找到的、使用的路径与依赖信息,这些都是后续Makefile的空指部
2 - 编译 · make
当configure把一切都布置妥当之后,make就可以出来干活了。带头的Makefile大哥会在根目录下调度各个子模块的小弟,小弟Makefile们会将资源文件*.c
通过编译器gcc
编译成*.o
文件(中间其实还有预处理的.i
和编译的.s
和汇编),链接器会组织各个.o
到各自的队伍中去,排队链接成静态库.a
(动态的话就是.so
),然后老大们挑选自己心仪的小弟作为依赖,最终生成可执行文件ffmpeg
、ffprobe
等
这个过程中就需要决定哪些模块需要被编译、哪些应该被忽略、编译时应该用哪一个工具附带哪一些参数。config.mak将作为Makefile的“头文件”不断被参考,提供编译链接的技术指导,而config.h则作为宏定义掌管各个模块开与关的统筹规划
Makefile的基本的编译规则其实很简单,拿一个变量记好想要编译的.c
,再拿一个变量收集生成的.o
文件,收集的时候把.c
送到指定的规则里等着掉出来.o
(也可能会掉出来一个.S
,汇编的话),然后再排队等着链接进.a
。区别可能就是各个模块之间用以联系的规则以及生成时的各种附加的条件
在每一层的Makefile中都应当记录产生与销毁的信息,以实现几个常见的功能:
-
make install
将生成的东西搬运到指定的地方 -
make uninstall
去指定的地方把之前放的东西销毁 -
make clean
销毁各自临时生成的东西(不管运出去的,只管make的) -
make distclean
冷酷无情的消灭所有痕迹(不管运出去的,同时包含configure生成的)
有关编译链接装载的详细过程推荐一本书《程序员的自我修养——链接、装载与库》,也可以先试读一下我挖过的坑:《程序员的自我修养——链接、装载与库》读书笔记(一),有兴趣建议详读
值得提醒的一点是除了“make”这个关键词外,其他包括静态的、动态的、安装、卸载、清理……都只是约定俗成的名称,可以看做一个叫“make”的工具调用了一个叫“install”的函数(貌似实际也是),configure的选项也是同理
3 - 使用 · ffxxxx(x)
如果在可执行文件目录下直接运行,再简单不过了:./ffmpeg
加上参数就成,运行时需要注意-i
前是用于规范输入文件的参数,-i
后是用于规范输出文件的参数,第一个没有被指明参数类型的参数将被视作输出文件,ffplay和ffprobe可以省略-i
。
如果想要在任何路径下都能任性的呼唤ffmpeg,除了在安装时将输出路径指定为PATH
路径下的某个路径(如/usr/local/
)外,也可以将可执行文件的目录添加进自己的PATH
里,需要注意如果包含动态库则也应该把库目录加进ldconfig的路径下
详细的使用方式此处不做过多讨论
推荐一本很棒的ffmpeg工具书《FFmpeg从入门到精通》,很详细的介绍了ffmpeg的使用方法,建议实体书方便随时翻看 ~
附 - 编译ffmpeg的常见问题
A.1 各种文件找不到
编译最头疼的地方就是把各类资源放到一起,经常会遇见库找不到,解决方法主要有以下几种:
- 能安装的直接安装,但是要注意是否存在版本限制
- 有
.pc
文件的优先使用PKG_CONFIG_PATH
- 实在没有就把头文件路径扔进
C_INCLUDE_PATH
,库放进LD_LIBRARY_PATH
使用pkg-config查找libxxx是否存在:$ pkg-config --exists --print-errors xxx
使用pkg-config查找libxxx当前版本:$ pkg-config --modversion xxx
查看当前可直接执行到底是哪一个:$ which xxx
查找最终生成的文件是用的哪一个:$ ldd ./ffmpeg_g | grep libdavs2
(ffmpeg_g是保留了全部符号的调试版本,默认生成)
※ 首先应该确定:真的只是库没包含进去,而且真的应该包含这个库。特殊环境(如交叉编译)尤其应当搞清楚到底在加啥加到哪
A.2 pkg-config说找到了,configure不相信
在使用外部库的时候经常遇见一个很神奇的问题,明明直接使用pkg-config找到的库很正常,configure的时候却遇到ERROR: xxx not found using pkg-config
。实际上configure在使用pkg-config测试依赖环境的时候出问题都会报这个错,真正的错误被记录在编译日志~/ffbuild/config.log
里,去那里看看或许可以找到新的头绪(其他想要的信息也可以来这里找找看)
A.3 库找到了,但好像不太对
有的时候pkg-config或ld搜索路径下可能包含多个符合条件的库,不同系统不同工具有各自的搜索顺序,可以按照顺序将需要的库放在合适的位置,但还是建议通过环境变量合理控制下库的活动版本,暴力点也可以直接指定绝对路径
A.4 使用make -jx没成功也没看见错
多线程编译的时候线程们并不是一个出错全体罢工,而是某一个线程出错后其他线程完成自己的工作后小心翼翼的停下,这样会导致后面大量的编译日志覆盖掉前面的错误信息,不要费力去翻,只需要重新执行一次单线程的make操作,会很快的停止在出错的位置
A.5 忘记上次编译用了什么参数了
其实仔细看看ffmpeg在每次运行的时候都会输出他的编译参数以及版本信息,对应看看或许能找到你想要的
—END—
Author: hwrenx 2019-08-06