Android FFmpeg 交叉编译全指南:NDK编译 + CMake 集成

开发环境搭建

下载最新版Android Studio,安装SDK和模拟器

在Android Studio中配置:Setting → Android SDK → SDK Tools → 勾选CMake 和 NDK → Apply,按照提示下载安装

  • SDK是Android应用开发的基础工具包,适合大多数上层逻辑开发。
  • NDK是补充工具,用于需要直接控制硬件或高性能计算的场景。允许开发者使用C/C++编写高性能代码,直接运行于Android设备硬件。

创建Native C++项目

JNI:Java 原生接口

JNI中只能调用C函数(C++因为函数重载名字会被修改,所以要用extern "C"修饰)

可以在头文件中添加#include <android/log.h> ,使用

__android_log_print(*ANDROID_LOG_INFO*, "Test", "test for android log");语句测试

FFmpeg交叉编译

基础知识

应用二进制接口(ABI):armeabi / x86

  • CPU指令集
  • 内存字节序
  • 可执行二进制的格式
  • 解析的各种约定:对齐限制、堆栈使用和调用函数
  • 系统库集

常见的架构包括:

目录名 架构 Android ABI名称 常见设备 位数
aarch64-linux-android ARM64(ARMv8-A) arm64-v8a 现代Android手机(如骁龙8系、天玑9000) 64位
arm-linux-androideabi ARMv7(兼容ARMv6) armeabi-v7a 旧款手机(如骁龙625、Helio P10) 32位
i686-linux-android x86(Intel 32位) x86 模拟器或老旧x86平板(如部分ZenFone) 32位
x86_64-linux-android x86-64(Intel 64位) x86_64 现代x86模拟器或Chromebook 64位
riscv64-linux-android RISC-V 64位 riscv64 新兴架构(如部分IoT设备或实验性设备) 64位

NEON(浮点数协处理器 )是ARM架构中的一种SIMD(单指令多数据)指令集扩展,专门用于加速浮点数和整数运算,尤其在多媒体处理、信号计算等场景中性能提升显著。

具体步骤

网络上交叉编译Android平台的ffmpeg都是手动执行脚本,例如:https://github.com/AnJoiner/FFmpegCommand/blob/master/ffmpeg-wiki/编译FFmpeg.md

可是这种硬编码随着ffmpeg版本和ndk版本不同存在很大的缺陷:ndk的目录组织结构在不同版本差异很大,导致toolchain等路径都得重新设置。现代 NDK(r21+)提供了更便捷的 android.toolchain.cmake 工具链文件,可以大幅简化交叉编译流程。

  • 无需手动配置编译器路径:CMake 自动识别 NDK 中的工具链。
  • 支持多架构并行编译:通过 ANDROID_ABI 参数一键切换。
  • 更好的兼容性:避免因路径或环境变量错误导致的编译失败。

通过使用**ExternalProject_Add 命令,在 CMake 中集成外部项目的构建过程**(如 FFmpeg 这种非 CMake 项目)

makefile 复制代码
include(ExternalProject)  # 必须包含该模块

ExternalProject_Add(
    <项目名称>            # 自定义标识(如 "ffmpeg")
    
    # --- 关键参数 ---
    SOURCE_DIR      <path>       # 源码目录(如 ${CMAKE_SOURCE_DIR}/ffmpeg)
    CONFIGURE_COMMAND <cmd>      # 配置命令(如 ./configure --prefix=...)
    BUILD_COMMAND     <cmd>      # 构建命令(如 make -j8)
    INSTALL_COMMAND   <cmd>      # 安装命令(如 make install)
    
    # --- 可选参数 ---
    BUILD_IN_SOURCE   TRUE       # 在源码目录内构建(适用于 FFmpeg)
    INSTALL_DIR       <path>     # 安装路径(等价于 --prefix)
    DEPENDS           <targets>  # 依赖的其他 CMake 目标
)

参考官方文档:https://developer.android.com/ndk/guides/cmake?hl=zh-cn#command-line

