当前位置: 欣欣网 > 码农

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

2024-03-06码农

编译环境

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

交叉编译

检测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构建

    生成的.a文件
    通过构建脚本的方式我们将上面的 configure make 命令做成一个 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 [email protected] 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 加上如上内容,基本在检查配置的环节基本就不会有问题了,然后我们继续执行就可以得到一个编译结果。

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

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

    使用

    1. 我们新建一个native项目,然后将我们编译好的 libffmpeg-org.so 放置于jniLibs目录下。
    将我们之前编译的头文件,也就是上面提到的 include 下的所有文件导入项目 cpp 目录下
    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") } externalfun getSupportCodecs():String? }

    1. 编辑我们的 ffmpeg-cmd.cpp ,在其内容关联 getSupportCodecs 方法

    extern"C"JNIEXPORT jstring JNICALL Java_com_adaiyuns_ffmpeg_jni_FFmpegCmd_getSupportCodecs(JNIEnv *env, jobject thiz){ returnnullptr; } 这里需要注意上面的函数名,函数名为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); } 可以看一下执行之后的效果 从图上可以看到我们的ffmpeg已经支持 h264_mediacodec hevc_mediacodec 。到此基本就完成了简单的集成。✿✿ヽ(°▽°)ノ✿

    开源库

    基于简单的音视频处理,我也开发了FFmpeg的开源库 FFmpegCommand ,大致支持如下特色功能: 开源库中提供了常用的命令和方法 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 outputPathval 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_mediacodec libx264 进行转码流程,得出如下结果,所以小伙伴们快使用 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 简历服务,职业规划。 我的付费社群 推荐: