【Android 音视频开发打怪升级:FFmpeg音视频编解码篇】一、FFmpeg so库编译

编译工具链目录:
toolchains/llvm/prebuilt/darwin-x86_64/bin

交叉编译环境目录:
toolchains/llvm/prebuilt/darwin-x86_64/sysroot

  • 编译工具路径

编译工具

根据不同的CPU架构区和不同的Android版本,区分了不同的clang工具,根据自己需要选择就好了。

本文选择 CPU 架构 armv7a,Android版本 21:

armv7a-linux-androideabi21-clang
armv7a-linux-androideabi21-clang++

  • 编译环境路径

toolchains/llvm/prebuilt/darwin-x86_64/sysroot 目录下,包含了两个目录: usr/includeusr/lib,分别对应了 头文件库文件

库文件和头文件

下载FFmpeg源码

FFmpeg官网下载,直接DownLoad即可。

本文使用的是目前最新的版本 ffmpeg-4.2.2

下载好源码后,进入根目录,找到一个名为 congfigure 的文件,这是一个shell脚本,用于生成一些 FFmpeg 编译需要的配置文件。

这个文件非常重要,FFmpeg 的编译配置就是靠它完成的。

后面我们将对其中一些重要的内容进行分析,这是理解 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= O U T P U T   − − t a r g e t − o s = a n d r o i d   − − a r c h = a r m   − − c p u = a r m v 7 − a   − − e n a b l e − a s m   − − e n a b l e − n e o n   − − e n a b l e − c r o s s − c o m p i l e   − − e n a b l e − s h a r e d   − − d i s a b l e − s t a t i c   − − d i s a b l e − d o c   − − d i s a b l e − f f p l a y   − − d i s a b l e − f f p r o b e   − − d i s a b l e − s y m v e r   − − d i s a b l e − f f m p e g   − − s y s r o o t = OUTPUT \ --target-os=android \ --arch=arm \ --cpu=armv7-a \ --enable-asm \ --enable-neon \ --enable-cross-compile \ --enable-shared \ --disable-static \ --disable-doc \ --disable-ffplay \ --disable-ffprobe \ --disable-symver \ --disable-ffmpeg \ --sysroot= OUTPUT targetos=android arch=arm cpu=armv7a enableasm enableneon enablecrosscompile enableshared disablestatic disabledoc disableffplay disableffprobe disablesymver disableffmpeg sysroot=SYSROOT
–cross-prefix= 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 −   − − c r o s s − p r e f i x − c l a n g = TOOLCHAIN/bin/arm-linux-androideabi- \ --cross-prefix-clang= TOOLCHAIN/bin/armlinuxandroideabi crossprefixclang=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= 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 − g c c   − − c r o s s − p r e f i x = TOOLCHAIN/bin/arm-linux-androideabi-gcc \ --cross-prefix= TOOLCHAIN/bin/armlinuxandroideabigcc crossprefix=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 的编译参数。

下载 Android NDK r17b

前面就说过,NDK r17c 以后,Googole 就移除了 GCC,所以要使用 GCC 只能下载 r17c 及以前的版本,本文使用 r17c 来编译。

根据自己编译平台选择对应的版本:NDK r17c

本文选择的是 Mac 版本:Mac OS X。

NDK 相关的环境路径

NDK r17c 目录

NDK r20b 相比,NDK r17c的目录稍微有些变化。

  • 交叉编译环境路径

库文件路径

android-ndk-r17c/platforms/android-21/arch-arm/usr/lib

头文件路径

android-ndk-r17c/sysroot/usr/include

最后

答应大伙的备战金三银四,大厂面试真题来啦!

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960全网最全Android开发笔记》

《379页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

腾讯、字节跳动、阿里、百度等BAT大厂 2020-2021面试真题解析

资料收集不易,如果大家喜欢这篇文章,或者对你有帮助不妨多多点赞转发关注哦。文章会持续更新的。绝对干货!!!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数

[外链图片转存中…(img-kqlFQ1Sk-1714940089492)]

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

[外链图片转存中…(img-cZMnPNXE-1714940089494)]

腾讯、字节跳动、阿里、百度等BAT大厂 2020-2021面试真题解析

[外链图片转存中…(img-xRCOOvvn-1714940089495)]

资料收集不易,如果大家喜欢这篇文章,或者对你有帮助不妨多多点赞转发关注哦。文章会持续更新的。绝对干货!!!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值