| Index: talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java
|
| diff --git a/talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java b/talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java
|
| index eff539044ca022c57bfbe085cce1376aa159a547..ee01eede9e6e87f63929b4956143fae2b02f79fd 100644
|
| --- a/talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java
|
| +++ b/talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java
|
| @@ -34,7 +34,7 @@ import android.hardware.Camera.PreviewCallback;
|
| import android.opengl.GLES11Ext;
|
| import android.opengl.GLES20;
|
| import android.os.Handler;
|
| -import android.os.Looper;
|
| +import android.os.HandlerThread;
|
| import android.os.SystemClock;
|
| import android.view.Surface;
|
| import android.view.WindowManager;
|
| @@ -50,7 +50,7 @@ import java.util.HashMap;
|
| import java.util.IdentityHashMap;
|
| import java.util.List;
|
| import java.util.Map;
|
| -import java.util.concurrent.Exchanger;
|
| +import java.util.concurrent.CountDownLatch;
|
| import java.util.concurrent.TimeUnit;
|
|
|
| // Android specific implementation of VideoCapturer.
|
| @@ -60,29 +60,27 @@ import java.util.concurrent.TimeUnit;
|
| // front and back camera. It also provides methods for enumerating valid device
|
| // names.
|
| //
|
| -// Threading notes: this class is called from C++ code, and from Camera
|
| -// Java callbacks. Since these calls happen on different threads,
|
| -// the entry points to this class are all synchronized. This shouldn't present
|
| -// a performance bottleneck because only onPreviewFrame() is called more than
|
| -// once (and is called serially on a single thread), so the lock should be
|
| -// uncontended. Note that each of these synchronized methods must check
|
| -// |camera| for null to account for having possibly waited for stopCapture() to
|
| -// complete.
|
| +// Threading notes: this class is called from C++ code, Android Camera callbacks, and possibly
|
| +// arbitrary Java threads. All public entry points are thread safe, and delegate the work to the
|
| +// camera thread. The internal *OnCameraThread() methods must check |camera| for null to check if
|
| +// the camera has been stopped.
|
| @SuppressWarnings("deprecation")
|
| public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallback {
|
| private final static String TAG = "VideoCapturerAndroid";
|
| private final static int CAMERA_OBSERVER_PERIOD_MS = 5000;
|
|
|
| private Camera camera; // Only non-null while capturing.
|
| - private CameraThread cameraThread;
|
| - private Handler cameraThreadHandler;
|
| + private HandlerThread cameraThread;
|
| + private final Handler cameraThreadHandler;
|
| // |cameraSurfaceTexture| is used with setPreviewTexture. Must be a member, see issue webrtc:5021.
|
| private SurfaceTexture cameraSurfaceTexture;
|
| private Context applicationContext;
|
| + // Synchronization lock for |id|.
|
| + private final Object cameraIdLock = new Object();
|
| private int id;
|
| private Camera.CameraInfo info;
|
| private int cameraGlTexture = 0;
|
| - private final FramePool videoBuffers = new FramePool();
|
| + private final FramePool videoBuffers;
|
| // Remember the requested format in case we want to switch cameras.
|
| private int requestedWidth;
|
| private int requestedHeight;
|
| @@ -91,6 +89,7 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| private CaptureFormat captureFormat;
|
| private int cameraFramesCount;
|
| private int captureBuffersCount;
|
| + private final Object pendingCameraSwitchLock = new Object();
|
| private volatile boolean pendingCameraSwitch;
|
| private CapturerObserver frameObserver = null;
|
| private CameraErrorHandler errorHandler = null;
|
| @@ -136,9 +135,7 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| } else {
|
| cameraFramesCount = 0;
|
| captureBuffersCount = 0;
|
| - if (cameraThreadHandler != null) {
|
| - cameraThreadHandler.postDelayed(this, CAMERA_OBSERVER_PERIOD_MS);
|
| - }
|
| + cameraThreadHandler.postDelayed(this, CAMERA_OBSERVER_PERIOD_MS);
|
| }
|
| }
|
| };
|
| @@ -149,6 +146,15 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| public void onCameraError(String errorDescription);
|
| }
|
|
|
| + // Camera switch handler - one of these functions are invoked with the result of switchCamera().
|
| + // The callback may be called on an arbitrary thread.
|
| + public interface CameraSwitchHandler {
|
| + // Invoked on success. |isFrontCamera| is true if the new camera is front facing.
|
| + void onCameraSwitchDone(boolean isFrontCamera);
|
| + // Invoked on failure, e.g. camera is stopped or only one camera available.
|
| + void onCameraSwitchError(String errorDescription);
|
| + }
|
| +
|
| public static VideoCapturerAndroid create(String name,
|
| CameraErrorHandler errorHandler) {
|
| VideoCapturer capturer = VideoCapturer.create(name);
|
| @@ -162,41 +168,48 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
|
|
| // Switch camera to the next valid camera id. This can only be called while
|
| // the camera is running.
|
| - // Returns true on success. False if the next camera does not support the
|
| - // current resolution.
|
| - public synchronized boolean switchCamera(final Runnable switchDoneEvent) {
|
| - if (Camera.getNumberOfCameras() < 2 )
|
| - return false;
|
| -
|
| - if (cameraThreadHandler == null) {
|
| - Logging.e(TAG, "Calling switchCamera() for stopped camera.");
|
| - return false;
|
| + public void switchCamera(final CameraSwitchHandler handler) {
|
| + if (Camera.getNumberOfCameras() < 2) {
|
| + if (handler != null) {
|
| + handler.onCameraSwitchError("No camera to switch to.");
|
| + }
|
| + return;
|
| }
|
| - if (pendingCameraSwitch) {
|
| - // 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.");
|
| - return false;
|
| + synchronized (pendingCameraSwitchLock) {
|
| + if (pendingCameraSwitch) {
|
| + // 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.");
|
| + }
|
| + return;
|
| + }
|
| + pendingCameraSwitch = true;
|
| }
|
| -
|
| - pendingCameraSwitch = true;
|
| - id = (id + 1) % Camera.getNumberOfCameras();
|
| cameraThreadHandler.post(new Runnable() {
|
| @Override public void run() {
|
| - switchCameraOnCameraThread(switchDoneEvent);
|
| + if (camera == null) {
|
| + if (handler != null) {
|
| + handler.onCameraSwitchError("Camera is stopped.");
|
| + }
|
| + return;
|
| + }
|
| + switchCameraOnCameraThread();
|
| + synchronized (pendingCameraSwitchLock) {
|
| + pendingCameraSwitch = false;
|
| + }
|
| + if (handler != null) {
|
| + handler.onCameraSwitchDone(info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT);
|
| + }
|
| }
|
| });
|
| - return true;
|
| }
|
|
|
| // Requests a new output format from the video capturer. Captured frames
|
| // by the camera will be scaled/or dropped by the video capturer.
|
| - public synchronized void onOutputFormatRequest(
|
| - final int width, final int height, final int fps) {
|
| - if (cameraThreadHandler == null) {
|
| - Logging.e(TAG, "Calling onOutputFormatRequest() for already stopped camera.");
|
| - return;
|
| - }
|
| + // TODO(magjed/perkj): Document what this function does. Change name?
|
| + public void onOutputFormatRequest(final int width, final int height, final int fps) {
|
| cameraThreadHandler.post(new Runnable() {
|
| @Override public void run() {
|
| onOutputFormatRequestOnCameraThread(width, height, fps);
|
| @@ -206,12 +219,7 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
|
|
| // Reconfigure the camera to capture in a new format. This should only be called while the camera
|
| // is running.
|
| - public synchronized void changeCaptureFormat(
|
| - final int width, final int height, final int framerate) {
|
| - if (cameraThreadHandler == null) {
|
| - Logging.e(TAG, "Calling changeCaptureFormat() for already stopped camera.");
|
| - return;
|
| - }
|
| + public void changeCaptureFormat(final int width, final int height, final int framerate) {
|
| cameraThreadHandler.post(new Runnable() {
|
| @Override public void run() {
|
| startPreviewOnCameraThread(width, height, framerate);
|
| @@ -219,66 +227,95 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| });
|
| }
|
|
|
| - public synchronized List<CaptureFormat> getSupportedFormats() {
|
| - return CameraEnumerationAndroid.getSupportedFormats(id);
|
| + // 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;
|
| + }
|
| + }
|
| +
|
| + public List<CaptureFormat> getSupportedFormats() {
|
| + return CameraEnumerationAndroid.getSupportedFormats(getCurrentCameraId());
|
| }
|
|
|
| - // Return a list of timestamps for the frames that have been sent out, but not returned yet.
|
| - // Useful for logging and testing.
|
| - public String pendingFramesTimeStamps() {
|
| - return videoBuffers.pendingFramesTimeStamps();
|
| + // Called from native code.
|
| + private String getSupportedFormatsAsJson() throws JSONException {
|
| + return CameraEnumerationAndroid.getSupportedFormatsAsJson(getCurrentCameraId());
|
| }
|
|
|
| private VideoCapturerAndroid() {
|
| Logging.d(TAG, "VideoCapturerAndroid");
|
| + cameraThread = new HandlerThread(TAG);
|
| + cameraThread.start();
|
| + cameraThreadHandler = new Handler(cameraThread.getLooper());
|
| + videoBuffers = new FramePool(cameraThread);
|
| + }
|
| +
|
| + private void checkIsOnCameraThread() {
|
| + if (Thread.currentThread() != cameraThread) {
|
| + throw new IllegalStateException("Wrong thread");
|
| + }
|
| }
|
|
|
| // Called by native code.
|
| // Initializes local variables for the camera named |deviceName|. If |deviceName| is empty, the
|
| // first available device is used in order to be compatible with the generic VideoCapturer class.
|
| - synchronized boolean init(String deviceName) {
|
| + boolean init(String deviceName) {
|
| Logging.d(TAG, "init: " + deviceName);
|
| if (deviceName == null)
|
| return false;
|
|
|
| - boolean foundDevice = false;
|
| if (deviceName.isEmpty()) {
|
| - this.id = 0;
|
| - foundDevice = true;
|
| + synchronized (cameraIdLock) {
|
| + this.id = 0;
|
| + }
|
| + return true;
|
| } else {
|
| for (int i = 0; i < Camera.getNumberOfCameras(); ++i) {
|
| - String existing_device = CameraEnumerationAndroid.getDeviceName(i);
|
| - if (existing_device != null && deviceName.equals(existing_device)) {
|
| - this.id = i;
|
| - foundDevice = true;
|
| + if (deviceName.equals(CameraEnumerationAndroid.getDeviceName(i))) {
|
| + synchronized (cameraIdLock) {
|
| + this.id = i;
|
| + }
|
| + return true;
|
| }
|
| }
|
| }
|
| - return foundDevice;
|
| - }
|
| -
|
| - String getSupportedFormatsAsJson() throws JSONException {
|
| - return CameraEnumerationAndroid.getSupportedFormatsAsJson(id);
|
| + return false;
|
| }
|
|
|
| - private class CameraThread extends Thread {
|
| - private Exchanger<Handler> handlerExchanger;
|
| - public CameraThread(Exchanger<Handler> handlerExchanger) {
|
| - this.handlerExchanger = handlerExchanger;
|
| + // Called by native code to quit the camera thread. This needs to be done manually, otherwise the
|
| + // thread and handler will not be garbage collected.
|
| + private void release() {
|
| + if (isReleased()) {
|
| + throw new IllegalStateException("Already released");
|
| }
|
| + cameraThreadHandler.post(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + if (camera != null) {
|
| + throw new IllegalStateException("Release called while camera is running");
|
| + }
|
| + if (videoBuffers.pendingFramesCount() != 0) {
|
| + throw new IllegalStateException("Release called with pending frames left");
|
| + }
|
| + }
|
| + });
|
| + cameraThread.quitSafely();
|
| + ThreadUtils.joinUninterruptibly(cameraThread);
|
| + cameraThread = null;
|
| + }
|
|
|
| - @Override public void run() {
|
| - Looper.prepare();
|
| - exchange(handlerExchanger, new Handler());
|
| - Looper.loop();
|
| - }
|
| + // Used for testing purposes to check if release() has been called.
|
| + public boolean isReleased() {
|
| + return (cameraThread == null);
|
| }
|
|
|
| - // Called by native code. Returns true if capturer is started.
|
| + // Called by native code.
|
| //
|
| // Note that this actually opens the camera, and Camera callbacks run on the
|
| // thread that calls open(), so this is done on the CameraThread.
|
| - synchronized void startCapture(
|
| + 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
|
| @@ -289,14 +326,6 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| if (frameObserver == null) {
|
| throw new RuntimeException("frameObserver not set.");
|
| }
|
| - if (cameraThreadHandler != null) {
|
| - throw new RuntimeException("Camera has already been started.");
|
| - }
|
| -
|
| - Exchanger<Handler> handlerExchanger = new Exchanger<Handler>();
|
| - cameraThread = new CameraThread(handlerExchanger);
|
| - cameraThread.start();
|
| - cameraThreadHandler = exchange(handlerExchanger, null);
|
| cameraThreadHandler.post(new Runnable() {
|
| @Override public void run() {
|
| startCaptureOnCameraThread(width, height, framerate, frameObserver,
|
| @@ -309,13 +338,19 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| int width, int height, int framerate, CapturerObserver frameObserver,
|
| Context applicationContext) {
|
| Throwable error = null;
|
| + checkIsOnCameraThread();
|
| + if (camera != null) {
|
| + throw new RuntimeException("Camera has already been started.");
|
| + }
|
| this.applicationContext = applicationContext;
|
| this.frameObserver = frameObserver;
|
| try {
|
| - Logging.d(TAG, "Opening camera " + id);
|
| - camera = Camera.open(id);
|
| - info = new Camera.CameraInfo();
|
| - Camera.getCameraInfo(id, info);
|
| + synchronized (cameraIdLock) {
|
| + Logging.d(TAG, "Opening camera " + id);
|
| + camera = Camera.open(id);
|
| + info = new Camera.CameraInfo();
|
| + Camera.getCameraInfo(id, info);
|
| + }
|
| // No local renderer (we only care about onPreviewFrame() buffers, not a
|
| // directly-displayed UI element). Camera won't capture without
|
| // setPreview{Texture,Display}, so we create a SurfaceTexture and hand
|
| @@ -347,7 +382,6 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| }
|
| Logging.e(TAG, "startCapture failed", error);
|
| stopCaptureOnCameraThread();
|
| - cameraThreadHandler = null;
|
| frameObserver.OnCapturerStarted(false);
|
| if (errorHandler != null) {
|
| errorHandler.onCameraError("Camera can not be started.");
|
| @@ -357,6 +391,7 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
|
|
| // (Re)start preview with the closest supported format to |width| x |height| @ |framerate|.
|
| private void startPreviewOnCameraThread(int width, int height, int framerate) {
|
| + checkIsOnCameraThread();
|
| Logging.d(
|
| TAG, "startPreviewOnCameraThread requested: " + width + "x" + height + "@" + framerate);
|
| if (camera == null) {
|
| @@ -420,31 +455,24 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| }
|
|
|
| // Called by native code. Returns true when camera is known to be stopped.
|
| - synchronized void stopCapture() throws InterruptedException {
|
| - if (cameraThreadHandler == null) {
|
| - Logging.e(TAG, "Calling stopCapture() for already stopped camera.");
|
| - return;
|
| - }
|
| + 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();
|
| }
|
| });
|
| - cameraThread.join();
|
| - cameraThreadHandler = null;
|
| + barrier.await();
|
| Logging.d(TAG, "stopCapture done");
|
| }
|
|
|
| private void stopCaptureOnCameraThread() {
|
| - doStopCaptureOnCameraThread();
|
| - Looper.myLooper().quit();
|
| - return;
|
| - }
|
| -
|
| - private void doStopCaptureOnCameraThread() {
|
| + checkIsOnCameraThread();
|
| Logging.d(TAG, "stopCaptureOnCameraThread");
|
| if (camera == null) {
|
| + Logging.e(TAG, "Calling stopCapture() for already stopped camera.");
|
| return;
|
| }
|
|
|
| @@ -462,25 +490,26 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| Logging.d(TAG, "Release camera.");
|
| camera.release();
|
| camera = null;
|
| + cameraSurfaceTexture.release();
|
| cameraSurfaceTexture = null;
|
| }
|
|
|
| - private void switchCameraOnCameraThread(Runnable switchDoneEvent) {
|
| + private void switchCameraOnCameraThread() {
|
| + checkIsOnCameraThread();
|
| Logging.d(TAG, "switchCameraOnCameraThread");
|
| -
|
| - doStopCaptureOnCameraThread();
|
| + stopCaptureOnCameraThread();
|
| + synchronized (cameraIdLock) {
|
| + id = (id + 1) % Camera.getNumberOfCameras();
|
| + }
|
| startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramerate, frameObserver,
|
| applicationContext);
|
| - pendingCameraSwitch = false;
|
| Logging.d(TAG, "switchCameraOnCameraThread done");
|
| - if (switchDoneEvent != null) {
|
| - switchDoneEvent.run();
|
| - }
|
| }
|
|
|
| - private void onOutputFormatRequestOnCameraThread(
|
| - int width, int height, int fps) {
|
| + private void onOutputFormatRequestOnCameraThread(int width, int height, int fps) {
|
| + checkIsOnCameraThread();
|
| if (camera == null) {
|
| + Logging.e(TAG, "Calling onOutputFormatRequest() on stopped camera.");
|
| return;
|
| }
|
| Logging.d(TAG, "onOutputFormatRequestOnCameraThread: " + width + "x" + height +
|
| @@ -488,8 +517,12 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| frameObserver.OnOutputFormatRequest(width, height, fps);
|
| }
|
|
|
| - void returnBuffer(long timeStamp) {
|
| - videoBuffers.returnBuffer(timeStamp);
|
| + public void returnBuffer(final long timeStamp) {
|
| + cameraThreadHandler.post(new Runnable() {
|
| + @Override public void run() {
|
| + videoBuffers.returnBuffer(timeStamp);
|
| + }
|
| + });
|
| }
|
|
|
| private int getDeviceOrientation() {
|
| @@ -518,9 +551,7 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| // Called on cameraThread so must not "synchronized".
|
| @Override
|
| public void onPreviewFrame(byte[] data, Camera callbackCamera) {
|
| - if (Thread.currentThread() != cameraThread) {
|
| - throw new RuntimeException("Camera callback not on camera thread?!?");
|
| - }
|
| + checkIsOnCameraThread();
|
| if (camera == null) {
|
| return;
|
| }
|
| @@ -549,37 +580,12 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| }
|
| }
|
|
|
| - // runCameraThreadUntilIdle make sure all posted messages to the cameraThread
|
| - // is processed before returning. It does that by itself posting a message to
|
| - // to the message queue and waits until is has been processed.
|
| - // It is used in tests.
|
| - void runCameraThreadUntilIdle() {
|
| - if (cameraThreadHandler == null)
|
| - return;
|
| - final Exchanger<Boolean> result = new Exchanger<Boolean>();
|
| - cameraThreadHandler.post(new Runnable() {
|
| - @Override public void run() {
|
| - exchange(result, true); // |true| is a dummy here.
|
| - }
|
| - });
|
| - exchange(result, false); // |false| is a dummy value here.
|
| - return;
|
| - }
|
| -
|
| - // Exchanges |value| with |exchanger|, converting InterruptedExceptions to
|
| - // RuntimeExceptions (since we expect never to see these).
|
| - private static <T> T exchange(Exchanger<T> exchanger, T value) {
|
| - try {
|
| - return exchanger.exchange(value);
|
| - } catch (InterruptedException e) {
|
| - throw new RuntimeException(e);
|
| - }
|
| - }
|
| -
|
| // Class used for allocating and bookkeeping video frames. All buffers are
|
| // direct allocated so that they can be directly used from native code. This class is
|
| - // synchronized and can be called from multiple threads.
|
| + // not thread-safe, and enforces single thread use.
|
| private static class FramePool {
|
| + // Thread that all calls should be made on.
|
| + private final Thread thread;
|
| // 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).
|
| @@ -593,12 +599,24 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| private int frameSize = 0;
|
| private Camera camera;
|
|
|
| - synchronized int numCaptureBuffersAvailable() {
|
| + public FramePool(Thread thread) {
|
| + this.thread = thread;
|
| + }
|
| +
|
| + private void checkIsOnValidThread() {
|
| + if (Thread.currentThread() != thread) {
|
| + throw new IllegalStateException("Wrong thread");
|
| + }
|
| + }
|
| +
|
| + public int numCaptureBuffersAvailable() {
|
| + checkIsOnValidThread();
|
| return queuedBuffers.size();
|
| }
|
|
|
| // Discards previous queued buffers and adds new callback buffers to camera.
|
| - synchronized void queueCameraBuffers(int frameSize, Camera camera) {
|
| + public void queueCameraBuffers(int frameSize, Camera camera) {
|
| + checkIsOnValidThread();
|
| this.camera = camera;
|
| this.frameSize = frameSize;
|
|
|
| @@ -612,7 +630,14 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| + " buffers of size " + frameSize + ".");
|
| }
|
|
|
| - synchronized String pendingFramesTimeStamps() {
|
| + // Return number of pending frames that have not been returned.
|
| + public int pendingFramesCount() {
|
| + checkIsOnValidThread();
|
| + return pendingBuffers.size();
|
| + }
|
| +
|
| + public String pendingFramesTimeStamps() {
|
| + checkIsOnValidThread();
|
| List<Long> timeStampsMs = new ArrayList<Long>();
|
| for (Long timeStampNs : pendingBuffers.keySet()) {
|
| timeStampsMs.add(TimeUnit.NANOSECONDS.toMillis(timeStampNs));
|
| @@ -620,7 +645,8 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| return timeStampsMs.toString();
|
| }
|
|
|
| - synchronized void stopReturnBuffersToCamera() {
|
| + public void stopReturnBuffersToCamera() {
|
| + checkIsOnValidThread();
|
| this.camera = null;
|
| queuedBuffers.clear();
|
| // Frames in |pendingBuffers| need to be kept alive until they are returned.
|
| @@ -630,7 +656,8 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| : " Pending buffers: " + pendingFramesTimeStamps() + "."));
|
| }
|
|
|
| - synchronized boolean reserveByteBuffer(byte[] data, long timeStamp) {
|
| + public boolean reserveByteBuffer(byte[] data, long timeStamp) {
|
| + checkIsOnValidThread();
|
| final ByteBuffer buffer = queuedBuffers.remove(data);
|
| if (buffer == null) {
|
| // Frames might be posted to |onPreviewFrame| with the previous format while changing
|
| @@ -654,7 +681,8 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
|
| return true;
|
| }
|
|
|
| - synchronized void returnBuffer(long timeStamp) {
|
| + public void returnBuffer(long timeStamp) {
|
| + checkIsOnValidThread();
|
| final ByteBuffer returnedFrame = pendingBuffers.remove(timeStamp);
|
| if (returnedFrame == null) {
|
| throw new RuntimeException("unknown data buffer with time stamp "
|
|
|