Fix bugs in HardwareVideoDecoder reinitialization.

Fixes bug where callback is set to null on reinitialization. Also fixes
a race condition where callback can be null in onTextureFrameAvailable.

BUG=webrtc:8124

Review-Url: https://codereview.webrtc.org/3002093002
Cr-Original-Commit-Position: refs/heads/master@{#19493}
Cr-Mirrored-From: https://chromium.googlesource.com/external/webrtc
Cr-Mirrored-Commit: 6c36dcb324f8dbcefd906ff00c656ca60c9be348
diff --git a/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java b/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java
index ffa78ad..e6f2cfd 100644
--- a/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java
+++ b/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java
@@ -18,7 +18,7 @@
 import android.view.Surface;
 import java.io.IOException;
 import java.nio.ByteBuffer;
-import java.util.Deque;
+import java.util.concurrent.BlockingDeque;
 import java.util.concurrent.LinkedBlockingDeque;
 import org.webrtc.ThreadUtils.ThreadChecker;
 
@@ -64,11 +64,12 @@
     }
   }
 
-  private final Deque<FrameInfo> frameInfos;
+  private final BlockingDeque<FrameInfo> frameInfos;
   private int colorFormat;
 
   // Output thread runs a loop which polls MediaCodec for decoded output buffers.  It reformats
-  // those buffers into VideoFrames and delivers them to the callback.
+  // those buffers into VideoFrames and delivers them to the callback.  Variable is set on decoder
+  // thread and is immutable while the codec is running.
   private Thread outputThread;
 
   // Checker that ensures work is run on the output thread.
@@ -94,12 +95,15 @@
   private int sliceHeight;
 
   // Whether the decoder has finished the first frame.  The codec may not change output dimensions
-  // after delivering the first frame.
+  // after delivering the first frame.  Only accessed on the output thread while the decoder is
+  // running.
   private boolean hasDecodedFirstFrame;
-  // Whether the decoder has seen a key frame.  The first frame must be a key frame.
+  // Whether the decoder has seen a key frame.  The first frame must be a key frame.  Only accessed
+  // on the decoder thread.
   private boolean keyFrameRequired;
 
   private final EglBase.Context sharedContext;
+  // Valid and immutable while the decoder is running.
   private SurfaceTextureHelper surfaceTextureHelper;
   private Surface surface = null;
 
@@ -124,9 +128,11 @@
   // thread.
   private DecodedTextureMetadata renderedTextureMetadata;
 
-  // Decoding proceeds asynchronously.  This callback returns decoded frames to the caller.
+  // Decoding proceeds asynchronously.  This callback returns decoded frames to the caller.  Valid
+  // and immutable while the decoder is running.
   private Callback callback;
 
+  // Valid and immutable while the decoder is running.
   private MediaCodec codec = null;
 
   HardwareVideoDecoder(
@@ -144,11 +150,20 @@
   @Override
   public VideoCodecStatus initDecode(Settings settings, Callback callback) {
     this.decoderThreadChecker = new ThreadChecker();
-    return initDecodeInternal(settings.width, settings.height, callback);
+
+    this.callback = callback;
+    if (sharedContext != null) {
+      surfaceTextureHelper = SurfaceTextureHelper.create("decoder-texture-thread", sharedContext);
+      surface = new Surface(surfaceTextureHelper.getSurfaceTexture());
+      surfaceTextureHelper.startListening(this);
+    }
+    return initDecodeInternal(settings.width, settings.height);
   }
 
-  private VideoCodecStatus initDecodeInternal(int width, int height, Callback callback) {
+  // Internal variant is used when restarting the codec due to reconfiguration.
+  private VideoCodecStatus initDecodeInternal(int width, int height) {
     decoderThreadChecker.checkIsOnValidThread();
+    Logging.d(TAG, "initDecodeInternal");
     if (outputThread != null) {
       Logging.e(TAG, "initDecodeInternal called while the codec is already running");
       return VideoCodecStatus.ERROR;
@@ -156,7 +171,6 @@
 
     // Note:  it is not necessary to initialize dimensions under the lock, since the output thread
     // is not running.
-    this.callback = callback;
     this.width = width;
     this.height = height;
 
@@ -175,10 +189,6 @@
       MediaFormat format = MediaFormat.createVideoFormat(codecType.mimeType(), width, height);
       if (sharedContext == null) {
         format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
-      } else {
-        surfaceTextureHelper = SurfaceTextureHelper.create("decoder-texture-thread", sharedContext);
-        surface = new Surface(surfaceTextureHelper.getSurfaceTexture());
-        surfaceTextureHelper.startListening(this);
       }
       codec.configure(format, surface, null, 0);
       codec.start();
@@ -187,11 +197,11 @@
       release();
       return VideoCodecStatus.ERROR;
     }
-
     running = true;
     outputThread = createOutputThread();
     outputThread.start();
 
+    Logging.d(TAG, "initDecodeInternal done");
     return VideoCodecStatus.OK;
   }
 
@@ -199,6 +209,7 @@
   public VideoCodecStatus decode(EncodedImage frame, DecodeInfo info) {
     decoderThreadChecker.checkIsOnValidThread();
     if (codec == null || callback == null) {
+      Logging.d(TAG, "decode uninitalized, codec: " + codec + ", callback: " + callback);
       return VideoCodecStatus.UNINITIALIZED;
     }
 
@@ -299,6 +310,22 @@
     // TODO(sakal): This is not called on the correct thread but is still called synchronously.
     // Re-enable the check once this is called on the correct thread.
     // decoderThreadChecker.checkIsOnValidThread();
+    Logging.d(TAG, "release");
+    VideoCodecStatus status = releaseInternal();
+    if (surface != null) {
+      surface.release();
+      surface = null;
+      surfaceTextureHelper.stopListening();
+      surfaceTextureHelper.dispose();
+      surfaceTextureHelper = null;
+    }
+    callback = null;
+    frameInfos.clear();
+    return status;
+  }
+
+  // Internal variant is used when restarting the codec due to reconfiguration.
+  private VideoCodecStatus releaseInternal() {
     if (!running) {
       Logging.d(TAG, "release: Decoder is not running.");
       return VideoCodecStatus.OK;
@@ -320,27 +347,18 @@
       }
     } finally {
       codec = null;
-      callback = null;
       outputThread = null;
-      frameInfos.clear();
-      if (surface != null) {
-        surface.release();
-        surface = null;
-        surfaceTextureHelper.stopListening();
-        surfaceTextureHelper.dispose();
-        surfaceTextureHelper = null;
-      }
     }
     return VideoCodecStatus.OK;
   }
 
   private VideoCodecStatus reinitDecode(int newWidth, int newHeight) {
     decoderThreadChecker.checkIsOnValidThread();
-    VideoCodecStatus status = release();
+    VideoCodecStatus status = releaseInternal();
     if (status != VideoCodecStatus.OK) {
       return status;
     }
-    return initDecodeInternal(newWidth, newHeight, callback);
+    return initDecodeInternal(newWidth, newHeight);
   }
 
   private Thread createOutputThread() {
@@ -646,10 +664,6 @@
       // Propagate exceptions caught during release back to the main thread.
       shutdownException = e;
     }
-    codec = null;
-    callback = null;
-    outputThread = null;
-    frameInfos.clear();
     Logging.d(TAG, "Release on output thread done");
   }