Android编译FFmpeg 6.0 - 支持MediaCodec编解码

前两天听群里的小伙伴说,FFmpeg 6.0已经支持MediaCodec编码了,这不得试试效果。所以直接就开干~~

编译环境

这次采用的交叉编译环境是:Macos 13.2 + GCC + Cmake + NDK 21

编译的第三方库:x264 + mp3lame + fdk-aac + opencore-amr

第三方库 版本 下载地址
ffmpeg 6.0 ffmpeg.org/releases/ff...
x264 X264-20191217.2245 download.videolan.org/pub/videola...
mp3lame 3.100 sourceforge.net/projects/la...
fdkaac 2.0.1-ff69b4 downloads.sourceforge.net/opencore-am...
opencore-amr 1.1.5 sourceforge.net/projects/op...
ndk 21 dl.google.com/android/rep...

交叉编译

检测FFmpeg配置是否支持MediaCodec的编码,确实是支持的,不仅支持h264还支持h265编码,结果如下:

shell 复制代码
./configure --list-encoders | grep mediacodec
h264_mediacodec         pcm_f64be               wmav1
hevc_mediacodec         pcm_s24le_planar        zlib

在FFmpeg 6.0上不需要再开启我们MediaCodec的硬件加速了(是哪个版本取消的,我也不知道 😊),可硬件加速的列表如下:

shell 复制代码
./configure --list-hwaccels
av1_d3d11va             hevc_d3d11va            mpeg2_nvdec             vp8_nvdec
av1_d3d11va2            hevc_d3d11va2           mpeg2_vaapi             vp8_vaapi
av1_dxva2               hevc_dxva2              mpeg2_vdpau             vp9_d3d11va
av1_nvdec               hevc_nvdec              mpeg2_videotoolbox      vp9_d3d11va2
av1_vaapi               hevc_vaapi              mpeg4_nvdec             vp9_dxva2
av1_vdpau               hevc_vdpau              mpeg4_vaapi             vp9_nvdec
h263_vaapi              hevc_videotoolbox       mpeg4_vdpau             vp9_vaapi
h263_videotoolbox       mjpeg_nvdec             mpeg4_videotoolbox      vp9_vdpau
h264_d3d11va            mjpeg_vaapi             prores_videotoolbox     vp9_videotoolbox
h264_d3d11va2           mpeg1_nvdec             vc1_d3d11va             wmv3_d3d11va
h264_dxva2              mpeg1_vdpau             vc1_d3d11va2            wmv3_d3d11va2
h264_nvdec              mpeg1_videotoolbox      vc1_dxva2               wmv3_dxva2
h264_vaapi              mpeg2_d3d11va           vc1_nvdec               wmv3_nvdec
h264_vdpau              mpeg2_d3d11va2          vc1_vaapi               wmv3_vaapi
h264_videotoolbox       mpeg2_dxva2             vc1_vdpau               wmv3_vdpau
  • _nvdec结尾的是NVIDIA显卡解码硬件加速
  • _videotoolbox结尾的是苹果ios和Macos多媒体框架硬件加速

那接下来就直接开始编译,编译的配置文档,网上很多,我这里上一份我使用的配置(网上很多):

