➡️最近在做一个智能挖机的操控平板,视频板块的实时渲染,需要低于160ms,虽然在局域网下,但是平常的播放器 【VLC、Medio3等等....】均不能达到效果,并且采集端的分辨率是1080x1920.在这样的分辨率下更加能达到超低延迟效果,硬件供应商并没有提供能够改变分辨率的接口。
➡️最终采用NDK交叉编译,生成so文件, FFmpeg+Surface实现底层渲染。
准备材料:
FFmpeg(下载地址): https://ffmpeg.org/download.html
MSYS(下载地址): https://www.msys2.org/
NDK(下载地址): https://developer.android.com/ndk/downloads
⬆️将上面的材料准备好,ndk、ffmpeg放在同一个文件夹。
⬆️完成以上三步,就具备了执行FFmpeg for Android编译的所有基础材料。之后,在MSYS2环⬆️境中运行您的编译脚本即可
生成一个build_ffmpeg.sh脚本:
运行:bush
cpp
./build_ffmpeg.sh
编写:build_ffmpeg.sh
cpp
#!/bin/bash
# FFmpeg智能编译脚本 - 避免重复下载
# 适用于Windows MSYS2环境
# 设置变量
NDK_PATH="/C/Android/android-ndk-r21e"
API_LEVEL=21
FFMPEG_VERSION="n6.0"
FFMPEG_SOURCE_DIR="$HOME/ffmpeg-source"
BUILD_DIR="$HOME/ffmpeg-build"
OUTPUT_DIR="/c/ffmpeg-android"
STATE_FILE="$HOME/ffmpeg_build_state.txt"
CACHE_DIR="$HOME/.ffmpeg-cache"
# 创建必要的目录
mkdir -p "$FFMPEG_SOURCE_DIR"
mkdir -p "$BUILD_DIR"
mkdir -p "$OUTPUT_DIR"
mkdir -p "$CACHE_DIR"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
log_error() { echo -s "${RED}[ERROR]${NC} $1" >&2; }
# 检查状态文件
check_ffmpeg_source() {
if [ -f "${CACHE_DIR}/ffmpeg_${FFMPEG_VERSION}_downloaded" ]; then
if [ -d "${FFMPEG_SOURCE_DIR}/.git" ]; then
log_success "FFmpeg源码已存在 (版本: $FFMPEG_VERSION)"
cd "${FFMPEG_SOURCE_DIR}"
CURRENT_VERSION=$(git describe --tags 2>/dev/null || echo "unknown")
if [ "$CURRENT_VERSION" != "$FFMPEG_VERSION" ] && [ "$CURRENT_VERSION" != "unknown" ]; then
log_warn "检测到不同版本: $CURRENT_VERSION,需要切换..."
return 1
fi
return 0
fi
fi
return 1
}
# 标记下载完成
mark_downloaded() {
echo "FFMPEG_VERSION=${FFMPEG_VERSION}" > "${CACHE_DIR}/ffmpeg_${FFMPEG_VERSION}_downloaded"
echo "DOWNLOAD_DATE=$(date +%Y-%m-%d_%H:%M:%S)" >> "${CACHE_DIR}/ffmpeg_${FFMPEG_VERSION}_downloaded"
echo "SOURCE_DIR=${FFMPEG_SOURCE_DIR}" >> "${CACHE_DIR}/ffmpeg_${FFMPEG_VERSION}_downloaded"
log_success "已标记FFmpeg源码下载完成"
}
# 下载FFmpeg源码
download_ffmpeg() {
log_info "开始下载FFmpeg源码 (版本: $FFMPEG_VERSION)..."
if [ -d "${FFMPEG_SOURCE_DIR}" ] && [ "$(ls -A ${FFMPEG_SOURCE_DIR})" ]; then
log_info "清理旧源码目录..."
rm -rf "${FFMPEG_SOURCE_DIR}"
fi
# 使用git克隆
git clone --depth 1 --branch "${FFMPEG_VERSION}" https://git.ffmpeg.org/ffmpeg.git "${FFMPEG_SOURCE_DIR}"
if [ $? -ne 0 ]; then
log_error "git克隆失败,尝试git clone master分支..."
git clone --depth 1 https://git.ffmpeg.org/ffmpeg.git "${FFMPEG_SOURCE_DIR}"
if [ $? -ne 0 ]; then
log_error "FFmpeg源码下载失败!"
exit 1
fi
fi
cd "${FFMPEG_SOURCE_DIR}"
log_success "FFmpeg源码下载完成"
mark_downloaded
}
# 检查编译状态
check_build_status() {
local arch="$1"
if [ -f "${CACHE_DIR}/build_${arch}_success" ]; then
if [ -d "${OUTPUT_DIR}/${arch}" ] && [ "$(ls -A ${OUTPUT_DIR}/${arch}/*.so 2>/dev/null)" ]; then
log_info "架构 $arch 已编译,跳过..."
return 0
else
log_warn "标记已编译但未找到so文件,重新编译..."
rm -f "${CACHE_DIR}/build_${arch}_success"
fi
fi
return 1
}
# 标记编译完成
mark_build_complete() {
local arch="$1"
echo "ARCH=${arch}" > "${CACHE_DIR}/build_${arch}_success"
echo "BUILD_DATE=$(date +%Y-%m-%d_%H:%M:%S)" >> "${CACHE_DIR}/build_${arch}_success"
echo "FFMPEG_VERSION=${FFMPEG_VERSION}" >> "${CACHE_DIR}/build_${arch}_success"
}
# 编译函数
build_for_arch() {
local arch="$1"
local cpu="$2"
log_info "开始编译架构: $arch"
# 检查是否已编译
if check_build_status "$arch"; then
return 0
fi
# 设置编译参数
case "$arch" in
"armeabi-v7a")
TARGET_CPU="armv7-a"
TOOLCHAIN_PREFIX="armv7a-linux-androideabi"
EXTRA_CFLAGS="-march=armv7-a -mfpu=neon -mfloat-abi=softfp -Wl,--fix-cortex-a8"
SYSROOT="${NDK_PATH}/sysroot/usr/lib/arm-linux-androideabi"
;;
"arm64-v8a")
TARGET_CPU="armv8-a"
TOOL _PREFIX="aarch64-linux-android"
EXTRA_CFLAGS=""
SYSROOT="${NDK_PATH}/sysroot/usr/lib/aarch64-linux-android"
;;
"x86")
TARGET_CPU="i686"
TOOLCHAIN_PREFIX="i686-linux-android"
SYSROOT="${NDK_PATH}/sysroot/usr/lib/i686-linux-android"
;;
"x86_64")
TARGET_CPU="x86-64"
TOOLCHAIN_PREFIX="x86_64-linux-android"
EXTRA_CFLAGS="-march=x86-64 -msse4.2 -mpopcnt"
SYSROOT="${NDK_PATH}/sysroot/usr/lib/x86_64-linux-android"
;;
*)
log_error "不支持的架构: $arch"
return 1
;;
esac
local TOOLCHAIN="${NDK_PATH}/toolchains/llvm/prebuilt/windows-x86_64"
local BUILD_DIR_ARCH="${BUILD_DIR}/${arch}"
local OUTPUT_DIR_ARCH="${OUTPUT_DIR}/${arch}"
mkdir -p "${BUILD_DIR_ARCH}"
mkdir -p "${OUTPUT_DIR_ARCH}"
cd "${FFMPEG_SOURCE_DIR}"
# 清理之前配置
if [ -f "config.h" ]; then
make distclean
fi
# 配置FFmpeg
log_info "配置FFmpeg for $arch..."
./configure \
--prefix="${BUILD_DIR_ARCH}" \
--enable-shared \
--disable-static \
--disable-doc \
--disable-programs \
--enable-avdevice \
--enable-postproc \
--enable-swresample \
--enable-swscale \
--enable-avfilter \
--target-os=android \
--arch="$arch" \
--cpu="$TARGET_CPU" \
--enable-cross-compile \
--sysroot="${TOOLCHAIN}/sysroot" \
--cc="${TOOLCHAIN}/bin/${TOOLCHAIN_PREFIX}${API_LEVEL}-clang" \
--cxx="${TOOLCHAIN}/bin/${TOOLCHAIN_PREFIX}${API_LEVEL}-clang++" \
--ar="${TOOLCHAIN}/bin/llvm-ar" \
--strip="${TOOLCHAIN}/bin/llvm-strip" \
--extra-cflags="-Os -fPIC $EXTRA_CFLAGS" \
--extra-ldflags="" \
--enable-small \
--enable-gpl \
--enable-neon \
--enable-hwaccels \
--enable-jni \
--enable-mediacodec \
--enable-decoder=h264 \
--enable-decoder=hevc \
--enable-decoder=aac \
--enable-decoder=mp3 \
--enable-encoder=aac \
--enable-encoder=libx264 \
--enable-parser=h264 \
--enable-parser=hevc \
--enable-parser=aac \
--enable-muxer=mp4 \
--enable-muxer=flv \
--enable-demuxer=mpegts \
--enable-demuxer=flv \
--enable-protocol=file \
--enable-protocol=http \
--enable-protocol=https \
--enable-zlib \
--disable-iconv \
--disable-asm
if [ $? -ne 0 ]; then
log_error "配置失败: $arch"
return 1
fi
# 编译
log_info "编译FFmpeg for $arch..."
make -j$(nproc)
if [ $? -ne 0 ]; then
log_error "编译失败: $arch"
return 1
fi
# 安装
log_info "安装FFmpeg for $arch..."
make install
if [ $? -ne 0 ]; then
log_error "安装失败: $arch"
return 1
fi
# 复制.so文件
log_info "复制.so文件..."
find "${BUILD_DIR_ARCH}/lib" -name "*.so" -exec cp {} "${OUTPUT_DIR_ARCH}/" \;
# 验证文件存在
if [ $(ls -1 "${OUTPUT_DIR_ARCH}".so 2>/dev/null | wc -l) -eq 0 ]; then
log_error "未生成.so文件: $arch"
return 1
fi
# 标记编译完成
mark_build_complete "$arch"
log_success "架构 $arch 编译完成!"
return 0
}
# 主函数
main() {
log_info "===== FFmpeg智能编译脚本 ====="
log_info "FFmpeg版本: $FFMPEG_VERSION"
log_info "NDK路径: $NDK_PATH"
log_info "输出目录: $OUTPUT_DIR"
log_info "============================="
# 检查NDK
if [ ! -d "$NDK_PATH" ]; then
log_error "NDK未找到: $NDK_PATH"
log_info "请从 https://developer.android.com/ndk/downloads 下载NDK r25+"
log_info "并解压到: C:\\Android\\android-ndk-r21b"
exit 1
fi
# 检查并下载源码
if ! check_ffmpeg_source; then
download_ffmpeg
fi
# 编译所有架构
local ARCHS=("armeabi-v7a" "arm64-v8a" "x86" "x86_64")
local FAILED_ARCHS=()
for arch in "${ARCHS[@]}"; do
case "$arch" in
"armeabi-v7a") cpu="armv7-a" ;;
"arm64-v8a") cpu="armv8-a" ;;
"x86") cpu="i686" ;;
"x86_64") cpu="x86-64" ;;
esac
if build_for_arch "$arch" "$cpu"; then
log_success "成功编译: $arch"
else
log_error "编译失败: $arch"
FAILED_ARCHS+=("$arch")
fi
done
# 输出总结
log_info "===== 编译完成 ====="
log_info "输出目录: $OUTPUT_DIR"
for arch in "${ARCHS[@]}"; do
local so_count=$(ls -1 "${OUTPUT_DIR}/${arch}".so 2>/dev/null | wc -l)
if [ "$so_count" -gt 0 ]; then
log_success "$arch: 生成 $so_count 个.so文件"
else
log_warn "$arch: 未生成.so文件"
fi
done
if [ ${#FAILED_ARCHS[@]} -gt 0 ]; then
log_error "以下架构编译失败: ${FAILED_ARCHS[@]}"
fi
}
# 解析参数
if [ "$1" = "--clean" ] || [ "$1" = "-c" ]; then
log_warn "清理所有缓存和输出文件..."
rm -rf "$FFMPEG_SOURCE_DIR"
rm -rf "$BUILD_DIR"
rm -rf "$OUTPUT_DIR"
rm -rf "$CACHE_DIR"
log_info "清理完成"
exit 0
elif [ "$1" = "--force" ] || [ "$1" = "-f" ]; then
log_warn "强制重新下载和编译..."
rm -rf "$FFMPEG_SOURCE_DIR"
rm -rf "$BUILD_DIR"
rm -rf "$OUTPUT_DIR".so
rm -rf "$CACHE_DIR"_downloaded
rm -rf "$CACHE_DIR"/build_*_success
fi
# 运行主函数
main
大约等待十来分钟,期间你会看到




下一篇文章继续讲解,如何引入到Android studio中,展示实操流程。