摘要:
本文作为“x264视频编码器应用与实现”系列博文的第三篇,主要讨论如何从官方镜像下载x264的源代码,并进行编译和调试。
1. x264源代码的下载
如前文所述,x264的源代码托管于VideoLan的代码库中。我们可以通过git来轻易获取到相应的源代码:
1 | git clone https://code.videolan.org/videolan/x264.git |
下载完成后,x264目录中的文件内容如下图所示:
2. 工程配置文件Configure
在Linux下的代码中,Configure是一个非常常见的文件。该文件是一个shell脚本文件,其主要作用是进行特定编译环境的配置。x264的整个Configure文件共1584行,其中343行之前是函数定义部分,执行部分从344行开始一直到1584行为止。如果希望了解x264的Configure文件可以接受的参数,可以在加入–help作为参数运行Configure文件:
1 | ./configure --help |
随后Configure文件会输出以下的信息:
1 | Help: |
下面分类讨论Configure的各个参数。
2.1 基本参数 Standard Options
Standard Options主要包含了如生成文件的安装位置以及程序生成和运行过程(编译、汇编、链接、运行)中的参数等。
- –prefix=PREFIX:指定架构无关生成文件的保存位置,PREFIX默认值为/usr/local;
- –exec-prefix=EPREFIX:指定架构相关生成文件的保存位置,EPREFIX默认值同为PREFIX的值;
- –bindir=DIR:指定二进制可执行文件的保存位置,DIR默认值为EPREFIX/bin;
- –libdir=DIR:指定生成库文件的保存位置,DIR默认值为EPREFIX/lib;
- –includedir=DIR:指定头文件目录的位置,DIR默认值为EPREFIX/include;
- –extra-asflags=EASFLAGS:指定汇编器(assembler)参数;
- –extra-cflags=ECFLAGS:指定编译器(compiler)参数;
- –extra-ldflags=ELDFLAGS:指定链接器参数;
- –extra-rcflags=ERCFLAGS:指定运行相关的参数;
2.2 配置参数 Configuration options
Configuration options中主要包含了生成的文件的一些特性和功能的开关。
- –disable-cli:不生成x264命令行工具;
- –system-libx264:指定使用系统的libx264库而非内部库
- –enable-shared:生成共享库;
- –enable-static:生成静态库;
- –disable-opencl:禁用opencl;
- –disable-gpl:禁用gpl协议相关的功能;
- –disable-thread:禁用多线程编码;
- –disable-win32thread:在Windows下禁用win32线程;
- –disable-interlaced:禁用交错编码功能;
- –bit-depth=BIT_DEPTH:指定编码比特深度,默认支持 8 bit 和 10 bit;
- –chroma-format=FORMAT:指定支持的颜色空间,默认支持400/420/422/444格式;
2.3 高级参数 Advanced options
Advanced options中主要包含了一些对编译生成过程中的一些高级设置。
- –disable-asm:禁用汇编优化;
- –enable-lto:启用链接时优化;
- –enable-debug:启用调试模式;
- –enable-gprof:启用性能测试工具gprof;
- –enable-strip:启用精简模式;
- –enable-pic:生成位置无关代码;
2.4 交叉编译选项 Cross-compilation
Cross-compilation主要用于指定一些交叉编译的信息,在编译非Linux平台的项目时非常有用,如iOS和Android端应用时。
- –host=HOST:指定目标操作系统;
- –cross-prefix=PREFIX:指定交叉编译的参数;
- –sysroot=SYSROOT:指定编译的逻辑目录根目录;
2.5 第三方库支持 External library support
External library support用于设置是否开启对第三方库的支持,如ffmpeg、gpac等。
- –disable-avs:禁用avisynth;
- –disable-swscale:禁用libswscale;
- –disable-lavf:禁用libavformat库;
- –disable-ffms:禁用FFMpeg相关;
- –disable-gpac:禁用gpac;
- –disable-lsmash:禁用lsmash;
3. x264推荐编译配置
1 | ./configure --prefix=$HOME/Code/x264-demo/Output/x264 --enable-static --disable-opencl --disable-win32thread --disable-interlaced --disable-asm --enable-debug --disable-avs --disable-swscale --disable-lavf --disable-ffms --disable-gpac --disable-lsmash |
完成后,/Code/Output/x264/目录下生成3个目录,分别保存了相应的生成文件:
- bin:生成的二进制命令行工具x264;
- include:保存了相应的头文件x264.h和x264_config.h;
- lib:保存二进制静态库libx264.a和pkgconfig文件;
而在源代码目录,同样生成了若干个新的文件,除了上述移动到指定目录中的libx264.a等文件之外,还包括config.h、x264_config.h等生成的头文件,以及config.mak、config.log等执行Configure时生成的附加文件。
编译执行完成后的源代码目录如下图所示:
4. 如何在 xcode 中单步调试x264源代码
- 关闭x264源代码编译时的优化选项。具体操作为,在Configure文件中找到1294行,将其中的**-O1改为-O0**,即改为:
1 | CFLAGS="-O0 -g $CFLAGS" |
按前文的讲述,编译 x264 源代码,生成库文件和可执行程序;
在xcode中新建类型为“External Build System”的新工程:
对新建的工程命名:
在新建工程中添加 x264 的源代码目录:
注意需选择原始的代码存放目录而不是生成库文件、头文件或二进制文件的目录:
在 target 的属性页面上选择添加进来的 x264 源代码目录:
例如:
配置运行信息:
选择执行的二进制文件和参数:
在“Arguments”选项中添加传入二进制可执行程序的参数:
从源代码目录中选择主程序源代码 x264.c,加入断点并调试运行:
断点调试结果:
5. 如何在 gdb 中单步调试 x264 的源代码
在 Linux 平台,gdb 是使用最多的代码调试工具之一。与 Visual Studio、Xcode 等 IDE 不同的是,使用 gdb 进行代码调试更多地依赖终端命令,而非图形界面。这种方式由于需要记忆部分指定的命令,有一定的学习成本,因此对于初学者不是非常友好。但是由于 gdb 远比各种 IDE 更加轻量化,且无需进行复杂的配置,因此在简单掌握 gdb 的基本使用后,使用 gdb 进行代码调试可以极大提升学习和开发效率。
如果希望使用gdb进行调试,在编译时同样需要加入生成调试信息的配置,即在配置时使用“–endable-debug”,并确保Configure文件中的CFLAGS选项加入了“-g”。可参考前面章节中的编译配置即可。
gdb调试程序的基本使用方法可参考以下文章:https://zhuanlan.zhihu.com/p/74897601
5.1 使用gdb开始调试x264
使用gdb调试x264的基本方法:
1 | gdb ./x264 |
此时,命令行终端便进入了gdb调试模式。
进入gdb调试模式后,使用gdb的各种命令可进行查看代码、加入断点、调试运行等功能。
5.2 在gdb中查看x264代码源文件
进入gdb调试模式后,使用list命令(简写为l)可查看被调试程序的源代码。使用该命令可查看x264源代码的内容:
使用list命令时可指定显示源代码中的某一个函数或者某一行源代码。例如,我们希望查看x264的main函数,可使用以下命令:
1 | l main |
效果如下图所示:
如果希望查看指定源文件的某一行(如cavlc.c的121行),可使用以下命令:
1 | l cavlc.c:121 |
效果如下图所示:
除以上方法外,list还有多种更复杂的使用方法,可参考官方文档中的描述。
5.3 在gdb中运行程序
如果仅仅只是查看源代码,那么调试将毫无意义,因此必须将被调试程序运行起来,如此方能检查程序的流程并发现其中潜在的问题。在gdb中可以使用run命令(简写为r)将程序以调试模式运行。在执行run命令时,同时携带x264可执行程序的命令行参数。例如,使用参数”–help“可输出x264的帮助文档,那么在gdb中使用以下命令:
1 | r --help |
输出结果如下:
5.4 在程序中添加、禁用、启用和删除断点
从上述例子中可看出,使用run命令运行程序后,程序将自动持续运行直至结束后退出。为了控制程序运行的流程,最基础的一个功能就是在程序中添加断点。在我们的运行环境中,gdb可支持以下三种断点:
- 中断点breakpoint:程序运行至中断点的位置即停止继续执行;
- 观测点watchpoint:当指定表达式的值发生变化时,程序停止执行;此类型的断点又可称为”数据中断点“;
- 捕捉点catchpoint:当某种事件(如程序抛出异常)发生时,程序停止执行;
在gdb中添加断点使用break命令(简写为b)实现。例如,在main函数处添加断点,可在gdb中使用以下命令:
1 | b main |
在指定源文件的指定位置添加断点(如在base.c文件的345行)添加断点,可在gdb中使用以下命令:
1 | b base.c:345 |
断点设置完成后,使用info breakpoints可查看当前的断点情况:
从上图可看出,每一个断点都有一个独立的编号,通过该编号可对断点进行操作。使用delete命令可删除某个指定的断点,例如删除第一号断点使用以下命令:
1 | delete 1 |
与删除类似,使用disable命令和enable命令可实现对某个断点的禁用和启用功能。
在设置断点后,使用r命令执行程序,可看到程序停止在指定的断点处:
5.5 单步调试与断点调试
单步调试是程序调试的核心,通过调试和断点调试可清晰地显示程序的执行路径与变量的变化情况。在gdb中可通过以下几种形式实现调试操作:
- 单步执行:使用命令”next“实现(简写为“n”),对标Visual Studio等IDE中的”step over“操作;
- 单步进入:使用命令”step“实现(简写为“s”),对标Visual Studio等IDE中的”step in“操作;
- 返回上层:使用命令”finish“实现,对标Visual Studio等IDE中的”step out“操作;
- 执行多步:使用命令”until“实现(简写为“u”),对标Visual Studio等IDE中的”run until“操作;
接下来我们逐个尝试上述命令,观察其效果。
单步执行
首先参考5.4节的操作,在main函数入口处设置断点,并使用命令“r –help”运行。在到达断点后,连续两次执行“next”命令,结果如下图所示:
从上图可见,在程序中断在377行后,第一次执行n命令后到达378行,第二次执行n命令后到达382行。
单步进入
通过单步执行到达第397行,执行“step”命令可单步进入parse函数,并停留与1415行,即“x264_param_default“函数的位置,如下图所示:
再次执行”step“命令,即可单步进入“x264_param_default“函数内部:
返回上层
使用”finish“命令可在调试时直接跳出当前函数,返回调用堆栈的上层。例如,程序在函数“x264_param_default“中时,执行该命令可直接返回上层函数,并停留在下一条语句(即1416行)处:
执行多步
通过”until“命令可实现多步的执行。例如,若我们希望执行到下图中的1423行,可用”until 1423“实现:
实际上until还有其他使用方法,可参考相应帮助文档,此处不再赘述。
断点调试
当代码中加入多个断点后,除了上述的单步调试外,使用”continue“命令(简写为”c“)可实现断点调试。例如,在main函数和x264.c的1439行分别设置断点,使用”r –help“执行,程序停止于第一个断点位置。此时执行”continue“,程序将直接运行至1439行:
5.6 打印数值和数据
单步调试和断点调试可以检查程序执行的流程,但多数时候这并不够,因为我们还需要随时知晓当前的变量或内存区的值。在gdb中打印变量和内存区保存的值采用不同的方法,本小节中分别讨论。
打印变量
在gdb中打印变量通过命令”print“(简写为”p“)实现。对于int型等普通变量,直接将变量名称作为参数即可;对于指针变量,print命令输出的是其地址,添加”*“即可输出其变量的值。如下图所示:
打印内存数据
检查内存数据使用命令”x“实现。”x“命令的格式为:
1 | x/FMT addr |
其中”FMT“表示输出格式参数,共包含三个部分,即内存单元数、显示数据格式和单个内存单元长度。其中显示数据格式的取值可参考:https://sourceware.org/gdb/current/onlinedocs/gdb/Output-Formats.html#Output-Formats,单个内存单元长度的取值可参考:https://sourceware.org/gdb/current/onlinedocs/gdb/Memory.html#Memory。
例如,如果希望打印地址addr所指向的数据内容,打印的格式为8个字节,按照十六进制输出,可使用以下命令:
1 | x/8xb addr |