【Android 音视频开发打怪升级:FFmpeg音视频编解码篇,Android程序员月薪20k的涨薪秘籍

后面我们将对其中一些重要的内容进行分析,这是理解 FFmpeg 编译配置的关键。

有了以上基础以后,就可以对FFmpeg进行编译了。

配置脚本
  • 修改 configure 脚本
  1. 新增 cross_prefix_clang 参数

打开(注:不是双击运行) ffmpeg-4.2.2 根目录下的 configure 文件,搜索 CMDLINE_SET ,可以找到以下代码,然后新增一个命令行选项:cross_prefix_clang

CMDLINE_SET="
$PATHS_LIST
ar
arch
as
assert_level
build_suffix
cc
objcc
cpu
cross_prefix

新增命令行参数

cross_prefix_clang
custom_allocator
cxx
dep_cc

省略其他…

"

  1. 修改编译工具路径设置

搜索 ar_default="${cross_prefix}${ar_default}" , 找到以下代码

ar_default=" c r o s s p r e f i x {cross_prefix} crossprefix{ar_default}"
cc_default=" c r o s s p r e f i x {cross_prefix} crossprefix{cc_default}"
cxx_default=" c r o s s p r e f i x {cross_prefix} crossprefix{cxx_default}"
nm_default=" c r o s s p r e f i x {cross_prefix} crossprefix{nm_default}"
pkg_config_default=" c r o s s p r e f i x {cross_prefix} crossprefix{pkg_config_default}"

将中间两行修改为

ar_default=" c r o s s p r e f i x {cross_prefix} crossprefix{ar_default}"
#------------------------------------------------
cc_default=" c r o s s p r e f i x c l a n g {cross_prefix_clang} crossprefixclang{cc_default}"
cxx_default=" c r o s s p r e f i x c l a n g {cross_prefix_clang} crossprefixclang{cxx_default}"
#------------------------------------------------
nm_default=" c r o s s p r e f i x {cross_prefix} crossprefix{nm_default}"
pkg_config_default=" c r o s s p r e f i x {cross_prefix} crossprefix{pkg_config_default}"

至于为什么这么修改,将在后面的 configure 分析中详细讲解

  • 新建编译配置脚本

ffmpeg-4.2.2 根目录下新建 shell 脚本,命名为: build_android_clang.sh

#!/bin/bash
set -x

目标Android版本

API=21
CPU=armv7-a
#so库输出目录
OUTPUT=/Users/cxp/Desktop/FFmpeg/ffmpeg-4.2.2/android/$CPU

NDK的路径,根据自己的NDK位置进行设置

NDK=/Users/cxp/Desktop/FFmpeg/android-ndk-r20b

编译工具链路径

TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/darwin-x86_64

编译环境

SYSROOT=$TOOLCHAIN/sysroot

function build
{
./configure
–prefix=KaTeX parse error: Undefined control sequence: \ at position 8: OUTPUT \̲ ̲--target-os=and…SYSROOT
–cross-prefix=KaTeX parse error: Undefined control sequence: \ at position 38: …x-androideabi- \̲ ̲--cross-prefix-…TOOLCHAIN/bin/armv7a-linux-androideabi$API-
–extra-cflags="-fPIC"

make clean all

这里是定义用几个CPU编译

make -j12
make install
}

build

这个shell脚本,大体上其实还是很容易懂的,比如

--disabble-static --enable-shared 分别用于禁止输出静态库,以及输出动态库;

--arch --cpu 用于配置输出的so库是什么架构的;

--prefix 用于配置输出的so库的存放路径。

接下来重点来讲一下几个选项:

  • target-os

--target-os=android:在旧版本的 FFmpeg 中,对Android平台的支持并不是很完善,并没有 android 这个target,所以在一些比较老的文章中都会提到,编译Android平台的so库,需要对 configure 做以下修改,否则会按照 linux 标准的方式输出so库,其命名方式和Android的so不一样,Android是无法加载的。

SLIBNAME_WITH_VERSION=’ ( S L I B N A M E ) . (SLIBNAME). (SLIBNAME).(LIBVERSION)’
SLIBNAME_WITH_MAJOR=’ ( S L I B N A M E ) . (SLIBNAME). (SLIBNAME).(LIBMAJOR)’
LIB_INSTALL_EXTRA_CMD=’?(RANLIB) “ ( L I B D I R ) / (LIBDIR)/ (LIBDIR)/(LIBNAME)”’
SLIB_INSTALL_NAME=’ ( S L I B N A M E W I T H V E R S I O N ) ′ S L I B I N S T A L L L I N K S = ′ (SLIBNAME_WITH_VERSION)' SLIB_INSTALL_LINKS=' (SLIBNAMEWITHVERSION)SLIBINSTALLLINKS=(SLIBNAME_WITH_MAJOR) $(SLIBNAME)’

