FFmpeg是什么?
FFmpeg是一套开源免费跨平台的多媒体框架,它提供录制、转换以及流化音视频的完整解决方案,在Windows、iOS、Android提供了一个音视频处理的框架,FFmpeg从90年代一直到现在都是非常火的。
如果你把FFmpeg整个都学完了并且学好了,那么你在国内找一份非常好的工作是没有任何问题的
FFmpeg它广泛存在于一线互联网企业的软件应用里面,比如谷歌、B站、抖音、YouTube、美图,甚至包括微信、QQ……
并且我们也可以用一种非常简单的方法去验证它到底有没有使用FFmpeg
比如某个App,我们可以将它反编译一下,之前有将App下载下来了,我们把它的后缀名改成zip格式,然后解压到当前文件夹。
点进去打开lib文件夹会发现里面有很多的lib目录,其中就会有一个叫做FFmpeg,并且这个FFmpeg.so这个库还是挺大的,可以看到是有3M多
这也可以从侧面证明,所有的应用,只要和音视频相关,那FFmpeg是必选项。
到这里,可能有人就会问了,如果我只要去播放一个视频,需不需要用到FFmpeg呢?
这个时候就取决于你要播放哪种视频,如果你是播放MP4、AVI,那是不需要用到FFmpeg的,直接用我们的MediaPlayer就可以实现。但是,假如我们要像抖音一样增加一些比较特别的特效,像我们经常会看到的“狗头特效”、“灵魂出窍特效”,还有一些非常好看的磨皮美白等,这个时候就都需要用到FFmpeg来进行解码与绘制。
所以,如果你要去学会音视频的编解码的话,那你必须要学习FFmpeg。
FFmpeg这么牛逼,那我们怎么去用呢?
毕竟FFmpeg并不是针对Android来开发的,它是针对于完整平台的一个解决方案,有Windows、iOS、Android,开发语言是C语言
所以,我们如果要学习FFmpeg,就要懂得去编译它
那编译就需要有一台云服务器,系统镜像最好是CentOS,或者Ubuntu也可以
这边使用的是CentOS 7.3。我们先把IP复制一下,可以用Xshell也可以像我这里面的MobaXterm去进行一个链接,我们就采取这个SSH工具进行连接
我们把工具打开,填上刚才的云服务器,用户名是root,然后我们再来输入密码
这样我们的云服务器就已经被连接成功了,大家可以看到这个云服务器是没有任何东西的
然后我们再来看如何去搭建编译FFmpeg的环境呢?
搭建编译FFmpeg的环境
FFmpeg它提供了一个完整的解决方案,但是这个解决方案它是用C来写的,我们将FFmpeg的源码下载下来之后解压看一下,一起看看FFmpeg源码到底是什么样子
这里是4.1.3的版本,后面我们会以4.2.1进行操作学习
FFmpeg从最开始到现在4.x.x版本,中间的版本变化还是非常大的,比如2.0-3.0、3.0-4.0区别都比较大,4.0之后的版本都是通过NDK Clang来进行编译的,不会再用到gcc的方式来进行编译,所以我们主要给大家讲解Clang的交叉编译的过程
我们打开FFmpeg源码,点开文件夹发现里面所有的代码都是一些C和C++代码,成百上千个文件,如果我们将这些文件放到Android Studio来进行编译的话,那这个时候你编译一个Apk会需要相当长的时间,这就是为什么我们需要通过交叉编译去生成so然后再放到我们项目中的原因
所以我们用FFmpeg一般怎么用呢?
我们把FFmpeg的源码下载下来,然后编译到Android系统,编译成动态库或静态库,那这个编译过程就需要我们的编译环境,不能是Windows,必须要是Linux。这个Linux我们用的是CentOS 7.3,然后用Linux将源码进行编译之后产生动态库或静态库,放到我们的App当中,再通过头文件进行调用
进入FFmpeg的官网,我们可以在里面去下载源码,但是我们下载到Windows的话,是没有任何卵用的,我们必须下载到Linux
重新来到我们刚才的服务器,下载FFmpeg源码
创建一个FFmpeg,后面写到音视频直播拉流的时候,我们也是用的FFmpeg
这样我们一个FFmpeg就已经下完了,下完之后,还需要进行解压
我们调用一下解压命令xvf,然后把FFmpeg进行解压
在解压的过程中,大家就可以发现它里面有很多很多的C文件
这就是我们FFmpeg的源代码,FFmpeg的源码我们要进行编译,在Linux里面是有gcc的,那我们能不能通过Linux的gcc去编译FFmpeg呢?
很显然是不应该的,因为Linux里面的gcc和我们Android的gcc工具是不一样的。Linux它编译的东西只能在Linux使用,并不能移植到我们Android系统,这个时候我们需要学到另外一个技术——交叉编译。
目前,在NDK 17以后,它推荐的交叉工具叫做Clang,在以前的版本中,我们都是用的Android的gcc。
Android的gcc和Clang到底有什么区别呢?
为了让我们编译速度更快、执行C代码更快,就开发了一个非常便利的编译工具,就叫做Clang
后续也会通过最新的NDK版本、Clang来编译FFmpeg
那既然源码已经下下来了,接下来我们还差一个编译工具——NDK
NDK环境配置
因为在NDK源文件里面会有很多关于C的编译工具,我们必须用NDK的编译工具而不能用Linux的编译工具,我们把这种编译就称为交叉编译。
再回到云服务器,我们需要下载一个NDK,大家下载的时候可以选择NDKr20,我们将用这个版本去编译
然后我们还要对NDK进行环境配置
mkdir ndk 创建一个NDK目录
这里推荐大家买服务器最好是选择香港的服务器,因为香港的服器下载这些国外的软件是非常快的,比如我这台服务器的,实际上它就是一个香港服务器
然后我们使用wget命令,就可以把NDK下载下来了。NDK相对来说还是比较大的,但因为香港服务器的缘故,下载还是非常快的
下载完后,使用zip命令将下载的文件解压
unzip,解压的时间可能会有点长,根据每个服务器的配置不一样,解压的时间长短也会不一样
解压完成后我们再去配置NDK环境,这个配置环境和Windows还是有些不一样的,在Windows中我们直接在我的电脑中就可以配置到,但是在Linux里面,它是不一样的,我们需要去编辑etc/profile
在最底部加上我们的环境,我们设定一个NDK ROOT,把它加到PATH路径里面去
然后再退出,让环境生效
到目前,我们NDK的环境就已经配好了,这个时候我们需要编译FFmpeg就需要用到shell脚本
shell脚本
shell脚本它专门是针对于Linux里面,进行编译的语法,类似于Java语法
说到这里,shell语法我们要不要学呢?
学肯定是需要的,但我们只需要大概去了解这个shell脚本怎么去写,就已经可以满足我们这个系列文章里面所要掌握的内容
我们来看这个编写脚本里面内容,脚本的作用是将FFmpeg源码打包成我们在Android里面想要用到的.so文件,或者说.a静态库文件,供我们在Android项目里面去调用
在FFmpeg源文件夹下去建立一个android_build.sh
直接copy下面代码进去
#!/bin/bashexport NDK=/root/ndk/android-ndk-r20# 当前系统export HOST_TAG=linux-x86_64# 支持的 Android CUP 架构# export ARCH=aarch64# export CPU=armv8-aexport ARCH=armv7aexport CPU=armv7-a# 支持的 Android 最低系统版本export MIN=21export ANDROID_NDK_PLATFORM=android-21export PREFIX=$(pwd)/android/$CPUexport MIN_PLATFORM=$NDK/platforms/android-$MINexport SYSROOT=$NDK/sysrootexport TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/$HOST_TAGexport AR=$TOOLCHAIN/bin/arm-linux-androideabi-arexport AS=$TOOLCHAIN/bin/arm-linux-androideabi-asexport CC=$TOOLCHAIN/bin/$ARCH-linux-androideabi$MIN-clangecho "-----------------------------"echo $CCexport CXX=$TOOLCHAIN/bin/$ARCH-linux-androideabi$MIN-clang++export LD=$TOOLCHAIN/bin/arm-linux-androideabi-ldexport NM=$TOOLCHAIN/bin/arm-linux-androideabi-nmexport RANLIB=$TOOLCHAIN/bin/arm-linux-androideabi-ranlibexport STRIP=$TOOLCHAIN/bin/arm-linux-androideabi-stripOPTIMIZE_CFLAGS="-DANDROID -I$NDK/sysroot/usr/include/arm-linux-androideabi/"ADDI_LDFLAGS="-Wl,-rpath-link=$MIN_PLATFORM/arch-arm/usr/lib -L$MIN_PLATFORM/arch-arm/usr/lib -nostdlib"sed -i "" "s/SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'/SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'/" configuresed -i "" "s/LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)\/$(LIBNAME)"'/LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)\/$(LIBNAME)"'/" configuresed -i "" "s/SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'/SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'/" configuresed -i "" "s/SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR) $(SLIBNAME)'/SLIB_INSTALL_LINKS='$(SLIBNAME)'/" configuresed -i -e 's/#define getenv(x) NULL//*#define getenv(x) NULL*//g' config.h# sed -i "" "s/SHFLAGS='-shared -Wl,-soname,$(SLIBNAME)'/SHFLAGS='-shared -soname $(SLIBNAME)'/" configure# sed -i "" "s/-Wl//g" configure./configure --prefix=$PREFIX --ar=$AR --as=$AS --cc=$CC --cxx=$CXX --nm=$NM --ranlib=$RANLIB --strip=$STRIP --arch=$ARCH --target-os=android --enable-cross-compile --disable-asm --enable-shared --disable-static --disable-ffprobe --disable-ffplay --disable-ffmpeg --disable-debug --disable-symver --disable-stripping --extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS"--extra-ldflags="$ADDI_LDFLAGS"sed -i "" "s/#define HAVE_TRUNC 0/#define HAVE_TRUNC 1/" config.hsed -i "" "s/#define HAVE_TRUNCF 0/#define HAVE_TRUNCF 1/" config.hsed -i "" "s/#define HAVE_RINT 0/#define HAVE_RINT 1/" config.hsed -i "" "s/#define HAVE_LRINT 0/#define HAVE_LRINT 1/" config.hsed -i "" "s/#define HAVE_LRINTF 0/#define HAVE_LRINTF 1/" config.hsed -i "" "s/#define HAVE_ROUND 0/#define HAVE_ROUND 1/" config.hsed -i "" "s/#define HAVE_ROUNDF 0/#define HAVE_ROUNDF 1/" config.hsed -i "" "s/#define HAVE_CBRT 0/#define HAVE_CBRT 1/" config.hsed -i "" "s/#define HAVE_CBRTF 0/#define HAVE_CBRTF 1/" config.hsed -i "" "s/#define HAVE_COPYSIGN 0/#define HAVE_COPYSIGN 1/" config.hsed -i "" "s/#define HAVE_ERF 0/#define HAVE_ERF 1/" config.hsed -i "" "s/#define HAVE_HYPOT 0/#define HAVE_HYPOT 1/" config.hsed -i "" "s/#define HAVE_ISNAN 0/#define HAVE_ISNAN 1/" config.hsed -i "" "s/#define HAVE_ISFINITE 0/#define HAVE_ISFINITE 1/" config.hsed -i "" "s/#define HAVE_INET_ATON 0/#define HAVE_INET_ATON 1/" config.hsed -i "" "s/#define getenv(x) NULL/\/\/ #define getenv(x) NULL/" config.h
我们这个shell脚本内容比较多,但是这个shell脚本其实说白了只有一句话,就是调用了我们./configure这个文件
这个./configure它是在build.sh的同级目录下的configure文件
我们直接在FFmpeg源码里面去找,在FFmpeg源码同级目录下有个叫做configure的玩意,然后我们把它打开
这个./configure它也是属于shell文件,它是一个可执行的shell文件,类似我们Java里面有一个main函数
我们如何去判断它是不是shell文件呢?
它第一句话就是#!/bin/sh就代表它是一个shell文件,这个shell文件是个可执行的,就相当于main函数里面或许会有一些参数传递进来,比如我们经常会去Java里面写一些main函数,然后main函数里面有一个steing数组,我们通过在命令行执行这个Java文件可以传递一些参数进来。同样的,在shell文件它也会传递这些参数,只不过在configure里面传递的参数就比较复杂了
总之,我们执行这个shell文件只为了去调用这样的一句话 ./configure 去执行我们刚才的shell文件
参数设置
下面的都是属于参数的传递
而上面的是为了去给它定义一些参数的变量,类似于全局的变量
我们来看./configure 第一个–prefix 代表的是我们要输出在哪一个目录
我们最终编译出来的是一个动态库,那这个动态库它肯定就有文件夹,但是文件放在这个prefix这个变量里面,我们往上找下载到源码怎么用,找到prefix定义变量声明的地方,声明在我们当前的目录
pwd是一个命令,shell语法它是可以直接去调用这些命令的,比如我们pwd就可以输出到当前的目录
那这个当前目录下的Android CPU,就是我们当前编译的CPU,我们编译的是armv7a,编译的过程中会产生一个Android文件,然后在Android文件下再去产生个子文件叫做armCPUv7a,最后把我们的编译成果放到这个文件夹下
–ar是Clang里面的一个链接编译器,我们可以打开NDK,我在AndroidStudio的NDK打开,然后接下来我们打开它所在的目录Android/SDK这个目录
这个目录里面有一个NDK-bundle,我们直接是打开了Windows的,Windows和Linux是比较相似的,因为他们都是一个版本,只不过后缀名一个是.exe一个是Linux里面的可执行文件
我们打开NDK bundle然后再toolchains
toolchains:代表的是工具链的意思
继续进去,我们可以发现很多的可执行文件,在Windows里面后缀名都是.exe
然后它里面有以下几个文件需要在FFmpeg里面进行传递参数传进去的
ar、as、nm、strip这几个路径我们需要进行指定,它主要是用来做一个链接编译的作用,加快我们编译so的速度
所以大家会发现在传递参数的时候,第一个是–ar,它的值就是AR这个变量,在上面我们就会定义一个叫做AR的变量
他的路径就是下图,也就是刚刚我们看到的那些路径
这些都是起了链接编译工具的作用
在以前的NDK版本里面是没有这几个文件的,以前只有gcc,现在用Clang来进行交叉编译后我们需要指定这些交叉编译工具链里面的内容
像上面这五个路径都是为了去指定我们交叉工具链里面的指定工具,就是我们把这些工具指定进去之后FFmpeg编译的时候就会用这些工具来进行编译
然后最主要的两个参数就是接下来的–cc、–cxx,在以前我们是–gcc,然后现在是cc,cc就是指的Clang
$CC是定义的这个变量
我们去找一下,在这里面我们就会发现有很多Clang的编译工具
我们主要是用Clang来进行编译,clang++是用来去编译C++,cang是用来编译C文件,我们只需要去指定这两个,就可以实现Clang的语法去进行交叉编译
接下来–arch是代表平台的意思下载到源码怎么用,这个平台我们编译的一个叫做armv7a,我们找到在上面就有这个armv7a平台
这个CPU的平台可以指定arm平台也可以指定x86,如果你指定多个平台的话,只需要用空格去表示
target-os=android代表我们编译的是运行在android目录,它可以编译出ios也可以编译出windows,平台是不一样的
enable-cross-compile代表是允许混合编译,因为交叉编译我们需要去指定他的编译器,这个编译器就是我们在NDK里面看到的Clang和Clang++,如果你把enable写成disable之后,那它就是没有办法去编译Android平台的
然后disable-asm加快我们编译的速度
enable-shared是代表我们编译出的是一个动态库,shared是动态库,static是静态库
如果你要编译的是一个动态库,那记住一定要把静态库给关闭掉,关闭命令是disable
再下面的是为了减少我们编译出来的体积,把一些不常用的东西禁用掉,比如ffprobe和ffplay,ffplay是一个播放器,这个播放器一般是在Windows和Linux里面用到的,它是一个可执行的工具,我们可以用ffplay去分析视频
然后debug也关闭掉,再下面的extra-cflags是一个标志的意思
这些大致的语法就先讲到这里,shell语法我们不用去深究,大家只需要把我们的shell脚本copy到服务器,然后再执行一下就OK了
如果你要编译静态库,那你就把它enable,把动态库给disable
如果你要编译x86,那我们就把CPU的编译平台这个变量换成x86
编译FFmpeg
我们来执行一下,直接用sh build.sh
编译的过程有点慢,可能需要十多分钟,如果你的服务器比较好的话,那速度又会相对快一些
这个时候我们还没有编译完,需要去执行make
因为所有的命令都是通过makefile来编译,我们就需要make命令来进一步编译
经过漫长的等待,FFmpeg的编译就已经到这里了
然后接下来我们还要去执行一个叫做make install的命令
make install是把我们刚才编译的文件输出到Android目录
我们先看看当前目录有没有Android目录
很明显,没有,这个时候我们去执行make install
make install它的命令执行速度还是比较快的,执行完后我们再来一个ls,这个时候就会多了一个Android目录
打开Android目录,我们看到一个非常熟悉的名字,叫做armv7a
继续深入直到看到lib,这个时候最终成果就展现到我们面前了
总共有7个so:
avcodec:是跟编解码器相关的,因为我们在视频播放的时候要对它进行解码;视频编辑的时候要对它进行编码,那我们就会用到avcodec
avdevice:平台不一样,是跟设备相关的一个so库
avfilter:有滤镜,因为FFmpeg本身有一些美颜、美白,那我们就可以加一些动态滤镜
avformat:是跟格式相关的,因为在FFmpeg中即使是flv、mp4、rmvb,甚至一些我们启蒙老师苍老师的小电影它也都是支持的
avutil:它包含FFmpeg会用到的工具类,工具函数都是放在avutil里
swresample:是用来做重采样的,比如我们音频需要重采样,有mp3、wma这些音频我们需要对它重新进行采样然后输出到播放器里面进行播放
swscale:是一个转换器,后续我们实现FFmpeg万能播放器的时候就会用到这个转换器
刚刚我们介绍了FFmpeg的这七个模块,后续我们将会把这些so编译到AndroidStudio中,然后把项目集成到FFmpeg环境
最后
给大家分享一套非常系统的音视频资料,如果有需要的话,直接私信我【音视频】即可获取
还分享一份由大佬亲自收录整理的Android学习PDF+架构视频+面试文档+源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料
这些都是我现在闲暇时还会反复翻阅的精品资料。里面对近几年的大厂面试高频知识点都有详细的讲解。相信可以有效地帮助大家掌握知识、理解原理,帮助大家在未来取得一份不错的答卷。
当然,你也可以拿去查漏补缺,提升自身的竞争力。
真心希望可以帮助到大家,Android路漫漫,共勉!
如果你有需要的话,只需私信我【进阶】即可获取
本文到此结束,希望对大家有所帮助!