Revert "Improve unit testing for HardwareVideoEncoder and fix bugs."

This reverts commit 7a2bfd22e69f14e2af989b9e30ddd834f585caa9.

Reason for revert: Breaks external test.

Original change's description:
> Improve unit testing for HardwareVideoEncoder and fix bugs.
> 
> Improves the unit testing for HardwareVideoEncoder and fixes bugs in it.
> The main added feature is support for dynamically switching between
> texture and byte buffer modes.
> 
> Bug: webrtc:7760
> Change-Id: Iaffe6b7700047c7d0f9a7b89a6118f6ff932cd9b
> Reviewed-on: https://webrtc-review.googlesource.com/2682
> Commit-Queue: Sami Kalliomäki <sakal@webrtc.org>
> Reviewed-by: Magnus Jedvert <magjed@webrtc.org>
> Cr-Commit-Position: refs/heads/master@{#19963}

TBR=magjed@webrtc.org,sakal@webrtc.org

Change-Id: If1e283a8429c994ad061c7a8320d76633bd0d66b
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: webrtc:7760
Reviewed-on: https://webrtc-review.googlesource.com/3640
Reviewed-by: Sami Kalliomäki <sakal@webrtc.org>
Commit-Queue: Sami Kalliomäki <sakal@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#19964}
diff --git a/sdk/android/api/org/webrtc/HardwareVideoEncoderFactory.java b/sdk/android/api/org/webrtc/HardwareVideoEncoderFactory.java
index 4b561d4..9324ba3 100644
--- a/sdk/android/api/org/webrtc/HardwareVideoEncoderFactory.java
+++ b/sdk/android/api/org/webrtc/HardwareVideoEncoderFactory.java
@@ -72,13 +72,13 @@
 
     String codecName = info.getName();
     String mime = type.mimeType();
-    Integer surfaceColorFormat = MediaCodecUtils.selectColorFormat(
-        MediaCodecUtils.TEXTURE_COLOR_FORMATS, info.getCapabilitiesForType(mime));
-    Integer yuvColorFormat = MediaCodecUtils.selectColorFormat(
-        MediaCodecUtils.ENCODER_COLOR_FORMATS, info.getCapabilitiesForType(mime));
+    int colorFormat = MediaCodecUtils.selectColorFormat(sharedContext == null
+            ? MediaCodecUtils.ENCODER_COLOR_FORMATS
+            : MediaCodecUtils.TEXTURE_COLOR_FORMATS,
+        info.getCapabilitiesForType(mime));
 
-    return new HardwareVideoEncoder(codecName, type, surfaceColorFormat, yuvColorFormat,
-        input.params, getKeyFrameIntervalSec(type), getForcedKeyFrameIntervalMs(type, codecName),
+    return new HardwareVideoEncoder(codecName, type, colorFormat, input.params,
+        getKeyFrameIntervalSec(type), getForcedKeyFrameIntervalMs(type, codecName),
         createBitrateAdjuster(type, codecName), sharedContext);
   }
 
@@ -128,8 +128,10 @@
       return false;
     }
     // Check for a supported color format.
