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

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

Powered by Google App Engine
This is Rietveld 408576698