| Index: talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java
 | 
| diff --git a/talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java b/talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java
 | 
| index ef2055645d9ad218231fcbbb630ec7716af62e28..5312fe39a891c8a91a1b92a092fd0f7b7937fe73 100644
 | 
| --- a/talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java
 | 
| +++ b/talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java
 | 
| @@ -27,7 +27,6 @@
 | 
|  
 | 
|  package org.webrtc;
 | 
|  
 | 
| -import android.graphics.SurfaceTexture;
 | 
|  import android.media.MediaCodec;
 | 
|  import android.media.MediaCodecInfo;
 | 
|  import android.media.MediaCodecInfo.CodecCapabilities;
 | 
| @@ -42,8 +41,9 @@ import android.view.Surface;
 | 
|  import org.webrtc.Logging;
 | 
|  
 | 
|  import java.nio.ByteBuffer;
 | 
| -import java.util.List;
 | 
|  import java.util.Arrays;
 | 
| +import java.util.List;
 | 
| +import java.util.concurrent.TimeUnit;
 | 
|  
 | 
|  // Java-side of peerconnection_jni.cc:MediaCodecVideoDecoder.
 | 
|  // This class is an implementation detail of the Java PeerConnection API.
 | 
| @@ -93,10 +93,11 @@ public class MediaCodecVideoDecoder {
 | 
|    private int stride;
 | 
|    private int sliceHeight;
 | 
|    private boolean useSurface;
 | 
| -  private int textureID = 0;
 | 
| -  private SurfaceTexture surfaceTexture = null;
 | 
| +  // |isWaitingForTexture| is true when waiting for the transition:
 | 
| +  // MediaCodec.releaseOutputBuffer() -> onTextureFrameAvailable().
 | 
| +  private boolean isWaitingForTexture = false;
 | 
| +  private TextureListener textureListener;
 | 
|    private Surface surface = null;
 | 
| -  private EglBase eglBase;
 | 
|  
 | 
|    private MediaCodecVideoDecoder() { }
 | 
|  
 | 
| @@ -180,12 +181,13 @@ public class MediaCodecVideoDecoder {
 | 
|      }
 | 
|    }
 | 
|  
 | 
| -  // Pass null in |sharedContext| to configure the codec for ByteBuffer output.
 | 
