Create the VideoEncoderFactory and implement it.

Adds the VideoEncoderFactory interface and implements it for use with HardwareVideoEncoder.  This uses MediaCodecVideoEncoder's initialization code as an example.

BUG=webrtc:7760

Change-Id: I9fbc93ce9ac4ad866750a4386c4f15e800a3073e
Reviewed-on: https://chromium-review.googlesource.com/530063
Commit-Queue: Bjorn Mellem <mellem@webrtc.org>
Reviewed-by: Sami Kalliomäki <sakal@webrtc.org>
Reviewed-by: Peter Thatcher <pthatcher@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#18636}
diff --git a/webrtc/sdk/android/BUILD.gn b/webrtc/sdk/android/BUILD.gn
index 8f9fb07..9332a15 100644
--- a/webrtc/sdk/android/BUILD.gn
+++ b/webrtc/sdk/android/BUILD.gn
@@ -168,6 +168,7 @@
     "api/org/webrtc/GlShader.java",
     "api/org/webrtc/GlTextureFrameBuffer.java",
     "api/org/webrtc/GlUtil.java",
+    "api/org/webrtc/HardwareVideoEncoderFactory.java",
     "api/org/webrtc/IceCandidate.java",
     "api/org/webrtc/MediaCodecVideoDecoder.java",
     "api/org/webrtc/MediaCodecVideoEncoder.java",
@@ -198,6 +199,7 @@
     "api/org/webrtc/VideoCodecStatus.java",
     "api/org/webrtc/VideoDecoder.java",
     "api/org/webrtc/VideoEncoder.java",
+    "api/org/webrtc/VideoEncoderFactory.java",
     "api/org/webrtc/VideoFileRenderer.java",
     "api/org/webrtc/VideoFrame.java",
     "api/org/webrtc/VideoRenderer.java",
@@ -216,6 +218,8 @@
     "src/java/org/webrtc/FramerateBitrateAdjuster.java",
     "src/java/org/webrtc/HardwareVideoEncoder.java",
     "src/java/org/webrtc/Histogram.java",
+    "src/java/org/webrtc/I420BufferImpl.java",
+    "src/java/org/webrtc/VideoCodecType.java",
     "src/java/org/webrtc/WrappedNativeI420Buffer.java",
     "src/java/org/webrtc/YuvConverter.java",
   ]
@@ -247,6 +251,7 @@
       "instrumentationtests/src/org/webrtc/EglRendererTest.java",
       "instrumentationtests/src/org/webrtc/FileVideoCapturerTest.java",
       "instrumentationtests/src/org/webrtc/GlRectDrawerTest.java",
+      "instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java",
       "instrumentationtests/src/org/webrtc/MediaCodecVideoEncoderTest.java",
       "instrumentationtests/src/org/webrtc/NetworkMonitorTest.java",
       "instrumentationtests/src/org/webrtc/PeerConnectionTest.java",
