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

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

Issue 2078473002: Android: Camera2 implementation and tests for it. (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@enumerator_change
Patch Set: Changes according to magjed's comments #1 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.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 }
OLDNEW
« no previous file with comments | « webrtc/api/java/android/org/webrtc/Camera1Enumerator.java ('k') | webrtc/api/java/android/org/webrtc/Camera2Enumerator.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698