1 FFmpeg 简介
FFmpeg 是一个开源的多媒体框架,能够处理音频、视频以及其他多媒体文件和流。它包括了各种工具和库来进行音视频处理、编码解码、转码、视频剪辑、音视频合成等工作。FFmpeg 的核心库包括:
- libavcodec:编解码库,处理音视频编码和解码。
- libavformat:格式库,负责读取、写入多种格式的容器文件。
- libavfilter:过滤器库,支持各种音视频过滤。
- libswscale:缩放库,支持图片和视频的像素格式转换和缩放。
- libswresample:音频重采样库,用于音频格式的转换。
- libpostproc:后处理库,进行视频后处理。
FFmpeg 主要的命令行工具包括:
- ffmpeg:用于转换音视频格式。
- ffplay:用于播放音视频。
- ffprobe:用于查看音视频的元数据。
在 Android 开发中,FFmpeg 常用于处理音视频文件,或者进行实时音视频流处理。
2 如何在 Android 项目中集成 FFmpeg
在 Android 中集成 FFmpeg 通常有两种方式:
- 使用现成的 FFmpeg Android 库,例如 FFmpeg Android Java 或 MobileFFmpeg。
- 使用 NDK 通过源码集成 FFmpeg,编译并在 Android 项目中调用。
下面介绍这两种方式的使用方法。
方法 1:使用 FFmpeg Android 库(例如 MobileFFmpeg)
MobileFFmpeg 是一个为 Android 提供 FFmpeg 功能的库,它封装了 FFmpeg 的操作,并提供了易于使用的 Java/Kotlin 接口。
1.1 添加 MobileFFmpeg 依赖
- 打开
build.gradle文件,添加 MobileFFmpeg 的依赖项。
kotlin
复制代码
dependencies {
implementation 'com.arthenica:ffmpeg-kit-full:5.1.1'
}
2. 同步项目以下载依赖。
1.2 使用 MobileFFmpeg 执行视频转码
例如,下面的代码使用 MobileFFmpeg 将一个视频文件转换为另一个格式。
kotlin
复制代码
import com.arthenica.mobileffmpeg.FFmpeg
import com.arthenica.mobileffmpeg.Config
fun convertVideo(inputPath: String, outputPath: String) {
val command = "-i $inputPath -c:v libx264 -preset fast -c:a aac -b:a 192k -y $outputPath"
// 执行 FFmpeg 命令
val result = FFmpeg.execute(command)
if (result == Config.RETURN_CODE_SUCCESS) {
println("Video conversion successful!")
} else {
println("Video conversion failed!")
}
}
1.3 使用 MobileFFmpeg 播放视频
除了转换,MobileFFmpeg 还可以用来播放视频或提取视频的某些信息。例如,使用 ffprobe 获取视频的信息:
kotlin
复制代码
val command = "-v quiet -print_format json -show_format -show_streams $videoFilePath"
val result = FFmpeg.execute(command)
if (result == Config.RETURN_CODE_SUCCESS) {
println("Video info extracted successfully!")
} else {
println("Failed to extract video info")
}
1.4 注意事项
- 性能:FFmpeg 是一个高效的多媒体框架,但是它的性能取决于硬件设备,尤其在进行高质量视频转码时会消耗较多 CPU 和内存。
- 权限:在操作文件时,确保你申请了合适的权限,尤其是对于访问存储的权限。
- 兼容性:MobileFFmpeg 已经针对 Android 进行了优化,可以在多种 Android 设备上工作,但在一些低性能设备上可能会出现问题。
方法 2:通过 NDK 集成 FFmpeg 源码
如果你需要更大的灵活性,并且想直接通过 NDK 使用 FFmpeg,可以通过以下步骤集成 FFmpeg 源码到 Android 项目中。
2.1 下载 FFmpeg 源码
首先,你需要从 FFmpeg 的官网或 GitHub 仓库下载源码,并为 Android 编译它。你可以从 FFmpeg 官方网站 获取源码,或者使用 Git 克隆 FFmpeg 仓库:
bash
复制代码
git clone https://git.ffmpeg.org/ffmpeg.git
2.2 编译 FFmpeg 为 Android
使用 Android NDK 工具链编译 FFmpeg 源码。以下是简化的步骤:
2.3 JNI 调用 FFmpeg 函数
编译完成后,你可以通过 JNI 调用 FFmpeg 函数。例如,调用 FFmpeg 的解码函数来解码音视频数据:
cpp
复制代码
extern "C" JNIEXPORT void JNICALL
Java_com_example_myapplication_MainActivity_decodeVideo(JNIEnv* env, jobject /* this */) {
// 使用 FFmpeg 函数解码视频或音频
av_register_all();
avformat_network_init();
// 解码操作代码(示例)
}
2.4 调用本地方法
在 MainActivity.kt 中声明本地方法,并使用 JNI 调用:
kotlin
复制代码
class MainActivity : AppCompatActivity() {
// 声明本地方法
external fun decodeVideo()
companion object {
init {
System.loadLibrary("ffmpeg") // 加载本地库
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 调用本地方法
decodeVideo()
}
}
3 编译 FFmpeg 为 Android 详细步骤
- 下载 FFmpeg 源代码
- 安装 Android NDK 和 CMake
- 设置编译环境
- 编译 FFmpeg 为 Android
- 在 Android 项目中使用 FFmpeg
1. 下载 FFmpeg 源代码
你可以从 FFmpeg 官方 GitHub 仓库或官网下载源代码。
使用 Git 克隆 FFmpeg 仓库
bash
复制代码
git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg
cd ffmpeg
你可以选择下载稳定版本的 FFmpeg,或者直接使用最新的开发版本。
或者从 FFmpeg 官方网站下载
你也可以直接访问 FFmpeg 下载页面 获取源码。
2. 安装 Android NDK 和 CMake
你需要 Android NDK 和 CMake 来编译 FFmpeg 为 Android 的 .so 库。
- 打开 Android Studio,进入 SDK Manager(点击
Tools>SDK Manager)。 - 在
SDK Tools标签页中,确保勾选了NDK (Side by side)和CMake。 - 安装或更新到适合的版本。
确认你已安装了 NDK 和 CMake 后,可以使用这些工具来编译 FFmpeg。
3. 设置编译环境
在准备编译 FFmpeg 前,我们需要设置编译环境,包括配置 Android NDK 工具链,指定目标架构等。
3.1 设置 Android NDK 环境变量
环境变量的设置通常包括以下两部分:
ANDROID_NDK_HOME:指向 NDK 根目录,告诉开发工具链在哪里找到 NDK。ANDROID_SDK_HOME(可选):指向 Android SDK 根目录。
3.1.1 在 Linux/macOS 上设置 NDK 环境变量
如果你在 Linux 或 macOS 上开发,可以在你的 Shell 配置文件(例如 .bashrc 或 .zshrc)中设置 NDK 环境变量:
-
打开终端。
-
编辑配置文件,使用适合的文本编辑器(例如
nano、vi或vim)打开配置文件:nano ~/.bashrc # 如果你使用 bash或者,如果你使用
zsh:nano ~/.zshrc -
在文件中添加以下内容(根据你的 NDK 安装路径修改):
export NDK_PATH=/Users/fengxiemojie/Library/Android/sdk/ndk/22.1.7171670
export TOOLCHAIN_PATH=$NDK_PATH/toolchains/llvm/prebuilt/darwin-x86_64
export CC=$TOOLCHAIN_PATH/bin/aarch64-linux-android30-clang
export CXX=$TOOLCHAIN_PATH/bin/aarch64-linux-android30-clang++
export AR=$TOOLCHAIN_PATH/bin/aarch64-linux-android-ar
export AS=$TOOLCHAIN_PATH/bin/aarch64-linux-android-as
export LD=$TOOLCHAIN_PATH/bin/aarch64-linux-android-ld
export RANLIB=$TOOLCHAIN_PATH/bin/aarch64-linux-android-ranlib
export STRIP=$TOOLCHAIN_PATH/bin/aarch64-linux-android-strip
其中 `/path/to/your/ndk` 是你本地 Android NDK 的安装路径。通常,如果你使用 Android Studio 安装 NDK,路径会类似于:
```
/Users/your-user-name/Library/Android/sdk/ndk/21.3.6528147
```
-
保存文件并关闭编辑器(如果使用
nano,按Ctrl + X,然后按Y保存更改)。 -
重新加载配置文件,使更改生效:
source ~/.bashrc # 如果你使用 bash或者:
source ~/.zshrc # 如果你使用 zsh
3.1..2 在 Windows 上设置 NDK 环境变量
在 Windows 上,你可以通过 环境变量 来设置 Android NDK:
-
右键点击桌面上的“计算机”图标,选择 属性。
-
在左侧选择 高级系统设置,然后点击 环境变量。
-
在 系统变量 部分,点击 新建。
-
在变量名框中输入
ANDROID_NDK_HOME,在变量值框中输入你的 NDK 安装路径。例如:C:\Users\your-user-name\AppData\Local\Android\Sdk\ndk\21.3.6528147 -
点击 确定 完成设置。
-
重新启动命令提示符或 IDE(如 Android Studio)以使环境变量生效。
3.1.3 验证 NDK 环境变量设置
你可以通过命令行验证 NDK 环境变量是否设置成功。
在终端中输入以下命令:
echo $ANDROID_NDK_HOME # 在 Linux/macOS 上
或在 Windows 命令提示符中输入:
echo %ANDROID_NDK_HOME% # 在 Windows 上
如果一切正常,你应该看到 NDK 的安装路径。
3.2. 其他常用的 NDK 环境变量
NDK_TOOLCHAIN_VERSION: 指定使用的 NDK 工具链版本。例如:4.9、clang等。NDK_PROJECT_PATH: 指定 NDK 项目路径,通常用于ndk-build构建系统中。ANDROID_NDK_HOME: 已经设置了,指向 NDK 安装路径。ANDROID_SDK_HOME: 用于指定 Android SDK 的路径,通常不需要手动设置,除非你有多个 SDK 安装目录。
3.3 配置 FFmpeg 编译选项
使用 NDK 构建 FFmpeg 时,我们需要指定 Android 工具链和目标平台。可以通过创建一个 Android 配置文件(例如 android-arm64.sh)来进行配置。
在 ffmpeg 目录下创建一个 android 文件夹,然后在该文件夹中创建如下的脚本文件(例如 android-arm64.sh)来配置编译选项:
注意:这里面的 API 级别为所支持的最小设备版本,即仅可运行在该API 及以上平台
#!/bin/bash
# =============================
# 配置路径和环境变量
# =============================
# 设置 Android NDK 路径
NDK=/Users/fengxiemojie/Library/Android/sdk/ndk/22.1.7171670
if [ ! -d "$NDK" ]; then
echo "NDK 路径无效,请检查: $NDK"
exit 1
fi
# 设置工具链路径
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/darwin-x86_64
# 架构、API 级别和目标平台
ARCH=arm64
API=30
CPU=armv8-a
PREFIX=$(pwd)/android/dynamics/$ARCH
# 检查工具链是否存在
if [ ! -d "$TOOLCHAIN" ]; then
echo "工具链路径无效: $TOOLCHAIN"
exit 1
fi
# =============================
# 清理之前的构建
# =============================
make distclean
# =============================
# 配置 FFmpeg
# =============================
./configure \
--prefix=$PREFIX \
--target-os=android \
--arch=$ARCH \
--cpu=$CPU \
--enable-shared \
--disable-static \
--enable-neon \
--enable-jni \
--enable-mediacodec \
--disable-debug \
--disable-doc \
--disable-programs \
--enable-hwaccel=h264 \
--cross-prefix=$TOOLCHAIN/bin/aarch64-linux-android- \
--cc=$TOOLCHAIN/bin/aarch64-linux-android$API-clang \
--cxx=$TOOLCHAIN/bin/aarch64-linux-android$API-clang++ \
--sysroot=$TOOLCHAIN/sysroot \
--extra-cflags="-fPIC -I$TOOLCHAIN/sysroot/usr/include" \
--extra-ldflags="-L$TOOLCHAIN/sysroot/usr/lib"
# =============================
# 构建和安装
# =============================
if [ $? -ne 0 ]; then
echo "配置失败,请检查日志文件 config.log"
exit 1
fi
make -j$(nproc)
if [ $? -ne 0 ]; then
echo "编译失败,请检查输出日志"
exit 1
fi
make install
if [ $? -ne 0 ]; then
echo "安装失败,请检查输出日志"
exit 1
fi
echo "FFmpeg 编译完成!输出路径: $PREFIX"
关键配置选项解释
--enable-shared:启用生成.so文件(共享库)。--disable-static:禁用静态库生成,确保只生成动态库(.so)。--prefix=$PREFIX:指定安装路径,编译后的.so文件将安装在此路径。--cross-prefix=$TOOLCHAIN/bin/aarch64-linux-android-:设置交叉编译前缀,确保工具链用于交叉编译。--cc=$TOOLCHAIN/bin/aarch64-linux-android$API-clang:设置 C 编译器。--cxx=$TOOLCHAIN/bin/aarch64-linux-android$API-clang++:设置 C++ 编译器。--sysroot=$TOOLCHAIN/sysroot:指定工具链的 sysroot。--extra-cflags="-fPIC -I$TOOLCHAIN/sysroot/usr/include":指定额外的编译选项,确保生成位置无关代码(PIC),并包含工具链头文件。--extra-ldflags="-L$TOOLCHAIN/sysroot/usr/lib":指定链接时的库路径。
3.4 配置 FFmpeg 以支持 RTMP
在编译 FFmpeg 时,您需要启用 --enable-librtmp,并指定 librtmp 的路径。以下是更新后的 FFmpeg 配置步骤:
# =============================
# 配置路径和环境变量
# =============================
export NDK=/Users/fengxiemojie/Library/Android/sdk/ndk/22.1.7171670
export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/darwin-x86_64
# 设置目标架构和 API 版本
export TARGET=aarch64-linux-android
export API=21
export PREFIX=$(pwd)/$TARGET$API
# 设置 librtmp 的路径
export RTMP_LIB_PATH=$(pwd)/rtmpdump/$TARGET$API
# =============================
# 清理旧的构建
# =============================
make clean
# =============================
# 配置 FFmpeg 构建选项
# =============================
./configure \
--prefix=$PREFIX \
--target-os=android \
--arch=$TARGET \
--cpu=armv8-a \
--enable-shared \ # 启用动态库
--disable-static \ # 禁用静态库
--enable-neon \ # 启用 NEON 优化
--enable-jni \ # 启用 JNI
--enable-mediacodec \ # 启用 MediaCodec
--disable-debug \ # 禁用调试信息
--disable-doc \ # 禁用文档生成
--disable-programs \ # 禁用生成可执行文件
--enable-librtmp \ # 启用 RTMP 支持
--cross-prefix=$TOOLCHAIN/bin/aarch64-linux-android- \
--cc=$TOOLCHAIN/bin/aarch64-linux-android$API-clang \
--cxx=$TOOLCHAIN/bin/aarch64-linux-android$API-clang++ \
--sysroot=$TOOLCHAIN/sysroot \
--extra-cflags="-fPIC -I$TOOLCHAIN/sysroot/usr/include -I$RTMP_LIB_PATH/include" \
--extra-ldflags="-L$TOOLCHAIN/sysroot/usr/lib -L$RTMP_LIB_PATH/lib" \
--extra-libs="-lrtmp"
if [ $? -ne 0 ]; then
echo "配置失败,请检查日志文件 config.log"
exit 1
fi
# =============================
# 构建 FFmpeg 生成动态库(.so)
# =============================
make -j$(nproc)
if [ $? -ne 0 ]; then
echo "编译失败,请检查输出日志"
exit 1
fi
# =============================
# 安装生成的动态库到目标路径
# =============================
make install
if [ $? -ne 0 ]; then
echo "安装失败,请检查输出日志"
exit 1
fi
echo "FFmpeg 编译完成!输出路径: $PREFIX"
关键配置选项解释:
--enable-librtmp:启用 RTMP 协议支持。--extra-cflags="-I$RTMP_LIB_PATH/include":指定librtmp的头文件路径。--extra-ldflags="-L$RTMP_LIB_PATH/lib":指定librtmp的库文件路径。--extra-libs="-lrtmp":在链接时链接librtmp库。
3.5 给予执行权限
如果你创建了配置脚本,记得给它执行权限:
chmod +x android-arm64.sh
chmod +x android-armeabi-v7a.sh
4. 编译 FFmpeg 为 Android
验证主机依赖: 在 macOS 上,确保安装了以下依赖:
# 安装 Homebrew(如果还没有安装)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 安装编译所需的工具和库
brew install yasm pkg-config libx264
#在 macOS 上,确保安装了以下依赖
brew install automake autoconf libtool pkg-config
yasm:一个汇编程序,FFmpeg 编译时需要使用。pkg-config:一个帮助 C 编译器查找库文件的工具。libx264:一个用于 H.264 视频编码的库。如果你不需要 H.264 编码,可以跳过安装libx264。
4.1 运行编译脚本
在 ffmpeg 源码目录下,运行以下命令来编译 FFmpeg:
//动态库
./android/dynamics/android-arm64.sh
./android/dynamics/android-armeabi-v7a.sh
//静态库
./android/static/android-arm64.sh
./android/static/android-armeabi-v7a.sh
这个命令会开始下载必要的依赖(如果没有下载过的话),并开始编译 FFmpeg。
4.2 多架构编译
如果你需要支持多个架构(如 armeabi-v7a, arm64-v8a, x86_64 等),你可以为每个架构创建不同的配置脚本并分别编译。例如,创建 android-arm.sh 和 android-x86_64.sh,然后分别执行:
./android/android-arm.sh
./android/android-x86_64.sh
4.3 检查编译结果
编译完成后,FFmpeg 的库文件(.so)将被安装到你指定的 PREFIX 目录下。检查该目录,应该会有类似以下文件结构:
makefile
复制代码
android/
arm64/
lib/
libavcodec.so
libavformat.so
libavfilter.so
libswscale.so
...
include/
libavcodec/
libavformat/
...
这些 .so 文件就是你需要将其包含到 Android 项目中的本地库。
5. 在 Android 项目中使用 FFmpeg
完成 FFmpeg 编译后,你需要将这些 .so 库文件集成到 Android 项目中。
5.1 创建 Android 项目
在 Android 项目的 app/src/main 目录下创建一个 jniLibs 文件夹,并将编译好的 .so 文件放入适当的架构文件夹中。例如:
css
复制代码
app/
src/
main/
jniLibs/
arm64-v8a/
libavcodec.so
libavformat.so
libavfilter.so
libswscale.so
...
armeabi-v7a/
...
x86_64/
...
5.2 在 build.gradle 中配置 NDK
在 build.gradle 文件中配置支持的 NDK 架构:
kotlin
复制代码
android {
compileSdkVersion 34
defaultConfig {
minSdkVersion 21
targetSdkVersion 34
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86_64"
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
5.3 调用 FFmpeg 本地方法
在 Kotlin 或 Java 中通过 JNI 调用 FFmpeg 的本地方法。首先,在 C++ 中编写 JNI 函数,然后在 Kotlin/Java 中声明这些本地方法。
cpp
复制代码
#include <jni.h>
#include <string>
#include <ffmpeg/avformat.h>
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */) {
av_register_all();
return env->NewStringUTF("FFmpeg Initialized!");
}
然后在 Kotlin 中声明并调用这个本地方法:
kotlin
复制代码
class MainActivity : AppCompatActivity() {
external fun stringFromJNI(): String
companion object {
init {
System.loadLibrary("ffmpeg") // 加载本地库
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView: TextView = findViewById(R.id.sample_text)
textView.text = stringFromJNI()
}
}