diff --git a/webrtc/sdk/android/api/org/webrtc/HardwareVideoEncoderFactory.java b/webrtc/sdk/android/api/org/webrtc/HardwareVideoEncoderFactory.java
new file mode 100644
index 0000000..adf1646
--- /dev/null
+++ b/webrtc/sdk/android/api/org/webrtc/HardwareVideoEncoderFactory.java
@@ -0,0 +1,273 @@
+/*
+ *  Copyright 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+package org.webrtc;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecList;
+import android.os.Build;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Factory for android hardware video encoders. */
+@SuppressWarnings("deprecation") // API 16 requires the use of deprecated methods.
+public class HardwareVideoEncoderFactory implements VideoEncoderFactory {
+  private static final String TAG = "HardwareVideoEncoderFactory";
+
+  // Prefixes for supported hardware encoder component names.
+  private static final String QCOM_PREFIX = "OMX.qcom.";
+  private static final String EXYNOS_PREFIX = "OMX.Exynos.";
+  private static final String INTEL_PREFIX = "OMX.Intel.";
+
+  // Forced key frame interval - used to reduce color distortions on Qualcomm platforms.
+  private static final int QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_L_MS = 15000;
+  private static final int QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_M_MS = 20000;
+  private static final int QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_N_MS = 15000;
+
+  // 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");
+
+  // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
+  // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
+  private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
+
+  // Supported color formats, in order of preference.
+  private static final int[] SUPPORTED_COLOR_FORMATS = {
+      MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar,
+      MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
+      MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
+      COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m};
+
+  // Keys for H264 VideoCodecInfo properties.
+  private static final String H264_FMTP_PROFILE_LEVEL_ID = "profile-level-id";
+  private static final String H264_FMTP_LEVEL_ASYMMETRY_ALLOWED = "level-asymmetry-allowed";
+  private static final String H264_FMTP_PACKETIZATION_MODE = "packetization-mode";
+
+  // Supported H264 profile ids and levels.
+  private static final String H264_PROFILE_CONSTRAINED_BASELINE = "4200";
+  private static final String H264_PROFILE_CONSTRAINED_HIGH = "640c";
+  private static final String H264_LEVEL_3_1 = "1f"; // 31 in hex.
+  private static final String H264_CONSTRAINED_BASELINE_3_1 =
+      H264_PROFILE_CONSTRAINED_BASELINE + H264_LEVEL_3_1;
+  private static final String H264_CONSTRAINED_HIGH_3_1 =
+      H264_PROFILE_CONSTRAINED_HIGH + H264_LEVEL_3_1;
+
+  private final boolean enableIntelVp8Encoder;
+  private final boolean enableH264HighProfile;
+
+  public HardwareVideoEncoderFactory(boolean enableIntelVp8Encoder, boolean enableH264HighProfile) {
+    this.enableIntelVp8Encoder = enableIntelVp8Encoder;
+    this.enableH264HighProfile = enableH264HighProfile;
+  }
+
+  @Override
+  public VideoEncoder createEncoder(VideoCodecInfo input) {
+    VideoCodecType type = VideoCodecType.valueOf(input.name);
+    MediaCodecInfo info = findCodecForType(type);
+
+    if (info == null) {
+      return null; // No support for this type.
+    }
+
+    String codecName = info.getName();
+    String mime = type.mimeType();
+    int colorFormat = selectColorFormat(SUPPORTED_COLOR_FORMATS, info.getCapabilitiesForType(mime));
+
+    return new HardwareVideoEncoder(codecName, type, colorFormat, getKeyFrameIntervalSec(type),
+        getForcedKeyFrameIntervalMs(type, codecName), createBitrateAdjuster(type, codecName));
+  }
+
+  @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 (VideoCodecType type :
+        new VideoCodecType[] {VideoCodecType.VP8, VideoCodecType.VP9, VideoCodecType.H264}) {
+      MediaCodecInfo codec = findCodecForType(type);
+      if (codec != null) {
+        String name = type.name();
+        if (type == VideoCodecType.H264 && isH264HighProfileSupported(codec)) {
+          supportedCodecInfos.add(new VideoCodecInfo(0, name, getCodecProperties(type, true)));
+        }
+
+        supportedCodecInfos.add(new VideoCodecInfo(0, name, getCodecProperties(type, false)));
+      }
+    }
+    return supportedCodecInfos.toArray(new VideoCodecInfo[supportedCodecInfos.size()]);
+  }
+
+  private MediaCodecInfo findCodecForType(VideoCodecType type) {
+    for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
+      MediaCodecInfo info = null;
+      try {
+        info = MediaCodecList.getCodecInfoAt(i);
+      } catch (IllegalArgumentException e) {
+        Logging.e(TAG, "Cannot retrieve encoder codec info", e);
+      }
+
+      if (info == null || !info.isEncoder()) {
+        continue;
+      }
+
+      if (isSupportedCodec(info, type)) {
+        return info;
+      }
+    }
+    return null; // No support for this type.
+  }
+
+  // Returns true if the given MediaCodecInfo indicates a supported encoder for the given type.
+  private boolean isSupportedCodec(MediaCodecInfo info, VideoCodecType type) {
+    if (!codecSupportsType(info, type)) {
+      return false;
+    }
+    // Check for a supported color format.
+    if (selectColorFormat(SUPPORTED_COLOR_FORMATS, info.getCapabilitiesForType(type.mimeType()))
+        == null) {
+      return false;
+    }
+    return isHardwareSupportedInCurrentSdk(info, type);
+  }
+
+  private Integer selectColorFormat(int[] supportedColorFormats, CodecCapabilities capabilities) {
+    for (int supportedColorFormat : supportedColorFormats) {
+      for (int codecColorFormat : capabilities.colorFormats) {
+        if (codecColorFormat == supportedColorFormat) {
+          return codecColorFormat;
+        }
+      }
+    }
+    return null;
+  }
+
+  private boolean codecSupportsType(MediaCodecInfo info, VideoCodecType type) {
+    for (String mimeType : info.getSupportedTypes()) {
+      if (type.mimeType().equals(mimeType)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // Returns true if the given MediaCodecInfo indicates a hardware module that is supported on the
+  // current SDK.
+  private boolean isHardwareSupportedInCurrentSdk(MediaCodecInfo info, VideoCodecType 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);
+  }
+
+  private int getKeyFrameIntervalSec(VideoCodecType type) {
+    switch (type) {
+      case VP8: // Fallthrough intended.
+      case VP9:
+        return 100;
+      case H264:
+        return 20;
+    }
+    throw new IllegalArgumentException("Unsupported VideoCodecType " + type);
+  }
+
+  private int getForcedKeyFrameIntervalMs(VideoCodecType type, String codecName) {
+    if (type == VideoCodecType.VP8 && codecName.startsWith(QCOM_PREFIX)) {
+      if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP
+          || Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1) {
+        return QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_L_MS;
+      } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M) {
+        return QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_M_MS;
+      } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
+        return QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_N_MS;
+      }
+    }
+    // Other codecs don't need key frame forcing.
+    return 0;
+  }
+
+  private BitrateAdjuster createBitrateAdjuster(VideoCodecType type, String codecName) {
+    if (codecName.startsWith(EXYNOS_PREFIX)) {
+      if (type == VideoCodecType.VP8) {
+        // Exynos VP8 encoders need dynamic bitrate adjustment.
+        return new DynamicBitrateAdjuster();
+      } else {
+        // Exynos VP9 and H264 encoders need framerate-based bitrate adjustment.
+        return new FramerateBitrateAdjuster();
+      }
+    }
+    // Other codecs don't need bitrate adjustment.
+    return new BaseBitrateAdjuster();
+  }
+
+  private boolean isH264HighProfileSupported(MediaCodecInfo info) {
+    return enableH264HighProfile && info.getName().startsWith(QCOM_PREFIX);
+  }
+
+  private Map<String, String> getCodecProperties(VideoCodecType type, boolean highProfile) {
+    switch (type) {
+      case VP8:
+      case VP9:
+        return new HashMap<String, String>();
+      case H264:
+        Map<String, String> properties = new HashMap<>();
+        properties.put(H264_FMTP_LEVEL_ASYMMETRY_ALLOWED, "1");
+        properties.put(H264_FMTP_PACKETIZATION_MODE, "1");
+        properties.put(H264_FMTP_PROFILE_LEVEL_ID,
+            highProfile ? H264_CONSTRAINED_HIGH_3_1 : H264_CONSTRAINED_BASELINE_3_1);
+        return properties;
+      default:
+        throw new IllegalArgumentException("Unsupported codec: " + type);
+    }
+  }
+}
diff --git a/webrtc/sdk/android/api/org/webrtc/VideoEncoderFactory.java b/webrtc/sdk/android/api/org/webrtc/VideoEncoderFactory.java
new file mode 100644
index 0000000..e7611ff
--- /dev/null
+++ b/webrtc/sdk/android/api/org/webrtc/VideoEncoderFactory.java
@@ -0,0 +1,20 @@
+/*
+ *  Copyright 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+package org.webrtc;
+
+/** Factory for creating VideoEncoders. */
+interface VideoEncoderFactory {
+  /** Creates an encoder for the given video codec. */
+  public VideoEncoder createEncoder(VideoCodecInfo info);
+
+  /** Enumerates the list of supported video codecs. */
+  public VideoCodecInfo[] getSupportedCodecs();
+}
diff --git a/webrtc/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java b/webrtc/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java
new file mode 100644
index 0000000..87fe6ef
--- /dev/null
+++ b/webrtc/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java
@@ -0,0 +1,96 @@
+/*
+ *  Copyright 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+package org.webrtc;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.TargetApi;
+import android.graphics.Matrix;
+import android.support.test.filters.SmallTest;
+import android.util.Log;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@TargetApi(16)
+@RunWith(BaseJUnit4ClassRunner.class)
+public class HardwareVideoEncoderTest {
+  final static String TAG = "MediaCodecVideoEncoderTest";
+
+  private static final boolean ENABLE_INTEL_VP8_ENCODER = true;
+  private static final boolean ENABLE_H264_HIGH_PROFILE = true;
+  private static final VideoEncoder.Settings SETTINGS = new VideoEncoder.Settings(
+      1 /* core */, 640 /* width */, 480 /* height */, 300 /* kbps */, 30 /* fps */);
+
+  @Test
+  @SmallTest
+  public void testInitializeUsingYuvBuffer() {
+    HardwareVideoEncoderFactory factory =
+        new HardwareVideoEncoderFactory(ENABLE_INTEL_VP8_ENCODER, ENABLE_H264_HIGH_PROFILE);
+    VideoCodecInfo[] supportedCodecs = factory.getSupportedCodecs();
+    if (supportedCodecs.length == 0) {
+      Log.w(TAG, "No hardware encoding support, skipping testInitializeUsingYuvBuffer");
+      return;
+    }
+    VideoEncoder encoder = factory.createEncoder(supportedCodecs[0]);
+    assertEquals(encoder.initEncode(SETTINGS, null), VideoCodecStatus.OK);
+    assertEquals(encoder.release(), VideoCodecStatus.OK);
+  }
+
+  @Test
+  @SmallTest
+  public void testEncodeYuvBuffer() throws InterruptedException {
+    HardwareVideoEncoderFactory factory =
+        new HardwareVideoEncoderFactory(ENABLE_INTEL_VP8_ENCODER, ENABLE_H264_HIGH_PROFILE);
+    VideoCodecInfo[] supportedCodecs = factory.getSupportedCodecs();
+    if (supportedCodecs.length == 0) {
+      Log.w(TAG, "No hardware encoding support, skipping testEncodeYuvBuffer");
+      return;
+    }
+
+    VideoEncoder encoder = factory.createEncoder(supportedCodecs[0]);
+
+    final long presentationTimestampUs = 20000;
+    final CountDownLatch encodeDone = new CountDownLatch(1);
+
+    VideoEncoder.Callback callback = new VideoEncoder.Callback() {
+      @Override
+      public void onEncodedFrame(EncodedImage image, VideoEncoder.CodecSpecificInfo info) {
+        assertTrue(image.buffer.capacity() > 0);
+        assertEquals(image.encodedWidth, SETTINGS.width);
+        assertEquals(image.encodedHeight, SETTINGS.height);
+        assertEquals(image.captureTimeMs, presentationTimestampUs / 1000);
+        assertEquals(image.frameType, EncodedImage.FrameType.VideoFrameKey);
+        assertEquals(image.rotation, 0);
+        assertTrue(image.completeFrame);
+
+        encodeDone.countDown();
+      }
+    };
+
+    assertEquals(encoder.initEncode(SETTINGS, callback), VideoCodecStatus.OK);
+
+    VideoFrame.I420Buffer buffer = new I420BufferImpl(SETTINGS.width, SETTINGS.height);
+    VideoFrame frame =
+        new VideoFrame(buffer, 0 /* rotation */, presentationTimestampUs * 1000, new Matrix());
+    VideoEncoder.EncodeInfo info = new VideoEncoder.EncodeInfo(
+        new EncodedImage.FrameType[] {EncodedImage.FrameType.VideoFrameKey});
+
+    assertEquals(encoder.encode(frame, info), VideoCodecStatus.OK);
+
+    ThreadUtils.awaitUninterruptibly(encodeDone);
+
+    assertEquals(encoder.release(), VideoCodecStatus.OK);
+  }
+}
diff --git a/webrtc/sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java b/webrtc/sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java
index d36ef6c..105458b 100644
--- a/webrtc/sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java
+++ b/webrtc/sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java
@@ -48,15 +48,8 @@
   private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000;
   private static final int DEQUEUE_OUTPUT_BUFFER_TIMEOUT_US = 100000;
 
