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 |