Android使用FFmpeg的API库

简介: Android使用FFmpeg的API库

Java 可以通过 JNI 调用原生库中的函数,原生库完全是由原生的 CPU 指令堆叠起来的,所以运行速度很快。大部分的原生库都是用 C/C++ 编译出来的。

因此,Android 里面也能通过 JNI 的方式调用 so 动态库,或者 .a 静态库里面的函数。JNI 的全称是 Java Native Interface,Java 原生接口。

Android 提供了一套 NDK 工具,来编译使用 C/C++ 代码。有关 NDK,JNI 相关的资料,本文不进行讲解,推荐阅读以下资料。

1,《Java Native Interface: Programmer's Guide and Specification》- Sheng Liang

2,《Android Studio添加C/C++代码

3,《NDK 使用入门》,这是官方文档。

4,《细说Android 4.0 NDK编程》- 王家林


掌握 JNI/NDK 基本概念之后,现在就来实战巩固一下,引入 ffmpeg 的动态库给之前的 Hello app 使用,相关代码可在 Github 下载。

首先我们需要使用 Android Studio 里面的 NDK 的 clang 编译器来编译 ffmpeg 的代码,编译出来 so 动态库。本文采用的 ffmpeg 版本是 n4.4.1。

如果没有安装 NDK,可以点击 Tools > SDK Manager 进行安装,如下:

安装完成之后,会在 SDK 的安装目录上看到这个 clang.exe 编译器,如下:

本文使用的 NDK 版本是 23.1,在进行 NDK 开发的时候,谷歌推荐使用 clang 编译器来编译 C/C++ 代码,而不是 gcc 或者 msvc。


由于我们是在 Windows10 系统上编译 C/C++ 代码,生成手机上运行的 CPU 指令,这是交叉编译,也叫跨平台编译,所以需要指定 --target,指定生成那种 CPU 架构的指令集,同时也要指定 Android 的 API 级别。

手机的 CPU 架构可以用命令查看,先通过 adb 链接上手机,然后执行 cat /proc/cpuinfo 命令,如下:

cd C:\Users\loken\AppData\Local\Android\Sdk\platform-tools
adb.exe shell
cat /proc/cpuinfo

可以看到,CPU 架构是 8 ,那 8 代表什么呢? 8 代表这个 CPU 是 ARMv8 的架构,暂时可以把 aarch64 跟 ARMv8 看成是一个东西。

我的手机是 Redmi 5 Plus,Android 版本 8.1.0,对应的 API 级别是 27。读者可以在《SDK 平台版本说明》查阅自己手机的 Android 版本对应的 API 级别。因此本文的设备是 aarch64 + API27

Android Studio 已经提前弄好一些 shell 跟 batch 脚本给我们用,如下:

cmd 后缀是用在 windows 命令行的,没有 cmd 的是 shell 脚本,用在 Linux 的命令行。

这个 aarch64-linux-android27-clang 其实就是一个 shell 脚本,里面也是调的 clang.exe 编译器,只是这个 shell 脚本预设了一些参数,它的部分内容如下:

`dirname $0`/clang.exe --target=aarch64-linux-android27 "$@"

我们不需要用到这个 shell 脚本,只是参考一下他的 --target 参数。


下面我们打开 msys2 的 shell 窗口,这里直接使用下面的命令打开即可,不需要继承 vs2019 的环境变量,因为不需要用到 msvc。

cd C:\msys64
.\msys2_shell.cmd -mingw64

在 msys2 环境下,安装一些 必要的软件:

# 刷新软件包数据
pacman -Sy  
# 安装mingw-w64。
pacman -S mingw-w64-x86_64-toolchain
pacman -S git
pacman -S make
pacman -S automake 
pacman -S autoconf
pacman -S perl
pacman -S mingw-w64-x86_64-SDL2
pacman -S libtool
pacman -S mingw-w64-x86_64-cmake
pacman -S pkg-config 
pacman -S yasm
pacman -S diffutils
# 编译x264 需要 nasm
pacman -S nasm

然后上 Github 下载 FFmpeg-n4.4.1.zip 代码,放到 下图中的目录,这样 msys2 环境也能找到。


然后我们需要设置一下 PATH 环境变量,让 clang.exe 编译器能被找到,命令如下:

export PATH=$PATH:/C/Users/loken/AppData/Local/Android/Sdk/ndk/23.1.7779620/toolchains/llvm/prebuilt/windows-x86_64/bin

进入 FFmpeg-n4.4.1 目录,开始编译,命令如下:

cd /home/loken/ffmpeg/ffmpeg-n4.4.1
./configure \
--prefix=/home/loken/ffmpeg/build64/ffmpeg-n4.4.1-android \
--target-os=android \
--arch=arm64  \
--cpu=armv8-a \
--cc=clang \
--cxx=clang++ \
--strip=llvm-strip \
--extra-cflags="--target=aarch64-linux-android27" \
--extra-ldflags="--target=aarch64-linux-android27" \
--extra-cflags="-Os -fpic -march=armv8-a" \
--disable-asm \
--disable-strip \
--disable-doc \
--disable-ffplay \
--disable-ffprobe \
--disable-symver \
--disable-ffmpeg \
--enable-neon \
--enable-cross-compile \
--enable-shared
make -j8
make install

