OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2015 The WebRTC project authors. All Rights Reserved. | 2 * Copyright 2015 The WebRTC project authors. All Rights Reserved. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license | 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 | 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 | 6 * tree. An additional intellectual property rights grant can be found |
7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
9 */ | 9 */ |
10 | 10 |
(...skipping 20 matching lines...) Expand all Loading... |
31 // An instance of this class can be created by an application using | 31 // An instance of this class can be created by an application using |
32 // VideoCapturerAndroid.create(); | 32 // VideoCapturerAndroid.create(); |
33 // This class extends VideoCapturer with a method to easily switch between the | 33 // This class extends VideoCapturer with a method to easily switch between the |
34 // front and back camera. It also provides methods for enumerating valid device | 34 // front and back camera. It also provides methods for enumerating valid device |
35 // names. | 35 // names. |
36 // | 36 // |
37 // Threading notes: this class is called from C++ code, Android Camera callbacks
, and possibly | 37 // Threading notes: this class is called from C++ code, Android Camera callbacks
, and possibly |
38 // arbitrary Java threads. All public entry points are thread safe, and delegate
the work to the | 38 // arbitrary Java threads. All public entry points are thread safe, and delegate
the work to the |
39 // camera thread. The internal *OnCameraThread() methods must check |camera| for
null to check if | 39 // camera thread. The internal *OnCameraThread() methods must check |camera| for
null to check if |
40 // the camera has been stopped. | 40 // the camera has been stopped. |
41 // TODO(magjed): This class name is now confusing - rename to Camera1VideoCaptur
er. | |
42 @SuppressWarnings("deprecation") | 41 @SuppressWarnings("deprecation") |
43 public class VideoCapturerAndroid implements | 42 public class VideoCapturerAndroid implements |
44 CameraVideoCapturer, | 43 VideoCapturer, |
45 android.hardware.Camera.PreviewCallback, | 44 android.hardware.Camera.PreviewCallback, |
46 SurfaceTextureHelper.OnTextureFrameAvailableListener { | 45 SurfaceTextureHelper.OnTextureFrameAvailableListener { |
47 private final static String TAG = "VideoCapturerAndroid"; | 46 private final static String TAG = "VideoCapturerAndroid"; |
| 47 private final static int CAMERA_OBSERVER_PERIOD_MS = 2000; |
| 48 private final static int CAMERA_FREEZE_REPORT_TIMOUT_MS = 4000; |
48 private static final int CAMERA_STOP_TIMEOUT_MS = 7000; | 49 private static final int CAMERA_STOP_TIMEOUT_MS = 7000; |
49 | 50 |
50 private boolean isDisposed = false; | 51 private boolean isDisposed = false; |
51 private android.hardware.Camera camera; // Only non-null while capturing. | 52 private android.hardware.Camera camera; // Only non-null while capturing. |
52 private final Object handlerLock = new Object(); | 53 private final Object handlerLock = new Object(); |
53 // |cameraThreadHandler| must be synchronized on |handlerLock| when not on the
camera thread, | 54 // |cameraThreadHandler| must be synchronized on |handlerLock| when not on the
camera thread, |
54 // or when modifying the reference. Use maybePostOnCameraThread() instead of p
osting directly to | 55 // or when modifying the reference. Use maybePostOnCameraThread() instead of p
osting directly to |
55 // the handler - this way all callbacks with a specifed token can be removed a
t once. | 56 // the handler - this way all callbacks with a specifed token can be removed a
t once. |
56 private Handler cameraThreadHandler; | 57 private Handler cameraThreadHandler; |
57 private Context applicationContext; | 58 private Context applicationContext; |
58 // Synchronization lock for |id|. | 59 // Synchronization lock for |id|. |
59 private final Object cameraIdLock = new Object(); | 60 private final Object cameraIdLock = new Object(); |
60 private int id; | 61 private int id; |
61 private android.hardware.Camera.CameraInfo info; | 62 private android.hardware.Camera.CameraInfo info; |
62 private CameraStatistics cameraStatistics; | 63 private final CameraStatistics cameraStatistics; |
63 // Remember the requested format in case we want to switch cameras. | 64 // Remember the requested format in case we want to switch cameras. |
64 private int requestedWidth; | 65 private int requestedWidth; |
65 private int requestedHeight; | 66 private int requestedHeight; |
66 private int requestedFramerate; | 67 private int requestedFramerate; |
67 // The capture format will be the closest supported format to the requested fo
rmat. | 68 // The capture format will be the closest supported format to the requested fo
rmat. |
68 private CaptureFormat captureFormat; | 69 private CaptureFormat captureFormat; |
69 private final Object pendingCameraSwitchLock = new Object(); | 70 private final Object pendingCameraSwitchLock = new Object(); |
70 private volatile boolean pendingCameraSwitch; | 71 private volatile boolean pendingCameraSwitch; |
71 private CapturerObserver frameObserver = null; | 72 private CapturerObserver frameObserver = null; |
72 private final CameraEventsHandler eventsHandler; | 73 private final CameraEventsHandler eventsHandler; |
(...skipping 23 matching lines...) Expand all Loading... |
96 } else { | 97 } else { |
97 errorMessage = "Camera error: " + error; | 98 errorMessage = "Camera error: " + error; |
98 } | 99 } |
99 Logging.e(TAG, errorMessage); | 100 Logging.e(TAG, errorMessage); |
100 if (eventsHandler != null) { | 101 if (eventsHandler != null) { |
101 eventsHandler.onCameraError(errorMessage); | 102 eventsHandler.onCameraError(errorMessage); |
102 } | 103 } |
103 } | 104 } |
104 }; | 105 }; |
105 | 106 |
| 107 // Camera observer - monitors camera framerate. Observer is executed on camera
thread. |
| 108 private final Runnable cameraObserver = new Runnable() { |
| 109 private int freezePeriodCount; |
| 110 @Override |
| 111 public void run() { |
| 112 int cameraFramesCount = cameraStatistics.getAndResetFrameCount(); |
| 113 int cameraFps = (cameraFramesCount * 1000 + CAMERA_OBSERVER_PERIOD_MS / 2) |
| 114 / CAMERA_OBSERVER_PERIOD_MS; |
| 115 |
| 116 Logging.d(TAG, "Camera fps: " + cameraFps +"."); |
| 117 if (cameraFramesCount == 0) { |
| 118 ++freezePeriodCount; |
| 119 if (CAMERA_OBSERVER_PERIOD_MS * freezePeriodCount >= CAMERA_FREEZE_REPOR
T_TIMOUT_MS |
| 120 && eventsHandler != null) { |
| 121 Logging.e(TAG, "Camera freezed."); |
| 122 if (surfaceHelper.isTextureInUse()) { |
| 123 // This can only happen if we are capturing to textures. |
| 124 eventsHandler.onCameraFreezed("Camera failure. Client must return vi
deo buffers."); |
| 125 } else { |
| 126 eventsHandler.onCameraFreezed("Camera failure."); |
| 127 } |
| 128 return; |
| 129 } |
| 130 } else { |
| 131 freezePeriodCount = 0; |
| 132 } |
| 133 maybePostDelayedOnCameraThread(CAMERA_OBSERVER_PERIOD_MS, this); |
| 134 } |
| 135 }; |
| 136 |
| 137 private static class CameraStatistics { |
| 138 private int frameCount = 0; |
| 139 private final ThreadUtils.ThreadChecker threadChecker = new ThreadUtils.Thre
adChecker(); |
| 140 |
| 141 CameraStatistics() { |
| 142 threadChecker.detachThread(); |
| 143 } |
| 144 |
| 145 public void addFrame() { |
| 146 threadChecker.checkIsOnValidThread(); |
| 147 ++frameCount; |
| 148 } |
| 149 |
| 150 public int getAndResetFrameCount() { |
| 151 threadChecker.checkIsOnValidThread(); |
| 152 int count = frameCount; |
| 153 frameCount = 0; |
| 154 return count; |
| 155 } |
| 156 } |
| 157 |
| 158 public static interface CameraEventsHandler { |
| 159 // Camera error handler - invoked when camera can not be opened |
| 160 // or any camera exception happens on camera thread. |
| 161 void onCameraError(String errorDescription); |
| 162 |
| 163 // Invoked when camera stops receiving frames |
| 164 void onCameraFreezed(String errorDescription); |
| 165 |
| 166 // Callback invoked when camera is opening. |
| 167 void onCameraOpening(int cameraId); |
| 168 |
| 169 // Callback invoked when first camera frame is available after camera is ope
ned. |
| 170 void onFirstFrameAvailable(); |
| 171 |
| 172 // Callback invoked when camera closed. |
| 173 void onCameraClosed(); |
| 174 } |
| 175 |
| 176 // Camera switch handler - one of these functions are invoked with the result
of switchCamera(). |
| 177 // The callback may be called on an arbitrary thread. |
| 178 public interface CameraSwitchHandler { |
| 179 // Invoked on success. |isFrontCamera| is true if the new camera is front fa
cing. |
| 180 void onCameraSwitchDone(boolean isFrontCamera); |
| 181 // Invoked on failure, e.g. camera is stopped or only one camera available. |
| 182 void onCameraSwitchError(String errorDescription); |
| 183 } |
| 184 |
106 public static VideoCapturerAndroid create(String name, | 185 public static VideoCapturerAndroid create(String name, |
107 CameraEventsHandler eventsHandler) { | 186 CameraEventsHandler eventsHandler) { |
108 return VideoCapturerAndroid.create(name, eventsHandler, false /* captureToTe
xture */); | 187 return VideoCapturerAndroid.create(name, eventsHandler, false /* captureToTe
xture */); |
109 } | 188 } |
110 | 189 |
111 public static VideoCapturerAndroid create(String name, | 190 public static VideoCapturerAndroid create(String name, |
112 CameraEventsHandler eventsHandler, boolean captureToTexture) { | 191 CameraEventsHandler eventsHandler, boolean captureToTexture) { |
113 final int cameraId = lookupDeviceName(name); | 192 final int cameraId = lookupDeviceName(name); |
114 if (cameraId == -1) { | 193 if (cameraId == -1) { |
115 return null; | 194 return null; |
(...skipping 14 matching lines...) Expand all Loading... |
130 Logging.d(TAG, "VideoCapturerAndroid stacks trace:"); | 209 Logging.d(TAG, "VideoCapturerAndroid stacks trace:"); |
131 for (StackTraceElement stackTrace : cameraStackTraces) { | 210 for (StackTraceElement stackTrace : cameraStackTraces) { |
132 Logging.d(TAG, stackTrace.toString()); | 211 Logging.d(TAG, stackTrace.toString()); |
133 } | 212 } |
134 } | 213 } |
135 } | 214 } |
136 } | 215 } |
137 | 216 |
138 // Switch camera to the next valid camera id. This can only be called while | 217 // Switch camera to the next valid camera id. This can only be called while |
139 // the camera is running. | 218 // the camera is running. |
140 @Override | |
141 public void switchCamera(final CameraSwitchHandler switchEventsHandler) { | 219 public void switchCamera(final CameraSwitchHandler switchEventsHandler) { |
142 if (android.hardware.Camera.getNumberOfCameras() < 2) { | 220 if (android.hardware.Camera.getNumberOfCameras() < 2) { |
143 if (switchEventsHandler != null) { | 221 if (switchEventsHandler != null) { |
144 switchEventsHandler.onCameraSwitchError("No camera to switch to."); | 222 switchEventsHandler.onCameraSwitchError("No camera to switch to."); |
145 } | 223 } |
146 return; | 224 return; |
147 } | 225 } |
148 synchronized (pendingCameraSwitchLock) { | 226 synchronized (pendingCameraSwitchLock) { |
149 if (pendingCameraSwitch) { | 227 if (pendingCameraSwitch) { |
150 // Do not handle multiple camera switch request to avoid blocking | 228 // Do not handle multiple camera switch request to avoid blocking |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
214 // Returns true if this VideoCapturer is setup to capture video frames to a Su
rfaceTexture. | 292 // Returns true if this VideoCapturer is setup to capture video frames to a Su
rfaceTexture. |
215 public boolean isCapturingToTexture() { | 293 public boolean isCapturingToTexture() { |
216 return isCapturingToTexture; | 294 return isCapturingToTexture; |
217 } | 295 } |
218 | 296 |
219 private VideoCapturerAndroid(int cameraId, CameraEventsHandler eventsHandler, | 297 private VideoCapturerAndroid(int cameraId, CameraEventsHandler eventsHandler, |
220 boolean captureToTexture) { | 298 boolean captureToTexture) { |
221 this.id = cameraId; | 299 this.id = cameraId; |
222 this.eventsHandler = eventsHandler; | 300 this.eventsHandler = eventsHandler; |
223 isCapturingToTexture = captureToTexture; | 301 isCapturingToTexture = captureToTexture; |
| 302 cameraStatistics = new CameraStatistics(); |
224 Logging.d(TAG, "VideoCapturerAndroid isCapturingToTexture : " + isCapturingT
oTexture); | 303 Logging.d(TAG, "VideoCapturerAndroid isCapturingToTexture : " + isCapturingT
oTexture); |
225 } | 304 } |
226 | 305 |
227 private void checkIsOnCameraThread() { | 306 private void checkIsOnCameraThread() { |
228 if (Thread.currentThread() != cameraThreadHandler.getLooper().getThread()) { | 307 if (Thread.currentThread() != cameraThreadHandler.getLooper().getThread()) { |
229 throw new IllegalStateException("Wrong thread"); | 308 throw new IllegalStateException("Wrong thread"); |
230 } | 309 } |
231 } | 310 } |
232 | 311 |
233 // Returns the camera index for camera with name |deviceName|, or -1 if no suc
h camera can be | 312 // Returns the camera index for camera with name |deviceName|, or -1 if no suc
h camera can be |
(...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
374 Logging.d(TAG, "Camera orientation: " + info.orientation + | 453 Logging.d(TAG, "Camera orientation: " + info.orientation + |
375 " .Device orientation: " + getDeviceOrientation()); | 454 " .Device orientation: " + getDeviceOrientation()); |
376 camera.setErrorCallback(cameraErrorCallback); | 455 camera.setErrorCallback(cameraErrorCallback); |
377 startPreviewOnCameraThread(width, height, framerate); | 456 startPreviewOnCameraThread(width, height, framerate); |
378 frameObserver.onCapturerStarted(true); | 457 frameObserver.onCapturerStarted(true); |
379 if (isCapturingToTexture) { | 458 if (isCapturingToTexture) { |
380 surfaceHelper.startListening(this); | 459 surfaceHelper.startListening(this); |
381 } | 460 } |
382 | 461 |
383 // Start camera observer. | 462 // Start camera observer. |
384 cameraStatistics = new CameraStatistics(surfaceHelper, eventsHandler); | 463 maybePostDelayedOnCameraThread(CAMERA_OBSERVER_PERIOD_MS, cameraObserver); |
385 return; | 464 return; |
386 } catch (RuntimeException e) { | 465 } catch (RuntimeException e) { |
387 error = e; | 466 error = e; |
388 } | 467 } |
389 Logging.e(TAG, "startCapture failed", error); | 468 Logging.e(TAG, "startCapture failed", error); |
390 // Make sure the camera is released. | 469 // Make sure the camera is released. |
391 stopCaptureOnCameraThread(); | 470 stopCaptureOnCameraThread(); |
392 synchronized (handlerLock) { | 471 synchronized (handlerLock) { |
393 // Remove all pending Runnables posted from |this|. | 472 // Remove all pending Runnables posted from |this|. |
394 cameraThreadHandler.removeCallbacksAndMessages(this /* token */); | 473 cameraThreadHandler.removeCallbacksAndMessages(this /* token */); |
(...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
527 private void stopCaptureOnCameraThread() { | 606 private void stopCaptureOnCameraThread() { |
528 checkIsOnCameraThread(); | 607 checkIsOnCameraThread(); |
529 Logging.d(TAG, "stopCaptureOnCameraThread"); | 608 Logging.d(TAG, "stopCaptureOnCameraThread"); |
530 // Note that the camera might still not be started here if startCaptureOnCam
eraThread failed | 609 // Note that the camera might still not be started here if startCaptureOnCam
eraThread failed |
531 // and we posted a retry. | 610 // and we posted a retry. |
532 | 611 |
533 // Make sure onTextureFrameAvailable() is not called anymore. | 612 // Make sure onTextureFrameAvailable() is not called anymore. |
534 if (surfaceHelper != null) { | 613 if (surfaceHelper != null) { |
535 surfaceHelper.stopListening(); | 614 surfaceHelper.stopListening(); |
536 } | 615 } |
537 if (cameraStatistics != null) { | 616 cameraThreadHandler.removeCallbacks(cameraObserver); |
538 cameraStatistics.release(); | 617 cameraStatistics.getAndResetFrameCount(); |
539 cameraStatistics = null; | |
540 } | |
541 Logging.d(TAG, "Stop preview."); | 618 Logging.d(TAG, "Stop preview."); |
542 if (camera != null) { | 619 if (camera != null) { |
543 camera.stopPreview(); | 620 camera.stopPreview(); |
544 camera.setPreviewCallbackWithBuffer(null); | 621 camera.setPreviewCallbackWithBuffer(null); |
545 } | 622 } |
546 queuedBuffers.clear(); | 623 queuedBuffers.clear(); |
547 captureFormat = null; | 624 captureFormat = null; |
548 | 625 |
549 Logging.d(TAG, "Release camera."); | 626 Logging.d(TAG, "Release camera."); |
550 if (camera != null) { | 627 if (camera != null) { |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
669 // Undo the mirror that the OS "helps" us with. | 746 // Undo the mirror that the OS "helps" us with. |
670 // http://developer.android.com/reference/android/hardware/Camera.html#set
DisplayOrientation(int) | 747 // http://developer.android.com/reference/android/hardware/Camera.html#set
DisplayOrientation(int) |
671 transformMatrix = | 748 transformMatrix = |
672 RendererCommon.multiplyMatrices(transformMatrix, RendererCommon.horizo
ntalFlipMatrix()); | 749 RendererCommon.multiplyMatrices(transformMatrix, RendererCommon.horizo
ntalFlipMatrix()); |
673 } | 750 } |
674 cameraStatistics.addFrame(); | 751 cameraStatistics.addFrame(); |
675 frameObserver.onTextureFrameCaptured(captureFormat.width, captureFormat.heig
ht, oesTextureId, | 752 frameObserver.onTextureFrameCaptured(captureFormat.width, captureFormat.heig
ht, oesTextureId, |
676 transformMatrix, rotation, timestampNs); | 753 transformMatrix, rotation, timestampNs); |
677 } | 754 } |
678 } | 755 } |
OLD | NEW |