修改为:

SLIBNAME_WITH_VERSION=’ ( S L I B N A M E ) . (SLIBNAME). (SLIBNAME).(LIBVERSION)’
SLIBNAME_WITH_MAJOR=’ ( S L I B P R E F ) (SLIBPREF) (SLIBPREF)(FULLNAME)- ( L I B M A J O R ) (LIBMAJOR) (LIBMAJOR)(SLIBSUF)’
LIB_INSTALL_EXTRA_CMD=’?(RANLIB)" ( L I B D I R ) / (LIBDIR)/ (LIBDIR)/(LIBNAME)"’
SLIB_INSTALL_NAME=’ ( S L I B N A M E W I T H M A J O R ) ′ S L I B I N S T A L L L I N K S = ′ (SLIBNAME_WITH_MAJOR)' SLIB_INSTALL_LINKS=' (SLIBNAMEWITHMAJOR)SLIBINSTALLLINKS=(SLIBNAME)’

但是在新版本的FFmpeg中,这个问题终于被解决了,FFmpeg加入了 android 这个 target所以我们再也不需要手动去修改了

  • sysroot

--sysroot=$SYSROOT: 用于配置交叉编译环境的 根路径 ,编译的时候会默认从这个路径下去寻找 usr/include usr/lib 这两个路径,进而找到相关的头文件和库文件。

r20b 版本的 NDK 系统的头文件和库文件就是在 $SYSYROOT/usr/include$SYSYROOT/usr/lib 中。

基本上很多新手在编译的时候都会出现找不到各种头文件,导致编译失败。所以当编译出现找不到头文件的时候,首先要检查的就是这个路径。

一点疑问

在使用最新的 ndk r20b 版本进行编译的时候发现,即使不配置 sysroot 也可以正常编译,怀疑 Android 的 clang 工具是否经过了处理,会自动去寻找对应的路径。 目前没有从 configure 文件中找到原因。

如有知情者的,还望告知呀~。

说到 sysroot 就不得不提到另外一个参数 -isysyroot ,这个参数也让我困惑了很久,因为很少文章会提到这个两个参数的联系和区别,然而这个参数也很导致让人很莫名奇妙的编译失败。

  • extra-cflags

介绍 -isysroot 之前,先看看这个 extra-cflags 选项。

这个选项的作用是,给编译器指定除了 sysroot 之外的头文件搜索路径。比如:

–extra-cflags="-I$SYSROOT/usr/include"

其中 -I 用于区分不同的路径

-isysroot 是这个选项的一个配置。比如

–extra-cflags="-isysroot $SYSROOT"

-isysroot 的作用就是,把后面的路径设置为默认的头文件搜索路径,这时候,前面 sysroot 配置路径就不再作为 头文件 默认的搜索路径了,不过依然是 库文件 默认的搜索路径。

可以看到,这两个配置从某种程度上说是一样的:

–extra-cflags="-I$SYSROOT/usr/include"

约等于

–extra-cflags="-isysroot $SYSROOT"

  • extra-ldflags

这个和上面的 extra-cflags 作用是类似的,不过是用于配置额外的 库文件 搜索路径,如

–extra-ldflags="-L$SYSROOT/usr/lib"

其中 -L 用于区分不同的路径

可以看到 extra-cflags extra-ldflags 结合起来可以替代 sysroot

  • cross-prefix

这个选项直译为 交叉编译前缀,指的是交叉编译工具的前缀。

这个选项经常和另外一个选项 cc 一起出现搭配使用。

这是什么意思呢?网上有的文章对于 cc 这个选项经常出现两种配置方式:

一种是只配置 cross-prefix ,没有配置 cc ,比如本文。

另一种是既配置 cross-prefix ,又配置 cc

比如:

–cc=KaTeX parse error: Undefined control sequence: \ at position 41: …ndroideabi-gcc \̲ ̲--cross-prefix=TOOLCHAIN/bin/arm-linux-androideabi- \

这是两种完全不同的配置方式,但是很神奇的是有时候他们都能成功编译,有时候又会出现找不到编译链工具的错误。

为了搞明白 cross-prefix cc 这两个选项的配置到底有什么影响,到底应该怎么使用这两个配置,我特地仔细的去看了 FFmpeg 根目录下的 configure 配置脚本,找到了一些蛛丝马迹。