shell 复制代码
./configure \
    --prefix=$PREFIX \ # 编译之后的保存位置
    --disable-encoders \ # 禁用所有编码器
    --disable-decoders \ # 禁用所有解码器
    --disable-doc \ # 禁用文档
    --disable-htmlpages \
    --disable-manpages \
    --disable-podpages \
    --disable-txtpages \
    --disable-ffmpeg \ # 禁用 ffmpeg 可执行程序构建
    --disable-ffplay \ # 禁用 ffplay 可执行程序构建
    --disable-ffprobe \ # 禁用 ffprobe 可执行程序构建
    --disable-symver \
    --disable-shared \ # 禁用共享链接
    --disable-asm \
    --disable-x86asm \
    --disable-avdevice \ # 禁用libavdevice构建
    --disable-postproc \ # 禁用libpostproc构建
    --disable-cuvid \ # 禁用Nvidia Cuvid
    --disable-nvenc \ # 禁用Nvidia视频编码
    --disable-vaapi \ # 禁用视频加速API代码(Unix/Intel)
    --disable-vdpau \ # 禁用禁用Nvidia解码和API代码(Unix)
    --disable-videotoolbox \ # 禁用ios和macos的多媒体处理框架videotoolbox
    --disable-audiotoolbox \ # 禁用ios和macos的音频处理框架audiotoolbox
    --disable-appkit \ # 禁用苹果 appkit framework
    --disable-avfoundation \ 禁用苹果 avfoundation framework
    --enable-static \ # 启用静态链接
    --enable-nonfree \ # 启用非免费的组件
    --enable-gpl \ # 启用公共授权组件
    --enable-version3 \ 
    --enable-pic \
    --enable-pthreads \ # 启用多线程
    --enable-encoder=bmp \ 
    --enable-encoder=flv \
    --enable-encoder=gif \
    --enable-encoder=mpeg4 \
    --enable-encoder=rawvideo \
    --enable-encoder=png \
    --enable-encoder=mjpeg \
    --enable-encoder=yuv4 \
    --enable-encoder=aac \
    --enable-encoder=pcm_s16le \
    --enable-encoder=subrip \
    --enable-encoder=text \
    --enable-encoder=srt \
    --enable-libx264 \ # 启用支持h264
    --enable-encoder=libx264 \
    --enable-libfdk-aac \ # 启用支持fdk-aac
    --enable-encoder=libfdk_aac \
    --enable-decoder=libfdk_aac \
    --enable-libmp3lame \ # 启用支持mp3lame
    --enable-encoder=libmp3lame \
    --enable-libopencore-amrnb \ # 启用支持opencore-amrnb
    --enable-encoder=libopencore_amrnb \
    --enable-decoder=libopencore_amrnb \
    --enable-libopencore-amrwb \ # 启用支持opencore-amrwb
    --enable-decoder=libopencore_amrwb \
    --enable-mediacodec \ # 启用支持mediacodec
    --enable-encoder=h264_mediacodec \
    --enable-encoder=hevc_mediacodec \
    --enable-decoder=h264_mediacodec \
    --enable-decoder=hevc_mediacodec \
    --enable-decoder=mpeg4_mediacodec \
    --enable-decoder=vp8_mediacodec \
    --enable-decoder=vp9_mediacodec \
    --enable-decoder=bmp \
    --enable-decoder=flv \
    --enable-decoder=gif \
    --enable-decoder=mpeg4 \
    --enable-decoder=rawvideo \
    --enable-decoder=h264 \
    --enable-decoder=png \
    --enable-decoder=mjpeg \
    --enable-decoder=yuv4 \
    --enable-decoder=aac \
    --enable-decoder=aac_latm \
    --enable-decoder=pcm_s16le \
    --enable-decoder=mp3 \
    --enable-decoder=flac \
    --enable-decoder=srt \
    --enable-decoder=xsub \
    --enable-small \
    --enable-neon \
    --enable-hwaccels \
    --enable-jni \
    --enable-cross-compile \
    --cross-prefix=$CROSS_PREFIX \
    --target-os=android \
    --arch=$COMPILE_ARCH \
    --cpu=$ANDROID_CUP \
    --cc=$CC \
    --cxx=$CXX \
    --nm=$NM \
    --ar=$AR \
    --as=$AS \
    --strip=$STRIP \
    --ranlib=$RANLIB \
    --sysroot=$SYSROOT \
    --extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \
    --extra-ldflags="$ADDI_LDFLAGS"

我这里编译的是静态链接库也就是.a文件,所以我禁用了共享链接库,如果你编译.so文件,那么就修改如下:

shell 复制代码
--disable-static \ # 禁用静态链接
--enable-shared \ # 启用共享链接

因为我们不需要使用到avdevice和postproc,所以我选择禁用,这样最终只会生成6个.a文件

shell 复制代码
 --disable-avdevice \ # 禁用libavdevice构建
 --disable-postproc \ # 禁用libpostproc构建

通过构建脚本的方式我们将上面的configuremake命令做成一个shell脚本,如果需要可以在评论区留言。

开始编译

然后我们就进入FFmpeg 6.0目录,通过terminal执行我们的shell脚本。编译的时候只遇到了这么一个问题:

x264 pkg-config没找到,报错的内容如下:

shell 复制代码
➜  ffmpeg ./darwin_android_lite.sh
compiling ffmpeg for armeabi-v7a
ERROR: x264 not found using pkg-config

If you think configure made a mistake, make sure you are using the latest
version from Git.  If the latest version fails, report the problem to the
ffmpeg-user@ffmpeg.org mailing list or IRC #ffmpeg on irc.libera.chat.
Include the log file "ffbuild/config.log" produced by configure as this will help
solve the problem.

这时就需要我们在我们的配置里面加上如下内容:

shell 复制代码
--pkg-config="pkg-config --static"

这个就表示需要我们指定pkg-config位置

