开发环境搭建
下载最新版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/%E7%BC%96%E8%AF%91FFmpeg.md
可是这种硬编码随着ffmpeg版本和ndk版本不同存在很大的缺陷:ndk的目录组织结构在不同版本差异很大,导致toolchain等路径都得重新设置。现代 NDK(r21+)提供了更便捷的 android.toolchain.cmake
工具链文件,可以大幅简化交叉编译流程。
- 无需手动配置编译器路径:CMake 自动识别 NDK 中的工具链。
- 支持多架构并行编译:通过
ANDROID_ABI
参数一键切换。 - 更好的兼容性:避免因路径或环境变量错误导致的编译失败。
通过使用**ExternalProject_Add
命令,在 CMake 中集成外部项目的构建过程**(如 FFmpeg 这种非 CMake 项目)
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/
-
准备 FFmpeg 源码
git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg cd ffmpeg
-
创建**
CMakeLists.txt
**在 FFmpeg 根目录新建
CMakeLists.txt
,内容如下: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(ffmpegSOURCE_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 -j8INSTALL_COMMAND make installBUILD_IN_SOURCE TRUE )
-
执行 CMake 编译
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
-
输出结果
编译后的头文件、动态库(
.so
文件)会生成在build
目录的ffmpeg_install
下的include
和lib
目录中。
./configure
报错输出的详细信息位于ffbuild/config.log
文件中,如果遇到问题可以针对详细报错进行修改调试。
如果不愿意使用CMake,也可以尝试使用bash脚本,我通过参考博主MOHD UMIR的编译脚本,根据我的Mac环境进行修改后可以编译成功。
#!/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=$1TARGET_CPU=$2CROSS_PREFIX=$3EXTRA_CFLAGS=$4EXTRA_CONFIG=$5CLANG="${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 cleanmake -j 8make install -j2}echo -e "\e[1;32mCompiling FFMPEG for Android...\e[0m"for ARCH in "${ARCH_LIST[@]}"; docase "$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;;esacconfigure_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同级),拷贝后的目录结构如下:
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库。
# 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}# ffmpegavcodec# List libraries link to the target libraryandroidlog)
值得注意的是CMake中的${ANDROID_ABI}
变量的内容是被Android Studio自动设置的,为了指定编译的ABI,需要在build.gradle.kts(:app)
中进行配置。找到android
下配置添加以下内容:
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
中添加以下内容:
#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库。