| Index: webrtc/api/android/java/src/org/webrtc/VideoCapturerAndroid.java
|
| diff --git a/webrtc/api/android/java/src/org/webrtc/VideoCapturerAndroid.java b/webrtc/api/android/java/src/org/webrtc/VideoCapturerAndroid.java
|
| index 8f6f911b1da3c2c7a19035b6da2509ed020bd948..1e9c7ee712c2457620ab30ea69c16bf8030a62a1 100644
|
| --- a/webrtc/api/android/java/src/org/webrtc/VideoCapturerAndroid.java
|
| +++ b/webrtc/api/android/java/src/org/webrtc/VideoCapturerAndroid.java
|
| @@ -23,6 +23,7 @@ import java.nio.ByteBuffer;
|
| import java.util.HashSet;
|
| import java.util.List;
|
| import java.util.Set;
|
| +import java.util.concurrent.atomic.AtomicBoolean;
|
| import java.util.concurrent.CountDownLatch;
|
| import java.util.concurrent.TimeUnit;
|
|
|
| @@ -43,15 +44,14 @@ public class VideoCapturerAndroid implements
|
| CameraVideoCapturer,
|
| android.hardware.Camera.PreviewCallback,
|
| SurfaceTextureHelper.OnTextureFrameAvailableListener {
|
| - private final static String TAG = "VideoCapturerAndroid";
|
| + private static final String TAG = "VideoCapturerAndroid";
|
| private static final int CAMERA_STOP_TIMEOUT_MS = 7000;
|
|
|
| private android.hardware.Camera camera; // Only non-null while capturing.
|
| - private final Object handlerLock = new Object();
|
| - // |cameraThreadHandler| must be synchronized on |handlerLock| when not on the camera thread,
|
| - // or when modifying the reference. Use maybePostOnCameraThread() instead of posting directly to
|
| - // the handler - this way all callbacks with a specifed token can be removed at once.
|
| - private Handler cameraThreadHandler;
|
| + private final AtomicBoolean isCameraRunning = new AtomicBoolean();
|
| + // Use maybePostOnCameraThread() instead of posting directly to the handler - this way all
|
| + // callbacks with a specifed token can be removed at once.
|
| + private volatile Handler cameraThreadHandler;
|
| private Context applicationContext;
|
| // Synchronization lock for |id|.
|
| private final Object cameraIdLock = new Object();
|
| @@ -117,10 +117,8 @@ public class VideoCapturerAndroid implements
|
|
|
| public void printStackTrace() {
|
| Thread cameraThread = null;
|
| - synchronized (handlerLock) {
|
| - if (cameraThreadHandler != null) {
|
| - cameraThread = cameraThreadHandler.getLooper().getThread();
|
| - }
|
| + if (cameraThreadHandler != null) {
|
| + cameraThread = cameraThreadHandler.getLooper().getThread();
|
| }
|
| if (cameraThread != null) {
|
| StackTraceElement[] cameraStackTraces = cameraThread.getStackTrace();
|
| @@ -232,12 +230,10 @@ public class VideoCapturerAndroid implements
|
| }
|
|
|
| private void checkIsOnCameraThread() {
|
| - synchronized (handlerLock) {
|
| - if (cameraThreadHandler == null) {
|
| - Logging.e(TAG, "Camera is stopped - can't check thread.");
|
| - } else if (Thread.currentThread() != cameraThreadHandler.getLooper().getThread()) {
|
| - throw new IllegalStateException("Wrong thread");
|
| - }
|
| + if (cameraThreadHandler == null) {
|
| + Logging.e(TAG, "Camera is not initialized - can't check thread.");
|
| + } else if (Thread.currentThread() != cameraThreadHandler.getLooper().getThread()) {
|
| + throw new IllegalStateException("Wrong thread");
|
| }
|
| }
|
|
|
| @@ -246,11 +242,9 @@ public class VideoCapturerAndroid implements
|
| }
|
|
|
| private boolean maybePostDelayedOnCameraThread(int delayMs, Runnable runnable) {
|
| - synchronized (handlerLock) {
|
| - return cameraThreadHandler != null
|
| - && cameraThreadHandler.postAtTime(
|
| - runnable, this /* token */, SystemClock.uptimeMillis() + delayMs);
|
| - }
|
| + return cameraThreadHandler != null && isCameraRunning.get()
|
| + && cameraThreadHandler.postAtTime(
|
| + runnable, this /* token */, SystemClock.uptimeMillis() + delayMs);
|
| }
|
|
|
| @Override
|
| @@ -258,67 +252,75 @@ public class VideoCapturerAndroid implements
|
| Logging.d(TAG, "dispose");
|
| }
|
|
|
| + private boolean isInitialized() {
|
| + return applicationContext != null && frameObserver != null;
|
| + }
|
| +
|
| + @Override
|
| + public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context applicationContext,
|
| + CapturerObserver frameObserver) {
|
| + Logging.d(TAG, "initialize");
|
| + if (applicationContext == null) {
|
| + throw new IllegalArgumentException("applicationContext not set.");
|
| + }
|
| + if (frameObserver == null) {
|
| + throw new IllegalArgumentException("frameObserver not set.");
|
| + }
|
| + if (isInitialized()) {
|
| + throw new IllegalStateException("Already initialized");
|
| + }
|
| + this.applicationContext = applicationContext;
|
| + this.frameObserver = frameObserver;
|
| + this.surfaceHelper = surfaceTextureHelper;
|
| + this.cameraThreadHandler =
|
| + surfaceTextureHelper == null ? null : surfaceTextureHelper.getHandler();
|
| + }
|
| +
|
| // Note that this actually opens the camera, and Camera callbacks run on the
|
| // thread that calls open(), so this is done on the CameraThread.
|
| @Override
|
| - public void startCapture(
|
| - final int width, final int height, final int framerate,
|
| - final SurfaceTextureHelper surfaceTextureHelper, final Context applicationContext,
|
| - final CapturerObserver frameObserver) {
|
| + public void startCapture(final int width, final int height, final int framerate) {
|
| Logging.d(TAG, "startCapture requested: " + width + "x" + height + "@" + framerate);
|
| - if (surfaceTextureHelper == null) {
|
| + if (!isInitialized()) {
|
| + throw new IllegalStateException("startCapture called in uninitialized state");
|
| + }
|
| + if (surfaceHelper == null) {
|
| frameObserver.onCapturerStarted(false /* success */);
|
| if (eventsHandler != null) {
|
| eventsHandler.onCameraError("No SurfaceTexture created.");
|
| }
|
| return;
|
| }
|
| - if (applicationContext == null) {
|
| - throw new IllegalArgumentException("applicationContext not set.");
|
| - }
|
| - if (frameObserver == null) {
|
| - throw new IllegalArgumentException("frameObserver not set.");
|
| + if (isCameraRunning.getAndSet(true)) {
|
| + Logging.e(TAG, "Camera has already been started.");
|
| + return;
|
| }
|
| - synchronized (handlerLock) {
|
| - if (this.cameraThreadHandler != null) {
|
| - throw new RuntimeException("Camera has already been started.");
|
| + final boolean didPost = maybePostOnCameraThread(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + openCameraAttempts = 0;
|
| + startCaptureOnCameraThread(width, height, framerate);
|
| }
|
| - this.cameraThreadHandler = surfaceTextureHelper.getHandler();
|
| - this.surfaceHelper = surfaceTextureHelper;
|
| - final boolean didPost = maybePostOnCameraThread(new Runnable() {
|
| - @Override
|
| - public void run() {
|
| - openCameraAttempts = 0;
|
| - startCaptureOnCameraThread(width, height, framerate, frameObserver,
|
| - applicationContext);
|
| - }
|
| - });
|
| - if (!didPost) {
|
| - frameObserver.onCapturerStarted(false);
|
| - if (eventsHandler != null) {
|
| - eventsHandler.onCameraError("Could not post task to camera thread.");
|
| - }
|
| + });
|
| + if (!didPost) {
|
| + frameObserver.onCapturerStarted(false);
|
| + if (eventsHandler != null) {
|
| + eventsHandler.onCameraError("Could not post task to camera thread.");
|
| }
|
| + isCameraRunning.set(false);
|
| }
|
| }
|
|
|
| - private void startCaptureOnCameraThread(
|
| - final int width, final int height, final int framerate, final CapturerObserver frameObserver,
|
| - final Context applicationContext) {
|
| - synchronized (handlerLock) {
|
| - if (cameraThreadHandler == null) {
|
| - Logging.e(TAG, "startCaptureOnCameraThread: Camera is stopped");
|
| - return;
|
| - } else {
|
| - checkIsOnCameraThread();
|
| - }
|
| + private void startCaptureOnCameraThread(final int width, final int height, final int framerate) {
|
| + checkIsOnCameraThread();
|
| + if (!isCameraRunning.get()) {
|
| + Logging.e(TAG, "startCaptureOnCameraThread: Camera is stopped");
|
| + return;
|
| }
|
| if (camera != null) {
|
| Logging.e(TAG, "startCaptureOnCameraThread: Camera has already been started.");
|
| return;
|
| }
|
| - this.applicationContext = applicationContext;
|
| - this.frameObserver = frameObserver;
|
| this.firstFrameReported = false;
|
|
|
| try {
|
| @@ -337,9 +339,9 @@ public class VideoCapturerAndroid implements
|
| if (openCameraAttempts < MAX_OPEN_CAMERA_ATTEMPTS) {
|
| Logging.e(TAG, "Camera.open failed, retrying", e);
|
| maybePostDelayedOnCameraThread(OPEN_CAMERA_DELAY_MS, new Runnable() {
|
| - @Override public void run() {
|
| - startCaptureOnCameraThread(width, height, framerate, frameObserver,
|
| - applicationContext);
|
| + @Override
|
| + public void run() {
|
| + startCaptureOnCameraThread(width, height, framerate);
|
| }
|
| });
|
| return;
|
| @@ -373,13 +375,10 @@ public class VideoCapturerAndroid implements
|
|
|
| // (Re)start preview with the closest supported format to |width| x |height| @ |framerate|.
|
| private void startPreviewOnCameraThread(int width, int height, int framerate) {
|
| - synchronized (handlerLock) {
|
| - if (cameraThreadHandler == null || camera == null) {
|
| - Logging.e(TAG, "startPreviewOnCameraThread: Camera is stopped");
|
| - return;
|
| - } else {
|
| - checkIsOnCameraThread();
|
| - }
|
| + checkIsOnCameraThread();
|
| + if (!isCameraRunning.get() || camera == null) {
|
| + Logging.e(TAG, "startPreviewOnCameraThread: Camera is stopped");
|
| + return;
|
| }
|
| Logging.d(
|
| TAG, "startPreviewOnCameraThread requested: " + width + "x" + height + "@" + framerate);
|
| @@ -489,13 +488,7 @@ public class VideoCapturerAndroid implements
|
| }
|
|
|
| private void stopCaptureOnCameraThread(boolean stopHandler) {
|
| - synchronized (handlerLock) {
|
| - if (cameraThreadHandler == null) {
|
| - Logging.e(TAG, "stopCaptureOnCameraThread: Camera is stopped");
|
| - } else {
|
| - checkIsOnCameraThread();
|
| - }
|
| - }
|
| + checkIsOnCameraThread();
|
| Logging.d(TAG, "stopCaptureOnCameraThread");
|
| // Note that the camera might still not be started here if startCaptureOnCameraThread failed
|
| // and we posted a retry.
|
| @@ -505,21 +498,15 @@ public class VideoCapturerAndroid implements
|
| surfaceHelper.stopListening();
|
| }
|
| if (stopHandler) {
|
| - synchronized (handlerLock) {
|
| - // Clear the cameraThreadHandler first, in case stopPreview or
|
| - // other driver code deadlocks. Deadlock in
|
| - // android.hardware.Camera._stopPreview(Native Method) has
|
| - // been observed on Nexus 5 (hammerhead), OS version LMY48I.
|
| - // The camera might post another one or two preview frames
|
| - // before stopped, so we have to check for a null
|
| - // cameraThreadHandler in our handler. Remove all pending
|
| - // Runnables posted from |this|.
|
| - if (cameraThreadHandler != null) {
|
| - cameraThreadHandler.removeCallbacksAndMessages(this /* token */);
|
| - cameraThreadHandler = null;
|
| - }
|
| - surfaceHelper = null;
|
| - }
|
| + // Clear the cameraThreadHandler first, in case stopPreview or
|
| + // other driver code deadlocks. Deadlock in
|
| + // android.hardware.Camera._stopPreview(Native Method) has
|
| + // been observed on Nexus 5 (hammerhead), OS version LMY48I.
|
| + // The camera might post another one or two preview frames
|
| + // before stopped, so we have to check |isCameraRunning|.
|
| + // Remove all pending Runnables posted from |this|.
|
| + isCameraRunning.set(false);
|
| + cameraThreadHandler.removeCallbacksAndMessages(this /* token */);
|
| }
|
| if (cameraStatistics != null) {
|
| cameraStatistics.release();
|
| @@ -545,33 +532,22 @@ public class VideoCapturerAndroid implements
|
| }
|
|
|
| private void switchCameraOnCameraThread() {
|
| - synchronized (handlerLock) {
|
| - if (cameraThreadHandler == null) {
|
| - Logging.e(TAG, "switchCameraOnCameraThread: Camera is stopped");
|
| - return;
|
| - } else {
|
| - checkIsOnCameraThread();
|
| - }
|
| + checkIsOnCameraThread();
|
| + if (!isCameraRunning.get()) {
|
| + Logging.e(TAG, "switchCameraOnCameraThread: Camera is stopped");
|
| + return;
|
| }
|
| Logging.d(TAG, "switchCameraOnCameraThread");
|
| stopCaptureOnCameraThread(false /* stopHandler */);
|
| synchronized (cameraIdLock) {
|
| id = (id + 1) % android.hardware.Camera.getNumberOfCameras();
|
| }
|
| - startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramerate, frameObserver,
|
| - applicationContext);
|
| + startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramerate);
|
| Logging.d(TAG, "switchCameraOnCameraThread done");
|
| }
|
|
|
| private void onOutputFormatRequestOnCameraThread(int width, int height, int framerate) {
|
| - synchronized (handlerLock) {
|
| - if (cameraThreadHandler == null || camera == null) {
|
| - Logging.e(TAG, "onOutputFormatRequestOnCameraThread: Camera is stopped");
|
| - return;
|
| - } else {
|
| - checkIsOnCameraThread();
|
| - }
|
| - }
|
| + checkIsOnCameraThread();
|
| Logging.d(TAG, "onOutputFormatRequestOnCameraThread: " + width + "x" + height +
|
| "@" + framerate);
|
| frameObserver.onOutputFormatRequest(width, height, framerate);
|
| @@ -611,13 +587,10 @@ public class VideoCapturerAndroid implements
|
| // Called on cameraThread so must not "synchronized".
|
| @Override
|
| public void onPreviewFrame(byte[] data, android.hardware.Camera callbackCamera) {
|
| - synchronized (handlerLock) {
|
| - if (cameraThreadHandler == null) {
|
| - Logging.e(TAG, "onPreviewFrame: Camera is stopped");
|
| - return;
|
| - } else {
|
| - checkIsOnCameraThread();
|
| - }
|
| + checkIsOnCameraThread();
|
| + if (!isCameraRunning.get()) {
|
| + Logging.e(TAG, "onPreviewFrame: Camera is stopped");
|
| + return;
|
| }
|
| if (!queuedBuffers.contains(data)) {
|
| // |data| is an old invalid buffer.
|
| @@ -644,14 +617,11 @@ public class VideoCapturerAndroid implements
|
| @Override
|
| public void onTextureFrameAvailable(
|
| int oesTextureId, float[] transformMatrix, long timestampNs) {
|
| - synchronized (handlerLock) {
|
| - if (cameraThreadHandler == null) {
|
| - Logging.e(TAG, "onTextureFrameAvailable: Camera is stopped");
|
| - surfaceHelper.returnTextureFrame();
|
| - return;
|
| - } else {
|
| - checkIsOnCameraThread();
|
| - }
|
| + checkIsOnCameraThread();
|
| + if (!isCameraRunning.get()) {
|
| + Logging.e(TAG, "onTextureFrameAvailable: Camera is stopped");
|
| + surfaceHelper.returnTextureFrame();
|
| + return;
|
| }
|
| if (eventsHandler != null && !firstFrameReported) {
|
| eventsHandler.onFirstFrameAvailable();
|
|
|