news 2026/9/1 2:12:41

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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的你有帮助。

先说清楚这篇笔记的边界:平台锁定Win10,NDK用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里包含更多的编译工具。

yasmnasm建议都装上。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-os=android --arch=aarch64 --enable-cross-compile

--target-os=android告诉FFmpeg我们是为Android系统编译的,这会影响到一些平台相关的条件编译逻辑,比如Log级别的输出方式。--arch=aarch64对应的是arm64-v8a的architecture名称。注意不要把arch值设成arm64,FFmpeg的标准命名是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 21(Android 5.0)。如果你需要支持更早的设备,可以改成aarch64-linux-android16-clang.cmd,但对于纯arm64-v8a的64位库来说,API 21是标准起点。

sysroot相关参数:

--sysroot=$TOOLCHAIN/sysroot

sysroot指定了编译时使用的系统根目录,这里面包含了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和.a,make阶段会报符号冲突。

我的建议是分两步走:先执行一次动态库编译,记录下所有参数;然后再执行一次静态库编译。这样能同时拿到两种形态的库,方便后续不同场景使用。整个过程多花十几分钟,但收益明显。后面第4章的实操过程就是按照这个思路来的。

补充一个--enable-pic的说明。PIC(Position Independent Code)是生成动态库的必需条件,Android加载.so时会在运行时重定位,所以代码必须是与位置无关的。NDK r22的编译器默认对aarch64开启PIC,但为了保险起见,还是显式加上这个参数。

4. 编译实操与踩坑记录

4.1 构建脚本完整示例

我把整套编译过程封装成了一个shell脚本,在MSYS2的MINGW64环境里直接执行。脚本有几个关键点要特别说明,先看整体代码:

#!/bin/bash NDK=E:/Android/Sdk/ndk/22.1.7171670 TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/windows-x86_64 API=21 PREFIX=$(pwd)/android_arm64 make clean rm -rf $PREFIX ./configure \ --prefix=$PREFIX \ --target-os=android \ --arch=aarch64 \ --cpu=armv8-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

--cpu=armv8-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设置问题,当API=21时,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前面,avcodecavutil前面,反了会报一堆undefined reference。

还有一个额外的经验:如果你把FFmpeg库编译成动态库,并且只把单个libavcodec.so放进jniLibs,而其他几个库也引用到了,那么Android的打包系统会帮你把jniLibs里所有.so都打进APK,只要都在目录下就问题不大。但如果你手动用android:extractNativeLibs="false",要确保这些.so没有经过压缩,否则安装后可能加载失败。

最后要说的是,编译FFmpeg这件事,第一次跑通最重要。不要一开始就想着把x264、openssl等第三方库全部集成进去,那会把问题复杂化。先把一个干净的arm64-v8a库编出来,跑通整个流程,再逐步加功能。我第一次做的时候,就是先编译了一个没有任何第三方依赖的版本,集成进一个简单的播放器工程里,确认能正常解码本地视频,然后才回头去集成其他库。这个过程虽然保守,但每一步都在掌握之中,排查问题也容易得多。

另外,如果你的目标是学习FFmpeg而不是真的要打包一个播放器,那动态库和静态库都可以多编几次,每次改改configure参数,看看产物的差异。我自己的经验是,只看文档记不住哪些参数是干什么的,亲手编译几遍,看看生成的库的大小、符号数量、依赖关系变化,这些知识才会真正变成自己的。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 2:12:35

电赛E题满分视频制作指南:把视频当工程交付物

“2026电赛E题满分视频”这个说法,乍看会让人以为是一篇教“剪片”的教程。但真到参赛时你才会理解,评委观看视频时能接收的信息量非常有限:他们没有条件反复回看你的工程细节,也没有义务从模糊的画面里替你补全逻辑。一个拿到高分…

作者头像 李华
网站建设 2026/9/1 2:12:05

热成像与可见光双模态融合:从配准到检测的完整工程实践

简介:本资源是一套面向计算机视觉与智能感知领域的工程实践方案,聚焦热成像与可见光双模态图像融合核心技术,适用于安防监控、自动驾驶及环境目标追踪等实时性要求高的工业与科研场景,适合具备图像处理基础和深度学习经验的开发者…

作者头像 李华
网站建设 2026/9/1 2:11:06

智能保险箱技术拆解:办公场景选型、安装验收与故障排查指南

智能保险箱这几年从单纯的机械锁柜体,变成了一台集指纹识别、人脸识别、密码键盘、无线通信和远程报警于一体的智能安防设备。像艾谱(AIPU)这类品牌推出的高端办公防盗保险箱,已经把“旗舰智控”“远程报警”作为卖点,…

作者头像 李华
网站建设 2026/9/1 2:11:00

IAR下LPC1768工程RAM.icf链接脚本深度解析与实战指南

简介:本资源是面向嵌入式初学者与LPC1768开发者的IAR集成开发环境专用工程包,聚焦ARM Cortex-M3架构下的基础外设实践,解决从环境搭建到代码调试的关键入门问题。压缩包共567个文件,含135个C源码、158个头文件(.h&…

作者头像 李华
网站建设 2026/9/1 2:08:59

单片机毕业设计-基于 STM32 或 51 单片机的语音播报距离检测报警系统设计 基于 STM32 或 51 单片机的 LCD1602 显示超声波报警设备开发(022905)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/9/1 2:08:47

基于FPGA读写MT25QL SPI NOR Flash的工程实现与验证

简介:本资源是一套基于Xilinx FPGA平台实现MT25QL系列SPI NOR Flash读写控制的完整Verilog工程,面向数字电路初学者、FPGA开发工程师及嵌入式硬件开发者,解决Flash芯片底层驱动开发与验证难题。工程以Vivado 2018.3为开发环境,通过…

作者头像 李华