shell 复制代码
export PKG_CONFIG_PATH=$(pwd)/../x264/android/$ANDROID_ABI/lib/pkgconfig

加上如上内容,基本在检查配置的环节基本就不会有问题了,然后我们继续执行就可以得到一个编译结果。

  • include:放置的是.h的头文件。
  • lib:编译好的.so或者.a文件以及pkg-config。
  • share:demo和一些api相关内容。

我通过ld工具将所有的.a合成了一个libffmpeg-org.so的文件,就不需要对上面6个进行分别加载了,只需要加载一个就行。

kotlin 复制代码
System.loadLibrary("ffmpeg-org")

那多个合成一个的配置,也在我们的shell脚本中,大致就是如下:

bash 复制代码
$TOOLCHAIN_EXECUTE/${CROSS_COMPILE}ld \
    -rpath-link=$SYSROOT/usr/lib/$HOST/$API \
    -L$SYSROOT/usr/lib/$HOST/$API \
    -L$TOOLCHAIN/lib/gcc/$HOST/4.9.x \
    -L$PREFIX/lib -soname libffmpeg-org.so \
    -shared -Bsymbolic --whole-archive --no-undefined -o \
    $PREFIX/libffmpeg-org.so \
    $PREFIX/lib/libavcodec.a \
    $PREFIX/lib/libavfilter.a \
    $PREFIX/lib/libswresample.a \
    $PREFIX/lib/libavformat.a \
    $PREFIX/lib/libavutil.a \
    $PREFIX/lib/libswscale.a \
    $X264_LIB/libx264.a \
    $FDK_LIB/libfdk-aac.a \
    $LAME_LIB/libmp3lame.a \
    $AMR_LIB/libopencore-amrnb.a \
    $AMR_LIB/libopencore-amrwb.a \
    -lc -lm -lz -ldl -llog -landroid --dynamic-linker=/system/bin/linker \
    $TOOLCHAIN/lib/gcc/$HOST/4.9.x/libgcc_real.a || exit 1

上方配置有一个需要注意的点,我们一定要使用libgcc_real.a,而不是libgcc.a,这里被误导了好久。以前一直使用的是libgcc.a,也没有问题,但这次使用NDK21就出现了这个问题。

到这里我们的交叉编译就完成了,那我们接下来就看看怎么使用。

使用

  1. 我们新建一个native项目,然后将我们编译好的libffmpeg-org.so放置于jniLibs目录下。
  1. 将我们之前编译的头文件,也就是上面提到的include下的所有文件导入项目cpp目录下
  1. 将我们的之前的libffmpeg-org.org加入我们的CmakeLists.txt的配置中
scss 复制代码
add_library(
        ffmpeg-org
        SHARED
        IMPORTED
)
SET_TARGET_PROPERTIES(
        ffmpeg-org
        PROPERTIES IMPORTED_LOCATION
        ${PROJECT_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}/libffmpeg-org.so
)
include_directories(ffmpeg) # 头文件相对路径
  1. 新建我们自己的ffmpeg-cmd.cpp并加入CmakeLists.txt
bash 复制代码
add_library(
        ffmpeg-cmd
        SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        ffmpeg-cmd.cpp)
  1. 最后动态链接所有内容,完整CmakeLists.txt的配置如下
scss 复制代码
cmake_minimum_required(VERSION 3.22.1)

project("ffmpeg")

add_library(
        ffmpeg-cmd
        SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        ffmpeg-cmd.cpp)

add_library(
        ffmpeg-org
        SHARED
        IMPORTED
)
SET_TARGET_PROPERTIES(
        ffmpeg-org
        PROPERTIES IMPORTED_LOCATION
        ${PROJECT_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}/libffmpeg-org.so
)

include_directories(ffmpeg) # 头文件相对路径

target_link_libraries(
        ffmpeg-cmd
        ffmpeg-org
        # List libraries link to the target library
        android
        log)
  1. 新建我们的FFmpegCmd类,将so动态加载,并新增一个获取所有编解码器的方法。
kotlin 复制代码
class FFmpegCmd {

    init {
        System.loadLibrary("ffmpeg-org")
        System.loadLibrary("ffmpeg-cmd")
    }

    external fun getSupportCodecs():String?
}
  1. 编辑我们的ffmpeg-cmd.cpp,在其内容关联getSupportCodecs方法
arduino 复制代码
extern "C"
JNIEXPORT jstring JNICALL
Java_com_adaiyuns_ffmpeg_jni_FFmpegCmd_getSupportCodecs(JNIEnv *env, jobject thiz) {

    return nullptr;
}