-  // TODO(mellem):  Maybe move mime types to the factory or a common location.
-  private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
-  private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9";
-  private static final String H264_MIME_TYPE = "video/avc";
-  private static final Set<String> SUPPORTED_MIME_TYPES =
-      new HashSet<>(Arrays.asList(VP8_MIME_TYPE, VP9_MIME_TYPE, H264_MIME_TYPE));
-
   private final String codecName;
-  private final String mimeType;
+  private final VideoCodecType codecType;
   private final int colorFormat;
   private final ColorFormat inputColorFormat;
   // Base interval for generating key frames.
@@ -94,27 +87,23 @@
   private ByteBuffer configBuffer = null;
 
   /**
-   * Creates a new HardwareVideoEncoder with the given codecName, mimeType, colorFormat, key frame
+   * Creates a new HardwareVideoEncoder with the given codecName, codecType, colorFormat, key frame
    * intervals, and bitrateAdjuster.
    *
    * @param codecName the hardware codec implementation to use
-   * @param mimeType MIME type of the codec's output; must be one of "video/x-vnd.on2.vp8",
-   *     "video/x-vnd.on2.vp9", or "video/avc"
+   * @param codecType the type of the given video codec (eg. VP8, VP9, or H264)
    * @param colorFormat color format used by the input buffer
    * @param keyFrameIntervalSec interval in seconds between key frames; used to initialize the codec
    * @param forceKeyFrameIntervalMs interval at which to force a key frame if one is not requested;
    *     used to reduce distortion caused by some codec implementations
    * @param bitrateAdjuster algorithm used to correct codec implementations that do not produce the
    *     desired bitrates
-   * @throws IllegalArgumentException if either mimeType or colorFormat is unsupported
+   * @throws IllegalArgumentException if colorFormat is unsupported
    */