-    if (MediaCodecUtils.selectColorFormat(
-            MediaCodecUtils.ENCODER_COLOR_FORMATS, info.getCapabilitiesForType(type.mimeType()))
+    if (MediaCodecUtils.selectColorFormat(sharedContext == null
+                ? MediaCodecUtils.ENCODER_COLOR_FORMATS
+                : MediaCodecUtils.TEXTURE_COLOR_FORMATS,
+            info.getCapabilitiesForType(type.mimeType()))
         == null) {
       return false;
     }
diff --git a/sdk/android/instrumentationtests/AndroidManifest.xml b/sdk/android/instrumentationtests/AndroidManifest.xml
index 591932c..c239a83 100644
--- a/sdk/android/instrumentationtests/AndroidManifest.xml
+++ b/sdk/android/instrumentationtests/AndroidManifest.xml
@@ -33,7 +33,7 @@
 
     <!-- tools:ignore needed for chromium-junit4 tag. crbug.com/640116
          TODO(sakal): Remove once the tag is no longer needed. -->
-    <instrumentation android:name="org.chromium.base.test.BaseChromiumAndroidJUnitRunner"
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
         tools:ignore="MissingPrefix"
         android:targetPackage="org.webrtc"
         android:label="Tests for WebRTC Android SDK"
diff --git a/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java b/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java
index 120ed3f..92e9fa3 100644
--- a/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java
+++ b/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java
@@ -11,417 +11,202 @@
 package org.webrtc;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import android.annotation.TargetApi;
 import android.graphics.Matrix;
 import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
 import android.support.test.filters.SmallTest;
 import android.util.Log;
 import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import org.chromium.base.test.params.BaseJUnit4RunnerDelegate;
-import org.chromium.base.test.params.ParameterAnnotations.ClassParameter;
-import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
-import org.chromium.base.test.params.ParameterSet;
-import org.chromium.base.test.params.ParameterizedRunner;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
+import java.util.concurrent.CountDownLatch;
+import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @TargetApi(16)
-@RunWith(ParameterizedRunner.class)
-@UseRunnerDelegate(BaseJUnit4RunnerDelegate.class)
+@RunWith(BaseJUnit4ClassRunner.class)
 public class HardwareVideoEncoderTest {
-  @ClassParameter private static List<ParameterSet> CLASS_PARAMS = new ArrayList<>();
-
-  static {
-    CLASS_PARAMS.add(new ParameterSet()
-                         .value(false /* useTextures */, false /* useEglContext */)
-                         .name("I420WithoutEglContext"));
-    CLASS_PARAMS.add(new ParameterSet()
-                         .value(true /* useTextures */, false /* useEglContext */)
-                         .name("TextureWithoutEglContext"));
-    CLASS_PARAMS.add(new ParameterSet()
-                         .value(true /* useTextures */, true /* useEglContext */)
-                         .name("TextureWithEglContext"));
-  }
-
-  private final boolean useTextures;
-  private final boolean useEglContext;
-
-  public HardwareVideoEncoderTest(boolean useTextures, boolean useEglContext) {
-    this.useTextures = useTextures;
-    this.useEglContext = useEglContext;
-  }
-
-  final static String TAG = "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 */, true /* automaticResizeOn */);
-  private static final int ENCODE_TIMEOUT_MS = 1000;
-  private static final int NUM_TEST_FRAMES = 10;
-  private static final int NUM_ENCODE_TRIES = 100;
-  private static final int ENCODE_RETRY_SLEEP_MS = 1;
-
-  // # Mock classes
-  /**
-   * Mock encoder callback that allows easy verification of the general properties of the encoded
-   * frame such as width and height.
-   */
-  private static class MockEncoderCallback implements VideoEncoder.Callback {
-    private BlockingQueue<EncodedImage> frameQueue = new LinkedBlockingQueue<>();
-
-    public void onEncodedFrame(EncodedImage frame, VideoEncoder.CodecSpecificInfo info) {
-      assertNotNull(frame);
-      assertNotNull(info);
-      frameQueue.offer(frame);
-    }
-
-    public EncodedImage poll() {
-      try {
-        EncodedImage image = frameQueue.poll(ENCODE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertNotNull("Timed out waiting for the frame to be encoded.", image);
-        return image;
-      } catch (InterruptedException e) {
-        throw new RuntimeException(e);
-      }
-    }
-
-    public void assertFrameEncoded(VideoFrame frame) {
-      final VideoFrame.Buffer buffer = frame.getBuffer();
-      final EncodedImage image = poll();
-      assertTrue(image.buffer.capacity() > 0);
-      assertEquals(image.encodedWidth, buffer.getWidth());
-      assertEquals(image.encodedHeight, buffer.getHeight());
-      assertEquals(image.captureTimeNs, frame.getTimestampNs());
-      assertEquals(image.rotation, frame.getRotation());
-    }
-  }
-
-  /** A common base class for the texture and I420 buffer that implements reference counting. */
-  private static abstract class MockBufferBase implements VideoFrame.Buffer {
-    protected final int width;
-    protected final int height;
-    private final Runnable releaseCallback;
-    private final Object refCountLock = new Object();
-    private int refCount = 1;
-
-    public MockBufferBase(int width, int height, Runnable releaseCallback) {
-      this.width = width;
-      this.height = height;
-      this.releaseCallback = releaseCallback;
-    }
-
-    @Override
-    public int getWidth() {
-      return width;
-    }
-
-    @Override
-    public int getHeight() {
-      return height;
-    }
-
-    @Override
-    public void retain() {
-      synchronized (refCountLock) {
-        assertTrue("Buffer retained after being destroyed.", refCount > 0);
-        ++refCount;
-      }
-    }
-
-    @Override
-    public void release() {
-      synchronized (refCountLock) {
-        assertTrue("Buffer released too many times.", --refCount >= 0);
-        if (refCount == 0) {
-          releaseCallback.run();
-        }
-      }
-    }
-  }
-
-  private static class MockTextureBuffer
-      extends MockBufferBase implements VideoFrame.TextureBuffer {
-    private final int textureId;
-
-    public MockTextureBuffer(int textureId, int width, int height, Runnable releaseCallback) {
-      super(width, height, releaseCallback);
-      this.textureId = textureId;
-    }
-
-    @Override
-    public VideoFrame.TextureBuffer.Type getType() {
-      return VideoFrame.TextureBuffer.Type.OES;
-    }
-
-    @Override
-    public int getTextureId() {
-      return textureId;
-    }
-
-    @Override
-    public Matrix getTransformMatrix() {
-      return new Matrix();
-    }
-
-    @Override
-    public VideoFrame.I420Buffer toI420() {
-      return I420BufferImpl.allocate(width, height);
-    }
-
-    @Override
-    public VideoFrame.Buffer cropAndScale(
-        int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) {
-      retain();
-      return new MockTextureBuffer(textureId, scaleWidth, scaleHeight, this ::release);
-    }
-  }
-
-  private static class MockI420Buffer extends MockBufferBase implements VideoFrame.I420Buffer {
-    private final I420BufferImpl realBuffer;
-
-    public MockI420Buffer(int width, int height, Runnable releaseCallback) {
-      super(width, height, releaseCallback);
-      // We never release this but it is not a problem in practice because the release is a no-op.
-      realBuffer = I420BufferImpl.allocate(width, height);
-    }
-
-    @Override
-    public ByteBuffer getDataY() {
-      return realBuffer.getDataY();
-    }
-
-    @Override
-    public ByteBuffer getDataU() {
-      return realBuffer.getDataU();
-    }
-
-    @Override
-    public ByteBuffer getDataV() {
-      return realBuffer.getDataV();
-    }
-
-    @Override
-    public int getStrideY() {
-      return realBuffer.getStrideY();
-    }
-
-    @Override
-    public int getStrideU() {
-      return realBuffer.getStrideU();
-    }
-
-    @Override
-    public int getStrideV() {
-      return realBuffer.getStrideV();
-    }
-
-    @Override
-    public VideoFrame.I420Buffer toI420() {
-      retain();
-      return this;
-    }
-
-    @Override
-    public VideoFrame.Buffer cropAndScale(
-        int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) {
-      return realBuffer.cropAndScale(cropX, cropY, cropWidth, cropHeight, scaleWidth, scaleHeight);
-    }
-  }
-
-  // # Test fields
-  private Object referencedFramesLock = new Object();
-  private int referencedFrames = 0;
-
-  private Runnable releaseFrameCallback = new Runnable() {
-    public void run() {
-      synchronized (referencedFramesLock) {
-        --referencedFrames;
-      }
-    }
-  };
-
-  private EglBase14 eglBase;
-  private long lastTimestampNs;
-
-  // # Helper methods
-  private VideoEncoderFactory createEncoderFactory(EglBase.Context eglContext) {
-    return new HardwareVideoEncoderFactory(
-        eglContext, ENABLE_INTEL_VP8_ENCODER, ENABLE_H264_HIGH_PROFILE);
-  }
-
-  private VideoEncoder createEncoder() {
-    VideoEncoderFactory factory =
-        createEncoderFactory(useTextures ? eglBase.getEglBaseContext() : null);
-    VideoCodecInfo[] supportedCodecs = factory.getSupportedCodecs();
-    return factory.createEncoder(supportedCodecs[0]);
-  }
-
-  private VideoFrame generateI420Frame(int width, int height) {
-    synchronized (referencedFramesLock) {
-      ++referencedFrames;
-    }
-    lastTimestampNs += TimeUnit.SECONDS.toNanos(1) / SETTINGS.maxFramerate;
-    VideoFrame.Buffer buffer = new MockI420Buffer(width, height, releaseFrameCallback);
-    return new VideoFrame(buffer, 0 /* rotation */, lastTimestampNs);
-  }
-
-  private VideoFrame generateTextureFrame(int width, int height) {
-    synchronized (referencedFramesLock) {
-      ++referencedFrames;
-    }
-    final int textureId = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
-    lastTimestampNs += TimeUnit.SECONDS.toNanos(1) / SETTINGS.maxFramerate;
-    VideoFrame.Buffer buffer =
-        new MockTextureBuffer(textureId, width, height, releaseFrameCallback);
-    return new VideoFrame(buffer, 0 /* rotation */, lastTimestampNs);
-  }
-
-  private VideoFrame generateFrame(int width, int height) {
-    return useTextures ? generateTextureFrame(width, height) : generateI420Frame(width, height);
-  }
-
-  private void testEncodeFrame(
-      VideoEncoder encoder, VideoFrame frame, VideoEncoder.EncodeInfo info) {
-    int numTries = 0;
-
-    // It takes a while for the encoder to become ready so try until it accepts the frame.
-    while (true) {
-      ++numTries;
-
-      final VideoCodecStatus returnValue = encoder.encode(frame, info);
-      switch (returnValue) {
-        case OK:
-          return; // Success
-        case NO_OUTPUT:
-          if (numTries < NUM_ENCODE_TRIES) {
-            try {
-              Thread.sleep(ENCODE_RETRY_SLEEP_MS); // Try again.
-            } catch (InterruptedException e) {
-              throw new RuntimeException(e);
-            }
-            break;
-          } else {
-            fail("encoder.encode keeps returning NO_OUTPUT");
-          }
-        default:
-          fail("encoder.encode returned: " + returnValue); // Error
-      }
-    }
-  }
-
-  // # Tests
-  @Before
-  public void setUp() {
-    eglBase = new EglBase14(null, EglBase.CONFIG_PLAIN);
-    eglBase.createDummyPbufferSurface();
-    eglBase.makeCurrent();
-    lastTimestampNs = System.nanoTime();
-  }
-
-  @After
-  public void tearDown() {
-    eglBase.release();
-    synchronized (referencedFramesLock) {
-      assertEquals("All frames were not released", 0, referencedFrames);
-    }
-  }
 
   @Test
   @SmallTest
-  public void testInitialize() {
-    VideoEncoder encoder = createEncoder();
+  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(VideoCodecStatus.OK, encoder.initEncode(SETTINGS, null));
     assertEquals(VideoCodecStatus.OK, encoder.release());
   }
 
   @Test
   @SmallTest
-  public void testEncode() {
-    VideoEncoder encoder = createEncoder();
-    MockEncoderCallback callback = new MockEncoderCallback();
-    assertEquals(VideoCodecStatus.OK, encoder.initEncode(SETTINGS, callback));
-
-    for (int i = 0; i < NUM_TEST_FRAMES; i++) {
-      Log.d(TAG, "Test frame: " + i);
-      VideoFrame frame = generateFrame(SETTINGS.width, SETTINGS.height);
-      VideoEncoder.EncodeInfo info = new VideoEncoder.EncodeInfo(
-          new EncodedImage.FrameType[] {EncodedImage.FrameType.VideoFrameDelta});
-      testEncodeFrame(encoder, frame, info);
-
-      callback.assertFrameEncoded(frame);
-      frame.release();
+  public void testInitializeUsingTextures() {
+    EglBase14 eglBase = new EglBase14(null, EglBase.CONFIG_PLAIN);
+    HardwareVideoEncoderFactory factory = new HardwareVideoEncoderFactory(
+        eglBase.getEglBaseContext(), ENABLE_INTEL_VP8_ENCODER, ENABLE_H264_HIGH_PROFILE);
+    VideoCodecInfo[] supportedCodecs = factory.getSupportedCodecs();
+    if (supportedCodecs.length == 0) {
+      Log.w(TAG, "No hardware encoding support, skipping testInitializeUsingTextures");
+      return;
     }
-
+    VideoEncoder encoder = factory.createEncoder(supportedCodecs[0]);
+    assertEquals(VideoCodecStatus.OK, encoder.initEncode(SETTINGS, null));
     assertEquals(VideoCodecStatus.OK, encoder.release());
+    eglBase.release();
   }
 
   @Test
   @SmallTest
-  public void testEncodeAltenatingBuffers() {
-    VideoEncoder encoder = createEncoder();
-    MockEncoderCallback callback = new MockEncoderCallback();
-    assertEquals(VideoCodecStatus.OK, encoder.initEncode(SETTINGS, callback));
-
-    for (int i = 0; i < NUM_TEST_FRAMES; i++) {
-      Log.d(TAG, "Test frame: " + i);
-      VideoFrame frame;
-      VideoEncoder.EncodeInfo info = new VideoEncoder.EncodeInfo(
-          new EncodedImage.FrameType[] {EncodedImage.FrameType.VideoFrameDelta});
-
-      frame = generateTextureFrame(SETTINGS.width, SETTINGS.height);
-      testEncodeFrame(encoder, frame, info);
-      callback.assertFrameEncoded(frame);
-      frame.release();
-
-      frame = generateI420Frame(SETTINGS.width, SETTINGS.height);
-      testEncodeFrame(encoder, frame, info);
-      callback.assertFrameEncoded(frame);
-      frame.release();
+  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;
     }
 
-    assertEquals(VideoCodecStatus.OK, encoder.release());
-  }
+    VideoEncoder encoder = factory.createEncoder(supportedCodecs[0]);
 
-  @Test
-  @SmallTest
-  public void testEncodeDifferentSizes() {
-    VideoEncoder encoder = createEncoder();
-    MockEncoderCallback callback = new MockEncoderCallback();
-    assertEquals(VideoCodecStatus.OK, encoder.initEncode(SETTINGS, callback));
+    final long presentationTimestampNs = 20000;
+    final CountDownLatch encodeDone = new CountDownLatch(1);
 
-    VideoFrame frame;
+    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.captureTimeNs, presentationTimestampNs);
+        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 = I420BufferImpl.allocate(SETTINGS.width, SETTINGS.height);
+    VideoFrame frame = new VideoFrame(buffer, 0 /* rotation */, presentationTimestampNs);
     VideoEncoder.EncodeInfo info = new VideoEncoder.EncodeInfo(
-        new EncodedImage.FrameType[] {EncodedImage.FrameType.VideoFrameDelta});
+        new EncodedImage.FrameType[] {EncodedImage.FrameType.VideoFrameKey});
 
-    frame = generateFrame(SETTINGS.width / 2, SETTINGS.height / 2);
-    testEncodeFrame(encoder, frame, info);
-    callback.assertFrameEncoded(frame);
-    frame.release();
+    assertEquals(encoder.encode(frame, info), VideoCodecStatus.OK);
 
-    frame = generateFrame(SETTINGS.width, SETTINGS.height);
-    testEncodeFrame(encoder, frame, info);
-    callback.assertFrameEncoded(frame);
-    frame.release();
+    ThreadUtils.awaitUninterruptibly(encodeDone);
 
-    frame = generateFrame(SETTINGS.width / 4, SETTINGS.height / 4);
-    testEncodeFrame(encoder, frame, info);
-    callback.assertFrameEncoded(frame);
-    frame.release();
+    assertEquals(encoder.release(), VideoCodecStatus.OK);
+  }
 
-    assertEquals(VideoCodecStatus.OK, encoder.release());
+  @Test
+  @SmallTest
+  public void testEncodeTextures() throws InterruptedException {
+    final EglBase14 eglOesBase = new EglBase14(null, EglBase.CONFIG_PIXEL_BUFFER);
+    HardwareVideoEncoderFactory factory = new HardwareVideoEncoderFactory(
+        eglOesBase.getEglBaseContext(), ENABLE_INTEL_VP8_ENCODER, ENABLE_H264_HIGH_PROFILE);
+    VideoCodecInfo[] supportedCodecs = factory.getSupportedCodecs();
+    if (supportedCodecs.length == 0) {
+      Log.w(TAG, "No hardware encoding support, skipping testEncodeTextures");
+      return;
+    }
+
+    eglOesBase.createDummyPbufferSurface();
+    eglOesBase.makeCurrent();
+    final int oesTextureId = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
+
+    VideoEncoder encoder = factory.createEncoder(supportedCodecs[0]);
+
+    final long presentationTimestampNs = 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.captureTimeNs, presentationTimestampNs);
+        assertEquals(image.frameType, EncodedImage.FrameType.VideoFrameKey);
+        assertEquals(image.rotation, 0);
+        assertTrue(image.completeFrame);
+
+        encodeDone.countDown();
+      }
+    };
+
+    assertEquals(encoder.initEncode(SETTINGS, callback), VideoCodecStatus.OK);
+
+    VideoFrame.TextureBuffer buffer = new VideoFrame.TextureBuffer() {
+      @Override
+      public VideoFrame.TextureBuffer.Type getType() {
+        return VideoFrame.TextureBuffer.Type.OES;
+      }
+
+      @Override
+      public int getTextureId() {
+        return oesTextureId;
+      }
+
+      @Override
+      public Matrix getTransformMatrix() {
+        return new Matrix();
+      }
+
+      @Override
+      public int getWidth() {
+        return SETTINGS.width;
+      }
+
+      @Override
+      public int getHeight() {
+        return SETTINGS.height;
+      }
+
+      @Override
+      public VideoFrame.I420Buffer toI420() {
+        return null;
+      }
+
+      @Override
+      public void retain() {}
+
+      @Override
+      public void release() {}
+
+      @Override
+      public VideoFrame.Buffer cropAndScale(
+          int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) {
+        return null;
+      }
+    };
+    VideoFrame frame = new VideoFrame(buffer, 0 /* rotation */, presentationTimestampNs);
+    VideoEncoder.EncodeInfo info = new VideoEncoder.EncodeInfo(
+        new EncodedImage.FrameType[] {EncodedImage.FrameType.VideoFrameKey});
+
+    assertEquals(encoder.encode(frame, info), VideoCodecStatus.OK);
+    GlUtil.checkNoGLES2Error("encodeTexture");
+
+    // It should be Ok to delete the texture after calling encodeTexture.
+    GLES20.glDeleteTextures(1, new int[] {oesTextureId}, 0);
+
+    ThreadUtils.awaitUninterruptibly(encodeDone);
+
+    assertEquals(encoder.release(), VideoCodecStatus.OK);
+    eglOesBase.release();
   }
 }
