Index: webrtc/api/android/java/src/org/webrtc/Camera2Capturer.java |
diff --git a/webrtc/api/android/java/src/org/webrtc/Camera2Capturer.java b/webrtc/api/android/java/src/org/webrtc/Camera2Capturer.java |
index 8e44d69eaf58bb21dd8828ef5e76cb54882be303..8110945a33c9a623ae764806202d53a0a0eb87cd 100644 |
--- a/webrtc/api/android/java/src/org/webrtc/Camera2Capturer.java |
+++ b/webrtc/api/android/java/src/org/webrtc/Camera2Capturer.java |
@@ -35,7 +35,7 @@ import android.view.WindowManager; |
import java.util.Arrays; |
import java.util.List; |
import java.util.concurrent.CountDownLatch; |
-import java.util.concurrent.Semaphore; |
+import java.util.concurrent.atomic.AtomicBoolean; |
@TargetApi(21) |
public class Camera2Capturer implements |
@@ -58,17 +58,20 @@ public class Camera2Capturer implements |
private final CameraManager cameraManager; |
private final CameraEventsHandler eventsHandler; |
+ // Set once in initialization(), before any other calls, so therefore thread safe. |
+ // --------------------------------------------------------------------------------------------- |
+ private SurfaceTextureHelper surfaceTextureHelper; |
+ private Context applicationContext; |
+ private CapturerObserver capturerObserver; |
+ // Use postOnCameraThread() instead of posting directly to the handler - this way all callbacks |
+ // with a specifed token can be removed at once. |
+ private Handler cameraThreadHandler; |
// Shared state - guarded by cameraStateLock. Will only be edited from camera thread (when it is |
// running). |
// --------------------------------------------------------------------------------------------- |
private final Object cameraStateLock = new Object(); |
- private CameraState cameraState = CameraState.IDLE; |
- // |cameraThreadHandler| must be synchronized on |cameraStateLock| when not on the camera thread, |
- // or when modifying the reference. Use postOnCameraThread() instead of posting directly to |
- // the handler - this way all callbacks with a specifed token can be removed at once. |
- // |cameraThreadHandler| must be null if and only if CameraState is IDLE. |
- private Handler cameraThreadHandler; |
+ private volatile CameraState cameraState = CameraState.IDLE; |
// Remember the requested format in case we want to switch cameras. |
private int requestedWidth; |
private int requestedHeight; |
@@ -79,22 +82,18 @@ public class Camera2Capturer implements |
private boolean isFrontCamera; |
private int cameraOrientation; |
- // Semaphore for allowing only one switch at a time. |
- private final Semaphore pendingCameraSwitchSemaphore = new Semaphore(1); |
- // Guarded by pendingCameraSwitchSemaphore |
+ // Atomic boolean for allowing only one switch at a time. |
+ private final AtomicBoolean isPendingCameraSwitch = new AtomicBoolean(); |
+ // Guarded by isPendingCameraSwitch. |
private CameraSwitchHandler switchEventsHandler; |
// Internal state - must only be modified from camera thread |
// --------------------------------------------------------- |
private CaptureFormat captureFormat; |
- private Context applicationContext; |
- private CapturerObserver capturerObserver; |
private CameraStatistics cameraStatistics; |
- private SurfaceTextureHelper surfaceTextureHelper; |
private CameraCaptureSession captureSession; |
private Surface surface; |
private CameraDevice cameraDevice; |
- private CameraStateCallback cameraStateCallback; |
// Factor to convert between Android framerates and CaptureFormat.FramerateRange. It will be |
// either 1 or 1000. |
@@ -111,28 +110,16 @@ public class Camera2Capturer implements |
setCameraName(cameraName); |
} |
- /** |
- * Helper method for checking method is executed on camera thread. Also allows calls from other |
- * threads if camera is closed. |
- */ |
- private void checkIsOnCameraThread() { |
- if (cameraState == CameraState.IDLE) { |
- return; |
- } |
- |
- checkIsStrictlyOnCameraThread(); |
+ private boolean isOnCameraThread() { |
+ return Thread.currentThread() == cameraThreadHandler.getLooper().getThread(); |
} |
/** |
- * Like checkIsOnCameraThread but doesn't allow the camera to be stopped. |
+ * Helper method for checking method is executed on camera thread. |
*/ |
- private void checkIsStrictlyOnCameraThread() { |
- if (cameraThreadHandler == null) { |
- throw new IllegalStateException("Camera is closed."); |
- } |
- |
- if (Thread.currentThread() != cameraThreadHandler.getLooper().getThread()) { |
- throw new IllegalStateException("Wrong thread"); |
+ private void checkIsOnCameraThread() { |
+ if (!isOnCameraThread()) { |
+ throw new IllegalStateException("Not on camera thread"); |
} |
} |
@@ -247,14 +234,14 @@ public class Camera2Capturer implements |
* thread and camera must not be stopped. |
*/ |
private void reportError(String errorDescription) { |
- checkIsStrictlyOnCameraThread(); |
+ checkIsOnCameraThread(); |
Logging.e(TAG, "Error in camera at state " + cameraState + ": " + errorDescription); |
if (switchEventsHandler != null) { |
switchEventsHandler.onCameraSwitchError(errorDescription); |
switchEventsHandler = null; |
- pendingCameraSwitchSemaphore.release(); |
} |
+ isPendingCameraSwitch.set(false); |
switch (cameraState) { |
case STARTING: |
@@ -276,22 +263,19 @@ public class Camera2Capturer implements |
} |
private void closeAndRelease() { |
- checkIsStrictlyOnCameraThread(); |
+ checkIsOnCameraThread(); |
Logging.d(TAG, "Close and release."); |
setCameraState(CameraState.STOPPING); |
// Remove all pending Runnables posted from |this|. |
cameraThreadHandler.removeCallbacksAndMessages(this /* token */); |
- applicationContext = null; |
- capturerObserver = null; |
if (cameraStatistics != null) { |
cameraStatistics.release(); |
cameraStatistics = null; |
} |
if (surfaceTextureHelper != null) { |
surfaceTextureHelper.stopListening(); |
- surfaceTextureHelper = null; |
} |
if (captureSession != null) { |
captureSession.close(); |
@@ -320,7 +304,6 @@ public class Camera2Capturer implements |
Logging.w(TAG, "closeAndRelease called while cameraDevice is null"); |
setCameraState(CameraState.IDLE); |
} |
- this.cameraStateCallback = null; |
} |
/** |
@@ -328,16 +311,9 @@ public class Camera2Capturer implements |
*/ |
private void setCameraState(CameraState newState) { |
// State must only be modified on the camera thread. It can be edited from other threads |
- // if cameraState is IDLE since there is no camera thread. |
- checkIsOnCameraThread(); |
- |
- if (newState != CameraState.IDLE) { |
- if (cameraThreadHandler == null) { |
- throw new IllegalStateException( |
- "cameraThreadHandler must be null if and only if CameraState is IDLE."); |
- } |
- } else { |
- cameraThreadHandler = null; |
+ // if cameraState is IDLE since the camera thread is idle and not modifying the state. |
+ if (cameraState != CameraState.IDLE) { |
+ checkIsOnCameraThread(); |
} |
switch (newState) { |
@@ -376,37 +352,49 @@ public class Camera2Capturer implements |
*/ |
private void openCamera() { |
try { |
- checkIsStrictlyOnCameraThread(); |
+ checkIsOnCameraThread(); |
if (cameraState != CameraState.STARTING) { |
throw new IllegalStateException("Camera should be in state STARTING in openCamera."); |
} |
- if (cameraThreadHandler == null) { |
- throw new RuntimeException("Someone set cameraThreadHandler to null while the camera " |
- + "state was STARTING. This should never happen"); |
- } |
- |
// Camera is in state STARTING so cameraName will not be edited. |
- cameraManager.openCamera(cameraName, cameraStateCallback, cameraThreadHandler); |
+ cameraManager.openCamera(cameraName, new CameraStateCallback(), cameraThreadHandler); |
} catch (CameraAccessException e) { |
reportError("Failed to open camera: " + e); |
} |
} |
- private void startCaptureOnCameraThread( |
- final int requestedWidth, final int requestedHeight, final int requestedFramerate, |
- final SurfaceTextureHelper surfaceTextureHelper, final Context applicationContext, |
- final CapturerObserver capturerObserver) { |
- checkIsStrictlyOnCameraThread(); |
- |
- firstFrameReported = false; |
- consecutiveCameraOpenFailures = 0; |
+ private boolean isInitialized() { |
+ return applicationContext != null && capturerObserver != null; |
+ } |
+ @Override |
+ public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context applicationContext, |
+ CapturerObserver capturerObserver) { |
+ Logging.d(TAG, "initialize"); |
+ if (applicationContext == null) { |
+ throw new IllegalArgumentException("applicationContext not set."); |
+ } |
+ if (capturerObserver == null) { |
+ throw new IllegalArgumentException("capturerObserver not set."); |
+ } |
+ if (isInitialized()) { |
+ throw new IllegalStateException("Already initialized"); |
+ } |
this.applicationContext = applicationContext; |
this.capturerObserver = capturerObserver; |
this.surfaceTextureHelper = surfaceTextureHelper; |
- this.cameraStateCallback = new CameraStateCallback(); |
+ this.cameraThreadHandler = |
+ surfaceTextureHelper == null ? null : surfaceTextureHelper.getHandler(); |
+ } |
+ |
+ private void startCaptureOnCameraThread( |
+ final int requestedWidth, final int requestedHeight, final int requestedFramerate) { |
+ checkIsOnCameraThread(); |
+ |
+ firstFrameReported = false; |
+ consecutiveCameraOpenFailures = 0; |
synchronized (cameraStateLock) { |
// Remember the requested format in case we want to switch cameras. |
@@ -466,36 +454,32 @@ public class Camera2Capturer implements |
*/ |
@Override |
public void startCapture( |
- final int requestedWidth, final int requestedHeight, final int requestedFramerate, |
- final SurfaceTextureHelper surfaceTextureHelper, final Context applicationContext, |
- final CapturerObserver capturerObserver) { |
+ final int requestedWidth, final int requestedHeight, final int requestedFramerate) { |
Logging.d(TAG, "startCapture requested: " + requestedWidth + "x" + requestedHeight |
+ "@" + requestedFramerate); |
- if (surfaceTextureHelper == null) { |
- throw new IllegalArgumentException("surfaceTextureHelper not set."); |
+ if (!isInitialized()) { |
+ throw new IllegalStateException("startCapture called in uninitialized state"); |
} |
- if (applicationContext == null) { |
- throw new IllegalArgumentException("applicationContext not set."); |
- } |
- if (capturerObserver == null) { |
- throw new IllegalArgumentException("capturerObserver not set."); |
+ if (surfaceTextureHelper == null) { |
+ capturerObserver.onCapturerStarted(false /* success */); |
+ if (eventsHandler != null) { |
+ eventsHandler.onCameraError("No SurfaceTexture created."); |
+ } |
+ return; |
} |
- |
synchronized (cameraStateLock) { |
waitForCameraToStopIfStopping(); |
if (cameraState != CameraState.IDLE) { |
Logging.e(TAG, "Unexpected camera state for startCapture: " + cameraState); |
return; |
} |
- this.cameraThreadHandler = surfaceTextureHelper.getHandler(); |
setCameraState(CameraState.STARTING); |
} |
postOnCameraThread(new Runnable() { |
@Override |
public void run() { |
- startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramerate, |
- surfaceTextureHelper, applicationContext, capturerObserver); |
+ startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramerate); |
} |
}); |
} |
@@ -521,14 +505,14 @@ public class Camera2Capturer implements |
@Override |
public void onDisconnected(CameraDevice camera) { |
- checkIsStrictlyOnCameraThread(); |
+ checkIsOnCameraThread(); |
cameraDevice = camera; |
reportError("Camera disconnected."); |
} |
@Override |
public void onError(CameraDevice camera, int errorCode) { |
- checkIsStrictlyOnCameraThread(); |
+ checkIsOnCameraThread(); |
cameraDevice = camera; |
if (cameraState == CameraState.STARTING && ( |
@@ -555,7 +539,7 @@ public class Camera2Capturer implements |
@Override |
public void onOpened(CameraDevice camera) { |
- checkIsStrictlyOnCameraThread(); |
+ checkIsOnCameraThread(); |
Logging.d(TAG, "Camera opened."); |
if (cameraState != CameraState.STARTING) { |
@@ -576,7 +560,7 @@ public class Camera2Capturer implements |
@Override |
public void onClosed(CameraDevice camera) { |
- checkIsStrictlyOnCameraThread(); |
+ checkIsOnCameraThread(); |
Logging.d(TAG, "Camera device closed."); |
@@ -597,14 +581,14 @@ public class Camera2Capturer implements |
final class CaptureSessionCallback extends CameraCaptureSession.StateCallback { |
@Override |
public void onConfigureFailed(CameraCaptureSession session) { |
- checkIsStrictlyOnCameraThread(); |
+ checkIsOnCameraThread(); |
captureSession = session; |
reportError("Failed to configure capture session."); |
} |
@Override |
public void onConfigured(CameraCaptureSession session) { |
- checkIsStrictlyOnCameraThread(); |
+ checkIsOnCameraThread(); |
Logging.d(TAG, "Camera capture session configured."); |
captureSession = session; |
try { |
@@ -642,8 +626,8 @@ public class Camera2Capturer implements |
if (switchEventsHandler != null) { |
switchEventsHandler.onCameraSwitchDone(isFrontCamera); |
switchEventsHandler = null; |
- pendingCameraSwitchSemaphore.release(); |
} |
+ isPendingCameraSwitch.set(false); |
} |
} |
@@ -692,8 +676,9 @@ public class Camera2Capturer implements |
return; |
} |
// Do not handle multiple camera switch request to avoid blocking camera thread by handling too |
- // many switch request from a queue. We have to be careful to always release this. |
- if (!pendingCameraSwitchSemaphore.tryAcquire()) { |
+ // many switch request from a queue. We have to be careful to always release |
+ // |isPendingCameraSwitch| by setting it to false when done. |
+ if (isPendingCameraSwitch.getAndSet(true)) { |
Logging.w(TAG, "Ignoring camera switch request."); |
if (switchEventsHandler != null) { |
switchEventsHandler.onCameraSwitchError("Pending camera switch already in progress."); |
@@ -702,9 +687,6 @@ public class Camera2Capturer implements |
} |
final String newCameraId; |
- final SurfaceTextureHelper surfaceTextureHelper; |
- final Context applicationContext; |
- final CapturerObserver capturerObserver; |
final int requestedWidth; |
final int requestedHeight; |
final int requestedFramerate; |
@@ -717,7 +699,7 @@ public class Camera2Capturer implements |
if (switchEventsHandler != null) { |
switchEventsHandler.onCameraSwitchError("Camera is stopped."); |
} |
- pendingCameraSwitchSemaphore.release(); |
+ isPendingCameraSwitch.set(false); |
return; |
} |
@@ -731,11 +713,6 @@ public class Camera2Capturer implements |
final int newCameraIndex = (currentCameraIndex + 1) % cameraIds.length; |
newCameraId = cameraIds[newCameraIndex]; |
- // Remember parameters. These are not null since camera is in RUNNING state. They aren't |
- // edited either while camera is in RUNNING state. |
- surfaceTextureHelper = this.surfaceTextureHelper; |
- applicationContext = this.applicationContext; |
- capturerObserver = this.capturerObserver; |
requestedWidth = this.requestedWidth; |
requestedHeight = this.requestedHeight; |
requestedFramerate = this.requestedFramerate; |
@@ -745,8 +722,7 @@ public class Camera2Capturer implements |
// Make the switch. |
stopCapture(); |
setCameraName(newCameraId); |
- startCapture(requestedWidth, requestedHeight, requestedFramerate, surfaceTextureHelper, |
- applicationContext, capturerObserver); |
+ startCapture(requestedWidth, requestedHeight, requestedFramerate); |
// Note: switchEventsHandler will be called from onConfigured / reportError. |
} |
@@ -761,10 +737,6 @@ public class Camera2Capturer implements |
postOnCameraThread(new Runnable() { |
@Override |
public void run() { |
- if (capturerObserver == null) { |
- Logging.e(TAG, "Calling onOutputFormatRequest() on stopped camera."); |
- return; |
- } |
Logging.d(TAG, |
"onOutputFormatRequestOnCameraThread: " + width + "x" + height + "@" + framerate); |
capturerObserver.onOutputFormatRequest(width, height, framerate); |
@@ -776,10 +748,6 @@ public class Camera2Capturer implements |
// is running. |
@Override |
public void changeCaptureFormat(final int width, final int height, final int framerate) { |
- final SurfaceTextureHelper surfaceTextureHelper; |
- final Context applicationContext; |
- final CapturerObserver capturerObserver; |
- |
synchronized (cameraStateLock) { |
waitForCameraToStartIfStarting(); |
@@ -791,17 +759,12 @@ public class Camera2Capturer implements |
requestedWidth = width; |
requestedHeight = height; |
requestedFramerate = framerate; |
- |
- surfaceTextureHelper = this.surfaceTextureHelper; |
- applicationContext = this.applicationContext; |
- capturerObserver = this.capturerObserver; |
} |
// Make the switch. |
stopCapture(); |
// TODO(magjed/sakal): Just recreate session. |
- startCapture(width, height, framerate, |
- surfaceTextureHelper, applicationContext, capturerObserver); |
+ startCapture(width, height, framerate); |
} |
@Override |
@@ -896,7 +859,7 @@ public class Camera2Capturer implements |
@Override |
public void onTextureFrameAvailable( |
int oesTextureId, float[] transformMatrix, long timestampNs) { |
- checkIsStrictlyOnCameraThread(); |
+ checkIsOnCameraThread(); |
if (eventsHandler != null && !firstFrameReported) { |
eventsHandler.onFirstFrameAvailable(); |