Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(279)

Side by Side Diff: webrtc/api/android/java/src/org/webrtc/Camera2Capturer.java

Issue 2106333005: Revert of Combine webrtc/api/java/android and webrtc/api/java/src. (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698