上面的命令有一个重点,就是无论是编译阶段,还是链接阶段,都需要加上 --target=aarch64-linux-android27,要不会报错的。

网上有些文章说要指定 --sysroot 的路径,但是好像我不指定也没问题。

上面的命令执行完之后,ffmpeg-n4.4.1-android 目录的结构如下:


so 动态库编译出来之后,就可以在 Android Studio 里面使用了。

先在原来的 Hello 项目里面 main 目录下创建一个 jinLibs 文件夹,然后再创建一个 arm64-v8a 文件夹,如下:

然后把之前生成的 so 库全部拷贝到 arm64-v8a 目录,如下:

要使用这些动态库里面的函数,还需要有头文件,头文件我们不需要一个一个拷贝,之前 make install 的时候,已经把所需要的头文件拷贝到 build64/ffmpeg-n4.4.1-android 的 include 目录下,如下:

现在我们在把 build64/ffmpeg-n4.4.1-android 的 include 目录拷贝到 Hello app 项目的 cpp 目录下,通常 Android 项目是没有 cpp 目录的,所以我们需要右键 app 目录,点击 Add C++ to Module,如下:

这样操作,Android Studio 就会帮我们生成 cpp 目录,以及相关的 CMakeLists.txthello.cpp

提醒:这些 cpp 目录以及文件,是可以自己手动添加的,但是我试了一下,手动添加完之后,Android Studio 没有加载出来,可能是有个配置文件负责管理的,所以保险起见,还是用 Add C++ to Module 这个功能。


现在我们在把 build64/ffmpeg-n4.4.1-android 的 include 目录拷贝到 Hello app 项目的 cpp 目录,如下:

现在我们需要对 CMakeLists.txt 做一下修改,把 FFmpeg 的 so 库引入进来,重点的修改如下:

然后再修改一下 hello.cpp 的代码,调用一下 so 库里面的一个版本函数,代码如下:

#include <jni.h>
#include <string>
#include <unistd.h>
extern "C" {
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libavfilter/avfilter.h>
    #include <libavcodec/jni.h>
    JNIEXPORT jstring JNICALL
    Java_com_example_hello_FirstActivity_ffmpegVersion(JNIEnv *env, jobject  /* this */) {
        unsigned ver = avformat_version();
        char info[40000] = {0};
        sprintf(info, "avformat_version %u:", ver);
        return env->NewStringUTF(info);
    }
}

然后我们就可以在 FirstActivity 里面调用这个 ffmpegVersion() 函数了,如下:

上图中,我把 avformat 库的版本号加在后面了,然后直接编译项目把 apk 安装到手机,整个过程如果没有报错,就会生成 app-debug.apk 文件,如下:

Android Studio 提供了一个 apkanalyzer.exe 工具来分析 apk 文件,我们可以分析一下 apk,如下:

可以看到,so 库已经打包进去 apk 里面了。

手机上的运行效果如下:

Jni 完整的项目代码可以点击 helloJni 进行下载。


官网文档的 两篇文章 说要用 Android.mk 跟 Application.mk 来引入外部的库,但是我没看懂这两篇文章,貌似不创建这两个文件也可以。


本文只是做了一些简单的介绍,把 FFmpeg 的库编译进去 apk,然后调用一个函数。关于 NDK开发,料推荐阅读以下书籍 深入学习

1,《Android C++高级编程-使用NDK》

2,《细说Android 4.0 NDK编程》


结尾,分享一些本人的学习技巧,本文是参考《Android FFmpeg 编译和集成》一文进行写作的,这篇文章里面创建了一个 build_android_clang.sh 脚本,然后定义一些变量传递进去,如下:

我能看懂大部分的 shell 以及 batch(cmd)代码,我看了一下这个 shell,为了降低本文的复杂度,所以我没有创建新的 shell 脚本,而是直接把参数写在 configure 上了。

再回到之前的 Android Studio 提供的 aarch64-linux-android27-clang 脚本。

`dirname $0`/clang.exe --target=aarch64-linux-android27 "$@"

这个脚本实际上就是给 clang.exe 编译器定义一个别名,这个别名会附加上 --target 的参数。

由于 clang.exe 会被调用两次,一次是编译的时候,一次是链接的时候,所以当把 参数往 configure 上丢的时候,需要丢两次,所以就有了下面的代码:

./configure \
...省略...
--extra-cflags="--target=aarch64-linux-android27" \
--extra-ldflags="--target=aarch64-linux-android27" \
...省略...

上面的命令,--extra-cflags 是往编译阶段传递参数,--extra-ldflags 是往链接阶段传递参数。

