
1. 为什么要在Android中集成FFmpegFFmpeg是音视频处理领域的瑞士军刀几乎能处理所有主流格式的音视频文件。在Android平台上直接调用FFmpeg的C/C API而不是通过命令行能获得三大优势性能提升5-10倍命令行方式需要频繁创建进程和内存拷贝而直接调用API避免了这些开销。实测一个1080P视频转码命令行耗时12秒API调用仅需1.8秒精细控制可以逐帧处理视频实时调整编码参数实现滤镜链式处理等复杂操作功能完整性100%使用FFmpeg所有功能如硬件加速、自定义滤镜等典型应用场景包括短视频编辑裁剪/滤镜/转场直播推流实时编码/水印音视频转码HEVC/H.264转换媒体文件分析元数据提取2. 交叉编译FFmpeg生成动态库2.1 准备NDK环境推荐使用NDK r21e版本兼容性好且支持armeabi-v7a/arm64-v8a/x86/x86_64# 下载NDK wget https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip unzip android-ndk-r21e-linux-x86_64.zip -d ~/Library/Android/sdk/ndk在~/.bashrc中添加环境变量export ANDROID_NDK_HOME~/Library/Android/sdk/ndk/android-ndk-r21e export PATH$PATH:$ANDROID_NDK_HOME2.2 配置编译脚本新建build_ffmpeg.sh关键配置如下#!/bin/bash API21 ARCHarm64-v8a # 可替换为armeabi-v7a/x86/x86_64 PREFIX$(pwd)/android/$ARCH TOOLCHAIN$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64 ./configure \ --prefix$PREFIX \ --enable-shared \ --disable-static \ --disable-doc \ --disable-ffplay \ --disable-ffprobe \ --target-osandroid \ --archaarch64 \ # armv7a for 32-bit --enable-cross-compile \ --cross-prefix$TOOLCHAIN/bin/aarch64-linux-android- \ --sysroot$TOOLCHAIN/sysroot \ --extra-cflags-marcharmv8-a \ --extra-ldflags-pie2.3 执行编译# 下载FFmpeg源码 git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg cd ffmpeg # 赋予执行权限并编译 chmod x build_ffmpeg.sh ./build_ffmpeg.sh make -j8 make install编译完成后会在android/arm64-v8a目录生成include/头文件lib/动态库文件如libavcodec.so踩坑提示如果遇到relocation R_AARCH64_ADR_PREL_PG_HI21错误在extra-cflags中添加-fPIC参数3. Android Studio集成配置3.1 导入库文件将编译产物按以下结构放置app/ ├── libs/ │ ├── arm64-v8a/ │ │ ├── libavcodec.so │ │ └── ... └── src/main/cpp/ └── include/ # FFmpeg头文件3.2 配置CMakeLists.txt关键配置示例cmake_minimum_required(VERSION 3.10) # 设置FFmpeg库路径 set(FFMPEG_DIR ${CMAKE_SOURCE_DIR}/../../../../libs) include_directories(${CMAKE_SOURCE_DIR}/src/main/cpp/include) # 添加动态库 add_library(avcodec SHARED IMPORTED) set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION ${FFMPEG_DIR}/${ANDROID_ABI}/libavcodec.so) # 链接到本地库 add_library(native-lib SHARED native-lib.cpp) target_link_libraries(native-lib avcodec avfilter avformat avutil swresample swscale android log)3.3 修改build.gradle确保ABI过滤与编译的架构一致android { defaultConfig { externalNativeBuild { cmake { abiFilters arm64-v8a, armeabi-v7a cppFlags -stdc11 } } } sourceSets { main { jniLibs.srcDirs [libs] } } }4. JNI接口封装实战4.1 基础封装类创建FFmpegWrapper.cpp处理初始化#include jni.h #include libavformat/avformat.h extern C JNIEXPORT void JNICALL Java_com_example_FFmpegWrapper_init(JNIEnv *env, jobject thiz) { avformat_network_init(); // 初始化网络模块 av_log_set_level(AV_LOG_DEBUG); // 设置日志级别 }对应的Java接口public class FFmpegWrapper { static { System.loadLibrary(native-lib); } public native void init(); }4.2 视频解码示例实现视频解码到RGB的JNI方法extern C JNIEXPORT jint JNICALL Java_com_example_FFmpegWrapper_decodeVideo( JNIEnv *env, jobject thiz, jstring input_path, jobject surface) { const char *path env-GetStringUTFChars(input_path, 0); AVFormatContext *fmt_ctx NULL; // 打开输入文件 if(avformat_open_input(fmt_ctx, path, NULL, NULL) 0) { env-ReleaseStringUTFChars(input_path, path); return -1; } // 获取视频流索引 int video_idx av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); // 获取解码器 AVCodecParameters *codec_par fmt_ctx-streams[video_idx]-codecpar; AVCodec *codec avcodec_find_decoder(codec_par-codec_id); // 创建解码上下文 AVCodecContext *codec_ctx avcodec_alloc_context3(codec); avcodec_parameters_to_context(codec_ctx, codec_par); // 打开解码器 if(avcodec_open2(codec_ctx, codec, NULL) 0) { // 错误处理... } // 解码循环 AVPacket *pkt av_packet_alloc(); AVFrame *frame av_frame_alloc(); while(av_read_frame(fmt_ctx, pkt) 0) { if(pkt-stream_index video_idx) { avcodec_send_packet(codec_ctx, pkt); avcodec_receive_frame(codec_ctx, frame); // 渲染到Surface... } av_packet_unref(pkt); } // 释放资源... return 0; }5. 音视频转码完整案例5.1 实现MP4转GIF核心转码逻辑void convertToGif(const char *input, const char *output) { // 初始化格式上下文 AVFormatContext *in_fmt_ctx NULL; avformat_open_input(in_fmt_ctx, input, NULL, NULL); // 配置输出格式 AVFormatContext *out_fmt_ctx NULL; avformat_alloc_output_context2(out_fmt_ctx, NULL, gif, output); // 配置视频流 AVStream *in_stream in_fmt_ctx-streams[0]; AVStream *out_stream avformat_new_stream(out_fmt_ctx, NULL); // 配置编码器 AVCodecContext *enc_ctx avcodec_alloc_context3(NULL); enc_ctx-width in_stream-codecpar-width; enc_ctx-height in_stream-codecpar-height; enc_ctx-pix_fmt AV_PIX_FMT_RGB8; enc_ctx-time_base (AVRational){1, 15}; // 15FPS // 转码处理 // ... }5.2 Java层调用通过JNI暴露接口public class GifConverter { public static native int convert(String inputPath, String outputPath); // 使用示例 void startConvert() { new Thread(() - { int ret convert(/sdcard/input.mp4, /sdcard/output.gif); runOnUiThread(() - { Toast.makeText(this, ret0?成功:失败, Toast.LENGTH_SHORT).show(); }); }).start(); } }6. 性能优化技巧6.1 启用硬件加速修改解码器配置AVCodec *codec avcodec_find_decoder_by_name(h264_mediacodec); if(codec) { codec_ctx-get_format get_media_codec_format; }6.2 多线程解码设置线程数codec_ctx-thread_count 4; // 根据CPU核心数调整 codec_ctx-thread_type FF_THREAD_FRAME;6.3 内存管理使用AVBuffer减少拷贝AVFrame *frame av_frame_alloc(); frame-buf[0] av_buffer_alloc(data_size);7. 常见问题解决问题1SO文件加载失败检查System.loadLibrary()调用顺序确认ABI匹配adb shell查看设备ABIgetprop ro.product.cpu.abi问题2版本兼容性冲突保持所有FFmpeg库版本一致在CMake中指定精确版本find_library(avcodec NAMES avcodec-58 PATHS ${FFMPEG_DIR}/${ANDROID_ABI})问题3滤镜初始化失败确保编译时启用滤镜--enable-filterscale,crop,overlay在实际项目中我遇到过最棘手的问题是ANR应用无响应。后来发现是因为在UI线程执行了耗时的avformat_open_input操作。解决方案是将所有FFmpeg调用放到子线程使用Handler与主线程通信添加超时机制如avio_open2设置超时