本章开始分析PeerConnectionFactory中与Video相关的模块,也不多,就两VideoEncoderFactory ?/ VideoDecoderFactory。
开始前抛一个我常问的面试题:用Android的MediaCodec相关API 就是硬编 / 硬解码吗?
一、VideoDecoderFactory
// AppRTCDemo.PeerConnectionClient.java
private void createPeerConnectionFactoryInternal(PeerConnectionFactory.Options options) {
// ... ...
final AudioDeviceModule adm = createJavaAudioDevice();
final boolean enableH264HighProfile =
VIDEO_CODEC_H264_HIGH.equals(peerConnectionParameters.videoCodec);
final VideoEncoderFactory encoderFactory;
final VideoDecoderFactory decoderFactory;
if (peerConnectionParameters.videoCodecHwAcceleration) {
encoderFactory = new DefaultVideoEncoderFactory(
rootEglBase.getEglBaseContext(), true, enableH264HighProfile);
decoderFactory = new DefaultVideoDecoderFactory(rootEglBase.getEglBaseContext());
} else {
encoderFactory = new SoftwareVideoEncoderFactory();
decoderFactory = new SoftwareVideoDecoderFactory();
}
factory = PeerConnectionFactory.builder()
.setOptions(options)
.setAudioDeviceModule(adm)
.setVideoEncoderFactory(encoderFactory)
.setVideoDecoderFactory(decoderFactory)
.createPeerConnectionFactory();
Log.d(TAG, "Peer connection factory created.");
adm.release();
}
熟悉的入口方法,我们看到 VideoEncoderFactory 和?VideoDecoderFactory的模式是一样的,根据信令参数HwAcceleration的区分DefaultVideo*****Factory和SoftwareVideo*****Factory。先以解码Decoder展开分析,继承关系如下(看不清楚的请点击放大):
意想不到的是,有两个实现继承MediaCodecVideoDecoderFactory,HardwareVideoDecoderFactory /?PlatformSoftwareVideoDecoderFactory,从命名方式了解到一个是真正的硬件解码器,另外一个平台实现解码器。两者的区别就是传入super构造函数的Predicate<MediaCodecInfo>,查看代码其实就是一个比较器,根据MediaCodecInfo得出优先级。(图中能查看基本代码逻辑)
这回到文章开头的提问:用Android的MediaCodec相关API 就是硬编 / 硬解码吗?答案是否定的。MediaCodec其实也分系统软实现和厂商硬实现。MediaCodecUtils.java中的代码中进行了两种类型的解码器判断。
// HardwareVideoDecoderFactory.Predicate<MediaCodecInfo>
static boolean isHardwareAccelerated(MediaCodecInfo info) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return isHardwareAcceleratedQOrHigher(info);
}
return !isSoftwareOnly(info);
}
@TargetApi(29)
private static boolean isHardwareAcceleratedQOrHigher(MediaCodecInfo codecInfo) {
return codecInfo.isHardwareAccelerated();
}
// PlatformSoftwareVideoDecoderFactory.Predicate<MediaCodecInfo>
static boolean isSoftwareOnly(MediaCodecInfo codecInfo) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return isSoftwareOnlyQOrHigher(codecInfo);
}
String name = codecInfo.getName();
for (String prefix : SOFTWARE_IMPLEMENTATION_PREFIXES) {
if (name.startsWith(prefix)) {
return true;
}
}
return false;
}
@TargetApi(29)
private static boolean isSoftwareOnlyQOrHigher(MediaCodecInfo codecInfo) {
return codecInfo.isSoftwareOnly();
}
static final String[] SOFTWARE_IMPLEMENTATION_PREFIXES = {
"OMX.google.", "OMX.SEC.", "c2.android"};
顺着我们看看MediaCodecVideoDecoderFactory的代码实现,往下探索细节可以深入到?isH264HighProfileSupported 判断是否支持H264 high profile。
// MediaCodecVideoDecoderFactory.java
@Override
public VideoDecoder createDecoder(VideoCodecInfo codecType) {
VideoCodecMimeType type = VideoCodecMimeType.valueOf(codecType.getName());
MediaCodecInfo info = findCodecForType(type);
if (info == null) {
return null;
}
CodecCapabilities capabilities = info.getCapabilitiesForType(type.mimeType());
return new AndroidVideoDecoder(new MediaCodecWrapperFactoryImpl(), info.getName(), type,
MediaCodecUtils.selectColorFormat(MediaCodecUtils.DECODER_COLOR_FORMATS, capabilities),
sharedContext);
}
@Override
public VideoCodecInfo[] getSupportedCodecs() {
List<VideoCodecInfo> supportedCodecInfos = new ArrayList<VideoCodecInfo>();
// Generate a list of supported codecs in order of preference:
// VP8, VP9, H264 (high profile), and H264 (baseline profile).
for (VideoCodecMimeType type : new VideoCodecMimeType[]{
VideoCodecMimeType.VP8, VideoCodecMimeType.VP9, VideoCodecMimeType.H264}) {
MediaCodecInfo codec = findCodecForType(type);
if (codec != null) {
String name = type.name();
if (type == VideoCodecMimeType.H264 && isH264HighProfileSupported(codec)) {
supportedCodecInfos.add(new VideoCodecInfo(
name, MediaCodecUtils.getCodecProperties(type, /* highProfile= */ true)));
}
supportedCodecInfos.add(new VideoCodecInfo(
name, MediaCodecUtils.getCodecProperties(type, /* highProfile= */ false)));
}
}
return supportedCodecInfos.toArray(new VideoCodecInfo[supportedCodecInfos.size()]);
}
判断支持H264 high profile之后构造对应的VideoCodecInfo,调用了MediaCodecUtils.getCodecProperties,我们跟进去看看。
// MediaCodecUtils.java
static Map<String, String> getCodecProperties(VideoCodecMimeType type, boolean highProfile) {
switch (type) {
case VP8:
case VP9:
return new HashMap<String, String>();
case H264:
return H264Utils.getDefaultH264Params(highProfile);
default:
throw new IllegalArgumentException("Unsupported codec: " + type);
}
}
// H264Utils.java
public static final String H264_FMTP_PROFILE_LEVEL_ID = "profile-level-id";
public static final String H264_FMTP_LEVEL_ASYMMETRY_ALLOWED = "level-asymmetry-allowed";
public static final String H264_FMTP_PACKETIZATION_MODE = "packetization-mode";
public static final String H264_PROFILE_CONSTRAINED_BASELINE = "42e0";
public static final String H264_PROFILE_CONSTRAINED_HIGH = "640c";
public static final String H264_LEVEL_3_1 = "1f"; // 31 in hex.
public static final String H264_CONSTRAINED_HIGH_3_1 =
H264_PROFILE_CONSTRAINED_HIGH + H264_LEVEL_3_1;
public static final String H264_CONSTRAINED_BASELINE_3_1 =
H264_PROFILE_CONSTRAINED_BASELINE + H264_LEVEL_3_1;
public static Map<String, String> getDefaultH264Params(boolean isHighProfile) {
final Map<String, String> params = new HashMap<>();
params.put(VideoCodecInfo.H264_FMTP_LEVEL_ASYMMETRY_ALLOWED, "1");
params.put(VideoCodecInfo.H264_FMTP_PACKETIZATION_MODE, "1");
params.put(VideoCodecInfo.H264_FMTP_PROFILE_LEVEL_ID,
isHighProfile ? VideoCodecInfo.H264_CONSTRAINED_HIGH_3_1
: VideoCodecInfo.H264_CONSTRAINED_BASELINE_3_1);
return params;
}
这里看到了默认的h264 high profile参数,三个参数的含义需要普及一下:
1.?profile-level-id
profile-level-id是16进制表示的3个字节的整数,按顺序分成3个字节,每个字节分别表示不同的含义。
- profile_idc
- profile-iop: 前6位分别是constraint_set0_flag, constraint_set1_flag, constraint_set2_flag, constraint_set3_flag, constraint_set4_flag, constraint_set5_flag, 最后两位为保留位
- level_idc
Profile | profile_idc (16进制) | profile-iop (2进制) | CB | 42 (B) | x1xx0000 | CB | 4D (M) | 1xxx0000 | CB | 58 (E) | 11xx0000 | B | 42 (B) | x0xx0000 | B | 58 (E) | 10xx0000 | M | 4D (M) | 0x0x0000 | E | 58 | 00xx0000 | H | 64 | 00000000 | H10 | 6E | 00000000 | H42 | 7A | 00000000 | H44 | F4 | 00000000 | H10I | 6E | 00010000 | H42I | 7A | 00010000 | H44I | F4 | 00010000 | C44I | 2C | 00010000 |
具体profile的名字含义如下:
CB: Constrained Baseline profile, B: Baseline profile, M: Main profile, E: Extended profile, H: High profile, H10: High 10 profile, H42: High 4:2:2 profile, H44: High 4:4:4 Predictive profile, H10I: High 10 Intra profile, H42I: High 4:2:2 Intra profile, H44I: High 4:4:4 Intra profile, C44I: CAVLC 4:4:4 Intra profile
level_idc表示level的数值,例子中0x1f == 31,也就是Level 3.1。
H264 level表格参考:https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
2. packetization-mode: packetization-mode表示图像数据包分拆发送的方式。
0: Single NAL (Network Abstraction Layer),每帧图像数据全部放在一个NAL单元传送; 1: Not Interleaved,每帧图像数据被拆放到多个NAL单元传送,这些NAL单元传送的顺序是按照解码的顺序发送; 2: Interleaved,每帧图像数据被拆放到多个NAL单元传送,但是这些NAL单元传送的顺序可以不按照解码的顺序发送 实际上,只有I帧可以被拆分发送,P帧和B帧都不能被拆分发送。所以如果packetization-mode=1,则意味着I帧会被拆分发送。
3.level-asymmetry-allowed:
level-asymmetry-allowed表示是否允许两端编码的Level不一致。注意必须两端的SDP中该值都为1才生效。
以上这些其实都是在端与端之间SDP交换中,解析H264 RTP协议之间的用到的。以后我们在深入学习这方面的内容。
至于SoftwareVideoDecoderFactory比较简单,我们初略看看就得了。
@Override
public VideoDecoder createDecoder(VideoCodecInfo codecType) {
if (codecType.getName().equalsIgnoreCase("VP8")) {
return new LibvpxVp8Decoder();
}
if (codecType.getName().equalsIgnoreCase("VP9") && LibvpxVp9Decoder.nativeIsSupported()) {
return new LibvpxVp9Decoder();
}
return null;
}
@Override
public VideoCodecInfo[] getSupportedCodecs() {
return supportedCodecs();
}
static VideoCodecInfo[] supportedCodecs() {
List<VideoCodecInfo> codecs = new ArrayList<VideoCodecInfo>();
codecs.add(new VideoCodecInfo("VP8", new HashMap<>()));
if (LibvpxVp9Decoder.nativeIsSupported()) {
codecs.add(new VideoCodecInfo("VP9", new HashMap<>()));
}
return codecs.toArray(new VideoCodecInfo[codecs.size()]);
}
至于 LibvpxVp8Decoder / LibvpxVp9Decoder /?AndroidVideoDecoder每个解码器都留着往后单独开一篇博客分析。现在先理解整体架构组成,要不然单独分析解码器而不知道如何工作还是要回头分析架构组成。
二、VideoEncoderFactory
咋一看VideoEncoderFactory的继承图,比较VideoDecoderFactory又不一样了,少了一层MediaCodecVideoDecoderFactory。咋MediaCodec的Encoder就不分软实现和硬实现了吗?DefaultVideoEncoderFactory和SoftwareVideoEncoderFactory与解码部分没区别。带着疑问,我们重点放在HardwareVideoEncoderFactory的实现上。
其中最大的差别就是在于没有了额外的 Predicate?,顺着思路很快锁定到:
MediaCodecInfo info = findCodecForType(type);
里面的?isSupportedCodec方法 的 isCodecAllowed方法(藏得有点深)我们一起来看看区别:
// HardwareVideoEncoderFactory.java
private boolean isSupportedCodec(MediaCodecInfo info, VideoCodecMimeType type) {
if (!MediaCodecUtils.codecSupportsType(info, type)) {
return false;
}
// Check for a supported color format.
if (MediaCodecUtils.selectColorFormat(
MediaCodecUtils.ENCODER_COLOR_FORMATS, info.getCapabilitiesForType(type.mimeType()))
== null) {
return false;
}
return isHardwareSupportedInCurrentSdk(info, type) && isMediaCodecAllowed(info);
}
// MediaCodecVideoDecoderFactory.java
private boolean isSupportedCodec(MediaCodecInfo info, VideoCodecMimeType type) {
String name = info.getName();
if (!MediaCodecUtils.codecSupportsType(info, type)) {
return false;
}
// Check for a supported color format.
if (MediaCodecUtils.selectColorFormat(
MediaCodecUtils.DECODER_COLOR_FORMATS, info.getCapabilitiesForType(type.mimeType()))
== null) {
return false;
}
return isCodecAllowed(info);
}
?显然看到Encoder多了?isHardwareSupportedInCurrentSdk ,再跟踪代码。
// Returns true if the given MediaCodecInfo indicates a hardware module that is supported on the current SDK.
private boolean isHardwareSupportedInCurrentSdk(MediaCodecInfo info, VideoCodecMimeType type) {
switch (type) {
case VP8:
return isHardwareSupportedInCurrentSdkVp8(info);
case VP9:
return isHardwareSupportedInCurrentSdkVp9(info);
case H264:
return isHardwareSupportedInCurrentSdkH264(info);
}
return false;
}
private boolean isHardwareSupportedInCurrentSdkVp8(MediaCodecInfo info) {
String name = info.getName();
// QCOM Vp8 encoder is supported in KITKAT or later.
return (name.startsWith(QCOM_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
// Exynos VP8 encoder is supported in M or later.
|| (name.startsWith(EXYNOS_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
// Intel Vp8 encoder is supported in LOLLIPOP or later, with the intel encoder enabled.
|| (name.startsWith(INTEL_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& enableIntelVp8Encoder);
}
private boolean isHardwareSupportedInCurrentSdkVp9(MediaCodecInfo info) {
String name = info.getName();
return (name.startsWith(QCOM_PREFIX) || name.startsWith(EXYNOS_PREFIX))
// Both QCOM and Exynos VP9 encoders are supported in N or later.
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
}
private boolean isHardwareSupportedInCurrentSdkH264(MediaCodecInfo info) {
// First, H264 hardware might perform poorly on this model.
if (H264_HW_EXCEPTION_MODELS.contains(Build.MODEL)) {
return false;
}
String name = info.getName();
// QCOM H264 encoder is supported in KITKAT or later.
return (name.startsWith(QCOM_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
// Exynos H264 encoder is supported in LOLLIPOP or later.
|| (name.startsWith(EXYNOS_PREFIX)
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP);
}
// List of devices with poor H.264 encoder quality.
// HW H.264 encoder on below devices has poor bitrate control - actual
// bitrates deviates a lot from the target value.
private static final List<String> H264_HW_EXCEPTION_MODELS =
Arrays.asList("SAMSUNG-SGH-I337", "Nexus 7", "Nexus 4");
仔细发现一个H264_HW_EXCEPTION_MODELS的玩意,从备注上可以了解到一些机器的编码器码率控制不准啊,这也算是一个设备黑名单了。所以不能单纯的靠API反馈来判断是否硬实现还是软实现。
剩下的 LibvpxVp8Encoder / LibvpxVp9Encoder /?HardwareVideoEncoder也是留着往后单独分析。
三、总结
这篇文章貌似没说什么,主要是简单分析了PeerConnectionFactory.builder传入的VideoEncoderFactory和VideoDecoderFactory,了解到MediaCodec的硬实现和软实现的区别,正所谓开卷有益啊。具体的编解码器分析留到 创建createPeerConnectionFactory的流程之后分析。
参考资料:
https://blog.csdn.net/zqxf123456789/article/details/109696266
|