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/androidtests/src/org/webrtc/VideoCapturerAndroidTest.java b/talk/app/webrtc/androidtests/src/org/webrtc/VideoCapturerAndroidTest.java
index 109c294..fec3b50 100644
--- a/talk/app/webrtc/androidtests/src/org/webrtc/VideoCapturerAndroidTest.java
+++ b/talk/app/webrtc/androidtests/src/org/webrtc/VideoCapturerAndroidTest.java
@@ -67,6 +67,7 @@
     private int frameSize = 0;
     private Object frameLock = 0;
     private Object capturerStartLock = 0;
+    private Object capturerStopLock = 0;
     private boolean captureStartResult = false;
 
     @Override
@@ -78,6 +79,13 @@
     }
 
     @Override
+    public void OnCapturerStopped() {
+      synchronized (capturerStopLock) {
+        capturerStopLock.notify();
+      }
+    }
+
+    @Override
     public void OnFrameCaptured(byte[] data, int rotation, long timeStamp) {
       synchronized (frameLock) {
         ++framesCaptured;
@@ -93,6 +101,12 @@
       }
     }
 
+    public void WaitForCapturerToStop() throws InterruptedException {
+      synchronized (capturerStopLock) {
+        capturerStopLock.wait();
+      }
+    }
+
     public int WaitForNextCapturedFrame() throws InterruptedException {
       synchronized (frameLock) {
         frameLock.wait();
@@ -124,6 +138,7 @@
     assertTrue(callbacks.WaitForNextFrameToRender() > 0);
     track.dispose();
     source.dispose();
+    factory.dispose();
   }
 
   @Override
@@ -199,6 +214,7 @@
     assertTrue(callbacks.WaitForNextFrameToRender() > 0);
     track.dispose();
     source.dispose();
+    factory.dispose();
   }
 
   @SmallTest
@@ -223,6 +239,7 @@
     assertEquals(MediaSource.State.LIVE, source.state());
     track.dispose();
     source.dispose();
+    factory.dispose();
   }
 
   @SmallTest
@@ -242,9 +259,10 @@
           getInstrumentation().getContext(), observer);
       assertTrue(observer.WaitForCapturerToStart());
       observer.WaitForNextCapturedFrame();
-      // Check the frame size. NV21 is assumed.
+      // Check the frame size.
       assertEquals((format.width*format.height*3)/2, observer.frameSize());
-      assertTrue(capturer.stopCapture());
+      capturer.stopCapture();
+      observer.WaitForCapturerToStop();
     }
     capturer.dispose();
   }
diff --git a/talk/app/webrtc/androidvideocapturer.cc b/talk/app/webrtc/androidvideocapturer.cc
index 3264421..02f77db 100644
--- a/talk/app/webrtc/androidvideocapturer.cc
+++ b/talk/app/webrtc/androidvideocapturer.cc
@@ -27,7 +27,6 @@
 #include "talk/app/webrtc/androidvideocapturer.h"
 
 #include "talk/media/webrtc/webrtcvideoframe.h"
-#include "webrtc/base/bind.h"
 #include "webrtc/base/common.h"
 #include "webrtc/base/json.h"
 #include "webrtc/base/timeutils.h"
@@ -101,7 +100,7 @@
       delegate_(delegate.Pass()),
       worker_thread_(NULL),
       frame_factory_(NULL),
-      current_state_(cricket::CS_STOPPED){
+      current_state_(cricket::CS_STOPPED) {
   std::string json_string = delegate_->GetSupportedFormats();
   LOG(LS_INFO) << json_string;
 
@@ -174,13 +173,6 @@
 }
 
 void AndroidVideoCapturer::OnCapturerStarted(bool success) {
-  // This method is called from a Java thread.
-  DCHECK(!worker_thread_->IsCurrent());
-  worker_thread_->Invoke<void>(
-      rtc::Bind(&AndroidVideoCapturer::OnCapturerStarted_w, this, success));
-}
-
-void AndroidVideoCapturer::OnCapturerStarted_w(bool success) {
   DCHECK(worker_thread_->IsCurrent());
   cricket::CaptureState new_state =
       success ? cricket::CS_RUNNING : cricket::CS_FAILED;
@@ -194,21 +186,10 @@
   SignalStateChange(this, new_state);
 }
 
-void AndroidVideoCapturer::OnIncomingFrame(signed char* videoFrame,
+void AndroidVideoCapturer::OnIncomingFrame(signed char* frame_data,
                                            int length,
                                            int rotation,
                                            int64 time_stamp) {
-  // This method is called from a Java thread.
-  DCHECK(!worker_thread_->IsCurrent());
-  worker_thread_->Invoke<void>(
-      rtc::Bind(&AndroidVideoCapturer::OnIncomingFrame_w, this, videoFrame,
-                length, rotation, time_stamp));
-}
-
-void AndroidVideoCapturer::OnIncomingFrame_w(signed char* frame_data,
-                                             int length,
-                                             int rotation,
-                                             int64 time_stamp) {
   DCHECK(worker_thread_->IsCurrent());
   frame_factory_->UpdateCapturedFrame(frame_data, length, rotation, time_stamp);
   SignalFrameCaptured(this, frame_factory_->GetCapturedFrame());
diff --git a/talk/app/webrtc/androidvideocapturer.h b/talk/app/webrtc/androidvideocapturer.h
index ef72cd6..ed31cd2 100644
--- a/talk/app/webrtc/androidvideocapturer.h
+++ b/talk/app/webrtc/androidvideocapturer.h
@@ -44,9 +44,9 @@
   virtual void Start(int width, int height, int framerate,
                      AndroidVideoCapturer* capturer) = 0;
 
-  // Stops capturing. The implementation must synchronously stop the capturer.
+  // Stops capturing.
   // The delegate may not call into AndroidVideoCapturer after this call.
-  virtual bool Stop() = 0;
+  virtual void Stop() = 0;
 
   // Must returns a JSON string "{{width=xxx, height=xxx, framerate = xxx}}"
   virtual std::string GetSupportedFormats() = 0;
@@ -74,13 +74,6 @@
   AndroidVideoCapturerDelegate* delegate() { return delegate_.get(); }
 
  private:
-  void OnCapturerStarted_w(bool success);
-
-  void OnIncomingFrame_w(signed char* frame_data,
-                         int length,
-                         int rotation,
-                         int64 time_stamp);
-
   // cricket::VideoCapturer implementation.
   // Video frames will be delivered using
   // cricket::VideoCapturer::SignalFrameCaptured on the thread that calls Start.
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);
   }
 }