| Index: webrtc/api/android/java/src/org/webrtc/VideoCapturerAndroid.java
|
| diff --git a/webrtc/api/android/java/src/org/webrtc/VideoCapturerAndroid.java b/webrtc/api/android/java/src/org/webrtc/VideoCapturerAndroid.java
|
| index d3a9faff21b9bcb18c1daee086aedf3ec4305c0c..a60a5bde6e29c76c5991e78860568a169e116843 100644
|
| --- a/webrtc/api/android/java/src/org/webrtc/VideoCapturerAndroid.java
|
| +++ b/webrtc/api/android/java/src/org/webrtc/VideoCapturerAndroid.java
|
| @@ -46,40 +46,55 @@ public class VideoCapturerAndroid implements
|
| SurfaceTextureHelper.OnTextureFrameAvailableListener {
|
| private static final String TAG = "VideoCapturerAndroid";
|
| private static final int CAMERA_STOP_TIMEOUT_MS = 7000;
|
| + // Arbitrary queue depth. Higher number means more memory allocated & held,
|
| + // lower number means more sensitivity to processing time in the client (and
|
| + // potentially stalling the capturer if it runs out of buffers to write to).
|
| + private static final int NUMBER_OF_CAPTURE_BUFFERS = 3;
|
| + private final static int MAX_OPEN_CAMERA_ATTEMPTS = 3;
|
| + private final static int OPEN_CAMERA_DELAY_MS = 500;
|
| + private static enum CameraState { UNINITIALIAZED, IDLE, STARTING, RUNNING }
|
|
|
| - private android.hardware.Camera camera; // Only non-null while capturing.
|
| - private final AtomicBoolean isCameraRunning = new AtomicBoolean();
|
| - // Use maybePostOnCameraThread() instead of posting directly to the handler - this way all
|
| + private final Set<byte[]> queuedBuffers = new HashSet<byte[]>();
|
| + private final boolean isCapturingToTexture;
|
| + private final CameraEventsHandler eventsHandler;
|
| +
|
| + // Initialized on initialize
|
| + // -------------------------
|
| + // Use postOnCameraThread() instead of posting directly to the handler - this way all
|
| // callbacks with a specifed token can be removed at once.
|
| - private volatile Handler cameraThreadHandler;
|
| + private Handler cameraThreadHandler;
|
| private Context applicationContext;
|
| - // Synchronization lock for |id|.
|
| - private final Object cameraIdLock = new Object();
|
| - private int id;
|
| - private android.hardware.Camera.CameraInfo info;
|
| - private CameraStatistics cameraStatistics;
|
| + private CapturerObserver capturerObserver = null;
|
| + private SurfaceTextureHelper surfaceHelper;
|
| +
|
| + // Internal state - will only be touched from the camera thread
|
| + // ------------------------------------------------------------
|
| + private android.hardware.Camera camera; // Only non-null while capturing.
|
| // Remember the requested format in case we want to switch cameras.
|
| private int requestedWidth;
|
| private int requestedHeight;
|
| private int requestedFramerate;
|
| - // The capture format will be the closest supported format to the requested format.
|
| + // Populated with the information of the active camera.
|
| + private android.hardware.Camera.CameraInfo info;
|
| + // Active capture format.
|
| private CaptureFormat captureFormat;
|
| - private final Object pendingCameraSwitchLock = new Object();
|
| - private volatile boolean pendingCameraSwitch;
|
| - private CapturerObserver frameObserver = null;
|
| - private final CameraEventsHandler eventsHandler;
|
| + private CameraStatistics cameraStatistics;
|
| private boolean firstFrameReported;
|
| - // Arbitrary queue depth. Higher number means more memory allocated & held,
|
| - // lower number means more sensitivity to processing time in the client (and
|
| - // potentially stalling the capturer if it runs out of buffers to write to).
|
| - private static final int NUMBER_OF_CAPTURE_BUFFERS = 3;
|
| - private final Set<byte[]> queuedBuffers = new HashSet<byte[]>();
|
| - private final boolean isCapturingToTexture;
|
| - private SurfaceTextureHelper surfaceHelper;
|
| - private final static int MAX_OPEN_CAMERA_ATTEMPTS = 3;
|
| - private final static int OPEN_CAMERA_DELAY_MS = 500;
|
| private int openCameraAttempts;
|
|
|
| + // Locked objects
|
| + // --------------
|
| + private final Object cameraIdLock = new Object();
|
| + private int id; // Only edited from camera thread while holding the lock.
|
| +
|
| + private final Object cameraStateLock = new Object();
|
| + private CameraState cameraState = CameraState.UNINITIALIAZED;
|
| +
|
| + // Only allow one camera switch a time.
|
| + private final Object cameraSwitchLock = new Object();
|
| + private boolean pendingCameraSwitch;
|
| + private CameraSwitchHandler switchEventsHandler;
|
| +
|
| // Camera error callback.
|
| private final android.hardware.Camera.ErrorCallback cameraErrorCallback =
|
| new android.hardware.Camera.ErrorCallback() {
|
| @@ -98,6 +113,14 @@ public class VideoCapturerAndroid implements
|
| }
|
| };
|
|
|
| + private void setCameraState(CameraState newState) {
|
| + Logging.d(TAG, "Camera changing state from " + cameraState + " to " + newState);
|
| + synchronized (cameraStateLock) {
|
| + cameraState = newState;
|
| + cameraStateLock.notifyAll();
|
| + }
|
| + }
|
| +
|
| public static VideoCapturerAndroid create(String name,
|
| CameraEventsHandler eventsHandler) {
|
| return VideoCapturerAndroid.create(name, eventsHandler, false /* captureToTexture */);
|
| @@ -134,14 +157,15 @@ public class VideoCapturerAndroid implements
|
| // Switch camera to the next valid camera id. This can only be called while
|
| // the camera is running.
|
| @Override
|
| - public void switchCamera(final CameraSwitchHandler switchEventsHandler) {
|
| + public synchronized void switchCamera(final CameraSwitchHandler switchEventsHandler) {
|
| if (android.hardware.Camera.getNumberOfCameras() < 2) {
|
| if (switchEventsHandler != null) {
|
| switchEventsHandler.onCameraSwitchError("No camera to switch to.");
|
| }
|
| return;
|
| }
|
| - synchronized (pendingCameraSwitchLock) {
|
| +
|
| + synchronized (cameraSwitchLock) {
|
| if (pendingCameraSwitch) {
|
| // Do not handle multiple camera switch request to avoid blocking
|
| // camera thread by handling too many switch request from a queue.
|
| @@ -151,24 +175,24 @@ public class VideoCapturerAndroid implements
|
| }
|
| return;
|
| }
|
| +
|
| pendingCameraSwitch = true;
|
| }
|
| - final boolean didPost = maybePostOnCameraThread(new Runnable() {
|
| +
|
| + postOnCameraThread(new Runnable() {
|
| @Override
|
| public void run() {
|
| + // Set switchEventsHandler only here because now openCameraOnCameraThread cannot be called
|
| + // in-between these calls.
|
| + synchronized (cameraSwitchLock) {
|
| + VideoCapturerAndroid.this.switchEventsHandler = switchEventsHandler;
|
| + }
|
| switchCameraOnCameraThread();
|
| - synchronized (pendingCameraSwitchLock) {
|
| + synchronized (cameraSwitchLock) {
|
| pendingCameraSwitch = false;
|
| }
|
| - if (switchEventsHandler != null) {
|
| - switchEventsHandler.onCameraSwitchDone(
|
| - info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
|
| - }
|
| }
|
| });
|
| - if (!didPost && switchEventsHandler != null) {
|
| - switchEventsHandler.onCameraSwitchError("Camera is stopped.");
|
| - }
|
| }
|
|
|
| // Requests a new output format from the video capturer. Captured frames
|
| @@ -177,8 +201,9 @@ public class VideoCapturerAndroid implements
|
| // the same result as |width| = 480, |height| = 640.
|
| // TODO(magjed/perkj): Document what this function does. Change name?
|
| @Override
|
| - public void onOutputFormatRequest(final int width, final int height, final int framerate) {
|
| - maybePostOnCameraThread(new Runnable() {
|
| + public synchronized void onOutputFormatRequest(
|
| + final int width, final int height, final int framerate) {
|
| + postOnCameraThread(new Runnable() {
|
| @Override public void run() {
|
| onOutputFormatRequestOnCameraThread(width, height, framerate);
|
| }
|
| @@ -188,25 +213,24 @@ public class VideoCapturerAndroid implements
|
| // Reconfigure the camera to capture in a new format. This should only be called while the camera
|
| // is running.
|
| @Override
|
| - public void changeCaptureFormat(final int width, final int height, final int framerate) {
|
| - maybePostOnCameraThread(new Runnable() {
|
| + public synchronized void changeCaptureFormat(
|
| + final int width, final int height, final int framerate) {
|
| + postOnCameraThread(new Runnable() {
|
| @Override public void run() {
|
| - startPreviewOnCameraThread(width, height, framerate);
|
| + requestedWidth = width;
|
| + requestedHeight = height;
|
| + requestedFramerate = framerate;
|
| +
|
| + startPreviewOnCameraThread();
|
| }
|
| });
|
| }
|
|
|
| - // Helper function to retrieve the current camera id synchronously. Note that the camera id might
|
| - // change at any point by switchCamera() calls.
|
| - private int getCurrentCameraId() {
|
| - synchronized (cameraIdLock) {
|
| - return id;
|
| - }
|
| - }
|
| -
|
| @Override
|
| public List<CaptureFormat> getSupportedFormats() {
|
| - return Camera1Enumerator.getSupportedFormats(getCurrentCameraId());
|
| + synchronized (cameraIdLock) {
|
| + return Camera1Enumerator.getSupportedFormats(id);
|
| + }
|
| }
|
|
|
| // Returns true if this VideoCapturer is setup to capture video frames to a SurfaceTexture.
|
| @@ -231,117 +255,127 @@ public class VideoCapturerAndroid implements
|
|
|
| private void checkIsOnCameraThread() {
|
| if (cameraThreadHandler == null) {
|
| - Logging.e(TAG, "Camera is not initialized - can't check thread.");
|
| + throw new IllegalStateException("Camera is not initialized - can't check thread.");
|
| } else if (Thread.currentThread() != cameraThreadHandler.getLooper().getThread()) {
|
| throw new IllegalStateException("Wrong thread");
|
| }
|
| }
|
|
|
| - private boolean maybePostOnCameraThread(Runnable runnable) {
|
| - return maybePostDelayedOnCameraThread(0 /* delayMs */, runnable);
|
| + /**
|
| + * Return true if camera is initialized otherwise returns false and logs an error.
|
| + */
|
| + private boolean checkIsInitialized() {
|
| + synchronized (cameraStateLock) {
|
| + if (cameraState == CameraState.UNINITIALIAZED) {
|
| + Logging.e(TAG, "Calling methods on an uninitialized camera.");
|
| + return false;
|
| + }
|
| + return true;
|
| + }
|
| + }
|
| +
|
| + private void postOnCameraThread(Runnable runnable) {
|
| + postDelayedOnCameraThread(0 /* delayMs */, runnable);
|
| }
|
|
|
| - private boolean maybePostDelayedOnCameraThread(int delayMs, Runnable runnable) {
|
| - return cameraThreadHandler != null && isCameraRunning.get()
|
| - && cameraThreadHandler.postAtTime(
|
| - runnable, this /* token */, SystemClock.uptimeMillis() + delayMs);
|
| + private void postDelayedOnCameraThread(int delayMs, Runnable runnable) {
|
| + if (!cameraThreadHandler.postAtTime(
|
| + runnable, this /* token */, SystemClock.uptimeMillis() + delayMs)) {
|
| + throw new RuntimeException("Failed to post a runnable on camera thread.");
|
| + }
|
| }
|
|
|
| @Override
|
| public void dispose() {
|
| Logging.d(TAG, "dispose");
|
| - }
|
| -
|
| - private boolean isInitialized() {
|
| - return applicationContext != null && frameObserver != null;
|
| + stopCapture(); // Stop the camera in case the caller forgot
|
| }
|
|
|
| @Override
|
| public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context applicationContext,
|
| - CapturerObserver frameObserver) {
|
| + CapturerObserver capturerObserver) {
|
| Logging.d(TAG, "initialize");
|
| if (applicationContext == null) {
|
| throw new IllegalArgumentException("applicationContext not set.");
|
| }
|
| - if (frameObserver == null) {
|
| - throw new IllegalArgumentException("frameObserver not set.");
|
| + if (capturerObserver == null) {
|
| + throw new IllegalArgumentException("capturerObserver not set.");
|
| }
|
| - if (isInitialized()) {
|
| + if (cameraState != CameraState.UNINITIALIAZED) {
|
| throw new IllegalStateException("Already initialized");
|
| }
|
| this.applicationContext = applicationContext;
|
| - this.frameObserver = frameObserver;
|
| + this.capturerObserver = capturerObserver;
|
| this.surfaceHelper = surfaceTextureHelper;
|
| this.cameraThreadHandler =
|
| surfaceTextureHelper == null ? null : surfaceTextureHelper.getHandler();
|
| +
|
| + // Nothing should be running on the camera thread so should be safe.
|
| + setCameraState(CameraState.IDLE);
|
| }
|
|
|
| // Note that this actually opens the camera, and Camera callbacks run on the
|
| // thread that calls open(), so this is done on the CameraThread.
|
| @Override
|
| - public void startCapture(final int width, final int height, final int framerate) {
|
| - Logging.d(TAG, "startCapture requested: " + width + "x" + height + "@" + framerate);
|
| - if (!isInitialized()) {
|
| - throw new IllegalStateException("startCapture called in uninitialized state");
|
| + public synchronized void startCapture(final int width, final int height, final int framerate) {
|
| + if (!checkIsInitialized()) {
|
| + return;
|
| }
|
| +
|
| + Logging.d(TAG, "startCapture requested: " + width + "x" + height + "@" + framerate);
|
| +
|
| if (surfaceHelper == null) {
|
| - frameObserver.onCapturerStarted(false /* success */);
|
| + capturerObserver.onCapturerStarted(false /* success */);
|
| if (eventsHandler != null) {
|
| eventsHandler.onCameraError("No SurfaceTexture created.");
|
| }
|
| return;
|
| }
|
| - if (isCameraRunning.getAndSet(true)) {
|
| - Logging.e(TAG, "Camera has already been started.");
|
| - return;
|
| - }
|
| - final boolean didPost = maybePostOnCameraThread(new Runnable() {
|
| +
|
| + postOnCameraThread(new Runnable() {
|
| @Override
|
| public void run() {
|
| - openCameraAttempts = 0;
|
| startCaptureOnCameraThread(width, height, framerate);
|
| }
|
| });
|
| - if (!didPost) {
|
| - frameObserver.onCapturerStarted(false);
|
| - if (eventsHandler != null) {
|
| - eventsHandler.onCameraError("Could not post task to camera thread.");
|
| - }
|
| - isCameraRunning.set(false);
|
| - }
|
| }
|
|
|
| private void startCaptureOnCameraThread(final int width, final int height, final int framerate) {
|
| checkIsOnCameraThread();
|
| - if (!isCameraRunning.get()) {
|
| - Logging.e(TAG, "startCaptureOnCameraThread: Camera is stopped");
|
| - return;
|
| - }
|
| - if (camera != null) {
|
| - Logging.e(TAG, "startCaptureOnCameraThread: Camera has already been started.");
|
| +
|
| + if (cameraState != CameraState.IDLE) {
|
| + Logging.e(TAG, "Camera has already been started.");
|
| return;
|
| }
|
| - this.firstFrameReported = false;
|
|
|
| + openCameraAttempts = 0;
|
| + firstFrameReported = false;
|
| + setCameraState(CameraState.STARTING);
|
| +
|
| + requestedWidth = width;
|
| + requestedHeight = height;
|
| + requestedFramerate = framerate;
|
| +
|
| + openCameraOnCameraThread();
|
| + }
|
| +
|
| + private void openCameraOnCameraThread() {
|
| try {
|
| + Logging.d(TAG, "Opening camera " + id);
|
| + if (eventsHandler != null) {
|
| + eventsHandler.onCameraOpening(id);
|
| + }
|
| +
|
| try {
|
| - synchronized (cameraIdLock) {
|
| - Logging.d(TAG, "Opening camera " + id);
|
| - if (eventsHandler != null) {
|
| - eventsHandler.onCameraOpening(id);
|
| - }
|
| - camera = android.hardware.Camera.open(id);
|
| - info = new android.hardware.Camera.CameraInfo();
|
| - android.hardware.Camera.getCameraInfo(id, info);
|
| - }
|
| + camera = android.hardware.Camera.open(id);
|
| } catch (RuntimeException e) {
|
| openCameraAttempts++;
|
| if (openCameraAttempts < MAX_OPEN_CAMERA_ATTEMPTS) {
|
| Logging.e(TAG, "Camera.open failed, retrying", e);
|
| - maybePostDelayedOnCameraThread(OPEN_CAMERA_DELAY_MS, new Runnable() {
|
| + postDelayedOnCameraThread(OPEN_CAMERA_DELAY_MS, new Runnable() {
|
| @Override
|
| public void run() {
|
| - startCaptureOnCameraThread(width, height, framerate);
|
| + openCameraOnCameraThread();
|
| }
|
| });
|
| return;
|
| @@ -349,43 +383,58 @@ public class VideoCapturerAndroid implements
|
| throw e;
|
| }
|
|
|
| + setCameraState(CameraState.RUNNING);
|
| +
|
| + info = new android.hardware.Camera.CameraInfo();
|
| + android.hardware.Camera.getCameraInfo(id, info);
|
| +
|
| camera.setPreviewTexture(surfaceHelper.getSurfaceTexture());
|
|
|
| Logging.d(TAG, "Camera orientation: " + info.orientation +
|
| " .Device orientation: " + getDeviceOrientation());
|
| camera.setErrorCallback(cameraErrorCallback);
|
| - startPreviewOnCameraThread(width, height, framerate);
|
| - frameObserver.onCapturerStarted(true);
|
| + startPreviewOnCameraThread();
|
| + capturerObserver.onCapturerStarted(true);
|
| + synchronized (cameraSwitchLock) {
|
| + if (switchEventsHandler != null) {
|
| + switchEventsHandler.onCameraSwitchDone(
|
| + info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
|
| + }
|
| + switchEventsHandler = null;
|
| + }
|
| if (isCapturingToTexture) {
|
| surfaceHelper.startListening(this);
|
| }
|
|
|
| - // Start camera observer.
|
| + // Start camera statistics collector.
|
| cameraStatistics = new CameraStatistics(surfaceHelper, eventsHandler);
|
| } catch (IOException|RuntimeException e) {
|
| Logging.e(TAG, "startCapture failed", e);
|
| // Make sure the camera is released.
|
| - stopCaptureOnCameraThread(true /* stopHandler */);
|
| - frameObserver.onCapturerStarted(false);
|
| + capturerObserver.onCapturerStarted(false);
|
| + setCameraState(CameraState.IDLE);
|
| + releaseCameraOnCameraThread();
|
| if (eventsHandler != null) {
|
| eventsHandler.onCameraError("Camera can not be started.");
|
| }
|
| - }
|
| + synchronized (cameraSwitchLock) {
|
| + if (switchEventsHandler != null) {
|
| + switchEventsHandler.onCameraSwitchError("Camera can not be started.");
|
| + }
|
| + switchEventsHandler = null;
|
| + }
|
| + }
|
| }
|
|
|
| // (Re)start preview with the closest supported format to |width| x |height| @ |framerate|.
|
| - private void startPreviewOnCameraThread(int width, int height, int framerate) {
|
| + private void startPreviewOnCameraThread() {
|
| checkIsOnCameraThread();
|
| - if (!isCameraRunning.get() || camera == null) {
|
| - Logging.e(TAG, "startPreviewOnCameraThread: Camera is stopped");
|
| + if (cameraState != CameraState.RUNNING) {
|
| + Logging.e(TAG, "startPreviewOnCameraThread: Camera is not running");
|
| return;
|
| }
|
| - Logging.d(
|
| - TAG, "startPreviewOnCameraThread requested: " + width + "x" + height + "@" + framerate);
|
| -
|
| - requestedWidth = width;
|
| - requestedHeight = height;
|
| - requestedFramerate = framerate;
|
| + Logging.d(TAG, "startPreviewOnCameraThread requested: "
|
| + + requestedWidth + "x" + requestedHeight + "@" + requestedFramerate);
|
|
|
| // Find closest supported format for |width| x |height| @ |framerate|.
|
| final android.hardware.Camera.Parameters parameters = camera.getParameters();
|
| @@ -394,10 +443,12 @@ public class VideoCapturerAndroid implements
|
| Logging.d(TAG, "Available fps ranges: " + supportedFramerates);
|
|
|
| final CaptureFormat.FramerateRange fpsRange =
|
| - CameraEnumerationAndroid.getClosestSupportedFramerateRange(supportedFramerates, framerate);
|
| + CameraEnumerationAndroid.getClosestSupportedFramerateRange(
|
| + supportedFramerates, requestedFramerate);
|
|
|
| final Size previewSize = CameraEnumerationAndroid.getClosestSupportedSize(
|
| - Camera1Enumerator.convertSizes(parameters.getSupportedPreviewSizes()), width, height);
|
| + Camera1Enumerator.convertSizes(parameters.getSupportedPreviewSizes()),
|
| + requestedWidth, requestedHeight);
|
|
|
| final CaptureFormat captureFormat =
|
| new CaptureFormat(previewSize.width, previewSize.height, fpsRange);
|
| @@ -426,7 +477,8 @@ public class VideoCapturerAndroid implements
|
| // Picture size is for taking pictures and not for preview/video, but we need to set it anyway
|
| // as a workaround for an aspect ratio problem on Nexus 7.
|
| final Size pictureSize = CameraEnumerationAndroid.getClosestSupportedSize(
|
| - Camera1Enumerator.convertSizes(parameters.getSupportedPictureSizes()), width, height);
|
| + Camera1Enumerator.convertSizes(parameters.getSupportedPictureSizes()),
|
| + requestedWidth, requestedHeight);
|
| parameters.setPictureSize(pictureSize.width, pictureSize.height);
|
|
|
| // Temporarily stop preview if it's already running.
|
| @@ -462,53 +514,90 @@ public class VideoCapturerAndroid implements
|
| camera.startPreview();
|
| }
|
|
|
| - // Blocks until camera is known to be stopped.
|
| + // Blocks until camera is known to be stopped. Synchronized to allow only on stop capture call at
|
| + // a time.
|
| @Override
|
| - public void stopCapture() throws InterruptedException {
|
| + public synchronized void stopCapture() {
|
| + if (!checkIsInitialized()) {
|
| + return;
|
| + }
|
| Logging.d(TAG, "stopCapture");
|
| - final CountDownLatch barrier = new CountDownLatch(1);
|
| - final boolean didPost = maybePostOnCameraThread(new Runnable() {
|
| +
|
| + // Count down latch to ensure runnable has been started and it is not some other callback
|
| + // stopping the camera.
|
| + final CountDownLatch countDownLatch = new CountDownLatch(1);
|
| +
|
| + postOnCameraThread(new Runnable() {
|
| @Override public void run() {
|
| - stopCaptureOnCameraThread(true /* stopHandler */);
|
| - barrier.countDown();
|
| + countDownLatch.countDown();
|
| + stopCaptureOnCameraThread();
|
| }
|
| });
|
| - if (!didPost) {
|
| - Logging.e(TAG, "Calling stopCapture() for already stopped camera.");
|
| - return;
|
| - }
|
| - if (!barrier.await(CAMERA_STOP_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
|
| - Logging.e(TAG, "Camera stop timeout");
|
| - printStackTrace();
|
| - if (eventsHandler != null) {
|
| - eventsHandler.onCameraError("Camera stop timeout");
|
| +
|
| + Logging.d(TAG, "stopCapture: Waiting for the stopping to start");
|
| + ThreadUtils.awaitUninterruptibly(countDownLatch);
|
| + Logging.d(TAG, "stopCapture: Waiting for the camera to stop");
|
| + synchronized (cameraStateLock) {
|
| + while (cameraState != CameraState.IDLE) {
|
| + try {
|
| + cameraStateLock.wait();
|
| + } catch (InterruptedException e) {
|
| + Logging.w(TAG, "Interrupt while waiting capturer to stop: "
|
| + + e.getMessage());
|
| + }
|
| }
|
| }
|
| - frameObserver.onCapturerStopped();
|
| +
|
| Logging.d(TAG, "stopCapture done");
|
| }
|
|
|
| - private void stopCaptureOnCameraThread(boolean stopHandler) {
|
| + private void stopCaptureOnCameraThread() {
|
| checkIsOnCameraThread();
|
| Logging.d(TAG, "stopCaptureOnCameraThread");
|
| - // Note that the camera might still not be started here if startCaptureOnCameraThread failed
|
| - // and we posted a retry.
|
|
|
| - // Make sure onTextureFrameAvailable() is not called anymore.
|
| - if (surfaceHelper != null) {
|
| - surfaceHelper.stopListening();
|
| - }
|
| - if (stopHandler) {
|
| - // Clear the cameraThreadHandler first, in case stopPreview or
|
| - // other driver code deadlocks. Deadlock in
|
| - // android.hardware.Camera._stopPreview(Native Method) has
|
| - // been observed on Nexus 5 (hammerhead), OS version LMY48I.
|
| - // The camera might post another one or two preview frames
|
| - // before stopped, so we have to check |isCameraRunning|.
|
| - // Remove all pending Runnables posted from |this|.
|
| - isCameraRunning.set(false);
|
| - cameraThreadHandler.removeCallbacksAndMessages(this /* token */);
|
| + if (cameraState == CameraState.IDLE) {
|
| + Logging.d(TAG, "Calling stopCapture() for already stopped camera.");
|
| + return;
|
| + }
|
| +
|
| + final CameraState oldState = cameraState;
|
| +
|
| + // Clear the cameraThreadHandler first, in case stopPreview or
|
| + // other driver code deadlocks. Deadlock in
|
| + // android.hardware.Camera._stopPreview(Native Method) has
|
| + // been observed on Nexus 5 (hammerhead), OS version LMY48I.
|
| + // The camera might post another one or two preview frames
|
| + // before stopped, so we have to check |isCameraRunning|.
|
| + // Remove all pending Runnables posted from |this|.
|
| + // Make sure no other callback starts the camera again after this
|
| + cameraThreadHandler.removeCallbacksAndMessages(this /* token */);
|
| + setCameraState(CameraState.IDLE); // No more frames will be delivered
|
| +
|
| + if (oldState == CameraState.STARTING) {
|
| + Logging.d(TAG, "Camera starting while calling stopCapture, canceling.");
|
| + capturerObserver.onCapturerStarted(false);
|
| + setCameraState(CameraState.IDLE);
|
| + return;
|
| }
|
| +
|
| + capturerObserver.onCapturerStopped();
|
| +
|
| + releaseCameraOnCameraThread();
|
| + if (eventsHandler != null) {
|
| + eventsHandler.onCameraClosed();
|
| + }
|
| +
|
| + Logging.d(TAG, "stopCaptureOnCameraThread done");
|
| + }
|
| +
|
| + // Note: stopPreview or other driver code might deadlock. Deadlock in
|
| + // android.hardware.Camera._stopPreview(Native Method) has
|
| + // been observed on Nexus 5 (hammerhead), OS version LMY48I.
|
| + private void releaseCameraOnCameraThread() {
|
| + checkIsOnCameraThread();
|
| +
|
| + // Make sure onTextureFrameAvailable() is not called anymore.
|
| + surfaceHelper.stopListening();
|
| if (cameraStatistics != null) {
|
| cameraStatistics.release();
|
| cameraStatistics = null;
|
| @@ -520,30 +609,32 @@ public class VideoCapturerAndroid implements
|
| }
|
| queuedBuffers.clear();
|
| captureFormat = null;
|
| -
|
| Logging.d(TAG, "Release camera.");
|
| if (camera != null) {
|
| camera.release();
|
| camera = null;
|
| }
|
| - if (eventsHandler != null) {
|
| - eventsHandler.onCameraClosed();
|
| - }
|
| - Logging.d(TAG, "stopCaptureOnCameraThread done");
|
| }
|
|
|
| private void switchCameraOnCameraThread() {
|
| checkIsOnCameraThread();
|
| - if (!isCameraRunning.get()) {
|
| + Logging.d(TAG, "switchCameraOnCameraThread");
|
| +
|
| + if (cameraState == CameraState.IDLE) {
|
| Logging.e(TAG, "switchCameraOnCameraThread: Camera is stopped");
|
| + if (switchEventsHandler != null) {
|
| + switchEventsHandler.onCameraSwitchError("Camera is stopped.");
|
| + }
|
| return;
|
| }
|
| - Logging.d(TAG, "switchCameraOnCameraThread");
|
| - stopCaptureOnCameraThread(false /* stopHandler */);
|
| +
|
| + stopCaptureOnCameraThread();
|
| synchronized (cameraIdLock) {
|
| id = (id + 1) % android.hardware.Camera.getNumberOfCameras();
|
| }
|
| - startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramerate);
|
| + startCaptureOnCameraThread(requestedWidth, requestedHeight,
|
| + requestedFramerate);
|
| +
|
| Logging.d(TAG, "switchCameraOnCameraThread done");
|
| }
|
|
|
| @@ -551,7 +642,7 @@ public class VideoCapturerAndroid implements
|
| checkIsOnCameraThread();
|
| Logging.d(TAG, "onOutputFormatRequestOnCameraThread: " + width + "x" + height +
|
| "@" + framerate);
|
| - frameObserver.onOutputFormatRequest(width, height, framerate);
|
| + capturerObserver.onOutputFormatRequest(width, height, framerate);
|
| }
|
|
|
| private int getDeviceOrientation() {
|
| @@ -588,11 +679,12 @@ public class VideoCapturerAndroid implements
|
| // Called on cameraThread so must not "synchronized".
|
| @Override
|
| public void onPreviewFrame(byte[] data, android.hardware.Camera callbackCamera) {
|
| - checkIsOnCameraThread();
|
| - if (!isCameraRunning.get()) {
|
| - Logging.e(TAG, "onPreviewFrame: Camera is stopped");
|
| + if (cameraState != CameraState.RUNNING) {
|
| + Logging.d(TAG, "onPreviewFrame: Camera is stopped");
|
| return;
|
| }
|
| + checkIsOnCameraThread();
|
| +
|
| if (!queuedBuffers.contains(data)) {
|
| // |data| is an old invalid buffer.
|
| return;
|
| @@ -610,7 +702,7 @@ public class VideoCapturerAndroid implements
|
| }
|
|
|
| cameraStatistics.addFrame();
|
| - frameObserver.onByteBufferFrameCaptured(data, captureFormat.width, captureFormat.height,
|
| + capturerObserver.onByteBufferFrameCaptured(data, captureFormat.width, captureFormat.height,
|
| getFrameOrientation(), captureTimeNs);
|
| camera.addCallbackBuffer(data);
|
| }
|
| @@ -618,12 +710,13 @@ public class VideoCapturerAndroid implements
|
| @Override
|
| public void onTextureFrameAvailable(
|
| int oesTextureId, float[] transformMatrix, long timestampNs) {
|
| - checkIsOnCameraThread();
|
| - if (!isCameraRunning.get()) {
|
| - Logging.e(TAG, "onTextureFrameAvailable: Camera is stopped");
|
| + if (cameraState != CameraState.RUNNING) {
|
| + Logging.d(TAG, "onTextureFrameAvailable: Camera is stopped");
|
| surfaceHelper.returnTextureFrame();
|
| return;
|
| }
|
| + checkIsOnCameraThread();
|
| +
|
| if (eventsHandler != null && !firstFrameReported) {
|
| eventsHandler.onFirstFrameAvailable();
|
| firstFrameReported = true;
|
| @@ -637,7 +730,7 @@ public class VideoCapturerAndroid implements
|
| RendererCommon.multiplyMatrices(transformMatrix, RendererCommon.horizontalFlipMatrix());
|
| }
|
| cameraStatistics.addFrame();
|
| - frameObserver.onTextureFrameCaptured(captureFormat.width, captureFormat.height, oesTextureId,
|
| + capturerObserver.onTextureFrameCaptured(captureFormat.width, captureFormat.height, oesTextureId,
|
| transformMatrix, rotation, timestampNs);
|
| }
|
| }
|
|
|