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

字节流动

共 11294字,需浏览 23分钟

 · 2024-04-11

编译环境


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

4cb8f8f5749705b6f03d11d3dba7b760.webp

交叉编译

检测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构建
7fc88492c0f877282681a9533f3ed793.webp生成的.a文件 通过构建脚本的方式我们将上面的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
加上如上内容,基本在检查配置的环节基本就不会有问题了,然后我们继续执行就可以得到一个编译结果。 0841c5a328364d0f778ecbbc63a63438.webp
  • 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就出现了这个问题。 bc43b13de091faa64161f9e218f67b92.webplibgcc 到这里我们的交叉编译就完成了,那我们接下来就看看怎么使用。

使用

  1. 我们新建一个native项目,然后将我们编译好的libffmpeg-org.so放置于jniLibs目录下。
4fb3279736b955bc97c4d5b5cfbde8c6.webp将我们之前编译的头文件,也就是上面提到的include下的所有文件导入项目cpp目录下 a4aeafaacd1480dcb3f6e3ad5ee3282e.webp
  1. 将我们的之前的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);
}
可以看一下执行之后的效果 f96028200387ec1a92a8c634ef45f6e4.webp 从图上可以看到我们的ffmpeg已经支持h264_mediacodechevc_mediacodec。到此基本就完成了简单的集成。✿✿ヽ(°▽°)ノ✿

开源库

基于简单的音视频处理,我也开发了FFmpeg的开源库 FFmpegCommand ,大致支持如下特色功能: d9537db6a60df271162e79c89d665833.webp 开源库中提供了常用的命令和方法 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_mediacodec 4507毫秒 ≈ 4.5秒
libx264 57264毫秒 ≈ 57.2秒
原文链接: https://juejin.cn/post/7297838901090648118资源链接: https://github.com/AnJoiner/FFmpegCommand
-- END --

进技术交流群,扫码添加我的微信:Byte-Flow

获取相关资料和源码

学习音视频、OpenGL ES、Vulkan 、Metal、图像滤镜、视频特效及相关渲染技术的付费社群,面试指导,1v1 简历服务,职业规划。 我的付费社群 推荐: Android FFmpeg 实现带滤镜的微信小视频录制功能全网最全的 Android 音视频和 OpenGL ES 干货,都在这了 一文掌握 YUV 图像的基本处理

抖音传送带特效是怎么实现的?

所有你想要的图片转场效果,都在这了

面试官:如何利用 Shader 实现 RGBA 到 NV21 图像格式转换?

我用 OpenGL ES 给小姐姐做了几个抖音滤镜
浏览 6
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报