参考编译脚本:https://umirtech.com/how-to-compile-build-ffmpeg-for-android-and-use-it-in-android-studio/

  1. 准备 FFmpeg 源码

    bash 复制代码
    git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg
    cd ffmpeg
  2. 创建**CMakeLists.txt**

    在 FFmpeg 根目录新建 CMakeLists.txt,内容如下:

    makefile 复制代码
    cmake_minimum_required(VERSION 3.10)
    project(ffmpeg_android)
    # 基本配置(可通过命令行参数覆盖)
    set(ANDROID_ABI "arm64-v8a" CACHE STRING "Target ABI")
    set(ANDROID_STL "c++_static" CACHE STRING "C++ STL implementation")
    set(ANDROID_PLATFORM "android-24" CACHE STRING "Android platform version")
    
    # 指定 NDK 工具链文件(通过命令行参数传递)
    if(NOT CMAKE_TOOLCHAIN_FILE)
        message(FATAL_ERROR "Please specify NDK toolchain file with -DCMAKE_TOOLCHAIN_FILE=")
    endif()
    # 获取 NDK 工具链路径
    set(ANDROID_NDK_TOOLCHAIN_DIR "${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/darwin-x86_64/bin")
    
    # 从工具链文件中获取的信息
    message(STATUS "Using NDK: ${CMAKE_ANDROID_NDK}")
    message(STATUS "Using NDK toolchain dir: ${ANDROID_NDK_TOOLCHAIN_DIR}")
    message(STATUS "Target ABI: ${ANDROID_ABI}")
    message(STATUS "Android platform: ${ANDROID_PLATFORM}")
    message(STATUS "SYSROOT: ${CMAKE_SYSROOT}")
    message(STATUS "CC: ${CMAKE_C_COMPILER}")
    
    # 根据 ABI 设置 FFmpeg 特定参数
    if(ANDROID_ABI STREQUAL "arm64-v8a")
        set(TARGET_ARCH "aarch64")
        set(TARGET_CPU "armv8-a")
        set(TARGET_TRIPLE "aarch64-linux-android")
        set(EXTRA_CFLAGS "-fPIC -DANDROID -O3 -march=${TARGET_CPU} -fomit-frame-pointer")
        set(EXTRA_CONFIG "--enable-neon")
    elseif(ANDROID_ABI STREQUAL "armeabi-v7a")
        set(TARGET_ARCH "arm")
        set(TARGET_CPU "armv7-a")
        set(TARGET_TRIPLE "armv7a-linux-androideabi")
        set(EXTRA_CFLAGS "-fPIC -DANDROID -O3 -marm -march=${TARGET_CPU} -mfpu=neon -fomit-frame-pointer")
        set(EXTRA_CONFIG "--enable-neon --disable-armv5te --disable-armv6 --disable-armv6t2")
    endif()
    
    set(CMAKE_C_COMPILER "${ANDROID_NDK_TOOLCHAIN_DIR}/${TARGET_TRIPLE}${ANDROID_PLATFORM_LEVEL}-clang")
    set(CMAKE_CXX_COMPILER "${ANDROID_NDK_TOOLCHAIN_DIR}/${TARGET_TRIPLE}${ANDROID_PLATFORM_LEVEL}-clang++")
    
    # 设置 FFmpeg 配置选项
    set(ENABLED_CONFIG
        --enable-small
        --enable-avcodec
        --enable-avformat
        --enable-avutil
        --enable-swscale
        --enable-swresample
        --enable-demuxers
        --enable-parser=*
        --enable-decoders
        --enable-shared
        --enable-nonfree
        --enable-gpl
        --enable-small
        --enable-neon
        --enable-hwaccels
        --enable-avdevice
        --enable-jni
        --enable-mediacodec
        --enable-decoder=h264_mediacodec
        --enable-hwaccel=h264_mediacodec
    )
    
    set(DISABLED_CONFIG
        --disable-zlib
        --disable-v4l2-m2m
        --disable-cuda-llvm
        --disable-indevs
        --disable-libxml2
        --disable-avdevice
        --disable-network
        --disable-static
        --disable-debug
        --disable-ffmpeg
        --disable-ffplay
        --disable-ffprobe
        --disable-doc
        --disable-symver
        --disable-programs
    )
    
    # 添加 FFmpeg 为外部项目
    include(ExternalProject)
    ExternalProject_Add(ffmpeg
        SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}
        CONFIGURE_COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/configure
            --prefix=${CMAKE_BINARY_DIR}/ffmpeg_install/${ANDROID_ABI}
            --target-os=android
            --arch=${TARGET_ARCH}
            --cpu=${TARGET_CPU}
            --enable-cross-compile
            --cross-prefix=${ANDROID_TOOLCHAIN_PREFIX}
            --cc=${CMAKE_C_COMPILER}
            --cxx=${CMAKE_CXX_COMPILER}
            --sysroot=${CMAKE_SYSROOT}
            --extra-cflags=${EXTRA_CFLAGS}
            --extra-ldflags="-L${CMAKE_SYSROOT}/usr/lib/${TARGET_ARCH}-linux-android/${ANDROID_PLATFORM_LEVEL}"
            --ar=${CMAKE_AR}
            --nm=${CMAKE_NM}
            --ranlib=${CMAKE_RANLIB}
            --strip=${CMAKE_STRIP}
            ${ENABLED_CONFIG}
            ${DISABLED_CONFIG}
            ${EXTRA_CONFIG}
        BUILD_COMMAND make -j8
        INSTALL_COMMAND make install
        BUILD_IN_SOURCE TRUE
    )
  3. 执行 CMake 编译

    bash 复制代码
    mkdir build && cd build
    # NDK=<your ndk path> # 根据你的NDK路径设置NDK变量
    # NDK=/Users/cloud.wang/Library/Android/sdk/ndk/29.0.13113456
    cmake .. -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake
    make
  4. 输出结果

    编译后的头文件、动态库(.so 文件)会生成在 build目录的 ffmpeg_install 下的includelib目录中。

