Stop AndroidVideoCapturer asynchronously.
The purpose is to avoid a deadlock between the C++ thread calling Stop and the Java thread that provides video frames.

BUG=4318
R=glaznev@webrtc.org, magjed@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/35249004

Cr-Commit-Position: refs/heads/master@{#8425}
git-svn-id: http://webrtc.googlecode.com/svn/trunk@8425 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/app/webrtc/java/jni/androidvideocapturer_jni.cc b/talk/app/webrtc/java/jni/androidvideocapturer_jni.cc
index 4f575b1..6e9710f 100644
--- a/talk/app/webrtc/java/jni/androidvideocapturer_jni.cc
+++ b/talk/app/webrtc/java/jni/androidvideocapturer_jni.cc
@@ -29,11 +29,70 @@
 #include "talk/app/webrtc/java/jni/androidvideocapturer_jni.h"
 
 #include "talk/app/webrtc/java/jni/classreferenceholder.h"
+#include "webrtc/base/bind.h"
 
 namespace webrtc_jni {
 
 jobject AndroidVideoCapturerJni::application_context_ = nullptr;
 
+// JavaCaptureProxy is responsible for marshaling calls from the
+// Java VideoCapturerAndroid to the C++ class AndroidVideoCapturer.
+// Calls from Java occur on a Java thread and are marshaled to
+// AndroidVideoCapturer on the thread that creates an instance of this object.
+//
+// An instance is created when AndroidVideoCapturerJni::Start is called and
+// ownership is passed to an instance of the Java class NativeObserver.
+// JavaCaptureProxy is destroyed when NativeObserver has reported that the
+// capturer has stopped, see
+// VideoCapturerAndroid_00024NativeObserver_nativeCapturerStopped.
+// Marshaling is done as long as JavaCaptureProxy has a pointer to the
+// AndroidVideoCapturer.
+class JavaCaptureProxy {
+ public:
+  JavaCaptureProxy() : thread_(rtc::Thread::Current()), capturer_(nullptr) {
+  }
+
+  ~JavaCaptureProxy() {
+  }
+
+  void SetAndroidCapturer(webrtc::AndroidVideoCapturer* capturer) {
+    DCHECK(thread_->IsCurrent());
+    capturer_ = capturer;
+  }
+
+  void OnCapturerStarted(bool success) {
+    thread_->Invoke<void>(
+        rtc::Bind(&JavaCaptureProxy::OnCapturerStarted_w, this, success));
+  }
+
+  void OnIncomingFrame(signed char* video_frame,
+                       int length,
+                       int rotation,
+                       int64 time_stamp) {
+    thread_->Invoke<void>(
+        rtc::Bind(&JavaCaptureProxy::OnIncomingFrame_w, this, video_frame,
+                  length, rotation, time_stamp));
+  }
+
+ private:
+  void OnCapturerStarted_w(bool success) {
+    DCHECK(thread_->IsCurrent());
+    if (capturer_)
+      capturer_->OnCapturerStarted(success);
+  }
+  void OnIncomingFrame_w(signed char* video_frame,
+                         int length,
+                         int rotation,
+                         int64 time_stamp) {
+    DCHECK(thread_->IsCurrent());
+    if (capturer_)
+      capturer_->OnIncomingFrame(video_frame, length, rotation, time_stamp);
+  }
+
+  rtc::Thread* thread_;
+  webrtc::AndroidVideoCapturer* capturer_;
+};
+
 // static
 int AndroidVideoCapturerJni::SetAndroidObjects(JNIEnv* jni,
                                                jobject appliction_context) {
@@ -50,24 +109,50 @@
     : j_capturer_global_(jni, j_video_capturer),
       j_video_capturer_class_(
           jni, FindClass(jni, "org/webrtc/VideoCapturerAndroid")),
-      j_frame_observer_class_(
+      j_observer_class_(
           jni,
           FindClass(jni,
-                    "org/webrtc/VideoCapturerAndroid$NativeFrameObserver")) {
-  }
+                    "org/webrtc/VideoCapturerAndroid$NativeObserver")),
+      proxy_(nullptr) {
+  thread_checker_.DetachFromThread();
+}
 
-AndroidVideoCapturerJni::~AndroidVideoCapturerJni() {}
+bool AndroidVideoCapturerJni::Init(jstring device_name) {
+  const jmethodID m(GetMethodID(
+        jni(), *j_video_capturer_class_, "init", "(Ljava/lang/String;)Z"));
+  if (!jni()->CallBooleanMethod(*j_capturer_global_, m, device_name)) {
+    return false;
+  }
+  CHECK_EXCEPTION(jni()) << "error during CallVoidMethod";
+  return true;
+}
+
+AndroidVideoCapturerJni::~AndroidVideoCapturerJni() {
+  DeInit();
+}
+
+void AndroidVideoCapturerJni::DeInit() {
+  DCHECK(proxy_ == nullptr);
+  jmethodID m = GetMethodID(jni(), *j_video_capturer_class_, "deInit", "()V");
+  jni()->CallVoidMethod(*j_capturer_global_, m);
+  CHECK_EXCEPTION(jni()) << "error during VideoCapturerAndroid.DeInit";
+}
 
 void AndroidVideoCapturerJni::Start(int width, int height, int framerate,
                                     webrtc::AndroidVideoCapturer* capturer) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(proxy_ == nullptr);
+  proxy_ = new JavaCaptureProxy();
+  proxy_->SetAndroidCapturer(capturer);
+
   j_frame_observer_ = NewGlobalRef(
       jni(),
-      jni()->NewObject(*j_frame_observer_class_,
+      jni()->NewObject(*j_observer_class_,
                        GetMethodID(jni(),
-                                   *j_frame_observer_class_,
+                                   *j_observer_class_,
                                    "<init>",
                                    "(J)V"),
-                                   jlongFromPointer(capturer)));
+                                   jlongFromPointer(proxy_)));
   CHECK_EXCEPTION(jni()) << "error during NewObject";
 
   jmethodID m = GetMethodID(
@@ -82,13 +167,15 @@
   CHECK_EXCEPTION(jni()) << "error during VideoCapturerAndroid.startCapture";
 }
 
-bool AndroidVideoCapturerJni::Stop() {
+void AndroidVideoCapturerJni::Stop() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  proxy_->SetAndroidCapturer(nullptr);
+  proxy_ = nullptr;
   jmethodID m = GetMethodID(jni(), *j_video_capturer_class_,
-                            "stopCapture", "()Z");
-  jboolean result = jni()->CallBooleanMethod(*j_capturer_global_, m);
+                            "stopCapture", "()V");
+  jni()->CallVoidMethod(*j_capturer_global_, m);
   CHECK_EXCEPTION(jni()) << "error during VideoCapturerAndroid.stopCapture";
   DeleteGlobalRef(jni(), j_frame_observer_);
-  return result;
 }
 
 std::string AndroidVideoCapturerJni::GetSupportedFormats() {
@@ -103,20 +190,27 @@
 
 JNIEnv* AndroidVideoCapturerJni::jni() { return AttachCurrentThreadIfNeeded(); }
 
-JOW(void, VideoCapturerAndroid_00024NativeFrameObserver_nativeOnFrameCaptured)
-    (JNIEnv* jni, jclass, jlong j_capturer, jbyteArray j_frame,
+JOW(void, VideoCapturerAndroid_00024NativeObserver_nativeOnFrameCaptured)
+    (JNIEnv* jni, jclass, jlong j_proxy, jbyteArray j_frame,
         jint rotation, jlong ts) {
   jbyte* bytes = jni->GetByteArrayElements(j_frame, NULL);
-  reinterpret_cast<webrtc::AndroidVideoCapturer*>(
-      j_capturer)->OnIncomingFrame(bytes, jni->GetArrayLength(j_frame),
-                                   rotation, ts);
+  reinterpret_cast<JavaCaptureProxy*>(
+      j_proxy)->OnIncomingFrame(bytes, jni->GetArrayLength(j_frame), rotation,
+                                ts);
   jni->ReleaseByteArrayElements(j_frame, bytes, JNI_ABORT);
 }
 
-JOW(void, VideoCapturerAndroid_00024NativeFrameObserver_nativeCapturerStarted)
-    (JNIEnv* jni, jclass, jlong j_capturer, jboolean j_success) {
-  reinterpret_cast<webrtc::AndroidVideoCapturer*>(
-      j_capturer)->OnCapturerStarted(j_success);
+JOW(void, VideoCapturerAndroid_00024NativeObserver_nativeCapturerStarted)
+    (JNIEnv* jni, jclass, jlong j_proxy, jboolean j_success) {
+  JavaCaptureProxy* proxy = reinterpret_cast<JavaCaptureProxy*>(j_proxy);
+  proxy->OnCapturerStarted(j_success);
+  if (!j_success)
+    delete proxy;
+}
+
+JOW(void, VideoCapturerAndroid_00024NativeObserver_nativeCapturerStopped)
+    (JNIEnv* jni, jclass, jlong j_proxy) {
+  delete reinterpret_cast<JavaCaptureProxy*>(j_proxy);
 }
 
 }  // namespace webrtc_jni
diff --git a/talk/app/webrtc/java/jni/androidvideocapturer_jni.h b/talk/app/webrtc/java/jni/androidvideocapturer_jni.h
index 846905a..33c586f 100644
--- a/talk/app/webrtc/java/jni/androidvideocapturer_jni.h
+++ b/talk/app/webrtc/java/jni/androidvideocapturer_jni.h
@@ -33,9 +33,12 @@
 
 #include "talk/app/webrtc/androidvideocapturer.h"
 #include "talk/app/webrtc/java/jni/jni_helpers.h"
+#include "webrtc/base/thread_checker.h"
 
 namespace webrtc_jni {
 
+class JavaCaptureProxy;
+
 // AndroidVideoCapturerJni implements AndroidVideoCapturerDelegate.
 // The purpose of the delegate is to hide the JNI specifics from the C++ only
 // AndroidVideoCapturer.
@@ -45,20 +48,29 @@
   AndroidVideoCapturerJni(JNIEnv* jni, jobject j_video_capturer);
   ~AndroidVideoCapturerJni();
 
+  bool Init(jstring device_name);
+
   void Start(int width, int height, int framerate,
              webrtc::AndroidVideoCapturer* capturer) override;
-  bool Stop() override;
+  void Stop() override;
 
   std::string GetSupportedFormats() override;
 
  private:
   JNIEnv* jni();
+  void DeInit();
 
   const ScopedGlobalRef<jobject> j_capturer_global_;
   const ScopedGlobalRef<jclass> j_video_capturer_class_;
-  const ScopedGlobalRef<jclass> j_frame_observer_class_;
+  const ScopedGlobalRef<jclass> j_observer_class_;
   jobject j_frame_observer_;
 
+  rtc::ThreadChecker thread_checker_;
+
+  // The proxy is a valid pointer between calling Start and Stop.
+  // It destroys itself when Java VideoCapturerAndroid has been stopped.
+  JavaCaptureProxy* proxy_;
+
   static jobject application_context_;
 
   DISALLOW_COPY_AND_ASSIGN(AndroidVideoCapturerJni);
diff --git a/talk/app/webrtc/java/jni/classreferenceholder.cc b/talk/app/webrtc/java/jni/classreferenceholder.cc
index 25a1101..8c30a0a 100644
--- a/talk/app/webrtc/java/jni/classreferenceholder.cc
+++ b/talk/app/webrtc/java/jni/classreferenceholder.cc
@@ -72,7 +72,7 @@
 #if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
   LoadClass(jni, "android/graphics/SurfaceTexture");
   LoadClass(jni, "org/webrtc/VideoCapturerAndroid");
-  LoadClass(jni, "org/webrtc/VideoCapturerAndroid$NativeFrameObserver");
+  LoadClass(jni, "org/webrtc/VideoCapturerAndroid$NativeObserver");
   LoadClass(jni, "org/webrtc/MediaCodecVideoEncoder");
   LoadClass(jni, "org/webrtc/MediaCodecVideoEncoder$OutputBufferInfo");
   LoadClass(jni, "org/webrtc/MediaCodecVideoDecoder");
diff --git a/talk/app/webrtc/java/jni/peerconnection_jni.cc b/talk/app/webrtc/java/jni/peerconnection_jni.cc
index d210054..b8d1311 100644
--- a/talk/app/webrtc/java/jni/peerconnection_jni.cc
+++ b/talk/app/webrtc/java/jni/peerconnection_jni.cc
@@ -1385,15 +1385,10 @@
                                             j_videocapturer_ctor);
   CHECK_EXCEPTION(jni) << "error during NewObject";
 
-  const jmethodID m(GetMethodID(
-      jni, j_video_capturer_class, "Init", "(Ljava/lang/String;)Z"));
-  if (!jni->CallBooleanMethod(j_video_capturer, m, j_device_name)) {
-    return nullptr;
-  }
-  CHECK_EXCEPTION(jni) << "error during CallVoidMethod";
-
-  rtc::scoped_ptr<webrtc::AndroidVideoCapturerDelegate> delegate(
+  rtc::scoped_ptr<AndroidVideoCapturerJni> delegate(
       new AndroidVideoCapturerJni(jni, j_video_capturer));
+  if (!delegate->Init(j_device_name))
+    return nullptr;
   rtc::scoped_ptr<webrtc::AndroidVideoCapturer> capturer(
       new webrtc::AndroidVideoCapturer(delegate.Pass()));
 
diff --git a/talk/app/webrtc/java/src/org/webrtc/VideoCapturerAndroid.java b/talk/app/webrtc/java/src/org/webrtc/VideoCapturerAndroid.java
index 2650e71..c68e270 100644
--- a/talk/app/webrtc/java/src/org/webrtc/VideoCapturerAndroid.java
+++ b/talk/app/webrtc/java/src/org/webrtc/VideoCapturerAndroid.java
@@ -178,30 +178,50 @@
 
   // Called by native code.
   // Enumerates resolution and frame rates for all cameras to be able to switch
-  // cameras. Initializes local variables for the camera named |deviceName|.
+  // cameras. Initializes local variables for the camera named |deviceName| and
+  // starts a thread to be used for capturing.
   // If deviceName is empty, the first available device is used in order to be
   // compatible with the generic VideoCapturer class.
-  boolean Init(String deviceName) {
-    Log.e(TAG, "Init " + deviceName);
-    if (!InitStatics())
+  boolean init(String deviceName) {
+    Log.d(TAG, "init " + deviceName);
+    if (!initStatics())
       return false;
 
+    boolean foundDevice = false;
     if (deviceName.isEmpty()) {
       this.id = 0;
-      return true;
-    }
-
-    Boolean foundDevice = false;
-    for (int i = 0; i < Camera.getNumberOfCameras(); ++i) {
-      if (deviceName.equals(getDeviceName(i))) {
-        this.id = i;
-        foundDevice = true;
+      foundDevice = true;
+    } else {
+      for (int i = 0; i < Camera.getNumberOfCameras(); ++i) {
+        if (deviceName.equals(getDeviceName(i))) {
+          this.id = i;
+          foundDevice = true;
+        }
       }
     }
+    Exchanger<Handler> handlerExchanger = new Exchanger<Handler>();
+    cameraThread = new CameraThread(handlerExchanger);
+    cameraThread.start();
+    cameraThreadHandler = exchange(handlerExchanger, null);
     return foundDevice;
   }
 
-  private static boolean InitStatics() {
+  // Called by native code. Frees the Java thread created in Init.
+  void deInit() throws InterruptedException {
+    Log.d(TAG, "deInit");
+    if (cameraThreadHandler != null) {
+      cameraThreadHandler.post(new Runnable() {
+        @Override public void run() {
+          Log.d(TAG, "stop CameraThread");
+          Looper.myLooper().quit();
+        }
+      });
+      cameraThread.join();
+      cameraThreadHandler = null;
+    }
+  }
+
+  private static boolean initStatics() {
     if (supportedFormats != null)
       return true;
     try {
@@ -298,20 +318,15 @@
       final Context applicationContext, final CapturerObserver frameObserver) {
     Log.d(TAG, "startCapture requested: " + width + "x" + height
         + "@" + framerate);
-    if (cameraThread != null || cameraThreadHandler != null) {
-      throw new RuntimeException("Camera thread already started!");
-    }
     if (applicationContext == null) {
       throw new RuntimeException("applicationContext not set.");
     }
     if (frameObserver == null) {
       throw new RuntimeException("frameObserver not set.");
     }
-
-    Exchanger<Handler> handlerExchanger = new Exchanger<Handler>();
-    cameraThread = new CameraThread(handlerExchanger);
-    cameraThread.start();
-    cameraThreadHandler = exchange(handlerExchanger, null);
+    this.width = width;
+    this.height = height;
+    this.framerate = framerate;
 
     cameraThreadHandler.post(new Runnable() {
       @Override public void run() {
@@ -327,9 +342,6 @@
     Throwable error = null;
     this.applicationContext = applicationContext;
     this.frameObserver = frameObserver;
-    this.width = width;
-    this.height = height;
-    this.framerate = framerate;
     try {
       this.camera = Camera.open(id);
       this.info = new Camera.CameraInfo();
@@ -406,8 +418,7 @@
     }
     Log.e(TAG, "startCapture failed", error);
     if (camera != null) {
-      Exchanger<Boolean> resultDropper = new Exchanger<Boolean>();
-      stopCaptureOnCameraThread(resultDropper);
+      stopCaptureOnCameraThread();
       frameObserver.OnCapturerStarted(false);
     }
     frameObserver.OnCapturerStarted(false);
@@ -415,37 +426,19 @@
   }
 
   // Called by native code.  Returns true when camera is known to be stopped.
-  synchronized boolean stopCapture() {
+  synchronized void stopCapture() {
     Log.d(TAG, "stopCapture");
-    final Exchanger<Boolean> result = new Exchanger<Boolean>();
     cameraThreadHandler.post(new Runnable() {
         @Override public void run() {
-          stopCaptureOnCameraThread(result);
+          stopCaptureOnCameraThread();
         }
     });
-    boolean status = exchange(result, false);  // |false| is a dummy value here.
-    Log.d(TAG, "stopCapture wait");
-    try {
-      cameraThread.join();
-    } catch (InterruptedException e) {
-      throw new RuntimeException(e);
-    }
-    cameraThreadHandler = null;
-    cameraThread = null;
-    Log.d(TAG, "stopCapture done");
-    return status;
   }
 
-  private void stopCaptureOnCameraThread(Exchanger<Boolean> result) {
+  private void stopCaptureOnCameraThread() {
     Log.d(TAG, "stopCaptureOnCameraThread");
-    if (camera == null) {
-      throw new RuntimeException("Camera is already stopped!");
-    }
-    frameObserver = null;
-
     doStopCaptureOnCamerathread();
-    exchange(result, true);
-    Looper.myLooper().quit();
+    frameObserver.OnCapturerStopped();
     return;
   }
 
@@ -570,9 +563,13 @@
 
   // Interface used for providing callbacks to an observer.
   interface CapturerObserver {
-    // Notify if the camera have beens started successfully or not.
+    // Notify if the camera have been started successfully or not.
     // Called on a Java thread owned by VideoCapturerAndroid.
     abstract void OnCapturerStarted(boolean success);
+
+    // Notify that the camera have been stopped.
+    // Called on a Java thread owned by VideoCapturerAndroid.
+    abstract void OnCapturerStopped();
     // Delivers a captured frame. Called on a Java thread owned by
     // VideoCapturerAndroid.
     abstract void OnFrameCaptured(byte[] data, int rotation, long timeStamp);
@@ -580,27 +577,32 @@
 
   // An implementation of CapturerObserver that forwards all calls from
   // Java to the C layer.
-  public static class NativeFrameObserver implements CapturerObserver {
-    private final long nativeCapturer;
+  public static class NativeObserver implements CapturerObserver {
+    private final long nativeProxy;
 
-    public NativeFrameObserver(long nativeCapturer) {
-      this.nativeCapturer = nativeCapturer;
+    public NativeObserver(long nativeProxy) {
+      this.nativeProxy = nativeProxy;
     }
 
     @Override
     public void OnFrameCaptured(byte[] data, int rotation, long timeStamp) {
-      nativeOnFrameCaptured(nativeCapturer, data, rotation, timeStamp);
+      nativeOnFrameCaptured(nativeProxy, data, rotation, timeStamp);
     }
 
-    private native void nativeOnFrameCaptured(
-        long captureObject, byte[] data, int rotation, long timeStamp);
-
     @Override
     public void OnCapturerStarted(boolean success) {
-      nativeCapturerStarted(nativeCapturer, success);
+      nativeCapturerStarted(nativeProxy, success);
     }
 
-    private native void nativeCapturerStarted(long captureObject,
+    @Override
+    public void OnCapturerStopped() {
+      nativeCapturerStopped(nativeProxy);
+    }
+
+    private native void nativeCapturerStarted(long proxyObject,
         boolean success);
+    private native void nativeCapturerStopped(long proxyObject);
+    private native void nativeOnFrameCaptured(
+        long proxyObject, byte[] data, int rotation, long timeStamp);
   }
 }