| -  private boolean initDecode(VideoCodecType type, int width, int height, EGLContext sharedContext) {
 | 
| +  // Pass null in |surfaceTextureHelper| to configure the codec for ByteBuffer output.
 | 
| +  private boolean initDecode(
 | 
| +      VideoCodecType type, int width, int height, SurfaceTextureHelper surfaceTextureHelper) {
 | 
|      if (mediaCodecThread != null) {
 | 
|        throw new RuntimeException("Forgot to release()?");
 | 
|      }
 | 
| -    useSurface = (sharedContext != null);
 | 
| +    useSurface = (surfaceTextureHelper != null);
 | 
|      String mime = null;
 | 
|      String[] supportedCodecPrefixes = null;
 | 
|      if (type == VideoCodecType.VIDEO_CODEC_VP8) {
 | 
| @@ -204,9 +206,6 @@ public class MediaCodecVideoDecoder {
 | 
|      Logging.d(TAG, "Java initDecode: " + type + " : "+ width + " x " + height +
 | 
|          ". Color: 0x" + Integer.toHexString(properties.colorFormat) +
 | 
|          ". Use Surface: " + useSurface);
 | 
| -    if (sharedContext != null) {
 | 
| -      Logging.d(TAG, "Decoder shared EGL Context: " + sharedContext);
 | 
| -    }
 | 
|      mediaCodecThread = Thread.currentThread();
 | 
|      try {
 | 
|        this.width = width;
 | 
| @@ -215,16 +214,8 @@ public class MediaCodecVideoDecoder {
 | 
|        sliceHeight = height;
 | 
|  
 | 
|        if (useSurface) {
 | 
| -        // Create shared EGL context.
 | 
| -        eglBase = new EglBase(sharedContext, EglBase.ConfigType.PIXEL_BUFFER);
 | 
| -        eglBase.createDummyPbufferSurface();
 | 
| -        eglBase.makeCurrent();
 | 
| -
 | 
| -        // Create output surface
 | 
| -        textureID = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
 | 
| -        Logging.d(TAG, "Video decoder TextureID = " + textureID);
 | 
| -        surfaceTexture = new SurfaceTexture(textureID);
 | 
| -        surface = new Surface(surfaceTexture);
 | 
| +        textureListener = new TextureListener(surfaceTextureHelper);
 | 
| +        surface = new Surface(surfaceTextureHelper.getSurfaceTexture());
 | 
|        }
 | 
|  
 | 
|        MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
 | 
| @@ -265,11 +256,7 @@ public class MediaCodecVideoDecoder {
 | 
|      if (useSurface) {
 | 
|        surface.release();
 | 
|        surface = null;
 | 
| -      Logging.d(TAG, "Delete video decoder TextureID " + textureID);
 | 
| -      GLES20.glDeleteTextures(1, new int[] {textureID}, 0);
 | 
| -      textureID = 0;
 | 
| -      eglBase.release();
 | 
| -      eglBase = null;
 | 
| +      textureListener.release();
 | 
|      }
 | 
|    }
 | 
|  
 | 
| @@ -317,11 +304,72 @@ public class MediaCodecVideoDecoder {
 | 
|  
 | 
|    private static class DecodedTextureBuffer {
 | 
|      private final int textureID;
 | 
| -    private final long presentationTimestampUs;
 | 
| +    private final float[] transformMatrix;
 | 
| +    private final long timestampNs;
 | 
|  
 | 
| -    public DecodedTextureBuffer(int textureID, long presentationTimestampUs) {
 | 
| +    public DecodedTextureBuffer(int textureID, float[] transformMatrix, long timestampNs) {
 | 
|        this.textureID = textureID;
 | 
| -      this.presentationTimestampUs = presentationTimestampUs;
 | 
| +      this.transformMatrix = transformMatrix;
 | 
| +      this.timestampNs = timestampNs;
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  // Poll based texture listener.
 | 
| +  private static class TextureListener
 | 
| +      implements SurfaceTextureHelper.OnTextureFrameAvailableListener {
 | 
| +    private final SurfaceTextureHelper surfaceTextureHelper;
 | 
| +    private DecodedTextureBuffer textureBuffer;
 | 
| +    // |newFrameLock| is used to synchronize arrival of new frames with wait()/notifyAll().
 | 
| +    private final Object newFrameLock = new Object();
 | 
| +
 | 
| +    public TextureListener(SurfaceTextureHelper surfaceTextureHelper) {
 | 
| +      this.surfaceTextureHelper = surfaceTextureHelper;
 | 
| +      surfaceTextureHelper.setListener(this);
 | 
| +    }
 | 
| +
 | 
| +    // Callback from |surfaceTextureHelper|. May be called on an arbitrary thread.
 | 
| +    @Override
 | 
| +    public void onTextureFrameAvailable(
 | 
| +        int oesTextureId, float[] transformMatrix, long timestampNs) {
 | 
| +      synchronized (newFrameLock) {
 | 
| +        if (textureBuffer != null) {
 | 
| +          Logging.e(TAG,
 | 
| +              "Unexpected onTextureFrameAvailable() called while already holding a texture.");
 | 
| +          throw new IllegalStateException("Already holding a texture.");
 | 
| +        }
 | 
| +        textureBuffer = new DecodedTextureBuffer(oesTextureId, transformMatrix, timestampNs);
 | 
| +        newFrameLock.notifyAll();
 | 
| +      }
 | 
| +    }
 | 
| +
 | 
| +    // Dequeues and returns a texture buffer if available, or null otherwise.
 | 
| +    public DecodedTextureBuffer dequeueTextureFrame(int timeoutMs) {
 | 
| +      synchronized (newFrameLock) {
 | 
| +        if (textureBuffer == null && timeoutMs > 0) {
 | 
| +          try {
 | 
| +            newFrameLock.wait(timeoutMs);
 | 
| +          } catch(InterruptedException e) {
 | 
| +            // Restore the interrupted status by reinterrupting the thread.
 | 
| +            Thread.currentThread().interrupt();
 | 
| +          }
 | 
| +        }
 | 
| +        final DecodedTextureBuffer textureBuffer = this.textureBuffer;
 | 
| +        this.textureBuffer = null;
 | 
| +        return textureBuffer;
 | 
| +      }
 | 
| +    }
 | 
| +
 | 
| +    public void release() {
 | 
| +      // SurfaceTextureHelper.disconnect() will block until any onTextureFrameAvailable() in
 | 
| +      // progress is done. Therefore, the call to disconnect() must be outside any synchronized
 | 
| +      // statement that is also used in the onTextureFrameAvailable() above to avoid deadlocks.
 | 
| +      surfaceTextureHelper.disconnect();
 | 
| +      synchronized (newFrameLock) {
 | 
| +        if (textureBuffer != null) {
 | 
| +          surfaceTextureHelper.returnTextureFrame();
 | 
| +          textureBuffer = null;
 | 
| +        }
 | 
| +      }
 | 
|      }
 | 
|    }
 | 
|  
 | 
| @@ -330,14 +378,25 @@ public class MediaCodecVideoDecoder {
 | 
|    // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an
 | 
|    // unsupported format, or if |mediaCodec| is not in the Executing state. Throws CodecException
 | 
|    // upon codec error.
 | 
| -  private Object dequeueOutputBuffer(int dequeueTimeoutUs)
 | 
| +  private Object dequeueOutputBuffer(int dequeueTimeoutMs)
 | 
|        throws IllegalStateException, MediaCodec.CodecException {
 | 
|      checkOnMediaCodecThread();
 | 
| +    // Calling multiple MediaCodec.releaseOutputBuffer() with render=true in a row will result in
 | 
| +    // dropped texture frames. Therefore, wait for any pending onTextureFrameAvailable() before
 | 
| +    // proceeding.
 | 
| +    if (isWaitingForTexture) {
 | 
| +      final DecodedTextureBuffer textureBuffer =
 | 
| +          textureListener.dequeueTextureFrame(dequeueTimeoutMs);
 | 
| +      isWaitingForTexture = (textureBuffer == null);
 | 
| +      return textureBuffer;
 | 
| +    }
 | 
| +
 | 
|      // Drain the decoder until receiving a decoded buffer or hitting
 | 
|      // MediaCodec.INFO_TRY_AGAIN_LATER.
 | 
|      final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
 | 
|      while (true) {
 | 
| -      final int result = mediaCodec.dequeueOutputBuffer(info, dequeueTimeoutUs);
 | 
| +      final int result = mediaCodec.dequeueOutputBuffer(
 | 
| +          info, TimeUnit.MILLISECONDS.toMicros(dequeueTimeoutMs));
 | 
|        switch (result) {
 | 
|          case MediaCodec.INFO_TRY_AGAIN_LATER:
 | 
|            return null;
 | 
| @@ -371,9 +430,10 @@ public class MediaCodecVideoDecoder {
 | 
|            // Output buffer decoded.
 | 
|            if (useSurface) {
 | 
|              mediaCodec.releaseOutputBuffer(result, true /* render */);
 | 
| -            // TODO(magjed): Wait for SurfaceTexture.onFrameAvailable() before returning a texture
 | 
| -            // frame.
 | 
| -            return new DecodedTextureBuffer(textureID, info.presentationTimeUs);
 | 
| +            final DecodedTextureBuffer textureBuffer =
 | 
| +                textureListener.dequeueTextureFrame(dequeueTimeoutMs);
 | 
| +            isWaitingForTexture = (textureBuffer == null);
 | 
| +            return textureBuffer;
 | 
|            } else {
 | 
|              return new DecodedByteBuffer(result, info.offset, info.size, info.presentationTimeUs);
 | 
|            }
 | 
| 
 |