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 |