2024年Android音视频开发进阶-(一)-NDK编译FFmpeg实战:从环境搭建到动态库集成
1. 环境准备:搭建NDK与FFmpeg编译环境
在开始编译FFmpeg之前,我们需要先准备好开发环境。这里我推荐使用Ubuntu系统进行编译,因为Linux环境下工具链更完整,遇到问题也更容易找到解决方案。不过如果你习惯使用MacOS,大部分命令也是通用的。
首先需要安装必要的依赖库,打开终端执行以下命令:
sudo apt-get update
sudo apt-get install -y build-essential git cmake make yasm nasm pkg-config
这些基础工具是编译过程中必不可少的。其中yasm和nasm是汇编编译器,FFmpeg为了提高编解码效率使用了大量汇编指令,缺少这两个工具会导致编译失败。
接下来需要下载NDK工具链。我建议使用NDK r20b版本,这个版本对FFmpeg 4.2.2的兼容性较好。可以从官网下载后解压到指定目录:
wget https://dl.google.com/android/repository/android-ndk-r20b-linux-x86_64.zip
unzip android-ndk-r20b-linux-x86_64.zip -d ~/android
设置NDK环境变量,方便后续脚本调用:
export NDK=~/android/android-ndk-r20b
export PATH=$NDK:$PATH
然后是下载FFmpeg源码。这里我们使用4.2.2版本:
wget https://ffmpeg.org/releases/ffmpeg-4.2.2.tar.bz2
tar -xvjf ffmpeg-4.2.2.tar.bz2
cd ffmpeg-4.2.2
1.1 配置编译工具链
NDK从r18开始就移除了gcc工具链,全面转向clang。所以在配置时需要特别注意工具链的选择。我们使用NDK自带的clang进行编译:
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64
这个路径下包含了针对不同Android CPU架构的clang编译器。后续我们会根据目标平台选择对应的编译器。
2. 编写编译脚本:支持多平台架构
现在我们来编写编译脚本,支持armeabi-v7a和arm64-v8a两种架构。创建一个build_android.sh文件:
#!/bin/bash
echo ">>>>>>>>> 开始编译FFmpeg <<<<<<<<<"
echo ">>>>>>>>> 编译环境:NDK r20b + FFmpeg 4.2.2 <<<<<<<<<"
# 设置NDK路径
export NDK=/root/android/ndk/android-ndk-r20b
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64
function build_android {
echo "开始编译 $CPU"
./configure \
--prefix=$PREFIX \
--enable-neon \
--enable-hwaccels \
--enable-gpl \
--enable-postproc \
--enable-shared \
--disable-static \
--disable-debug \
--enable-small \
--enable-jni \
--enable-mediacodec \
--enable-decoder=h264_mediacodec \
--disable-doc \
--disable-ffplay \
--disable-ffprobe \
--disable-avdevice \
--disable-symver \
--cross-prefix=$CROSS_PREFIX \
--target-os=android \
--arch=$ARCH \
--cpu=$CPU \
--cc=$CC \
--cxx=$CXX \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS"
make clean
make -j8
make install
echo "编译成功 $CPU"
}
# 编译arm64-v8a
ARCH=arm64
CPU=armv8-a
API=21
CC=$TOOLCHAIN/bin/aarch64-linux-android$API-clang
CXX=$TOOLCHAIN/bin/aarch64-linux-android$API-clang++
SYSROOT=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot
CROSS_PREFIX=$TOOLCHAIN/bin/aarch64-linux-android-
PREFIX=$(pwd)/android/$CPU
OPTIMIZE_CFLAGS="-march=$CPU"
build_android
# 编译armeabi-v7a
ARCH=arm
CPU=armv7-a
API=16
CC=$TOOLCHAIN/bin/armv7a-linux-androideabi$API-clang
CXX=$TOOLCHAIN/bin/armv7a-linux-androideabi$API-clang++
SYSROOT=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot
CROSS_PREFIX=$TOOLCHAIN/bin/arm-linux-androideabi-
PREFIX=$(pwd)/android/$CPU
OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU"
build_android
给脚本添加执行权限:
chmod +x build_android.sh
2.1 关键配置参数解析
这个脚本中有几个关键配置需要特别注意:
--enable-shared:生成动态库(.so文件),如果只需要静态库可以改为--enable-static--enable-neon:启用NEON指令集加速,对armeabi-v7a架构性能提升明显--enable-jni:启用JNI支持,方便Java层调用--enable-mediacodec:启用Android硬解码支持--disable-avdevice:禁用设备相关模块,在Android上通常不需要--extra-cflags:添加编译优化选项,-Os表示优化大小,-fpic生成位置无关代码
3. 常见编译问题与解决方案
在实际编译过程中,可能会遇到各种问题。这里我总结几个常见的错误和解决方法。
3.1 nasm/yasm not found
错误信息:
nasm/yasm not found or too old. Use --disable-x86asm for a crippled build.
解决方法:
sudo apt-get install yasm nasm
如果还是报错,可以临时禁用x86汇编:
./configure --disable-x86asm
3.2 链接器错误
有时会遇到类似下面的链接错误:
aarch64-linux-android-ld: cannot find -lc
这是因为没有正确设置sysroot路径。确保在configure参数中正确指定了:
--sysroot=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot
3.3 版本兼容性问题
如果遇到GLIBC版本不兼容的问题,比如:
libc.so.6: version `GLIBC_2.18' not found
可以考虑在Docker容器中使用统一的环境进行编译,避免主机环境差异带来的问题。
4. 集成到Android项目
编译完成后,会在android目录下生成对应架构的动态库。接下来我们看看如何集成到Android Studio项目中。
4.1 项目结构配置
将编译好的so库放入Android项目的libs目录:
app/
libs/
arm64-v8a/
libavcodec.so
libavformat.so
...
armeabi-v7a/
libavcodec.so
libavformat.so
...
在app的build.gradle中配置:
android {
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
4.2 CMake配置
创建CMakeLists.txt文件:
cmake_minimum_required(VERSION 3.4.1)
# 设置FFmpeg头文件路径
include_directories(${CMAKE_SOURCE_DIR}/include)
# 添加FFmpeg库
add_library(avcodec SHARED IMPORTED)
set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/libs/${CMAKE_ANDROID_ARCH_ABI}/libavcodec.so)
add_library(avformat SHARED IMPORTED)
set_target_properties(avformat PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/libs/${CMAKE_ANDROID_ARCH_ABI}/libavformat.so)
# 添加你的本地库
add_library(native-lib SHARED native-lib.cpp)
# 链接库
target_link_libraries(native-lib
avcodec
avformat
avutil
swresample
swscale
log)
4.3 简单测试代码
创建一个简单的JNI函数测试FFmpeg版本:
#include <jni.h>
#include <string>
extern "C" {
#include <libavutil/avutil.h>
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ffmpegdemo_MainActivity_getFFmpegVersion(
JNIEnv* env,
jobject /* this */) {
return env->NewStringUTF(av_version_info());
}
在Java层调用:
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.sample_text);
tv.setText(getFFmpegVersion());
}
public native String getFFmpegVersion();
}
如果一切正常,运行应用后会显示FFmpeg的版本信息,表明集成成功。
5. 进阶配置与优化
5.1 启用硬件加速
为了充分利用Android设备的硬件编解码能力,可以在编译时启用mediacodec:
--enable-mediacodec \
--enable-decoder=h264_mediacodec \
--enable-decoder=hevc_mediacodec \
--enable-decoder=mpeg4_mediacodec \
--enable-decoder=vp8_mediacodec \
--enable-decoder=vp9_mediacodec
5.2 裁剪FFmpeg模块
FFmpeg功能强大但体积较大,可以通过禁用不需要的模块来减小库文件大小:
--disable-avdevice \
--disable-postproc \
--disable-swresample \
--disable-swscale \
--disable-avfilter \
--disable-everything \
--enable-decoder=h264 \
--enable-decoder=aac \
--enable-demuxer=mov \
--enable-demuxer=mpegts \
--enable-protocol=file
5.3 性能优化选项
在extra-cflags中添加优化参数:
--extra-cflags="-O3 -fstrict-aliasing -fPIC -DANDROID -D__ANDROID__ -D__ANDROID_API__=$API"
其中:
-O3:最高级别优化-fstrict-aliasing:启用严格别名规则-fPIC:生成位置无关代码-DANDROID:定义Android平台宏
6. 实际项目中的经验分享
在真实项目中集成FFmpeg时,有几个坑需要特别注意:
-
ABI兼容性:现在Google Play要求应用必须支持64位架构,所以至少要编译arm64-v8a版本。如果还要兼容老设备,可以同时提供armeabi-v7a版本。
-
动态库加载顺序:FFmpeg各个库之间有依赖关系,加载顺序很重要。正确的顺序是:
System.loadLibrary("avutil"); System.loadLibrary("swresample"); System.loadLibrary("avcodec"); System.loadLibrary("avformat"); System.loadLibrary("swscale"); System.loadLibrary("avfilter"); -
版本管理:建议将编译好的so库和头文件放入版本控制系统,方便团队共享。同时记录编译时的配置参数,便于后续维护。
-
调试技巧:遇到native crash时,可以使用addr2line工具定位问题:
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-addr2line -e libnative-lib.so <address> -
内存管理:FFmpeg有自己的内存管理机制,使用av_malloc/av_free等函数分配释放内存。如果与Java层交互,要特别注意内存泄漏问题。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)