FFmpeg与 Android NDK平台M1交叉编译记录
theme: mk-cute highlight: atelier-estuary-dark
FFmpeg与 Android NDK平台M1交叉编译记录
一.前言
交叉编译是在一个平台上生成另一个平台上的可执行代码。
本文所描述的,就是在Mac OS M1平台下,交叉编译的记录,最终的目标是,让ffmpeg-release/5.0版本编译出适合Android ARM64-v8a运行的产物,让FFMpeg在Android上运行起来。
本文只做一些记录。整个过程中,其实和是不是M1没啥关系,只不过NDK24加入了对M1的支持:
你需要以下的环境和工具:
- 基于M1芯片的MacOS;
- GitHub - FFmpeg/FFmpeg: Mirror of http://git.ffmpeg.org/ffmpeg.git
- NDK 下载 | Android NDK | Android Developers (google.cn)
- 一个用于测试的mp4文件
首先看看NDK,这玩意下载出来是一个dmg文件,其实我们需要的是其中的NDK和其文件夹下的东西,所以我们需要打开这个DMG文件,在里面的AndroidNDK821888应用图标上,右键显示包内容,将Contents中的NDK文件夹整个移动出来,整个便是我们需要的NDK工具。
二. 环境预配置
我们接下来要做的,就是在FFmpeg文件夹下,指定编译器和编译平台,为Android编译可用的FFmpeg,在.zshrc或者.bashrc下,设置NDK_HOME变量:
export NDK_HOME=~/sdk/NDK/
完成后,source .zshrc一下,在终端中输入:
echo $NDK_HOME
如果正确输出了路径,那么就配置完成了。当然,你不配也行,在后面脚本中直接固定写NDK路径也是一样的。
接着,我们来到FFmpeg源代码文件夹,并且使用:
git checkout release/5.0
切换到我们的目标分支。
三. 编写编译脚本
我们需要配置编译的平台、指定目标平台的一些架构信息。
在FFmpeg根目录下,新建一个build_android.sh
文件,然后打开,键入:
```
!/bin/bash
NDK的路径 (在bash.rc、zshrc中设置的路径,其中的NDK_HOME需要配置环境变量,如果你不配,在这改成你自己的也可以。)
NDK_ROOT=$NDK_HOME
NDK_ROOT=你的NDK路径
TOOLCHAIN_PREFIX=$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64 echo "<<<<<< FFMPEG 交叉编译 <<<<<<" echo "<<<<<< 基于当前系统NDK地址: $NDK_HOME <<<<<<"
编译的相关参数
目标平台的CPU指令类型 ARMv8
CPU=armv8-a
架构类型 : ARM
ARCH=arm64
操作系统
OS=android
平台
PLATFORM=aarch64-linux-android OPTIMIZE_CFLAGS="-march=$CPU"
指定输出路径
PREFIX=~/tools/outputs/ffmpeg/aarch64
SYSROOT
SYSROOT=$TOOLCHAIN_PREFIX/sysroot
交叉编译工具链
CROSS_PREFIX=$TOOLCHAIN_PREFIX/bin/llvm-
Android交叉编译工具链的位置
ANDROID_CROSS_PREFIX=$TOOLCHAIN_PREFIX/bin/${PLATFORM}29 echo ">>>>>> FFMPEG 开始编译 >>>>>>" ./configure \ --prefix=$PREFIX \ --enable-shared \ --enable-gpl \ --enable-neon \ --enable-hwaccels \ --enable-postproc \ --enable-jni \ --enable-small \ --enable-mediacodec \ --enable-decoder=h264_mediacodec \ --enable-ffmpeg \ --disable-ffplay \ --disable-ffprobe \ --disable-ffplay \ --disable-avdevice \ --disable-debug \ --disable-static \ --disable-doc \ --disable-symver \ --cross-prefix=$CROSS_PREFIX \ --target-os=$OS \ --arch=$ARCH \ --cpu=$CPU \ --cc=${ANDROID_CROSS_PREFIX}-clang \ --cxx=${ANDROID_CROSS_PREFIX}-clang++ \ --enable-cross-compile \ --sysroot=$SYSROOT \ --extra-cflags="-Os -fPIC $OPTIMIZE_CFLAGS" \ --extra-ldflags="$ADDI_LDFLAGS" \ make clean
创建目标路径,如果不存在的话,最终产物存储在Prefix对应路径之下。
mkdir -p $PREFIX sudo make -j8 sudo make install echo "<<<<<< 编译完成,产物存储在:$PREFIX <<<<<<" ```
然后直接在终端执行该脚本即可开始编译,过程大概五到十分钟,如果权限不够,需要使用:
chmod 777 build_android.sh
为build_android.sh加上执行权限。
最终的产物,会存在上述脚本的.sh目录之下,我们需要的是libs文件夹中的一些.so文件:
四. Android端测试
首先我们先新建一个Android Module:ffmpeg,然后将刚才生成的所有.so文件放入如下文件夹:ffmpeg/src/main/cpp/libs/arm64-v8a
:
然后编辑ffmpeg模块下的build.gradle:
android {
compileSdk 32
defaultConfig {
……
ndk {
abiFilters "arm64-v8a"
}
}
……
因为是测试,我们只保留arm64-v8a一种型号的设备,有需要可以自己按照前面的步骤去编译x86或者是armeabi-v7a等等。
编辑CMakeLists.txt,删掉最后一行的target_link_libraries
的调用,并在最后追加:
``` set(JNI_LIBS_DIR ${CMAKE_CURRENT_SOURCE_DIR})
引入一些头文件,这里填你FFmpeg源码存在的路径,主要是引入一些头文件,编译时要使用。
include_directories(~/tools/FFmpeg) add_library(avutil SHARED IMPORTED) set_target_properties(avutil PROPERTIES IMPORTED_LOCATION ${JNI_LIBS_DIR}/libs/${ANDROID_ABI}/libavutil.so) add_library(swresample SHARED IMPORTED) set_target_properties(swresample PROPERTIES IMPORTED_LOCATION ${JNI_LIBS_DIR}/libs/${ANDROID_ABI}/libswresample.so) add_library(swscale SHARED IMPORTED) set_target_properties(swscale PROPERTIES IMPORTED_LOCATION ${JNI_LIBS_DIR}/libs/${ANDROID_ABI}/libswscale.so) add_library(avcodec SHARED IMPORTED) set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION ${JNI_LIBS_DIR}/libs/${ANDROID_ABI}/libavcodec.so) add_library(avformat SHARED IMPORTED) set_target_properties(avformat PROPERTIES IMPORTED_LOCATION ${JNI_LIBS_DIR}/libs/${ANDROID_ABI}/libavformat.so) add_library(avfilter SHARED IMPORTED) set_target_properties(avfilter PROPERTIES IMPORTED_LOCATION ${JNI_LIBS_DIR}/libs/${ANDROID_ABI}/libavfilter.so) add_library(postproc SHARED IMPORTED) set_target_properties(postproc PROPERTIES IMPORTED_LOCATION ${JNI_LIBS_DIR}/libs/${ANDROID_ABI}/libpostproc.so) target_link_libraries(ffmpeg avutil swresample swscale avcodec avformat avfilter postproc ${log-lib} ${android-lib} ${log}) ```
然后在自带的NativeLib.java中,编写一个方法:
// 定义一个方法获取Path对应视频的FFmpegContext
external fun getFFmpegContext(path:String):String
然后按住options+enter
,在ffmpeg.cpp中自动生成对应的cpp方法,编辑ffmpeg.cpp:
```
include
include
include
include
include
include
extern "C" {
include
include
include
include
include
extern "C" JNIEXPORT jstring JNICALL Java_com_example_ffmpeg_NativeLib_stringFromJNI( JNIEnv env, jobject / this /) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); } extern "C" JNIEXPORT jstring JNICALL Java_com_example_ffmpeg_NativeLib_getFFmpegContext(JNIEnv env, jobject thiz, jstring path) { AVFormatContext fmt_ctx = avformat_alloc_context(); const char str = env->GetStringUTFChars(path, 0); int ret = avformat_open_input(&fmt_ctx, str, nullptr, nullptr); if (ret < 0) { __android_log_print(ANDROID_LOG_ERROR, "FFmpeg Error", "Cannot open %s ,cause %s\n", str, av_err2str(ret)); goto __finally; } __finally: avformat_close_input(&fmt_ctx); env->ReleaseStringUTFChars(path, str); return env->NewStringUTF("complete!"); } } ```
因为是一个测试,所以我们直接在app的MainActivity中调用该FFMpeg方法,其中的hftw.mp4是一个视频文件,你需要将它手动放到/data/user/0/com.example.ffmpegcrosscomplieproj/cache/hftw.mp4文件夹下,你可以使用Android Studio界面边缘的Device File Explorer上传一个MP4视频文件。
这个路径你可以自定义,然后传给JNI。只要保证App能够取到即可。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val path = applicationContext.cacheDir.absolutePath + "/hftw.mp4"
Log.i("Storage Environment", path)
NativeLib().getFFmpegContext(path)
}
}
最后,我们在app模块的build.gradle引入对我们自建的ffmpeg module的引用:
implementation project(":ffmpeg")
完成后,我们在ffmpeg.cpp的
if (ret < 0) {
这一行打上断点,程序以Debug模式启动之后,在该位置停下,我们查看fmt_ctx的内容,如果有数据,就说明FFmpeg读取文件生效了,读出的时长是正确的。
~ end
- [Flutter] 认识Zone和异常处理
- 细说Android的Launch Mode
- FFmpeg与 Android NDK平台M1交叉编译记录
- 音视频与ffmpeg入门(一) 声音与声音采样
- JavaScript中对事件的三种监听方式
- Query类操作是什么意思?
- linux网络配置相关文件
- Linux云计算-01_介绍以及Linux操作系统安装
- OCP 071【中文】考试题库(cuug整理)第40题
- 游戏CSS的特性叠性怎么去做?
- CSS的特性之层叠性介绍
- Graphviz--开源可视化工具(决策树等图结构)
- 用编程js语言实现简易红绿灯
- 【PaddlePaddle百度飞桨领航团零基础图像分类速成营】初识图像处理
- 一文带你彻底搞懂Linux 文件权限管理
- HTML及CSS笔记
- 开源项目的版权声明已无存在必要?
- Elasticsearch curl -XGET 'localhost:9200/_cluster/health?pretty' 返回yellow
- API和微服务网关工具的价值
- 利用ArcGIS和Python计算中国各省路网密度