./configure报错输出的详细信息位于ffbuild/config.log文件中,如果遇到问题可以针对详细报错进行修改调试。

如果不愿意使用CMake,也可以尝试使用bash脚本,我通过参考博主MOHD UMIR的编译脚本,根据我的Mac环境进行修改后可以编译成功。

bash 复制代码
#!/bin/bash

### Describe Your Target Architectures ###
ARCH_LIST=( "armv8a" ) 

### Change As per Yours ####
BUILD_PLATFORM=darwin
ANDROID_API_LEVEL="24"
ANDROID_NDK_PATH="/Users/cloud.wang/Library/Android/sdk/ndk/29.0.13113456"
FFMPEG_SOURCE_DIR="/Users/cloud.wang/Code/ffmpeg7/ffmpeg-7.0.2"
FFMPEG_BUILD_DIR="/Users/cloud.wang/Code/build_ffmpeg_android"

### Enable FFMPEG BUILD MODULES ####
ENABLED_CONFIG="\
    --enable-small \
		--enable-avcodec \
		--enable-avformat \
		--enable-avutil \
		--enable-swscale \
		--enable-swresample \
		--enable-demuxers \
		--enable-parser=* \
		--enable-decoders \
		--enable-shared  \
		--enable-nonfree \
		--enable-gpl \
		--enable-small \
		--enable-neon \
		--enable-hwaccels \
		--enable-avdevice \
		--enable-jni \
		--enable-mediacodec \
		--enable-decoder=h264_mediacodec \
		--enable-hwaccel=h264_mediacodec "

### Disable FFMPEG BUILD MODULES ####
DISABLED_CONFIG="\
		--disable-zlib \
		--disable-v4l2-m2m \
		--disable-cuda-llvm \
		--disable-indevs \
		--disable-libxml2 \
		--disable-avdevice \
		--disable-network \
		--disable-static \
		--disable-debug \
		--disable-ffmpeg \
		--disable-ffplay \
		--disable-ffprobe \
		--disable-doc \
		--disable-symver \
		--disable-programs "

### Dont Change ####
SYSROOT="$ANDROID_NDK_PATH/toolchains/llvm/prebuilt/$BUILD_PLATFORM-x86_64/sysroot"
LLVM_AR="$ANDROID_NDK_PATH/toolchains/llvm/prebuilt/$BUILD_PLATFORM-x86_64/bin/llvm-ar"
LLVM_NM="$ANDROID_NDK_PATH/toolchains/llvm/prebuilt/$BUILD_PLATFORM-x86_64/bin/llvm-nm"
LLVM_RANLIB="$ANDROID_NDK_PATH/toolchains/llvm/prebuilt/$BUILD_PLATFORM-x86_64/bin/llvm-ranlib"
LLVM_STRIP="$ANDROID_NDK_PATH/toolchains/llvm/prebuilt/$BUILD_PLATFORM-x86_64/bin/llvm-strip"