这里需要注意上面的函数名,函数名为FFmpegCmd的全路径。那我们就实现一个获取所有的编解码器

arduino 复制代码
#include <jni.h>
#include <string>

extern "C"{
#include "ffmpeg/libavcodec/avcodec.h"
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_adaiyuns_ffmpeg_jni_FFmpegCmd_getSupportCodecs(JNIEnv *env, jobject thiz) {
    // 定义临时缓存区
    char info[20000] = {0};
    // 初始化编码器遍历器
    void *opaque = NULL;
    const AVCodec *avcodec = av_codec_iterate(&opaque);

    // 遍历所有支持的编码器
    while (avcodec != NULL) {
        sprintf(info, "%s%s,", info, avcodec->name);
        avcodec = av_codec_iterate(&opaque);
    }

    return env->NewStringUTF(info);
}

可以看一下执行之后的效果

从图上可以看到我们的ffmpeg已经支持h264_mediacodechevc_mediacodec。到此基本就完成了简单的集成。✿✿ヽ(°▽°)ノ✿

开源库

基于简单的音视频处理,我也开发了FFmpeg的开源库FFmpegCommand,大致支持如下特色功能:

特色功能 支持 描述
ffmpeg命令 支持所有的ffmpeg命令
进度回调 支持所有命令的回调
命令取消 支持在命令执行过程中取消命令执行
debug模式 支持开启/关闭调试模式
获取媒体信息 获取媒体信息(宽、高...)
MediaCodec编解码 支持MediaCodec(从 v1.3.0)
平台架构 支持 armeabi-v7a, arm64-v8a
独立so 将多个so合并成一个 ffmpeg-org.so

开源库中提供了常用的命令和方法FFmpegUtils,可直接使用其中的方法。

kotlin 复制代码
MainScope().launch(Dispatchers.IO) {
    FFmpegCommand.runCmd(FFmpegUtils.transformAudio(audioPath, targetPath), callback("音频转码完成", targetPath))
}

也可以自定义命令,以下是一个自定义使用MediaCodec进行解码、编码、转格式的例子:

kotlin 复制代码
// shell 命令: ffmpeg -y -c:v h264_mediacodec -i inputPath -c:v h264_mediacodec outputPath
val command = CommandParams()
    .append("-c:v") // 设置解码器
    .append("h264_mediacodec")
    .append("-i")
    .append(inputPath)
    .append("-b") // 硬编码一般需要设置视频的比特率(bitrate)
    .append("1500k")
    .append("-c:v") // 设置编码器
    .append("h264_mediacodec")
    .append(outputPath)
    .get()

MainScope().launch(Dispatchers.IO) {
    FFmpegCommand.runCmd(command, callback("格式转换成功", targetPath))
}

需要注意:

  • 在使用MediaCodec进行编码的时候,必须同时配置MediaCodec解码,如上例子所示,不然会造成失败!!!
  • H264编解码器是h264_mediacodec,H265的编解码器是hevc_mediacodec。同时可以使用H264解码和H265编码。
  • 硬编码一般需要设置视频的比特率,否则会出现画面模糊不清晰的情况。
  • 最好使用CommandParams构建我们的命令参数,这样能保证参数不被路径中空格影响,导致命令执行不成功。

最后用相同的一个视频文件(4.07 MB (4,277,182 字节)),分别使用h264_mediacodeclibx264进行转码流程,得出如下结果,所以小伙伴们快使用MediaCodec进行编码吧~~

编解器 耗时
h264_mediacodec 4507毫秒 ≈ 4.5秒
libx264 57264毫秒 ≈ 57.2秒
相关推荐
ᥬ 小月亮8 分钟前
webpack基础
前端·webpack
移动开发者1号24 分钟前
深入理解原子类与CAS无锁编程:原理、实战与优化
android·kotlin
陈卓41025 分钟前
MySQL-主从复制&分库分表
android·mysql·adb
YongGit27 分钟前
探索 AI + MCP 渲染前端 UI
前端·后端·node.js
移动开发者1号28 分钟前
深入理解 ThreadLocal:原理、实战与优化指南
android·kotlin
zhangphil30 分钟前
Android PNG/JPG图ARGB_8888/RGB_565‌解码形成Bitmap在物理内存占用大小的简单计算
android
慧一居士1 小时前
<script setup>中的setup作用以及和不带的区别对比
前端
厦门德仔1 小时前
【WPF】WPF(样式)
android·java·wpf
RainbowSea1 小时前
NVM 切换 Node 版本工具的超详细安装说明
java·前端
读书点滴2 小时前
笨方法学python -练习14
java·前端·python