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

编译环境

这次采用的交叉编译环境是:Macos 13.2 + GCC + Cmake + NDK 21 编译的第三方库:x264 + mp3lame + fdk-aac + opencore-amr

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

交叉编译

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

./configure --list-encoders | grep mediacodec
h264_mediacodec         pcm_f64be               wmav1
hevc_mediacodec         pcm_s24le_planar        zlib

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

./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多媒体框架硬件加速

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

./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文件,那么就修改如下:

--disable-static \ # 禁用静态链接
--enable-shared \ # 启用共享链接

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

 --disable-avdevice \ # 禁用libavdevice构建
 --disable-postproc \ # 禁用libpostproc构建
Android 编译 FFmpeg 6.0 - 支持MediaCodec编解码

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

开始编译

然后我们就进入FFmpeg 6.0目录,通过terminal执行我们的shell脚本。编译的时候只遇到了这么一个问题: x264 pkg-config没找到,报错的内容如下:

➜  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.

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

--pkg-config="pkg-config --static"

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

export PKG_CONFIG_PATH=$(pwd)/../x264/android/$ANDROID_ABI/lib/pkgconfig

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

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

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

System.loadLibrary("ffmpeg-org")

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

$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就出现了这个问题。

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

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

使用

  1. 我们新建一个native项目,然后将我们编译好的libffmpeg-org.so放置于jniLibs目录下。
Android 编译 FFmpeg 6.0 - 支持MediaCodec编解码

2. 将我们之前编译的头文件,也就是上面提到的include下的所有文件导入项目cpp目录下

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

3. 将我们的之前的libffmpeg-org.org加入我们的CmakeLists.txt的配置中

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
add_library(
        ffmpeg-cmd
        SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        ffmpeg-cmd.cpp)
  1. 最后动态链接所有内容,完整CmakeLists.txt的配置如下
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动态加载,并新增一个获取所有编解码器的方法。
class FFmpegCmd {

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

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

    return nullptr;
}

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

#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);
}

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

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

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

开源库

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

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

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

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

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

// 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_mediacodec4507毫秒 ≈ 4.5秒
libx26457264毫秒 ≈ 57.2秒

原文链接: https://juejin.cn/post/7297838901090648118

资源链接: https://github.com/AnJoiner/FFmpegCommand

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论