Android 使用NDK R21 编译 ffmpeg

一、前言

编译ffmpeg 是学习 ffmpeg 的第一步,本篇博客的环境是 mac os 上 NDK21 版本编译ffmpeg,显然这篇文章是一篇基础文章。

1.1 目标

之所以写这篇博客,主要是因为去年编译的时候一切顺利进行,而今年电脑 CPU 烧了(使用mac电脑设备要特别小心电量问题,电量太低容易造成cpu发热),修理之后,所有数据都没了,所以都是新环境,和去年一样的脚本,今年编译却失败了。

至于失败的原因是什么,我们很难去追踪。相信大家都有这样的印象,在设备使用习惯之后,安装其他软件很顺手,成功率会很高,主要原因是在不断的更新系统和软件的时候,我们可能会下载提前完成一些软件的下载,后续安装其他软件的错误就没那么多了,安装成功率其实和个人平时的偏好有一定关系吧。

当然,写本篇的主要目的有三个:

  • 记录NDK21 编译ffmpeg
  • 记录为ExoPlayer编译ffmpeg
  • 记录一些编译过程中的错误处理方法

1.2 ffmpeg 简介

FFmpeg 是一个开放源代码自由软件,可以执行音频和视频多种格式的录影、转换、串流功能[6],包含了libavcodec------这是一个用于多个项目中音频和视频的解码器函数库,以及libavformat------一个音频与视频格式转换函数库。

"FFmpeg"这个单词中的"FF"指的是"Fast Forward(快速前进)"[7]。"FFmpeg"的项目负责人在一封回信中说:"Just for the record, the original meaning of "FF" in FFmpeg is "Fast Forward"..."

这个项目最初是由法国程序员法布里斯·贝拉(Fabrice Bellard)发起的,而现在是由迈克尔·尼德梅尔(Michael Niedermayer)在进行维护。许多FFmpeg的开发者同时也是MPlayer项目的成员,FFmpeg在MPlayer项目中是被设计为服务器版本进行开发。

2011年3月13日,FFmpeg部分开发人士决定另组Libav,同时制定了一套关于项目继续发展和维护的规则。

1.3 ffmpeg 组成组件

此计划由几个组件组成:

  • 应用程序

    • ffmpeg:用于对视频文档或音频档案转换格式
    • ffplay :一个简单的播放器,基于SDL与FFmpeg函数库,可以直接播放音视频
    • ffprobe :用于显示媒体文件的信息,见MediaInfo
  • 函数库

    • libavresample 音频重采样函数库
    • libavcodec:包含全部FFmpeg音频/视频编解码函数库
    • libavformat:包含demuxers和muxer函数库 (封装和解封装使用)
    • libavutil:包含一些工具函数库
    • libpostproc:对于视频做前处理的函数库
    • libswscale:对于影像作缩放的函数库
    • libavfilter 滤镜模块

一般情况下,libavcodec和libavformat和是两个最核心的功能,前者负责音视频的编解码工作,后者负责将编码后的音频和视频按Track封装成NALU但愿,当然NALU中包含PPS、SPS、关键帧等。

二 编译过程

上面是简介内容,下面我们来实现编译

2.1 常见错误

失败是成功之母,造成编译失败的原因很多。

在本篇的编译过程中,发现存在两种问题,所选的 sysroot 和 toolchains 出了问题,在ndk的目录中,想当然使用了和其他博客一样的方法。

toolchains工具链选择了:

/Users/${USER}/Library/Android/sdk/ndk/21.0.6113669/toolchains/arm-linux-androideabi-4.9

sysroot 选择了:

/Users/${USER}/Library/Android/sdk/ndk/21.0.6113669/sysroot

但是这两种其实是不正确的,因为在新的ndk版本中,一些配置项实际上在llvm中,一些工具的名称也发生了变更。

现象是:

  • 【1】部分头文件找不到,连 ndk 核心 jni 文件也找不到
  • 【2】clang、clang++ 找不到

三、解决方法

这里工具链一定要使用llvm下面的,因为本身llvm支持交叉编译,android ndk中llvm中的工具链最为完善。另一个sysroot同样也可以使用llvm中的,另外也可以使用platform中的,编译过程没有异常

bash 复制代码
#!/bin/bash

NDK=/Users/${USER}/Library/Android/sdk/ndk/21.0.6113669
TOOLCHAIN_ROOT_DIR=darwin-x86_b4
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/$TOOLCHAIN_ROOT_DIR/
API=18

