简介:面向 Android 音视频开发者的 FFmpeg ARMv8 预编译库合集,针对 64 位 ARM 设备(arm64-v8a)优化,解决在移动端集成音视频编解码、封装解析与滤镜处理能力时反复交叉编译的痛点,适合具备 NDK 基础、需要快速落地播放、录制或转码功能的中级开发者。压缩包共 162 个文件,约 14.35MB,以 111 个 C 头文件与 23 个 C 源文件构成接口与示例基础,另含 6 个 .a 静态库、6 个 .so 动态库及 6 个 pkg-config 描述文件,覆盖 avformat、avcodec、avfilter、swresample、swscale、avutil 六大组件,并附带 transcode_aac、muxing、demuxing_decoding 等官方示例源码,便于对照理解调用流程。已有 614 人学习下载。读者可直接将动态库放入 jniLibs、静态库链接进可执行文件,省去编译环节,同时借助示例与头文件快速掌握封装读写、编解码与音频重采样、视频缩放等核心用法,并据此规划多架构兼容与主线程外异步处理方案。
1. 拿到ffmpeg-armv8-a.zip先别急着解压:它到底解决什么问题
如果你在 ARMv8 平台上做过音视频相关的东西,大概率遇到过这个场景:交叉编译好的 FFmpeg 动态库往设备上一扔,运行时提示找不到libavcodec.so,或者版本对不上、符号缺失,折腾半天最后发现是目标板上的系统库和编译产物 ABI 不匹配。ffmpeg-armv8-a.zip这类资源包,本质上就是把这个环节的产物提前给你备好了——里面是面向 ARMv8(aarch64)架构预编译的 FFmpeg 静态库(.a)以及可能的动态库(.so),省掉从源码 configure、make 到交叉工具链适配的一整条链路。它适合两类人:一是手上有个 ARMv8 板子、想快速把音视频解码/转码能力嵌进自己程序里的嵌入式开发者;二是做移动端或边缘设备、需要把 FFmpeg 以静态链接方式塞进最终二进制、避免运行时依赖的工程师。关键词 ffmpeg、.a、.so 三个词基本概括了它的全部价值:给你现成的库文件,而不是源码。
2. 拆包看结构:.a与.so到底该选哪个
拿到压缩包,第一步不是写代码,而是先搞清楚里面有什么、每个文件是干什么的。这一步做扎实,后面链接阶段的翻车概率能降一大半。
2.1 解压后先做一次文件盘点
解压到一个干净目录,用find把文件类型和架构信息摸清楚。不要只看文件名,文件名会骗人,file命令不会。
# 解压到独立目录,避免污染工作区 mkdir -p ffmpeg-armv8 && cd ffmpeg-armv8 unzip ../ffmpeg-armv8-a.zip # 盘点所有库文件,确认架构与类型 find . -name "*.a" -o -name "*.so*" | while read f; do echo "=== $f ===" file "$f" done这段脚本的输出重点看两处:一是file结果里是否出现ARM aarch64,如果出现x86-64说明包拿错了;二是区分current ar archive(静态库)和ELF 64-bit LSB shared object(动态库)。静态库.a是一堆.o的归档,链接时被整体或按需抽取进你的可执行文件;动态库.so是运行时加载,体积小但要求目标系统上有对应文件。参数上没什么可调的,关键是养成「先验架构再动手」的习惯,我见过太多人链接报错查了两小时,最后发现是 x86 的包。
2.2 静态链接与动态链接的选型判断
选.a还是.so,不是喜好问题,是部署约束问题。下面这张表是我一般用来做决策的依据:
| 维度 | 静态库.a | 动态库.so |
|---|---|---|
| 运行时依赖 | 无,全部打进可执行文件 | 需要目标系统存在对应.so |
| 最终体积 | 较大(只抽取用到的.o) | 较小 |
| 升级方式 | 重新链接整个程序 | 替换.so即可 |
| 许可证注意 | FFmpeg 多为 LGPL,静态链接有合规要求 | 动态链接合规压力小 |
| 适合场景 | 单文件分发、依赖受限的嵌入式设备 | 系统级部署、多程序共享 |
如果你的设备是那种刷一次固件就不太动的边缘盒子,静态链接省心;如果是跑在完整 Linux 发行版上、多个程序都要用,动态库更合理。这里有个血泪经验:静态链接 FFmpeg 时如果开了 GPL 组件(比如某些编码器),整个程序的分发就要按 GPL 处理,商用前务必确认编译选项,别等上线了才发现许可证问题。
2.3 用nm和readelf验证符号与依赖
库文件能不能用,光看架构不够,还得确认关键符号在不在、动态库的依赖链干不干净。
# 检查静态库里是否包含常用的解码入口符号 nm -g --defined-only libavcodec.a 2>/dev/null | grep -E "avcodec_(send_packet|receive_frame)" # 检查动态库的 SONAME 和依赖 readelf -d libavcodec.so | grep -E "SONAME|NEEDED"nm -g只列全局符号,--defined-only过滤掉未定义引用,grep锁定avcodec_send_packet/avcodec_receive_frame这对新 API——如果你的代码用的是老式avcodec_decode_video2,得确认库里还有没有这个符号,新版 FFmpeg 已经把它标记废弃甚至移除。readelf -d看NEEDED项,如果动态库依赖了一堆你目标板上没有的库(比如某个特定版本的libz),运行时就会直接挂掉。这一步做完,你对这个包能不能用、怎么用,心里就有底了。
3. 把库接进工程:交叉编译与链接参数怎么配
库验证通过,接下来是真正把它用起来。这一章的核心是把「库文件」变成「能跑的程序」,中间涉及头文件、链接顺序、运行时路径三个容易出错的点。
3.1 头文件与库路径的组织方式
预编译包通常只给库文件,头文件要么在包里另有一个include目录,要么需要你从对应版本的 FFmpeg 源码里取。头文件和库的版本必须严格对应,这是硬约束——用 5.x 的头去配 4.x 的库,结构体布局一变,运行时就是随机崩溃,这种玄学问题最难查。
# 假设包内结构为 lib/ 与 include/,整理成工程可用的布局 mkdir -p third_party/ffmpeg/{lib,include} cp -r ffmpeg-armv8/lib/*.a third_party/ffmpeg/lib/ cp -r ffmpeg-armv8/include/* third_party/ffmpeg/include/ # 确认头文件版本宏与库版本一致 grep -r "LIBAVCODEC_VERSION_MAJOR" third_party/ffmpeg/include/libavcodec/version.h最后那条grep是后悔药:把主版本号记下来,回头链接报「undefined reference」时先对一下,很多符号缺失其实是版本错配导致的,不是库坏了。头文件目录建议原样保留libavcodec/、libavformat/这种层级,因为 FFmpeg 的#include路径是写死带前缀的,你重排目录反而要改一堆 include。
3.2 链接顺序与-lavcodec依赖链
FFmpeg 的库之间有依赖关系:avformat依赖avcodec,avcodec依赖avutil、swresample等。静态链接时链接器是单遍扫描,顺序错了就会出现「符号明明在库里却报未定义」。正确顺序是从上层到下层。
# 交叉编译示例:aarch64 工具链 + 静态链接 FFmpeg aarch64-linux-gnu-gcc main.c -o demo \ -Ithird_party/ffmpeg/include \ -Lthird_party/ffmpeg/lib \ -lavformat -lavcodec -lswresample -lavutil \ -lm -lpthread -lz参数逐个说:-I指头文件,-L指库搜索路径,后面-lavformat -lavcodec -lswresample -lavutil的顺序不能随意调换,avformat在最前、avutil在最后是标准写法;-lm -lpthread -lz是 FFmpeg 自身依赖的系统库,缺了会报undefined reference to pow之类的错。如果你的工具链前缀不是aarch64-linux-gnu-,换成实际的即可,但一定要用目标架构的编译器,用宿主机的 gcc 链接 ARM 库是链接不上的。链接完用file demo确认产物是 ARM aarch64,这一步别省。
3.3 动态库部署时的rpath与LD_LIBRARY_PATH
如果选的是.so,编译能过不代表设备上能跑。动态库的查找路径是运行时的事,编译期的-L只管链接那一刻。
# 方式一:编译时写入 rpath,让程序自己去指定目录找库 aarch64-linux-gnu-gcc main.c -o demo \ -Ithird_party/ffmpeg/include \ -Lthird_party/ffmpeg/lib \ -lavformat -lavcodec -lavutil \ -Wl,-rpath,'$ORIGIN/lib' # 方式二:运行时通过环境变量指定(调试期常用) export LD_LIBRARY_PATH=/opt/ffmpeg/lib:$LD_LIBRARY_PATH ./demo-Wl,-rpath,'$ORIGIN/lib'里的$ORIGIN表示可执行文件所在目录,这样把.so放在程序旁边的lib/下就能找到,不依赖系统路径,适合绿色部署。注意单引号不能少,否则$ORIGIN会被 shell 提前展开成空。LD_LIBRARY_PATH适合临时调试,别写进生产启动脚本,容易和其他程序的库版本打架。部署后用ldd demo检查依赖是否全部解析成功,出现not found就顺着那一行去补库。
4. 避坑与排查:链接期和运行期的高频翻车点
这一章是我踩过的坑的合集,按「现象 → 原因 → 解决」写,遇到问题直接对号入座。
4.1 现象:undefined reference to 'av_xxx',但nm明明有符号
原因通常是链接顺序错误,或者库被链接器当成「已扫描过」而跳过。静态库在命令行里出现的位置必须在引用它的目标文件之后。解决:把-lavcodec等库参数挪到所有.c/.o之后,并严格按avformat → avcodec → swresample → avutil排序;如果还不行,用-Wl,--start-group ... -Wl,--end-group把库包起来强制多遍扫描,代价是链接变慢。
4.2 现象:程序在设备上一启动就Segmentation fault,PC 上交叉编译无报错
原因是头文件与库版本不匹配,结构体大小或字段偏移不一致,访问时越界。解决:用grep LIBAVCODEC_VERSION_MAJOR核对头文件版本,与库的编译版本对齐;实在拿不到匹配头文件,就从同版本源码里取include目录,别凑合。
4.3 现象:动态库部署后报cannot open shared object file
原因是运行时找不到.so,-L只影响链接期。解决:优先用-Wl,-rpath写入相对路径;临时调试用LD_LIBRARY_PATH;用ldd逐项确认依赖,注意ldd在交叉环境下要在目标板上跑,宿主机上跑出来的是宿主机的解析结果,没有参考价值。
4.4 现象:静态链接后二进制体积暴涨到几十 MB
原因是链接器把整个.a里被间接引用的.o都拉进来了,FFmpeg 的库粒度较粗。解决:编译时加-Wl,--gc-sections配合源码的-ffunction-sections -fdata-sections裁剪无用段;更彻底的做法是只保留实际用到的解码器/封装格式,这需要重新编译 FFmpeg 并--disable-everything后按需开启,预编译包做不到这一点,属于它的能力边界。
4.5 现象:设备上解码正常但播放花屏或音画不同步
原因往往不是库的问题,而是调用方没按新 API 的 send/receive 模型写,或者时间基(time_base)换算错误。解决:确认用的是avcodec_send_packet/avcodec_receive_frame配对调用,检查AVFrame->pts与AVStream->time_base的换算,别拿解码器时间基直接当显示时间基用。这类问题库本身救不了,得回到调用逻辑。
5. 进阶验证:写一个最小解码回路确认库真的可用
库接进去、编译通过,不等于功能正确。我一般会写一个最小可运行的回环来验证:打开一个文件、找到视频流、解出第一帧、打印宽高和像素格式。这段代码不长,但能把「库能不能用」这件事从猜测变成事实。
#include <libavformat/avformat.h> #include <libavcodec/avcodec.h> int main(int argc, char *argv[]) { AVFormatContext *fmt = NULL; // 打开输入,失败直接返回,别继续往下走 if (avformat_open_input(&fmt, argv[1], NULL, NULL) < 0) return -1; if (avformat_find_stream_info(fmt, NULL) < 0) return -2; // 找第一条视频流 int idx = av_find_best_stream(fmt, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if (idx < 0) return -3; AVStream *st = fmt->streams[idx]; const AVCodec *dec = avcodec_find_decoder(st->codecpar->codec_id); AVCodecContext *ctx = avcodec_alloc_context3(dec); avcodec_parameters_to_context(ctx, st->codecpar); if (avcodec_open2(ctx, dec, NULL) < 0) return -4; // 读包、送包、收帧,收到第一帧就打印信息退出 AVPacket *pkt = av_packet_alloc(); AVFrame *frame = av_frame_alloc(); while (av_read_frame(fmt, pkt) >= 0) { if (pkt->stream_index == idx) { avcodec_send_packet(ctx, pkt); if (avcodec_receive_frame(ctx, frame) == 0) { printf("decoded: %dx%d fmt=%d\n", frame->width, frame->height, frame->format); break; } } av_packet_unref(pkt); } // 释放顺序与分配相反 av_frame_free(&frame); av_packet_free(&pkt); avcodec_free_context(&ctx); avformat_close_input(&fmt); return 0; }逻辑上分四段:打开输入并探测流信息、定位视频流并初始化解码器、循环读包送包收帧、按相反顺序释放资源。参数上重点看av_find_best_stream的第二个参数AVMEDIA_TYPE_VIDEO,换成AUDIO就能验证音频链路;avcodec_receive_frame返回 0 表示拿到一帧,返回AVERROR(EAGAIN)表示需要继续送包,这是正常流程不是错误,别一看到非零就退出。编译就用第 3 章的链接命令,跑通后输出类似decoded: 1920x1080 fmt=0就说明库、头文件、链接、运行时四者全部对齐。
验证通过后,如果你想进一步压体积,可以对比静态链接加--gc-sections前后的ls -lh结果,通常能砍掉可观的一部分;如果发现砍完运行时报符号缺失,说明某个被裁剪的段其实被间接引用,把对应库加回--start-group即可。从那以后我每次拿到预编译库,都强制先跑一遍这个最小回环再往业务代码里接,省下的排查时间远比写这几十行多。希望帮到你。
本文还有配套的精品资源,点击获取