1 /* 2 * Copyright 2017 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 package org.webrtc; 12 13 import static org.webrtc.MediaCodecUtils.EXYNOS_PREFIX; 14 import static org.webrtc.MediaCodecUtils.INTEL_PREFIX; 15 import static org.webrtc.MediaCodecUtils.QCOM_PREFIX; 16 17 import android.media.MediaCodecInfo; 18 import android.media.MediaCodecList; 19 import android.os.Build; 20 import androidx.annotation.Nullable; 21 import java.util.ArrayList; 22 import java.util.Arrays; 23 import java.util.List; 24 25 /** Factory for android hardware video encoders. */ 26 @SuppressWarnings("deprecation") // API 16 requires the use of deprecated methods. 27 public class HardwareVideoEncoderFactory implements VideoEncoderFactory { 28 private static final String TAG = "HardwareVideoEncoderFactory"; 29 30 // We don't need periodic keyframes. But some HW encoders, Exynos in particular, fails to 31 // initialize with value -1 which should disable periodic keyframes according to the spec. Set it 32 // to 1 hour. 33 private static final int PERIODIC_KEY_FRAME_INTERVAL_S = 3600; 34 35 // Forced key frame interval - used to reduce color distortions on Qualcomm platforms. 36 private static final int QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_L_MS = 15000; 37 private static final int QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_M_MS = 20000; 38 private static final int QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_N_MS = 15000; 39 40 // List of devices with poor H.264 encoder quality. 41 // HW H.264 encoder on below devices has poor bitrate control - actual 42 // bitrates deviates a lot from the target value. 43 private static final List<String> H264_HW_EXCEPTION_MODELS = 44 Arrays.asList("SAMSUNG-SGH-I337", "Nexus 7", "Nexus 4"); 45 46 @Nullable private final EglBase14.Context sharedContext; 47 private final boolean enableIntelVp8Encoder; 48 private final boolean enableH264HighProfile; 49 @Nullable private final Predicate<MediaCodecInfo> codecAllowedPredicate; 50 51 /** 52 * Creates a HardwareVideoEncoderFactory that supports surface texture encoding. 53 * 54 * @param sharedContext The textures generated will be accessible from this context. May be null, 55 * this disables texture support. 56 * @param enableIntelVp8Encoder true if Intel's VP8 encoder enabled. 57 * @param enableH264HighProfile true if H264 High Profile enabled. 58 */ HardwareVideoEncoderFactory( EglBase.Context sharedContext, boolean enableIntelVp8Encoder, boolean enableH264HighProfile)59 public HardwareVideoEncoderFactory( 60 EglBase.Context sharedContext, boolean enableIntelVp8Encoder, boolean enableH264HighProfile) { 61 this(sharedContext, enableIntelVp8Encoder, enableH264HighProfile, 62 /* codecAllowedPredicate= */ null); 63 } 64 65 /** 66 * Creates a HardwareVideoEncoderFactory that supports surface texture encoding. 67 * 68 * @param sharedContext The textures generated will be accessible from this context. May be null, 69 * this disables texture support. 70 * @param enableIntelVp8Encoder true if Intel's VP8 encoder enabled. 71 * @param enableH264HighProfile true if H264 High Profile enabled. 72 * @param codecAllowedPredicate optional predicate to filter codecs. All codecs are allowed 73 * when predicate is not provided. 74 */ HardwareVideoEncoderFactory(EglBase.Context sharedContext, boolean enableIntelVp8Encoder, boolean enableH264HighProfile, @Nullable Predicate<MediaCodecInfo> codecAllowedPredicate)75 public HardwareVideoEncoderFactory(EglBase.Context sharedContext, boolean enableIntelVp8Encoder, 76 boolean enableH264HighProfile, @Nullable Predicate<MediaCodecInfo> codecAllowedPredicate) { 77 // Texture mode requires EglBase14. 78 if (sharedContext instanceof EglBase14.Context) { 79 this.sharedContext = (EglBase14.Context) sharedContext; 80 } else { 81 Logging.w(TAG, "No shared EglBase.Context. Encoders will not use texture mode."); 82 this.sharedContext = null; 83 } 84 this.enableIntelVp8Encoder = enableIntelVp8Encoder; 85 this.enableH264HighProfile = enableH264HighProfile; 86 this.codecAllowedPredicate = codecAllowedPredicate; 87 } 88 89 @Deprecated HardwareVideoEncoderFactory(boolean enableIntelVp8Encoder, boolean enableH264HighProfile)90 public HardwareVideoEncoderFactory(boolean enableIntelVp8Encoder, boolean enableH264HighProfile) { 91 this(null, enableIntelVp8Encoder, enableH264HighProfile); 92 } 93 94 @Nullable 95 @Override createEncoder(VideoCodecInfo input)96 public VideoEncoder createEncoder(VideoCodecInfo input) { 97 VideoCodecMimeType type = VideoCodecMimeType.valueOf(input.getName()); 98 MediaCodecInfo info = findCodecForType(type); 99 100 if (info == null) { 101 return null; 102 } 103 104 String codecName = info.getName(); 105 String mime = type.mimeType(); 106 Integer surfaceColorFormat = MediaCodecUtils.selectColorFormat( 107 MediaCodecUtils.TEXTURE_COLOR_FORMATS, info.getCapabilitiesForType(mime)); 108 Integer yuvColorFormat = MediaCodecUtils.selectColorFormat( 109 MediaCodecUtils.ENCODER_COLOR_FORMATS, info.getCapabilitiesForType(mime)); 110 111 if (type == VideoCodecMimeType.H264) { 112 boolean isHighProfile = H264Utils.isSameH264Profile( 113 input.params, MediaCodecUtils.getCodecProperties(type, /* highProfile= */ true)); 114 boolean isBaselineProfile = H264Utils.isSameH264Profile( 115 input.params, MediaCodecUtils.getCodecProperties(type, /* highProfile= */ false)); 116 117 if (!isHighProfile && !isBaselineProfile) { 118 return null; 119 } 120 if (isHighProfile && !isH264HighProfileSupported(info)) { 121 return null; 122 } 123 } 124 125 return new HardwareVideoEncoder(new MediaCodecWrapperFactoryImpl(), codecName, type, 126 surfaceColorFormat, yuvColorFormat, input.params, PERIODIC_KEY_FRAME_INTERVAL_S, 127 getForcedKeyFrameIntervalMs(type, codecName), createBitrateAdjuster(type, codecName), 128 sharedContext); 129 } 130 131 @Override getSupportedCodecs()132 public VideoCodecInfo[] getSupportedCodecs() { 133 List<VideoCodecInfo> supportedCodecInfos = new ArrayList<VideoCodecInfo>(); 134 // Generate a list of supported codecs in order of preference: 135 // VP8, VP9, H264 (high profile), H264 (baseline profile) and AV1. 136 for (VideoCodecMimeType type : new VideoCodecMimeType[] {VideoCodecMimeType.VP8, 137 VideoCodecMimeType.VP9, VideoCodecMimeType.H264, VideoCodecMimeType.AV1}) { 138 MediaCodecInfo codec = findCodecForType(type); 139 if (codec != null) { 140 String name = type.name(); 141 // TODO(sakal): Always add H264 HP once WebRTC correctly removes codecs that are not 142 // supported by the decoder. 143 if (type == VideoCodecMimeType.H264 && isH264HighProfileSupported(codec)) { 144 supportedCodecInfos.add(new VideoCodecInfo( 145 name, MediaCodecUtils.getCodecProperties(type, /* highProfile= */ true))); 146 } 147 148 supportedCodecInfos.add(new VideoCodecInfo( 149 name, MediaCodecUtils.getCodecProperties(type, /* highProfile= */ false))); 150 } 151 } 152 153 return supportedCodecInfos.toArray(new VideoCodecInfo[supportedCodecInfos.size()]); 154 } 155 findCodecForType(VideoCodecMimeType type)156 private @Nullable MediaCodecInfo findCodecForType(VideoCodecMimeType type) { 157 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) { 158 MediaCodecInfo info = null; 159 try { 160 info = MediaCodecList.getCodecInfoAt(i); 161 } catch (IllegalArgumentException e) { 162 Logging.e(TAG, "Cannot retrieve encoder codec info", e); 163 } 164 165 if (info == null || !info.isEncoder()) { 166 continue; 167 } 168 169 if (isSupportedCodec(info, type)) { 170 return info; 171 } 172 } 173 return null; // No support for this type. 174 } 175 176 // Returns true if the given MediaCodecInfo indicates a supported encoder for the given type. isSupportedCodec(MediaCodecInfo info, VideoCodecMimeType type)177 private boolean isSupportedCodec(MediaCodecInfo info, VideoCodecMimeType type) { 178 if (!MediaCodecUtils.codecSupportsType(info, type)) { 179 return false; 180 } 181 // Check for a supported color format. 182 if (MediaCodecUtils.selectColorFormat( 183 MediaCodecUtils.ENCODER_COLOR_FORMATS, info.getCapabilitiesForType(type.mimeType())) 184 == null) { 185 return false; 186 } 187 return isHardwareSupportedInCurrentSdk(info, type) && isMediaCodecAllowed(info); 188 } 189 190 // Returns true if the given MediaCodecInfo indicates a hardware module that is supported on the 191 // current SDK. isHardwareSupportedInCurrentSdk(MediaCodecInfo info, VideoCodecMimeType type)192 private boolean isHardwareSupportedInCurrentSdk(MediaCodecInfo info, VideoCodecMimeType type) { 193 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 194 return info.isHardwareAccelerated(); 195 } 196 197 switch (type) { 198 case VP8: 199 return isHardwareSupportedInCurrentSdkVp8(info); 200 case VP9: 201 return isHardwareSupportedInCurrentSdkVp9(info); 202 case H264: 203 return isHardwareSupportedInCurrentSdkH264(info); 204 case AV1: 205 return false; 206 } 207 return false; 208 } 209 isHardwareSupportedInCurrentSdkVp8(MediaCodecInfo info)210 private boolean isHardwareSupportedInCurrentSdkVp8(MediaCodecInfo info) { 211 String name = info.getName(); 212 // QCOM Vp8 encoder is always supported. 213 return name.startsWith(QCOM_PREFIX) 214 // Exynos VP8 encoder is supported in M or later. 215 || (name.startsWith(EXYNOS_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 216 // Intel Vp8 encoder is always supported, with the intel encoder enabled. 217 || (name.startsWith(INTEL_PREFIX) && enableIntelVp8Encoder); 218 } 219 isHardwareSupportedInCurrentSdkVp9(MediaCodecInfo info)220 private boolean isHardwareSupportedInCurrentSdkVp9(MediaCodecInfo info) { 221 String name = info.getName(); 222 return (name.startsWith(QCOM_PREFIX) || name.startsWith(EXYNOS_PREFIX)) 223 // Both QCOM and Exynos VP9 encoders are supported in N or later. 224 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; 225 } 226 isHardwareSupportedInCurrentSdkH264(MediaCodecInfo info)227 private boolean isHardwareSupportedInCurrentSdkH264(MediaCodecInfo info) { 228 // First, H264 hardware might perform poorly on this model. 229 if (H264_HW_EXCEPTION_MODELS.contains(Build.MODEL)) { 230 return false; 231 } 232 String name = info.getName(); 233 // QCOM and Exynos H264 encoders are always supported. 234 return name.startsWith(QCOM_PREFIX) || name.startsWith(EXYNOS_PREFIX); 235 } 236 isMediaCodecAllowed(MediaCodecInfo info)237 private boolean isMediaCodecAllowed(MediaCodecInfo info) { 238 if (codecAllowedPredicate == null) { 239 return true; 240 } 241 return codecAllowedPredicate.test(info); 242 } 243 getForcedKeyFrameIntervalMs(VideoCodecMimeType type, String codecName)244 private int getForcedKeyFrameIntervalMs(VideoCodecMimeType type, String codecName) { 245 if (type == VideoCodecMimeType.VP8 && codecName.startsWith(QCOM_PREFIX)) { 246 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { 247 return QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_L_MS; 248 } 249 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M) { 250 return QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_M_MS; 251 } 252 return QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_N_MS; 253 } 254 // Other codecs don't need key frame forcing. 255 return 0; 256 } 257 createBitrateAdjuster(VideoCodecMimeType type, String codecName)258 private BitrateAdjuster createBitrateAdjuster(VideoCodecMimeType type, String codecName) { 259 if (codecName.startsWith(EXYNOS_PREFIX)) { 260 if (type == VideoCodecMimeType.VP8) { 261 // Exynos VP8 encoders need dynamic bitrate adjustment. 262 return new DynamicBitrateAdjuster(); 263 } else { 264 // Exynos VP9 and H264 encoders need framerate-based bitrate adjustment. 265 return new FramerateBitrateAdjuster(); 266 } 267 } 268 // Other codecs don't need bitrate adjustment. 269 return new BaseBitrateAdjuster(); 270 } 271 isH264HighProfileSupported(MediaCodecInfo info)272 private boolean isH264HighProfileSupported(MediaCodecInfo info) { 273 return enableH264HighProfile && Build.VERSION.SDK_INT > Build.VERSION_CODES.M 274 && info.getName().startsWith(EXYNOS_PREFIX); 275 } 276 } 277