| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright 2016 The WebRTC project authors. All Rights Reserved. | |
| 3 * | |
| 4 * Use of this source code is governed by a BSD-style license | |
| 5 * that can be found in the LICENSE file in the root of the source | |
| 6 * tree. An additional intellectual property rights grant can be found | |
| 7 * in the file PATENTS. All contributing project authors may | |
| 8 * be found in the AUTHORS file in the root of the source tree. | |
| 9 */ | |
| 10 | |
| 11 package org.webrtc; | |
| 12 | |
| 13 import org.webrtc.CameraEnumerationAndroid.CaptureFormat; | |
| 14 | |
| 15 import android.annotation.TargetApi; | |
| 16 import android.content.Context; | |
| 17 import android.graphics.SurfaceTexture; | |
| 18 import android.hardware.camera2.CameraAccessException; | |
| 19 import android.hardware.camera2.CameraCaptureSession; | |
| 20 import android.hardware.camera2.CameraCharacteristics; | |
| 21 import android.hardware.camera2.CameraDevice; | |
| 22 import android.hardware.camera2.CameraManager; | |
| 23 import android.hardware.camera2.CameraMetadata; | |
| 24 import android.hardware.camera2.CaptureFailure; | |
| 25 import android.hardware.camera2.CaptureRequest; | |
| 26 import android.hardware.camera2.TotalCaptureResult; | |
| 27 import android.hardware.camera2.params.StreamConfigurationMap; | |
| 28 import android.os.Build; | |
| 29 import android.os.Handler; | |
| 30 import android.os.SystemClock; | |
| 31 import android.util.Range; | |
| 32 import android.view.Surface; | |
| 33 import android.view.WindowManager; | |
| 34 | |
| 35 import java.util.Arrays; | |
| 36 import java.util.List; | |
| 37 import java.util.concurrent.CountDownLatch; | |
| 38 import java.util.concurrent.Semaphore; | |
| 39 | |
| 40 @TargetApi(21) | |
| 41 public class Camera2Capturer implements | |
| 42 CameraVideoCapturer, | |
| 43 SurfaceTextureHelper.OnTextureFrameAvailableListener { | |
| 44 private final static String TAG = "Camera2Capturer"; | |
| 45 | |
| 46 private final static int MAX_OPEN_CAMERA_ATTEMPTS = 3; | |
| 47 private final static int OPEN_CAMERA_DELAY_MS = 500; | |
| 48 private final static int STOP_TIMEOUT = 10000; | |
| 49 private final static int START_TIMEOUT = 10000; | |
| 50 private final static Object STOP_TIMEOUT_RUNNABLE_TOKEN = new Object(); | |
| 51 | |
| 52 // In the Camera2 API, starting a camera is inherently asynchronous, and this
state is | |
| 53 // represented with 'STARTING'. Stopping is also asynchronous and this state i
s 'STOPPING'. | |
| 54 private static enum CameraState { IDLE, STARTING, RUNNING, STOPPING } | |
| 55 | |
| 56 // Thread safe objects. | |
| 57 // -------------------- | |
| 58 private final CameraManager cameraManager; | |
| 59 private final CameraEventsHandler eventsHandler; | |
| 60 | |
| 61 | |
| 62 // Shared state - guarded by cameraStateLock. Will only be edited from camera
thread (when it is | |
| 63 // running). | |
| 64 // ---------------------------------------------------------------------------
------------------ | |
| 65 private final Object cameraStateLock = new Object(); | |
| 66 private CameraState cameraState = CameraState.IDLE; | |
| 67 // |cameraThreadHandler| must be synchronized on |cameraStateLock| when not on
the camera thread, | |
| 68 // or when modifying the reference. Use postOnCameraThread() instead of postin
g directly to | |
| 69 // the handler - this way all callbacks with a specifed token can be removed a
t once. | |
| 70 // |cameraThreadHandler| must be null if and only if CameraState is IDLE. | |
| 71 private Handler cameraThreadHandler; | |
| 72 // Remember the requested format in case we want to switch cameras. | |
| 73 private int requestedWidth; | |
| 74 private int requestedHeight; | |
| 75 private int requestedFramerate; | |
| 76 | |
| 77 // Will only be edited while camera state is IDLE and cameraStateLock is acqui
red. | |
| 78 private String cameraName; | |
| 79 private boolean isFrontCamera; | |
| 80 private int cameraOrientation; | |
| 81 | |
| 82 // Semaphore for allowing only one switch at a time. | |
| 83 private final Semaphore pendingCameraSwitchSemaphore = new Semaphore(1); | |
| 84 // Guarded by pendingCameraSwitchSemaphore | |
| 85 private CameraSwitchHandler switchEventsHandler; | |
| 86 | |
| 87 // Internal state - must only be modified from camera thread | |
| 88 // --------------------------------------------------------- | |
| 89 private CaptureFormat captureFormat; | |
| 90 private Context applicationContext; | |
| 91 private CapturerObserver capturerObserver; | |
| 92 private CameraStatistics cameraStatistics; | |
| 93 private SurfaceTextureHelper surfaceTextureHelper; | |
| 94 private CameraCaptureSession captureSession; | |
| 95 private Surface surface; | |
| 96 private CameraDevice cameraDevice; | |
| 97 private CameraStateCallback cameraStateCallback; | |
| 98 | |
| 99 // Factor to convert between Android framerates and CaptureFormat.FramerateRan
ge. It will be | |
| 100 // either 1 or 1000. | |
| 101 private int fpsUnitFactor; | |
| 102 private boolean firstFrameReported; | |
| 103 private int consecutiveCameraOpenFailures; | |
| 104 | |
| 105 public Camera2Capturer( | |
| 106 Context context, String cameraName, CameraEventsHandler eventsHandler) { | |
| 107 Logging.d(TAG, "Camera2Capturer ctor, camera name: " + cameraName); | |
| 108 this.cameraManager = (CameraManager) context.getSystemService(Context.CAMERA
_SERVICE); | |
| 109 this.eventsHandler = eventsHandler; | |
| 110 | |
| 111 setCameraName(cameraName); | |
| 112 } | |
| 113 | |
| 114 /** | |
| 115 * Helper method for checking method is executed on camera thread. Also allows
calls from other | |
| 116 * threads if camera is closed. | |
| 117 */ | |
| 118 private void checkIsOnCameraThread() { | |
| 119 if (cameraState == CameraState.IDLE) { | |
| 120 return; | |
| 121 } | |
| 122 | |
| 123 checkIsStrictlyOnCameraThread(); | |
| 124 } | |
| 125 | |
| 126 /** | |
| 127 * Like checkIsOnCameraThread but doesn't allow the camera to be stopped. | |
| 128 */ | |
| 129 private void checkIsStrictlyOnCameraThread() { | |
| 130 if (cameraThreadHandler == null) { | |
| 131 throw new IllegalStateException("Camera is closed."); | |
| 132 } | |
| 133 | |
| 134 if (Thread.currentThread() != cameraThreadHandler.getLooper().getThread()) { | |
| 135 throw new IllegalStateException("Wrong thread"); | |
| 136 } | |
| 137 } | |
| 138 | |
| 139 /** | |
| 140 * Checks method is not invoked on the camera thread. Used in functions waitin
g for the camera | |
| 141 * state to change since executing them on the camera thread would cause a dea
dlock. | |
| 142 */ | |
| 143 private void checkNotOnCameraThread() { | |
| 144 if (cameraThreadHandler == null) { | |
| 145 return; | |
| 146 } | |
| 147 | |
| 148 if (Thread.currentThread() == cameraThreadHandler.getLooper().getThread()) { | |
| 149 throw new IllegalStateException( | |
| 150 "Method waiting for camera state to change executed on camera thread")
; | |
| 151 } | |
| 152 } | |
| 153 | |
| 154 private void waitForCameraToExitTransitionalState( | |
| 155 CameraState transitionalState, long timeoutMs) { | |
| 156 checkNotOnCameraThread(); | |
| 157 | |
| 158 // We probably should already have the lock when this is called but acquire
it in case | |
| 159 // we don't have it. | |
| 160 synchronized (cameraStateLock) { | |
| 161 long timeoutAt = SystemClock.uptimeMillis() + timeoutMs; | |
| 162 | |
| 163 while (cameraState == transitionalState) { | |
| 164 Logging.d(TAG, "waitForCameraToExitTransitionalState waiting: " | |
| 165 + cameraState); | |
| 166 | |
| 167 long timeLeft = timeoutAt - SystemClock.uptimeMillis(); | |
| 168 | |
| 169 if (timeLeft <= 0) { | |
| 170 Logging.e(TAG, "Camera failed to exit transitional state " + transitio
nalState | |
| 171 + " within the time limit."); | |
| 172 break; | |
| 173 } | |
| 174 | |
| 175 try { | |
| 176 cameraStateLock.wait(timeLeft); | |
| 177 } catch (InterruptedException e) { | |
| 178 Logging.w(TAG, "Trying to interrupt while waiting to exit transitional
state " | |
| 179 + transitionalState + ", ignoring: " + e); | |
| 180 } | |
| 181 } | |
| 182 } | |
| 183 } | |
| 184 | |
| 185 /** | |
| 186 * Waits until camera state is not STOPPING. | |
| 187 */ | |
| 188 private void waitForCameraToStopIfStopping() { | |
| 189 waitForCameraToExitTransitionalState(CameraState.STOPPING, STOP_TIMEOUT); | |
| 190 } | |
| 191 | |
| 192 /** | |
| 193 * Wait until camera state is not STARTING. | |
| 194 */ | |
| 195 private void waitForCameraToStartIfStarting() { | |
| 196 waitForCameraToExitTransitionalState(CameraState.STARTING, START_TIMEOUT); | |
| 197 } | |
| 198 | |
| 199 /** | |
| 200 * Sets the name of the camera. Camera must be stopped or stopping when this i
s called. | |
| 201 */ | |
| 202 private void setCameraName(String cameraName) { | |
| 203 final CameraCharacteristics characteristics; | |
| 204 try { | |
| 205 final String[] cameraIds = cameraManager.getCameraIdList(); | |
| 206 | |
| 207 if (cameraName.isEmpty() && cameraIds.length != 0) { | |
| 208 cameraName = cameraIds[0]; | |
| 209 } | |
| 210 | |
| 211 if (!Arrays.asList(cameraIds).contains(cameraName)) { | |
| 212 throw new IllegalArgumentException( | |
| 213 "Camera name: " + cameraName + " does not match any known camera dev
ice:"); | |
| 214 } | |
| 215 | |
| 216 characteristics = cameraManager.getCameraCharacteristics(cameraName); | |
| 217 } catch (CameraAccessException e) { | |
| 218 throw new RuntimeException("Camera access exception: " + e); | |
| 219 } | |
| 220 | |
| 221 synchronized (cameraStateLock) { | |
| 222 waitForCameraToStopIfStopping(); | |
| 223 | |
| 224 if (cameraState != CameraState.IDLE) { | |
| 225 throw new RuntimeException("Changing camera name on running camera."); | |
| 226 } | |
| 227 | |
| 228 // Note: Usually changing camera state from outside camera thread is not a
llowed. It is | |
| 229 // allowed here because camera is not running. | |
| 230 this.cameraName = cameraName; | |
| 231 isFrontCamera = characteristics.get(CameraCharacteristics.LENS_FACING) | |
| 232 == CameraMetadata.LENS_FACING_FRONT; | |
| 233 | |
| 234 /* | |
| 235 * Clockwise angle through which the output image needs to be rotated to b
e upright on the | |
| 236 * device screen in its native orientation. | |
| 237 * Also defines the direction of rolling shutter readout, which is from to
p to bottom in the | |
| 238 * sensor's coordinate system. | |
| 239 * Units: Degrees of clockwise rotation; always a multiple of 90 | |
| 240 */ | |
| 241 cameraOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIEN
TATION); | |
| 242 } | |
| 243 } | |
| 244 | |
| 245 /** | |
| 246 * Triggers appropriate error handlers based on the camera state. Must be call
ed on the camera | |
| 247 * thread and camera must not be stopped. | |
| 248 */ | |
| 249 private void reportError(String errorDescription) { | |
| 250 checkIsStrictlyOnCameraThread(); | |
| 251 Logging.e(TAG, "Error in camera at state " + cameraState + ": " + errorDescr
iption); | |
| 252 | |
| 253 if (switchEventsHandler != null) { | |
| 254 switchEventsHandler.onCameraSwitchError(errorDescription); | |
| 255 switchEventsHandler = null; | |
| 256 pendingCameraSwitchSemaphore.release(); | |
| 257 } | |
| 258 | |
| 259 switch (cameraState) { | |
| 260 case STARTING: | |
| 261 capturerObserver.onCapturerStarted(false /* success */); | |
| 262 // fall through | |
| 263 case RUNNING: | |
| 264 if (eventsHandler != null) { | |
| 265 eventsHandler.onCameraError(errorDescription); | |
| 266 } | |
| 267 break; | |
| 268 case STOPPING: | |
| 269 setCameraState(CameraState.IDLE); | |
| 270 Logging.e(TAG, "Closing camera failed: " + errorDescription); | |
| 271 return; // We don't want to call closeAndRelease in this case. | |
| 272 default: | |
| 273 throw new RuntimeException("Unknown camera state: " + cameraState); | |
| 274 } | |
| 275 closeAndRelease(); | |
| 276 } | |
| 277 | |
| 278 private void closeAndRelease() { | |
| 279 checkIsStrictlyOnCameraThread(); | |
| 280 | |
| 281 Logging.d(TAG, "Close and release."); | |
| 282 setCameraState(CameraState.STOPPING); | |
| 283 | |
| 284 // Remove all pending Runnables posted from |this|. | |
| 285 cameraThreadHandler.removeCallbacksAndMessages(this /* token */); | |
| 286 applicationContext = null; | |
| 287 capturerObserver = null; | |
| 288 if (cameraStatistics != null) { | |
| 289 cameraStatistics.release(); | |
| 290 cameraStatistics = null; | |
| 291 } | |
| 292 if (surfaceTextureHelper != null) { | |
| 293 surfaceTextureHelper.stopListening(); | |
| 294 surfaceTextureHelper = null; | |
| 295 } | |
| 296 if (captureSession != null) { | |
| 297 captureSession.close(); | |
| 298 captureSession = null; | |
| 299 } | |
| 300 if (surface != null) { | |
| 301 surface.release(); | |
| 302 surface = null; | |
| 303 } | |
| 304 if (cameraDevice != null) { | |
| 305 // Add a timeout for stopping the camera. | |
| 306 cameraThreadHandler.postAtTime(new Runnable() { | |
| 307 @Override | |
| 308 public void run() { | |
| 309 Logging.e(TAG, "Camera failed to stop within the timeout. Force stoppi
ng."); | |
| 310 setCameraState(CameraState.IDLE); | |
| 311 if (eventsHandler != null) { | |
| 312 eventsHandler.onCameraError("Camera failed to stop (timeout)."); | |
| 313 } | |
| 314 } | |
| 315 }, STOP_TIMEOUT_RUNNABLE_TOKEN, SystemClock.uptimeMillis() + STOP_TIMEOUT)
; | |
| 316 | |
| 317 cameraDevice.close(); | |
| 318 cameraDevice = null; | |
| 319 } else { | |
| 320 Logging.w(TAG, "closeAndRelease called while cameraDevice is null"); | |
| 321 setCameraState(CameraState.IDLE); | |
| 322 } | |
| 323 this.cameraStateCallback = null; | |
| 324 } | |
| 325 | |
| 326 /** | |
| 327 * Sets the camera state while ensuring constraints are followed. | |
| 328 */ | |
| 329 private void setCameraState(CameraState newState) { | |
| 330 // State must only be modified on the camera thread. It can be edited from o
ther threads | |
| 331 // if cameraState is IDLE since there is no camera thread. | |
| 332 checkIsOnCameraThread(); | |
| 333 | |
| 334 if (newState != CameraState.IDLE) { | |
| 335 if (cameraThreadHandler == null) { | |
| 336 throw new IllegalStateException( | |
| 337 "cameraThreadHandler must be null if and only if CameraState is IDLE
."); | |
| 338 } | |
| 339 } else { | |
| 340 cameraThreadHandler = null; | |
| 341 } | |
| 342 | |
| 343 switch (newState) { | |
| 344 case STARTING: | |
| 345 if (cameraState != CameraState.IDLE) { | |
| 346 throw new IllegalStateException("Only stopped camera can start."); | |
| 347 } | |
| 348 break; | |
| 349 case RUNNING: | |
| 350 if (cameraState != CameraState.STARTING) { | |
| 351 throw new IllegalStateException("Only starting camera can go to runnin
g state."); | |
| 352 } | |
| 353 break; | |
| 354 case STOPPING: | |
| 355 if (cameraState != CameraState.STARTING && cameraState != CameraState.RU
NNING) { | |
| 356 throw new IllegalStateException("Only starting or running camera can s
top."); | |
| 357 } | |
| 358 break; | |
| 359 case IDLE: | |
| 360 if (cameraState != CameraState.STOPPING) { | |
| 361 throw new IllegalStateException("Only stopping camera can go to idle s
tate."); | |
| 362 } | |
| 363 break; | |
| 364 default: | |
| 365 throw new RuntimeException("Unknown camera state: " + newState); | |
| 366 } | |
| 367 | |
| 368 synchronized (cameraStateLock) { | |
| 369 cameraState = newState; | |
| 370 cameraStateLock.notifyAll(); | |
| 371 } | |
| 372 } | |
| 373 | |
| 374 /** | |
| 375 * Internal method for opening the camera. Must be called on the camera thread
. | |
| 376 */ | |
| 377 private void openCamera() { | |
| 378 try { | |
| 379 checkIsStrictlyOnCameraThread(); | |
| 380 | |
| 381 if (cameraState != CameraState.STARTING) { | |
| 382 throw new IllegalStateException("Camera should be in state STARTING in o
penCamera."); | |
| 383 } | |
| 384 | |
| 385 if (cameraThreadHandler == null) { | |
| 386 throw new RuntimeException("Someone set cameraThreadHandler to null whil
e the camera " | |
| 387 + "state was STARTING. This should never happen"); | |
| 388 } | |
| 389 | |
| 390 // Camera is in state STARTING so cameraName will not be edited. | |
| 391 cameraManager.openCamera(cameraName, cameraStateCallback, cameraThreadHand
ler); | |
| 392 } catch (CameraAccessException e) { | |
| 393 reportError("Failed to open camera: " + e); | |
| 394 } | |
| 395 } | |
| 396 | |
| 397 private void startCaptureOnCameraThread( | |
| 398 final int requestedWidth, final int requestedHeight, final int requestedFr
amerate, | |
| 399 final SurfaceTextureHelper surfaceTextureHelper, final Context application
Context, | |
| 400 final CapturerObserver capturerObserver) { | |
| 401 checkIsStrictlyOnCameraThread(); | |
| 402 | |
| 403 firstFrameReported = false; | |
| 404 consecutiveCameraOpenFailures = 0; | |
| 405 | |
| 406 this.applicationContext = applicationContext; | |
| 407 this.capturerObserver = capturerObserver; | |
| 408 this.surfaceTextureHelper = surfaceTextureHelper; | |
| 409 this.cameraStateCallback = new CameraStateCallback(); | |
| 410 | |
| 411 synchronized (cameraStateLock) { | |
| 412 // Remember the requested format in case we want to switch cameras. | |
| 413 this.requestedWidth = requestedWidth; | |
| 414 this.requestedHeight = requestedHeight; | |
| 415 this.requestedFramerate = requestedFramerate; | |
| 416 } | |
| 417 | |
| 418 final CameraCharacteristics cameraCharacteristics; | |
| 419 try { | |
| 420 // Camera is in state STARTING so cameraName will not be edited. | |
| 421 cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraName)
; | |
| 422 } catch (CameraAccessException e) { | |
| 423 reportError("getCameraCharacteristics(): " + e.getMessage()); | |
| 424 return; | |
| 425 } | |
| 426 | |
| 427 List<CaptureFormat.FramerateRange> framerateRanges = | |
| 428 Camera2Enumerator.getSupportedFramerateRanges(cameraCharacteristics); | |
| 429 List<Size> sizes = Camera2Enumerator.getSupportedSizes(cameraCharacteristics
); | |
| 430 | |
| 431 if (framerateRanges.isEmpty() || sizes.isEmpty()) { | |
| 432 reportError("No supported capture formats."); | |
| 433 } | |
| 434 | |
| 435 // Some LEGACY camera implementations use fps rates that are multiplied with
1000. Make sure | |
| 436 // all values are multiplied with 1000 for consistency. | |
| 437 this.fpsUnitFactor = (framerateRanges.get(0).max > 1000) ? 1 : 1000; | |
| 438 | |
| 439 final CaptureFormat.FramerateRange bestFpsRange = | |
| 440 CameraEnumerationAndroid.getClosestSupportedFramerateRange( | |
| 441 framerateRanges, requestedFramerate); | |
| 442 | |
| 443 final Size bestSize = CameraEnumerationAndroid.getClosestSupportedSize( | |
| 444 sizes, requestedWidth, requestedHeight); | |
| 445 | |
| 446 this.captureFormat = new CaptureFormat(bestSize.width, bestSize.height, best
FpsRange); | |
| 447 Logging.d(TAG, "Using capture format: " + captureFormat); | |
| 448 | |
| 449 Logging.d(TAG, "Opening camera " + cameraName); | |
| 450 if (eventsHandler != null) { | |
| 451 int cameraIndex = -1; | |
| 452 try { | |
| 453 cameraIndex = Integer.parseInt(cameraName); | |
| 454 } catch (NumberFormatException e) { | |
| 455 Logging.d(TAG, "External camera with non-int identifier: " + cameraName)
; | |
| 456 } | |
| 457 eventsHandler.onCameraOpening(cameraIndex); | |
| 458 } | |
| 459 | |
| 460 openCamera(); | |
| 461 } | |
| 462 | |
| 463 /** | |
| 464 * Starts capture using specified settings. This is automatically called for y
ou by | |
| 465 * VideoCapturerTrackSource if you are just using the camera as source for vid
eo track. | |
| 466 */ | |
| 467 @Override | |
| 468 public void startCapture( | |
| 469 final int requestedWidth, final int requestedHeight, final int requestedFr
amerate, | |
| 470 final SurfaceTextureHelper surfaceTextureHelper, final Context application
Context, | |
| 471 final CapturerObserver capturerObserver) { | |
| 472 Logging.d(TAG, "startCapture requested: " + requestedWidth + "x" + requested
Height | |
| 473 + "@" + requestedFramerate); | |
| 474 if (surfaceTextureHelper == null) { | |
| 475 throw new IllegalArgumentException("surfaceTextureHelper not set."); | |
| 476 } | |
| 477 if (applicationContext == null) { | |
| 478 throw new IllegalArgumentException("applicationContext not set."); | |
| 479 } | |
| 480 if (capturerObserver == null) { | |
| 481 throw new IllegalArgumentException("capturerObserver not set."); | |
| 482 } | |
| 483 | |
| 484 synchronized (cameraStateLock) { | |
| 485 waitForCameraToStopIfStopping(); | |
| 486 if (cameraState != CameraState.IDLE) { | |
| 487 Logging.e(TAG, "Unexpected camera state for startCapture: " + cameraStat
e); | |
| 488 return; | |
| 489 } | |
| 490 this.cameraThreadHandler = surfaceTextureHelper.getHandler(); | |
| 491 setCameraState(CameraState.STARTING); | |
| 492 } | |
| 493 | |
| 494 postOnCameraThread(new Runnable() { | |
| 495 @Override | |
| 496 public void run() { | |
| 497 startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFra
merate, | |
| 498 surfaceTextureHelper, applicationContext, capturerObserver); | |
| 499 } | |
| 500 }); | |
| 501 } | |
| 502 | |
| 503 final class CameraStateCallback extends CameraDevice.StateCallback { | |
| 504 private String getErrorDescription(int errorCode) { | |
| 505 switch (errorCode) { | |
| 506 case CameraDevice.StateCallback.ERROR_CAMERA_DEVICE: | |
| 507 return "Camera device has encountered a fatal error."; | |
| 508 case CameraDevice.StateCallback.ERROR_CAMERA_DISABLED: | |
| 509 return "Camera device could not be opened due to a device policy."; | |
| 510 case CameraDevice.StateCallback.ERROR_CAMERA_IN_USE: | |
| 511 return "Camera device is in use already."; | |
| 512 case CameraDevice.StateCallback.ERROR_CAMERA_SERVICE: | |
| 513 return "Camera service has encountered a fatal error."; | |
| 514 case CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE: | |
| 515 return "Camera device could not be opened because" | |
| 516 + " there are too many other open camera devices."; | |
| 517 default: | |
| 518 return "Unknown camera error: " + errorCode; | |
| 519 } | |
| 520 } | |
| 521 | |
| 522 @Override | |
| 523 public void onDisconnected(CameraDevice camera) { | |
| 524 checkIsStrictlyOnCameraThread(); | |
| 525 cameraDevice = camera; | |
| 526 reportError("Camera disconnected."); | |
| 527 } | |
| 528 | |
| 529 @Override | |
| 530 public void onError(CameraDevice camera, int errorCode) { | |
| 531 checkIsStrictlyOnCameraThread(); | |
| 532 cameraDevice = camera; | |
| 533 | |
| 534 if (cameraState == CameraState.STARTING && ( | |
| 535 errorCode == CameraDevice.StateCallback.ERROR_CAMERA_IN_USE || | |
| 536 errorCode == CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE)) { | |
| 537 consecutiveCameraOpenFailures++; | |
| 538 | |
| 539 if (consecutiveCameraOpenFailures < MAX_OPEN_CAMERA_ATTEMPTS) { | |
| 540 Logging.w(TAG, "Opening camera failed, trying again: " + getErrorDescr
iption(errorCode)); | |
| 541 | |
| 542 postDelayedOnCameraThread(OPEN_CAMERA_DELAY_MS, new Runnable() { | |
| 543 public void run() { | |
| 544 openCamera(); | |
| 545 } | |
| 546 }); | |
| 547 return; | |
| 548 } else { | |
| 549 Logging.e(TAG, "Opening camera failed too many times. Passing the erro
r."); | |
| 550 } | |
| 551 } | |
| 552 | |
| 553 reportError(getErrorDescription(errorCode)); | |
| 554 } | |
| 555 | |
| 556 @Override | |
| 557 public void onOpened(CameraDevice camera) { | |
| 558 checkIsStrictlyOnCameraThread(); | |
| 559 | |
| 560 Logging.d(TAG, "Camera opened."); | |
| 561 if (cameraState != CameraState.STARTING) { | |
| 562 throw new IllegalStateException("Unexpected state when camera opened: "
+ cameraState); | |
| 563 } | |
| 564 | |
| 565 cameraDevice = camera; | |
| 566 final SurfaceTexture surfaceTexture = surfaceTextureHelper.getSurfaceTextu
re(); | |
| 567 surfaceTexture.setDefaultBufferSize(captureFormat.width, captureFormat.hei
ght); | |
| 568 surface = new Surface(surfaceTexture); | |
| 569 try { | |
| 570 camera.createCaptureSession( | |
| 571 Arrays.asList(surface), new CaptureSessionCallback(), cameraThreadHa
ndler); | |
| 572 } catch (CameraAccessException e) { | |
| 573 reportError("Failed to create capture session. " + e); | |
| 574 } | |
| 575 } | |
| 576 | |
| 577 @Override | |
| 578 public void onClosed(CameraDevice camera) { | |
| 579 checkIsStrictlyOnCameraThread(); | |
| 580 | |
| 581 Logging.d(TAG, "Camera device closed."); | |
| 582 | |
| 583 if (cameraState != CameraState.STOPPING) { | |
| 584 Logging.e(TAG, "Camera state was not STOPPING in onClosed. Most likely c
amera didn't stop " | |
| 585 + "within timelimit and this method was invoked twice."); | |
| 586 return; | |
| 587 } | |
| 588 | |
| 589 cameraThreadHandler.removeCallbacksAndMessages(STOP_TIMEOUT_RUNNABLE_TOKEN
); | |
| 590 setCameraState(CameraState.IDLE); | |
| 591 if (eventsHandler != null) { | |
| 592 eventsHandler.onCameraClosed(); | |
| 593 } | |
| 594 } | |
| 595 } | |
| 596 | |
| 597 final class CaptureSessionCallback extends CameraCaptureSession.StateCallback
{ | |
| 598 @Override | |
| 599 public void onConfigureFailed(CameraCaptureSession session) { | |
| 600 checkIsStrictlyOnCameraThread(); | |
| 601 captureSession = session; | |
| 602 reportError("Failed to configure capture session."); | |
| 603 } | |
| 604 | |
| 605 @Override | |
| 606 public void onConfigured(CameraCaptureSession session) { | |
| 607 checkIsStrictlyOnCameraThread(); | |
| 608 Logging.d(TAG, "Camera capture session configured."); | |
| 609 captureSession = session; | |
| 610 try { | |
| 611 /* | |
| 612 * The viable options for video capture requests are: | |
| 613 * TEMPLATE_PREVIEW: High frame rate is given priority over the highest-
quality | |
| 614 * post-processing. | |
| 615 * TEMPLATE_RECORD: Stable frame rate is used, and post-processing is se
t for recording | |
| 616 * quality. | |
| 617 */ | |
| 618 final CaptureRequest.Builder captureRequestBuilder = | |
| 619 cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); | |
| 620 // Set auto exposure fps range. | |
| 621 captureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, ne
w Range<Integer>( | |
| 622 captureFormat.framerate.min / fpsUnitFactor, | |
| 623 captureFormat.framerate.max / fpsUnitFactor)); | |
| 624 captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, | |
| 625 CaptureRequest.CONTROL_AE_MODE_ON); | |
| 626 captureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false); | |
| 627 | |
| 628 captureRequestBuilder.addTarget(surface); | |
| 629 session.setRepeatingRequest( | |
| 630 captureRequestBuilder.build(), new CameraCaptureCallback(), cameraTh
readHandler); | |
| 631 } catch (CameraAccessException e) { | |
| 632 reportError("Failed to start capture request. " + e); | |
| 633 return; | |
| 634 } | |
| 635 | |
| 636 Logging.d(TAG, "Camera device successfully started."); | |
| 637 surfaceTextureHelper.startListening(Camera2Capturer.this); | |
| 638 capturerObserver.onCapturerStarted(true /* success */); | |
| 639 cameraStatistics = new CameraStatistics(surfaceTextureHelper, eventsHandle
r); | |
| 640 setCameraState(CameraState.RUNNING); | |
| 641 | |
| 642 if (switchEventsHandler != null) { | |
| 643 switchEventsHandler.onCameraSwitchDone(isFrontCamera); | |
| 644 switchEventsHandler = null; | |
| 645 pendingCameraSwitchSemaphore.release(); | |
| 646 } | |
| 647 } | |
| 648 } | |
| 649 | |
| 650 final class CameraCaptureCallback extends CameraCaptureSession.CaptureCallback
{ | |
| 651 static final int MAX_CONSECUTIVE_CAMERA_CAPTURE_FAILURES = 10; | |
| 652 int consecutiveCameraCaptureFailures; | |
| 653 | |
| 654 @Override | |
| 655 public void onCaptureFailed( | |
| 656 CameraCaptureSession session, CaptureRequest request, CaptureFailure fai
lure) { | |
| 657 checkIsOnCameraThread(); | |
| 658 ++consecutiveCameraCaptureFailures; | |
| 659 if (consecutiveCameraCaptureFailures > MAX_CONSECUTIVE_CAMERA_CAPTURE_FAIL
URES) { | |
| 660 reportError("Capture failed " + consecutiveCameraCaptureFailures + " con
secutive times."); | |
| 661 } | |
| 662 } | |
| 663 | |
| 664 @Override | |
| 665 public void onCaptureCompleted( | |
| 666 CameraCaptureSession session, CaptureRequest request, TotalCaptureResu
lt result) { | |
| 667 // TODO(sakal): This sometimes gets called after camera has stopped, inves
tigate | |
| 668 checkIsOnCameraThread(); | |
| 669 consecutiveCameraCaptureFailures = 0; | |
| 670 } | |
| 671 } | |
| 672 | |
| 673 | |
| 674 | |
| 675 // Switch camera to the next valid camera id. This can only be called while | |
| 676 // the camera is running. | |
| 677 @Override | |
| 678 public void switchCamera(final CameraSwitchHandler switchEventsHandler) { | |
| 679 final String[] cameraIds; | |
| 680 try { | |
| 681 cameraIds = cameraManager.getCameraIdList(); | |
| 682 } catch (CameraAccessException e) { | |
| 683 if (switchEventsHandler != null) { | |
| 684 switchEventsHandler.onCameraSwitchError("Could not get camera names: " +
e); | |
| 685 } | |
| 686 return; | |
| 687 } | |
| 688 if (cameraIds.length < 2) { | |
| 689 if (switchEventsHandler != null) { | |
| 690 switchEventsHandler.onCameraSwitchError("No camera to switch to."); | |
| 691 } | |
| 692 return; | |
| 693 } | |
| 694 // Do not handle multiple camera switch request to avoid blocking camera thr
ead by handling too | |
| 695 // many switch request from a queue. We have to be careful to always release
this. | |
| 696 if (!pendingCameraSwitchSemaphore.tryAcquire()) { | |
| 697 Logging.w(TAG, "Ignoring camera switch request."); | |
| 698 if (switchEventsHandler != null) { | |
| 699 switchEventsHandler.onCameraSwitchError("Pending camera switch already i
n progress."); | |
| 700 } | |
| 701 return; | |
| 702 } | |
| 703 | |
| 704 final String newCameraId; | |
| 705 final SurfaceTextureHelper surfaceTextureHelper; | |
| 706 final Context applicationContext; | |
| 707 final CapturerObserver capturerObserver; | |
| 708 final int requestedWidth; | |
| 709 final int requestedHeight; | |
| 710 final int requestedFramerate; | |
| 711 | |
| 712 synchronized (cameraStateLock) { | |
| 713 waitForCameraToStartIfStarting(); | |
| 714 | |
| 715 if (cameraState != CameraState.RUNNING) { | |
| 716 Logging.e(TAG, "Calling swithCamera() on stopped camera."); | |
| 717 if (switchEventsHandler != null) { | |
| 718 switchEventsHandler.onCameraSwitchError("Camera is stopped."); | |
| 719 } | |
| 720 pendingCameraSwitchSemaphore.release(); | |
| 721 return; | |
| 722 } | |
| 723 | |
| 724 // Calculate new camera index and camera id. Camera is in state RUNNING so
cameraName will | |
| 725 // not be edited. | |
| 726 final int currentCameraIndex = Arrays.asList(cameraIds).indexOf(cameraName
); | |
| 727 if (currentCameraIndex == -1) { | |
| 728 Logging.e(TAG, "Couldn't find current camera id " + cameraName | |
| 729 + " in list of camera ids: " + Arrays.toString(cameraIds)); | |
| 730 } | |
| 731 final int newCameraIndex = (currentCameraIndex + 1) % cameraIds.length; | |
| 732 newCameraId = cameraIds[newCameraIndex]; | |
| 733 | |
| 734 // Remember parameters. These are not null since camera is in RUNNING stat
e. They aren't | |
| 735 // edited either while camera is in RUNNING state. | |
| 736 surfaceTextureHelper = this.surfaceTextureHelper; | |
| 737 applicationContext = this.applicationContext; | |
| 738 capturerObserver = this.capturerObserver; | |
| 739 requestedWidth = this.requestedWidth; | |
| 740 requestedHeight = this.requestedHeight; | |
| 741 requestedFramerate = this.requestedFramerate; | |
| 742 this.switchEventsHandler = switchEventsHandler; | |
| 743 } | |
| 744 | |
| 745 // Make the switch. | |
| 746 stopCapture(); | |
| 747 setCameraName(newCameraId); | |
| 748 startCapture(requestedWidth, requestedHeight, requestedFramerate, surfaceTex
tureHelper, | |
| 749 applicationContext, capturerObserver); | |
| 750 | |
| 751 // Note: switchEventsHandler will be called from onConfigured / reportError. | |
| 752 } | |
| 753 | |
| 754 // Requests a new output format from the video capturer. Captured frames | |
| 755 // by the camera will be scaled/or dropped by the video capturer. | |
| 756 // It does not matter if width and height are flipped. I.E, |width| = 640, |he
ight| = 480 produce | |
| 757 // the same result as |width| = 480, |height| = 640. | |
| 758 // TODO(magjed/perkj): Document what this function does. Change name? | |
| 759 @Override | |
| 760 public void onOutputFormatRequest(final int width, final int height, final int
framerate) { | |
| 761 postOnCameraThread(new Runnable() { | |
| 762 @Override | |
| 763 public void run() { | |
| 764 if (capturerObserver == null) { | |
| 765 Logging.e(TAG, "Calling onOutputFormatRequest() on stopped camera."); | |
| 766 return; | |
| 767 } | |
| 768 Logging.d(TAG, | |
| 769 "onOutputFormatRequestOnCameraThread: " + width + "x" + height + "@"
+ framerate); | |
| 770 capturerObserver.onOutputFormatRequest(width, height, framerate); | |
| 771 } | |
| 772 }); | |
| 773 } | |
| 774 | |
| 775 // Reconfigure the camera to capture in a new format. This should only be call
ed while the camera | |
| 776 // is running. | |
| 777 @Override | |
| 778 public void changeCaptureFormat(final int width, final int height, final int f
ramerate) { | |
| 779 final SurfaceTextureHelper surfaceTextureHelper; | |
| 780 final Context applicationContext; | |
| 781 final CapturerObserver capturerObserver; | |
| 782 | |
| 783 synchronized (cameraStateLock) { | |
| 784 waitForCameraToStartIfStarting(); | |
| 785 | |
| 786 if (cameraState != CameraState.RUNNING) { | |
| 787 Logging.e(TAG, "Calling changeCaptureFormat() on stopped camera."); | |
| 788 return; | |
| 789 } | |
| 790 | |
| 791 requestedWidth = width; | |
| 792 requestedHeight = height; | |
| 793 requestedFramerate = framerate; | |
| 794 | |
| 795 surfaceTextureHelper = this.surfaceTextureHelper; | |
| 796 applicationContext = this.applicationContext; | |
| 797 capturerObserver = this.capturerObserver; | |
| 798 } | |
| 799 | |
| 800 // Make the switch. | |
| 801 stopCapture(); | |
| 802 // TODO(magjed/sakal): Just recreate session. | |
| 803 startCapture(width, height, framerate, | |
| 804 surfaceTextureHelper, applicationContext, capturerObserver); | |
| 805 } | |
| 806 | |
| 807 @Override | |
| 808 public List<CaptureFormat> getSupportedFormats() { | |
| 809 synchronized (cameraState) { | |
| 810 return Camera2Enumerator.getSupportedFormats(this.cameraManager, cameraNam
e); | |
| 811 } | |
| 812 } | |
| 813 | |
| 814 @Override | |
| 815 public void dispose() { | |
| 816 synchronized (cameraStateLock) { | |
| 817 waitForCameraToStopIfStopping(); | |
| 818 | |
| 819 if (cameraState != CameraState.IDLE) { | |
| 820 throw new IllegalStateException("Unexpected camera state for dispose: "
+ cameraState); | |
| 821 } | |
| 822 } | |
| 823 } | |
| 824 | |
| 825 // Blocks until camera is known to be stopped. | |
| 826 @Override | |
| 827 public void stopCapture() { | |
| 828 final CountDownLatch cameraStoppingLatch = new CountDownLatch(1); | |
| 829 | |
| 830 Logging.d(TAG, "stopCapture"); | |
| 831 checkNotOnCameraThread(); | |
| 832 | |
| 833 synchronized (cameraStateLock) { | |
| 834 waitForCameraToStartIfStarting(); | |
| 835 | |
| 836 if (cameraState != CameraState.RUNNING) { | |
| 837 Logging.w(TAG, "stopCapture called for already stopped camera."); | |
| 838 return; | |
| 839 } | |
| 840 | |
| 841 postOnCameraThread(new Runnable() { | |
| 842 @Override | |
| 843 public void run() { | |
| 844 Logging.d(TAG, "stopCaptureOnCameraThread"); | |
| 845 | |
| 846 // Stop capture. | |
| 847 closeAndRelease(); | |
| 848 cameraStoppingLatch.countDown(); | |
| 849 } | |
| 850 }); | |
| 851 } | |
| 852 | |
| 853 // Wait for the stopping to start | |
| 854 ThreadUtils.awaitUninterruptibly(cameraStoppingLatch); | |
| 855 | |
| 856 Logging.d(TAG, "stopCapture done"); | |
| 857 } | |
| 858 | |
| 859 private void postOnCameraThread(Runnable runnable) { | |
| 860 postDelayedOnCameraThread(0 /* delayMs */, runnable); | |
| 861 } | |
| 862 | |
| 863 private void postDelayedOnCameraThread(int delayMs, Runnable runnable) { | |
| 864 synchronized (cameraStateLock) { | |
| 865 if ((cameraState != CameraState.STARTING && cameraState != CameraState.RUN
NING) | |
| 866 || !cameraThreadHandler.postAtTime( | |
| 867 runnable, this /* token */, SystemClock.uptimeMillis() + delayMs))
{ | |
| 868 Logging.w(TAG, "Runnable not scheduled even though it was requested."); | |
| 869 } | |
| 870 } | |
| 871 } | |
| 872 | |
| 873 private int getDeviceOrientation() { | |
| 874 int orientation = 0; | |
| 875 | |
| 876 WindowManager wm = (WindowManager) applicationContext.getSystemService( | |
| 877 Context.WINDOW_SERVICE); | |
| 878 switch(wm.getDefaultDisplay().getRotation()) { | |
| 879 case Surface.ROTATION_90: | |
| 880 orientation = 90; | |
| 881 break; | |
| 882 case Surface.ROTATION_180: | |
| 883 orientation = 180; | |
| 884 break; | |
| 885 case Surface.ROTATION_270: | |
| 886 orientation = 270; | |
| 887 break; | |
| 888 case Surface.ROTATION_0: | |
| 889 default: | |
| 890 orientation = 0; | |
| 891 break; | |
| 892 } | |
| 893 return orientation; | |
| 894 } | |
| 895 | |
| 896 @Override | |
| 897 public void onTextureFrameAvailable( | |
| 898 int oesTextureId, float[] transformMatrix, long timestampNs) { | |
| 899 checkIsStrictlyOnCameraThread(); | |
| 900 | |
| 901 if (eventsHandler != null && !firstFrameReported) { | |
| 902 eventsHandler.onFirstFrameAvailable(); | |
| 903 firstFrameReported = true; | |
| 904 } | |
| 905 | |
| 906 int rotation; | |
| 907 if (isFrontCamera) { | |
| 908 // Undo the mirror that the OS "helps" us with. | |
| 909 // http://developer.android.com/reference/android/hardware/Camera.html#set
DisplayOrientation(int) | |
| 910 rotation = cameraOrientation + getDeviceOrientation(); | |
| 911 transformMatrix = | |
| 912 RendererCommon.multiplyMatrices(transformMatrix, RendererCommon.horizo
ntalFlipMatrix()); | |
| 913 } else { | |
| 914 rotation = cameraOrientation - getDeviceOrientation(); | |
| 915 } | |
| 916 // Make sure |rotation| is between 0 and 360. | |
| 917 rotation = (360 + rotation % 360) % 360; | |
| 918 | |
| 919 // Undo camera orientation - we report it as rotation instead. | |
| 920 transformMatrix = RendererCommon.rotateTextureMatrix(transformMatrix, -camer
aOrientation); | |
| 921 | |
| 922 cameraStatistics.addFrame(); | |
| 923 capturerObserver.onTextureFrameCaptured(captureFormat.width, captureFormat.h
eight, oesTextureId, | |
| 924 transformMatrix, rotation, timestampNs); | |
| 925 } | |
| 926 } | |
| OLD | NEW |