自从Cisco 宣布旗下的H264 Codec开源为OpenH264,并且替所有OpenH264的使用者支付了H264的专利费,WebRTC也随即对h264进行了支持, 在Android平台, 软编用 OpenH264, 软解用FFMPGE, 硬编硬解用 MediaCodec. 在android和iOs中软编软解默认是禁止的,要想启用需要把OpenH264和FFMPGE编译进来,这样就会大大增加库的大小,况且软编软解比较费cpu,耗电量和发热都会增加,使用软编软解的好处是编解码不依赖于硬件,不存在设备适配问题。硬件编解码能够解决软编软解的痛处,但是存在设备适配问题,因为不同的设备使用的硬解码器不同,可能会导致编码失败及解码失败。以android为例,由于android厂商众多,使用的cpu芯片更是繁多,同一芯片厂商不同型号实现的硬解码器也有不同,这样就造成适配很困难,况且h264不是google推荐使用的编解码器(毕竟不是亲生的),所以在适配方面存在很多问题。
android中硬编码实现在MediaCodecVideoEncoder.java中,硬解码实现在MediaCodecVideoDecoder.java中,下面逐个分析
来看一下如何创建编码器:
boolean initEncode(VideoCodecType type, int profile, int width, int height, int kbps, int fps,
boolean useSurface) {
...
EncoderProperties properties = null;
String mime = null;
int keyFrameIntervalSec = 0;
boolean configureH264HighProfile = false;
if (type == VideoCodecType.VIDEO_CODEC_VP8) {
mime = VP8_MIME_TYPE;
properties = findHwEncoder(
VP8_MIME_TYPE, vp8HwList(), useSurface ? supportedSurfaceColorList : supportedColorList);
keyFrameIntervalSec = 100;
} else if (type == VideoCodecType.VIDEO_CODEC_VP9) {
mime = VP9_MIME_TYPE;
properties = findHwEncoder(
VP9_MIME_TYPE, vp9HwList, useSurface ? supportedSurfaceColorList : supportedColorList);
keyFrameIntervalSec = 100;
} else if (type == VideoCodecType.VIDEO_CODEC_H264) {
mime = H264_MIME_TYPE;
properties = findHwEncoder(H264_MIME_TYPE, h264HwList(),
useSurface ? supportedSurfaceColorList : supportedColorList);
if (profile == H264Profile.CONSTRAINED_HIGH.getValue()) {
EncoderProperties h264HighProfileProperties = findHwEncoder(H264_MIME_TYPE,
h264HighProfileHwList, useSurface ? supportedSurfaceColorList : supportedColorList);
if (h264HighProfileProperties != null) {
Logging.d(TAG, "High profile H.264 encoder supported.");
configureH264HighProfile = true;
} else {
Logging.d(TAG, "High profile H.264 encoder requested, but not supported. Use baseline.");
}
}
keyFrameIntervalSec = 20;
} else {
throw new RuntimeException("initEncode: Non-supported codec " + type);
}
...
}
由于我们使用的是h264编码,传入initEncode函数的type参数是VideoCodecType.VIDEO_CODEC_H264,接下来调用:
findHwEncoder(H264_MIME_TYPE, h264HwList(),useSurface ? supportedSurfaceColorList : supportedColorList)去寻找可用的编码器,该函数第一个和最后一个参数很好理解,中间h264HwList()是什么鬼?往下看:
// List of supported HW H.264 encoders.
private static final MediaCodecProperties qcomH264HwProperties = new MediaCodecProperties(
"OMX.qcom.", Build.VERSION_CODES.KITKAT, BitrateAdjustmentType.NO_ADJUSTMENT);
private static final MediaCodecProperties exynosH264HwProperties = new MediaCodecProperties(
"OMX.Exynos.", Build.VERSION_CODES.LOLLIPOP, BitrateAdjustmentType.FRAMERATE_ADJUSTMENT);
private static final MediaCodecProperties mediatekH264HwProperties = new MediaCodecProperties(
"OMX.MTK.", Build.VERSION_CODES.O_MR1, BitrateAdjustmentType.FRAMERATE_ADJUSTMENT);
private static final MediaCodecProperties[] h264HwList() {
final ArrayList supported_codecs = new ArrayList();
supported_codecs.add(qcomH264HwProperties);
supported_codecs.add(exynosH264HwProperties);
if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-MediaTekH264").equals("Enabled")) {
supported_codecs.add(mediatekH264HwProperties);
}
return supported_codecs.toArray(new MediaCodecProperties[supported_codecs.size()]);
}
注释写都很清楚:支持的硬件h264编码器列表,这里竟然写成了白名单的形式,my God!!!,再往下看findHwEncoder的具体实现:
private static @Nullable EncoderProperties findHwEncoder(
String mime, MediaCodecProperties[] supportedHwCodecProperties, int[] colorList) {
//仅支持4.4及其以上系统
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return null;
}
...
// Check if this is supported HW encoder.
boolean supportedCodec = false;
BitrateAdjustmentType bitrateAdjustmentType = BitrateAdjustmentType.NO_ADJUSTMENT;
for (MediaCodecProperties codecProperties : supportedHwCodecProperties) {
//判断寻找到的编码器是否在名单中
if (name.startsWith(codecProperties.codecPrefix)) {
if (Build.VERSION.SDK_INT < codecProperties.minSdk) {
Logging.w(
TAG, "Codec " + name + " is disabled due to SDK version " + Build.VERSION.SDK_INT);
continue;
}
if (codecProperties.bitrateAdjustmentType != BitrateAdjustmentType.NO_ADJUSTMENT) {
bitrateAdjustmentType = codecProperties.bitrateAdjustmentType;
Logging.w(
TAG, "Codec " + name + " requires bitrate adjustment: " + bitrateAdjustmentType);
}
supportedCodec = true;
break;
}
}
if (!supportedCodec) {
continue;
}
...
}
这里的确对设备支持的h264编码器进行了白名单比对,而白名单中仅列出了"OMX.qcom.","OMX.Exynos.","OMX.MTK." 三种,可实际上呢,还有很多,比如"OMX.Intel.", "OMX.hisi.", "OMX.google.","OMX.rk", "OMX.Exynos.", 即使最新的WebRTC代码仍然仅支持上面的三种,假如你什么都不改动,你会发现至少有一半都设备无法使用h264编解码,我这里仅分析了编码的情况,事实上解码同样存在这个问题,那解决方法呢?
有三种解决方案:
第一种:把所有的h264编码器名字都加入白名单,可实际上很难做到,市场上android机千奇百怪,很难全部罗列完。
第二种:去除白名单比对。代码如下:
private static @Nullable EncoderProperties findHwEncoder(
String mime, MediaCodecProperties[] supportedHwCodecProperties, int[] colorList) {
...
/*
// Check if this is supported HW encoder.
boolean supportedCodec = false;
BitrateAdjustmentType bitrateAdjustmentType = BitrateAdjustmentType.NO_ADJUSTMENT;
for (MediaCodecProperties codecProperties : supportedHwCodecProperties) {
//判断寻找到到编码器是否在白名单中
if (name.startsWith(codecProperties.codecPrefix)) {
if (Build.VERSION.SDK_INT < codecProperties.minSdk) {
Logging.w(
TAG, "Codec " + name + " is disabled due to SDK version " + Build.VERSION.SDK_INT);
continue;
}
if (codecProperties.bitrateAdjustmentType != BitrateAdjustmentType.NO_ADJUSTMENT) {
bitrateAdjustmentType = codecProperties.bitrateAdjustmentType;
Logging.w(
TAG, "Codec " + name + " requires bitrate adjustment: " + bitrateAdjustmentType);
}
supportedCodec = true;
break;
}
}
if (!supportedCodec) {
continue;
}
*/
...
}
仅仅对白名单比对的代码进行了注释,这里有个隐患,只使用发现到第一个编码器,设备可能支持的h264不只一个,该使用哪个暂时无法判断,还好,目前使用该方法没有发现有任何问题。
第三种:使用createEncoderByType函数创建编码器,只要传入正确的type参数即可。
综合以上三种方法,较好的是第二种和第三种。
总结:WebRTC在h264支持方面还是不够好,存在很多问题,本文仅仅列出一处比较严重的地方,事实上还有很多。毕竟不是亲生的啊,支持力度就是不一样。
或者微信搜索公众号:webrtc home