最近在开发一款APP,因为使用了FFmpeg库,所以需要将FFmpeg交叉编译以便应用于Android平台上。在开始编译之前,我个人认为有必要了解一下FFmpeg编译过程中的一些基础知识,毕竟按照我的教程只是依葫芦画瓢,知其然知其所以然,才可以更好地进阶Android开发。
1.JNI
JNI 全称 Java Native Interface,Java 本地化接口,可以通过 JNI 调用系统提供的 API。操作系统,无论是 Linux,Windows 还是 Mac OS,或者一些汇编语言写的底层硬件驱动都是 C/C++ 写的。Java和C/C++不同 ,它不会直接编译成平台机器码,而是编译成虚拟机可以运行的Java字节码的.class文件,通过JIT技术即时编译成本地机器码,所以有效率就比不上C/C++代码,JNI技术就解决了这一痛点。JNI 可以说是 C 语言和 Java 语言交流的适配器、中间件,即通过JNI,Java 和 其他语言之间可以相互调用,不止局限于 Java 调用其他语言,其他语言也可以主动调用 Java 。下面我们来看看JNI调用示意图:
从上图可以得知,JNI技术通过JVM调用到各个平台的API,虽然JNI可以调用C/C++ ,但是JNI调用还是比C/C++编写的原生应用还是要慢一点,不过对高性能计算来说,这点算不得什么,享受它的便利,也要承担它的弊端 。
Java 虚拟机实现了跨平台特性, 无法很好的实现与操作系统相关的本地操作,而 C 或 C++ 可以,同时代表着 C 或 C++ 不具备 Java 的跨平台能力。那么当我们在程序中使用 JNI 功能时,就必须关注程序的平台可移植性, JNI 标准要求本地代码至少能工作在任何Java 虚拟机环境。简而言之,跨平台的 Java 调用不了跨平台的 C/C++,使程序丧失了跨平台性,这就是 JNI 的副作用。而大多数不可避免的情况是:已存在用 C/C++ 写的程序/库或者 Java 语言不支持程序所要实现的特性,比如 ffmpeg 是由 C 编写的,则必须要通过 JNI 实现调用。
JNI作用
- 扩展:JNI扩展了JVM能力,驱动开发,例如开发一个wifi驱动,可以将手机设置为无限路由;
- 高效: 本地代码效率高,游戏渲染,音频视频处理等方面使用JNI调用本地代码,C语言可以灵活操作内存;
- 复用: 在文件压缩算法 7zip开源代码库,机器视觉 OpenCV开放算法库等方面可以复用C平台上的代码,不必在开发一套完整的Java体系,避免重复发明轮子;
- 特殊: 产品的核心技术一般也采用JNI开发,不易破解;
JNI 的实现步骤(具体操作会在后面章节进行介绍)
- 编写带有 native 方法的 Java 类
- 生成该类扩展名为 .h 的头文件
- 创建该头文件的 C/C++ 文件,实现 native 方法
- 将该 C/C++ 文件编译成动态链接库
- 在Java 程序中加载该动态链接库
动态链接库是一组源代码的模块,其中包含可供应用程序调用的函数。比如 Windows 下的 .dll 文件就是一种动态链接库,也就是说 Java 程序在 Windows 中运行,所需的动态链接库就是 .dll 文件; 如果 Java 程序在 Linux 中运行,所需的动态链接库就是 .so 文件 ,这里 JNI 的副作用已初见端倪,本来无视操作系统的 Java ,因为 JNI ,就要考虑运行环境是 Windows 还是 Linux 。除此之外,还要考虑 CPU 架构,这也是 Android 中使用 JNI 主要需考虑的 so 库兼容型问题。
Android是Linux发行版本,所以所需要的动态链接库就是 .so 文件。
对于 Java 程序来说,需要的仅仅是编译后的动态链接库,不需要 C/C++ 文件和 .h 头文件。Android 亦如此,网上很多 Android NDK 教程会把需要编译的 C/C++ 源码放入 Android 工程中,这对新手来说可能会产生误导,误以为 Android 工程需要 C/C++ 文件或 .h 头文件或者其他的文件,要清楚的是, Android 工程需要的仅仅是编译后的 .so 库,所以我们可以在工程之外编译完后,只将 .so 库移植到工程中。那为什么大多数教程会把源码先移植到 Android 工程中再去编译呢?目的只有一个:节省目录的切换以及 .so 库的复制时间,实际上这些时间微乎其微,我推荐新手将编译操作于工程外进行,更易理解。
2. CPU架构
我们都知道 CPU 是什么,那 CPU 架构到底是什么呢?回归到“架构”这个词本身含义,CPU 架构就是 CPU 的框架结构、设计方案,处理器厂商以某种架构为基础,生产自己的 CPU,就好比“总-分-总”是文章的一种架构,多篇文章可以都基于“总-分-总”架构。
常见的 CPU 架构有 x86、x86-64 以及 arm 等, x86-64 其实也是基于 x86 架构,只是在 x86 的基础上做了一些扩展,以支持 64 位程序的应用,常见的 Intel 、AMD 处理器都是基于 x86 架构的。
而 x86 架构主打的是 pc 端,对于移动端,arm 架构处于霸主地位 ,由于其体积小、低功耗、低成本、高性能的优点,被广泛应用在嵌入式系统中,目前大多数安卓、苹果手机的 CPU 都基于 arm 架构,此处所说的 arm 架构指 arm 系列架构,其中包括 ARMv5 、ARMv7 等等。
最后再看 Android 端 , Android 系统目前支持 ARMv5、ARMv7、ARMv8、 x86 、x86_64、MIPS 以及 MIPS64 共七种 CPU 架构,也就是说除此之外其他 CPU 架构的硬件并不能运行 Android 系统。
3. 交叉编译
在某个平台上,编译该平台的可执行程序,叫做本地编译,比如在 Windows 平台上编译 Windows 自身的可执行程序;在 x86 平台上,编译 x86 平台自身的可执行程序。
在某个平台上,编译另一种平台的可执行程序,就是交叉编译,比如在 x86 平台上,编译 arm 平台的可执行程序,这也是 Android 端使用最多的交叉编译类型。
在交叉编译时,由于主机与目标的体系架构、环境不同,所以交叉编译比本地编译复杂很多,需要一些工具来解决主机与目标不同特性的问题,这些工具构成的工具集就叫做交叉编译链。
既然交叉编译比本地复杂很多,那为什么不使用本地编译,比如在 arm 平台编译 arm 平台的可执行程序呢?这是因为目标平台存储空间和计算能力通常是有限的,而编译过程需要较大的存储空间和较快的计算能力,但目标平台无法提供。
4. NDK
我们需要的是 arm 平台的动态库,而这一编译过程往往是在 x86 平台上进行,所以属于交叉编译,需要交叉编译链来实现,所以 NDK(Native Development Kit )中提供了交叉编译链,方便开发。
Android 中包括七种 CPU 架构,NDK 中自然就有与之对应的交叉编译链,以下是 Android 官网对此的表格描述:
除此之外,NDK 还提供了一些原生标头和共享库文件,包括 C/C++ 支持库、从 C/C++ 代码中可以向 Android 系统输出日志的 < android/log.h > 等等,总之,NDK 是用来帮助我们实现交叉编译的工具。
在实际使用时,比较重要的是 Android.mk 语法,内容并不多,但你必须了解,不然只复制别人的配置很容易出错,关键是你无法真正的掌握这部分知识,而最好的学习方法就是仔细阅读几遍 Android.mk 官网教程 。
另外还需要了解什么是 ABI ,ABI 即 application binary interface ,应用程序二进制接口,顾名思义,“二进制接口”说明这是程序与系统之间的底层接口,它定义了程序如何与系统交互。我们应该指定每个 CPU 架构所对应的 ABI,所以 Android 中就出现了 armeabi 、armeabi-v7a、arm64-v8a、x86、x86_64、mips 以及 mips64 目录来区分不同的 ABI ,我们将编译好的动态库放入对应 CPU 架构的 ABI 目录中就可以了。
JNI与NDK区别
- JNI:JNI是一套编程接口,用来实现Java代码与本地的C/C++代码进行交互;
- NDK: NDK是Google开发的一套开发和编译工具集,可以生成动态链接库,主要用于Android的JNI开发
5. FFmpeg
FFmpeg是一个自由软件,可以运行音频和视频多种格式的录影、转换、流功能,包含了libavcodec ─这是一个用于多个项目中音频和视频的解码器库,以及libavformat——一个音频与视频格式转换库。
- FFmpeg项目由以下几部分组成:
- FFmpeg视频文件转换命令行工具,也支持经过实时电视卡抓取和编码成视频文件;
- ffserver基于HTTP、RTSP用于实时广播的多媒体服务器.也支持时间平移;
- ffplay用 SDL和FFmpeg库开发的一个简单的媒体播放器;
- libavcodec一个包含了所有FFmpeg音视频编×××的库。为了保证最优性能和高可复用性,大多数编×××从头开发的;
- libavformat一个包含了所有的普通音视格式的解析器和产生器的库。
掌握了以上知识点,才能知道 Android 集成 FFmpeg本质上是在做什么,为什么要这样做。不只是集成 FFmpeg,这些知识对于任何底层库的集成都是通用、必要的。
参考文献:
JNI开发系列①JNI概念及开发流程 https://www.jianshu.com/p/ac00d59993aa
Android FFmpeg JNI开发入门_编译Android的so库 https://www.jianshu.com/p/8321a7a9b1bd
本文介绍了Android开发中使用FFmpeg的背景,详细阐述了JNI的作用、CPU架构、交叉编译和NDK的相关知识,为Android平台上的FFmpeg编译奠定基础。讲解了JNI作为Java和C/C++交互的桥梁,以及在Android开发中使用NDK进行交叉编译的原因和过程。
&spm=1001.2101.3001.5002&articleId=104723005&d=1&t=3&u=084fb77084f74ad5ad31f1881e84d97d)

被折叠的 条评论
为什么被折叠?