diff --git a/sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java b/sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java
index e9df705..f3be4cb 100644
--- a/sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java
+++ b/sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java
@@ -22,10 +22,8 @@
 import java.nio.ByteBuffer;
 import java.util.Deque;
 import java.util.Map;
-import java.util.concurrent.BlockingDeque;
 import java.util.concurrent.LinkedBlockingDeque;
 import java.util.concurrent.TimeUnit;
-import org.webrtc.ThreadUtils.ThreadChecker;
 
 /** Android hardware video encoder. */
 @TargetApi(19)
@@ -51,60 +49,29 @@
   private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000;
   private static final int DEQUEUE_OUTPUT_BUFFER_TIMEOUT_US = 100000;
 
-  // --- Initialized on construction.
   private final String codecName;
   private final VideoCodecType codecType;
-  private final Integer surfaceColorFormat;
-  private final Integer yuvColorFormat;
-  private final YuvFormat yuvFormat;
+  private final int colorFormat;
   private final Map<String, String> params;
-  private final int keyFrameIntervalSec; // Base interval for generating key frames.
+  private final ColorFormat inputColorFormat;
+  // Base interval for generating key frames.
+  private final int keyFrameIntervalSec;
   // Interval at which to force a key frame. Used to reduce color distortions caused by some
   // Qualcomm video encoders.
   private final long forcedKeyFrameNs;
