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、双屏异显展开,希望大家继续关注。

相关推荐
迷雾漫步者1 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-2 小时前
验证码机制
前端·后端
燃先生._.3 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
拭心3 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
高山我梦口香糖4 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235244 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240255 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar5 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人5 小时前
前端知识补充—CSS
前端·css