-  public HardwareVideoEncoder(String codecName, String mimeType, int colorFormat,
+  public HardwareVideoEncoder(String codecName, VideoCodecType codecType, int colorFormat,
       int keyFrameIntervalSec, int forceKeyFrameIntervalMs, BitrateAdjuster bitrateAdjuster) {
-    if (!SUPPORTED_MIME_TYPES.contains(mimeType)) {
-      throw new IllegalArgumentException("Unsupported MIME type: " + mimeType);
-    }
     this.codecName = codecName;
-    this.mimeType = mimeType;
+    this.codecType = codecType;
     this.colorFormat = colorFormat;
     this.inputColorFormat = ColorFormat.valueOf(colorFormat);
     this.keyFrameIntervalSec = keyFrameIntervalSec;
@@ -150,7 +139,7 @@
       return VideoCodecStatus.ERROR;
     }
     try {
-      MediaFormat format = MediaFormat.createVideoFormat(mimeType, width, height);
+      MediaFormat format = MediaFormat.createVideoFormat(codecType.mimeType(), width, height);
       format.setInteger(MediaFormat.KEY_BIT_RATE, adjustedBitrate);
       format.setInteger(KEY_BITRATE_MODE, VIDEO_ControlRateConstant);
       format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
@@ -369,7 +358,7 @@
 
         ByteBuffer frameBuffer;
         boolean isKeyFrame = (info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
-        if (isKeyFrame && mimeType.equals(H264_MIME_TYPE)) {
+        if (isKeyFrame && codecType == VideoCodecType.H264) {
           Logging.d(TAG,
               "Prepending config frame of size " + configBuffer.capacity()
                   + " to output buffer with offset " + info.offset + ", size " + info.size);
diff --git a/webrtc/sdk/android/src/java/org/webrtc/I420BufferImpl.java b/webrtc/sdk/android/src/java/org/webrtc/I420BufferImpl.java
new file mode 100644
index 0000000..87fc202
--- /dev/null
+++ b/webrtc/sdk/android/src/java/org/webrtc/I420BufferImpl.java
@@ -0,0 +1,85 @@
+/*
+ *  Copyright 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+package org.webrtc;
+
+import java.nio.ByteBuffer;
+import org.webrtc.VideoFrame.I420Buffer;
+
+/** Implementation of an I420 VideoFrame buffer. */
+class I420BufferImpl implements VideoFrame.I420Buffer {
+  private final int width;
+  private final int height;
+  private final int strideUV;
+  private final ByteBuffer y;
+  private final ByteBuffer u;
+  private final ByteBuffer v;
+
+  I420BufferImpl(int width, int height) {
+    this.width = width;
+    this.height = height;
+    this.strideUV = (width + 1) / 2;
+    int halfHeight = (height + 1) / 2;
+    this.y = ByteBuffer.allocateDirect(width * height);
+    this.u = ByteBuffer.allocateDirect(strideUV * halfHeight);
+    this.v = ByteBuffer.allocateDirect(strideUV * halfHeight);
+  }
+
+  @Override
+  public int getWidth() {
+    return width;
+  }
+
+  @Override
+  public int getHeight() {
+    return height;
+  }
+
+  @Override
+  public ByteBuffer getDataY() {
+    return y;
+  }
+
+  @Override
+  public ByteBuffer getDataU() {
+    return u;
+  }
+
+  @Override
+  public ByteBuffer getDataV() {
+    return v;
+  }
+
+  @Override
+  public int getStrideY() {
+    return width;
+  }
+
+  @Override
+  public int getStrideU() {
+    return strideUV;
+  }
+
+  @Override
+  public int getStrideV() {
+    return strideUV;
+  }
+
+  @Override
+  public I420Buffer toI420() {
+    return this;
+  }
+
+  @Override
+  public void retain() {}
+
+  @Override
+  public void release() {}
+}
diff --git a/webrtc/sdk/android/src/java/org/webrtc/VideoCodecType.java b/webrtc/sdk/android/src/java/org/webrtc/VideoCodecType.java
new file mode 100644
index 0000000..2d4ef9a
--- /dev/null
+++ b/webrtc/sdk/android/src/java/org/webrtc/VideoCodecType.java
@@ -0,0 +1,28 @@
+/*
+ *  Copyright 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+package org.webrtc;
+
+/** Enumeration of supported video codec types. */
+enum VideoCodecType {
+  VP8("video/x-vnd.on2.vp8"),
+  VP9("video/x-vnd.on2.vp9"),
+  H264("video/avc");
+
+  private final String mimeType;
+
+  private VideoCodecType(String mimeType) {
+    this.mimeType = mimeType;
+  }
+
+  String mimeType() {
+    return mimeType;
+  }
+}