OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright 2015 The WebRTC project authors. All Rights Reserved. | |
3 * | |
4 * Use of this source code is governed by a BSD-style license | |
5 * that can be found in the LICENSE file in the root of the source | |
6 * tree. An additional intellectual property rights grant can be found | |
7 * in the file PATENTS. All contributing project authors may | |
8 * be found in the AUTHORS file in the root of the source tree. | |
9 */ | |
10 | |
11 package org.webrtc; | |
12 | |
13 import org.webrtc.CameraEnumerationAndroid.CaptureFormat; | |
14 | |
15 import android.content.Context; | |
16 import android.os.Handler; | |
17 import android.os.SystemClock; | |
18 import android.view.Surface; | |
19 import android.view.WindowManager; | |
20 | |
21 import java.io.IOException; | |
22 import java.nio.ByteBuffer; | |
23 import java.util.HashSet; | |
24 import java.util.List; | |
25 import java.util.Set; | |
26 import java.util.concurrent.CountDownLatch; | |
27 import java.util.concurrent.TimeUnit; | |
28 | |
29 // Android specific implementation of VideoCapturer. | |
30 // An instance of this class can be created by an application using | |
31 // VideoCapturerAndroid.create(); | |
32 // This class extends VideoCapturer with a method to easily switch between the | |
33 // front and back camera. It also provides methods for enumerating valid device | |
34 // names. | |
35 // | |
36 // Threading notes: this class is called from C++ code, Android Camera callbacks
, and possibly | |
37 // arbitrary Java threads. All public entry points are thread safe, and delegate
the work to the | |
38 // camera thread. The internal *OnCameraThread() methods must check |camera| for
null to check if | |
39 // the camera has been stopped. | |
40 // TODO(magjed): This class name is now confusing - rename to Camera1VideoCaptur
er. | |
41 @SuppressWarnings("deprecation") | |
42 public class VideoCapturerAndroid implements | |
43 CameraVideoCapturer, | |
44 android.hardware.Camera.PreviewCallback, | |
45 SurfaceTextureHelper.OnTextureFrameAvailableListener { | |
46 private final static String TAG = "VideoCapturerAndroid"; | |
47 private static final int CAMERA_STOP_TIMEOUT_MS = 7000; | |
48 | |
49 private android.hardware.Camera camera; // Only non-null while capturing. | |
50 private final Object handlerLock = new Object(); | |
51 // |cameraThreadHandler| must be synchronized on |handlerLock| when not on the
camera thread, | |
52 // or when modifying the reference. Use maybePostOnCameraThread() instead of p
osting directly to | |
53 // the handler - this way all callbacks with a specifed token can be removed a
t once. | |
54 private 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. | |
62 private int requestedWidth; | |
63 private int requestedHeight; | |
64 private int requestedFramerate; | |
65 // The capture format will be the closest supported format to the requested fo
rmat. | |
66 private CaptureFormat captureFormat; | |
67 private final Object pendingCameraSwitchLock = new Object(); | |
68 private volatile boolean pendingCameraSwitch; | |
69 private CapturerObserver frameObserver = null; | |
70 private final CameraEventsHandler eventsHandler; | |
71 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; | |
82 | |
83 // Camera error callback. | |
84 private final android.hardware.Camera.ErrorCallback cameraErrorCallback = | |
85 new android.hardware.Camera.ErrorCallback() { | |
86 @Override | |
87 public void onError(int error, android.hardware.Camera camera) { | |
88 String errorMessage; | |
89 if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) { | |
90 errorMessage = "Camera server died!"; | |
91 } else { | |
92 errorMessage = "Camera error: " + error; | |
93 } | |
94 Logging.e(TAG, errorMessage); | |
95 if (eventsHandler != null) { | |
96 eventsHandler.onCameraError(errorMessage); | |
97 } | |
98 } | |
99 }; | |
100 | |
101 public static VideoCapturerAndroid create(String name, | |
102 CameraEventsHandler eventsHandler) { | |
103 return VideoCapturerAndroid.create(name, eventsHandler, false /* captureToTe
xture */); | |
104 } | |
105 | |
106 // Use ctor directly instead. | |
107 @Deprecated | |
108 public static VideoCapturerAndroid create(String name, | |
109 CameraEventsHandler eventsHandler, boolean captureToTexture) { | |
110 try { | |
111 return new VideoCapturerAndroid(name, eventsHandler, captureToTexture); | |
112 } catch (RuntimeException e) { | |
113 Logging.e(TAG, "Couldn't create camera.", e); | |
114 return null; | |
115 } | |
116 } | |
117 | |
118 public void printStackTrace() { | |
119 Thread cameraThread = null; | |
120 synchronized (handlerLock) { | |
121 if (cameraThreadHandler != null) { | |
122 cameraThread = cameraThreadHandler.getLooper().getThread(); | |
123 } | |
124 } | |
125 if (cameraThread != null) { | |
126 StackTraceElement[] cameraStackTraces = cameraThread.getStackTrace(); | |
127 if (cameraStackTraces.length > 0) { | |
128 Logging.d(TAG, "VideoCapturerAndroid stacks trace:"); | |
129 for (StackTraceElement stackTrace : cameraStackTraces) { | |
130 Logging.d(TAG, stackTrace.toString()); | |
131 } | |
132 } | |
133 } | |
134 } | |
135 | |
136 // Switch camera to the next valid camera id. This can only be called while | |
137 // the camera is running. | |
138 @Override | |
139 public void switchCamera(final CameraSwitchHandler switchEventsHandler) { | |
140 if (android.hardware.Camera.getNumberOfCameras() < 2) { | |
141 if (switchEventsHandler != null) { | |
142 switchEventsHandler.onCameraSwitchError("No camera to switch to."); | |
143 } | |
144 return; | |
145 } | |
146 synchronized (pendingCameraSwitchLock) { | |
147 if (pendingCameraSwitch) { | |
148 // Do not handle multiple camera switch request to avoid blocking | |
149 // camera thread by handling too many switch request from a queue. | |
150 Logging.w(TAG, "Ignoring camera switch request."); | |
151 if (switchEventsHandler != null) { | |
152 switchEventsHandler.onCameraSwitchError("Pending camera switch already
in progress."); | |
153 } | |
154 return; | |
155 } | |
156 pendingCameraSwitch = true; | |
157 } | |
158 final boolean didPost = maybePostOnCameraThread(new Runnable() { | |
159 @Override | |
160 public void run() { | |
161 switchCameraOnCameraThread(); | |
162 synchronized (pendingCameraSwitchLock) { | |
163 pendingCameraSwitch = false; | |
164 } | |
165 if (switchEventsHandler != null) { | |
166 switchEventsHandler.onCameraSwitchDone( | |
167 info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FR
ONT); | |
168 } | |
169 } | |
170 }); | |
171 if (!didPost && switchEventsHandler != null) { | |
172 switchEventsHandler.onCameraSwitchError("Camera is stopped."); | |
173 } | |
174 } | |
175 | |
176 // Requests a new output format from the video capturer. Captured frames | |
177 // by the camera will be scaled/or dropped by the video capturer. | |
178 // It does not matter if width and height are flipped. I.E, |width| = 640, |he
ight| = 480 produce | |
179 // the same result as |width| = 480, |height| = 640. | |
180 // TODO(magjed/perkj): Document what this function does. Change name? | |
181 @Override | |
182 public void onOutputFormatRequest(final int width, final int height, final int
framerate) { | |
183 maybePostOnCameraThread(new Runnable() { | |
184 @Override public void run() { | |
185 onOutputFormatRequestOnCameraThread(width, height, framerate); | |
186 } | |
187 }); | |
188 } | |
189 | |
190 // Reconfigure the camera to capture in a new format. This should only be call
ed while the camera | |
191 // is running. | |
192 @Override | |
193 public void changeCaptureFormat(final int width, final int height, final int f
ramerate) { | |
194 maybePostOnCameraThread(new Runnable() { | |
195 @Override public void run() { | |
196 startPreviewOnCameraThread(width, height, framerate); | |
197 } | |
198 }); | |
199 } | |
200 | |
201 // Helper function to retrieve the current camera id synchronously. Note that
the camera id might | |
202 // change at any point by switchCamera() calls. | |
203 private int getCurrentCameraId() { | |
204 synchronized (cameraIdLock) { | |
205 return id; | |
206 } | |
207 } | |
208 | |
209 @Override | |
210 public List<CaptureFormat> getSupportedFormats() { | |
211 return Camera1Enumerator.getSupportedFormats(getCurrentCameraId()); | |
212 } | |
213 | |
214 // Returns true if this VideoCapturer is setup to capture video frames to a Su
rfaceTexture. | |
215 public boolean isCapturingToTexture() { | |
216 return isCapturingToTexture; | |
217 } | |
218 | |
219 public VideoCapturerAndroid(String cameraName, CameraEventsHandler eventsHandl
er, | |
220 boolean captureToTexture) { | |
221 if (android.hardware.Camera.getNumberOfCameras() == 0) { | |
222 throw new RuntimeException("No cameras available"); | |
223 } | |
224 if (cameraName == null || cameraName.equals("")) { | |
225 this.id = 0; | |
226 } else { | |
227 this.id = Camera1Enumerator.getCameraIndex(cameraName); | |
228 } | |
229 this.eventsHandler = eventsHandler; | |
230 isCapturingToTexture = captureToTexture; | |
231 Logging.d(TAG, "VideoCapturerAndroid isCapturingToTexture : " + isCapturingT
oTexture); | |
232 } | |
233 | |
234 private void checkIsOnCameraThread() { | |
235 synchronized (handlerLock) { | |
236 if (cameraThreadHandler == null) { | |
237 Logging.e(TAG, "Camera is stopped - can't check thread."); | |
238 } else if (Thread.currentThread() != cameraThreadHandler.getLooper().getTh
read()) { | |
239 throw new IllegalStateException("Wrong thread"); | |
240 } | |
241 } | |
242 } | |
243 | |
244 private boolean maybePostOnCameraThread(Runnable runnable) { | |
245 return maybePostDelayedOnCameraThread(0 /* delayMs */, runnable); | |
246 } | |
247 | |
248 private boolean maybePostDelayedOnCameraThread(int delayMs, Runnable runnable)
{ | |
249 synchronized (handlerLock) { | |
250 return cameraThreadHandler != null | |
251 && cameraThreadHandler.postAtTime( | |
252 runnable, this /* token */, SystemClock.uptimeMillis() + delayMs); | |
253 } | |
254 } | |
255 | |
256 @Override | |
257 public void dispose() { | |
258 Logging.d(TAG, "dispose"); | |
259 } | |
260 | |
261 // Note that this actually opens the camera, and Camera callbacks run on the | |
262 // thread that calls open(), so this is done on the CameraThread. | |
263 @Override | |
264 public void startCapture( | |
265 final int width, final int height, final int framerate, | |
266 final SurfaceTextureHelper surfaceTextureHelper, final Context application
Context, | |
267 final CapturerObserver frameObserver) { | |
268 Logging.d(TAG, "startCapture requested: " + width + "x" + height + "@" + fra
merate); | |
269 if (surfaceTextureHelper == null) { | |
270 frameObserver.onCapturerStarted(false /* success */); | |
271 if (eventsHandler != null) { | |
272 eventsHandler.onCameraError("No SurfaceTexture created."); | |
273 } | |
274 return; | |
275 } | |
276 if (applicationContext == null) { | |
277 throw new IllegalArgumentException("applicationContext not set."); | |
278 } | |
279 if (frameObserver == null) { | |
280 throw new IllegalArgumentException("frameObserver not set."); | |
281 } | |
282 synchronized (handlerLock) { | |
283 if (this.cameraThreadHandler != null) { | |
284 throw new RuntimeException("Camera has already been started."); | |
285 } | |
286 this.cameraThreadHandler = surfaceTextureHelper.getHandler(); | |
287 this.surfaceHelper = surfaceTextureHelper; | |
288 final boolean didPost = maybePostOnCameraThread(new Runnable() { | |
289 @Override | |
290 public void run() { | |
291 openCameraAttempts = 0; | |
292 startCaptureOnCameraThread(width, height, framerate, frameObserver, | |
293 applicationContext); | |
294 } | |
295 }); | |
296 if (!didPost) { | |
297 frameObserver.onCapturerStarted(false); | |
298 if (eventsHandler != null) { | |
299 eventsHandler.onCameraError("Could not post task to camera thread."); | |
300 } | |
301 } | |
302 } | |
303 } | |
304 | |
305 private void startCaptureOnCameraThread( | |
306 final int width, final int height, final int framerate, final CapturerObse
rver frameObserver, | |
307 final Context applicationContext) { | |
308 synchronized (handlerLock) { | |
309 if (cameraThreadHandler == null) { | |
310 Logging.e(TAG, "startCaptureOnCameraThread: Camera is stopped"); | |
311 return; | |
312 } else { | |
313 checkIsOnCameraThread(); | |
314 } | |
315 } | |
316 if (camera != null) { | |
317 Logging.e(TAG, "startCaptureOnCameraThread: Camera has already been starte
d."); | |
318 return; | |
319 } | |
320 this.applicationContext = applicationContext; | |
321 this.frameObserver = frameObserver; | |
322 this.firstFrameReported = false; | |
323 | |
324 try { | |
325 try { | |
326 synchronized (cameraIdLock) { | |
327 Logging.d(TAG, "Opening camera " + id); | |
328 if (eventsHandler != null) { | |
329 eventsHandler.onCameraOpening(id); | |
330 } | |
331 camera = android.hardware.Camera.open(id); | |
332 info = new android.hardware.Camera.CameraInfo(); | |
333 android.hardware.Camera.getCameraInfo(id, info); | |
334 } | |
335 } catch (RuntimeException e) { | |
336 openCameraAttempts++; | |
337 if (openCameraAttempts < MAX_OPEN_CAMERA_ATTEMPTS) { | |
338 Logging.e(TAG, "Camera.open failed, retrying", e); | |
339 maybePostDelayedOnCameraThread(OPEN_CAMERA_DELAY_MS, new Runnable() { | |
340 @Override public void run() { | |
341 startCaptureOnCameraThread(width, height, framerate, frameObserver
, | |
342 applicationContext); | |
343 } | |
344 }); | |
345 return; | |
346 } | |
347 throw e; | |
348 } | |
349 | |
350 camera.setPreviewTexture(surfaceHelper.getSurfaceTexture()); | |
351 | |
352 Logging.d(TAG, "Camera orientation: " + info.orientation + | |
353 " .Device orientation: " + getDeviceOrientation()); | |
354 camera.setErrorCallback(cameraErrorCallback); | |
355 startPreviewOnCameraThread(width, height, framerate); | |
356 frameObserver.onCapturerStarted(true); | |
357 if (isCapturingToTexture) { | |
358 surfaceHelper.startListening(this); | |
359 } | |
360 | |
361 // Start camera observer. | |
362 cameraStatistics = new CameraStatistics(surfaceHelper, eventsHandler); | |
363 } catch (IOException|RuntimeException e) { | |
364 Logging.e(TAG, "startCapture failed", e); | |
365 // Make sure the camera is released. | |
366 stopCaptureOnCameraThread(true /* stopHandler */); | |
367 frameObserver.onCapturerStarted(false); | |
368 if (eventsHandler != null) { | |
369 eventsHandler.onCameraError("Camera can not be started."); | |
370 } | |
371 } | |
372 } | |
373 | |
374 // (Re)start preview with the closest supported format to |width| x |height| @
|framerate|. | |
375 private void startPreviewOnCameraThread(int width, int height, int framerate)
{ | |
376 synchronized (handlerLock) { | |
377 if (cameraThreadHandler == null || camera == null) { | |
378 Logging.e(TAG, "startPreviewOnCameraThread: Camera is stopped"); | |
379 return; | |
380 } else { | |
381 checkIsOnCameraThread(); | |
382 } | |
383 } | |
384 Logging.d( | |
385 TAG, "startPreviewOnCameraThread requested: " + width + "x" + height + "
@" + framerate); | |
386 | |
387 requestedWidth = width; | |
388 requestedHeight = height; | |
389 requestedFramerate = framerate; | |
390 | |
391 // Find closest supported format for |width| x |height| @ |framerate|. | |
392 final android.hardware.Camera.Parameters parameters = camera.getParameters()
; | |
393 final List<CaptureFormat.FramerateRange> supportedFramerates = | |
394 Camera1Enumerator.convertFramerates(parameters.getSupportedPreviewFpsRan
ge()); | |
395 Logging.d(TAG, "Available fps ranges: " + supportedFramerates); | |
396 | |
397 final CaptureFormat.FramerateRange fpsRange = | |
398 CameraEnumerationAndroid.getClosestSupportedFramerateRange(supportedFram
erates, framerate); | |
399 | |
400 final Size previewSize = CameraEnumerationAndroid.getClosestSupportedSize( | |
401 Camera1Enumerator.convertSizes(parameters.getSupportedPreviewSizes()), w
idth, height); | |
402 | |
403 final CaptureFormat captureFormat = | |
404 new CaptureFormat(previewSize.width, previewSize.height, fpsRange); | |
405 | |
406 // Check if we are already using this capture format, then we don't need to
do anything. | |
407 if (captureFormat.equals(this.captureFormat)) { | |
408 return; | |
409 } | |
410 | |
411 // Update camera parameters. | |
412 Logging.d(TAG, "isVideoStabilizationSupported: " + | |
413 parameters.isVideoStabilizationSupported()); | |
414 if (parameters.isVideoStabilizationSupported()) { | |
415 parameters.setVideoStabilization(true); | |
416 } | |
417 // Note: setRecordingHint(true) actually decrease frame rate on N5. | |
418 // parameters.setRecordingHint(true); | |
419 if (captureFormat.framerate.max > 0) { | |
420 parameters.setPreviewFpsRange(captureFormat.framerate.min, captureFormat.f
ramerate.max); | |
421 } | |
422 parameters.setPreviewSize(previewSize.width, previewSize.height); | |
423 | |
424 if (!isCapturingToTexture) { | |
425 parameters.setPreviewFormat(captureFormat.imageFormat); | |
426 } | |
427 // Picture size is for taking pictures and not for preview/video, but we nee
d to set it anyway | |
428 // as a workaround for an aspect ratio problem on Nexus 7. | |
429 final Size pictureSize = CameraEnumerationAndroid.getClosestSupportedSize( | |
430 Camera1Enumerator.convertSizes(parameters.getSupportedPictureSizes()), w
idth, height); | |
431 parameters.setPictureSize(pictureSize.width, pictureSize.height); | |
432 | |
433 // Temporarily stop preview if it's already running. | |
434 if (this.captureFormat != null) { | |
435 camera.stopPreview(); | |
436 // Calling |setPreviewCallbackWithBuffer| with null should clear the inter
nal camera buffer | |
437 // queue, but sometimes we receive a frame with the old resolution after t
his call anyway. | |
438 camera.setPreviewCallbackWithBuffer(null); | |
439 } | |
440 | |
441 // (Re)start preview. | |
442 Logging.d(TAG, "Start capturing: " + captureFormat); | |
443 this.captureFormat = captureFormat; | |
444 | |
445 List<String> focusModes = parameters.getSupportedFocusModes(); | |
446 if (focusModes.contains(android.hardware.Camera.Parameters.FOCUS_MODE_CONTIN
UOUS_VIDEO)) { | |
447 parameters.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_CONT
INUOUS_VIDEO); | |
448 } | |
449 | |
450 camera.setParameters(parameters); | |
451 // Calculate orientation manually and send it as CVO instead. | |
452 camera.setDisplayOrientation(0 /* degrees */); | |
453 if (!isCapturingToTexture) { | |
454 queuedBuffers.clear(); | |
455 final int frameSize = captureFormat.frameSize(); | |
456 for (int i = 0; i < NUMBER_OF_CAPTURE_BUFFERS; ++i) { | |
457 final ByteBuffer buffer = ByteBuffer.allocateDirect(frameSize); | |
458 queuedBuffers.add(buffer.array()); | |
459 camera.addCallbackBuffer(buffer.array()); | |
460 } | |
461 camera.setPreviewCallbackWithBuffer(this); | |
462 } | |
463 camera.startPreview(); | |
464 } | |
465 | |
466 // Blocks until camera is known to be stopped. | |
467 @Override | |
468 public void stopCapture() throws InterruptedException { | |
469 Logging.d(TAG, "stopCapture"); | |
470 final CountDownLatch barrier = new CountDownLatch(1); | |
471 final boolean didPost = maybePostOnCameraThread(new Runnable() { | |
472 @Override public void run() { | |
473 stopCaptureOnCameraThread(true /* stopHandler */); | |
474 barrier.countDown(); | |
475 } | |
476 }); | |
477 if (!didPost) { | |
478 Logging.e(TAG, "Calling stopCapture() for already stopped camera."); | |
479 return; | |
480 } | |
481 if (!barrier.await(CAMERA_STOP_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { | |
482 Logging.e(TAG, "Camera stop timeout"); | |
483 printStackTrace(); | |
484 if (eventsHandler != null) { | |
485 eventsHandler.onCameraError("Camera stop timeout"); | |
486 } | |
487 } | |
488 Logging.d(TAG, "stopCapture done"); | |
489 } | |
490 | |
491 private void stopCaptureOnCameraThread(boolean stopHandler) { | |
492 synchronized (handlerLock) { | |
493 if (cameraThreadHandler == null) { | |
494 Logging.e(TAG, "stopCaptureOnCameraThread: Camera is stopped"); | |
495 } else { | |
496 checkIsOnCameraThread(); | |
497 } | |
498 } | |
499 Logging.d(TAG, "stopCaptureOnCameraThread"); | |
500 // Note that the camera might still not be started here if startCaptureOnCam
eraThread failed | |
501 // and we posted a retry. | |
502 | |
503 // Make sure onTextureFrameAvailable() is not called anymore. | |
504 if (surfaceHelper != null) { | |
505 surfaceHelper.stopListening(); | |
506 } | |
507 if (stopHandler) { | |
508 synchronized (handlerLock) { | |
509 // Clear the cameraThreadHandler first, in case stopPreview or | |
510 // other driver code deadlocks. Deadlock in | |
511 // android.hardware.Camera._stopPreview(Native Method) has | |
512 // been observed on Nexus 5 (hammerhead), OS version LMY48I. | |
513 // The camera might post another one or two preview frames | |
514 // before stopped, so we have to check for a null | |
515 // cameraThreadHandler in our handler. Remove all pending | |
516 // Runnables posted from |this|. | |
517 if (cameraThreadHandler != null) { | |
518 cameraThreadHandler.removeCallbacksAndMessages(this /* token */); | |
519 cameraThreadHandler = null; | |
520 } | |
521 surfaceHelper = null; | |
522 } | |
523 } | |
524 if (cameraStatistics != null) { | |
525 cameraStatistics.release(); | |
526 cameraStatistics = null; | |
527 } | |
528 Logging.d(TAG, "Stop preview."); | |
529 if (camera != null) { | |
530 camera.stopPreview(); | |
531 camera.setPreviewCallbackWithBuffer(null); | |
532 } | |
533 queuedBuffers.clear(); | |
534 captureFormat = null; | |
535 | |
536 Logging.d(TAG, "Release camera."); | |
537 if (camera != null) { | |
538 camera.release(); | |
539 camera = null; | |
540 } | |
541 if (eventsHandler != null) { | |
542 eventsHandler.onCameraClosed(); | |
543 } | |
544 Logging.d(TAG, "stopCaptureOnCameraThread done"); | |
545 } | |
546 | |
547 private void switchCameraOnCameraThread() { | |
548 synchronized (handlerLock) { | |
549 if (cameraThreadHandler == null) { | |
550 Logging.e(TAG, "switchCameraOnCameraThread: Camera is stopped"); | |
551 return; | |
552 } else { | |
553 checkIsOnCameraThread(); | |
554 } | |
555 } | |
556 Logging.d(TAG, "switchCameraOnCameraThread"); | |
557 stopCaptureOnCameraThread(false /* stopHandler */); | |
558 synchronized (cameraIdLock) { | |
559 id = (id + 1) % android.hardware.Camera.getNumberOfCameras(); | |
560 } | |
561 startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramera
te, frameObserver, | |
562 applicationContext); | |
563 Logging.d(TAG, "switchCameraOnCameraThread done"); | |
564 } | |
565 | |
566 private void onOutputFormatRequestOnCameraThread(int width, int height, int fr
amerate) { | |
567 synchronized (handlerLock) { | |
568 if (cameraThreadHandler == null || camera == null) { | |
569 Logging.e(TAG, "onOutputFormatRequestOnCameraThread: Camera is stopped")
; | |
570 return; | |
571 } else { | |
572 checkIsOnCameraThread(); | |
573 } | |
574 } | |
575 Logging.d(TAG, "onOutputFormatRequestOnCameraThread: " + width + "x" + heigh
t + | |
576 "@" + framerate); | |
577 frameObserver.onOutputFormatRequest(width, height, framerate); | |
578 } | |
579 | |
580 private int getDeviceOrientation() { | |
581 int orientation = 0; | |
582 | |
583 WindowManager wm = (WindowManager) applicationContext.getSystemService( | |
584 Context.WINDOW_SERVICE); | |
585 switch(wm.getDefaultDisplay().getRotation()) { | |
586 case Surface.ROTATION_90: | |
587 orientation = 90; | |
588 break; | |
589 case Surface.ROTATION_180: | |
590 orientation = 180; | |
591 break; | |
592 case Surface.ROTATION_270: | |
593 orientation = 270; | |
594 break; | |
595 case Surface.ROTATION_0: | |
596 default: | |
597 orientation = 0; | |
598 break; | |
599 } | |
600 return orientation; | |
601 } | |
602 | |
603 private int getFrameOrientation() { | |
604 int rotation = getDeviceOrientation(); | |
605 if (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK) { | |
606 rotation = 360 - rotation; | |
607 } | |
608 return (info.orientation + rotation) % 360; | |
609 } | |
610 | |
611 // Called on cameraThread so must not "synchronized". | |
612 @Override | |
613 public void onPreviewFrame(byte[] data, android.hardware.Camera callbackCamera
) { | |
614 synchronized (handlerLock) { | |
615 if (cameraThreadHandler == null) { | |
616 Logging.e(TAG, "onPreviewFrame: Camera is stopped"); | |
617 return; | |
618 } else { | |
619 checkIsOnCameraThread(); | |
620 } | |
621 } | |
622 if (!queuedBuffers.contains(data)) { | |
623 // |data| is an old invalid buffer. | |
624 return; | |
625 } | |
626 if (camera != callbackCamera) { | |
627 throw new RuntimeException("Unexpected camera in callback!"); | |
628 } | |
629 | |
630 final long captureTimeNs = | |
631 TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime()); | |
632 | |
633 if (eventsHandler != null && !firstFrameReported) { | |
634 eventsHandler.onFirstFrameAvailable(); | |
635 firstFrameReported = true; | |
636 } | |
637 | |
638 cameraStatistics.addFrame(); | |
639 frameObserver.onByteBufferFrameCaptured(data, captureFormat.width, captureFo
rmat.height, | |
640 getFrameOrientation(), captureTimeNs); | |
641 camera.addCallbackBuffer(data); | |
642 } | |
643 | |
644 @Override | |
645 public void onTextureFrameAvailable( | |
646 int oesTextureId, float[] transformMatrix, long timestampNs) { | |
647 synchronized (handlerLock) { | |
648 if (cameraThreadHandler == null) { | |
649 Logging.e(TAG, "onTextureFrameAvailable: Camera is stopped"); | |
650 surfaceHelper.returnTextureFrame(); | |
651 return; | |
652 } else { | |
653 checkIsOnCameraThread(); | |
654 } | |
655 } | |
656 if (eventsHandler != null && !firstFrameReported) { | |
657 eventsHandler.onFirstFrameAvailable(); | |
658 firstFrameReported = true; | |
659 } | |
660 | |
661 int rotation = getFrameOrientation(); | |
662 if (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT) { | |
663 // Undo the mirror that the OS "helps" us with. | |
664 // http://developer.android.com/reference/android/hardware/Camera.html#set
DisplayOrientation(int) | |
665 transformMatrix = | |
666 RendererCommon.multiplyMatrices(transformMatrix, RendererCommon.horizo
ntalFlipMatrix()); | |
667 } | |
668 cameraStatistics.addFrame(); | |
669 frameObserver.onTextureFrameCaptured(captureFormat.width, captureFormat.heig
ht, oesTextureId, | |
670 transformMatrix, rotation, timestampNs); | |
671 } | |
672 } | |
OLD | NEW |