#要编译的ffmpeg内容方法
function build_android {

  make clean
  echo "Compiling FFmpeg for $CPU"

	./configure \
    --prefix=$PREFIX \
    --cross-prefix=$CROSS_PREFIX \
    --target-os=android \
    --arch=$ARCH \
    --cpu=$CPU \
    --cc=$CC  \
    --cxx=$CXX  \
    --sysroot=$SYSROOT \
    --extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \
    --extra-ldflags="$ADDI_LDFLAGS" \
    --enable-gpl  \
    --enable-shared \
    --enable-runtime-cpudetect \
    --enable-small  \
    --enable-cross-compile  \
    --enable-asm  \
    --enable-neon \
    --enable-jni  \
    --enable-mediacodec \
    --enable-h2b4_mediacodec  \
    --enable-hwaccels_mediacodec  \
    --disable-debug \
    --disable-hwaccels \
    --disable-postproc \
    --disable-static \
    --disable-doc \
    --disable-ffmpeg \
    --disable-ffplay \
    --disable-ffprobe \
    --disable-ffserver \
    --disable-avdevice \
    --disable-doc \
    --disable-symver \
    --disable-avdevice \
    --disable-stripping \
    $ADDITIONAL_CONFIGURE_FLAG

	make j4
	make install
	echo "The Compilation of FFmpeg for $CPU is completed"
}

#armv7-a
  ARCH=arm
  CPU=armv7-a
  CC=$TOOLCHAIN/bin/armv7a-linux-androideabi$API-clang
  CXX=$TOOLCHAIN/bin/armv7a-linux-androideabi$API-clang++
  SYSROOT=$NDK/platforms/android-${API}/arch-arm
#  SYSROOT=$NDK/toolchains/llvm/prebuilt/$TOOLCHAIN_ROOT_DIR/sysroot     #这个也是可以的,大概率和和ndk版本一致,如果是21,则不支持之前的版本
  CROSS_PREFIX=$TOOLCHAIN/bin/arm-linux-androideabi-
  PREFIX=$(pwd)/android/$CPU
  OPTIMIZE_CFLAGS=" -DANDROID -mfloat-abi=softfp -mfpu=vfp -marm - march=$CPU "
#  OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=neon -marm - march=$CPU "

  build_android

以上命令执行之后,就能生成armv7-a的so库,当然在执行脚本的目中,有个android目录,至于arm64-v8a的也是类似的方式。更改CPU、CC、CXX配置即可。

四、为 ExoPlayer编译ffmpeg

Exoplayer中仅仅支持ffmpeg去解码音频操作,ExoPlayer官方曾经实现过ffmpeg视频解码,迫于LGPL/GPL风险最终删除了对视频解码,实际上对ffmpeg解密Video支持不完美,但是我们也可以自己去实现视频相关的 FfmpegVideoDecoder,当然前提是先学会为ExoPlayer编译ffmpeg扩展。

4.1 编译步骤

步骤如下:

【1】下载 ExoPlayer 源码

bash 复制代码
git clone https://github.com/google/ExoPlayer.git

【2】进入 ExoPlayer 项目,找到 ffmpeg 模块,切换到下面 jni 目录

ExoPlayer/extensions/ffmpeg/src/main/jni

【3】在 jni 目录下载 ffmpeg

bash 复制代码
git clone git://source.ffmpeg.org/ffmpeg
cd ffmpeg
git checkout release/4.2

【4】返回到 jni 目录,修改构建脚本 build_ffmpeg.sh ,内容修改如下即可

bash 复制代码
#!/bin/bash

set -eu

FFMPEG_MODULE_PATH=../
NDK_PATH=/Users/${USER}/Library/Android/sdk/ndk/21.0.6113669
HOST_PLATFORM=darwin-x86_b4
ENABLED_DECODERS=(mp3 aac ac3 flac alac)
JOBS=$(nproc 2> /dev/null || sysctl -n hw.ncpu 2> /dev/null || echo 4)
echo "Using $JOBS jobs for make"
COMMON_OPTIONS="
    --target-os=android
    --enable-static
    --disable-shared
    --disable-doc
    --disable-programs
    --disable-everything
    --disable-avdevice
    --disable-avformat
    --disable-swscale
    --disable-postproc
    --disable-avfilter
    --disable-symver
    --disable-avresample
    --enable-swresample
    --extra-ldexeflags=-pie
    "
TOOLCHAIN_PREFIX="${NDK_PATH}/toolchains/llvm/prebuilt/${HOST_PLATFORM}/bin"
for decoder in "${ENABLED_DECODERS[@]}"
do
    COMMON_OPTIONS="${COMMON_OPTIONS} --enable-decoder=${decoder}"
