Android 编译 FFmpeg 6.0 - 支持MediaCodec编解码
编译环境
这次采用的交叉编译环境是: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
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
加上如上内容,基本在检查配置的环节基本就不会有问题了,然后我们继续执行就可以得到一个编译结果。
- 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 到这里我们的交叉编译就完成了,那我们接下来就看看怎么使用。
使用
- 我们新建一个native项目,然后将我们编译好的
libffmpeg-org.so
放置于jniLibs目录下。
include
下的所有文件导入项目cpp
目录下
- 将我们的之前的
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) # 头文件相对路径
- 新建我们自己的
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)
- 最后动态链接所有内容,完整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)
- 新建我们的FFmpegCmd类,将
so
动态加载,并新增一个获取所有编解码器的方法。
class FFmpegCmd {
init {
System.loadLibrary("ffmpeg-org")
System.loadLibrary("ffmpeg-cmd")
}
external fun getSupportCodecs():String?
}
- 编辑我们的
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);
}
可以看一下执行之后的效果
从图上可以看到我们的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 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
构建我们的命令参数,这样能保证参数不被路径中空格影响,导致命令执行不成功。
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 简历服务,职业规划。 我的付费社群 推荐: Android FFmpeg 实现带滤镜的微信小视频录制功能全网最全的 Android 音视频和 OpenGL ES 干货,都在这了 一文掌握 YUV 图像的基本处理 所有你想要的图片转场效果,都在这了面试官:如何利用 Shader 实现 RGBA 到 NV21 图像格式转换?
我用 OpenGL ES 给小姐姐做了几个抖音滤镜评论