【FFmpeg+Surface 底层渲染,实现超低延迟100ms】

➡️最近在做一个智能挖机的操控平板,视频板块的实时渲染,需要低于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中,展示实操流程。

相关推荐
冷冷的菜哥2 小时前
springboot调用ffmpeg实现对视频的截图,截取与水印
java·spring boot·ffmpeg·音视频·水印·截图·截取
进击的CJR15 小时前
redis哨兵实现主从自动切换
mysql·ffmpeg·dba
huahualaly19 小时前
重建oracle测试库步骤
数据库·oracle·ffmpeg
aqi001 天前
FFmpeg开发笔记(九十九)基于Kotlin的国产开源播放器DKVideoPlayer
android·ffmpeg·kotlin·音视频·直播·流媒体
lizongyao1 天前
FFMPEG命令行典型案例
ffmpeg
冷冷的菜哥1 天前
ASP.NET Core调用ffmpeg对视频进行截图,截取,增加水印
开发语言·后端·ffmpeg·asp.net·音视频·asp.net core
冷冷的菜哥1 天前
go(golang)调用ffmpeg对视频进行截图、截取、增加水印
后端·golang·ffmpeg·go·音视频·水印截取截图
小尧嵌入式2 天前
【基础学习七十】ffmpeg命令
c++·stm32·嵌入式硬件·ffmpeg
烧饼Fighting2 天前
统信UOS操作系统离线安装ffmpeg
开发语言·javascript·ffmpeg