done
cd "${FFMPEG_MODULE_PATH}/jni/ffmpeg"
./configure \
    --libdir=android-libs/armeabi-v7a \
    --arch=arm \
    --cpu=armv7-a \
    --cross-prefix="${TOOLCHAIN_PREFIX}/armv7a-linux-androideabi16-" \
    --nm="${TOOLCHAIN_PREFIX}/llvm-nm" \
    --ar="${TOOLCHAIN_PREFIX}/llvm-ar" \
    --ranlib="${TOOLCHAIN_PREFIX}/llvm-ranlib" \
    --strip="${TOOLCHAIN_PREFIX}/llvm-strip" \
    --extra-cflags="-march=armv7-a -mfloat-abi=softfp" \
    --extra-ldflags="-Wl,--fix-cortex-a8" \
    ${COMMON_OPTIONS}
make -j$JOBS
make install-libs
make clean
./configure \
    --libdir=android-libs/armb4-v8a \
    --arch=aarchb4 \
    --cpu=armv8-a \
    --cross-prefix="${TOOLCHAIN_PREFIX}/aarchb4-linux-android21-" \
    --nm="${TOOLCHAIN_PREFIX}/llvm-nm" \
    --ar="${TOOLCHAIN_PREFIX}/llvm-ar" \
    --ranlib="${TOOLCHAIN_PREFIX}/llvm-ranlib" \
    --strip="${TOOLCHAIN_PREFIX}/llvm-strip" \
    ${COMMON_OPTIONS}
make -j$JOBS
make install-libs
make clean

【5】执行 build_ffmpeg.sh ,然后会在下面目录生成相应的 jni

ExoPlayer/extensions/ffmpeg/src/main/jni/ffmpeg/android-libs

【6】切换至 ExoPlayer/extensions/ffmpeg 目录,使用 gradle 构建

如: gradlew assemble 或 gradle -b build.gradle assemble

【7】生成 ffmpeg 相关的 aar,引入到应用项目中

java 复制代码
implementation files('libs/extension-ffmpeg-release.aar')

【8】在 Exo 中添加 FfmpegAudioRenderer,当然顺序可以前可以可以后,前面的容易被优先使用

java 复制代码
private val mRenderFactory =
    object : DefaultRenderersFactory(MyApp.instance.applicationContext) {
        override fun buildAudioRenderers(
            context: Context,
            extensionRendererMode: Int,
            mediaCodecSelector: MediaCodecSelector,
            enableDecoderFallback: Boolean,
            audioSink: AudioSink,
            eventHandler: Handler,
            eventListener: AudioRendererEventListener,
            out: ArrayList<Renderer>
        ) {
            //添加FfmpegAudioRenderer
            out.add(FfmpegAudioRenderer())
            super.buildAudioRenderers(
                context,
                extensionRendererMode,
                mediaCodecSelector,
                enableDecoderFallback,
                audioSink,
                eventHandler,
                eventListener,
                out
            )
        }
    }.apply {
        setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER)
    }

【8】完毕

4.2 编译错误处理

4.2.1 配置错误处理

bash 复制代码
/bin/sh: /Users/dagege/Library/Android/sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64 /bin/aarch64-linux-androideabi-ar: No such file or directory  
make: *** [libavcodec/libavcodec.a] Error 127  
make: *** Waiting for unfinished jobs....  
AR      libavcodec/libavcodec.a  
/bin/sh: /Users/dagege/Library/Android/sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64 /bin/aarch64-linux-androideabi-ar: No such file or directory  
make: *** [libavcodec/libavcodec.a] Error 127

如果出现这种情况

意味着 configure 时找不到改路径,没关系,添加上正确的路径即可

ini 复制代码
--ar="${TOOLCHAIN_PREFIX}/aarch64-linux-android-ar" 

当然,错误可能不是ar,也可能是其他,在脚本中同样配置即可,如ranlib等

4.2.2 新增配置

实际上新增配置很简单,只需要修改主要配置项如下

java 复制代码
COMMON_OPTIONS="
    --target-os=android
    --enable-static
    --disable-shared
    --enable-jni
    --enable-asm
    --enable-runtime-cpudetect
    --enable-cross-compile
    --disable-doc
    --disable-programs
    --disable-everything
    --disable-avdevice
    --disable-avformat
    --disable-swscale
    --disable-postproc
    --disable-avfilter
    --disable-symver
    --disable-avresample
    --disable-ffmpeg
    --disable-ffplay
    --disable-ffprobe
    --enable-swresample
    --extra-ldexeflags=-pie
    "
