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