-  private final BitrateAdjuster bitrateAdjuster;
-  // EGL context shared with the application.  Used to access texture inputs.
-  private final EglBase14.Context sharedContext;
-
-  // Drawer used to draw input textures onto the codec's input surface.
-  private final GlRectDrawer textureDrawer = new GlRectDrawer();
-  private final VideoFrameDrawer videoFrameDrawer = new VideoFrameDrawer();
-  // A queue of EncodedImage.Builders that correspond to frames in the codec.  These builders are
-  // pre-populated with all the information that can't be sent through MediaCodec.
-  private final BlockingDeque<EncodedImage.Builder> outputBuilders = new LinkedBlockingDeque<>();
-
-  private final ThreadChecker encodeThreadChecker = new ThreadChecker();
-  private final ThreadChecker outputThreadChecker = new ThreadChecker();
-
-  // --- Set on initialize and immutable until release.
-  private Callback callback;
-  private boolean automaticResizeOn;
-
-  // --- Valid and immutable while an encoding session is running.
-  private MediaCodec codec;
-  // Thread that delivers encoded frames to the user callback.
-  private Thread outputThread;
-
-  // EGL base wrapping the shared texture context.  Holds hooks to both the shared context and the
-  // input surface.  Making this base current allows textures from the context to be drawn onto the
-  // surface.
-  private EglBase14 textureEglBase;
-  // Input surface for the codec.  The encoder will draw input textures onto this surface.
-  private Surface textureInputSurface;
-
-  private int width;
-  private int height;
-  private boolean useSurfaceMode;
-
-  // --- Only accessed from the encoding thread.
   // Presentation timestamp of the last requested (or forced) key frame.
   private long lastKeyFrameNs;
 