TOOLCHAIN_PREFIX="${NDK_PATH}/toolchains/llvm/prebuilt/${HOST_PLATFORM}/bin"
for decoder in "${ENABLED_DECODERS[@]}"
do
    COMMON_OPTIONS="${COMMON_OPTIONS} --enable-decoder=${decoder}"
done

./configure \
    --libdir=android-libs/armeabi-v7a \
    --arch=arm \
    --cpu=armv7-a \
    --cross-prefix="${TOOLCHAIN_PREFIX}/armv7a-linux-androideabi21-" \
    --nm="${TOOLCHAIN_PREFIX}/arm-linux-androideabi-nm" \
    --ar="${TOOLCHAIN_PREFIX}/arm-linux-androideabi-ar" \
    --ranlib="${TOOLCHAIN_PREFIX}/arm-linux-androideabi-ranlib" \
    --strip="${TOOLCHAIN_PREFIX}/arm-linux-androideabi-strip" \
    --extra-cflags="-march=armv7-a -mfloat-abi=softfp" \
    --extra-ldflags="-Wl,--fix-cortex-a8" \
    ${COMMON_OPTIONS}
make -j4
make install-libs
make clean

4.3 引入ffmpeg so到项目中

上面仅仅是编译完成ffmpeg,但是要接入ExoPlayer,我们还需要继续编译,ExoPlayer仅仅提供了音频的解码,c层和java层代码都是写好的,因此这里我们只需要配置一下CmakeList引入即可。

注意:使用 CmakeLists 静态库会自动编入动态库

java 复制代码
cmake_minimum_required(VERSION 3.22.1 FATAL_ERROR)

# Enable C++11 features.
set(CMAKE_CXX_STANDARD 11)

project(libffmpeg_jni C CXX)

if(${ANDROID_ABI} MATCHES "arm64-v8a")
    set(CMAKE_CXX_FLAGS "-Wl,-Bsymbolic")
endif()

set(ffmpeg_location "${CMAKE_CURRENT_SOURCE_DIR}/ffmpeg")
set(ffmpeg_binaries "${ffmpeg_location}/android-libs/${ANDROID_ABI}")

foreach(ffmpeg_lib avutil swresample avcodec)
    set(ffmpeg_lib_filename lib${ffmpeg_lib}.a)
    set(ffmpeg_lib_file_path ${ffmpeg_binaries}/${ffmpeg_lib_filename})
    add_library(
            ${ffmpeg_lib}
            STATIC
            IMPORTED)
    set_target_properties(
            ${ffmpeg_lib} PROPERTIES
            IMPORTED_LOCATION
            ${ffmpeg_lib_file_path})
endforeach()

include_directories(${ffmpeg_location})
find_library(android_log_lib log)

add_library(ffmpeg_jni
            SHARED
            ffmpeg_jni.cc)

#静态库会自动编入ffmpeg,无需手动合并
target_link_libraries(ffmpeg_jni
                      PRIVATE android
                      PRIVATE swresample
                      PRIVATE avcodec
                      PRIVATE avutil
                      PRIVATE ${android_log_lib})

上面CmakeLists中,一个需要注意的点是arm64-v8a的编译需要添加-Wl,-Bsymbolic标记,否则编译过程会找不到源文件

java 复制代码
if(${ANDROID_ABI} MATCHES "arm64-v8a")
    set(CMAKE_CXX_FLAGS "-Wl,-Bsymbolic")
endif()

总结

本篇到这里就结束了,这篇是非常基础的文章。主要介绍了ffmpeg编译,以及为ExoPlayer编译ffmpeg扩展库。后续我们还会为ExoPlayer实现基于ffmpeg的视频解码。当然这里要提一点,在使用ExoPlayer时,ffmpeg仅仅提供libavcodec即可,因为ExoPlayer自身就有解封装的能力。当然,对于mpeg2的视频,ExoPlayer解封装也存在稳定性问题,后续我们可能会涉及解决方法。

本专栏的音视频相关的文章后续会围绕ffmpeg、exoplayer、opengl、双屏异显展开,希望大家继续关注。

相关推荐
ModestCoder_16 分钟前
将一个新的机器人模型导入最新版isaacLab进行训练(以unitree H1_2为例)
android·java·机器人
热爱编程的小曾21 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin33 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox1 小时前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
robin_suli1 小时前
Spring事务的传播机制
android·java·spring
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
鸿蒙布道师2 小时前
鸿蒙NEXT开发对象工具类(TS)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下2 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员