configure_ffmpeg(){
   TARGET_ARCH=$1
   TARGET_CPU=$2
   CROSS_PREFIX=$3
   EXTRA_CFLAGS=$4
   EXTRA_CONFIG=$5
   
   CLANG="${CROSS_PREFIX}clang"
   CLANGXX="${CROSS_PREFIX}clang++"
   PREFIX="${FFMPEG_BUILD_DIR}/$TARGET_ARCH-$ANDROID_API_LEVEL"
   
   cd $FFMPEG_SOURCE_DIR
   
   ./configure \
   --disable-everything \
   --target-os=android \
   --arch=$TARGET_ARCH \
   --cpu=$TARGET_CPU \
   --enable-cross-compile \
   --cross-prefix="$CROSS_PREFIX" \
   --cc="$CLANG" \
   --cxx="$CLANGXX" \
   --sysroot="$SYSROOT" \
   --prefix="$PREFIX" \
   --extra-cflags="-fPIC -DANDROID $EXTRA_CFLAGS" \
   --extra-ldflags="-L$SYSROOT/usr/lib/$TARGET_ARCH-linux-android/$ANDROID_API_LEVEL" \
   ${ENABLED_CONFIG} \
   ${DISABLED_CONFIG} \
   --ar="$LLVM_AR" \
   --nm="$LLVM_NM" \
   --ranlib="$LLVM_RANLIB" \
   --strip="$LLVM_STRIP" \
   ${EXTRA_CONFIG}

   make clean
   make -j 8
   make install -j2
   
}

echo -e "\e[1;32mCompiling FFMPEG for Android...\e[0m"

for ARCH in "${ARCH_LIST[@]}"; do
    case "$ARCH" in
        "armv8-a"|"aarch64"|"arm64-v8a"|"armv8a")
            echo -e "\e[1;32m$ARCH Libraries\e[0m"
            TARGET_ARCH="aarch64"
            TARGET_CPU="armv8-a"
            TARGET_ABI="aarch64"
            CROSS_PREFIX="$ANDROID_NDK_PATH/toolchains/llvm/prebuilt/$BUILD_PLATFORM-x86_64/bin/$TARGET_ABI-linux-android${ANDROID_API_LEVEL}-"
            EXTRA_CFLAGS="-O3 -march=$TARGET_CPU -fomit-frame-pointer"
            EXTRA_CONFIG="\
            		--enable-neon "
            ;;
        "armv7-a"|"armeabi-v7a"|"armv7a")
            echo -e "\e[1;32m$ARCH Libraries\e[0m"
            TARGET_ARCH="arm"
            TARGET_CPU="armv7-a"
            TARGET_ABI="armv7a"
            CROSS_PREFIX="$ANDROID_NDK_PATH/toolchains/llvm/prebuilt/$BUILD_PLATFORM-x86_64/bin/$TARGET_ABI-linux-androideabi${ANDROID_API_LEVEL}-"
            EXTRA_CFLAGS="-O3 -marm -march=$TARGET_CPU -mfpu=neon -fomit-frame-pointer"
            EXTRA_CONFIG="\
            		--disable-armv5te \
            		--disable-armv6 \
            		--disable-armv6t2 \
            		--enable-neon "
            ;;
        "x86-64"|"x86_64")
            echo -e "\e[1;32m$ARCH Libraries\e[0m"
            TARGET_ARCH="x86_64"
            TARGET_CPU="x86-64"
            TARGET_ABI="x86_64"
            CROSS_PREFIX="$ANDROID_NDK_PATH/toolchains/llvm/prebuilt/$BUILD_PLATFORM-x86_64/bin/$TARGET_ABI-linux-android${ANDROID_API_LEVEL}-"
            EXTRA_CFLAGS="-O3 -march=$TARGET_CPU -fomit-frame-pointer"
            		
            EXTRA_CONFIG="\
            		  "
            ;;
        "x86"|"i686")
            echo -e "\e[1;32m$ARCH Libraries\e[0m"
            TARGET_ARCH="i686"
            TARGET_CPU="i686"
            TARGET_ABI="i686"
            CROSS_PREFIX="$ANDROID_NDK_PATH/toolchains/llvm/prebuilt/$BUILD_PLATFORM-x86_64/bin/$TARGET_ABI-linux-android${ANDROID_API_LEVEL}-"
            EXTRA_CFLAGS="-O3 -march=$TARGET_CPU -fomit-frame-pointer"
            EXTRA_CONFIG="\
            		--disable-asm "
            ;;
           * )
            echo "Unknown architecture: $ARCH"
            exit 1
            ;;
    esac
    configure_ffmpeg "$TARGET_ARCH" "$TARGET_CPU" "$CROSS_PREFIX" "$EXTRA_CFLAGS" "$EXTRA_CONFIG"