本书的《FFmpeg编译过程分析》一章专门分析 configure 的代码。


然后,我没有使用 Ubuntu 或者 Mac 来编译 FFmpeg 的动态库,虽然也可以。我平时是在 Windows 上开发的,所以直接就采用 msys2 了,在前面的文章《用msys2与msvc编译FFmpeg》里,msys2 可以调 vs2019 的 msvc 编译器 cl.exe ,同理 msys2 肯定也能调 Android Studio 的 NDK 编译器 clang.exe,这些东西都是可以举一反三串联起来的。

最后,FFmpeg 官方的 configure 脚本非常值得称赞,这个脚本使得 很多选项可以配置,例如可以指定编译器,链接器,strip ,等等。因此 configure 的通用性非常强,在各种平台都能跑。Windows 可以用 msys2 来执行 shell 脚本,其他类 Linux 平台本身就支持 shell。


目录
相关文章
|
13天前
|
编译器 API Android开发
Android经典实战之Kotlin Multiplatform 中,如何处理不同平台的 API 调用
本文介绍Kotlin Multiplatform (KMP) 中使用 `expect` 和 `actual` 关键字处理多平台API调用的方法。通过共通代码集定义预期API,各平台提供具体实现,编译器确保正确匹配,支持依赖注入、枚举类处理等,实现跨平台代码重用与原生性能。附带示例展示如何定义跨平台函数与类。
43 0
|
9天前
|
编解码 测试技术 Android开发
Android经典实战之用 CameraX 库实现高质量的照片和视频拍摄功能
本文详细介绍了如何利用CameraX库实现高质量的照片及视频拍摄功能,包括添加依赖、初始化、权限请求、配置预览与捕获等关键步骤。此外,还特别针对不同分辨率和帧率的视频拍摄提供了性能优化策略,确保应用既高效又稳定。
36 1
Android经典实战之用 CameraX 库实现高质量的照片和视频拍摄功能
|
1天前
|
监控 Java API
Android经典实战之OkDownload:一个经典强大的文件下载开源库,支持断点续传
本文介绍的 OkDownload 是一个专为 Android 设计的开源下载框架,支持多线程下载、断点续传和任务队列管理等功能,具备可靠性、灵活性和高性能特点。它提供了多种配置选项和监听器,便于开发者集成和扩展。尽管已多年未更新,但依然适用于大多数文件下载需求。
11 1
|
10天前
|
API Android开发
Android项目架构设计问题之选择和使用合适的UI库如何解决
Android项目架构设计问题之选择和使用合适的UI库如何解决
21 0
|
22天前
|
API 数据库 索引
indexedDB 操作库IDBWRAPPER 教程翻译及API翻译第二部分part2
indexedDB 操作库IDBWRAPPER 教程翻译及API翻译第二部分part2
|
28天前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
很多文章都介绍了FTPClient如何连接ftp服务器,但却很少有人说如何连接一台开了SSL认证的ftp服务器,现在代码来了。
61 2
|
7天前
|
JSON API 网络架构
【Azure Developer】Azure REST API: 如何通过 API查看 Recovery Services Vaults(恢复保管库)的备份策略信息? 如备份中是否含有虚拟机的Disk
【Azure Developer】Azure REST API: 如何通过 API查看 Recovery Services Vaults(恢复保管库)的备份策略信息? 如备份中是否含有虚拟机的Disk
|
9天前
|
测试技术 API Android开发
Android经典实战之简化 Android 相机开发:CameraX 库的全面解析
CameraX是Android Jetpack的一个组件,旨在简化相机应用开发,提供了易于使用的API并支持从Android 5.0(API级别21)起的设备。其主要特性包括广泛的设备兼容性、简洁的API、生命周期感知、简化实现及方便的集成与测试。通过简单的几个步骤即可实现如拍照、视频录制等功能。此外,还提供了最佳实践指导以确保应用的稳定性和性能。
23 0
|
2月前
|
存储 数据库 Android开发
🔥Android Jetpack全解析!拥抱Google官方库,让你的开发之旅更加顺畅无阻!🚀
【7月更文挑战第28天】在Android开发中追求高效稳定的路径?Android Jetpack作为Google官方库集合,是你的理想选择。它包含多个独立又协同工作的库,覆盖UI到安全性等多个领域,旨在减少样板代码,提高开发效率与应用质量。Jetpack核心组件如LiveData、ViewModel、Room等简化了数据绑定、状态保存及数据库操作。引入Jetpack只需在`build.gradle`中添加依赖。例如,使用Room进行数据库操作变得异常简单,从定义实体到实现CRUD操作,一切尽在掌握之中。拥抱Jetpack,提升开发效率,构建高质量应用!
47 4
|
10天前
|
Java Android开发 开发者
Android项目架构设计问题之使用Retrofit2作为网络库如何解决
Android项目架构设计问题之使用Retrofit2作为网络库如何解决
22 0