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 d26275cb0d3a3728dd61632494079fbd23989b86..f1c37a155bb530a9afbfbf231d26a72324c91242 100644 |
--- a/webrtc/api/android/java/src/org/webrtc/Camera2Capturer.java |
+++ b/webrtc/api/android/java/src/org/webrtc/Camera2Capturer.java |
@@ -14,879 +14,41 @@ import org.webrtc.CameraEnumerationAndroid.CaptureFormat; |
import android.annotation.TargetApi; |
import android.content.Context; |
-import android.graphics.SurfaceTexture; |
-import android.hardware.camera2.CameraAccessException; |
-import android.hardware.camera2.CameraCaptureSession; |
-import android.hardware.camera2.CameraCharacteristics; |
-import android.hardware.camera2.CameraDevice; |
import android.hardware.camera2.CameraManager; |
-import android.hardware.camera2.CameraMetadata; |
-import android.hardware.camera2.CaptureFailure; |
-import android.hardware.camera2.CaptureRequest; |
-import android.hardware.camera2.TotalCaptureResult; |
import android.os.Handler; |
-import android.os.SystemClock; |
-import android.util.Range; |
-import android.view.Surface; |
-import android.view.WindowManager; |
-import java.util.Arrays; |
import java.util.List; |
-import java.util.concurrent.CountDownLatch; |
-import java.util.concurrent.atomic.AtomicBoolean; |
@TargetApi(21) |
-public class Camera2Capturer implements |
- CameraVideoCapturer, |
- SurfaceTextureHelper.OnTextureFrameAvailableListener { |
- private final static String TAG = "Camera2Capturer"; |
- |
- private final static int MAX_OPEN_CAMERA_ATTEMPTS = 3; |
- private final static int OPEN_CAMERA_DELAY_MS = 500; |
- private final static int STOP_TIMEOUT = 10000; |
- private final static int START_TIMEOUT = 10000; |
- private final static Object STOP_TIMEOUT_RUNNABLE_TOKEN = new Object(); |
- |
- // In the Camera2 API, starting a camera is inherently asynchronous, and this state is |
- // represented with 'STARTING'. Stopping is also asynchronous and this state is 'STOPPING'. |
- private static enum CameraState { IDLE, STARTING, RUNNING, STOPPING } |
- |
- // Thread safe objects. |
- // -------------------- |
+public class Camera2Capturer extends CameraCapturer { |
+ private final Context context; |
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 volatile CameraState cameraState = CameraState.IDLE; |
- // Remember the requested format in case we want to switch cameras. |
- private int requestedWidth; |
- private int requestedHeight; |
- private int requestedFramerate; |
- |
- // Will only be edited while camera state is IDLE and cameraStateLock is acquired. |
- private String cameraName; |
- private boolean isFrontCamera; |
- private int cameraOrientation; |
- |
- // 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 CameraStatistics cameraStatistics; |
- private CameraCaptureSession captureSession; |
- private Surface surface; |
- private CameraDevice cameraDevice; |
- |
- // Factor to convert between Android framerates and CaptureFormat.FramerateRange. It will be |
- // either 1 or 1000. |
- private int fpsUnitFactor; |
- private boolean firstFrameReported; |
- private int consecutiveCameraOpenFailures; |
- |
- public Camera2Capturer( |
- Context context, String cameraName, CameraEventsHandler eventsHandler) { |
- Logging.d(TAG, "Camera2Capturer ctor, camera name: " + cameraName); |
- this.cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); |
- this.eventsHandler = eventsHandler; |
- |
- setCameraName(cameraName); |
- } |
- |
- private boolean isOnCameraThread() { |
- return Thread.currentThread() == cameraThreadHandler.getLooper().getThread(); |
- } |
- |
- /** |
- * Helper method for checking method is executed on camera thread. |
- */ |
- private void checkIsOnCameraThread() { |
- if (!isOnCameraThread()) { |
- throw new IllegalStateException("Not on camera thread"); |
- } |
- } |
- |
- /** |
- * Checks method is not invoked on the camera thread. Used in functions waiting for the camera |
- * state to change since executing them on the camera thread would cause a deadlock. |
- */ |
- private void checkNotOnCameraThread() { |
- if (cameraThreadHandler == null) { |
- return; |
- } |
- |
- if (Thread.currentThread() == cameraThreadHandler.getLooper().getThread()) { |
- throw new IllegalStateException( |
- "Method waiting for camera state to change executed on camera thread"); |
- } |
- } |
- |
- private void waitForCameraToExitTransitionalState( |
- CameraState transitionalState, long timeoutMs) { |
- checkNotOnCameraThread(); |
- |
- // We probably should already have the lock when this is called but acquire it in case |
- // we don't have it. |
- synchronized (cameraStateLock) { |
- long timeoutAt = SystemClock.uptimeMillis() + timeoutMs; |
- |
- while (cameraState == transitionalState) { |
- Logging.d(TAG, "waitForCameraToExitTransitionalState waiting: " |
- + cameraState); |
- |
- long timeLeft = timeoutAt - SystemClock.uptimeMillis(); |
- |
- if (timeLeft <= 0) { |
- Logging.e(TAG, "Camera failed to exit transitional state " + transitionalState |
- + " within the time limit."); |
- break; |
- } |
- |
- try { |
- cameraStateLock.wait(timeLeft); |
- } catch (InterruptedException e) { |
- Logging.w(TAG, "Trying to interrupt while waiting to exit transitional state " |
- + transitionalState + ", ignoring: " + e); |
- } |
- } |
- } |
- } |
- |
- /** |
- * Waits until camera state is not STOPPING. |
- */ |
- private void waitForCameraToStopIfStopping() { |
- waitForCameraToExitTransitionalState(CameraState.STOPPING, STOP_TIMEOUT); |
- } |
- |
- /** |
- * Wait until camera state is not STARTING. |
- */ |
- private void waitForCameraToStartIfStarting() { |
- waitForCameraToExitTransitionalState(CameraState.STARTING, START_TIMEOUT); |
- } |
- |
- /** |
- * Sets the name of the camera. Camera must be stopped or stopping when this is called. |
- */ |
- private void setCameraName(String cameraName) { |
- final CameraCharacteristics characteristics; |
- try { |
- final String[] cameraIds = cameraManager.getCameraIdList(); |
- |
- if (cameraName.isEmpty() && cameraIds.length != 0) { |
- cameraName = cameraIds[0]; |
- } |
- |
- if (!Arrays.asList(cameraIds).contains(cameraName)) { |
- throw new IllegalArgumentException( |
- "Camera name: " + cameraName + " does not match any known camera device:"); |
- } |
- |
- characteristics = cameraManager.getCameraCharacteristics(cameraName); |
- } catch (CameraAccessException e) { |
- throw new RuntimeException("Camera access exception: " + e); |
- } |
- |
- synchronized (cameraStateLock) { |
- waitForCameraToStopIfStopping(); |
- |
- if (cameraState != CameraState.IDLE) { |
- throw new RuntimeException("Changing camera name on running camera."); |
- } |
- |
- // Note: Usually changing camera state from outside camera thread is not allowed. It is |
- // allowed here because camera is not running. |
- this.cameraName = cameraName; |
- isFrontCamera = characteristics.get(CameraCharacteristics.LENS_FACING) |
- == CameraMetadata.LENS_FACING_FRONT; |
- |
- /* |
- * Clockwise angle through which the output image needs to be rotated to be upright on the |
- * device screen in its native orientation. |
- * Also defines the direction of rolling shutter readout, which is from top to bottom in the |
- * sensor's coordinate system. |
- * Units: Degrees of clockwise rotation; always a multiple of 90 |
- */ |
- cameraOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); |
- } |
- } |
- |
- /** |
- * Triggers appropriate error handlers based on the camera state. Must be called on the camera |
- * thread and camera must not be stopped. |
- */ |
- private void reportError(String errorDescription) { |
- checkIsOnCameraThread(); |
- Logging.e(TAG, "Error in camera at state " + cameraState + ": " + errorDescription); |
- |
- if (switchEventsHandler != null) { |
- switchEventsHandler.onCameraSwitchError(errorDescription); |
- switchEventsHandler = null; |
- } |
- isPendingCameraSwitch.set(false); |
- |
- switch (cameraState) { |
- case STARTING: |
- capturerObserver.onCapturerStarted(false /* success */); |
- // fall through |
- case RUNNING: |
- if (eventsHandler != null) { |
- eventsHandler.onCameraError(errorDescription); |
- } |
- break; |
- case STOPPING: |
- setCameraState(CameraState.IDLE); |
- Logging.e(TAG, "Closing camera failed: " + errorDescription); |
- return; // We don't want to call closeAndRelease in this case. |
- default: |
- throw new RuntimeException("Unknown camera state: " + cameraState); |
- } |
- closeAndRelease(); |
- } |
- |
- private void closeAndRelease() { |
- checkIsOnCameraThread(); |
- |
- Logging.d(TAG, "Close and release."); |
- setCameraState(CameraState.STOPPING); |
- capturerObserver.onCapturerStopped(); |
- |
- // Remove all pending Runnables posted from |this|. |
- cameraThreadHandler.removeCallbacksAndMessages(this /* token */); |
- if (cameraStatistics != null) { |
- cameraStatistics.release(); |
- cameraStatistics = null; |
- } |
- if (surfaceTextureHelper != null) { |
- surfaceTextureHelper.stopListening(); |
- } |
- if (captureSession != null) { |
- captureSession.close(); |
- captureSession = null; |
- } |
- if (surface != null) { |
- surface.release(); |
- surface = null; |
- } |
- if (cameraDevice != null) { |
- // Add a timeout for stopping the camera. |
- cameraThreadHandler.postAtTime(new Runnable() { |
- @Override |
- public void run() { |
- Logging.e(TAG, "Camera failed to stop within the timeout. Force stopping."); |
- setCameraState(CameraState.IDLE); |
- if (eventsHandler != null) { |
- eventsHandler.onCameraError("Camera failed to stop (timeout)."); |
- } |
- } |
- }, STOP_TIMEOUT_RUNNABLE_TOKEN, SystemClock.uptimeMillis() + STOP_TIMEOUT); |
- |
- cameraDevice.close(); |
- cameraDevice = null; |
- } else { |
- Logging.w(TAG, "closeAndRelease called while cameraDevice is null"); |
- setCameraState(CameraState.IDLE); |
- } |
- } |
- |
- /** |
- * Sets the camera state while ensuring constraints are followed. |
- */ |
- 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 the camera thread is idle and not modifying the state. |
- if (cameraState != CameraState.IDLE) { |
- checkIsOnCameraThread(); |
- } |
- |
- switch (newState) { |
- case STARTING: |
- if (cameraState != CameraState.IDLE) { |
- throw new IllegalStateException("Only stopped camera can start."); |
- } |
- break; |
- case RUNNING: |
- if (cameraState != CameraState.STARTING) { |
- throw new IllegalStateException("Only starting camera can go to running state."); |
- } |
- break; |
- case STOPPING: |
- if (cameraState != CameraState.STARTING && cameraState != CameraState.RUNNING) { |
- throw new IllegalStateException("Only starting or running camera can stop."); |
- } |
- break; |
- case IDLE: |
- if (cameraState != CameraState.STOPPING) { |
- throw new IllegalStateException("Only stopping camera can go to idle state."); |
- } |
- break; |
- default: |
- throw new RuntimeException("Unknown camera state: " + newState); |
- } |
- |
- synchronized (cameraStateLock) { |
- cameraState = newState; |
- cameraStateLock.notifyAll(); |
- } |
- } |
- |
- /** |
- * Internal method for opening the camera. Must be called on the camera thread. |
- */ |
- private void openCamera() { |
- try { |
- checkIsOnCameraThread(); |
- |
- if (cameraState != CameraState.STARTING) { |
- throw new IllegalStateException("Camera should be in state STARTING in openCamera."); |
- } |
- |
- // Camera is in state STARTING so cameraName will not be edited. |
- cameraManager.openCamera(cameraName, new CameraStateCallback(), cameraThreadHandler); |
- } catch (CameraAccessException e) { |
- reportError("Failed to open camera: " + e); |
- } |
- } |
- |
- 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.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. |
- this.requestedWidth = requestedWidth; |
- this.requestedHeight = requestedHeight; |
- this.requestedFramerate = requestedFramerate; |
- } |
- |
- final CameraCharacteristics cameraCharacteristics; |
- try { |
- // Camera is in state STARTING so cameraName will not be edited. |
- cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraName); |
- } catch (CameraAccessException e) { |
- reportError("getCameraCharacteristics(): " + e.getMessage()); |
- return; |
- } |
- |
- Range<Integer>[] fpsRanges = |
- cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); |
- fpsUnitFactor = Camera2Enumerator.getFpsUnitFactor(fpsRanges); |
- List<CaptureFormat.FramerateRange> framerateRanges = |
- Camera2Enumerator.convertFramerates(fpsRanges, fpsUnitFactor); |
- List<Size> sizes = Camera2Enumerator.getSupportedSizes(cameraCharacteristics); |
- |
- if (framerateRanges.isEmpty() || sizes.isEmpty()) { |
- reportError("No supported capture formats."); |
- } |
- final CaptureFormat.FramerateRange bestFpsRange = |
- CameraEnumerationAndroid.getClosestSupportedFramerateRange( |
- framerateRanges, requestedFramerate); |
- |
- final Size bestSize = CameraEnumerationAndroid.getClosestSupportedSize( |
- sizes, requestedWidth, requestedHeight); |
- |
- this.captureFormat = new CaptureFormat(bestSize.width, bestSize.height, bestFpsRange); |
- Logging.d(TAG, "Using capture format: " + captureFormat); |
- |
- Logging.d(TAG, "Opening camera " + cameraName); |
- if (eventsHandler != null) { |
- int cameraIndex = -1; |
- try { |
- cameraIndex = Integer.parseInt(cameraName); |
- } catch (NumberFormatException e) { |
- Logging.d(TAG, "External camera with non-int identifier: " + cameraName); |
- } |
- eventsHandler.onCameraOpening(cameraIndex); |
- } |
- |
- openCamera(); |
- } |
- |
- /** |
- * Starts capture using specified settings. This is automatically called for you by |
- * VideoCapturerTrackSource if you are just using the camera as source for video track. |
- */ |
- @Override |
- public void startCapture( |
- final int requestedWidth, final int requestedHeight, final int requestedFramerate) { |
- Logging.d(TAG, "startCapture requested: " + requestedWidth + "x" + requestedHeight |
- + "@" + requestedFramerate); |
- if (!isInitialized()) { |
- throw new IllegalStateException("startCapture called in uninitialized state"); |
- } |
- 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; |
- } |
- setCameraState(CameraState.STARTING); |
- } |
- postOnCameraThread(new Runnable() { |
- @Override |
- public void run() { |
- startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramerate); |
- } |
- }); |
- } |
- |
- final class CameraStateCallback extends CameraDevice.StateCallback { |
- private String getErrorDescription(int errorCode) { |
- switch (errorCode) { |
- case CameraDevice.StateCallback.ERROR_CAMERA_DEVICE: |
- return "Camera device has encountered a fatal error."; |
- case CameraDevice.StateCallback.ERROR_CAMERA_DISABLED: |
- return "Camera device could not be opened due to a device policy."; |
- case CameraDevice.StateCallback.ERROR_CAMERA_IN_USE: |
- return "Camera device is in use already."; |
- case CameraDevice.StateCallback.ERROR_CAMERA_SERVICE: |
- return "Camera service has encountered a fatal error."; |
- case CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE: |
- return "Camera device could not be opened because" |
- + " there are too many other open camera devices."; |
- default: |
- return "Unknown camera error: " + errorCode; |
- } |
- } |
- |
- @Override |
- public void onDisconnected(CameraDevice camera) { |
- checkIsOnCameraThread(); |
- cameraDevice = camera; |
- reportError("Camera disconnected."); |
- } |
- |
- @Override |
- public void onError(CameraDevice camera, int errorCode) { |
- checkIsOnCameraThread(); |
- cameraDevice = camera; |
- |
- if (cameraState == CameraState.STARTING && ( |
- errorCode == CameraDevice.StateCallback.ERROR_CAMERA_IN_USE || |
- errorCode == CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE)) { |
- consecutiveCameraOpenFailures++; |
- |
- if (consecutiveCameraOpenFailures < MAX_OPEN_CAMERA_ATTEMPTS) { |
- Logging.w(TAG, "Opening camera failed, trying again: " + getErrorDescription(errorCode)); |
- |
- postDelayedOnCameraThread(OPEN_CAMERA_DELAY_MS, new Runnable() { |
- @Override |
- public void run() { |
- openCamera(); |
- } |
- }); |
- return; |
- } else { |
- Logging.e(TAG, "Opening camera failed too many times. Passing the error."); |
- } |
- } |
- |
- reportError(getErrorDescription(errorCode)); |
- } |
- |
- @Override |
- public void onOpened(CameraDevice camera) { |
- checkIsOnCameraThread(); |
- |
- Logging.d(TAG, "Camera opened."); |
- if (cameraState != CameraState.STARTING) { |
- throw new IllegalStateException("Unexpected state when camera opened: " + cameraState); |
- } |
- |
- cameraDevice = camera; |
- final SurfaceTexture surfaceTexture = surfaceTextureHelper.getSurfaceTexture(); |
- surfaceTexture.setDefaultBufferSize(captureFormat.width, captureFormat.height); |
- surface = new Surface(surfaceTexture); |
- try { |
- camera.createCaptureSession( |
- Arrays.asList(surface), new CaptureSessionCallback(), cameraThreadHandler); |
- } catch (CameraAccessException e) { |
- reportError("Failed to create capture session. " + e); |
- } |
- } |
- |
- @Override |
- public void onClosed(CameraDevice camera) { |
- checkIsOnCameraThread(); |
- |
- Logging.d(TAG, "Camera device closed."); |
- |
- if (cameraState != CameraState.STOPPING) { |
- Logging.e(TAG, "Camera state was not STOPPING in onClosed. Most likely camera didn't stop " |
- + "within timelimit and this method was invoked twice."); |
- return; |
- } |
- |
- cameraThreadHandler.removeCallbacksAndMessages(STOP_TIMEOUT_RUNNABLE_TOKEN); |
- setCameraState(CameraState.IDLE); |
- if (eventsHandler != null) { |
- eventsHandler.onCameraClosed(); |
- } |
- } |
- } |
- |
- final class CaptureSessionCallback extends CameraCaptureSession.StateCallback { |
- @Override |
- public void onConfigureFailed(CameraCaptureSession session) { |
- checkIsOnCameraThread(); |
- captureSession = session; |
- reportError("Failed to configure capture session."); |
- } |
- |
- @Override |
- public void onConfigured(CameraCaptureSession session) { |
- checkIsOnCameraThread(); |
- Logging.d(TAG, "Camera capture session configured."); |
- captureSession = session; |
- try { |
- /* |
- * The viable options for video capture requests are: |
- * TEMPLATE_PREVIEW: High frame rate is given priority over the highest-quality |
- * post-processing. |
- * TEMPLATE_RECORD: Stable frame rate is used, and post-processing is set for recording |
- * quality. |
- */ |
- final CaptureRequest.Builder captureRequestBuilder = |
- cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); |
- // Set auto exposure fps range. |
- captureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range<Integer>( |
- captureFormat.framerate.min / fpsUnitFactor, |
- captureFormat.framerate.max / fpsUnitFactor)); |
- captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, |
- CaptureRequest.CONTROL_AE_MODE_ON); |
- captureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false); |
- |
- captureRequestBuilder.addTarget(surface); |
- session.setRepeatingRequest( |
- captureRequestBuilder.build(), new CameraCaptureCallback(), cameraThreadHandler); |
- } catch (CameraAccessException e) { |
- reportError("Failed to start capture request. " + e); |
- return; |
- } |
- |
- Logging.d(TAG, "Camera device successfully started."); |
- surfaceTextureHelper.startListening(Camera2Capturer.this); |
- capturerObserver.onCapturerStarted(true /* success */); |
- cameraStatistics = new CameraStatistics(surfaceTextureHelper, eventsHandler); |
- setCameraState(CameraState.RUNNING); |
- |
- if (switchEventsHandler != null) { |
- switchEventsHandler.onCameraSwitchDone(isFrontCamera); |
- switchEventsHandler = null; |
- } |
- isPendingCameraSwitch.set(false); |
- } |
- } |
- |
- final class CameraCaptureCallback extends CameraCaptureSession.CaptureCallback { |
- static final int MAX_CONSECUTIVE_CAMERA_CAPTURE_FAILURES = 10; |
- int consecutiveCameraCaptureFailures; |
- |
- @Override |
- public void onCaptureFailed( |
- CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) { |
- checkIsOnCameraThread(); |
- ++consecutiveCameraCaptureFailures; |
- if (consecutiveCameraCaptureFailures > MAX_CONSECUTIVE_CAMERA_CAPTURE_FAILURES) { |
- reportError("Capture failed " + consecutiveCameraCaptureFailures + " consecutive times."); |
- } |
- } |
- |
- @Override |
- public void onCaptureCompleted( |
- CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { |
- // TODO(sakal): This sometimes gets called after camera has stopped, investigate |
- checkIsOnCameraThread(); |
- consecutiveCameraCaptureFailures = 0; |
- } |
- } |
- |
- |
- |
- // Switch camera to the next valid camera id. This can only be called while |
- // the camera is running. |
- @Override |
- public void switchCamera(final CameraSwitchHandler switchEventsHandler) { |
- final String[] cameraIds; |
- try { |
- cameraIds = cameraManager.getCameraIdList(); |
- } catch (CameraAccessException e) { |
- if (switchEventsHandler != null) { |
- switchEventsHandler.onCameraSwitchError("Could not get camera names: " + e); |
- } |
- return; |
- } |
- if (cameraIds.length < 2) { |
- if (switchEventsHandler != null) { |
- switchEventsHandler.onCameraSwitchError("No camera to switch to."); |
- } |
- 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 |
- // |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."); |
- } |
- return; |
- } |
- |
- final String newCameraId; |
- final int requestedWidth; |
- final int requestedHeight; |
- final int requestedFramerate; |
- |
- synchronized (cameraStateLock) { |
- waitForCameraToStartIfStarting(); |
- |
- if (cameraState != CameraState.RUNNING) { |
- Logging.e(TAG, "Calling swithCamera() on stopped camera."); |
- if (switchEventsHandler != null) { |
- switchEventsHandler.onCameraSwitchError("Camera is stopped."); |
- } |
- isPendingCameraSwitch.set(false); |
- return; |
- } |
- |
- // Calculate new camera index and camera id. Camera is in state RUNNING so cameraName will |
- // not be edited. |
- final int currentCameraIndex = Arrays.asList(cameraIds).indexOf(cameraName); |
- if (currentCameraIndex == -1) { |
- Logging.e(TAG, "Couldn't find current camera id " + cameraName |
- + " in list of camera ids: " + Arrays.toString(cameraIds)); |
- } |
- final int newCameraIndex = (currentCameraIndex + 1) % cameraIds.length; |
- newCameraId = cameraIds[newCameraIndex]; |
- |
- requestedWidth = this.requestedWidth; |
- requestedHeight = this.requestedHeight; |
- requestedFramerate = this.requestedFramerate; |
- this.switchEventsHandler = switchEventsHandler; |
- } |
- |
- // Make the switch. |
- stopCapture(); |
- setCameraName(newCameraId); |
- startCapture(requestedWidth, requestedHeight, requestedFramerate); |
- |
- // Note: switchEventsHandler will be called from onConfigured / reportError. |
- } |
- |
- // Requests a new output format from the video capturer. Captured frames |
- // by the camera will be scaled/or dropped by the video capturer. |
- // It does not matter if width and height are flipped. I.E, |width| = 640, |height| = 480 produce |
- // the same result as |width| = 480, |height| = 640. |
- // TODO(magjed/perkj): Document what this function does. Change name? |
- @Override |
- public void onOutputFormatRequest(final int width, final int height, final int framerate) { |
- postOnCameraThread(new Runnable() { |
- @Override |
- public void run() { |
- Logging.d(TAG, |
- "onOutputFormatRequestOnCameraThread: " + width + "x" + height + "@" + framerate); |
- capturerObserver.onOutputFormatRequest(width, height, framerate); |
- } |
- }); |
- } |
- |
- // Reconfigure the camera to capture in a new format. This should only be called while the camera |
- // is running. |
- @Override |
- public void changeCaptureFormat(final int width, final int height, final int framerate) { |
- synchronized (cameraStateLock) { |
- waitForCameraToStartIfStarting(); |
- |
- if (cameraState != CameraState.RUNNING) { |
- Logging.e(TAG, "Calling changeCaptureFormat() on stopped camera."); |
- return; |
- } |
- |
- requestedWidth = width; |
- requestedHeight = height; |
- requestedFramerate = framerate; |
- } |
+ public Camera2Capturer(Context context, String cameraName, CameraEventsHandler eventsHandler) { |
+ super(cameraName, eventsHandler, new Camera2Enumerator(context)); |
- // Make the switch. |
- stopCapture(); |
- // TODO(magjed/sakal): Just recreate session. |
- startCapture(width, height, framerate); |
+ this.context = context; |
+ cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); |
} |
@Override |
public List<CaptureFormat> getSupportedFormats() { |
- synchronized (cameraState) { |
- return Camera2Enumerator.getSupportedFormats(this.cameraManager, cameraName); |
- } |
- } |
- |
- @Override |
- public void dispose() { |
- synchronized (cameraStateLock) { |
- waitForCameraToStopIfStopping(); |
- |
- if (cameraState != CameraState.IDLE) { |
- throw new IllegalStateException("Unexpected camera state for dispose: " + cameraState); |
- } |
- } |
- } |
- |
- // Blocks until camera is known to be stopped. |
- @Override |
- public void stopCapture() { |
- final CountDownLatch cameraStoppingLatch = new CountDownLatch(1); |
- |
- Logging.d(TAG, "stopCapture"); |
- checkNotOnCameraThread(); |
- |
- synchronized (cameraStateLock) { |
- waitForCameraToStartIfStarting(); |
- |
- if (cameraState != CameraState.RUNNING) { |
- Logging.w(TAG, "stopCapture called for already stopped camera."); |
- return; |
- } |
- |
- postOnCameraThread(new Runnable() { |
- @Override |
- public void run() { |
- Logging.d(TAG, "stopCaptureOnCameraThread"); |
- |
- // Stop capture. |
- closeAndRelease(); |
- cameraStoppingLatch.countDown(); |
- } |
- }); |
- } |
- |
- // Wait for the stopping to start |
- ThreadUtils.awaitUninterruptibly(cameraStoppingLatch); |
- |
- Logging.d(TAG, "stopCapture done"); |
- } |
- |
- private void postOnCameraThread(Runnable runnable) { |
- postDelayedOnCameraThread(0 /* delayMs */, runnable); |
- } |
- |
- private void postDelayedOnCameraThread(int delayMs, Runnable runnable) { |
- synchronized (cameraStateLock) { |
- if ((cameraState != CameraState.STARTING && cameraState != CameraState.RUNNING) |
- || !cameraThreadHandler.postAtTime( |
- runnable, this /* token */, SystemClock.uptimeMillis() + delayMs)) { |
- Logging.w(TAG, "Runnable not scheduled even though it was requested."); |
- } |
- } |
- } |
- |
- private int getDeviceOrientation() { |
- int orientation = 0; |
- |
- WindowManager wm = (WindowManager) applicationContext.getSystemService( |
- Context.WINDOW_SERVICE); |
- switch(wm.getDefaultDisplay().getRotation()) { |
- case Surface.ROTATION_90: |
- orientation = 90; |
- break; |
- case Surface.ROTATION_180: |
- orientation = 180; |
- break; |
- case Surface.ROTATION_270: |
- orientation = 270; |
- break; |
- case Surface.ROTATION_0: |
- default: |
- orientation = 0; |
- break; |
- } |
- return orientation; |
- } |
- |
- @Override |
- public void onTextureFrameAvailable( |
- int oesTextureId, float[] transformMatrix, long timestampNs) { |
- checkIsOnCameraThread(); |
- |
- if (cameraState != CameraState.RUNNING) { |
- Logging.d(TAG, "Texture frame received while camera was not running."); |
- return; |
- } |
- |
- if (eventsHandler != null && !firstFrameReported) { |
- eventsHandler.onFirstFrameAvailable(); |
- firstFrameReported = true; |
- } |
- |
- int rotation; |
- if (isFrontCamera) { |
- // Undo the mirror that the OS "helps" us with. |
- // http://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int) |
- rotation = cameraOrientation + getDeviceOrientation(); |
- transformMatrix = |
- RendererCommon.multiplyMatrices(transformMatrix, RendererCommon.horizontalFlipMatrix()); |
- } else { |
- rotation = cameraOrientation - getDeviceOrientation(); |
- } |
- // Make sure |rotation| is between 0 and 360. |
- rotation = (360 + rotation % 360) % 360; |
- |
- // Undo camera orientation - we report it as rotation instead. |
- transformMatrix = RendererCommon.rotateTextureMatrix(transformMatrix, -cameraOrientation); |
- |
- cameraStatistics.addFrame(); |
- capturerObserver.onTextureFrameCaptured(captureFormat.width, captureFormat.height, oesTextureId, |
- transformMatrix, rotation, timestampNs); |
+ return Camera2Enumerator.getSupportedFormats(cameraManager, getCameraName()); |
+ } |
+ |
+ @Override |
+ protected void createCameraSession( |
+ CameraSession.CreateSessionCallback createSessionCallback, |
+ CameraEventsHandler eventsHandler, Context applicationContext, |
+ CameraVideoCapturer.CapturerObserver capturerObserver, |
+ SurfaceTextureHelper surfaceTextureHelper, |
+ String cameraName, int width, int height, int framerate) { |
+ Camera2Session.create( |
+ cameraManager, |
+ createSessionCallback, |
+ eventsHandler, applicationContext, |
+ capturerObserver, |
+ surfaceTextureHelper, |
+ cameraName, width, height, framerate); |
} |
} |