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

Unified Diff: webrtc/api/java/android/org/webrtc/VideoCapturerAndroid.java

Issue 1763673002: VideoCapturerAndroid: Use one thread per startCapture()/stopCapture() session (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: Rebase Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: webrtc/api/java/android/org/webrtc/VideoCapturerAndroid.java
diff --git a/webrtc/api/java/android/org/webrtc/VideoCapturerAndroid.java b/webrtc/api/java/android/org/webrtc/VideoCapturerAndroid.java
index 5c9b77b4cccfd0175dd15816ffff2fb827e5de58..659a48440a7532a11b214980fabafc848d11f181 100644
--- a/webrtc/api/java/android/org/webrtc/VideoCapturerAndroid.java
+++ b/webrtc/api/java/android/org/webrtc/VideoCapturerAndroid.java
@@ -51,9 +51,13 @@ public class VideoCapturerAndroid implements
private final static int CAMERA_OBSERVER_PERIOD_MS = 2000;
private final static int CAMERA_FREEZE_REPORT_TIMOUT_MS = 4000;
+ private boolean isDisposed = false;
private android.hardware.Camera camera; // Only non-null while capturing.
- private Thread cameraThread;
- private final Handler cameraThreadHandler;
+ private final Object handlerLock = new Object();
+ // |cameraThreadHandler| must be synchronized on |handlerLock| when not on the camera thread,
+ // or when modifying the reference. Use maybePostOnCameraThread() instead of posting directly to
+ // the handler - this way all callbacks with a specifed token can be removed at once.
+ private Handler cameraThreadHandler;
private Context applicationContext;
// Synchronization lock for |id|.
private final Object cameraIdLock = new Object();
@@ -81,9 +85,6 @@ public class VideoCapturerAndroid implements
// The camera API can output one old frame after the camera has been switched or the resolution
// has been changed. This flag is used for dropping the first frame after camera restart.
private boolean dropNextFrame = false;
- // |openCameraOnCodecThreadRunner| is used for retrying to open the camera if it is in use by
- // another application when startCaptureOnCameraThread is called.
- private Runnable openCameraOnCodecThreadRunner;
private final static int MAX_OPEN_CAMERA_ATTEMPTS = 3;
private final static int OPEN_CAMERA_DELAY_MS = 500;
private int openCameraAttempts;
@@ -132,7 +133,7 @@ public class VideoCapturerAndroid implements
} else {
freezePeriodCount = 0;
}
- cameraThreadHandler.postDelayed(this, CAMERA_OBSERVER_PERIOD_MS);
+ maybePostDelayedOnCameraThread(CAMERA_OBSERVER_PERIOD_MS, this);
}
};
@@ -199,6 +200,12 @@ public class VideoCapturerAndroid implements
}
public void printStackTrace() {
+ Thread cameraThread = null;
+ synchronized (handlerLock) {
+ if (cameraThreadHandler != null) {
+ cameraThread = cameraThreadHandler.getLooper().getThread();
+ }
+ }
if (cameraThread != null) {
StackTraceElement[] cameraStackTraces = cameraThread.getStackTrace();
if (cameraStackTraces.length > 0) {
@@ -212,10 +219,10 @@ public class VideoCapturerAndroid implements
// Switch camera to the next valid camera id. This can only be called while
// the camera is running.
- public void switchCamera(final CameraSwitchHandler handler) {
+ public void switchCamera(final CameraSwitchHandler switchEventsHandler) {
if (android.hardware.Camera.getNumberOfCameras() < 2) {
- if (handler != null) {
- handler.onCameraSwitchError("No camera to switch to.");
+ if (switchEventsHandler != null) {
+ switchEventsHandler.onCameraSwitchError("No camera to switch to.");
}
return;
}
@@ -224,31 +231,29 @@ public class VideoCapturerAndroid implements
// Do not handle multiple camera switch request to avoid blocking
// camera thread by handling too many switch request from a queue.
Logging.w(TAG, "Ignoring camera switch request.");
- if (handler != null) {
- handler.onCameraSwitchError("Pending camera switch already in progress.");
+ if (switchEventsHandler != null) {
+ switchEventsHandler.onCameraSwitchError("Pending camera switch already in progress.");
}
return;
}
pendingCameraSwitch = true;
}
- cameraThreadHandler.post(new Runnable() {
- @Override public void run() {
- if (camera == null) {
- if (handler != null) {
- handler.onCameraSwitchError("Camera is stopped.");
- }
- return;
- }
+ final boolean didPost = maybePostOnCameraThread(new Runnable() {
+ @Override
+ public void run() {
switchCameraOnCameraThread();
synchronized (pendingCameraSwitchLock) {
pendingCameraSwitch = false;
}
- if (handler != null) {
- handler.onCameraSwitchDone(
+ 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
@@ -257,7 +262,7 @@ public class VideoCapturerAndroid implements
// the same result as |width| = 480, |height| = 640.
// TODO(magjed/perkj): Document what this function does. Change name?
public void onOutputFormatRequest(final int width, final int height, final int framerate) {
- cameraThreadHandler.post(new Runnable() {
+ maybePostOnCameraThread(new Runnable() {
@Override public void run() {
onOutputFormatRequestOnCameraThread(width, height, framerate);
}
@@ -267,7 +272,7 @@ public class VideoCapturerAndroid implements
// Reconfigure the camera to capture in a new format. This should only be called while the camera
// is running.
public void changeCaptureFormat(final int width, final int height, final int framerate) {
- cameraThreadHandler.post(new Runnable() {
+ maybePostOnCameraThread(new Runnable() {
@Override public void run() {
startPreviewOnCameraThread(width, height, framerate);
}
@@ -304,13 +309,11 @@ public class VideoCapturerAndroid implements
isCapturingToTexture = (sharedContext != null);
cameraStatistics = new CameraStatistics();
surfaceHelper = SurfaceTextureHelper.create(sharedContext);
- cameraThreadHandler = surfaceHelper.getHandler();
- cameraThread = cameraThreadHandler.getLooper().getThread();
Logging.d(TAG, "VideoCapturerAndroid isCapturingToTexture : " + isCapturingToTexture);
}
private void checkIsOnCameraThread() {
- if (Thread.currentThread() != cameraThread) {
+ if (Thread.currentThread() != cameraThreadHandler.getLooper().getThread()) {
throw new IllegalStateException("Wrong thread");
}
}
@@ -333,29 +336,38 @@ public class VideoCapturerAndroid implements
return -1;
}
- // Quits the camera thread. This needs to be done manually, otherwise the thread and handler will
- // not be garbage collected.
+ private boolean maybePostOnCameraThread(Runnable runnable) {
+ return maybePostDelayedOnCameraThread(0 /* delayMs */, runnable);
+ }
+
+ private boolean maybePostDelayedOnCameraThread(int delayMs, Runnable runnable) {
+ synchronized (handlerLock) {
+ return cameraThreadHandler != null
+ && cameraThreadHandler.postAtTime(
+ runnable, this /* token */, SystemClock.uptimeMillis() + delayMs);
+ }
+ }
+
+ // Dispose the SurfaceTextureHelper. This needs to be done manually, otherwise the
+ // SurfaceTextureHelper thread and resources will not be garbage collected.
@Override
public void dispose() {
Logging.d(TAG, "release");
if (isDisposed()) {
throw new IllegalStateException("Already released");
}
- ThreadUtils.invokeUninterruptibly(cameraThreadHandler, new Runnable() {
- @Override
- public void run() {
- if (camera != null) {
- throw new IllegalStateException("Release called while camera is running");
- }
+ synchronized (handlerLock) {
+ if (cameraThreadHandler != null) {
+ throw new IllegalStateException("dispose() called while camera is running");
}
- });
+ }
surfaceHelper.dispose();
- cameraThread = null;
+ isDisposed = true;
}
// Used for testing purposes to check if dispose() has been called.
public boolean isDisposed() {
- return (cameraThread == null);
+ return isDisposed;
}
// Note that this actually opens the camera, and Camera callbacks run on the
@@ -364,21 +376,33 @@ public class VideoCapturerAndroid implements
public void startCapture(
final int width, final int height, final int framerate,
final Context applicationContext, final CapturerObserver frameObserver) {
- Logging.d(TAG, "startCapture requested: " + width + "x" + height
- + "@" + framerate);
+ Logging.d(TAG, "startCapture requested: " + width + "x" + height + "@" + framerate);
if (applicationContext == null) {
- throw new RuntimeException("applicationContext not set.");
+ throw new IllegalArgumentException("applicationContext not set.");
}
if (frameObserver == null) {
- throw new RuntimeException("frameObserver not set.");
+ throw new IllegalArgumentException("frameObserver not set.");
}
-
- cameraThreadHandler.post(new Runnable() {
- @Override public void run() {
- startCaptureOnCameraThread(width, height, framerate, frameObserver,
- applicationContext);
+ synchronized (handlerLock) {
+ if (this.cameraThreadHandler != null) {
+ throw new RuntimeException("Camera has already been started.");
}
- });
+ this.cameraThreadHandler = surfaceHelper.getHandler();
+ final boolean didPost = maybePostOnCameraThread(new Runnable() {
+ @Override
+ public void run() {
+ openCameraAttempts = 0;
+ startCaptureOnCameraThread(width, height, framerate, frameObserver,
+ applicationContext);
+ }
+ });
+ if (!didPost) {
+ frameObserver.onCapturerStarted(false);
+ if (eventsHandler != null) {
+ eventsHandler.onCameraError("Could not post task to camera thread.");
+ }
+ }
+ }
}
private void startCaptureOnCameraThread(
@@ -408,16 +432,14 @@ public class VideoCapturerAndroid implements
openCameraAttempts++;
if (openCameraAttempts < MAX_OPEN_CAMERA_ATTEMPTS) {
Logging.e(TAG, "Camera.open failed, retrying", e);
- openCameraOnCodecThreadRunner = new Runnable() {
+ maybePostDelayedOnCameraThread(OPEN_CAMERA_DELAY_MS, new Runnable() {
@Override public void run() {
startCaptureOnCameraThread(width, height, framerate, frameObserver,
applicationContext);
}
- };
- cameraThreadHandler.postDelayed(openCameraOnCodecThreadRunner, OPEN_CAMERA_DELAY_MS);
+ });
return;
}
- openCameraAttempts = 0;
throw e;
}
@@ -438,13 +460,21 @@ public class VideoCapturerAndroid implements
}
// Start camera observer.
- cameraThreadHandler.postDelayed(cameraObserver, CAMERA_OBSERVER_PERIOD_MS);
+ maybePostDelayedOnCameraThread(CAMERA_OBSERVER_PERIOD_MS, cameraObserver);
return;
} catch (RuntimeException e) {
error = e;
}
Logging.e(TAG, "startCapture failed", error);
- stopCaptureOnCameraThread();
+ if (camera != null) {
+ // Make sure the camera is released.
+ stopCaptureOnCameraThread();
+ }
+ synchronized (handlerLock) {
+ // Remove all pending Runnables posted from |this|.
+ cameraThreadHandler.removeCallbacksAndMessages(this /* token */);
+ cameraThreadHandler = null;
+ }
frameObserver.onCapturerStarted(false);
if (eventsHandler != null) {
eventsHandler.onCameraError("Camera can not be started.");
@@ -542,12 +572,21 @@ public class VideoCapturerAndroid implements
public void stopCapture() throws InterruptedException {
Logging.d(TAG, "stopCapture");
final CountDownLatch barrier = new CountDownLatch(1);
- cameraThreadHandler.post(new Runnable() {
- @Override public void run() {
- stopCaptureOnCameraThread();
- barrier.countDown();
+ final boolean didPost = maybePostOnCameraThread(new Runnable() {
+ @Override public void run() {
+ stopCaptureOnCameraThread();
+ synchronized (handlerLock) {
+ // Remove all pending Runnables posted from |this|.
+ cameraThreadHandler.removeCallbacksAndMessages(this /* token */);
+ cameraThreadHandler = null;
}
+ barrier.countDown();
+ }
});
+ if (!didPost) {
+ Logging.e(TAG, "Calling stopCapture() for already stopped camera.");
+ return;
+ }
barrier.await();
Logging.d(TAG, "stopCapture done");
}
@@ -555,18 +594,9 @@ public class VideoCapturerAndroid implements
private void stopCaptureOnCameraThread() {
checkIsOnCameraThread();
Logging.d(TAG, "stopCaptureOnCameraThread");
- if (openCameraOnCodecThreadRunner != null) {
- cameraThreadHandler.removeCallbacks(openCameraOnCodecThreadRunner);
- }
- openCameraAttempts = 0;
- if (camera == null) {
- Logging.e(TAG, "Calling stopCapture() for already stopped camera.");
- return;
- }
// Make sure onTextureFrameAvailable() is not called anymore.
surfaceHelper.stopListening();
- cameraThreadHandler.removeCallbacks(cameraObserver);
cameraStatistics.getAndResetFrameCount();
Logging.d(TAG, "Stop preview.");
camera.stopPreview();
@@ -645,9 +675,13 @@ public class VideoCapturerAndroid implements
// Called on cameraThread so must not "synchronized".
@Override
public void onPreviewFrame(byte[] data, android.hardware.Camera callbackCamera) {
+ if (cameraThreadHandler == null) {
+ // The camera has been stopped.
+ return;
+ }
checkIsOnCameraThread();
- if (camera == null || !queuedBuffers.contains(data)) {
- // The camera has been stopped or |data| is an old invalid buffer.
+ if (!queuedBuffers.contains(data)) {
+ // |data| is an old invalid buffer.
return;
}
if (camera != callbackCamera) {
@@ -671,7 +705,7 @@ public class VideoCapturerAndroid implements
@Override
public void onTextureFrameAvailable(
int oesTextureId, float[] transformMatrix, long timestampNs) {
- if (camera == null) {
+ if (cameraThreadHandler == null) {
throw new RuntimeException("onTextureFrameAvailable() called after stopCapture().");
}
checkIsOnCameraThread();
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698