FFmpeg Android 硬件编解码现状及展望
一、功能特性
自FFmpeg 6.0版本开始,FFmpeg对Android硬件编解码的支持逐步完善了,当前支持的功能特性包括:
通过JNI调用Java MediaCodec
直接调用NDK MediaCodec
解码:H264、H265、MPEG2、MPEG4、VP8、VP9、AV1
编码:H264、H265、MPEG4、VP8、VP9、AV1
输入输出方式
Surface/ANativeWindow作为编码器输入,作为解码器输出
Buffer输入输出:YUV420P/NV12格式的AVFrame
值得一提的是,Java MediaCodec和NDK MediaCodec两套编解码的实现是共存的,运行时可以通过参数控制选择用哪一个,并且在获取不到JVM时,自动选择NDK MediaCodec的实现。
基于NDK MediaCodec的实现一方面性能更好,另一方面是可以脱离JVM环境运行:
你可以在adb命令行里执行FFmpeg命令做硬件编解码,做推流
你也可以在App程序里直接调用FFmpeg命令行
Android上运行FFmpeg命令行,当前有两大热门的应用场景:
termux https://github.com/termux/termux-app
ffmpeg-kit https://github.com/arthenica/ffmpeg-kit
二、亮点功能
做Android音视频开发的同学熟悉,MediaCodec编码器对非16整数倍分辨率的视频有兼容性问题,特别是早期华为麒麟的芯片,容易crash。我在FFmpeg里实现MediaCodec硬件编码时,通过FFmpeg自带的bsf(bit stream filter)解决了和分辨率相关的兼容性问题。简单来说,就是自动做align,再通过bsf修改输出的码流,修改SPS中的crop部分,裁切掉多余的padding。
三、已知缺陷
当前编码器支持YUV420P和NV12两种pixel format输入。但是,某些芯片只支持NV12格式,配置YUV420P格式会报错。
1、设备支持哪种pixel format,Android只提供了Java的API,没有NDK API,没有JVM时就没法查询
2、即使能够查询,也得要知道MediaCodec编码器的名字,知道MediaCodec编码器的名字是在avcodec open之后,设置pixel format是在avcodec open之前
3、编码器的pixel format是用户设定的,不像解码器有个get_format回调能够协商
以上种种困难导致的后果就是,如果你设置编码器输入格式是YUV420P,打开编码器可能失败。
NV12基本都支持,一般建议用NV12格式。之所以提供YUV420P作为一个选项,是在输入图像本身是YUV420P的情况下,省去一道YUV420P到NV12的转换,提升性能。
最近发现的另一个问题是:有个Vivo手机,用的联发科天玑9000的CPU,AMediaFormat_getRect返回图像的right/bottom信息错乱了,结果是解码正常,但是按照crop信息输出,图像只剩一小块了。可能要加黑名单,忽略掉联发科芯片返回的crop信息。
四、展望
未来我会重点放在NDK MediaCodec上,通过JNI调用Java MediaCodec实在繁琐,只做基本维护,不做开发了。
高版本Android提供了异步的NDK MediaCodec接口,我在编码器上实现了一个版本,性能提升挺明显的,patch还没合并到FFmpeg仓库里,打算再多做些测试。解码器也会加上异步方式,只是原有的解码器实现比较乱,收拾起来要花更多时间。
等给FFmpeg MediaCodec加上异步的支持之后,再支持鸿蒙的硬件编解码基本水到渠成了,因为看公开的API,基本上只是把Android MediaCodec的API换了个鸿蒙的前缀。现在没有开发测试环境,等有条件再说吧!
关于10bit的编解码支持,当前可以用Surface/ANativeWindows做输入输出来实现,Android没提供可用的10bit的pixel format。HDR编码的支持,看Android API情况和应用场景而定,对我而言优先级不高。类似的还有tunneled 模式,支持起来也许不难,但是构造测试场景麻烦。
肯定还有其他场景我没考虑到的,欢迎来聊。