一、FFMPEG简介
FFmpeg即是一款音视频编解码工具,同时也是一组音视频编码开发套件,作为编码开发套件,它为开发者提供了丰富的音视频处理的调用接口。
FFmpeg提供了多种媒体格式的封装和解封装,包括多种音视频编码、多种协议的流媒体、多种多彩格式转换、多种采样率转换、多种码率转换等;FFmpeg框架提供了多种丰富的插件模块,包含封装与解封装的插件、编码与解码的插件等。
引用自:https://blog.youkuaiyun.com/Mr_robot_strange/article/details/109966774
二、FFMPEG模块组成
FFmpeg框架的基本组成包含AVFormat、AVCodec、AVFilter、AVDevice、AVUtil等模块库,结构图如下:
2.1 AVFormat–FFmpeg的封装模块
AVFormat中实现了目前多媒体领域中的绝大多数媒体封装格式,包括封装和解封装,如MP4、FLV、KV、TS等文件封装格式,RTMP、RTSP、MMS、HLS等网络协议封装格式。FFmpeg是否支持某种媒体封装格式,取决于编译时是否包含了该格式的封装库。根据实际需求,可进行媒体封装格式的扩展,增加自己定制的封装格式,即在AVFormat中增加自己的封装处理模块。
2.2 AVCodec–FFmpeg的编解码模块
AVCodec中实现了目前多媒体领域绝大多数常用的编解码格式,即支持编码,也支持解码。AVCodec除了支持MPEG4、AAC、MJPEG等自带的媒体编解码格式之外,还支持第三方的编解码器,如H.264(AVC)编码,需要使用x264编码器;H.265(HEVC)编码,需要使用x264编码器;MP3(mp3lame)编码,需要使用libmp3lame编码器。如果希望增加自己的编码格式,或者硬件编解码,则需要在AVCodec中增加相应的编解码模块。
2.3 AVFilter–FFmpeg的滤镜模块
AVFilter库提供了一个通用的音频、视频、字幕等滤镜处理框架。在AVFilter中,滤镜框架可以有多个输入和多个输出。
2.4 swresample–FFmpeg的音频转换计算模块
swresample模块提供了高级别的音频重采样API。例如允许操作音频采样、音频通道布局转换与布局调整。
2.5 swscale–FFmpeg的视频图像转换计算模块
swscale模块提供了高级别的图像转换API,例如它允许进行图像缩放和像素格式转换,常见于将图像从1080p转换成720p或者480p等的缩放,或者将图像数据从YUV420p转换成YUYV,或者YUV转RGB等图像格式转换。
引用自:https://blog.youkuaiyun.com/Mr_robot_strange/article/details/109966774
三、FFMPEG编译
在开发中,需要使用到FFMPEG的解封装功能,所以要将FFMPEG的源码编译成可以供上层调用的so库,集成到android系统源码中。因此要学习和了解FFMPEG编译的相关知识。
首先,在进行FFMPEG编译之前,需要了解一些必要知识点。
如何将FFMPEG编译成so库?
FFMEPG是属于第三方代码,android平台编译第三方C/C++代码最通用的解决的方案是利用Android NDK的工具链进行交叉编译。
那么什么是交叉编译呢?
在宿主机上编译,在目标机上执行。从事嵌入式系统或者单片机朋友再熟悉不过了。所有的嵌入系统都是在PC机上编译(宿主机),在单板上执行(目标机)。也就是在一个平台上生成另一个平台上的可执行代码,再简单来说,就是在一个机器上生成一个程序,这个程序可以跑在另外一个机器上。
因为PC上的环境和手机上的运行环境是绝然不同的,所以,交叉编译最重要的是,要配置好编译过程中使用到的相关的环境,而这个环境其实就是目标机器(比如Android手机)正在运行的环境。
对于C/C++的编译,通常有两个工具 GCC 和 CLANG 。
GCC 可能大家都有听说过,这是一个老牌的编译工具,不仅可以编译C/C++,也可以编译Java,Object-C,Go等语言。
CLANG 则是一个效率更高的C/C++编译工具,并且兼容GCC,Google在很早以前就开始建议使用clang进行编译,并且在 ndk 17 以后,把 GCC 移除了,全面推行使用 CLANG 。
那么,介绍完基本的知识点之后,现在开始正式讲解FFMPEG编译的内容。
3.1 下载Android NDK
需要使Google提供的NDK,将FFMPEG源码编译成Android可以调用的so.
下载地址:https://developer.android.google.cn/ndk/downloads?hl=zh_cn
因为我是在linux环境下编译的,所以这里下载对应的软件包
配置NDK环境变量
将下载下来的NDK包,解压至对应的路径,然后复制相应的路径。
打开终端,输入下面的命令
vim ~/.bashrc
翻到文件的末尾,按i键进入编辑模式,输入
export NDK_HOME=刚才复制的path
export PATH=$NDK_HOME:$PATH
然后按ESC键退出编辑模式,输入:wq回车,保存修改内容。
退出vim后,使用命令
source ~/.bashrc
使改动生效,至此,ndk的环境变量就配置好了。
3.2 下载FFMPEG源码
下载网址:http://ffmpeg.org/download.html
3.3 编辑修改配置文件
下载好源码后,进入根目录,找到一个名为 congfigure 的文件,这是一个shell脚本,用于生成一些 FFmpeg 编译需要的配置文件,如下图所示。
这个文件非常重要,FFmpeg 的编译配置就是靠它完成的。 后面我们将对其中一些重要的内容进行分析,这是理解 FFmpeg 编译配置的关键。
旧的版本会让修改configure文件中的如下部分,是为了生成Android可以使用的so。原因如下:
Android 工程中只支持导入 .so 结尾的动态库,形如:libavcodec-57.so 。但是 FFmpeg 编译生成的动态库默认格式为 xx.so.版本号 ,形如:libavcodec.so.57 , 所以需要修改 FFmpeg 根目录下的 configure 文件,使其生成以 .so 结尾格式的动态库。修改方法为:
# 将 configure 文件中 build settings 下的:
SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'
SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR) $(SLIBNAME)'
#替换为:
SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'
但在新版本上已经好像不用再这样修改了,如果编译出来的so库的后缀不符合要求的话,可以使用上面的方法修改configure文件。
除此之外,因为Google 在新版ndk把 GCC 移除了,全面推行使用 CLANG,所以我们需要把编译工具配置进行修改。修改流程如下:
1.新增 cross_prefix_clang 参数
我们可以搜索 CMDLINE_SET ,可以找到以下代码,然后新增一个命令行选项:cross_prefix_clang
2.修改编译工具路径设置
我们可以搜索 ar_default=“
c
r
o
s
s
p
r
e
f
i
x
{cross_prefix}
crossprefix{ar_default}” , 找到以下代码:
ar_default="${cross_prefix}${ar_default}"
cc_default="${cross_prefix}${cc_default}"
cxx_default="${cross_prefix}${cxx_default}"
nm_default="${cross_prefix}${nm_default}"
pkg_config_default="${cross_prefix}${pkg_config_default}"
将中间两行修改为:
ar_default="${cross_prefix}${ar_default}"
#------------------------------------------------
cc_default="${cross_prefix_clang}${cc_default}"
cxx_default="${cross_prefix_clang}${cxx_default}"
#------------------------------------------------
nm_default="${cross_prefix}${nm_default}"
pkg_config_default="${cross_prefix}${pkg_config_default}"
引用自:https://cloud.tencent.com/developer/article/1773965
3.4 编辑编译脚本
FFmpeg 已经为我们准备好了 Makefile 可以直接用于建构,还为我们提供了 configure 程序可以调节编译的设置,configure 提供许多参数可供选择,如编译模块,目标平台、编译工具链等等,通常的做法是编写一份脚本进行设置与建构
关于configure的参数含义,可参考:https://cloud.tencent.com/developer/article/2246778?areaSource=106000.5&traceId=EmVuSa-xvaoG4O5f0IpFQ
在ffmpeg路径下,新建一个名为"build_android.sh"的脚本,编译脚本内容如下:
#!/bin/bash
set -x
# 目标Android版本
API=29
ARCH=arm64
CPU=armv8-a
TOOL_CPU_NAME=aarch64
#so库输出目录
OUTPUT=$(pwd)/android/$CPU
# NDK的路径,根据自己的NDK位置进行设置
NDK=/home/gec/Documents/FFMPEG/NDK/android-ndk-r25c-linux/android-ndk-r25c
# 编译工具链路径
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64
# 编译环境
SYSROOT=$TOOLCHAIN/sysroot
TOOL_PREFIX="$TOOLCHAIN/bin/$TOOL_CPU_NAME-linux-android"
CC="$TOOL_PREFIX$API-clang"
CXX="$TOOL_PREFIX$API-clang++"
OPTIMIZE_CFLAGS="-march=$CPU"
function build
{
./configure \
--prefix=$OUTPUT \
--target-os=android \
--arch=$ARCH \
--cpu=$CPU \
--disable-asm \
--enable-neon \
--enable-cross-compile \
--enable-shared \
--disable-static \
--disable-doc \
--disable-ffplay \
--disable-ffprobe \
--disable-symver \
--disable-ffmpeg \
--cc=$CC \
--cxx=$CXX \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \
--disable-vulkan \
--disable-stripping
make clean all
# 这里是定义用几个CPU编译
make -j8
make install
}
build
注意,以上编译脚本内容不是一成不变的,不可以直接照抄,要根据自己的实际情况进行修改。
注意修改下面的部分。
如果想要确认android平台的CPU架构,可使用以下命令
adb shell getprop ro.product.cpu.abi
对于几个参数的介绍
target-os
–target-os=android:在旧版本的 FFmpeg 中,对Android平台的支持并不是很完善,并没有 android 这个target,所以在一些比较老的文章中都会提到,编译Android平台的so库,需要对 configure 做以下修改,否则会按照 linux 标准的方式输出so库,其命名方式和Android的so不一样,Android是无法加载的,就是之前说的问题。
sysroot
–sysroot=$SYSROOT: 用于配置交叉编译环境的 根路径 ,编译的时候会默认从这个路径下去寻找 usr/include usr/lib 这两个路径,进而找到相关的头文件和库文件。
r20b 版本的 NDK 系统的头文件和库文件就是在 $SYSYROOT/usr/include 和 $SYSYROOT/usr/lib 中。
基本上很多新手在编译的时候都会出现找不到各种头文件,导致编译失败。所以当编译出现找不到头文件的时候,首先要检查的就是这个路径。
3.5 运行脚本
执行下方的命令
./build_android.sh
如果有提示权限问题,可以加上sudo
sudo ./build_android.sh
3.6 踩过的坑
1.如果有遇到一些没有安装相应的工具报错
可以先更新一下包列表
sudo apt update
然后检查一下是否有安装如下的编译软件
sudo apt install autoconf \
automake \
build-essential \
cmake \
git-core \
libass-dev \
libfreetype6-dev \
libgnutls28-dev \
libmp3lame-dev \
libsdl2-dev \
libtool \
libva-dev \
libvdpau-dev \
libvorbis-dev \
libxcb1-dev \
libxcb-shm0-dev \
libxcb-xfixes0-dev \
meson \
ninja-build \
pkg-config \
texinfo \
wget \
yasm \
zlib1g-dev \
libunistring-dev \
libaom-dev \
libdav1d-dev \
pkg-config
2.查看相关log
进到ffbuild找到config.log并打开,拉到最下方,可以看到以下报错详情:
3.遇到如下编译报错Unable to recognise the format of the input file
STRIP install-libavdevice-shared
strip: Unable to recognise the format of the input file `/home/gec/Documents/FFMPEG/SourceCode/ffmpeg-6.0/android/armv8-a/lib/libavdevice.so'
make: *** [ffbuild/library.mak:120: install-libavdevice-shared] Error 1
在网上查找资料,遇到这种错误的原因,是因为宿主机和编译的目标所使用的strip不是同一个导致的,可以使用type strip命令查看。
但是没有从根本去解决问题,了解到strip是用于优化编译的,我是在脚本文件中添加了–disable-stripping解决的
参考
https://zhuanlan.zhihu.com/p/580515740
https://cloud.tencent.com/developer/article/1773965
https://blog.youkuaiyun.com/yonghuming_jesse/article/details/127244553
https://blog.youkuaiyun.com/qq_42612119/article/details/121238236
https://blog.youkuaiyun.com/fengliang191/article/details/119858225
https://blog.youkuaiyun.com/Mr_robot_strange/article/details/109966774