分析 configure 配置脚本

注:以下分析基于ffmpeg-4.2.2版本,其他版本可能有所不同,掌握基本原理即可。

  • 获取用户配置选项

打开(注:不是双击运行)configure shell脚本,首先来看看 configure 是如何获取用户配置的编译选项的。

搜索 for opt do,可以找到以下代码

for opt do
optval=“KaTeX parse error: Expected '}', got '#' at position 5: {opt#̲*=}" case "opt” in
–extra-ldflags=)
add_ldflags $optval
;;
–extra-ldexeflags=
)
add_ldexeflags $optval
;;
–extra-ldsoflags=)
add_ldsoflags $optval
;;
–extra-ldlibflags=
)
warn “The --extra-ldlibflags option is only provided for compatibility and will be\n”
“removed in the future. Use --extra-ldsoflags instead.”
add_ldsoflags $optval
;;
–extra-libs=*)
add_extralibs $optval
;;
–disable-devices)
disable $INDEV_LIST O U T D E V L I S T ; ; − − e n a b l e − d e b u g = ∗ ) d e b u g l e v e l = " OUTDEV_LIST ;; --enable-debug=*) debuglevel=" OUTDEVLIST;;enabledebug=)debuglevel="optval"
;;

省略中间一些代码…

*)
optname="KaTeX parse error: Expected '}', got 'EOF' at end of input: …%=*}" optname="{optname#–}"
optname= ( e c h o " (echo " (echo"optname" | sed ‘s/-/_/g’)
if is_in $optname $CMDLINE_SET; then
eval o p t n a m e = ′ optname=' optname=optval’
elif is_in $optname $CMDLINE_APPEND; then
append o p t n a m e " optname " optname"optval"
else
die_unknown $opt
fi
;;
esac
done

这个shell脚本的代码有很多特有的语法,也不用钻牛角尖,能大概看明白就可以了。

for循环的首行 通过分割 = 获取到用户设置的选项值 optval

下面除了一些特殊的选项,我们看看最后的通配符 *) ,这段代码的目的,其实就是把用户配置的选项和值关联起来。

比如 --cpu=armv7-a ,前面三行就是把 cpu 分割出来,赋值给 optname,再把 optval 赋值给 cpu,说白了就是初始化了 cpu 这个变量为 armv7-a

  • Android相关的配置

搜索 android 关键字,可以找到以下代码

ffmpeg-4.2.2/configure

if test “$target_os” = android; then
cc_default=“clang”
fi

ar_default=" c r o s s p r e f i x {cross_prefix} crossprefix{ar_default}"
cc_default=" c r o s s p r e f i x {cross_prefix} crossprefix{cc_default}"
cxx_default=" c r o s s p r e f i x {cross_prefix} crossprefix{cxx_default}"
nm_default=" c r o s s p r e f i x {cross_prefix} crossprefix{nm_default}"
pkg_config_default=" c r o s s p r e f i x {cross_prefix} crossprefix{pkg_config_default}"

当你配置了 --target-os=android 的时候,FFmpeg默认的编译工具为 clang

cc_default 其实就是配置项 cc 的默认值,可以看到 cc_default 在这里和 cross_prefix 做了拼接。这里就是为什么说 cross_prefix 是交叉编译工具前缀。

拼接完是这样的:

cc_defalut= T O O L C H A I N / b i n / a r m − l i n u x − a n d r o i d e a b i − TOOLCHAIN/bin/arm-linux-androideabi- TOOLCHAIN/bin/armlinuxandroideabicc

看下 ar_default cc_default cxx_default这些默认值是什么。

搜索 cc_default 可以找到以下代码

ffmpeg-4.2.2/configure

ar_default=“ar”
cc_default=“gcc”
cxx_default=“g++”
host_cc_default=“gcc”

可以看到,FFmpeg 默认的编译工具是 GCC

当你编译 Android 平台的库时,由于 configure 强制设置 cc_default="clang",所以:

  1. 当你使用 GCC 作为编译工具时,必须配置 cc 选项,或修改 configure 中的 cc_default="clang"cc_default="gcc" ;

  2. 当你使用 CLANG 作为编译工具时,可以不配置 cc 选项。

仔细想想会发现,为什么当 cc 配置为下边的值时,也可以正常编译呢?

–cc=$TOOLCHAIN/bin/arm-linux-androideabi-gcc

这时 cc_defalut 不就等于

cc_defalut= T O O L C H A I N / b i n / a r m − l i n u x − a n d r o i d e a b i − TOOLCHAIN/bin/arm-linux-androideabi- TOOLCHAIN/bin/armlinuxandroideabiTOOLCHAIN/bin/arm-linux-androideabi-gcc

