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

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

Issue 2168623002: Refactor stopCapture to be asynchronous in VideoCapturerAndroid. (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@androidvideotracksource
Patch Set: Synchronized. 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
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 28 matching lines...) Expand all
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. 41 // TODO(magjed): This class name is now confusing - rename to Camera1VideoCaptur er.
42 @SuppressWarnings("deprecation") 42 @SuppressWarnings("deprecation")
43 public class VideoCapturerAndroid implements 43 public class VideoCapturerAndroid implements
44 CameraVideoCapturer, 44 CameraVideoCapturer,
45 android.hardware.Camera.PreviewCallback, 45 android.hardware.Camera.PreviewCallback,
46 SurfaceTextureHelper.OnTextureFrameAvailableListener { 46 SurfaceTextureHelper.OnTextureFrameAvailableListener {
47 private static final String TAG = "VideoCapturerAndroid"; 47 private static final String TAG = "VideoCapturerAndroid";
48 private static final int CAMERA_STOP_TIMEOUT_MS = 7000; 48 private static final int CAMERA_STOP_TIMEOUT_MS = 7000;
49 // Arbitrary queue depth. Higher number means more memory allocated & held,
50 // lower number means more sensitivity to processing time in the client (and
51 // potentially stalling the capturer if it runs out of buffers to write to).
52 private static final int NUMBER_OF_CAPTURE_BUFFERS = 3;
53 private final static int MAX_OPEN_CAMERA_ATTEMPTS = 3;
54 private final static int OPEN_CAMERA_DELAY_MS = 500;
55 private static enum CameraState { UNINITIALIAZED, IDLE, STARTING, RUNNING }
49 56
57 private final Set<byte[]> queuedBuffers = new HashSet<byte[]>();
58 private final boolean isCapturingToTexture;
59 private final CameraEventsHandler eventsHandler;
60
61 // Initialized on initialize
62 // -------------------------
63 // Use postOnCameraThread() instead of posting directly to the handler - this way all
64 // callbacks with a specifed token can be removed at once.
65 private Handler cameraThreadHandler;
66 private Context applicationContext;
67 private CapturerObserver capturerObserver = null;
68 private SurfaceTextureHelper surfaceHelper;
69
70 // Internal state - will only be touched from the camera thread
71 // ------------------------------------------------------------
50 private android.hardware.Camera camera; // Only non-null while capturing. 72 private android.hardware.Camera camera; // Only non-null while capturing.
51 private final AtomicBoolean isCameraRunning = new AtomicBoolean();
52 // Use maybePostOnCameraThread() instead of posting directly to the handler - this way all
53 // callbacks with a specifed token can be removed at once.
54 private volatile Handler cameraThreadHandler;
55 private Context applicationContext;
56 // Synchronization lock for |id|.
57 private final Object cameraIdLock = new Object();
58 private int id;
59 private android.hardware.Camera.CameraInfo info;
60 private CameraStatistics cameraStatistics;
61 // Remember the requested format in case we want to switch cameras. 73 // Remember the requested format in case we want to switch cameras.
62 private int requestedWidth; 74 private int requestedWidth;
63 private int requestedHeight; 75 private int requestedHeight;
64 private int requestedFramerate; 76 private int requestedFramerate;
65 // The capture format will be the closest supported format to the requested fo rmat. 77 // Populated with the information of the active camera.
78 private android.hardware.Camera.CameraInfo info;
79 // Active capture format.
66 private CaptureFormat captureFormat; 80 private CaptureFormat captureFormat;
67 private final Object pendingCameraSwitchLock = new Object(); 81 private CameraStatistics cameraStatistics;
68 private volatile boolean pendingCameraSwitch;
69 private CapturerObserver frameObserver = null;
70 private final CameraEventsHandler eventsHandler;
71 private boolean firstFrameReported; 82 private boolean firstFrameReported;
72 // Arbitrary queue depth. Higher number means more memory allocated & held,
73 // lower number means more sensitivity to processing time in the client (and
74 // potentially stalling the capturer if it runs out of buffers to write to).
75 private static final int NUMBER_OF_CAPTURE_BUFFERS = 3;
76 private final Set<byte[]> queuedBuffers = new HashSet<byte[]>();
77 private final boolean isCapturingToTexture;
78 private SurfaceTextureHelper surfaceHelper;
79 private final static int MAX_OPEN_CAMERA_ATTEMPTS = 3;
80 private final static int OPEN_CAMERA_DELAY_MS = 500;
81 private int openCameraAttempts; 83 private int openCameraAttempts;
82 84
85 // Locked objects
86 // --------------
87 private final Object cameraIdLock = new Object();
88 private int id; // Only edited from camera thread while holding the lock.
89
90 private final Object cameraStateLock = new Object();
91 private CameraState cameraState = CameraState.UNINITIALIAZED;
92
93 // Only allow one camera switch a time.
94 private final Object cameraSwitchLock = new Object();
95 private boolean pendingCameraSwitch;
96 private CameraSwitchHandler switchEventsHandler;
97
83 // Camera error callback. 98 // Camera error callback.
84 private final android.hardware.Camera.ErrorCallback cameraErrorCallback = 99 private final android.hardware.Camera.ErrorCallback cameraErrorCallback =
85 new android.hardware.Camera.ErrorCallback() { 100 new android.hardware.Camera.ErrorCallback() {
86 @Override 101 @Override
87 public void onError(int error, android.hardware.Camera camera) { 102 public void onError(int error, android.hardware.Camera camera) {
88 String errorMessage; 103 String errorMessage;
89 if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) { 104 if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) {
90 errorMessage = "Camera server died!"; 105 errorMessage = "Camera server died!";
91 } else { 106 } else {
92 errorMessage = "Camera error: " + error; 107 errorMessage = "Camera error: " + error;
93 } 108 }
94 Logging.e(TAG, errorMessage); 109 Logging.e(TAG, errorMessage);
95 if (eventsHandler != null) { 110 if (eventsHandler != null) {
96 eventsHandler.onCameraError(errorMessage); 111 eventsHandler.onCameraError(errorMessage);
97 } 112 }
98 } 113 }
99 }; 114 };
100 115
116 private void setCameraState(CameraState newState) {
117 Logging.d(TAG, "Camera changing state from " + cameraState + " to " + newSta te);
118 synchronized (cameraStateLock) {
119 cameraState = newState;
120 cameraStateLock.notifyAll();
121 }
122 }
123
101 public static VideoCapturerAndroid create(String name, 124 public static VideoCapturerAndroid create(String name,
102 CameraEventsHandler eventsHandler) { 125 CameraEventsHandler eventsHandler) {
103 return VideoCapturerAndroid.create(name, eventsHandler, false /* captureToTe xture */); 126 return VideoCapturerAndroid.create(name, eventsHandler, false /* captureToTe xture */);
104 } 127 }
105 128
106 // Use ctor directly instead. 129 // Use ctor directly instead.
107 @Deprecated 130 @Deprecated
108 public static VideoCapturerAndroid create(String name, 131 public static VideoCapturerAndroid create(String name,
109 CameraEventsHandler eventsHandler, boolean captureToTexture) { 132 CameraEventsHandler eventsHandler, boolean captureToTexture) {
110 try { 133 try {
(...skipping 16 matching lines...) Expand all
127 for (StackTraceElement stackTrace : cameraStackTraces) { 150 for (StackTraceElement stackTrace : cameraStackTraces) {
128 Logging.d(TAG, stackTrace.toString()); 151 Logging.d(TAG, stackTrace.toString());
129 } 152 }
130 } 153 }
131 } 154 }
132 } 155 }
133 156
134 // Switch camera to the next valid camera id. This can only be called while 157 // Switch camera to the next valid camera id. This can only be called while
135 // the camera is running. 158 // the camera is running.
136 @Override 159 @Override
137 public void switchCamera(final CameraSwitchHandler switchEventsHandler) { 160 public synchronized void switchCamera(final CameraSwitchHandler switchEventsHa ndler) {
138 if (android.hardware.Camera.getNumberOfCameras() < 2) { 161 if (android.hardware.Camera.getNumberOfCameras() < 2) {
139 if (switchEventsHandler != null) { 162 if (switchEventsHandler != null) {
140 switchEventsHandler.onCameraSwitchError("No camera to switch to."); 163 switchEventsHandler.onCameraSwitchError("No camera to switch to.");
141 } 164 }
142 return; 165 return;
143 } 166 }
144 synchronized (pendingCameraSwitchLock) { 167
168 synchronized (cameraSwitchLock) {
145 if (pendingCameraSwitch) { 169 if (pendingCameraSwitch) {
146 // Do not handle multiple camera switch request to avoid blocking 170 // Do not handle multiple camera switch request to avoid blocking
147 // camera thread by handling too many switch request from a queue. 171 // camera thread by handling too many switch request from a queue.
148 Logging.w(TAG, "Ignoring camera switch request."); 172 Logging.w(TAG, "Ignoring camera switch request.");
149 if (switchEventsHandler != null) { 173 if (switchEventsHandler != null) {
150 switchEventsHandler.onCameraSwitchError("Pending camera switch already in progress."); 174 switchEventsHandler.onCameraSwitchError("Pending camera switch already in progress.");
151 } 175 }
152 return; 176 return;
153 } 177 }
178
154 pendingCameraSwitch = true; 179 pendingCameraSwitch = true;
155 } 180 }
156 final boolean didPost = maybePostOnCameraThread(new Runnable() { 181
182 postOnCameraThread(new Runnable() {
157 @Override 183 @Override
158 public void run() { 184 public void run() {
185 // Set switchEventsHandler only here because now openCameraOnCameraThrea d cannot be called
186 // in-between these calls.
187 synchronized (cameraSwitchLock) {
188 VideoCapturerAndroid.this.switchEventsHandler = switchEventsHandler;
189 }
159 switchCameraOnCameraThread(); 190 switchCameraOnCameraThread();
160 synchronized (pendingCameraSwitchLock) { 191 synchronized (cameraSwitchLock) {
161 pendingCameraSwitch = false; 192 pendingCameraSwitch = false;
162 } 193 }
163 if (switchEventsHandler != null) {
164 switchEventsHandler.onCameraSwitchDone(
165 info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FR ONT);
166 }
167 } 194 }
168 }); 195 });
169 if (!didPost && switchEventsHandler != null) {
170 switchEventsHandler.onCameraSwitchError("Camera is stopped.");
171 }
172 } 196 }
173 197
174 // Requests a new output format from the video capturer. Captured frames 198 // Requests a new output format from the video capturer. Captured frames
175 // by the camera will be scaled/or dropped by the video capturer. 199 // by the camera will be scaled/or dropped by the video capturer.
176 // It does not matter if width and height are flipped. I.E, |width| = 640, |he ight| = 480 produce 200 // It does not matter if width and height are flipped. I.E, |width| = 640, |he ight| = 480 produce
177 // the same result as |width| = 480, |height| = 640. 201 // the same result as |width| = 480, |height| = 640.
178 // TODO(magjed/perkj): Document what this function does. Change name? 202 // TODO(magjed/perkj): Document what this function does. Change name?
179 @Override 203 @Override
180 public void onOutputFormatRequest(final int width, final int height, final int framerate) { 204 public synchronized void onOutputFormatRequest(
181 maybePostOnCameraThread(new Runnable() { 205 final int width, final int height, final int framerate) {
206 postOnCameraThread(new Runnable() {
182 @Override public void run() { 207 @Override public void run() {
183 onOutputFormatRequestOnCameraThread(width, height, framerate); 208 onOutputFormatRequestOnCameraThread(width, height, framerate);
184 } 209 }
185 }); 210 });
186 } 211 }
187 212
188 // Reconfigure the camera to capture in a new format. This should only be call ed while the camera 213 // Reconfigure the camera to capture in a new format. This should only be call ed while the camera
189 // is running. 214 // is running.
190 @Override 215 @Override
191 public void changeCaptureFormat(final int width, final int height, final int f ramerate) { 216 public synchronized void changeCaptureFormat(
192 maybePostOnCameraThread(new Runnable() { 217 final int width, final int height, final int framerate) {
218 postOnCameraThread(new Runnable() {
193 @Override public void run() { 219 @Override public void run() {
194 startPreviewOnCameraThread(width, height, framerate); 220 requestedWidth = width;
221 requestedHeight = height;
222 requestedFramerate = framerate;
223
224 startPreviewOnCameraThread();
195 } 225 }
196 }); 226 });
197 } 227 }
198 228
199 // Helper function to retrieve the current camera id synchronously. Note that the camera id might 229 @Override
200 // change at any point by switchCamera() calls. 230 public List<CaptureFormat> getSupportedFormats() {
201 private int getCurrentCameraId() {
202 synchronized (cameraIdLock) { 231 synchronized (cameraIdLock) {
203 return id; 232 return Camera1Enumerator.getSupportedFormats(id);
204 } 233 }
205 } 234 }
206 235
207 @Override
208 public List<CaptureFormat> getSupportedFormats() {
209 return Camera1Enumerator.getSupportedFormats(getCurrentCameraId());
210 }
211
212 // Returns true if this VideoCapturer is setup to capture video frames to a Su rfaceTexture. 236 // Returns true if this VideoCapturer is setup to capture video frames to a Su rfaceTexture.
213 public boolean isCapturingToTexture() { 237 public boolean isCapturingToTexture() {
214 return isCapturingToTexture; 238 return isCapturingToTexture;
215 } 239 }
216 240
217 public VideoCapturerAndroid(String cameraName, CameraEventsHandler eventsHandl er, 241 public VideoCapturerAndroid(String cameraName, CameraEventsHandler eventsHandl er,
218 boolean captureToTexture) { 242 boolean captureToTexture) {
219 if (android.hardware.Camera.getNumberOfCameras() == 0) { 243 if (android.hardware.Camera.getNumberOfCameras() == 0) {
220 throw new RuntimeException("No cameras available"); 244 throw new RuntimeException("No cameras available");
221 } 245 }
222 if (cameraName == null || cameraName.equals("")) { 246 if (cameraName == null || cameraName.equals("")) {
223 this.id = 0; 247 this.id = 0;
224 } else { 248 } else {
225 this.id = Camera1Enumerator.getCameraIndex(cameraName); 249 this.id = Camera1Enumerator.getCameraIndex(cameraName);
226 } 250 }
227 this.eventsHandler = eventsHandler; 251 this.eventsHandler = eventsHandler;
228 isCapturingToTexture = captureToTexture; 252 isCapturingToTexture = captureToTexture;
229 Logging.d(TAG, "VideoCapturerAndroid isCapturingToTexture : " + isCapturingT oTexture); 253 Logging.d(TAG, "VideoCapturerAndroid isCapturingToTexture : " + isCapturingT oTexture);
230 } 254 }
231 255
232 private void checkIsOnCameraThread() { 256 private void checkIsOnCameraThread() {
233 if (cameraThreadHandler == null) { 257 if (cameraThreadHandler == null) {
234 Logging.e(TAG, "Camera is not initialized - can't check thread."); 258 throw new IllegalStateException("Camera is not initialized - can't check t hread.");
235 } else if (Thread.currentThread() != cameraThreadHandler.getLooper().getThre ad()) { 259 } else if (Thread.currentThread() != cameraThreadHandler.getLooper().getThre ad()) {
236 throw new IllegalStateException("Wrong thread"); 260 throw new IllegalStateException("Wrong thread");
237 } 261 }
238 } 262 }
239 263
240 private boolean maybePostOnCameraThread(Runnable runnable) { 264 /**
241 return maybePostDelayedOnCameraThread(0 /* delayMs */, runnable); 265 * Return true if camera is initialized otherwise returns false and logs an er ror.
266 */
267 private boolean checkIsInitialized() {
268 synchronized (cameraStateLock) {
269 if (cameraState == CameraState.UNINITIALIAZED) {
270 Logging.e(TAG, "Calling methods on an uninitialized camera.");
271 return false;
272 }
273 return true;
274 }
242 } 275 }
243 276
244 private boolean maybePostDelayedOnCameraThread(int delayMs, Runnable runnable) { 277 private void postOnCameraThread(Runnable runnable) {
245 return cameraThreadHandler != null && isCameraRunning.get() 278 postDelayedOnCameraThread(0 /* delayMs */, runnable);
246 && cameraThreadHandler.postAtTime( 279 }
247 runnable, this /* token */, SystemClock.uptimeMillis() + delayMs); 280
281 private void postDelayedOnCameraThread(int delayMs, Runnable runnable) {
282 if (!cameraThreadHandler.postAtTime(
283 runnable, this /* token */, SystemClock.uptimeMillis() + delayMs)) {
284 throw new RuntimeException("Failed to post a runnable on camera thread.");
285 }
248 } 286 }
249 287
250 @Override 288 @Override
251 public void dispose() { 289 public void dispose() {
252 Logging.d(TAG, "dispose"); 290 Logging.d(TAG, "dispose");
253 } 291 stopCapture(); // Stop the camera in case the caller forgot
254
255 private boolean isInitialized() {
256 return applicationContext != null && frameObserver != null;
257 } 292 }
258 293
259 @Override 294 @Override
260 public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context appl icationContext, 295 public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context appl icationContext,
261 CapturerObserver frameObserver) { 296 CapturerObserver capturerObserver) {
262 Logging.d(TAG, "initialize"); 297 Logging.d(TAG, "initialize");
263 if (applicationContext == null) { 298 if (applicationContext == null) {
264 throw new IllegalArgumentException("applicationContext not set."); 299 throw new IllegalArgumentException("applicationContext not set.");
265 } 300 }
266 if (frameObserver == null) { 301 if (capturerObserver == null) {
267 throw new IllegalArgumentException("frameObserver not set."); 302 throw new IllegalArgumentException("capturerObserver not set.");
268 } 303 }
269 if (isInitialized()) { 304 if (cameraState != CameraState.UNINITIALIAZED) {
270 throw new IllegalStateException("Already initialized"); 305 throw new IllegalStateException("Already initialized");
271 } 306 }
272 this.applicationContext = applicationContext; 307 this.applicationContext = applicationContext;
273 this.frameObserver = frameObserver; 308 this.capturerObserver = capturerObserver;
274 this.surfaceHelper = surfaceTextureHelper; 309 this.surfaceHelper = surfaceTextureHelper;
275 this.cameraThreadHandler = 310 this.cameraThreadHandler =
276 surfaceTextureHelper == null ? null : surfaceTextureHelper.getHandler(); 311 surfaceTextureHelper == null ? null : surfaceTextureHelper.getHandler();
312
313 // Nothing should be running on the camera thread so should be safe.
314 setCameraState(CameraState.IDLE);
277 } 315 }
278 316
279 // Note that this actually opens the camera, and Camera callbacks run on the 317 // Note that this actually opens the camera, and Camera callbacks run on the
280 // thread that calls open(), so this is done on the CameraThread. 318 // thread that calls open(), so this is done on the CameraThread.
281 @Override 319 @Override
282 public void startCapture(final int width, final int height, final int framerat e) { 320 public synchronized void startCapture(final int width, final int height, final int framerate) {
321 if (!checkIsInitialized()) {
322 return;
323 }
324
283 Logging.d(TAG, "startCapture requested: " + width + "x" + height + "@" + fra merate); 325 Logging.d(TAG, "startCapture requested: " + width + "x" + height + "@" + fra merate);
284 if (!isInitialized()) { 326
285 throw new IllegalStateException("startCapture called in uninitialized stat e");
286 }
287 if (surfaceHelper == null) { 327 if (surfaceHelper == null) {
288 frameObserver.onCapturerStarted(false /* success */); 328 capturerObserver.onCapturerStarted(false /* success */);
289 if (eventsHandler != null) { 329 if (eventsHandler != null) {
290 eventsHandler.onCameraError("No SurfaceTexture created."); 330 eventsHandler.onCameraError("No SurfaceTexture created.");
291 } 331 }
292 return; 332 return;
293 } 333 }
294 if (isCameraRunning.getAndSet(true)) { 334
295 Logging.e(TAG, "Camera has already been started."); 335 postOnCameraThread(new Runnable() {
296 return;
297 }
298 final boolean didPost = maybePostOnCameraThread(new Runnable() {
299 @Override 336 @Override
300 public void run() { 337 public void run() {
301 openCameraAttempts = 0;
302 startCaptureOnCameraThread(width, height, framerate); 338 startCaptureOnCameraThread(width, height, framerate);
303 } 339 }
304 }); 340 });
305 if (!didPost) {
306 frameObserver.onCapturerStarted(false);
307 if (eventsHandler != null) {
308 eventsHandler.onCameraError("Could not post task to camera thread.");
309 }
310 isCameraRunning.set(false);
311 }
312 } 341 }
313 342
314 private void startCaptureOnCameraThread(final int width, final int height, fin al int framerate) { 343 private void startCaptureOnCameraThread(final int width, final int height, fin al int framerate) {
315 checkIsOnCameraThread(); 344 checkIsOnCameraThread();
316 if (!isCameraRunning.get()) { 345
317 Logging.e(TAG, "startCaptureOnCameraThread: Camera is stopped"); 346 if (cameraState != CameraState.IDLE) {
347 Logging.e(TAG, "Camera has already been started.");
318 return; 348 return;
319 } 349 }
320 if (camera != null) {
321 Logging.e(TAG, "startCaptureOnCameraThread: Camera has already been starte d.");
322 return;
323 }
324 this.firstFrameReported = false;
325 350
351 openCameraAttempts = 0;
352 firstFrameReported = false;
353 setCameraState(CameraState.STARTING);
354
355 requestedWidth = width;
356 requestedHeight = height;
357 requestedFramerate = framerate;
358
359 openCameraOnCameraThread();
360 }
361
362 private void openCameraOnCameraThread() {
326 try { 363 try {
364 Logging.d(TAG, "Opening camera " + id);
365 if (eventsHandler != null) {
366 eventsHandler.onCameraOpening(id);
367 }
368
327 try { 369 try {
328 synchronized (cameraIdLock) { 370 camera = android.hardware.Camera.open(id);
329 Logging.d(TAG, "Opening camera " + id);
330 if (eventsHandler != null) {
331 eventsHandler.onCameraOpening(id);
332 }
333 camera = android.hardware.Camera.open(id);
334 info = new android.hardware.Camera.CameraInfo();
335 android.hardware.Camera.getCameraInfo(id, info);
336 }
337 } catch (RuntimeException e) { 371 } catch (RuntimeException e) {
338 openCameraAttempts++; 372 openCameraAttempts++;
339 if (openCameraAttempts < MAX_OPEN_CAMERA_ATTEMPTS) { 373 if (openCameraAttempts < MAX_OPEN_CAMERA_ATTEMPTS) {
340 Logging.e(TAG, "Camera.open failed, retrying", e); 374 Logging.e(TAG, "Camera.open failed, retrying", e);
341 maybePostDelayedOnCameraThread(OPEN_CAMERA_DELAY_MS, new Runnable() { 375 postDelayedOnCameraThread(OPEN_CAMERA_DELAY_MS, new Runnable() {
342 @Override 376 @Override
343 public void run() { 377 public void run() {
344 startCaptureOnCameraThread(width, height, framerate); 378 openCameraOnCameraThread();
345 } 379 }
346 }); 380 });
347 return; 381 return;
348 } 382 }
349 throw e; 383 throw e;
350 } 384 }
351 385
386 setCameraState(CameraState.RUNNING);
387
388 info = new android.hardware.Camera.CameraInfo();
389 android.hardware.Camera.getCameraInfo(id, info);
390
352 camera.setPreviewTexture(surfaceHelper.getSurfaceTexture()); 391 camera.setPreviewTexture(surfaceHelper.getSurfaceTexture());
353 392
354 Logging.d(TAG, "Camera orientation: " + info.orientation + 393 Logging.d(TAG, "Camera orientation: " + info.orientation +
355 " .Device orientation: " + getDeviceOrientation()); 394 " .Device orientation: " + getDeviceOrientation());
356 camera.setErrorCallback(cameraErrorCallback); 395 camera.setErrorCallback(cameraErrorCallback);
357 startPreviewOnCameraThread(width, height, framerate); 396 startPreviewOnCameraThread();
358 frameObserver.onCapturerStarted(true); 397 capturerObserver.onCapturerStarted(true);
398 synchronized (cameraSwitchLock) {
399 if (switchEventsHandler != null) {
400 switchEventsHandler.onCameraSwitchDone(
401 info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FR ONT);
402 }
403 switchEventsHandler = null;
404 }
359 if (isCapturingToTexture) { 405 if (isCapturingToTexture) {
360 surfaceHelper.startListening(this); 406 surfaceHelper.startListening(this);
361 } 407 }
362 408
363 // Start camera observer. 409 // Start camera statistics collector.
364 cameraStatistics = new CameraStatistics(surfaceHelper, eventsHandler); 410 cameraStatistics = new CameraStatistics(surfaceHelper, eventsHandler);
365 } catch (IOException|RuntimeException e) { 411 } catch (IOException|RuntimeException e) {
366 Logging.e(TAG, "startCapture failed", e); 412 Logging.e(TAG, "startCapture failed", e);
367 // Make sure the camera is released. 413 // Make sure the camera is released.
368 stopCaptureOnCameraThread(true /* stopHandler */); 414 capturerObserver.onCapturerStarted(false);
369 frameObserver.onCapturerStarted(false); 415 setCameraState(CameraState.IDLE);
416 releaseCameraOnCameraThread();
370 if (eventsHandler != null) { 417 if (eventsHandler != null) {
371 eventsHandler.onCameraError("Camera can not be started."); 418 eventsHandler.onCameraError("Camera can not be started.");
372 } 419 }
373 } 420 synchronized (cameraSwitchLock) {
421 if (switchEventsHandler != null) {
422 switchEventsHandler.onCameraSwitchError("Camera can not be started.");
423 }
424 switchEventsHandler = null;
425 }
426 }
374 } 427 }
375 428
376 // (Re)start preview with the closest supported format to |width| x |height| @ |framerate|. 429 // (Re)start preview with the closest supported format to |width| x |height| @ |framerate|.
377 private void startPreviewOnCameraThread(int width, int height, int framerate) { 430 private void startPreviewOnCameraThread() {
378 checkIsOnCameraThread(); 431 checkIsOnCameraThread();
379 if (!isCameraRunning.get() || camera == null) { 432 if (cameraState != CameraState.RUNNING) {
380 Logging.e(TAG, "startPreviewOnCameraThread: Camera is stopped"); 433 Logging.e(TAG, "startPreviewOnCameraThread: Camera is not running");
381 return; 434 return;
382 } 435 }
383 Logging.d( 436 Logging.d(TAG, "startPreviewOnCameraThread requested: "
384 TAG, "startPreviewOnCameraThread requested: " + width + "x" + height + " @" + framerate); 437 + requestedWidth + "x" + requestedHeight + "@" + requestedFramerate);
385
386 requestedWidth = width;
387 requestedHeight = height;
388 requestedFramerate = framerate;
389 438
390 // Find closest supported format for |width| x |height| @ |framerate|. 439 // Find closest supported format for |width| x |height| @ |framerate|.
391 final android.hardware.Camera.Parameters parameters = camera.getParameters() ; 440 final android.hardware.Camera.Parameters parameters = camera.getParameters() ;
392 final List<CaptureFormat.FramerateRange> supportedFramerates = 441 final List<CaptureFormat.FramerateRange> supportedFramerates =
393 Camera1Enumerator.convertFramerates(parameters.getSupportedPreviewFpsRan ge()); 442 Camera1Enumerator.convertFramerates(parameters.getSupportedPreviewFpsRan ge());
394 Logging.d(TAG, "Available fps ranges: " + supportedFramerates); 443 Logging.d(TAG, "Available fps ranges: " + supportedFramerates);
395 444
396 final CaptureFormat.FramerateRange fpsRange = 445 final CaptureFormat.FramerateRange fpsRange =
397 CameraEnumerationAndroid.getClosestSupportedFramerateRange(supportedFram erates, framerate); 446 CameraEnumerationAndroid.getClosestSupportedFramerateRange(
447 supportedFramerates, requestedFramerate);
398 448
399 final Size previewSize = CameraEnumerationAndroid.getClosestSupportedSize( 449 final Size previewSize = CameraEnumerationAndroid.getClosestSupportedSize(
400 Camera1Enumerator.convertSizes(parameters.getSupportedPreviewSizes()), w idth, height); 450 Camera1Enumerator.convertSizes(parameters.getSupportedPreviewSizes()),
451 requestedWidth, requestedHeight);
401 452
402 final CaptureFormat captureFormat = 453 final CaptureFormat captureFormat =
403 new CaptureFormat(previewSize.width, previewSize.height, fpsRange); 454 new CaptureFormat(previewSize.width, previewSize.height, fpsRange);
404 455
405 // Check if we are already using this capture format, then we don't need to do anything. 456 // Check if we are already using this capture format, then we don't need to do anything.
406 if (captureFormat.equals(this.captureFormat)) { 457 if (captureFormat.equals(this.captureFormat)) {
407 return; 458 return;
408 } 459 }
409 460
410 // Update camera parameters. 461 // Update camera parameters.
411 Logging.d(TAG, "isVideoStabilizationSupported: " + 462 Logging.d(TAG, "isVideoStabilizationSupported: " +
412 parameters.isVideoStabilizationSupported()); 463 parameters.isVideoStabilizationSupported());
413 if (parameters.isVideoStabilizationSupported()) { 464 if (parameters.isVideoStabilizationSupported()) {
414 parameters.setVideoStabilization(true); 465 parameters.setVideoStabilization(true);
415 } 466 }
416 // Note: setRecordingHint(true) actually decrease frame rate on N5. 467 // Note: setRecordingHint(true) actually decrease frame rate on N5.
417 // parameters.setRecordingHint(true); 468 // parameters.setRecordingHint(true);
418 if (captureFormat.framerate.max > 0) { 469 if (captureFormat.framerate.max > 0) {
419 parameters.setPreviewFpsRange(captureFormat.framerate.min, captureFormat.f ramerate.max); 470 parameters.setPreviewFpsRange(captureFormat.framerate.min, captureFormat.f ramerate.max);
420 } 471 }
421 parameters.setPreviewSize(previewSize.width, previewSize.height); 472 parameters.setPreviewSize(previewSize.width, previewSize.height);
422 473
423 if (!isCapturingToTexture) { 474 if (!isCapturingToTexture) {
424 parameters.setPreviewFormat(captureFormat.imageFormat); 475 parameters.setPreviewFormat(captureFormat.imageFormat);
425 } 476 }
426 // Picture size is for taking pictures and not for preview/video, but we nee d to set it anyway 477 // Picture size is for taking pictures and not for preview/video, but we nee d to set it anyway
427 // as a workaround for an aspect ratio problem on Nexus 7. 478 // as a workaround for an aspect ratio problem on Nexus 7.
428 final Size pictureSize = CameraEnumerationAndroid.getClosestSupportedSize( 479 final Size pictureSize = CameraEnumerationAndroid.getClosestSupportedSize(
429 Camera1Enumerator.convertSizes(parameters.getSupportedPictureSizes()), w idth, height); 480 Camera1Enumerator.convertSizes(parameters.getSupportedPictureSizes()),
481 requestedWidth, requestedHeight);
430 parameters.setPictureSize(pictureSize.width, pictureSize.height); 482 parameters.setPictureSize(pictureSize.width, pictureSize.height);
431 483
432 // Temporarily stop preview if it's already running. 484 // Temporarily stop preview if it's already running.
433 if (this.captureFormat != null) { 485 if (this.captureFormat != null) {
434 camera.stopPreview(); 486 camera.stopPreview();
435 // Calling |setPreviewCallbackWithBuffer| with null should clear the inter nal camera buffer 487 // Calling |setPreviewCallbackWithBuffer| with null should clear the inter nal camera buffer
436 // queue, but sometimes we receive a frame with the old resolution after t his call anyway. 488 // queue, but sometimes we receive a frame with the old resolution after t his call anyway.
437 camera.setPreviewCallbackWithBuffer(null); 489 camera.setPreviewCallbackWithBuffer(null);
438 } 490 }
439 491
(...skipping 15 matching lines...) Expand all
455 for (int i = 0; i < NUMBER_OF_CAPTURE_BUFFERS; ++i) { 507 for (int i = 0; i < NUMBER_OF_CAPTURE_BUFFERS; ++i) {
456 final ByteBuffer buffer = ByteBuffer.allocateDirect(frameSize); 508 final ByteBuffer buffer = ByteBuffer.allocateDirect(frameSize);
457 queuedBuffers.add(buffer.array()); 509 queuedBuffers.add(buffer.array());
458 camera.addCallbackBuffer(buffer.array()); 510 camera.addCallbackBuffer(buffer.array());
459 } 511 }
460 camera.setPreviewCallbackWithBuffer(this); 512 camera.setPreviewCallbackWithBuffer(this);
461 } 513 }
462 camera.startPreview(); 514 camera.startPreview();
463 } 515 }
464 516
465 // Blocks until camera is known to be stopped. 517 // Blocks until camera is known to be stopped. Synchronized to allow only on s top capture call at
518 // a time.
466 @Override 519 @Override
467 public void stopCapture() throws InterruptedException { 520 public synchronized void stopCapture() {
521 if (!checkIsInitialized()) {
522 return;
523 }
468 Logging.d(TAG, "stopCapture"); 524 Logging.d(TAG, "stopCapture");
469 final CountDownLatch barrier = new CountDownLatch(1); 525
470 final boolean didPost = maybePostOnCameraThread(new Runnable() { 526 // Count down latch to ensure runnable has been started and it is not some o ther callback
527 // stopping the camera.
528 final CountDownLatch countDownLatch = new CountDownLatch(1);
529
530 postOnCameraThread(new Runnable() {
471 @Override public void run() { 531 @Override public void run() {
472 stopCaptureOnCameraThread(true /* stopHandler */); 532 countDownLatch.countDown();
473 barrier.countDown(); 533 stopCaptureOnCameraThread();
474 } 534 }
475 }); 535 });
476 if (!didPost) { 536
477 Logging.e(TAG, "Calling stopCapture() for already stopped camera."); 537 Logging.d(TAG, "stopCapture: Waiting for the stopping to start");
478 return; 538 ThreadUtils.awaitUninterruptibly(countDownLatch);
479 } 539 Logging.d(TAG, "stopCapture: Waiting for the camera to stop");
480 if (!barrier.await(CAMERA_STOP_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { 540 synchronized (cameraStateLock) {
481 Logging.e(TAG, "Camera stop timeout"); 541 while (cameraState != CameraState.IDLE) {
482 printStackTrace(); 542 try {
483 if (eventsHandler != null) { 543 cameraStateLock.wait();
484 eventsHandler.onCameraError("Camera stop timeout"); 544 } catch (InterruptedException e) {
545 Logging.w(TAG, "Interrupt while waiting capturer to stop: "
546 + e.getMessage());
547 }
485 } 548 }
486 } 549 }
487 frameObserver.onCapturerStopped(); 550
488 Logging.d(TAG, "stopCapture done"); 551 Logging.d(TAG, "stopCapture done");
489 } 552 }
490 553
491 private void stopCaptureOnCameraThread(boolean stopHandler) { 554 private void stopCaptureOnCameraThread() {
492 checkIsOnCameraThread(); 555 checkIsOnCameraThread();
493 Logging.d(TAG, "stopCaptureOnCameraThread"); 556 Logging.d(TAG, "stopCaptureOnCameraThread");
494 // Note that the camera might still not be started here if startCaptureOnCam eraThread failed 557
495 // and we posted a retry. 558 if (cameraState == CameraState.IDLE) {
559 Logging.d(TAG, "Calling stopCapture() for already stopped camera.");
560 return;
561 }
562
563 final CameraState oldState = cameraState;
564
565 // Clear the cameraThreadHandler first, in case stopPreview or
566 // other driver code deadlocks. Deadlock in
567 // android.hardware.Camera._stopPreview(Native Method) has
568 // been observed on Nexus 5 (hammerhead), OS version LMY48I.
569 // The camera might post another one or two preview frames
570 // before stopped, so we have to check |isCameraRunning|.
571 // Remove all pending Runnables posted from |this|.
572 // Make sure no other callback starts the camera again after this
573 cameraThreadHandler.removeCallbacksAndMessages(this /* token */);
574 setCameraState(CameraState.IDLE); // No more frames will be delivered
575
576 if (oldState == CameraState.STARTING) {
577 Logging.d(TAG, "Camera starting while calling stopCapture, canceling.");
578 capturerObserver.onCapturerStarted(false);
579 setCameraState(CameraState.IDLE);
580 return;
581 }
582
583 capturerObserver.onCapturerStopped();
584
585 releaseCameraOnCameraThread();
586 if (eventsHandler != null) {
587 eventsHandler.onCameraClosed();
588 }
589
590 Logging.d(TAG, "stopCaptureOnCameraThread done");
591 }
592
593 // Note: stopPreview or other driver code might deadlock. Deadlock in
594 // android.hardware.Camera._stopPreview(Native Method) has
595 // been observed on Nexus 5 (hammerhead), OS version LMY48I.
596 private void releaseCameraOnCameraThread() {
597 checkIsOnCameraThread();
496 598
497 // Make sure onTextureFrameAvailable() is not called anymore. 599 // Make sure onTextureFrameAvailable() is not called anymore.
498 if (surfaceHelper != null) { 600 surfaceHelper.stopListening();
499 surfaceHelper.stopListening();
500 }
501 if (stopHandler) {
502 // Clear the cameraThreadHandler first, in case stopPreview or
503 // other driver code deadlocks. Deadlock in
504 // android.hardware.Camera._stopPreview(Native Method) has
505 // been observed on Nexus 5 (hammerhead), OS version LMY48I.
506 // The camera might post another one or two preview frames
507 // before stopped, so we have to check |isCameraRunning|.
508 // Remove all pending Runnables posted from |this|.
509 isCameraRunning.set(false);
510 cameraThreadHandler.removeCallbacksAndMessages(this /* token */);
511 }
512 if (cameraStatistics != null) { 601 if (cameraStatistics != null) {
513 cameraStatistics.release(); 602 cameraStatistics.release();
514 cameraStatistics = null; 603 cameraStatistics = null;
515 } 604 }
516 Logging.d(TAG, "Stop preview."); 605 Logging.d(TAG, "Stop preview.");
517 if (camera != null) { 606 if (camera != null) {
518 camera.stopPreview(); 607 camera.stopPreview();
519 camera.setPreviewCallbackWithBuffer(null); 608 camera.setPreviewCallbackWithBuffer(null);
520 } 609 }
521 queuedBuffers.clear(); 610 queuedBuffers.clear();
522 captureFormat = null; 611 captureFormat = null;
523
524 Logging.d(TAG, "Release camera."); 612 Logging.d(TAG, "Release camera.");
525 if (camera != null) { 613 if (camera != null) {
526 camera.release(); 614 camera.release();
527 camera = null; 615 camera = null;
528 } 616 }
529 if (eventsHandler != null) {
530 eventsHandler.onCameraClosed();
531 }
532 Logging.d(TAG, "stopCaptureOnCameraThread done");
533 } 617 }
534 618
535 private void switchCameraOnCameraThread() { 619 private void switchCameraOnCameraThread() {
536 checkIsOnCameraThread(); 620 checkIsOnCameraThread();
537 if (!isCameraRunning.get()) { 621 Logging.d(TAG, "switchCameraOnCameraThread");
622
623 if (cameraState == CameraState.IDLE) {
538 Logging.e(TAG, "switchCameraOnCameraThread: Camera is stopped"); 624 Logging.e(TAG, "switchCameraOnCameraThread: Camera is stopped");
625 if (switchEventsHandler != null) {
626 switchEventsHandler.onCameraSwitchError("Camera is stopped.");
627 }
539 return; 628 return;
540 } 629 }
541 Logging.d(TAG, "switchCameraOnCameraThread"); 630
542 stopCaptureOnCameraThread(false /* stopHandler */); 631 stopCaptureOnCameraThread();
543 synchronized (cameraIdLock) { 632 synchronized (cameraIdLock) {
544 id = (id + 1) % android.hardware.Camera.getNumberOfCameras(); 633 id = (id + 1) % android.hardware.Camera.getNumberOfCameras();
545 } 634 }
546 startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramera te); 635 startCaptureOnCameraThread(requestedWidth, requestedHeight,
636 requestedFramerate);
637
547 Logging.d(TAG, "switchCameraOnCameraThread done"); 638 Logging.d(TAG, "switchCameraOnCameraThread done");
548 } 639 }
549 640
550 private void onOutputFormatRequestOnCameraThread(int width, int height, int fr amerate) { 641 private void onOutputFormatRequestOnCameraThread(int width, int height, int fr amerate) {
551 checkIsOnCameraThread(); 642 checkIsOnCameraThread();
552 Logging.d(TAG, "onOutputFormatRequestOnCameraThread: " + width + "x" + heigh t + 643 Logging.d(TAG, "onOutputFormatRequestOnCameraThread: " + width + "x" + heigh t +
553 "@" + framerate); 644 "@" + framerate);
554 frameObserver.onOutputFormatRequest(width, height, framerate); 645 capturerObserver.onOutputFormatRequest(width, height, framerate);
555 } 646 }
556 647
557 private int getDeviceOrientation() { 648 private int getDeviceOrientation() {
558 int orientation = 0; 649 int orientation = 0;
559 650
560 WindowManager wm = (WindowManager) applicationContext.getSystemService( 651 WindowManager wm = (WindowManager) applicationContext.getSystemService(
561 Context.WINDOW_SERVICE); 652 Context.WINDOW_SERVICE);
562 switch(wm.getDefaultDisplay().getRotation()) { 653 switch(wm.getDefaultDisplay().getRotation()) {
563 case Surface.ROTATION_90: 654 case Surface.ROTATION_90:
564 orientation = 90; 655 orientation = 90;
(...skipping 16 matching lines...) Expand all
581 int rotation = getDeviceOrientation(); 672 int rotation = getDeviceOrientation();
582 if (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK) { 673 if (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK) {
583 rotation = 360 - rotation; 674 rotation = 360 - rotation;
584 } 675 }
585 return (info.orientation + rotation) % 360; 676 return (info.orientation + rotation) % 360;
586 } 677 }
587 678
588 // Called on cameraThread so must not "synchronized". 679 // Called on cameraThread so must not "synchronized".
589 @Override 680 @Override
590 public void onPreviewFrame(byte[] data, android.hardware.Camera callbackCamera ) { 681 public void onPreviewFrame(byte[] data, android.hardware.Camera callbackCamera ) {
591 checkIsOnCameraThread(); 682 if (cameraState != CameraState.RUNNING) {
592 if (!isCameraRunning.get()) { 683 Logging.d(TAG, "onPreviewFrame: Camera is stopped");
593 Logging.e(TAG, "onPreviewFrame: Camera is stopped");
594 return; 684 return;
595 } 685 }
686 checkIsOnCameraThread();
687
596 if (!queuedBuffers.contains(data)) { 688 if (!queuedBuffers.contains(data)) {
597 // |data| is an old invalid buffer. 689 // |data| is an old invalid buffer.
598 return; 690 return;
599 } 691 }
600 if (camera != callbackCamera) { 692 if (camera != callbackCamera) {
601 throw new RuntimeException("Unexpected camera in callback!"); 693 throw new RuntimeException("Unexpected camera in callback!");
602 } 694 }
603 695
604 final long captureTimeNs = 696 final long captureTimeNs =
605 TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime()); 697 TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime());
606 698
607 if (eventsHandler != null && !firstFrameReported) { 699 if (eventsHandler != null && !firstFrameReported) {
608 eventsHandler.onFirstFrameAvailable(); 700 eventsHandler.onFirstFrameAvailable();
609 firstFrameReported = true; 701 firstFrameReported = true;
610 } 702 }
611 703
612 cameraStatistics.addFrame(); 704 cameraStatistics.addFrame();
613 frameObserver.onByteBufferFrameCaptured(data, captureFormat.width, captureFo rmat.height, 705 capturerObserver.onByteBufferFrameCaptured(data, captureFormat.width, captur eFormat.height,
614 getFrameOrientation(), captureTimeNs); 706 getFrameOrientation(), captureTimeNs);
615 camera.addCallbackBuffer(data); 707 camera.addCallbackBuffer(data);
616 } 708 }
617 709
618 @Override 710 @Override
619 public void onTextureFrameAvailable( 711 public void onTextureFrameAvailable(
620 int oesTextureId, float[] transformMatrix, long timestampNs) { 712 int oesTextureId, float[] transformMatrix, long timestampNs) {
621 checkIsOnCameraThread(); 713 if (cameraState != CameraState.RUNNING) {
622 if (!isCameraRunning.get()) { 714 Logging.d(TAG, "onTextureFrameAvailable: Camera is stopped");
623 Logging.e(TAG, "onTextureFrameAvailable: Camera is stopped");
624 surfaceHelper.returnTextureFrame(); 715 surfaceHelper.returnTextureFrame();
625 return; 716 return;
626 } 717 }
718 checkIsOnCameraThread();
719
627 if (eventsHandler != null && !firstFrameReported) { 720 if (eventsHandler != null && !firstFrameReported) {
628 eventsHandler.onFirstFrameAvailable(); 721 eventsHandler.onFirstFrameAvailable();
629 firstFrameReported = true; 722 firstFrameReported = true;
630 } 723 }
631 724
632 int rotation = getFrameOrientation(); 725 int rotation = getFrameOrientation();
633 if (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT) { 726 if (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT) {
634 // Undo the mirror that the OS "helps" us with. 727 // Undo the mirror that the OS "helps" us with.
635 // http://developer.android.com/reference/android/hardware/Camera.html#set DisplayOrientation(int) 728 // http://developer.android.com/reference/android/hardware/Camera.html#set DisplayOrientation(int)
636 transformMatrix = 729 transformMatrix =
637 RendererCommon.multiplyMatrices(transformMatrix, RendererCommon.horizo ntalFlipMatrix()); 730 RendererCommon.multiplyMatrices(transformMatrix, RendererCommon.horizo ntalFlipMatrix());
638 } 731 }
639 cameraStatistics.addFrame(); 732 cameraStatistics.addFrame();
640 frameObserver.onTextureFrameCaptured(captureFormat.width, captureFormat.heig ht, oesTextureId, 733 capturerObserver.onTextureFrameCaptured(captureFormat.width, captureFormat.h eight, oesTextureId,
641 transformMatrix, rotation, timestampNs); 734 transformMatrix, rotation, timestampNs);
642 } 735 }
643 } 736 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698