-  // --- Only accessed on the output thread.
-  // Contents of the last observed config frame output by the MediaCodec. Used by H.264.
-  private ByteBuffer configBuffer = null;
+  private final BitrateAdjuster bitrateAdjuster;
   private int adjustedBitrate;
 
+  // A queue of EncodedImage.Builders that correspond to frames in the codec.  These builders are
+  // pre-populated with all the information that can't be sent through MediaCodec.
+  private final Deque<EncodedImage.Builder> outputBuilders;
+
+  // Thread that delivers encoded frames to the user callback.
+  private Thread outputThread;
+
   // Whether the encoder is running.  Volatile so that the output thread can watch this value and
   // exit when the encoder stops.
   private volatile boolean running = false;
@@ -112,14 +79,36 @@
   // value to send exceptions thrown during release back to the encoder thread.
   private volatile Exception shutdownException = null;
 
+  // Surface objects for texture-mode encoding.
+
+  // EGL context shared with the application.  Used to access texture inputs.
+  private EglBase14.Context textureContext;
+  // EGL base wrapping the shared texture context.  Holds hooks to both the shared context and the
+  // input surface.  Making this base current allows textures from the context to be drawn onto the
+  // surface.
+  private EglBase14 textureEglBase;
+  // Input surface for the codec.  The encoder will draw input textures onto this surface.
+  private Surface textureInputSurface;
+  // Drawer used to draw input textures onto the codec's input surface.
+  private GlRectDrawer textureDrawer;
+
+  private MediaCodec codec;
+  private Callback callback;
+
+  private boolean automaticResizeOn;
+  private int width;
+  private int height;
+
+  // Contents of the last observed config frame output by the MediaCodec. Used by H.264.
+  private ByteBuffer configBuffer = null;
+
   /**
    * Creates a new HardwareVideoEncoder with the given codecName, codecType, colorFormat, key frame
    * intervals, and bitrateAdjuster.
    *
    * @param codecName the hardware codec implementation to use
    * @param codecType the type of the given video codec (eg. VP8, VP9, or H264)
-   * @param surfaceColorFormat color format for surface mode or null if not available
-   * @param yuvColorFormat color format for bytebuffer mode
+   * @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
@@ -127,45 +116,46 @@
    *     desired bitrates
    * @throws IllegalArgumentException if colorFormat is unsupported
    */
