ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Win10下NDK r22编译FFmpeg arm64-v8a动静态库完整实践

Win10下NDK r22编译FFmpeg arm64-v8a动静态库完整实践 简介本资源是面向Android音视频开发者的FFmpeg交叉编译实践成果聚焦Win10环境下基于Android NDK r22构建arm64-v8a架构的完整FFmpeg库体系解决移动端集成FFmpeg时常见的ABI适配与静态/动态链接难题。压缩包含172个文件总大小30.87MB涵盖6个核心静态库.a、6个对应动态库.so、6个pkg-config配置文件.pc、129个头文件.h及23个C源码.c辅以Makefile构建脚本与README说明结构清晰、即拿即用。已有488人学习下载适用于需在Android Studio中集成原生FFmpeg能力的中级开发者尤其适合音视频转码如AAC/MP4、封装解封装、滤镜处理等典型场景。读者可直接引用头文件与库文件完成JNI调用避免重复编译耗时同时通过源码与配置文件反向理解NDK交叉编译链路设计逻辑。 搞Android音视频开发的人迟早都要自己动手编译一次FFmpeg。网上讲编译的文章不少但大多是在Linux或macOS下操作真正在Win10上用NDK r22编译arm64-v8a动静态库的完整记录反而不多。这篇笔记就是我自己在项目里折腾出来的实践总结把从环境准备、脚本配置到踩坑排错的全过程都捋了一遍希望对要在Windows上给Android交叉编译FFmpeg的你有帮助。先说清楚这篇笔记的边界平台锁定Win10NDK用r22目标架构是arm64-v8a产物是FFmpeg的动态库(.so)和静态库(.a)。如果你用的是更新的NDK版本或者编译的是armeabi-v7a整体流程一样但个别细节会有差异我会在相应位置标注。1. 编译前的环境准备与版本选型1.1 为什么锁定NDK r22我用的NDK版本是22.1.7171670。这个选择不是拍脑袋而是在对比了几个版本之后定下来的。NDK r22是从r19往后一个比较平衡的版本它在r21基础上继续完善了LLVM/Clang工具链同时还没像r23那样大规模移除兼容性代码。FFmpeg 4.4的configure脚本对NDK r22的识别很顺畅基本不用额外打补丁。如果你用r23或更高版本会遇到一个比较麻烦的问题FFmpeg 4.4及以下版本在解析NDK头文件路径时会出错报一些莫名其妙的undefined reference或者头文件找不到的错误。当然你可以升级到FFmpeg 5.x甚至6.x来兼容新版NDK但那是另一个话题。对于只想快速拿到arm64-v8a库的开发者来说NDK r22 FFmpeg 4.4这套组合是我目前测试过最省心的一套。1.2 MSYS2环境搭建与依赖安装在Win10上编译FFmpeg绕不开MSYS2。FFmpeg的构建系统是Unix风格的configure脚本是shell脚本make、sed、awk这些工具Windows原生环境都没有所以必须借助MSYS2提供一个类Unix环境。安装步骤很简单从官网下载MSYS2安装包装完后打开MSYS2 MINGW64终端执行pacman -Syu pacman -S make diffutils pkg-config yasm nasm这里要提醒一下MSYS2有两个终端入口MSYS2 MSYS和MSYS2 MINGW64。编译FFmpeg建议用MSYS2 MINGW64因为默认的PATH里包含更多的编译工具。yasm和nasm建议都装上。FFmpeg里x86相关的汇编优化依赖yasm而某些新格式的汇编优化依赖nasm。虽然本次目标是aarch64架构但configure脚本在检测阶段会检查这些工具是否存在缺失会导致部分优化特性被禁用。2. 源码下载与目录规划2.1 FFmpeg源码版本选择的逻辑FFmpeg的release分支有很多版本我选择的是4.4版本源码从官网或GitHub tag直接下载wget https://ffmpeg.org/releases/ffmpeg-4.4.tar.bz2 tar -xf ffmpeg-4.4.tar.bz2选4.4不选4.5或5.0主要是因为稳定性4.4在Android交叉编译这个场景下验证的人最多社区里的经验贴也最齐全遇到问题更容易找到解决方案。另外4.4对NDK r22的兼容性经过充分测试不需要修改configure脚本就能直接编译通过。如果你的项目有硬性需求要用新版本FFmpeg建议先花时间研究一下新版本对NDK版本的兼容性要求不要在编译环境上浪费太多时间。2.2 工具链路径解析与目录约定NDK r22在Windows上的工具链路径布局是E:/Android/Sdk/ndk/22.1.7171670/ └── toolchains/ └── llvm/ └── prebuilt/ └── windows-x86_64/ ├── bin/ └── sysroot/bin目录下有编译器、链接器、汇编器、strip工具等sysroot目录是嵌入式编译所需的系统头文件和库镜像。后面写configure脚本时这两个路径是核心。这里有一个Windows特有的坑NDK路径不要太深。如果你把SDK装在某个超长路径下configure和make阶段可能因为路径超过Windows的MAX_PATH限制而报奇怪的错误。建议把NDK放在类似E:/Android/Sdk/ndk/这种短路径下。另外整个编译产物的目录建议用一个单独的目录比如项目根目录下的build_android所有中间文件和最终结果都放这里方便出问题时直接删掉重来。3. configure配置脚本逐行拆解3.1 核心参数背后的含义FFmpeg的configure是整个编译流程的核心参数含义直接决定了你拿到的是什么架构、什么形态的库。下面是我整理的arm64-v8a编译脚本里最关键的几组参数--target-osandroid --archaarch64 --enable-cross-compile--target-osandroid告诉FFmpeg我们是为Android系统编译的这会影响到一些平台相关的条件编译逻辑比如Log级别的输出方式。--archaarch64对应的是arm64-v8a的architecture名称。注意不要把arch值设成arm64FFmpeg的标准命名是aarch64。--enable-cross-compile是切换交叉编译模式的开关它会禁用一些对目标平台无意义的本机编译检查。编译器相关参数--cc$TOOLCHAIN/bin/aarch64-linux-android21-clang.cmd --cxx$TOOLCHAIN/bin/aarch64-linux-android21-clang.cmd --strip$TOOLCHAIN/bin/llvm-strip.exe --nm$TOOLCHAIN/bin/llvm-nm.exe这里是我踩过的最深的坑在Windows的MSYS2环境下NDK的工具链bin目录里有两种后缀的文件。一种是Unix风格没有后缀的可执行文件在MSYS2内可以直接执行另一种是Windows风格的.cmd或.exe后缀文件。FFmpeg的configure脚本在Windows下默认认识.cmd后缀的编译器入口而.exe后缀的文件在某些场景下会因为MSYS2路径转换问题导致失败。我最终稳定使用的组合是clang用.cmd后缀strip和nm用.exe后缀。aarch64-linux-android21-clang.cmd这个名字里的21是API Level。NDK r22制作的编译器默认支持API 21到API 30这里指定21是因为arm64架构的Android设备最小支持API 21Android 5.0。如果你需要支持更早的设备可以改成aarch64-linux-android16-clang.cmd但对于纯arm64-v8a的64位库来说API 21是标准起点。sysroot相关参数--sysroot$TOOLCHAIN/sysrootsysroot指定了编译时使用的系统根目录这里面包含了Android的系统库libc、libm、liblog等和头文件。Android的sysroot是分架构的NDK r22在sysroot目录下的结构会自动适配arch所以不需要再加--extra-cflags来指定具体的架构头文件路径。功能裁剪参数也需要关注--disable-programs --disable-doc --disable-avdevice --disable-postproc--disable-programs很重要因为我们编译库是为了集成到Android应用里不需要生成ffmpeg命令行工具。--disable-avdevice关闭设备输入输出模块Android上用不到。这些裁剪不是必要的但能让编译体积变小、速度变快。3.2 动静态库的取舍策略这一步很多人容易纠结。先说结论如果你只是想快速拿到.so集成到Android项目里编译动态库就够了如果想在编译阶段静态链接或者想把FFmpeg封装进一个单独的.so里那编译静态库更合适。FFmpeg的configure参数里--enable-shared --disable-static # 只生成动态库 --disable-shared --enable-static # 只生成静态库这两组参数是互斥的。理论上也不能同时enable两者因为FFmpeg的构建系统如果同时生成.so和.amake阶段会报符号冲突。我的建议是分两步走先执行一次动态库编译记录下所有参数然后再执行一次静态库编译。这样能同时拿到两种形态的库方便后续不同场景使用。整个过程多花十几分钟但收益明显。后面第4章的实操过程就是按照这个思路来的。补充一个--enable-pic的说明。PICPosition Independent Code是生成动态库的必需条件Android加载.so时会在运行时重定位所以代码必须是与位置无关的。NDK r22的编译器默认对aarch64开启PIC但为了保险起见还是显式加上这个参数。4. 编译实操与踩坑记录4.1 构建脚本完整示例我把整套编译过程封装成了一个shell脚本在MSYS2的MINGW64环境里直接执行。脚本有几个关键点要特别说明先看整体代码#!/bin/bash NDKE:/Android/Sdk/ndk/22.1.7171670 TOOLCHAIN$NDK/toolchains/llvm/prebuilt/windows-x86_64 API21 PREFIX$(pwd)/android_arm64 make clean rm -rf $PREFIX ./configure \ --prefix$PREFIX \ --target-osandroid \ --archaarch64 \ --cpuarmv8-a \ --enable-cross-compile \ --cc$TOOLCHAIN/bin/aarch64-linux-android21-clang.cmd \ --cxx$TOOLCHAIN/bin/aarch64-linux-android21-clang.cmd \ --strip$TOOLCHAIN/bin/llvm-strip.exe \ --nm$TOOLCHAIN/bin/llvm-nm.exe \ --sysroot$TOOLCHAIN/sysroot \ --enable-shared \ --disable-static \ --enable-pic \ --enable-jni \ --enable-mediacodec \ --disable-programs \ --disable-doc \ --disable-avdevice \ --disable-postproc \ --disable-network \ --disable-symver make -j8 make install--cpuarmv8-a这一项是可选但推荐加上它告诉编译器具体的CPU微架构级别让编译器可以生成更匹配的指令序列。对arm64-v8a来说armv8-a是一个安全的通用值。--enable-jni和--enable-mediacodec这两个参数与Android平台能力相关。--enable-jni允许FFmpeg使用JNI接口调用Java层的能力--enable-mediacodec启用Android硬件编解码器的支持。如果只是基础学习不加这两个参数也能正常编译但做Android音视频应用一般都建议加上因为硬解是播放器性能的关键。--disable-symver这个参数很多人会漏掉。它用于禁用符号版本化。FFmpeg在Linux上会默认启用符号版本化但Android的动态链接器不支持这个机制不加这个参数生成的.so在加载时可能报错。编译过程如果顺利大概5到10分钟后取决于机器性能会在android_arm64目录下看到include/ lib/lib目录下就是生成的动态库libavcodec.so libavformat.so libavutil.so libswresample.so libswscale.so注意在Windows/MSYS2环境下这些.so其实是带版本号的文件的副本不像Linux下有符号链接。复制到Android工程时不要漏掉文件。4.2 典型编译异常的排查实录这一小节分享一下我在实际编译中遇到的几个问题每个问题背后都花了不少时间排查。第一个问题configure阶段报错ERROR: clang not found。这个问题的原因是configure脚本里的--cc参数指向了错误的编译器路径。在Windows环境下如果只指定了aarch64-linux-android21-clang而不带.cmd后缀MSYS2的PATH解析可能找不到这个命令。解决办法就是检查$TOOLCHAIN/bin/下的实际文件名确认是.cmd后缀并确保路径中用的是正斜杠而不是反斜杠。第二个问题make阶段出现undefined reference to av_jni_get_java_vm。这个问题出现在已开启--enable-jni的情况下某些涉及JNI的源文件没有被正确编译进去。排查后确认是API Level设置问题当API21时JNI相关的符号匹配正常但如果把API设成16或更低部分JNI符号会缺失。所以这里显式用aarch64-linux-android21-clang.cmd而不是更高API的编译器也是规避这类问题的一种手段。第三个问题make install结束后Android Studio集成.so文件时java.lang.UnsatisfiedLinkError。这个问题的根源在Windows下非常隐蔽FFmpeg在Windows/MSYS2下生成的lib目录里那些无版本号的.so文件其实没有正确生成直接复制到工程里的是一个0字节的占位文件而真正的库文件是带版本号的比如libavcodec.so.58。解决方法是在make install完成后手动检查lib目录下每个库文件的实际大小如果发现无版本号的.so文件是空的就从带版本号的文件复制一份重命名。这个坑我第一次遇到时整整排查了一个下午也是很多Windows编译FFmpeg的人会遇到的共性问题。第四个问题换行符导致的诡异错误。在MSYS2里执行代码中写好的脚本时如果脚本是用记事本编辑的会带CRLF换行bash在执行时会报$\r: command not found。解决办法是用VS Code或Notepad把换行符改成LF或者在MSYS2里执行dos2unix build_arm64.sh转换。5. 产物验证与Android工程集成5.1 验证库文件的基本方法编译完不能直接认为库就一定可用先做几个基本验证。第一步用file命令查看库的格式file libavcodec.so正常输出应该是ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, BuildID[sha1]..., stripped如果输出显示的是x86-64说明configure阶段arch参数没生效库是给x86平台编的。第二步用nm查看导出符号$TOOLCHAIN/bin/llvm-nm.exe -D libavcodec.so | grep avcodec_find_decoder能看到符号输出就说明库的导出符号表完整。如果提示没有符号检查是否用了strip工具过度裁剪。第三步检查依赖关系$TOOLCHAIN/bin/llvm-readelf.exe -d libavformat.so | grep NEEDED正常会看到对libavutil.so的依赖。如果出现奇怪的依赖项要回去检查NDK版本和sysroot路径。5.2 集成到项目的注意事项把编译好的库集成到Android Studio项目里有几个细节需要注意。目录结构方面在app/src/main下创建jniLibs/arm64-v8a目录把无版本号的.so放进去。注意如果项目要支持armeabi-v7a需要单独编译对应架构的库不能把arm64的库直接放到arm64-v8a之外的目录。加载方式上如果用动态库Java/Kotlin层用System.loadLibrary(avcodec)逐个加载。FFmpeg的库之间存在依赖关系加载顺序很重要先加载avutil再加载swresample、swscale、avformat最后avcodec。如果你把FFmpeg的这些.so放在同一个目录下Android的System.loadLibrary在加载一个so时会自动解析同目录下的依赖但为了避免奇怪的偶发问题我建议手动保证加载顺序。如果你走的是NDK C/C集成路线直接用CMake时编译引用静态库会更方便。把.a文件放进src/main/cpp/libs/arm64-v8a/头文件放进src/main/cpp/include/然后在CMakeLists.txt里add_library(avcodec STATIC IMPORTED) set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libavcodec.a) target_include_directories(native-lib PRIVATE ${CMAKE_SOURCE_DIR}/include) target_link_libraries(native-lib avcodec avformat avutil swresample swscale)静态库的链接顺序也需要注意avformat要在avcodec前面avcodec在avutil前面反了会报一堆undefined reference。还有一个额外的经验如果你把FFmpeg库编译成动态库并且只把单个libavcodec.so放进jniLibs而其他几个库也引用到了那么Android的打包系统会帮你把jniLibs里所有.so都打进APK只要都在目录下就问题不大。但如果你手动用android:extractNativeLibsfalse要确保这些.so没有经过压缩否则安装后可能加载失败。最后要说的是编译FFmpeg这件事第一次跑通最重要。不要一开始就想着把x264、openssl等第三方库全部集成进去那会把问题复杂化。先把一个干净的arm64-v8a库编出来跑通整个流程再逐步加功能。我第一次做的时候就是先编译了一个没有任何第三方依赖的版本集成进一个简单的播放器工程里确认能正常解码本地视频然后才回头去集成其他库。这个过程虽然保守但每一步都在掌握之中排查问题也容易得多。另外如果你的目标是学习FFmpeg而不是真的要打包一个播放器那动态库和静态库都可以多编几次每次改改configure参数看看产物的差异。我自己的经验是只看文档记不住哪些参数是干什么的亲手编译几遍看看生成的库的大小、符号数量、依赖关系变化这些知识才会真正变成自己的。本文还有配套的精品资源点击获取
返回列表