存稿。本篇主要解析 MediaCodecList,学习如何分析厂商的编解码硬件的性能。
个人在音视频方面没有什么建树,只能提供一些学习思路:
推荐关注博客:
雷霄骅:非常全面的 FFmpeg 入门教程。
也可以看一下笔者的几篇博客:
推荐关注两个开源项目:
跨平台项目,C/C++ 实现,有 Java 接口,支持视频、音频通信,看过部分源码,在 Android 端,主要还是通过 MediaCodec 利用硬件进行编解码。
一个用于录制、转换和流式传输音频和视频的完整的跨平台解决方案。主要是 c 实现,没有 Java 接口,但可以通过封装 JNI 进行调用。它主要支持软解,在核心部分通过汇编语言,通过单指令多数据(即SIMD),充分利用硬件特性进行加速。FFmpeg6.0 之后,在 Android 平台,也支持通过 MediaCodec,利用硬件进行编解码。
软解和硬解
软解主要通过 CPU 去处理,这意味着 CPU 占用高,但兼容性好。而硬解是使用专用硬件(而非通用的 CPU)来编解码音视频,但不同的厂商提供的硬解硬件参差不齐,比如,很多只支持 h264,不支持 h265,支持的视频分辨率也有差异。
所以,方案选择上,我们可以优先用硬解,不行再降级为软解.
MediaCodecList
MediaCodecList是系统支持的编/解码器(codec
)的信息列表,通过加载厂家配置好的media_codecs.xml
、media_codecs_performance.xml
生成,便于我们分析系统的编/解码器的硬件性能。
编/解码器按照设备制造商定义的首选顺序列在MediaCodecList中。因此,应用程序应使用列表中第一个合适的编/解码器,以实现功耗和性能之间的最佳平衡。
MediaCodecList的使用
有些人可能习惯通过MediaCodec的createDecoderByType(String type)
和createEncoderByType(String type)
直接初始化对应的编/解码器。
但官方更推荐通过MediaCodecList的findEncoderForFormat/findDecoderForFormat,选择最合适的编/解码器。下面展示如何通过MediaCodecList的findDecoderForFormat,初始化一个宽为1920,高为1080的h264视频的解码器。
ini
MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
MediaFormat videoFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080);
String name = mediaCodecList.findDecoderForFormat(videoFormat);
MediaCodec codec = null;
try {
codec = MediaCodec.createByCodecName(name);
} catch (IOException e) {
e.printStackTrace();
}
media_codecs.xml
media_codecs.xml提供编/解码器的一些基本信息。通过它,我们可以了解到编/解码器支持的视频分辨率、宽高对齐要求等等信息。
获取
media_codecs.xml可以通过adb命令获取:
bash
adb shell cat /system/etc/media_codecs.xml
或者
adb shell cat /vendor/etc/media_codecs.xml
也可以通过Android Studio的Device File Explorer在对应的文件夹下找到,双击打开即可浏览:
bash
/system/etc/media_codecs.xml
或者
/vendor/etc/media_codecs.xml
内容
xml
<MediaCodecs>
<!-- 编码器 -->
<Encoders>
<!-- Video Hardware -->
<MediaCodec name="OMX.qcom.video.encoder.avc" type="video/avc" >
<Limit name="size" min="96x96" max="4096x2304" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="blocks-per-second" min="36" max="1958400" />
<Limit name="bitrate" range="1-160000000" />
<Limit name="frame-rate" range="1-480" />
<Limit name="concurrent-instances" max="16" />
<Limit name="performance-point-4096x2304" value="56" />
<Limit name="performance-point-1920x1080" value="240" />
<Limit name="performance-point-1280x720" value="480" />
</MediaCodec>
...
</Encoders>
<!-- 解码器 -->
<Decoders>
...
</Decoders>
<MediaCodecs>
这是一个小米平板的media_codecs.xml的部分内容。比较关键的信息是:
- size:支持的宽高。min是最小宽高,max是最大宽高。
- alignment:宽高的对齐要求。硬解的对齐要求,根据编解码的格式、不同厂商的设备而不同。"2x2"表示,宽、高都必须是2的倍数,否则可能导致编码失败、视频有黑边等情况。
- block-size:即宏块大小。很多视频格式,比如h264、mpeg2等,都是基于宏块的格式。
- blocks-per-second:宏块每秒的处理数。
- bitrate:支持的比特率范围。
- frame-rate:支持的帧率范围。
- concurrent-instances:并发实例。这里表示最多支持16个编码器并发。
- performance-point-* :表示分辨率支持的最高帧率。
解码器的信息与编码器基本一致,这里不再一一列举。
编码
media_codecs.xml加载到内存后,就是MediaCodecList的一部分。MediaCodecList的本质是一个MediaCodecInfo
数组,每个MediaCodecInfo对应一个编/解码器的信息。
通过一个日志打印函数,简单介绍一下如何MediaCodecInfo获取media_codecs.xml对应的信息:
scss
private void printMediaCodecInfo(MediaCodecInfo info, String type) {
MediaCodecInfo.CodecCapabilities capabilitiesForType = info.getCapabilitiesForType(type);
MediaCodecInfo.VideoCapabilities vc = capabilitiesForType.getVideoCapabilities();
StringBuilder builder = new StringBuilder();
builder.append("MediaCodecInfo: \n")
.append("width = ").append(vc.getSupportedWidths()).append(", ")
.append("height = ").append(vc.getSupportedHeights()).append("\n")
.append("widthAlignment = ").append(vc.getWidthAlignment()).append(", ")
.append("heightAlignment = ").append(vc.getHeightAlignment()).append("\n")
.append("bitrateRange = ").append(vc.getBitrateRange()).append(", ")
.append("supportedFrameRates = ").append(vc.getSupportedFrameRates());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
builder.append("\n");
builder.append("PerformancePoints = [\n");
int size = vc.getSupportedPerformancePoints().size();
for (int i = 0; i < size; i++) {
builder.append(" ");
builder.append(vc.getSupportedPerformancePoints().get(i));
builder.append(i < size - 1?",\n":"\n");
}
builder.append("]");
}
Log.d(TAG, builder.toString());
}
我们传入一个MediaCodecInfo实例和MediaFormat.MIMETYPE_VIDEO_AVC
(即H.264)后,日志打印结果如下:
ini
MediaCodecInfo:
width = [96, 4096], height = [96, 2304]
widthAlignment = 2, heightAlignment = 2
bitrateRange = [1, 160000000], supportedFrameRates = [1, 480]
PerformancePoints = [
PerformancePoint(4096x2304@56),
PerformancePoint(4096x2160@60),
PerformancePoint(3840x2160@60),
PerformancePoint(1920x1088@240),
PerformancePoint(1280x720@480)
]
可以看到日志输出和我们之前在media_codecs.xml看到的信息一致。
media_codecs_performance.xml
获取
与media_codecs.xml类似。主要记录编/解码器的一些性能测试数据,让我们比较直观地了解到编/解码器的性能优劣。
内容
xml
<MediaCodecs>
<Encoders>
<MediaCodec name="OMX.qcom.video.encoder.avc" type="video/avc" update="true">
<Limit name="measured-frame-rate-320x240" range="238-238" />
<Limit name="measured-frame-rate-720x480" range="277-287" />
<Limit name="measured-frame-rate-1280x720" range="46-102" />
<Limit name="measured-frame-rate-1920x1080" range="116-118" />
</MediaCodec>
...
</Encoders>
<Decoders>
...
</Decoders>
</MediaCodecs>
- measured-frame-rate-* :表示某个分辨率的视频能达到的编解码帧速率。这是设备制造商提供的性能估计,是对编解码器支持的不同配置的常见尺寸视频进行全速解码和编码测量的统计抽样。
编码
也是通过MediaCodecInfo获取。
ini
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Range<Double> achievableFrameRatesFor = vc.getAchievableFrameRatesFor(1280, 720);
}
输出结果类似:
ini
[1280x720] achievableFrameRate = [46.0, 102.0]
MediaCodecList初始化
MediaCodecList初始化流程图
parseXmlFilesInSearchDir()是用来搜索media_codecs.xml、media_codecs_performance.xml等文件,有默认参数,头文件声明如下:
c
status_t parseXmlFilesInSearchDirs(
const std::vector<std::string> &xmlFiles = getDefaultXmlNames(),
const std::vector<std::string> &searchDirs = getDefaultSearchDirs());
getDefaultXmlNames()和getDefaultSearchDirs()如下:
c
std::vector<std::string> MediaCodecsXmlParser::getDefaultXmlNames() {
static constexpr char const* prefixes[] = {
"media_codecs",
"media_codecs_performance"
};
static std::vector<std::string> variants = {
android::base::GetProperty("ro.media.xml_variant.codecs", ""),
android::base::GetProperty("ro.media.xml_variant.codecs_performance", "")
};
static std::vector<std::string> names = {
prefixes[0] + variants[0] + ".xml",
prefixes[1] + variants[1] + ".xml",
// shaping information is not currently variant specific.
"media_codecs_shaping.xml"
};
return names;
}
static std::vector<std::string> getDefaultSearchDirs() {
return { "/product/etc",
"/odm/etc",
"/vendor/etc",
"/system/etc" };
}
MediaCodecList初始化源码流程概览
MediaCodecList.java
└───android_media_MediaCodecList.cpp
└────android_media_MediaCodecList_getCodecCount()
└──────getCodecList()
└───────MediaCodecList.cpp
└────────getInstance()
└─────────MediaPlayerService.cpp
└──────────getCodecList()
└───────────MediaCodecList.cpp
└────────────getLocalInstance()
└─────────────MediaCodecList()
└──────────────Codec2InfoBuilder.cpp
└───────────────buildMediaCodecList()
└────────────────MediaCodecsXmlParser.cpp
└─────────────────parseXmlFilesInSearchDirs()
└──────────────────parseXmlPath()
└───────────────────parseXmlFile()
结语
在 Android 平台,我们可以通过 MediaCodecList 这个类去检测设备支持的分辨率、帧率(本质就是读取media_codec.xml),对硬解条件不同的设备进行编码适配。