资讯动态

Android音视频开发:FFmpeg与openh264合并打包实战

发布时间:2026/10/15 1:24:34 来源:尧图企业网站定制
1. 为什么非要把 FFmpeg 和 openh264 打包进同一个 so 文件这个问题我第一次被问到时是在一个音视频 SDK 封装项目里。当时某公司提供的 Android SDK 文档里只写了一行“请确保 libffmpeg.so 同时支持 H.264 编解码”但没说清楚——它到底是指“FFmpeg 自带的 libx264”、“系统自带的 MediaCodec”、还是“独立加载的 libopenh264.so”结果我们团队在三台不同厂商的中低端安卓机上连续踩了五天坑有的机型能硬解但软解崩溃有的机型软解正常但硬解黑屏还有一台连avcodec_find_decoder(AV_CODEC_ID_H264)都返回空指针。后来翻日志才发现真正的问题不在编解码器本身而在于符号冲突与运行时加载顺序。Android 的动态链接器linker在处理多个 so 文件时对全局符号尤其是libavcodec内部大量使用的ff_前缀函数采用的是“先加载者胜出”策略。当libavcodec.so和libopenh264.so分开存在时如果libopenh264.so先被System.loadLibrary(openh264)加载它内部的WelsDecGetHandle、WelsCreateDecoder等符号就可能被libavcodec.so的avcodec_register_all()间接引用导致dlsym查找失败或函数指针错位。更隐蔽的是某些 NDK 版本如 r21e的libc_shared.so与 openh264 的 C 异常处理机制存在 ABI 不兼容分开加载时会在WelsDecPic调用栈深处触发std::terminate——而这个崩溃根本不会出现在 Java 层堆栈里只在 logcat 的DEBUG级别留下一行A/libc: Fatal signal 6 (SIGABRT)。把它们合并成一个libffmpeg.so本质是用静态链接替代动态符号解析。我们不是简单地把两个库的.o文件塞进一个归档而是让 openh264 的源码直接作为 FFmpeg 的一个“内置编码器模块”参与整个构建流程它的所有符号都变成libffmpeg.so的局部符号STB_LOCAL不再对外暴露它的内存分配全部走 FFmpeg 的av_malloc/av_free它的错误码映射由libavcodec统一转换为AVERROR_*。这相当于给 FFmpeg 做了一次“器官移植”——不是把另一个心脏放在旁边而是把心肌细胞直接整合进原有心脏组织里。这种做法在嵌入式音视频领域其实早有实践。比如某车载中控系统的多媒体框架就强制要求所有编解码器必须以“in-tree”方式集成进主 so理由很实在OTA 升级时只需替换一个文件避免因libopenh264.so版本不匹配导致的启动失败灰度发布时可通过 so 文件的 CRC32 快速校验完整性更重要的是NDK 的strip工具对单个大 so 的符号剥离更彻底最终 APK 体积反而比拆分成三个小 so 少 12%。提示这不是银弹。如果你的项目需要同时使用 openh264 的独立 API比如调用WelsSetOption设置自定义 GOP 结构那合并后这些接口就不可见了。必须权衡——你要的是“FFmpeg 接口统一性”还是“openh264 原生控制力”。2. 构建链路设计从源码到单 so 的四层过滤很多人以为“编译 FFmpeg openh264”就是下载两个源码包、分别./configure make、再用arm-linux-androideabi-gcc -shared把.a文件打包。这是典型的一知半解。真正的构建链路必须经过四层严格过滤缺一不可2.1 第一层NDK 工具链与 ABI 的精确锚定你不能只写--target-osandroid。Android 的 ABI 实际上是三维坐标CPU 架构arm64-v8a/armeabi-v7a/x86_64、C 标准库c_shared/c_static、以及最低 Android API 级别android-21/android-24。这三者组合决定了工具链的二进制兼容性。例如arm64-v8ac_sharedandroid-21→ 必须用aarch64-linux-android-clang且--sysroot指向$NDK/platforms/android-21/arch-arm64/armeabi-v7ac_staticandroid-19→ 必须用armv7a-linux-androideabi-clang--sysroot指向$NDK/platforms/android-19/arch-arm/我见过最惨的案例是开发者用x86_64工具链编译了arm64-v8a的 so因为 NDK 的clang默认会识别宿主机架构。结果生成的 so 文件头显示ELF64-x86-64但设备要求ELF64-AArch64dlopen直接返回dlopen failed: empty/missing DT_HASH/DT_GNU_HASH。解决方法不是重装 NDK而是显式指定--targetaarch64-linux-android21注意这里21是 API 级别不是 NDK 版本号。2.2 第二层openh264 源码的深度改造官方 openh264 的make系统默认生成的是独立的libopenh264.so其Makefile里硬编码了-fPIC -shared。要让它成为 FFmpeg 的子模块必须做三处手术第一禁用独立构建逻辑。在openh264/codec/build/android/mk/Android.mk中注释掉include $(BUILD_SHARED_LIBRARY)改为include $(BUILD_STATIC_LIBRARY)。这确保它只产出libopenh264.a。第二重定向内存管理。openh264 的WelsMalloc默认调用malloc但在 FFmpeg 的多线程解码场景下这会导致与av_malloc的内存池不一致。我们在openh264/codec/common/src/WelsThreadLib.cpp开头插入extern C { #include libavutil/mem.h } #define malloc av_malloc #define free av_free #define realloc av_realloc_fallback注意av_realloc_fallback是关键——它处理realloc失败时的降级逻辑比裸realloc更健壮。第三统一错误码体系。openh264 返回cmResultSuccess/cmInitParaError等枚举而 FFmpeg 要求int类型的AVERROR_*。我们在libavcodec/libopenh264dec.c里建立映射表static int wels_to_averr(int wels_ret) { switch(wels_ret) { case cmResultSuccess: return 0; case cmInitParaError: return AVERROR_INVALIDDATA; case cmUnsupportedDataError: return AVERROR_PATCHWELCOME; case cmMemoryExhausted: return AVERROR(ENOMEM); default: return AVERROR_UNKNOWN; } }2.3 第三层FFmpeg configure 的精准开关FFmpeg 的configure脚本有超过 200 个选项但和 openh264 相关的只有三个是命门--enable-libopenh264这是入口开关但它本身不干任何事只是启用后续的检测逻辑。--enable-decoderh264和--enable-encoderlibopenh264必须显式开启。很多人以为--enable-libopenh264会自动注册编解码器其实不会——FFmpeg 把 openh264 视为可选外部库不开启对应编解码器avcodec_find_decoder就找不到它。--disable-static --enable-shared这是最关键的矛盾点。--disable-static禁用静态库生成--enable-shared启用共享库但--enable-libopenh264要求 openh264 以静态方式链接进来。所以实际命令是./configure \ --target-osandroid \ --archaarch64 \ --cpuarmv8-a \ --cross-prefix$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21- \ --sysroot$NDK/platforms/android-21/arch-arm64/ \ --pkg-configpkg-config \ --pkg-config-flags--static \ --prefix$PREFIX \ --enable-shared \ --disable-static \ --enable-libopenh264 \ --enable-decoderh264 \ --enable-encoderlibopenh264 \ --enable-gpl \ --enable-version3 \ --enable-nonfree注意--pkg-config-flags--static它告诉 pkg-config 在查找 openh264 时优先返回-L/path/to/lib -lopenh264而不是-l:libopenh264.so从而强制静态链接。2.4 第四层链接时的符号控制与裁剪即使 configure 成功make生成的libffmpeg.so仍可能包含冗余符号。我们用readelf -Ws libffmpeg.so | grep FUNC.*GLOBAL查看发现ff_h264_decode_init、ff_h264_decode_frame等函数被导出但它们本该是内部实现细节。这会增大 so 体积并可能引发符号冲突。解决方案是使用--version-script生成符号版本脚本。创建libffmpeg.mapFFMPEG_5.1 { global: av_*; sws_*; swr_*; avcodec_*; avformat_*; avutil_*; local: *; };然后在 configure 中加入--extra-ldflags-Wl,--version-script$PWD/libffmpeg.map这样只有以av_、sws_、swr_、avcodec_等前缀开头的符号才会被导出其他全部设为STB_LOCAL。实测下来arm64-v8a版本的 so 体积从 18.7MB 降到 14.2MB且nm -D libffmpeg.so | wc -l显示导出符号数从 12,438 个锐减到 2,156 个。注意--version-script在 NDK r23 中需配合--unresolved-symbolsignore-in-object-files使用否则链接器会报undefined reference to WelsCreateDecoder。这是因为 openh264 的静态库中部分符号在 FFmpeg 主体里未被直接引用链接器默认当作错误。3. 实操步骤详解从零开始构建 arm64-v8a 版本现在把前面所有原理落地为可执行的步骤。以下命令均在 Ubuntu 22.04 NDK r25c 环境下验证通过路径变量已参数化复制粘贴即可运行。3.1 环境准备与目录结构首先建立清晰的构建树避免源码污染export NDK/path/to/android-ndk-r25c export TOOLCHAIN$NDK/toolchains/llvm/prebuilt/linux-x86_64 export TARGETaarch64-linux-android export API21 export PREFIX$(pwd)/android/$TARGET # 创建标准目录结构 mkdir -p ffmpeg-src openh264-src build-scripts cd ffmpeg-src git clone https://git.ffmpeg.org/ffmpeg.git . --depth1 cd ../openh264-src git clone https://github.com/cisco/openh264.git . --depth1关键点--depth1节省时间FFmpeg 和 openh264 的历史提交对构建无影响build-scripts目录专门存放定制化脚本不混入源码。3.2 openh264 的补丁注入进入openh264-src创建0001-ffmpeg-integration.patchdiff --git a/codec/build/android/mk/Android.mk b/codec/build/android/mk/Android.mk index 1a2b3c4..5d6e7f8 100644 --- a/codec/build/android/mk/Android.mk b/codec/build/android/mk/Android.mk -45,7 45,7 include $(CLEAR_VARS) LOCAL_MODULE : openh264 LOCAL_SRC_FILES : $(LOCAL_PATH)/../lib/$(TARGET_ARCH_ABI)/libopenh264.a -include $(BUILD_SHARED_LIBRARY) include $(BUILD_STATIC_LIBRARY) # For test app include $(CLEAR_VARS) diff --git a/codec/common/src/WelsThreadLib.cpp b/codec/common/src/WelsThreadLib.cpp index 9f8e7d6..1a2b3c4 100644 --- a/codec/common/src/WelsThreadLib.cpp b/codec/common/src/WelsThreadLib.cpp -1,3 1,10 #ifdef __ANDROID__ #include jni.h extern C { #include libavutil/mem.h } #define malloc av_malloc #define free av_free #endif然后应用补丁cd openh264-src git apply ../build-scripts/0001-ffmpeg-integration.patch这个补丁做了两件事一是把Android.mk改为静态库构建二是条件编译内存重定向。#ifdef __ANDROID__是安全边界确保桌面版 openh264 不受影响。3.3 FFmpeg 的 configure 脚本增强FFmpeg 官方 configure 不知道 openh264 的 Android 构建路径。我们需要在ffmpeg-src/configure里注入探测逻辑。找到enabled libopenh264所在的if块在其后添加if enabled libopenh264; then # Android-specific openh264 path detection if [ $target_os android ]; then if [ -f $source_path/../openh264-src/codec/common/inc/wels_common_defs.h ]; then add_cflags -I$source_path/../openh264-src/codec/common/inc add_cflags -I$source_path/../openh264-src/codec/decoder/core/inc add_cflags -I$source_path/../openh264-src/codec/decoder/plus/inc add_ldflags -L$source_path/../openh264-src/codec/build/android/obj/local/$TARGET_ARCH_ABI add_extralibs -lopenh264 else die openh264 source not found at $source_path/../openh264-src fi fi fi这段代码让 FFmpeg configure 能自动找到我们放在同级目录的 openh264 源码并设置正确的头文件路径和链接路径。$TARGET_ARCH_ABI会由外部脚本传入如arm64-v8a。3.4 构建脚本build_ffmpeg_android.sh创建build-scripts/build_ffmpeg_android.sh#!/bin/bash set -e # 参数校验 if [ $# -ne 1 ]; then echo Usage: $0 arch (e.g., arm64-v8a) exit 1 fi ARCH$1 case $ARCH in arm64-v8a) TARGETaarch64-linux-android; API21; ;; armeabi-v7a) TARGETarmv7a-linux-androideabi; API19; ;; x86_64) TARGETx86_64-linux-android; API21; ;; *) echo Unknown arch: $ARCH; exit 1; ;; esac # 路径设置 NDK/path/to/android-ndk-r25c TOOLCHAIN$NDK/toolchains/llvm/prebuilt/linux-x86_64 PREFIX$(pwd)/android/$ARCH SYSROOT$NDK/platforms/android-$API/arch-$(echo $ARCH | sed s/-.*//) # 清理旧构建 rm -rf $PREFIX mkdir -p $PREFIX # 构建 openh264 静态库 cd ../openh264-src make clean make OSandroid NDKROOT$NDK TARGET$ARCH -j$(nproc) # 构建 FFmpeg cd ../ffmpeg-src ./configure \ --target-osandroid \ --arch$(echo $ARCH | sed s/-.*//) \ --cpu$(echo $ARCH | sed s/-.*//) \ --cross-prefix$TOOLCHAIN/bin/$TARGET- \ --sysroot$SYSROOT \ --pkg-configpkg-config \ --pkg-config-flags--static \ --prefix$PREFIX \ --enable-shared \ --disable-static \ --enable-libopenh264 \ --enable-decoderh264 \ --enable-encoderlibopenh264 \ --enable-gpl \ --enable-version3 \ --enable-nonfree \ --disable-doc \ --disable-programs \ --disable-postproc \ --disable-avdevice \ --disable-swresample \ --disable-swscale \ --extra-ldflags-Wl,--version-script$(pwd)/../build-scripts/libffmpeg.map -Wl,--unresolved-symbolsignore-in-object-files make -j$(nproc) make install echo Build completed for $ARCH - $PREFIX运行它chmod x build-scripts/build_ffmpeg_android.sh ./build-scripts/build_ffmpeg_android.sh arm64-v8a整个过程约 12 分钟i7-11800H最终在android/arm64-v8a/lib/下生成libffmpeg.so。3.5 验证不只是跑通而是验证正确性生成 so 后不能只用adb push测试是否崩溃。必须做三层验证第一层符号完整性检查# 确认 openh264 符号已内联且无外部依赖 $TOOLCHAIN/bin/aarch64-linux-android-readelf -d android/arm64-v8a/lib/libffmpeg.so | grep NEEDED # 输出应只包含 libc.so、libm.so、libdl.so、liblog.so —— 绝对不能出现 libopenh264.so第二层编解码器注册验证写一个极简 JNI 函数JNIEXPORT jint JNICALL Java_com_example_FfmpegTest_checkH264Support(JNIEnv *env, jobject thiz) { avcodec_register_all(); // 确保初始化 const AVCodec *dec avcodec_find_decoder(AV_CODEC_ID_H264); const AVCodec *enc avcodec_find_encoder_by_name(libopenh264); if (!dec || !enc) { __android_log_print(ANDROID_LOG_ERROR, FFMPEG, H264 codec not found); return -1; } __android_log_print(ANDROID_LOG_INFO, FFMPEG, H264 decoder: %s, encoder: %s, dec-name, enc-name); return 0; }在 Java 层调用logcat 应输出H264 decoder: h264, encoder: libopenh264。第三层真实帧解码压力测试用一段 10 秒的 H.264 Annex B 格式裸流test.h264在 JNI 中循环解码 1000 帧AVCodecContext *ctx avcodec_alloc_context3(dec); ctx-pix_fmt AV_PIX_FMT_YUV420P; avcodec_open2(ctx, dec, NULL); AVPacket pkt; av_init_packet(pkt); AVFrame *frame av_frame_alloc(); int got_frame; while (read_next_nal_unit(pkt.data, pkt.size, test.h264)) { pkt.pts pkt.dts AV_NOPTS_VALUE; int ret avcodec_send_packet(ctx, pkt); if (ret 0) { while (avcodec_receive_frame(ctx, frame) 0) { // 检查 frame-data[0] 是否非空YUV 数据是否有效 } } }重点观察avcodec_receive_frame的返回值是否稳定为0或AVERROR(EAGAIN)绝不能出现AVERROR_INVALIDDATA说明 openh264 解析逻辑未生效。实操心得在armeabi-v7a构建时务必在 configure 中添加--enable-neon和--enable-thumb。否则 openh264 的 NEON 加速会失效解码性能下降 40%。而arm64-v8a默认启用 ASIMD无需额外开关。4. 常见问题排查从崩溃日志反推根源即使严格按照上述步骤仍可能遇到诡异问题。以下是我在五个不同项目中总结的高频问题及定位方法。4.1 问题java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol WelsCreateDecoder现象Java 层System.loadLibrary(ffmpeg)成功但调用avcodec_find_decoder时 JVM 崩溃logcat 显示找不到WelsCreateDecoder。根因分析这不是符号缺失而是符号可见性错误。WelsCreateDecoder在 openh264 静态库中是STB_GLOBAL但 FFmpeg 的链接器默认将其设为STB_LOCAL。当libffmpeg.so被加载时dlopen只解析STB_GLOBAL符号而WelsCreateDecoder因为未被任何 FFmpeg 源码直接引用被优化掉了。解决方案在 FFmpeg 的libavcodec/libopenh264dec.c开头强制引用// 添加此行确保链接器保留 openh264 符号 extern C { void* WelsCreateDecoder(); // 声明但不调用 }或者更彻底在链接时添加--undefinedWelsCreateDecoder --undefinedWelsDestroyDecoder等所有 openh264 导出函数。4.2 问题A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR)现象解码到第 37 帧时崩溃addr 0x0000000000000000堆栈指向libopenh264.so的WelsDecParseSliceHeader。根因分析这是经典的内存越界。openh264 的WelsDecParseSliceHeader会读取 NALU 的slice_type字段但如果输入的 H.264 流是 AVC 格式含 length prefix而非 Annex B 格式pSrc指针会偏移错误。FFmpeg 的h264_parser默认按 Annex B 解析但若你手动构造 packet 时忘了AV_PKT_FLAG_KEYavcodec_send_packet可能传入不完整的 slice。验证方法在libopenh264dec.c的decode_frame函数开头加日志__android_log_print(ANDROID_LOG_DEBUG, OPENH264, pkt size%d, data[0]0x%02x, data[1]0x%02x, data[2]0x%02x, data[3]0x%02x, pkt-size, pkt-data[0], pkt-data[1], pkt-data[2], pkt-data[3]);Annex B 流的前 4 字节应为0x00 0x00 0x00 0x01或0x00 0x00 0x01。如果不是说明你的输入流格式错误。修复在 Java 层用MediaExtractor提取帧时确保MediaFormat的KEY_MIME是video/avc且MediaCodec输出的 buffer 是 Annex B 格式大多数设备默认如此若用MediaMuxer录制需在MediaFormat中设置KEY_IS_ADTSfalse。4.3 问题E/FFMPEG: [h264 0x7f8c3a1200] error while decoding MB 12 15现象解码画面出现大面积马赛克日志显示MB宏块错误但不崩溃。根因分析openh264 的WelsDecConcealMB错误处理机制被禁用。FFmpeg 的h264_parser在解析 SPS/PPS 时若profile_idc字段解析错误如误读为0会导致 openh264 初始化失败但 FFmpeg 未捕获该错误继续解码损坏的 slice。定位步骤在libavcodec/h264_parser.c的h264_parse函数中打印sps-profile_idc在libavcodec/libopenh264dec.c的ff_libopenh264_decode_init中打印sConfig.uiTargetUsage对比两者若sps-profile_idc66Baseline但sConfig.uiTargetUsage1Main profile说明配置不匹配。修复在ff_libopenh264_decode_init中强制校准sConfig.uiTargetUsage (sps-profile_idc 66) ? 1 : 2; // Baseline→1, Main→24.4 问题W/System.err: java.lang.RuntimeException: Error during init: -12现象avcodec_open2返回-12对应AVERROR(ENOMEM)但设备内存充足。根因分析-12是 openh264 的cmMemoryExhausted但 FFmpeg 的av_strerror未映射。更深层原因是 openh264 的WelsCreateDecoder内部调用new CMemoryAlign时av_malloc返回NULL—— 因为你在configure中忘了--enable-small导致 FFmpeg 的av_malloc默认申请 1MB 对齐内存而 openh264 的CMemoryAlign又在此基础上乘以 2超出了 Android 的ashmem限制。终极修复在libavcodec/libopenh264dec.c的init函数中设置 openh264 的内存池大小sConfig.iMaxPicWidth 1280; sConfig.iMaxPicHeight 720; sConfig.iUsageType CAMERA_VIDEO_REAL_TIME; sConfig.uiTargetUsage 1; sConfig.iRCMode RC_OFF; // 关闭码率控制减少内存占用并确保--enable-small加入 configure它会启用av_mallocz_array等轻量内存函数。踩坑总结所有崩溃日志中的地址如0x7f8c3a1200都是libffmpeg.so的相对偏移。用aarch64-linux-android-addr2line -e android/arm64-v8a/lib/libffmpeg.so 0x7f8c3a1200可定位到具体 C 文件行号。这是比ndk-stack更精准的调试手段。5. 进阶技巧如何让单 so 支持多 ABI 并保持体积可控生产环境不可能只为arm64-v8a构建。但为四个 ABIarm64-v8a,armeabi-v7a,x86_64,x86分别构建APK 体积会爆炸。我们的方案是ABI 分离 按需加载 符号裁剪。5.1 构建矩阵自动化手写四个build_ffmpeg_android.sh太低效。我们用 Python 生成构建矩阵# build_matrix.py import subprocess import os ABIS [ {arch: arm64-v8a, target: aarch64-linux-android, api: 21}, {arch: armeabi-v7a, target: armv7a-linux-androideabi, api: 19}, {arch: x86_64, target: x86_64-linux-android, api: 21}, {arch: x86, target: i686-linux-android, api: 19}, ] for abi in ABIS: cmd f./build-scripts/build_ffmpeg_android.sh {abi[arch]} print(fRunning: {cmd}) subprocess.run(cmd, shellTrue, checkTrue)运行python3 build_matrix.py12 分钟后四个 ABI 的libffmpeg.so全部就绪。5.2 APK 体积优化从 42MB 到 18MB未优化的四 ABI APK 体积为 42MB。优化分三步第一步so 文件 stripNDK 的strip工具能移除调试符号$TOOLCHAIN/bin/aarch64-linux-android-strip --strip-unneeded android/arm64-v8a/lib/libffmpeg.so对每个 ABI 执行体积减少 30%。第二步ABI 过滤在app/build.gradle中android { splits { abi { reset() enable true universalApk false include arm64-v8a, armeabi-v7a } } }放弃x86和x86_64它们在模拟器上运行真机占比 0.3%。第三步FFmpeg 功能裁剪在 configure 中关闭不用的组件--disable-everything \ --enable-decoderh264,hevc,av1 \ --enable-encoderlibopenh264 \ --enable-parserh264,hevc,av1 \ --enable-demuxermp4,mov,avi,flv \ --enable-muxermp4,mov \ --enable-protocolfile,http,https \ --enable-filterscale,crop,rotate \ --enable-swresample \ --enable-swscale--disable-everything是关键它关闭所有默认启用的组件只打开明确需要的。实测下来arm64-v8a的 so 从 14.2MB 降到 6.8MB。5.3 运行时 ABI 选择策略不要在Application.onCreate()中无脑System.loadLibrary(ffmpeg)。应该public class FfmpegLoader { private static final String[] PREFERRED_ABIS { arm64-v8a, armeabi-v7a, x86_64, x86 }; public static void load() { String abi Build.SUPPORTED_ABIS[0]; // 获取最高优先级 ABI for (String preferred : PREFERRED_ABIS) { if (abi.startsWith(preferred)) { try { System.loadLibrary(ffmpeg- preferred); Log.i(FFMPEG, Loaded preferred); return; } catch (UnsatisfiedLinkError e) { Log.w(FFMPEG, Failed to load preferred, e); } } } throw new RuntimeException(No ffmpeg library loaded); } }这样arm64-v8a设备只会加载libffmpeg-arm64-v8a.so不会尝试加载armeabi-v7a版本。5.4 调试符号分离不影响线上包但保留 debug 能力发布 APK 时把调试符号抽出来单独存档# 构建后执行 mkdir -p symbols/ $TOOLCHAIN/bin/aarch64-linux-android-objcopy --only-keep-debug android/arm64-v8a/lib/libffmpeg.so symbols/libffmpeg-arm64-v8a.so.debug $TOOLCHAIN/bin/aarch64-linux-android-strip --strip-debug android/arm64-v8a/lib/libffmpeg.so线上 APK 用 stripped 版本崩溃时用ndk-stack -sym symbols/ -dump tombstone.txt还原堆栈。这是 Google Play Console 的标准做法。最后分享一个硬核技巧如果你的 App 需要支持 Android 10 的 scoped storagelibffmpeg.so里的avio_open无法直接访问file://URI。解决方案是在 JNI 层用ContentResolver.openAssetFileDescriptor获取ParcelFileDescriptor然后用dup()复制 fd再传给avio_open2的AVIOContext。这比改 FFmpeg 源码更安全且不破坏单 so 架构。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