IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 分析createPeerConnectionFactory之Video***Factory (Android-RTC-6) -> 正文阅读

[移动开发]分析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>,查看代码其实就是一个比较器,根据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
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<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

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-30 12:02:39  更:2021-09-30 12:02:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:56:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码