栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

分析createPeerConnectionFactory之Video***Factory (Android-RTC-6)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

分析createPeerConnectionFactory之Video***Factory (Android-RTC-6)

本章开始分析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得出优先级。(图中能查看基本代码逻辑)

这回到文章开头的提问:用Android的MediaCodec相关API 就是硬编 / 硬解码吗?答案是否定的。MediaCodec其实也分系统软实现和厂商硬实现。MediaCodecUtils.java中的代码中进行了两种类型的解码器判断。

// HardwareVideoDecoderFactory.Predicate 
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 
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 supportedCodecInfos = new ArrayList();
    // 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,  true)));
            }
            supportedCodecInfos.add(new VideoCodecInfo(
                    name, MediaCodecUtils.getCodecProperties(type,  false)));
        }
    }
    return supportedCodecInfos.toArray(new VideoCodecInfo[supportedCodecInfos.size()]);
}

判断支持H264 high profile之后构造对应的VideoCodecInfo,调用了MediaCodecUtils.getCodecProperties,我们跟进去看看。

// MediaCodecUtils.java
static Map getCodecProperties(VideoCodecMimeType type, boolean highProfile) {
    switch (type) {
        case VP8:
        case VP9:
            return new HashMap();
        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 getDefaultH264Params(boolean isHighProfile) {
    final Map 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
Profileprofile_idc (16进制)profile-iop (2进制)
CB42 (B)x1xx0000
CB4D (M)1xxx0000
CB58 (E)11xx0000
B42 (B)x0xx0000
B58 (E)10xx0000
M4D (M)0x0x0000
E5800xx0000
H6400000000
H106E00000000
H427A00000000
H44F400000000
H10I6E00010000
H42I7A00010000
H44IF400010000
C44I2C00010000

具体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 codecs = new ArrayList();
    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 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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/275909.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号