-  public HardwareVideoEncoder(String codecName, VideoCodecType codecType,
-      Integer surfaceColorFormat, Integer yuvColorFormat, Map<String, String> params,
-      int keyFrameIntervalSec, int forceKeyFrameIntervalMs, BitrateAdjuster bitrateAdjuster,
-      EglBase14.Context sharedContext) {
+  public HardwareVideoEncoder(String codecName, VideoCodecType codecType, int colorFormat,
+      Map<String, String> params, int keyFrameIntervalSec, int forceKeyFrameIntervalMs,
+      BitrateAdjuster bitrateAdjuster, EglBase14.Context textureContext) {
     this.codecName = codecName;
     this.codecType = codecType;
-    this.surfaceColorFormat = surfaceColorFormat;
-    this.yuvColorFormat = yuvColorFormat;
-    this.yuvFormat = YuvFormat.valueOf(yuvColorFormat);
+    this.colorFormat = colorFormat;
     this.params = params;
+    if (textureContext == null) {
+      this.inputColorFormat = ColorFormat.valueOf(colorFormat);
+    } else {
+      // ColorFormat copies bytes between buffers.  It is not used in texture mode.
+      this.inputColorFormat = null;
+    }
     this.keyFrameIntervalSec = keyFrameIntervalSec;
     this.forcedKeyFrameNs = TimeUnit.MILLISECONDS.toNanos(forceKeyFrameIntervalMs);
     this.bitrateAdjuster = bitrateAdjuster;
-    this.sharedContext = sharedContext;
+    this.outputBuilders = new LinkedBlockingDeque<>();
+    this.textureContext = textureContext;
   }
 
   @Override
   public VideoCodecStatus initEncode(Settings settings, Callback callback) {
-    encodeThreadChecker.checkIsOnValidThread();
-
-    this.callback = callback;
     automaticResizeOn = settings.automaticResizeOn;
-    this.width = settings.width;
-    this.height = settings.height;
-    useSurfaceMode = canUseSurface();
 
-    if (settings.startBitrate != 0 && settings.maxFramerate != 0) {
-      bitrateAdjuster.setTargets(settings.startBitrate * 1000, settings.maxFramerate);
+    return initEncodeInternal(
+        settings.width, settings.height, settings.startBitrate, settings.maxFramerate, callback);
+  }
+
+  private VideoCodecStatus initEncodeInternal(
+      int width, int height, int bitrateKbps, int fps, Callback callback) {
+    Logging.d(
+        TAG, "initEncode: " + width + " x " + height + ". @ " + bitrateKbps + "kbps. Fps: " + fps);
+    this.width = width;
+    this.height = height;
+    if (bitrateKbps != 0 && fps != 0) {
+      bitrateAdjuster.setTargets(bitrateKbps * 1000, fps);
     }
     adjustedBitrate = bitrateAdjuster.getAdjustedBitrateBps();
 
-    Logging.d(TAG,
-        "initEncode: " + width + " x " + height + ". @ " + settings.startBitrate
-            + "kbps. Fps: " + settings.maxFramerate + " Use surface mode: " + useSurfaceMode);
-    return initEncodeInternal();
-  }
-
-  private VideoCodecStatus initEncodeInternal() {
-    encodeThreadChecker.checkIsOnValidThread();
+    this.callback = callback;
 
     lastKeyFrameNs = -1;
 
@@ -175,8 +165,6 @@
       Logging.e(TAG, "Cannot create media encoder " + codecName);
       return VideoCodecStatus.ERROR;
     }
-
-    final int colorFormat = useSurfaceMode ? surfaceColorFormat : yuvColorFormat;
     try {
       MediaFormat format = MediaFormat.createVideoFormat(codecType.mimeType(), width, height);
       format.setInteger(MediaFormat.KEY_BIT_RATE, adjustedBitrate);
@@ -201,25 +189,24 @@
         }
       }
       Logging.d(TAG, "Format: " + format);
-      codec.configure(
-          format, null /* surface */, null /* crypto */, MediaCodec.CONFIGURE_FLAG_ENCODE);
+      codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
 
-      if (useSurfaceMode) {
-        textureEglBase = new EglBase14(sharedContext, EglBase.CONFIG_RECORDABLE);
+      if (textureContext != null) {
+        // Texture mode.
+        textureEglBase = new EglBase14(textureContext, EglBase.CONFIG_RECORDABLE);
         textureInputSurface = codec.createInputSurface();
         textureEglBase.createSurface(textureInputSurface);
-        textureEglBase.makeCurrent();
+        textureDrawer = new GlRectDrawer();
       }
 
       codec.start();
     } catch (IllegalStateException e) {
-      Logging.e(TAG, "initEncodeInternal failed", e);
+      Logging.e(TAG, "initEncode failed", e);
       release();
       return VideoCodecStatus.ERROR;
     }
 
     running = true;
-    outputThreadChecker.detachThread();
     outputThread = createOutputThread();
     outputThread.start();
 
@@ -228,60 +215,53 @@
 
   @Override
   public VideoCodecStatus release() {
-    encodeThreadChecker.checkIsOnValidThread();
-
-    final VideoCodecStatus returnValue;
-    if (outputThread == null) {
-      returnValue = VideoCodecStatus.OK;
-    } else {
+    try {
+      if (outputThread == null) {
+        return VideoCodecStatus.OK;
+      }
       // The outputThread actually stops and releases the codec once running is false.
       running = false;
       if (!ThreadUtils.joinUninterruptibly(outputThread, MEDIA_CODEC_RELEASE_TIMEOUT_MS)) {
         Logging.e(TAG, "Media encoder release timeout");
-        returnValue = VideoCodecStatus.TIMEOUT;
-      } else if (shutdownException != null) {
+        return VideoCodecStatus.TIMEOUT;
+      }
+      if (shutdownException != null) {
         // Log the exception and turn it into an error.
         Logging.e(TAG, "Media encoder release exception", shutdownException);
-        returnValue = VideoCodecStatus.ERROR;
-      } else {
-        returnValue = VideoCodecStatus.OK;
+        return VideoCodecStatus.ERROR;
+      }
+    } finally {
+      codec = null;
+      outputThread = null;
+      outputBuilders.clear();
+
+      if (textureDrawer != null) {
+        textureDrawer.release();
+        textureDrawer = null;
+      }
+      if (textureEglBase != null) {
+        textureEglBase.release();
+        textureEglBase = null;
+      }
+      if (textureInputSurface != null) {
+        textureInputSurface.release();
+        textureInputSurface = null;
       }
     }
-
-    textureDrawer.release();
-    videoFrameDrawer.release();
-    if (textureEglBase != null) {
-      textureEglBase.release();
-      textureEglBase = null;
-    }
-    if (textureInputSurface != null) {
-      textureInputSurface.release();
-      textureInputSurface = null;
-    }
-    outputBuilders.clear();
-
-    codec = null;
-    outputThread = null;
-
-    return returnValue;
+    return VideoCodecStatus.OK;
   }
 
   @Override
   public VideoCodecStatus encode(VideoFrame videoFrame, EncodeInfo encodeInfo) {
-    encodeThreadChecker.checkIsOnValidThread();
     if (codec == null) {
       return VideoCodecStatus.UNINITIALIZED;
     }
 
-    final VideoFrame.Buffer videoFrameBuffer = videoFrame.getBuffer();
-    final boolean isTextureBuffer = videoFrameBuffer instanceof VideoFrame.TextureBuffer;
-
     // If input resolution changed, restart the codec with the new resolution.
-    final int frameWidth = videoFrame.getBuffer().getWidth();
-    final int frameHeight = videoFrame.getBuffer().getHeight();
-    final boolean shouldUseSurfaceMode = canUseSurface() && isTextureBuffer;
-    if (frameWidth != width || frameHeight != height || shouldUseSurfaceMode != useSurfaceMode) {
-      VideoCodecStatus status = resetCodec(frameWidth, frameHeight, shouldUseSurfaceMode);
+    int frameWidth = videoFrame.getBuffer().getWidth();
+    int frameHeight = videoFrame.getBuffer().getHeight();
+    if (frameWidth != width || frameHeight != height) {
+      VideoCodecStatus status = resetCodec(frameWidth, frameHeight);
       if (status != VideoCodecStatus.OK) {
         return status;
       }
@@ -290,7 +270,7 @@
     if (outputBuilders.size() > MAX_ENCODER_Q_SIZE) {
       // Too many frames in the encoder.  Drop this frame.
       Logging.e(TAG, "Dropped frame, encoder queue full");
-      return VideoCodecStatus.NO_OUTPUT; // See webrtc bug 2887.
+      return VideoCodecStatus.OK; // See webrtc bug 2887.
     }
 
     boolean requestedKeyFrame = false;
@@ -304,6 +284,7 @@
       requestKeyFrame(videoFrame.getTimestampNs());
     }
 
+    VideoFrame.Buffer videoFrameBuffer = videoFrame.getBuffer();
     // Number of bytes in the video buffer. Y channel is sampled at one byte per pixel; U and V are
     // subsampled at one byte per four pixels.
     int bufferSize = videoFrameBuffer.getHeight() * videoFrameBuffer.getWidth() * 3 / 2;
@@ -315,35 +296,46 @@
                                        .setRotation(videoFrame.getRotation());
     outputBuilders.offer(builder);
 
-    final VideoCodecStatus returnValue;
-    if (useSurfaceMode) {
-      returnValue = encodeTextureBuffer(videoFrame);
+    if (textureContext != null) {
+      if (!(videoFrameBuffer instanceof VideoFrame.TextureBuffer)) {
+        Logging.e(TAG, "Cannot encode non-texture buffer in texture mode");
+        return VideoCodecStatus.ERROR;
+      }
+      VideoFrame.TextureBuffer textureBuffer = (VideoFrame.TextureBuffer) videoFrameBuffer;
+      return encodeTextureBuffer(videoFrame, textureBuffer);
     } else {
-      returnValue = encodeByteBuffer(videoFrame, videoFrameBuffer, bufferSize);
+      if (videoFrameBuffer instanceof VideoFrame.TextureBuffer) {
+        Logging.w(TAG, "Encoding texture buffer in byte mode; this may be inefficient");
+      }
+      return encodeByteBuffer(videoFrame, videoFrameBuffer, bufferSize);
     }
-
-    // Check if the queue was successful.
-    if (returnValue != VideoCodecStatus.OK) {
-      // Keep the output builders in sync with buffers in the codec.
-      outputBuilders.pollLast();
-    }
-
-    return returnValue;
   }
 
-  private VideoCodecStatus encodeTextureBuffer(VideoFrame videoFrame) {
-    encodeThreadChecker.checkIsOnValidThread();
+  private VideoCodecStatus encodeTextureBuffer(
+      VideoFrame videoFrame, VideoFrame.TextureBuffer textureBuffer) {
+    Matrix matrix = textureBuffer.getTransformMatrix();
+    float[] transformationMatrix = RendererCommon.convertMatrixFromAndroidGraphicsMatrix(matrix);
+
     try {
+      textureEglBase.makeCurrent();
       // TODO(perkj): glClear() shouldn't be necessary since every pixel is covered anyway,
       // but it's a workaround for bug webrtc:5147.
       GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-      // It is not necessary to release this frame because it doesn't own the buffer.
-      VideoFrame derotatedFrame =
-          new VideoFrame(videoFrame.getBuffer(), 0 /* rotation */, videoFrame.getTimestampNs());
-      videoFrameDrawer.drawFrame(derotatedFrame, textureDrawer, null /* additionalRenderMatrix */);
+      switch (textureBuffer.getType()) {
+        case OES:
+          textureDrawer.drawOes(textureBuffer.getTextureId(), transformationMatrix, width, height,
+              0, 0, width, height);
+          break;
+        case RGB:
+          textureDrawer.drawRgb(textureBuffer.getTextureId(), transformationMatrix, width, height,
+              0, 0, width, height);
+          break;
+      }
       textureEglBase.swapBuffers(videoFrame.getTimestampNs());
     } catch (RuntimeException e) {
       Logging.e(TAG, "encodeTexture failed", e);
+      // Keep the output builders in sync with buffers in the codec.
+      outputBuilders.pollLast();
       return VideoCodecStatus.ERROR;
     }
     return VideoCodecStatus.OK;
@@ -351,7 +343,6 @@
 
   private VideoCodecStatus encodeByteBuffer(
       VideoFrame videoFrame, VideoFrame.Buffer videoFrameBuffer, int bufferSize) {
-    encodeThreadChecker.checkIsOnValidThread();
     // Frame timestamp rounded to the nearest microsecond.
     long presentationTimestampUs = (videoFrame.getTimestampNs() + 500) / 1000;
 
@@ -361,13 +352,13 @@
       index = codec.dequeueInputBuffer(0 /* timeout */);
     } catch (IllegalStateException e) {
       Logging.e(TAG, "dequeueInputBuffer failed", e);
-      return VideoCodecStatus.ERROR;
+      return VideoCodecStatus.FALLBACK_SOFTWARE;
     }
 
     if (index == -1) {
       // Encoder is falling behind.  No input buffers available.  Drop the frame.
-      Logging.d(TAG, "Dropped frame, no input buffers available");
-      return VideoCodecStatus.NO_OUTPUT; // See webrtc bug 2887.
+      Logging.e(TAG, "Dropped frame, no input buffers available");
+      return VideoCodecStatus.OK; // See webrtc bug 2887.
     }
 
     ByteBuffer buffer;
@@ -377,13 +368,17 @@
       Logging.e(TAG, "getInputBuffers failed", e);
       return VideoCodecStatus.ERROR;
     }
-    yuvFormat.fillBuffer(buffer, videoFrameBuffer);
+    VideoFrame.I420Buffer i420 = videoFrameBuffer.toI420();
+    inputColorFormat.fillBufferFromI420(buffer, i420);
+    i420.release();
 
     try {
       codec.queueInputBuffer(
           index, 0 /* offset */, bufferSize, presentationTimestampUs, 0 /* flags */);
     } catch (IllegalStateException e) {
       Logging.e(TAG, "queueInputBuffer failed", e);
+      // Keep the output builders in sync with buffers in the codec.
+      outputBuilders.pollLast();
       // IllegalStateException thrown when the codec is in the wrong state.
       return VideoCodecStatus.ERROR;
     }
@@ -392,51 +387,43 @@
 
   @Override
   public VideoCodecStatus setChannelParameters(short packetLoss, long roundTripTimeMs) {
-    encodeThreadChecker.checkIsOnValidThread();
-    return VideoCodecStatus.OK; // No op.
-  }
-
-  @Override
-  public VideoCodecStatus setRateAllocation(BitrateAllocation bitrateAllocation, int framerate) {
-    encodeThreadChecker.checkIsOnValidThread();
-    if (framerate > MAX_VIDEO_FRAMERATE) {
-      framerate = MAX_VIDEO_FRAMERATE;
-    }
-    bitrateAdjuster.setTargets(bitrateAllocation.getSum(), framerate);
+    // No op.
     return VideoCodecStatus.OK;
   }
 
   @Override
+  public VideoCodecStatus setRateAllocation(BitrateAllocation bitrateAllocation, int framerate) {
+    if (framerate > MAX_VIDEO_FRAMERATE) {
+      framerate = MAX_VIDEO_FRAMERATE;
+    }
+    bitrateAdjuster.setTargets(bitrateAllocation.getSum(), framerate);
+    return updateBitrate();
+  }
+
+  @Override
   public ScalingSettings getScalingSettings() {
-    encodeThreadChecker.checkIsOnValidThread();
     return new ScalingSettings(automaticResizeOn);
   }
 
   @Override
   public String getImplementationName() {
-    encodeThreadChecker.checkIsOnValidThread();
     return "HardwareVideoEncoder: " + codecName;
   }
 
-  private VideoCodecStatus resetCodec(int newWidth, int newHeight, boolean newUseSurfaceMode) {
-    encodeThreadChecker.checkIsOnValidThread();
+  private VideoCodecStatus resetCodec(int newWidth, int newHeight) {
     VideoCodecStatus status = release();
     if (status != VideoCodecStatus.OK) {
       return status;
     }
-    width = newWidth;
-    height = newHeight;
-    useSurfaceMode = newUseSurfaceMode;
-    return initEncodeInternal();
+    // Zero bitrate and framerate indicate not to change the targets.
+    return initEncodeInternal(newWidth, newHeight, 0, 0, callback);
   }
 
   private boolean shouldForceKeyFrame(long presentationTimestampNs) {
-    encodeThreadChecker.checkIsOnValidThread();
     return forcedKeyFrameNs > 0 && presentationTimestampNs > lastKeyFrameNs + forcedKeyFrameNs;
   }
 
   private void requestKeyFrame(long presentationTimestampNs) {
-    encodeThreadChecker.checkIsOnValidThread();
     // Ideally MediaCodec would honor BUFFER_FLAG_SYNC_FRAME so we could
     // indicate this in queueInputBuffer() below and guarantee _this_ frame
     // be encoded as a key frame, but sadly that flag is ignored.  Instead,
@@ -465,7 +452,6 @@
   }
 
   private void deliverEncodedImage() {
-    outputThreadChecker.checkIsOnValidThread();
     try {
       MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
       int index = codec.dequeueOutputBuffer(info, DEQUEUE_OUTPUT_BUFFER_TIMEOUT_US);
@@ -487,12 +473,8 @@
           updateBitrate();
         }
 
-        final boolean isKeyFrame = (info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
-        if (isKeyFrame) {
-          Logging.d(TAG, "Sync frame generated");
-        }
-
-        final ByteBuffer frameBuffer;
+        ByteBuffer frameBuffer;
+        boolean isKeyFrame = (info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
         if (isKeyFrame && codecType == VideoCodecType.H264) {
           Logging.d(TAG,
               "Prepending config frame of size " + configBuffer.capacity()
@@ -507,10 +489,11 @@
         frameBuffer.put(codecOutputBuffer);
         frameBuffer.rewind();
 
-        final EncodedImage.FrameType frameType = isKeyFrame
-            ? EncodedImage.FrameType.VideoFrameKey
-            : EncodedImage.FrameType.VideoFrameDelta;
-
+        EncodedImage.FrameType frameType = EncodedImage.FrameType.VideoFrameDelta;
+        if (isKeyFrame) {
+          Logging.d(TAG, "Sync frame generated");
+          frameType = EncodedImage.FrameType.VideoFrameKey;
+        }
         EncodedImage.Builder builder = outputBuilders.poll();
         builder.setBuffer(frameBuffer).setFrameType(frameType);
         // TODO(mellem):  Set codec-specific info.
@@ -523,7 +506,6 @@
   }
 
   private void releaseCodecOnOutputThread() {
-    outputThreadChecker.checkIsOnValidThread();
     Logging.d(TAG, "Releasing MediaCodec on output thread");
     try {
       codec.stop();
@@ -537,12 +519,10 @@
       // Propagate exceptions caught during release back to the main thread.
       shutdownException = e;
     }
-    configBuffer = null;
     Logging.d(TAG, "Release on output thread done");
   }
 
   private VideoCodecStatus updateBitrate() {
-    outputThreadChecker.checkIsOnValidThread();
     adjustedBitrate = bitrateAdjuster.getAdjustedBitrateBps();
     try {
       Bundle params = new Bundle();
@@ -555,45 +535,37 @@
     }
   }
 
-  private boolean canUseSurface() {
-    return sharedContext != null && surfaceColorFormat != null;
-  }
-
   /**
-   * Enumeration of supported YUV color formats used for MediaCodec's input.
+   * Enumeration of supported color formats used for MediaCodec's input.
    */
-  private static enum YuvFormat {
+  private static enum ColorFormat {
     I420 {
       @Override
-      void fillBuffer(ByteBuffer inputBuffer, VideoFrame.Buffer buffer) {
-        VideoFrame.I420Buffer i420 = buffer.toI420();
-        inputBuffer.put(i420.getDataY());
-        inputBuffer.put(i420.getDataU());
-        inputBuffer.put(i420.getDataV());
-        i420.release();
+      void fillBufferFromI420(ByteBuffer buffer, VideoFrame.I420Buffer i420) {
+        buffer.put(i420.getDataY());
+        buffer.put(i420.getDataU());
+        buffer.put(i420.getDataV());
       }
     },
     NV12 {
       @Override
-      void fillBuffer(ByteBuffer inputBuffer, VideoFrame.Buffer buffer) {
-        VideoFrame.I420Buffer i420 = buffer.toI420();
-        inputBuffer.put(i420.getDataY());
+      void fillBufferFromI420(ByteBuffer buffer, VideoFrame.I420Buffer i420) {
+        buffer.put(i420.getDataY());
 
         // Interleave the bytes from the U and V portions, starting with U.
         ByteBuffer u = i420.getDataU();
         ByteBuffer v = i420.getDataV();
         int i = 0;
         while (u.hasRemaining() && v.hasRemaining()) {
-          inputBuffer.put(u.get());
-          inputBuffer.put(v.get());
+          buffer.put(u.get());
+          buffer.put(v.get());
         }
-        i420.release();
       }
     };
 
-    abstract void fillBuffer(ByteBuffer inputBuffer, VideoFrame.Buffer buffer);
+    abstract void fillBufferFromI420(ByteBuffer buffer, VideoFrame.I420Buffer i420);
 
-    static YuvFormat valueOf(int colorFormat) {
+    static ColorFormat valueOf(int colorFormat) {
       switch (colorFormat) {
         case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
           return I420;