• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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