Android项目中FFmpeg的.so包使用详情
-
- 新建一个C/C++项目
- 导入FFmpeg的so包
- Gradle文件配置
- CMakeLists.txt详细配置
- 测试调用
- 常见问题和解决方案
-
- [1. 路径问题](#1. 路径问题)
- [2. 架构不匹配](#2. 架构不匹配)
- [3. 依赖顺序问题](#3. 依赖顺序问题)
- [4. 头文件找不到](#4. 头文件找不到)
新建一个C/C++项目
在AS中选择Native C++创建项目

导入FFmpeg的so包
如何生成so包可以 参考这篇文章

- 在 Android Studio 中,打开你的项目,定位到app目录;
- 如果没有lib目录,右键New → Directory,命名为lib;
- 将不同架构的 so 文件(arm64-v8a)按上述目录结构复制到lib中;
Gradle文件配置
xml
defaultConfig {
// 指定支持的架构
ndk {
abiFilters.addAll(listOf("arm64-v8a", "x86"))
}
}
// 配置CMake
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
CMakeLists.txt详细配置
1.CmakeLists文件配置
xml
# 1. 设置最低CMake版本
cmake_minimum_required(VERSION 3.22.1)
# 2. 定义项目名称
project("ffmpegdemo")
# 3. 设置FFmpeg库路径
# CMAKE_SOURCE_DIR: 指向CMakeLists.txt所在目录 (app/src/main/cpp)
# ANDROID_ABI: Android构建系统提供的架构变量 (arm64-v8a, x86等)
set(FFMPEG_ROOT_DIR ${CMAKE_SOURCE_DIR}/../../../lib/${ANDROID_ABI})
set(FFMPEG_LIB_DIR ${FFMPEG_ROOT_DIR}/lib)
set(FFMPEG_INCLUDE_DIR ${FFMPEG_ROOT_DIR}/include)
# 4. 调试信息输出(可选,用于排查路径问题)
message(STATUS "CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}")
message(STATUS "ANDROID_ABI: ${ANDROID_ABI}")
message(STATUS "FFMPEG_ROOT_DIR: ${FFMPEG_ROOT_DIR}")
message(STATUS "FFMPEG_LIB_DIR: ${FFMPEG_LIB_DIR}")
message(STATUS "FFMPEG_INCLUDE_DIR: ${FFMPEG_INCLUDE_DIR}")
# 5. 添加头文件搜索路径
include_directories(${FFMPEG_INCLUDE_DIR})
# 6. 导入预编译的共享库
# 为每个FFmpeg库创建导入目标
add_library(avutil SHARED IMPORTED)
set_target_properties(avutil PROPERTIES
IMPORTED_LOCATION ${FFMPEG_LIB_DIR}/libavutil.so)
add_library(avcodec SHARED IMPORTED)
set_target_properties(avcodec PROPERTIES
IMPORTED_LOCATION ${FFMPEG_LIB_DIR}/libavcodec.so)
add_library(avformat SHARED IMPORTED)
set_target_properties(avformat PROPERTIES
IMPORTED_LOCATION ${FFMPEG_LIB_DIR}/libavformat.so)
add_library(avfilter SHARED IMPORTED)
set_target_properties(avfilter PROPERTIES
IMPORTED_LOCATION ${FFMPEG_LIB_DIR}/libavfilter.so)
add_library(avdevice SHARED IMPORTED)
set_target_properties(avdevice PROPERTIES
IMPORTED_LOCATION ${FFMPEG_LIB_DIR}/libavdevice.so)
add_library(swresample SHARED IMPORTED)
set_target_properties(swresample PROPERTIES
IMPORTED_LOCATION ${FFMPEG_LIB_DIR}/libswresample.so)
add_library(swscale SHARED IMPORTED)
set_target_properties(swscale PROPERTIES
IMPORTED_LOCATION ${FFMPEG_LIB_DIR}/libswscale.so)
# 7. 创建自己的共享库
add_library(${CMAKE_PROJECT_NAME} SHARED
native-lib.cpp
# 可以添加更多的cpp文件
)
# 8. 链接库
target_link_libraries(${CMAKE_PROJECT_NAME}
# FFmpeg库(按依赖顺序)
avformat # 格式处理,依赖avcodec和avutil
avcodec # 编解码器,依赖avutil
avfilter # 滤镜,依赖avutil
avdevice # 设备,依赖avformat等
swresample # 音频重采样,依赖avutil
swscale # 视频缩放,依赖avutil
avutil # 核心工具库,被其他库依赖
# Android系统库
android # Android NDK库
log # 日志库
)
2.JNI接口文件native-lib.cpp
xml
#include <jni.h>
#include <string>
#include <android/log.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavfilter/avfilter.h>
#include <libavdevice/avdevice.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
#define LOG_TAG "FFmpegDemo"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
extern "C" JNIEXPORT jstring JNICALL
Java_com_goolton_ffmpegdemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_goolton_ffmpegdemo_MainActivity_getFFmpegVersion(
JNIEnv* env,
jobject /* this */) {
// 获取FFmpeg版本信息
std::string version_info = "FFmpeg版本信息:\n";
// 获取各个库的版本
version_info += "libavutil: " + std::string(av_version_info()) + "\n";
version_info += "libavcodec: " + std::to_string(avcodec_version()) + "\n";
version_info += "libavformat: " + std::to_string(avformat_version()) + "\n";
version_info += "libavfilter: " + std::to_string(avfilter_version()) + "\n";
version_info += "libavdevice: " + std::to_string(avdevice_version()) + "\n";
version_info += "libswresample: " + std::to_string(swresample_version()) + "\n";
version_info += "libswscale: " + std::to_string(swscale_version());
LOGI("FFmpeg版本信息: %s", version_info.c_str());
return env->NewStringUTF(version_info.c_str());
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_goolton_ffmpegdemo_MainActivity_getFFmpegConfiguration(
JNIEnv* env,
jobject /* this */) {
// 获取FFmpeg配置信息
const char* config = avutil_configuration();
std::string config_info = "FFmpeg配置信息:\n" + std::string(config);
LOGI("FFmpeg配置信息: %s", config_info.c_str());
return env->NewStringUTF(config_info.c_str());
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_goolton_ffmpegdemo_FFmpegUtils_initFFmpeg(
JNIEnv* env,
jobject /* this */) {
try {
// 在FFmpeg 4.0+中,av_register_all()已经不需要了
// 但为了兼容性,我们可以保留网络初始化
avformat_network_init();
LOGI("FFmpeg初始化成功");
return JNI_TRUE;
} catch (...) {
LOGE("FFmpeg初始化失败");
return JNI_FALSE;
}
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_goolton_ffmpegdemo_FFmpegUtils_getSupportedCodecs(
JNIEnv* env,
jobject /* this */) {
std::string codecs_info = "支持的编解码器:\n";
const AVCodec* codec = nullptr;
void* iter = nullptr;
int count = 0;
while ((codec = av_codec_iterate(&iter)) && count < 20) { // 限制显示前20个
if (codec->name) {
codecs_info += std::string(codec->name) + " - ";
if (codec->long_name) {
codecs_info += std::string(codec->long_name);
}
codecs_info += "\n";
count++;
}
}
LOGI("获取编解码器列表完成,共显示%d个", count);
return env->NewStringUTF(codecs_info.c_str());
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_goolton_ffmpegdemo_FFmpegUtils_getSupportedFormats(
JNIEnv* env,
jobject /* this */) {
std::string formats_info = "支持的格式:\n";
const AVInputFormat* input_fmt = nullptr;
void* iter = nullptr;
int count = 0;
while ((input_fmt = av_demuxer_iterate(&iter)) && count < 15) { // 限制显示前15个
if (input_fmt->name) {
formats_info += std::string(input_fmt->name) + " - ";
if (input_fmt->long_name) {
formats_info += std::string(input_fmt->long_name);
}
formats_info += "\n";
count++;
}
}
LOGI("获取格式列表完成,共显示%d个", count);
return env->NewStringUTF(formats_info.c_str());
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_goolton_ffmpegdemo_FFmpegExample_getFFmpegVersionInfo(
JNIEnv* env,
jobject /* this */) {
std::string detailed_info = "=== FFmpeg详细版本信息 ===\n";
// 主版本信息
detailed_info += "FFmpeg版本: " + std::string(av_version_info()) + "\n\n";
// 各库版本(十六进制格式)
detailed_info += "库版本信息:\n";
detailed_info += "- libavutil: " + std::to_string(LIBAVUTIL_VERSION_MAJOR) + "." +
std::to_string(LIBAVUTIL_VERSION_MINOR) + "." +
std::to_string(LIBAVUTIL_VERSION_MICRO) + "\n";
detailed_info += "- libavcodec: " + std::to_string(LIBAVCODEC_VERSION_MAJOR) + "." +
std::to_string(LIBAVCODEC_VERSION_MINOR) + "." +
std::to_string(LIBAVCODEC_VERSION_MICRO) + "\n";
detailed_info += "- libavformat: " + std::to_string(LIBAVFORMAT_VERSION_MAJOR) + "." +
std::to_string(LIBAVFORMAT_VERSION_MINOR) + "." +
std::to_string(LIBAVFORMAT_VERSION_MICRO) + "\n";
// 许可证信息
detailed_info += "\n许可证: " + std::string(avutil_license()) + "\n";
LOGI("获取详细版本信息完成");
return env->NewStringUTF(detailed_info.c_str());
}
测试调用
新建一个activity,调用displayFFmpegInfo
java
private fun displayFFmpegInfo() {
try {
// 构建完整的版本信息显示
val versionInfo = StringBuilder()
versionInfo.append("=== FFmpeg Android Demo ===\n\n")
// 基本版本信息
val basicVersion = getFFmpegVersion()
versionInfo.append(basicVersion).append("\n\n")
// 详细版本信息
val detailedVersion = FFmpegExample.getFFmpegVersionInfo()
versionInfo.append(detailedVersion).append("\n\n")
// 支持的编解码器(前几个)
versionInfo.append("=== 部分支持的编解码器 ===\n")
val codecs = FFmpegUtils.getSupportedCodecs()
versionInfo.append(codecs).append("\n")
// 支持的格式(前几个)
versionInfo.append("=== 部分支持的格式 ===\n")
val formats = FFmpegUtils.getSupportedFormats()
versionInfo.append(formats)
// 显示在TextView中
binding.sampleText.text = versionInfo.toString()
// 在日志中显示配置信息
val configInfo = getFFmpegConfiguration()
android.util.Log.i("FFmpegDemo", "配置信息: $configInfo")
} catch (e: Exception) {
binding.sampleText.text = "获取FFmpeg信息时出错: ${e.message}"
android.util.Log.e("FFmpegDemo", "获取FFmpeg信息失败", e)
}
}
/**
* 获取FFmpeg版本信息
*/
external fun getFFmpegVersion(): String
/**
* 获取FFmpeg配置信息
*/
external fun getFFmpegConfiguration(): String
companion object {
// 加载native库
init {
System.loadLibrary("ffmpegdemo")
}
}
验证:出现以下结果即是已经导入成功,可以进行需求定制功能的接口调用

常见问题和解决方案
1. 路径问题
问题 : ninja: error: 'xxx.so', needed by 'yyy.so', missing
解决方案:
cmake
# 检查路径是否正确
message(STATUS "Library path: ${FFMPEG_LIB_DIR}/libavutil.so")
# 使用绝对路径进行调试
set_target_properties(avutil PROPERTIES
IMPORTED_LOCATION "/absolute/path/to/libavutil.so")
2. 架构不匹配
问题: 库文件架构与目标架构不匹配
解决方案:
kotlin
// 在build.gradle.kts中明确指定架构
ndk {
abiFilters.addAll(listOf("arm64-v8a")) // 只构建arm64
}
3. 依赖顺序问题
问题: 链接时出现未定义符号
解决方案:
cmake
# 正确的链接顺序:被依赖的库放在后面
target_link_libraries(${CMAKE_PROJECT_NAME}
avformat # 依赖avcodec
avcodec # 依赖avutil
avutil # 基础库
)
4. 头文件找不到
问题 : fatal error: 'libavcodec/avcodec.h' file not found
解决方案:
cmake
# 确保头文件路径正确
include_directories(${FFMPEG_INCLUDE_DIR})
# 或者使用target_include_directories
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
${FFMPEG_INCLUDE_DIR})