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); |
} |
} |