Android项目中FFmpeg的.so包使用详情

Android项目中FFmpeg的.so包使用详情

新建一个C/C++项目

在AS中选择Native C++创建项目

导入FFmpeg的so包

如何生成so包可以 参考这篇文章

  1. 在 Android Studio 中,打开你的项目,定位到app目录;
  2. 如果没有lib目录,右键New → Directory,命名为lib;
  3. 将不同架构的 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})
相关推荐
2501_915909062 小时前
iPhone 手机日志实时查看,开发和测试中常用的几种方法
android·ios·智能手机·小程序·uni-app·iphone·webview
ClassOps2 小时前
记录 Android WebView内核更新,安全区 和 Insets 消费问题
android·webview·compose
java_nn2 小时前
使用ffmpeg直播推流
ffmpeg
智能工业品检测-奇妙智能2 小时前
快速直播:Node.js + FFmpeg + flv.js 全栈实战
javascript·ffmpeg·node.js
ysh98882 小时前
2025年 Android Studio修仙传(kotlin版):基础篇
android·kotlin·android studio
XiaoLeisj2 小时前
Android 网络编程入门到实战:HttpURLConnection、JSON 处理、OkHttp 与 Retrofit2
android·网络·okhttp·json·gson·retrofit2·jsonobjecy
fengci.2 小时前
ctfshow36D杯
android
艾莉丝努力练剑3 小时前
【MYSQL】MYSQL学习的一大重点:MYSQL库的操作
android·linux·运维·数据库·人工智能·学习·mysql
Okailon3 小时前
Linux上的录屏经历 kazam OBS ffmpeg 及 oCam(Win) filmage screen(Mac)
linux·macos·ffmpeg