Chromium Code Reviews| 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..7103e752f6acceefbb02bd88d384000af11e39c3 100644 |
| --- a/webrtc/api/android/java/src/org/webrtc/VideoCapturerAndroid.java |
| +++ b/webrtc/api/android/java/src/org/webrtc/VideoCapturerAndroid.java |
| @@ -46,40 +46,54 @@ 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 AtomicBoolean generateCapturerEvents = new AtomicBoolean(); |
| + 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 touch from the camera thread |
| + // ---------------------------------------------------------- |
| + private android.hardware.Camera camera; // Only non-null while capturing. |
| + private CameraState cameraState; |
| // 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 objected |
|
magjed_webrtc
2016/07/20 14:56:08
objects
sakal
2016/07/21 11:17:24
Done.
|
| + // --------------- |
| + private final Object cameraIdLock = new Object(); |
| + private int id; // Only edited from camera thread while holding the lock. |
| + |
| + // 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 +112,10 @@ public class VideoCapturerAndroid implements |
| } |
| }; |
| + private void setCameraState(CameraState newState) { |
| + 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.
|
| + } |
| + |
| public static VideoCapturerAndroid create(String name, |
| CameraEventsHandler eventsHandler) { |
| return VideoCapturerAndroid.create(name, eventsHandler, false /* captureToTexture */); |
| @@ -141,7 +159,8 @@ public class VideoCapturerAndroid implements |
| } |
| 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 +170,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 |
| @@ -178,7 +197,7 @@ public class VideoCapturerAndroid implements |
| // 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() { |
| + postOnCameraThread(new Runnable() { |
| @Override public void run() { |
| onOutputFormatRequestOnCameraThread(width, height, framerate); |
| } |
| @@ -189,24 +208,22 @@ public class VideoCapturerAndroid implements |
| // is running. |
| @Override |
| public void changeCaptureFormat(final int width, final int height, final int framerate) { |
| - maybePostOnCameraThread(new Runnable() { |
| + 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. |
| @@ -216,6 +233,8 @@ public class VideoCapturerAndroid implements |
| public VideoCapturerAndroid(String cameraName, CameraEventsHandler eventsHandler, |
| boolean captureToTexture) { |
| + 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.
|
| + |
| if (android.hardware.Camera.getNumberOfCameras() == 0) { |
| throw new RuntimeException("No cameras available"); |
| } |
| @@ -231,49 +250,54 @@ 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); |
| + 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) { |
| + 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.
|
| + runnable, this /* token */, SystemClock.uptimeMillis() + delayMs); |
| } |
| @Override |
| public void dispose() { |
| Logging.d(TAG, "dispose"); |
| + stopCapture(); |
| + // 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.
|
| + cameraState = CameraState.UNINITIALIAZED; |
| } |
| private boolean isInitialized() { |
| - return applicationContext != null && frameObserver != null; |
| + 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.
|
| } |
| @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()) { |
| 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 |
| + 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.
|
| } |
| // Note that this actually opens the camera, and Camera callbacks run on the |
| @@ -281,67 +305,60 @@ public class VideoCapturerAndroid implements |
| @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"); |
| - } |
| + |
| + generateCapturerEvents.set(true); |
| 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."); |
| + |
| + openCameraAttempts = 0; |
| + 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.
|
| + if (cameraState != CameraState.IDLE) { |
| + Logging.e(TAG, "Camera has already been started."); |
| return; |
| } |
| - this.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 +366,52 @@ 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); |
| 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); |
| + releaseCameraOnCameraThread(); |
| + capturerObserver.onCapturerStarted(false); |
| if (eventsHandler != null) { |
| eventsHandler.onCameraError("Camera can not be started."); |
| } |
| - } |
| + } |
| + |
| + 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.
|
| + if (switchEventsHandler != null) { |
| + switchEventsHandler.onCameraSwitchDone( |
| + info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT); |
| + } |
| + 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 +420,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 +454,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. |
| @@ -464,51 +493,75 @@ public class VideoCapturerAndroid implements |
| // Blocks until camera is known to be stopped. |
| @Override |
| - public void stopCapture() throws InterruptedException { |
| + public void stopCapture() { |
| Logging.d(TAG, "stopCapture"); |
| - final CountDownLatch barrier = new CountDownLatch(1); |
| - final boolean didPost = maybePostOnCameraThread(new Runnable() { |
| + |
| + postOnCameraThread(new Runnable() { |
| @Override public void run() { |
| - stopCaptureOnCameraThread(true /* stopHandler */); |
| - barrier.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"); |
| + |
| + synchronized (generateCapturerEvents) { |
| + while (generateCapturerEvents.get()) { |
| + try { |
| + generateCapturerEvents.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."); |
|
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.
|
| + 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|. |
| + cameraThreadHandler.removeCallbacksAndMessages(this /* token */); |
| + setCameraState(CameraState.IDLE); // No more frames will be delivered |
| + synchronized (generateCapturerEvents) { |
| + generateCapturerEvents.set(false); |
| + generateCapturerEvents.notifyAll(); |
| + } |
| + |
| + if (oldState == CameraState.STARTING) { |
| + Logging.d(TAG, "Camera starting while calling stopCapture, cancelling."); |
| + setCameraState(CameraState.IDLE); |
| + return; |
| + } |
| + |
| + 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.
|
| + |
| + releaseCameraOnCameraThread(); |
| + if (eventsHandler != null) { |
| + eventsHandler.onCameraClosed(); |
| + } |
| + |
| + Logging.d(TAG, "stopCaptureOnCameraThread done"); |
| + } |
| + |
| + private void releaseCameraOnCameraThread() { |
| + checkIsOnCameraThread(); |
| + |
| + // Make sure onTextureFrameAvailable() is not called anymore. |
| + surfaceHelper.stopListening(); |
| if (cameraStatistics != null) { |
| cameraStatistics.release(); |
| cameraStatistics = null; |
| @@ -520,30 +573,30 @@ 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) { |
|
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
|
| Logging.e(TAG, "switchCameraOnCameraThread: Camera is stopped"); |
| + 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 +604,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() { |
| @@ -589,8 +642,9 @@ public class VideoCapturerAndroid implements |
| @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; |
| } |
| if (!queuedBuffers.contains(data)) { |
| @@ -610,7 +664,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); |
| } |
| @@ -619,8 +673,9 @@ public class VideoCapturerAndroid implements |
| 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; |
| } |
| @@ -637,7 +692,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); |
| } |
| } |