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。


目录
相关文章
|
3月前
|
编译器 API Android开发
Android经典实战之Kotlin Multiplatform 中,如何处理不同平台的 API 调用
本文介绍Kotlin Multiplatform (KMP) 中使用 `expect` 和 `actual` 关键字处理多平台API调用的方法。通过共通代码集定义预期API,各平台提供具体实现,编译器确保正确匹配,支持依赖注入、枚举类处理等,实现跨平台代码重用与原生性能。附带示例展示如何定义跨平台函数与类。
100 0
|
24天前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
Android远程连接和登录FTPS服务代码(commons.net库)
22 1
|
29天前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
81 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
1月前
|
存储 开发框架 .NET
.NET 8 实现无实体库表 API 部署服务
【10月更文挑战第12天】在.NET 8中,可通过以下步骤实现无实体库表的API部署:首先安装.NET 8 SDK及开发工具,并选用轻量级Web API框架如ASP.NET Core;接着创建新项目并设计API,利用内存数据结构模拟数据存储;最后配置项目设置并进行测试与部署。此方法适用于小型项目或临时解决方案,但对于大规模应用仍需考虑持久化存储以确保数据可靠性与可扩展性。
|
1月前
|
Ubuntu Shell API
Ubuntu 64系统编译android arm64-v8a 的openssl静态库libssl.a和libcrypto.a
Ubuntu 64系统编译android arm64-v8a 的openssl静态库libssl.a和libcrypto.a
|
2月前
|
JSON 资源调度 JavaScript
Vue框架中Ajax请求的实现方式:使用axios库或fetch API
选择 `axios`还是 `fetch`取决于项目需求和个人偏好。`axios`提供了更丰富的API和更灵活的错误处理方式,适用于需要复杂请求配置的场景。而 `fetch`作为现代浏览器的原生API,使用起来更为简洁,但在旧浏览器兼容性和某些高级特性上可能略显不足。无论选择哪种方式,它们都能有效地在Vue应用中实现Ajax请求的功能。
41 4
|
3月前
|
编解码 测试技术 Android开发
Android经典实战之用 CameraX 库实现高质量的照片和视频拍摄功能
本文详细介绍了如何利用CameraX库实现高质量的照片及视频拍摄功能,包括添加依赖、初始化、权限请求、配置预览与捕获等关键步骤。此外,还特别针对不同分辨率和帧率的视频拍摄提供了性能优化策略,确保应用既高效又稳定。
307 1
Android经典实战之用 CameraX 库实现高质量的照片和视频拍摄功能
|
3月前
|
监控 Java API
Android经典实战之OkDownload:一个经典强大的文件下载开源库,支持断点续传
本文介绍的 OkDownload 是一个专为 Android 设计的开源下载框架,支持多线程下载、断点续传和任务队列管理等功能,具备可靠性、灵活性和高性能特点。它提供了多种配置选项和监听器,便于开发者集成和扩展。尽管已多年未更新,但依然适用于大多数文件下载需求。
350 1
|
3月前
|
API Android开发
Android项目架构设计问题之选择和使用合适的UI库如何解决
Android项目架构设计问题之选择和使用合适的UI库如何解决
48 0
|
3月前
|
API 数据库 索引
indexedDB 操作库IDBWRAPPER 教程翻译及API翻译第二部分part2
indexedDB 操作库IDBWRAPPER 教程翻译及API翻译第二部分part2