热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【Android音视频开发打怪升级:FFmpeg音视频编解码篇

|—|目录一、Android音视频硬解码篇:[1,音视频基础知识](()[2,音视频硬解码流程:封装基础解码框架]((

| — |


目录


一、Android音视频硬解码篇:

  • [1,音视频基础知识](()
  • [2,音视频硬解码流程:封装基础解码框架](()
  • [3,音视频播放:音视频同步](()
  • [4,音视频解封和封装:生成一个MP4](()

二、使用OpenGL渲染视频画面篇

  • [1,初步了解OpenGL ES](()
  • [2,使用OpenGL渲染视频画面](()
  • [3,OpenGL渲染多视频,实现画中画](()
  • [4,深入了解OpenGL之EGL](()
  • [5,OpenGL FBO数据缓冲区](()
  • [6,Android音视频硬编码:生成一个MP4](()

三、Android FFmpeg音视频解码篇

  • [1,FFmpeg so库编译](()
  • [2,Android 引入FFmpeg](()
  • [3,Android FFmpeg视频解码播放](()
  • [4,Android FFmpeg+OpenSL ES音频解码播放](()
  • [5,Android FFmpeg+OpenGL ES播放视频](()
  • [6,Android FFmpeg简单合成MP4:视屏解封与重新封装](()
  • [7,Android FFmpeg视频编码](()



本文你可以了解到


使用 GCCCLANG 交叉编译出Android平台可以使用的FFmpeg so库。为了很好的迈出 FFmpeg 开发的第一步,不仅要知其然,更要知其所以然。不仅要知道怎么样能成功编译,更要知道为什么能成功编译。在开始动手之前,建议先通读整篇文章,相信本文定可以让你有所感悟。



一、前言

网上其实已经有很多的关于FFmpeg so库编译的分享,但是大部分都是直接把配置文件的内容贴出来。我想大部分去搜索 「如何编译FFmpeg so库」的人,对交叉编译这个东东都是比较陌生的。

特别对于移动端开发者来说,大部分人大多数时候都是在Java层做开发,很少接触到NDK层的东西。如果直接去看一份交叉编译的配置,估计会很上头。

通常情况下,在一篇FFmpeg编译的文章下面都会有很多的类似「为什么按照楼主的配置还是无法编译成功?」的评论,那为什么人家可以编译成功,我们copy下来却不可以呢?

原因有非常多,大部分其实集中在以下几个方面:


  1. 无脑copy,祈求有一个傻瓜式的配置可以成功编译;
  2. FFmpeg版本和NDK版本很多,每一个版本都可能需要不一样的配置;
  3. 不了解每个配置项的意义,即使好运配置对了, 但是稍微一修改,又无法正常编译了。

为什么FFmpeg让人觉得很难搞?

我想主要是因为迈出第一步就很困难,连so库都编译不出来,后面的都是扯淡了。


二、什么是交叉编译


定义


引自百度百科的定义:交叉编译,是在一个平台上生成另一个平台上的可执行代码。


什么意思呢?说白了,就是在一个机器上生成一个程序,这个程序可以跑在另外一个机器上。举栗:在PC上编译一个apk,这个apk可以跑在Android手机上,这其实就是一个交叉编译的过程。


为什么要交叉编译

我们知道,PC上的软件是直接在PC上编译生成的,那为什么Android上的软件不能在Android上自己编译生成呢?

理论上是可以,但是Android手机上的资源有限啊,在PC上编译一个apk都要那么久,你可以想象在Android手机上编译一个apk要多久吗?或者你能想象在手机上敲代码的情景吗?

那我们会想既然PC上资源那么丰富,那可不可以利用PC来编译出在手机上可以运行的软件呢?

于是,交叉编译出现。


交叉编译需要的什么


编译环境

我们知道PC上的环境和手机上的运行环境是绝然不同的,如果使用PC上的环境直接编译的话,可以想象这个编译出来的App,分分钟就会挂掉。

所以,交叉编译最重要的是,要配置好编译过程中使用到的相关的环境,而这个环境其实就是目标机器(比如Android手机)正在运行的环境。


编译工具链

对于C/C++的编译,通常有两个工具 GCCCLANG

GCC 可能大家都有听说过,这是一个老牌的编译工具,不仅可以编译C/C++,也可以编译Java,Object-C,Go等语言。

CLANG 则是一个效率更高的C/C++编译工具,并且兼容GCC,Google在很早以前就开始建议使用clang进行编译,并且在 ndk 17 以后,把 GCC 移除了,全面推行使用 CLANG


三、如何交叉编译FFmpeg


FFmpeg是什么

鼎鼎大名的FFmpeg,不说在音视频界如雷贯耳,就算一个不开发音视频的开发者也都是略有耳闻。

官方简介


A complete, cross-platform solution to record, convert and stream audio and video.


翻译过来就是:FFmpeg是一套集录制、转换以及流化音视频的完整的跨平台解决方案。

从这段简介可以看到FFmpeg有以下特点:


  1. 功能强大:录制、解码、编码、编辑、推流等等
  2. 跨平台

编译流程

从前面的介绍,基本上可以总结出FFmepg编译的基本流程:


  1. 选择编译工具
  2. 配置交叉编译环境
  3. 配置编译参数(比如去掉一些不需要的功能)
  4. 启动编译

流程就是这么简单,接下来就来详细看看,如何通过 CLANGGCC 两种方式来编译。


四、使用CLANG编译FFmpeg


注:本文编译平台为Mac,建议使用Mac或者Linux进行编译,据说Windows有很多坑。



下载Android NDK

Android 的 NDK 已经迭代了很多版本,在 r17c 以后,Google正式移除 GCC ,不再支持 GCC ,新版本的 NDK 都是使用 CLANG 进行编译。

这里就使用目前最新的 NDK r20b 版本来编译。

NDK 下载地址:[Android-NDK](()


NDK 目录

NDK r20b 目录

最主要的就是这两个路径:

编译工具链目录:
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/include,usr/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=“crossprefix{cross_prefix}crossprefix{ar_default}”
cc_default=“crossprefix{cross_prefix}crossprefix{cc_default}”
cxx_default=“crossprefix{cross_prefix}crossprefix{cxx_default}”
nm_default=“crossprefix{cross_prefix}crossprefix{nm_default}”
pkg_config_default=“crossprefix{cross_prefix}crossprefix{pkg_config_default}”

将中间两行修改为

ar_default=“crossprefix{cross_prefix}crossprefix{ar_default}”
#------------------------------------------------
cc_default=“crossprefixclang{cross_prefix_clang}crossprefixclang{cc_default}”
cxx_default=“crossprefixclang{cross_prefix_clang}crossprefixclang{cxx_default}”
#------------------------------------------------
nm_default=“crossprefix{cross_prefix}crossprefix{nm_default}”
pkg_config_default=“crossprefix{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=‘(SLIBNAME).(SLIBNAME).(SLIBNAME).(LIBVERSION)’
SLIBNAME_WITH_MAJOR=‘(SLIBNAME).(SLIBNAME).(SLIBNAME).(LIBMAJOR)’
LIB_INSTALL_EXTRA_CMD=‘?(RANLIB) “(LIBDIR)/(LIBDIR)/(LIBDIR)/(LIBNAME)”’
SLIB_INSTALL_NAME=‘(SLIBNAMEWITHVERSION)′SLIBINSTALLLINKS=′(SLIBNAME_WITH_VERSION)' SLIB_INSTALL_LINKS='(SLIBNAMEWITHVERSION)SLIBINSTALLLINKS=(SLIBNAME_WITH_MAJOR) $(SLIBNAME)’

修改为:

SLIBNAME_WITH_VERSION=‘(SLIBNAME).(SLIBNAME).(SLIBNAME).(LIBVERSION)’
SLIBNAME_WITH_MAJOR=‘(SLIBPREF)(SLIBPREF)(SLIBPREF)(FULLNAME)-(LIBMAJOR)(LIBMAJOR)(LIBMAJOR)(SLIBSUF)’
LIB_INSTALL_EXTRA_CMD=‘?(RANLIB)“(LIBDIR)/(LIBDIR)/(LIBDIR)/(LIBNAME)”’
SLIB_INSTALL_NAME=‘(SLIBNAMEWITHMAJOR)′SLIBINSTALLLINKS=′(SLIBNAME_WITH_MAJOR)' SLIB_INSTALL_LINKS='(SLIBNAMEWITHMAJOR)SLIBINSTALLLINKS=(SLIBNAME)’

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


  • sysroot

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

r20b 版本的 NDK 系统的头文 《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》无偿开源 徽信搜索公众号【编程进阶路】 件和库文件就是在 $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 OUTDEVLIST;;−−enable−debug=∗)debuglevel="OUTDEV_LIST ;; --enable-debug=*) debuglevel="OUTDEVLIST;;enabledebug=)debuglevel="optval"
;;


省略中间一些代码…

)
optname=“KaTeX parse error: Expected '}', got 'EOF' at end of input: …%=*}" optname="{optname#–}”
optname=(echo"(echo "(echo"optname" | sed ‘s/-/_/g’)
if is_in $optname $CMDLINE_SET; then
tion 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 OUTDEVLIST;;−−enable−debug=∗)debuglevel="OUTDEV_LIST ;; --enable-debug=*) debuglevel="OUTDEVLIST;;enabledebug=)debuglevel="optval"
;;


省略中间一些代码…

*)
optname=“KaTeX parse error: Expected '}', got 'EOF' at end of input: …%=*}" optname="{optname#–}”
optname=(echo"(echo "(echo"optname" | sed ‘s/-/_/g’)
if is_in $optname $CMDLINE_SET; then


推荐阅读
  • 本文讨论了如何使用GStreamer来删除H264格式视频文件中的中间部分,而不需要进行重编码。作者提出了使用gst_element_seek(...)函数来实现这个目标的思路,并提到遇到了一个解决不了的BUG。文章还列举了8个解决方案,希望能够得到更好的思路。 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • ***byte(字节)根据长度转成kb(千字节)和mb(兆字节)**parambytes*return*publicstaticStringbytes2kb(longbytes){ ... [详细]
  • 本文介绍了关于Java异常的八大常见问题,包括异常管理的最佳做法、在try块中定义的变量不能用于catch或finally的原因以及为什么Double.parseDouble(null)和Integer.parseInt(null)会抛出不同的异常。同时指出这些问题是由于不同的开发人员开发所导致的,不值得过多思考。 ... [详细]
  • java实现rstp格式转换使用ffmpeg实现linux命令第一步安装node.js和ffmpeg第二步搭建node.js启动websocket接收服务
    java实现rstp格式转换使用ffmpeg实现linux命令第一步安装node.js和ffmpeg第二步搭建node.js启动websocket接收服务第三步java实现 ... [详细]
  • Android跨进程通信IPC之9——Binder通信机制
    移步系列Android跨进程通信IPC系列1Android整体架构Android系统架构及系统源码目录Android系统架构 ... [详细]
  • 最近在调研基于WebRTC的Simulcast的方案,发现WebRTC1.0的草案里面已经Simulcast的相关定义,并且举出了相关的例子。EXAMPLE15varsignalingChann ... [详细]
author-avatar
哟呼的围脖
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有