done

Android Studio配置

在编译好Android平台的ffmpeg库文件后,需要在Android Studio中进行一些配置才能够实现在Android中调用ffmpeg。

首先我们需要将头文件和库文件拷贝到项目中,最好是拷贝到src/main/cpp目录下(和CMakeLists.txt同级),拷贝后的目录结构如下:

bash 复制代码
cpp
├── CMakeLists.txt
├── include
│   └── arm64-v8a
│       ├── libavcodec
│       ├── libavfilter
│       ├── libavformat
│       ├── libavutil
│       ├── libpostproc
│       ├── libswresample
│       └── libswscale
├── lib
│   └── arm64-v8a
│       ├── libavcodec.so
│       ├── libavfilter.so
│       ├── libavformat.so
│       ├── libavutil.so
│       ├── libpostproc.so
│       ├── libswresample.so
│       └── libswscale.so
└── native-lib.cpp

接下来需要在CMakeLists.txt中配置ffmpeg的头文件和库文件,在CMakeLists.txt中添加以下内容。需要注意的是我这里目前只添加了avcodec库,实际项目中根据需要应该添加需要的所有的ffmpeg库。

bash 复制代码
# FFmpeg
message(STATUS "ANDROID_ABI: ${ANDROID_ABI}")
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include/${ANDROID_ABI})
set(ffmpeg_lib ${CMAKE_CURRENT_SOURCE_DIR}/lib/${ANDROID_ABI})
add_library(avcodec SHARED IMPORTED)
set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION ${ffmpeg_lib}/libavcodec.so)

add_library(${CMAKE_PROJECT_NAME} SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        native-lib.cpp)

target_link_libraries(${CMAKE_PROJECT_NAME}
        # ffmpeg
        avcodec
        # List libraries link to the target library
        android
        log)

值得注意的是CMake中的${ANDROID_ABI} 变量的内容是被Android Studio自动设置的,为了指定编译的ABI,需要在build.gradle.kts(:app)中进行配置。找到android下配置添加以下内容:

kotlin 复制代码
defaultConfig {
		//...
    externalNativeBuild {
        //...
        ndk {
            abiFilters += "arm64-v8a"
        }
    }
}

sourceSets {
    getByName("main") {
        jniLibs.srcDirs("src/main/cpp/lib")
    }
}

ndk.abiFilters指定了ndk编译的abi平台,jniLibs指定了需要打包的库文件目录。需要注意的是jniLibs要求路径下是对应abi平台名称的文件夹,文件夹中是对应的库文件。

旧版本Android Studio可能是在build.gradle中配置,基本内容是一样的,不过语法有些不同。

修改完成同步之后,在native-lib.cpp中添加以下内容:

cpp 复制代码
#include <jni.h>
#include <string>
#include <android/log.h>

extern "C" {
#include "libavcodec/avcodec.h"
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_androidvlc_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    hello += avcodec_configuration();
    __android_log_print(ANDROID_LOG_INFO, "Test", "test for android log: %s", hello.c_str());
    return env->NewStringUTF(hello.c_str());
}

运行成功在应用界面看到ffmpeg详细的编译信息则说明配置成功,可以在Android Studio中调用ffmpeg库。

相关推荐
小贾要学习17 分钟前
【C++】继承----下篇
android·java·c++
投笔丶从戎2 小时前
Kotlin Multiplatform--01:项目结构基础
android·开发语言·kotlin
xiaoh_72 小时前
解决视频处理中的 HEVC 解码错误:Could not find ref with POC xxx【已解决】
python·ffmpeg·音视频
Lary_Rock3 小时前
Android 编译问题 prebuilts/clang/host/linux-x86
android·linux·运维
limingade4 小时前
手机打电话通话时如何向对方播放录制的IVR引导词声音
android·智能手机·蓝牙电话·手机提取通话声音
hepherd5 小时前
Flutter 环境搭建 (Android)
android·flutter·visual studio code
_一条咸鱼_5 小时前
揭秘 Android ListView:从源码深度剖析其使用原理
android·面试·android jetpack
_一条咸鱼_5 小时前
深入剖析 Android NestedScrollView 使用原理
android·面试·android jetpack