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 android.support.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 // Forced key frame interval - used to reduce color distortions on Qualcomm platforms. 31 private static final int QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_L_MS = 15000; 32 private static final int QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_M_MS = 20000; 33 private static final int QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_N_MS = 15000; 34 35 // List of devices with poor H.264 encoder quality. 36 // HW H.264 encoder on below devices has poor bitrate control - actual 37 // bitrates deviates a lot from the target value. 38 private static final List<String> H264_HW_EXCEPTION_MODELS = 39 Arrays.asList("SAMSUNG-SGH-I337", "Nexus 7", "Nexus 4"); 40 41 @Nullable private final EglBase14.Context sharedContext; 42 private final boolean enableIntelVp8Encoder; 43 private final boolean enableH264HighProfile; 44 @Nullable private final Predicate<MediaCodecInfo> codecAllowedPredicate; 45 46 /** 47 * Creates a HardwareVideoEncoderFactory that supports surface texture encoding. 48 * 49 * @param sharedContext The textures generated will be accessible from this context. May be null, 50 * this disables texture support. 51 * @param enableIntelVp8Encoder true if Intel's VP8 encoder enabled. 52 * @param enableH264HighProfile true if H264 High Profile enabled. 53 */ HardwareVideoEncoderFactory( EglBase.Context sharedContext, boolean enableIntelVp8Encoder, boolean enableH264HighProfile)54 public HardwareVideoEncoderFactory( 55 EglBase.Context sharedContext, boolean enableIntelVp8Encoder, boolean enableH264HighProfile) { 56 this(sharedContext, enableIntelVp8Encoder, enableH264HighProfile, 57 /* codecAllowedPredicate= */ null); 58 } 59 60 /** 61 * Creates a HardwareVideoEncoderFactory that supports surface texture encoding. 62 * 63 * @param sharedContext The textures generated will be accessible from this context. May be null, 64 * this disables texture support. 65 * @param enableIntelVp8Encoder true if Intel's VP8 encoder enabled. 66 * @param enableH264HighProfile true if H264 High Profile enabled. 67 * @param codecAllowedPredicate optional predicate to filter codecs. All codecs are allowed 68 * when predicate is not provided. 69 */ HardwareVideoEncoderFactory(EglBase.Context sharedContext, boolean enableIntelVp8Encoder, boolean enableH264HighProfile, @Nullable Predicate<MediaCodecInfo> codecAllowedPredicate)70 public HardwareVideoEncoderFactory(EglBase.Context sharedContext, boolean enableIntelVp8Encoder, 71 boolean enableH264HighProfile, @Nullable Predicate<MediaCodecInfo> codecAllowedPredicate) { 72 // Texture mode requires EglBase14. 73 if (sharedContext instanceof EglBase14.Context) { 74 this.sharedContext = (EglBase14.Context) sharedContext; 75 } else { 76 Logging.w(TAG, "No shared EglBase.Context. Encoders will not use texture mode."); 77 this.sharedContext = null; 78 } 79 this.enableIntelVp8Encoder = enableIntelVp8Encoder; 80 this.enableH264HighProfile = enableH264HighProfile; 81 this.codecAllowedPredicate = codecAllowedPredicate; 82 } 83 84 @Deprecated HardwareVideoEncoderFactory(boolean enableIntelVp8Encoder, boolean enableH264HighProfile)85 public HardwareVideoEncoderFactory(boolean enableIntelVp8Encoder, boolean enableH264HighProfile) { 86 this(null, enableIntelVp8Encoder, enableH264HighProfile); 87 } 88 89 @Nullable 90 @Override createEncoder(VideoCodecInfo input)91 public VideoEncoder createEncoder(VideoCodecInfo input) { 92 // HW encoding is not supported below Android Kitkat. 93 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 94 return null; 95 } 96 97 VideoCodecMimeType type = VideoCodecMimeType.valueOf(input.name); 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, getKeyFrameIntervalSec(type), 127 getForcedKeyFrameIntervalMs(type, codecName), createBitrateAdjuster(type, codecName), 128 sharedContext); 129 } 130 131 @Override getSupportedCodecs()132 public VideoCodecInfo[] getSupportedCodecs() { 133 // HW encoding is not supported below Android Kitkat. 134 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 135 return new VideoCodecInfo[0]; 136 } 137 138 List<VideoCodecInfo> supportedCodecInfos = new ArrayList<VideoCodecInfo>(); 139 // Generate a list of supported codecs in order of preference: 140 // VP8, VP9, H264 (high profile), and H264 (baseline profile). 141 for (VideoCodecMimeType type : new VideoCodecMimeType[] { 142 VideoCodecMimeType.VP8, VideoCodecMimeType.VP9, VideoCodecMimeType.H264}) { 143 MediaCodecInfo codec = findCodecForType(type); 144 if (codec != null) { 145 String name = type.name(); 146 // TODO(sakal): Always add H264 HP once WebRTC correctly removes codecs that are not 147 // supported by the decoder. 148 if (type == VideoCodecMimeType.H264 && isH264HighProfileSupported(codec)) { 149 supportedCodecInfos.add(new VideoCodecInfo( 150 name, MediaCodecUtils.getCodecProperties(type, /* highProfile= */ true))); 151 } 152 153 supportedCodecInfos.add(new VideoCodecInfo( 154 name, MediaCodecUtils.getCodecProperties(type, /* highProfile= */ false))); 155 } 156 } 157 158 return supportedCodecInfos.toArray(new VideoCodecInfo[supportedCodecInfos.size()]); 159 } 160 findCodecForType(VideoCodecMimeType type)161 private @Nullable MediaCodecInfo findCodecForType(VideoCodecMimeType type) { 162 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) { 163 MediaCodecInfo info = null; 164 try { 165 info = MediaCodecList.getCodecInfoAt(i); 166 } catch (IllegalArgumentException e) { 167 Logging.e(TAG, "Cannot retrieve encoder codec info", e); 168 } 169 170 if (info == null || !info.isEncoder()) { 171 continue; 172 } 173 174 if (isSupportedCodec(info, type)) { 175 return info; 176 } 177 } 178 return null; // No support for this type. 179 } 180 181 // Returns true if the given MediaCodecInfo indicates a supported encoder for the given type. isSupportedCodec(MediaCodecInfo info, VideoCodecMimeType type)182 private boolean isSupportedCodec(MediaCodecInfo info, VideoCodecMimeType type) { 183 if (!MediaCodecUtils.codecSupportsType(info, type)) { 184 return false; 185 } 186 // Check for a supported color format. 187 if (MediaCodecUtils.selectColorFormat( 188 MediaCodecUtils.ENCODER_COLOR_FORMATS, info.getCapabilitiesForType(type.mimeType())) 189 == null) { 190 return false; 191 } 192 return isHardwareSupportedInCurrentSdk(info, type) && isMediaCodecAllowed(info); 193 } 194 195 // Returns true if the given MediaCodecInfo indicates a hardware module that is supported on the 196 // current SDK. isHardwareSupportedInCurrentSdk(MediaCodecInfo info, VideoCodecMimeType type)197 private boolean isHardwareSupportedInCurrentSdk(MediaCodecInfo info, VideoCodecMimeType type) { 198 switch (type) { 199 case VP8: 200 return isHardwareSupportedInCurrentSdkVp8(info); 201 case VP9: 202 return isHardwareSupportedInCurrentSdkVp9(info); 203 case H264: 204 return isHardwareSupportedInCurrentSdkH264(info); 205 } 206 return false; 207 } 208 isHardwareSupportedInCurrentSdkVp8(MediaCodecInfo info)209 private boolean isHardwareSupportedInCurrentSdkVp8(MediaCodecInfo info) { 210 String name = info.getName(); 211 // QCOM Vp8 encoder is supported in KITKAT or later. 212 return (name.startsWith(QCOM_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) 213 // Exynos VP8 encoder is supported in M or later. 214 || (name.startsWith(EXYNOS_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 215 // Intel Vp8 encoder is supported in LOLLIPOP or later, with the intel encoder enabled. 216 || (name.startsWith(INTEL_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP 217 && 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 H264 encoder is supported in KITKAT or later. 234 return (name.startsWith(QCOM_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) 235 // Exynos H264 encoder is supported in LOLLIPOP or later. 236 || (name.startsWith(EXYNOS_PREFIX) 237 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP); 238 } 239 isMediaCodecAllowed(MediaCodecInfo info)240 private boolean isMediaCodecAllowed(MediaCodecInfo info) { 241 if (codecAllowedPredicate == null) { 242 return true; 243 } 244 return codecAllowedPredicate.test(info); 245 } 246 getKeyFrameIntervalSec(VideoCodecMimeType type)247 private int getKeyFrameIntervalSec(VideoCodecMimeType type) { 248 switch (type) { 249 case VP8: // Fallthrough intended. 250 case VP9: 251 return 100; 252 case H264: 253 return 20; 254 } 255 throw new IllegalArgumentException("Unsupported VideoCodecMimeType " + type); 256 } 257 getForcedKeyFrameIntervalMs(VideoCodecMimeType type, String codecName)258 private int getForcedKeyFrameIntervalMs(VideoCodecMimeType type, String codecName) { 259 if (type == VideoCodecMimeType.VP8 && codecName.startsWith(QCOM_PREFIX)) { 260 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP 261 || Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1) { 262 return QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_L_MS; 263 } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M) { 264 return QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_M_MS; 265 } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { 266 return QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_N_MS; 267 } 268 } 269 // Other codecs don't need key frame forcing. 270 return 0; 271 } 272 createBitrateAdjuster(VideoCodecMimeType type, String codecName)273 private BitrateAdjuster createBitrateAdjuster(VideoCodecMimeType type, String codecName) { 274 if (codecName.startsWith(EXYNOS_PREFIX)) { 275 if (type == VideoCodecMimeType.VP8) { 276 // Exynos VP8 encoders need dynamic bitrate adjustment. 277 return new DynamicBitrateAdjuster(); 278 } else { 279 // Exynos VP9 and H264 encoders need framerate-based bitrate adjustment. 280 return new FramerateBitrateAdjuster(); 281 } 282 } 283 // Other codecs don't need bitrate adjustment. 284 return new BaseBitrateAdjuster(); 285 } 286 isH264HighProfileSupported(MediaCodecInfo info)287 private boolean isH264HighProfileSupported(MediaCodecInfo info) { 288 return enableH264HighProfile && Build.VERSION.SDK_INT > Build.VERSION_CODES.M 289 && info.getName().startsWith(EXYNOS_PREFIX); 290 } 291 } 292