这个路径肯定是错的啊!

这就要来看到底 cc_default 是怎么使用的了。

  • 初始化变量

搜索 set_default arch ,可以看到以下代码,在这里 configure 重新设置了 cc 的默认值。

set_default arch cc cxx doxygen pkg_config ranlib strip sysinclude
target_exec x86asmexe nvcc

这里调用了一个叫 set_default 的函数,来看看这个函数的实现

set_default(){
for opt; do
eval : ${$opt:=?{opt}_default}
done
}

这也是一个看不太懂的shell语法,大概的意思就是:for循环获取所有的输入参数变量,然后给这个变量赋值。

比如 set_default cc ,意思就是 cc=cc_default ,不过有一点要注意的是中间这个符号 :=

这个符号类似Java中的三目运算符:

opt != null? opt:opt_defalut

也就是说,如果参数为空,将 xx_default 赋值给 xx

这就可以解释上面的疑问了。

  1. 当配置

–cc=$TOOLCHAIN/bin/arm-linux-androideabi-gcc

set_default cc 等于没有用了。因为经过 for 循环获取了用户的配置以后, cc 不为空。 set_default 后,cc 的值是不会改变的。

  1. cc 不配置的时候,FFmpeg 根据默认的拼接方式,把拼接好的路径设置给 cc

  2. 但是,不能配置 cc=gcc 这种,这样,最后 cc 的值就只有 gcc ,肯定是不能正确找到编译工具的。

  • 为什么要加入 corss-prefix-clang 这个选项

现在可以来解释为什么前面需要修改 configure 配置脚本了。

原始的配置是这样的

ar_default=" c r o s s p r e f i x {cross_prefix} crossprefix{ar_default}"
cc_default=" c r o s s p r e f i x {cross_prefix} crossprefix{cc_default}"
cxx_default=" c r o s s p r e f i x {cross_prefix} crossprefix{cxx_default}"
nm_default=" c r o s s p r e f i x {cross_prefix} crossprefix{nm_default}"
pkg_config_default=" c r o s s p r e f i x {cross_prefix} crossprefix{pkg_config_default}"

也就是说,默认的 cc ar nm 路径前缀是一样的,但是 Android NDK 的路径却是这样的

NDK clang路径

看到了不?ar/nmcc的前缀是不一样的,前者是 arm-linux-androideabi- , 后者是 armv7a-linux-androideabi16-

因此,需要对 cccxx 两个前缀进行修改,为此新加了 cross_prefix_clange 来进行单独配置。

这里只是针对 NDK r20b 的情况,不同的 NDK 版本可能有所不同,根据这个原理去设置即可。

综上,解释了一些编译 FFmpeg 常用的配置选项,并且从原理上弄明白为何要这样配置,基本上搞清楚了这些,想要组合两个不同版本的FFmpeg和NDK来编译,都会比较容易实现。

启动编译

打开cmd终端,cd 到 FFmpeg 所在目录

输入 ./build_android_clang.sh

等待编译完成,将会在 ffmpeg/android/armv7-a目录下得到 includelib 两个目录,分别是 头文件so库文件

生成的so

生成的头文件

五、使用 GCC 编译FFmpeg

目前大部分网上的文章都是使用 GCC 来编译 FFmpeg 的,下面就来看看如何配置 GCC 的编译参数。

文末

不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊

小编将自己6年以来的面试经验和学习笔记都整理成了一个**937页的PDF,**以及我学习进阶过程中看过的一些优质视频教程。

其实看到身边很多朋友抱怨自己的工资很低,包括笔者也是一样的,其原因是在面试过程中没有给面试官一个很好的答案。所以笔者会持续更新面试过程中遇到的问题,也希望大家和笔者一起进步,一起学习。

.(img-htvKViH4-1643536449852)]

五、使用 GCC 编译FFmpeg

目前大部分网上的文章都是使用 GCC 来编译 FFmpeg 的,下面就来看看如何配置 GCC 的编译参数。

文末

不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊

小编将自己6年以来的面试经验和学习笔记都整理成了一个**937页的PDF,**以及我学习进阶过程中看过的一些优质视频教程。

[外链图片转存中…(img-tlAg9bIu-1643536449853)]

其实看到身边很多朋友抱怨自己的工资很低,包括笔者也是一样的,其原因是在面试过程中没有给面试官一个很好的答案。所以笔者会持续更新面试过程中遇到的问题,也希望大家和笔者一起进步,一起学习。

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值