Android: Add initialize() function to VideoCapturer interface
This CL moves some arguments, e.g. the camera thread, from the startCapture() function to a new initialize() function. These arguments are constant during the lifetime of the VideoCapturer, and are not changed for different startCapture() calls. Setting them once allows for simplifications in the code. This CL also fixes a bug for camera2 where pendingCameraSwitchSemaphore might not be released when switchEventsHandler is null. In camera1, the handler lock and 'cameraThreadHandler == null' check is replaced with an atomic boolean to check if the camera is stopped. BUG=webrtc:5519 R=sakal@webrtc.org Review URL: https://codereview.webrtc.org/2122693002 . Cr-Commit-Position: refs/heads/master@{#13404}
This commit is contained in:
@ -35,7 +35,7 @@ import android.view.WindowManager;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
@TargetApi(21)
|
@TargetApi(21)
|
||||||
public class Camera2Capturer implements
|
public class Camera2Capturer implements
|
||||||
@ -58,17 +58,20 @@ public class Camera2Capturer implements
|
|||||||
private final CameraManager cameraManager;
|
private final CameraManager cameraManager;
|
||||||
private final CameraEventsHandler eventsHandler;
|
private final CameraEventsHandler eventsHandler;
|
||||||
|
|
||||||
|
// Set once in initialization(), before any other calls, so therefore thread safe.
|
||||||
|
// ---------------------------------------------------------------------------------------------
|
||||||
|
private SurfaceTextureHelper surfaceTextureHelper;
|
||||||
|
private Context applicationContext;
|
||||||
|
private CapturerObserver capturerObserver;
|
||||||
|
// Use postOnCameraThread() instead of posting directly to the handler - this way all callbacks
|
||||||
|
// with a specifed token can be removed at once.
|
||||||
|
private Handler cameraThreadHandler;
|
||||||
|
|
||||||
// Shared state - guarded by cameraStateLock. Will only be edited from camera thread (when it is
|
// Shared state - guarded by cameraStateLock. Will only be edited from camera thread (when it is
|
||||||
// running).
|
// running).
|
||||||
// ---------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------
|
||||||
private final Object cameraStateLock = new Object();
|
private final Object cameraStateLock = new Object();
|
||||||
private CameraState cameraState = CameraState.IDLE;
|
private volatile CameraState cameraState = CameraState.IDLE;
|
||||||
// |cameraThreadHandler| must be synchronized on |cameraStateLock| when not on the camera thread,
|
|
||||||
// or when modifying the reference. Use postOnCameraThread() instead of posting directly to
|
|
||||||
// the handler - this way all callbacks with a specifed token can be removed at once.
|
|
||||||
// |cameraThreadHandler| must be null if and only if CameraState is IDLE.
|
|
||||||
private Handler cameraThreadHandler;
|
|
||||||
// Remember the requested format in case we want to switch cameras.
|
// Remember the requested format in case we want to switch cameras.
|
||||||
private int requestedWidth;
|
private int requestedWidth;
|
||||||
private int requestedHeight;
|
private int requestedHeight;
|
||||||
@ -79,22 +82,18 @@ public class Camera2Capturer implements
|
|||||||
private boolean isFrontCamera;
|
private boolean isFrontCamera;
|
||||||
private int cameraOrientation;
|
private int cameraOrientation;
|
||||||
|
|
||||||
// Semaphore for allowing only one switch at a time.
|
// Atomic boolean for allowing only one switch at a time.
|
||||||
private final Semaphore pendingCameraSwitchSemaphore = new Semaphore(1);
|
private final AtomicBoolean isPendingCameraSwitch = new AtomicBoolean();
|
||||||
// Guarded by pendingCameraSwitchSemaphore
|
// Guarded by isPendingCameraSwitch.
|
||||||
private CameraSwitchHandler switchEventsHandler;
|
private CameraSwitchHandler switchEventsHandler;
|
||||||
|
|
||||||
// Internal state - must only be modified from camera thread
|
// Internal state - must only be modified from camera thread
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
private CaptureFormat captureFormat;
|
private CaptureFormat captureFormat;
|
||||||
private Context applicationContext;
|
|
||||||
private CapturerObserver capturerObserver;
|
|
||||||
private CameraStatistics cameraStatistics;
|
private CameraStatistics cameraStatistics;
|
||||||
private SurfaceTextureHelper surfaceTextureHelper;
|
|
||||||
private CameraCaptureSession captureSession;
|
private CameraCaptureSession captureSession;
|
||||||
private Surface surface;
|
private Surface surface;
|
||||||
private CameraDevice cameraDevice;
|
private CameraDevice cameraDevice;
|
||||||
private CameraStateCallback cameraStateCallback;
|
|
||||||
|
|
||||||
// Factor to convert between Android framerates and CaptureFormat.FramerateRange. It will be
|
// Factor to convert between Android framerates and CaptureFormat.FramerateRange. It will be
|
||||||
// either 1 or 1000.
|
// either 1 or 1000.
|
||||||
@ -111,28 +110,16 @@ public class Camera2Capturer implements
|
|||||||
setCameraName(cameraName);
|
setCameraName(cameraName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private boolean isOnCameraThread() {
|
||||||
* Helper method for checking method is executed on camera thread. Also allows calls from other
|
return Thread.currentThread() == cameraThreadHandler.getLooper().getThread();
|
||||||
* threads if camera is closed.
|
|
||||||
*/
|
|
||||||
private void checkIsOnCameraThread() {
|
|
||||||
if (cameraState == CameraState.IDLE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkIsStrictlyOnCameraThread();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like checkIsOnCameraThread but doesn't allow the camera to be stopped.
|
* Helper method for checking method is executed on camera thread.
|
||||||
*/
|
*/
|
||||||
private void checkIsStrictlyOnCameraThread() {
|
private void checkIsOnCameraThread() {
|
||||||
if (cameraThreadHandler == null) {
|
if (!isOnCameraThread()) {
|
||||||
throw new IllegalStateException("Camera is closed.");
|
throw new IllegalStateException("Not on camera thread");
|
||||||
}
|
|
||||||
|
|
||||||
if (Thread.currentThread() != cameraThreadHandler.getLooper().getThread()) {
|
|
||||||
throw new IllegalStateException("Wrong thread");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,14 +234,14 @@ public class Camera2Capturer implements
|
|||||||
* thread and camera must not be stopped.
|
* thread and camera must not be stopped.
|
||||||
*/
|
*/
|
||||||
private void reportError(String errorDescription) {
|
private void reportError(String errorDescription) {
|
||||||
checkIsStrictlyOnCameraThread();
|
checkIsOnCameraThread();
|
||||||
Logging.e(TAG, "Error in camera at state " + cameraState + ": " + errorDescription);
|
Logging.e(TAG, "Error in camera at state " + cameraState + ": " + errorDescription);
|
||||||
|
|
||||||
if (switchEventsHandler != null) {
|
if (switchEventsHandler != null) {
|
||||||
switchEventsHandler.onCameraSwitchError(errorDescription);
|
switchEventsHandler.onCameraSwitchError(errorDescription);
|
||||||
switchEventsHandler = null;
|
switchEventsHandler = null;
|
||||||
pendingCameraSwitchSemaphore.release();
|
|
||||||
}
|
}
|
||||||
|
isPendingCameraSwitch.set(false);
|
||||||
|
|
||||||
switch (cameraState) {
|
switch (cameraState) {
|
||||||
case STARTING:
|
case STARTING:
|
||||||
@ -276,22 +263,19 @@ public class Camera2Capturer implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void closeAndRelease() {
|
private void closeAndRelease() {
|
||||||
checkIsStrictlyOnCameraThread();
|
checkIsOnCameraThread();
|
||||||
|
|
||||||
Logging.d(TAG, "Close and release.");
|
Logging.d(TAG, "Close and release.");
|
||||||
setCameraState(CameraState.STOPPING);
|
setCameraState(CameraState.STOPPING);
|
||||||
|
|
||||||
// Remove all pending Runnables posted from |this|.
|
// Remove all pending Runnables posted from |this|.
|
||||||
cameraThreadHandler.removeCallbacksAndMessages(this /* token */);
|
cameraThreadHandler.removeCallbacksAndMessages(this /* token */);
|
||||||
applicationContext = null;
|
|
||||||
capturerObserver = null;
|
|
||||||
if (cameraStatistics != null) {
|
if (cameraStatistics != null) {
|
||||||
cameraStatistics.release();
|
cameraStatistics.release();
|
||||||
cameraStatistics = null;
|
cameraStatistics = null;
|
||||||
}
|
}
|
||||||
if (surfaceTextureHelper != null) {
|
if (surfaceTextureHelper != null) {
|
||||||
surfaceTextureHelper.stopListening();
|
surfaceTextureHelper.stopListening();
|
||||||
surfaceTextureHelper = null;
|
|
||||||
}
|
}
|
||||||
if (captureSession != null) {
|
if (captureSession != null) {
|
||||||
captureSession.close();
|
captureSession.close();
|
||||||
@ -320,7 +304,6 @@ public class Camera2Capturer implements
|
|||||||
Logging.w(TAG, "closeAndRelease called while cameraDevice is null");
|
Logging.w(TAG, "closeAndRelease called while cameraDevice is null");
|
||||||
setCameraState(CameraState.IDLE);
|
setCameraState(CameraState.IDLE);
|
||||||
}
|
}
|
||||||
this.cameraStateCallback = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -328,16 +311,9 @@ public class Camera2Capturer implements
|
|||||||
*/
|
*/
|
||||||
private void setCameraState(CameraState newState) {
|
private void setCameraState(CameraState newState) {
|
||||||
// State must only be modified on the camera thread. It can be edited from other threads
|
// State must only be modified on the camera thread. It can be edited from other threads
|
||||||
// if cameraState is IDLE since there is no camera thread.
|
// if cameraState is IDLE since the camera thread is idle and not modifying the state.
|
||||||
checkIsOnCameraThread();
|
if (cameraState != CameraState.IDLE) {
|
||||||
|
checkIsOnCameraThread();
|
||||||
if (newState != CameraState.IDLE) {
|
|
||||||
if (cameraThreadHandler == null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"cameraThreadHandler must be null if and only if CameraState is IDLE.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cameraThreadHandler = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (newState) {
|
switch (newState) {
|
||||||
@ -376,37 +352,49 @@ public class Camera2Capturer implements
|
|||||||
*/
|
*/
|
||||||
private void openCamera() {
|
private void openCamera() {
|
||||||
try {
|
try {
|
||||||
checkIsStrictlyOnCameraThread();
|
checkIsOnCameraThread();
|
||||||
|
|
||||||
if (cameraState != CameraState.STARTING) {
|
if (cameraState != CameraState.STARTING) {
|
||||||
throw new IllegalStateException("Camera should be in state STARTING in openCamera.");
|
throw new IllegalStateException("Camera should be in state STARTING in openCamera.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cameraThreadHandler == null) {
|
|
||||||
throw new RuntimeException("Someone set cameraThreadHandler to null while the camera "
|
|
||||||
+ "state was STARTING. This should never happen");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Camera is in state STARTING so cameraName will not be edited.
|
// Camera is in state STARTING so cameraName will not be edited.
|
||||||
cameraManager.openCamera(cameraName, cameraStateCallback, cameraThreadHandler);
|
cameraManager.openCamera(cameraName, new CameraStateCallback(), cameraThreadHandler);
|
||||||
} catch (CameraAccessException e) {
|
} catch (CameraAccessException e) {
|
||||||
reportError("Failed to open camera: " + e);
|
reportError("Failed to open camera: " + e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startCaptureOnCameraThread(
|
private boolean isInitialized() {
|
||||||
final int requestedWidth, final int requestedHeight, final int requestedFramerate,
|
return applicationContext != null && capturerObserver != null;
|
||||||
final SurfaceTextureHelper surfaceTextureHelper, final Context applicationContext,
|
}
|
||||||
final CapturerObserver capturerObserver) {
|
|
||||||
checkIsStrictlyOnCameraThread();
|
|
||||||
|
|
||||||
firstFrameReported = false;
|
|
||||||
consecutiveCameraOpenFailures = 0;
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context applicationContext,
|
||||||
|
CapturerObserver capturerObserver) {
|
||||||
|
Logging.d(TAG, "initialize");
|
||||||
|
if (applicationContext == null) {
|
||||||
|
throw new IllegalArgumentException("applicationContext not set.");
|
||||||
|
}
|
||||||
|
if (capturerObserver == null) {
|
||||||
|
throw new IllegalArgumentException("capturerObserver not set.");
|
||||||
|
}
|
||||||
|
if (isInitialized()) {
|
||||||
|
throw new IllegalStateException("Already initialized");
|
||||||
|
}
|
||||||
this.applicationContext = applicationContext;
|
this.applicationContext = applicationContext;
|
||||||
this.capturerObserver = capturerObserver;
|
this.capturerObserver = capturerObserver;
|
||||||
this.surfaceTextureHelper = surfaceTextureHelper;
|
this.surfaceTextureHelper = surfaceTextureHelper;
|
||||||
this.cameraStateCallback = new CameraStateCallback();
|
this.cameraThreadHandler =
|
||||||
|
surfaceTextureHelper == null ? null : surfaceTextureHelper.getHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startCaptureOnCameraThread(
|
||||||
|
final int requestedWidth, final int requestedHeight, final int requestedFramerate) {
|
||||||
|
checkIsOnCameraThread();
|
||||||
|
|
||||||
|
firstFrameReported = false;
|
||||||
|
consecutiveCameraOpenFailures = 0;
|
||||||
|
|
||||||
synchronized (cameraStateLock) {
|
synchronized (cameraStateLock) {
|
||||||
// Remember the requested format in case we want to switch cameras.
|
// Remember the requested format in case we want to switch cameras.
|
||||||
@ -466,36 +454,32 @@ public class Camera2Capturer implements
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void startCapture(
|
public void startCapture(
|
||||||
final int requestedWidth, final int requestedHeight, final int requestedFramerate,
|
final int requestedWidth, final int requestedHeight, final int requestedFramerate) {
|
||||||
final SurfaceTextureHelper surfaceTextureHelper, final Context applicationContext,
|
|
||||||
final CapturerObserver capturerObserver) {
|
|
||||||
Logging.d(TAG, "startCapture requested: " + requestedWidth + "x" + requestedHeight
|
Logging.d(TAG, "startCapture requested: " + requestedWidth + "x" + requestedHeight
|
||||||
+ "@" + requestedFramerate);
|
+ "@" + requestedFramerate);
|
||||||
|
if (!isInitialized()) {
|
||||||
|
throw new IllegalStateException("startCapture called in uninitialized state");
|
||||||
|
}
|
||||||
if (surfaceTextureHelper == null) {
|
if (surfaceTextureHelper == null) {
|
||||||
throw new IllegalArgumentException("surfaceTextureHelper not set.");
|
capturerObserver.onCapturerStarted(false /* success */);
|
||||||
|
if (eventsHandler != null) {
|
||||||
|
eventsHandler.onCameraError("No SurfaceTexture created.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (applicationContext == null) {
|
|
||||||
throw new IllegalArgumentException("applicationContext not set.");
|
|
||||||
}
|
|
||||||
if (capturerObserver == null) {
|
|
||||||
throw new IllegalArgumentException("capturerObserver not set.");
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized (cameraStateLock) {
|
synchronized (cameraStateLock) {
|
||||||
waitForCameraToStopIfStopping();
|
waitForCameraToStopIfStopping();
|
||||||
if (cameraState != CameraState.IDLE) {
|
if (cameraState != CameraState.IDLE) {
|
||||||
Logging.e(TAG, "Unexpected camera state for startCapture: " + cameraState);
|
Logging.e(TAG, "Unexpected camera state for startCapture: " + cameraState);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.cameraThreadHandler = surfaceTextureHelper.getHandler();
|
|
||||||
setCameraState(CameraState.STARTING);
|
setCameraState(CameraState.STARTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
postOnCameraThread(new Runnable() {
|
postOnCameraThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramerate,
|
startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramerate);
|
||||||
surfaceTextureHelper, applicationContext, capturerObserver);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -521,14 +505,14 @@ public class Camera2Capturer implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisconnected(CameraDevice camera) {
|
public void onDisconnected(CameraDevice camera) {
|
||||||
checkIsStrictlyOnCameraThread();
|
checkIsOnCameraThread();
|
||||||
cameraDevice = camera;
|
cameraDevice = camera;
|
||||||
reportError("Camera disconnected.");
|
reportError("Camera disconnected.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(CameraDevice camera, int errorCode) {
|
public void onError(CameraDevice camera, int errorCode) {
|
||||||
checkIsStrictlyOnCameraThread();
|
checkIsOnCameraThread();
|
||||||
cameraDevice = camera;
|
cameraDevice = camera;
|
||||||
|
|
||||||
if (cameraState == CameraState.STARTING && (
|
if (cameraState == CameraState.STARTING && (
|
||||||
@ -555,7 +539,7 @@ public class Camera2Capturer implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onOpened(CameraDevice camera) {
|
public void onOpened(CameraDevice camera) {
|
||||||
checkIsStrictlyOnCameraThread();
|
checkIsOnCameraThread();
|
||||||
|
|
||||||
Logging.d(TAG, "Camera opened.");
|
Logging.d(TAG, "Camera opened.");
|
||||||
if (cameraState != CameraState.STARTING) {
|
if (cameraState != CameraState.STARTING) {
|
||||||
@ -576,7 +560,7 @@ public class Camera2Capturer implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClosed(CameraDevice camera) {
|
public void onClosed(CameraDevice camera) {
|
||||||
checkIsStrictlyOnCameraThread();
|
checkIsOnCameraThread();
|
||||||
|
|
||||||
Logging.d(TAG, "Camera device closed.");
|
Logging.d(TAG, "Camera device closed.");
|
||||||
|
|
||||||
@ -597,14 +581,14 @@ public class Camera2Capturer implements
|
|||||||
final class CaptureSessionCallback extends CameraCaptureSession.StateCallback {
|
final class CaptureSessionCallback extends CameraCaptureSession.StateCallback {
|
||||||
@Override
|
@Override
|
||||||
public void onConfigureFailed(CameraCaptureSession session) {
|
public void onConfigureFailed(CameraCaptureSession session) {
|
||||||
checkIsStrictlyOnCameraThread();
|
checkIsOnCameraThread();
|
||||||
captureSession = session;
|
captureSession = session;
|
||||||
reportError("Failed to configure capture session.");
|
reportError("Failed to configure capture session.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigured(CameraCaptureSession session) {
|
public void onConfigured(CameraCaptureSession session) {
|
||||||
checkIsStrictlyOnCameraThread();
|
checkIsOnCameraThread();
|
||||||
Logging.d(TAG, "Camera capture session configured.");
|
Logging.d(TAG, "Camera capture session configured.");
|
||||||
captureSession = session;
|
captureSession = session;
|
||||||
try {
|
try {
|
||||||
@ -642,8 +626,8 @@ public class Camera2Capturer implements
|
|||||||
if (switchEventsHandler != null) {
|
if (switchEventsHandler != null) {
|
||||||
switchEventsHandler.onCameraSwitchDone(isFrontCamera);
|
switchEventsHandler.onCameraSwitchDone(isFrontCamera);
|
||||||
switchEventsHandler = null;
|
switchEventsHandler = null;
|
||||||
pendingCameraSwitchSemaphore.release();
|
|
||||||
}
|
}
|
||||||
|
isPendingCameraSwitch.set(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -692,8 +676,9 @@ public class Camera2Capturer implements
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Do not handle multiple camera switch request to avoid blocking camera thread by handling too
|
// Do not handle multiple camera switch request to avoid blocking camera thread by handling too
|
||||||
// many switch request from a queue. We have to be careful to always release this.
|
// many switch request from a queue. We have to be careful to always release
|
||||||
if (!pendingCameraSwitchSemaphore.tryAcquire()) {
|
// |isPendingCameraSwitch| by setting it to false when done.
|
||||||
|
if (isPendingCameraSwitch.getAndSet(true)) {
|
||||||
Logging.w(TAG, "Ignoring camera switch request.");
|
Logging.w(TAG, "Ignoring camera switch request.");
|
||||||
if (switchEventsHandler != null) {
|
if (switchEventsHandler != null) {
|
||||||
switchEventsHandler.onCameraSwitchError("Pending camera switch already in progress.");
|
switchEventsHandler.onCameraSwitchError("Pending camera switch already in progress.");
|
||||||
@ -702,9 +687,6 @@ public class Camera2Capturer implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String newCameraId;
|
final String newCameraId;
|
||||||
final SurfaceTextureHelper surfaceTextureHelper;
|
|
||||||
final Context applicationContext;
|
|
||||||
final CapturerObserver capturerObserver;
|
|
||||||
final int requestedWidth;
|
final int requestedWidth;
|
||||||
final int requestedHeight;
|
final int requestedHeight;
|
||||||
final int requestedFramerate;
|
final int requestedFramerate;
|
||||||
@ -717,7 +699,7 @@ public class Camera2Capturer implements
|
|||||||
if (switchEventsHandler != null) {
|
if (switchEventsHandler != null) {
|
||||||
switchEventsHandler.onCameraSwitchError("Camera is stopped.");
|
switchEventsHandler.onCameraSwitchError("Camera is stopped.");
|
||||||
}
|
}
|
||||||
pendingCameraSwitchSemaphore.release();
|
isPendingCameraSwitch.set(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -731,11 +713,6 @@ public class Camera2Capturer implements
|
|||||||
final int newCameraIndex = (currentCameraIndex + 1) % cameraIds.length;
|
final int newCameraIndex = (currentCameraIndex + 1) % cameraIds.length;
|
||||||
newCameraId = cameraIds[newCameraIndex];
|
newCameraId = cameraIds[newCameraIndex];
|
||||||
|
|
||||||
// Remember parameters. These are not null since camera is in RUNNING state. They aren't
|
|
||||||
// edited either while camera is in RUNNING state.
|
|
||||||
surfaceTextureHelper = this.surfaceTextureHelper;
|
|
||||||
applicationContext = this.applicationContext;
|
|
||||||
capturerObserver = this.capturerObserver;
|
|
||||||
requestedWidth = this.requestedWidth;
|
requestedWidth = this.requestedWidth;
|
||||||
requestedHeight = this.requestedHeight;
|
requestedHeight = this.requestedHeight;
|
||||||
requestedFramerate = this.requestedFramerate;
|
requestedFramerate = this.requestedFramerate;
|
||||||
@ -745,8 +722,7 @@ public class Camera2Capturer implements
|
|||||||
// Make the switch.
|
// Make the switch.
|
||||||
stopCapture();
|
stopCapture();
|
||||||
setCameraName(newCameraId);
|
setCameraName(newCameraId);
|
||||||
startCapture(requestedWidth, requestedHeight, requestedFramerate, surfaceTextureHelper,
|
startCapture(requestedWidth, requestedHeight, requestedFramerate);
|
||||||
applicationContext, capturerObserver);
|
|
||||||
|
|
||||||
// Note: switchEventsHandler will be called from onConfigured / reportError.
|
// Note: switchEventsHandler will be called from onConfigured / reportError.
|
||||||
}
|
}
|
||||||
@ -761,10 +737,6 @@ public class Camera2Capturer implements
|
|||||||
postOnCameraThread(new Runnable() {
|
postOnCameraThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (capturerObserver == null) {
|
|
||||||
Logging.e(TAG, "Calling onOutputFormatRequest() on stopped camera.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Logging.d(TAG,
|
Logging.d(TAG,
|
||||||
"onOutputFormatRequestOnCameraThread: " + width + "x" + height + "@" + framerate);
|
"onOutputFormatRequestOnCameraThread: " + width + "x" + height + "@" + framerate);
|
||||||
capturerObserver.onOutputFormatRequest(width, height, framerate);
|
capturerObserver.onOutputFormatRequest(width, height, framerate);
|
||||||
@ -776,10 +748,6 @@ public class Camera2Capturer implements
|
|||||||
// is running.
|
// is running.
|
||||||
@Override
|
@Override
|
||||||
public void changeCaptureFormat(final int width, final int height, final int framerate) {
|
public void changeCaptureFormat(final int width, final int height, final int framerate) {
|
||||||
final SurfaceTextureHelper surfaceTextureHelper;
|
|
||||||
final Context applicationContext;
|
|
||||||
final CapturerObserver capturerObserver;
|
|
||||||
|
|
||||||
synchronized (cameraStateLock) {
|
synchronized (cameraStateLock) {
|
||||||
waitForCameraToStartIfStarting();
|
waitForCameraToStartIfStarting();
|
||||||
|
|
||||||
@ -791,17 +759,12 @@ public class Camera2Capturer implements
|
|||||||
requestedWidth = width;
|
requestedWidth = width;
|
||||||
requestedHeight = height;
|
requestedHeight = height;
|
||||||
requestedFramerate = framerate;
|
requestedFramerate = framerate;
|
||||||
|
|
||||||
surfaceTextureHelper = this.surfaceTextureHelper;
|
|
||||||
applicationContext = this.applicationContext;
|
|
||||||
capturerObserver = this.capturerObserver;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the switch.
|
// Make the switch.
|
||||||
stopCapture();
|
stopCapture();
|
||||||
// TODO(magjed/sakal): Just recreate session.
|
// TODO(magjed/sakal): Just recreate session.
|
||||||
startCapture(width, height, framerate,
|
startCapture(width, height, framerate);
|
||||||
surfaceTextureHelper, applicationContext, capturerObserver);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -896,7 +859,7 @@ public class Camera2Capturer implements
|
|||||||
@Override
|
@Override
|
||||||
public void onTextureFrameAvailable(
|
public void onTextureFrameAvailable(
|
||||||
int oesTextureId, float[] transformMatrix, long timestampNs) {
|
int oesTextureId, float[] transformMatrix, long timestampNs) {
|
||||||
checkIsStrictlyOnCameraThread();
|
checkIsOnCameraThread();
|
||||||
|
|
||||||
if (eventsHandler != null && !firstFrameReported) {
|
if (eventsHandler != null && !firstFrameReported) {
|
||||||
eventsHandler.onFirstFrameAvailable();
|
eventsHandler.onFirstFrameAvailable();
|
||||||
|
|||||||
@ -88,14 +88,21 @@ public interface VideoCapturer {
|
|||||||
List<CameraEnumerationAndroid.CaptureFormat> getSupportedFormats();
|
List<CameraEnumerationAndroid.CaptureFormat> getSupportedFormats();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start capturing frames in a format that is as close as possible to |width| x |height| and
|
* This function is used to initialize the camera thread, the android application context, and the
|
||||||
* |framerate|. If the VideoCapturer wants to deliver texture frames, it should do this by
|
* capture observer. It will be called only once and before any startCapture() request. The
|
||||||
* rendering on the SurfaceTexture in |surfaceTextureHelper|, register itself as a listener,
|
* camera thread is guaranteed to be valid until dispose() is called. If the VideoCapturer wants
|
||||||
* and forward the texture frames to CapturerObserver.onTextureFrameCaptured().
|
* to deliver texture frames, it should do this by rendering on the SurfaceTexture in
|
||||||
|
* |surfaceTextureHelper|, register itself as a listener, and forward the texture frames to
|
||||||
|
* CapturerObserver.onTextureFrameCaptured().
|
||||||
*/
|
*/
|
||||||
void startCapture(
|
void initialize(SurfaceTextureHelper surfaceTextureHelper, Context applicationContext,
|
||||||
int width, int height, int framerate, SurfaceTextureHelper surfaceTextureHelper,
|
CapturerObserver capturerObserver);
|
||||||
Context applicationContext, CapturerObserver frameObserver);
|
|
||||||
|
/**
|
||||||
|
* Start capturing frames in a format that is as close as possible to |width| x |height| and
|
||||||
|
* |framerate|.
|
||||||
|
*/
|
||||||
|
void startCapture(int width, int height, int framerate);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop capturing. This function should block until capture is actually stopped.
|
* Stop capturing. This function should block until capture is actually stopped.
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import java.nio.ByteBuffer;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@ -43,15 +44,14 @@ public class VideoCapturerAndroid implements
|
|||||||
CameraVideoCapturer,
|
CameraVideoCapturer,
|
||||||
android.hardware.Camera.PreviewCallback,
|
android.hardware.Camera.PreviewCallback,
|
||||||
SurfaceTextureHelper.OnTextureFrameAvailableListener {
|
SurfaceTextureHelper.OnTextureFrameAvailableListener {
|
||||||
private final static String TAG = "VideoCapturerAndroid";
|
private static final String TAG = "VideoCapturerAndroid";
|
||||||
private static final int CAMERA_STOP_TIMEOUT_MS = 7000;
|
private static final int CAMERA_STOP_TIMEOUT_MS = 7000;
|
||||||
|
|
||||||
private android.hardware.Camera camera; // Only non-null while capturing.
|
private android.hardware.Camera camera; // Only non-null while capturing.
|
||||||
private final Object handlerLock = new Object();
|
private final AtomicBoolean isCameraRunning = new AtomicBoolean();
|
||||||
// |cameraThreadHandler| must be synchronized on |handlerLock| when not on the camera thread,
|
// Use maybePostOnCameraThread() instead of posting directly to the handler - this way all
|
||||||
// or when modifying the reference. Use maybePostOnCameraThread() instead of posting directly to
|
// callbacks with a specifed token can be removed at once.
|
||||||
// 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;
|
private Context applicationContext;
|
||||||
// Synchronization lock for |id|.
|
// Synchronization lock for |id|.
|
||||||
private final Object cameraIdLock = new Object();
|
private final Object cameraIdLock = new Object();
|
||||||
@ -117,10 +117,8 @@ public class VideoCapturerAndroid implements
|
|||||||
|
|
||||||
public void printStackTrace() {
|
public void printStackTrace() {
|
||||||
Thread cameraThread = null;
|
Thread cameraThread = null;
|
||||||
synchronized (handlerLock) {
|
if (cameraThreadHandler != null) {
|
||||||
if (cameraThreadHandler != null) {
|
cameraThread = cameraThreadHandler.getLooper().getThread();
|
||||||
cameraThread = cameraThreadHandler.getLooper().getThread();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (cameraThread != null) {
|
if (cameraThread != null) {
|
||||||
StackTraceElement[] cameraStackTraces = cameraThread.getStackTrace();
|
StackTraceElement[] cameraStackTraces = cameraThread.getStackTrace();
|
||||||
@ -232,12 +230,10 @@ public class VideoCapturerAndroid implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkIsOnCameraThread() {
|
private void checkIsOnCameraThread() {
|
||||||
synchronized (handlerLock) {
|
if (cameraThreadHandler == null) {
|
||||||
if (cameraThreadHandler == null) {
|
Logging.e(TAG, "Camera is not initialized - can't check thread.");
|
||||||
Logging.e(TAG, "Camera is stopped - can't check thread.");
|
} else if (Thread.currentThread() != cameraThreadHandler.getLooper().getThread()) {
|
||||||
} else if (Thread.currentThread() != cameraThreadHandler.getLooper().getThread()) {
|
throw new IllegalStateException("Wrong thread");
|
||||||
throw new IllegalStateException("Wrong thread");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,11 +242,9 @@ public class VideoCapturerAndroid implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean maybePostDelayedOnCameraThread(int delayMs, Runnable runnable) {
|
private boolean maybePostDelayedOnCameraThread(int delayMs, Runnable runnable) {
|
||||||
synchronized (handlerLock) {
|
return cameraThreadHandler != null && isCameraRunning.get()
|
||||||
return cameraThreadHandler != null
|
&& cameraThreadHandler.postAtTime(
|
||||||
&& cameraThreadHandler.postAtTime(
|
runnable, this /* token */, SystemClock.uptimeMillis() + delayMs);
|
||||||
runnable, this /* token */, SystemClock.uptimeMillis() + delayMs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -258,67 +252,75 @@ public class VideoCapturerAndroid implements
|
|||||||
Logging.d(TAG, "dispose");
|
Logging.d(TAG, "dispose");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that this actually opens the camera, and Camera callbacks run on the
|
private boolean isInitialized() {
|
||||||
// thread that calls open(), so this is done on the CameraThread.
|
return applicationContext != null && frameObserver != null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startCapture(
|
public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context applicationContext,
|
||||||
final int width, final int height, final int framerate,
|
CapturerObserver frameObserver) {
|
||||||
final SurfaceTextureHelper surfaceTextureHelper, final Context applicationContext,
|
Logging.d(TAG, "initialize");
|
||||||
final CapturerObserver frameObserver) {
|
|
||||||
Logging.d(TAG, "startCapture requested: " + width + "x" + height + "@" + framerate);
|
|
||||||
if (surfaceTextureHelper == null) {
|
|
||||||
frameObserver.onCapturerStarted(false /* success */);
|
|
||||||
if (eventsHandler != null) {
|
|
||||||
eventsHandler.onCameraError("No SurfaceTexture created.");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (applicationContext == null) {
|
if (applicationContext == null) {
|
||||||
throw new IllegalArgumentException("applicationContext not set.");
|
throw new IllegalArgumentException("applicationContext not set.");
|
||||||
}
|
}
|
||||||
if (frameObserver == null) {
|
if (frameObserver == null) {
|
||||||
throw new IllegalArgumentException("frameObserver not set.");
|
throw new IllegalArgumentException("frameObserver not set.");
|
||||||
}
|
}
|
||||||
synchronized (handlerLock) {
|
if (isInitialized()) {
|
||||||
if (this.cameraThreadHandler != null) {
|
throw new IllegalStateException("Already initialized");
|
||||||
throw new RuntimeException("Camera has already been started.");
|
}
|
||||||
|
this.applicationContext = applicationContext;
|
||||||
|
this.frameObserver = frameObserver;
|
||||||
|
this.surfaceHelper = surfaceTextureHelper;
|
||||||
|
this.cameraThreadHandler =
|
||||||
|
surfaceTextureHelper == null ? null : surfaceTextureHelper.getHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
if (surfaceHelper == null) {
|
||||||
|
frameObserver.onCapturerStarted(false /* success */);
|
||||||
|
if (eventsHandler != null) {
|
||||||
|
eventsHandler.onCameraError("No SurfaceTexture created.");
|
||||||
}
|
}
|
||||||
this.cameraThreadHandler = surfaceTextureHelper.getHandler();
|
return;
|
||||||
this.surfaceHelper = surfaceTextureHelper;
|
}
|
||||||
final boolean didPost = maybePostOnCameraThread(new Runnable() {
|
if (isCameraRunning.getAndSet(true)) {
|
||||||
@Override
|
Logging.e(TAG, "Camera has already been started.");
|
||||||
public void run() {
|
return;
|
||||||
openCameraAttempts = 0;
|
}
|
||||||
startCaptureOnCameraThread(width, height, framerate, frameObserver,
|
final boolean didPost = maybePostOnCameraThread(new Runnable() {
|
||||||
applicationContext);
|
@Override
|
||||||
}
|
public void run() {
|
||||||
});
|
openCameraAttempts = 0;
|
||||||
if (!didPost) {
|
startCaptureOnCameraThread(width, height, framerate);
|
||||||
frameObserver.onCapturerStarted(false);
|
|
||||||
if (eventsHandler != null) {
|
|
||||||
eventsHandler.onCameraError("Could not post task to camera thread.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
if (!didPost) {
|
||||||
|
frameObserver.onCapturerStarted(false);
|
||||||
|
if (eventsHandler != null) {
|
||||||
|
eventsHandler.onCameraError("Could not post task to camera thread.");
|
||||||
|
}
|
||||||
|
isCameraRunning.set(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startCaptureOnCameraThread(
|
private void startCaptureOnCameraThread(final int width, final int height, final int framerate) {
|
||||||
final int width, final int height, final int framerate, final CapturerObserver frameObserver,
|
checkIsOnCameraThread();
|
||||||
final Context applicationContext) {
|
if (!isCameraRunning.get()) {
|
||||||
synchronized (handlerLock) {
|
Logging.e(TAG, "startCaptureOnCameraThread: Camera is stopped");
|
||||||
if (cameraThreadHandler == null) {
|
return;
|
||||||
Logging.e(TAG, "startCaptureOnCameraThread: Camera is stopped");
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
checkIsOnCameraThread();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (camera != null) {
|
if (camera != null) {
|
||||||
Logging.e(TAG, "startCaptureOnCameraThread: Camera has already been started.");
|
Logging.e(TAG, "startCaptureOnCameraThread: Camera has already been started.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.applicationContext = applicationContext;
|
|
||||||
this.frameObserver = frameObserver;
|
|
||||||
this.firstFrameReported = false;
|
this.firstFrameReported = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -337,9 +339,9 @@ public class VideoCapturerAndroid implements
|
|||||||
if (openCameraAttempts < MAX_OPEN_CAMERA_ATTEMPTS) {
|
if (openCameraAttempts < MAX_OPEN_CAMERA_ATTEMPTS) {
|
||||||
Logging.e(TAG, "Camera.open failed, retrying", e);
|
Logging.e(TAG, "Camera.open failed, retrying", e);
|
||||||
maybePostDelayedOnCameraThread(OPEN_CAMERA_DELAY_MS, new Runnable() {
|
maybePostDelayedOnCameraThread(OPEN_CAMERA_DELAY_MS, new Runnable() {
|
||||||
@Override public void run() {
|
@Override
|
||||||
startCaptureOnCameraThread(width, height, framerate, frameObserver,
|
public void run() {
|
||||||
applicationContext);
|
startCaptureOnCameraThread(width, height, framerate);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@ -373,13 +375,10 @@ public class VideoCapturerAndroid implements
|
|||||||
|
|
||||||
// (Re)start preview with the closest supported format to |width| x |height| @ |framerate|.
|
// (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(int width, int height, int framerate) {
|
||||||
synchronized (handlerLock) {
|
checkIsOnCameraThread();
|
||||||
if (cameraThreadHandler == null || camera == null) {
|
if (!isCameraRunning.get() || camera == null) {
|
||||||
Logging.e(TAG, "startPreviewOnCameraThread: Camera is stopped");
|
Logging.e(TAG, "startPreviewOnCameraThread: Camera is stopped");
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
checkIsOnCameraThread();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Logging.d(
|
Logging.d(
|
||||||
TAG, "startPreviewOnCameraThread requested: " + width + "x" + height + "@" + framerate);
|
TAG, "startPreviewOnCameraThread requested: " + width + "x" + height + "@" + framerate);
|
||||||
@ -489,13 +488,7 @@ public class VideoCapturerAndroid implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void stopCaptureOnCameraThread(boolean stopHandler) {
|
private void stopCaptureOnCameraThread(boolean stopHandler) {
|
||||||
synchronized (handlerLock) {
|
checkIsOnCameraThread();
|
||||||
if (cameraThreadHandler == null) {
|
|
||||||
Logging.e(TAG, "stopCaptureOnCameraThread: Camera is stopped");
|
|
||||||
} else {
|
|
||||||
checkIsOnCameraThread();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Logging.d(TAG, "stopCaptureOnCameraThread");
|
Logging.d(TAG, "stopCaptureOnCameraThread");
|
||||||
// Note that the camera might still not be started here if startCaptureOnCameraThread failed
|
// Note that the camera might still not be started here if startCaptureOnCameraThread failed
|
||||||
// and we posted a retry.
|
// and we posted a retry.
|
||||||
@ -505,21 +498,15 @@ public class VideoCapturerAndroid implements
|
|||||||
surfaceHelper.stopListening();
|
surfaceHelper.stopListening();
|
||||||
}
|
}
|
||||||
if (stopHandler) {
|
if (stopHandler) {
|
||||||
synchronized (handlerLock) {
|
// Clear the cameraThreadHandler first, in case stopPreview or
|
||||||
// Clear the cameraThreadHandler first, in case stopPreview or
|
// other driver code deadlocks. Deadlock in
|
||||||
// other driver code deadlocks. Deadlock in
|
// android.hardware.Camera._stopPreview(Native Method) has
|
||||||
// android.hardware.Camera._stopPreview(Native Method) has
|
// been observed on Nexus 5 (hammerhead), OS version LMY48I.
|
||||||
// been observed on Nexus 5 (hammerhead), OS version LMY48I.
|
// The camera might post another one or two preview frames
|
||||||
// The camera might post another one or two preview frames
|
// before stopped, so we have to check |isCameraRunning|.
|
||||||
// before stopped, so we have to check for a null
|
// Remove all pending Runnables posted from |this|.
|
||||||
// cameraThreadHandler in our handler. Remove all pending
|
isCameraRunning.set(false);
|
||||||
// Runnables posted from |this|.
|
cameraThreadHandler.removeCallbacksAndMessages(this /* token */);
|
||||||
if (cameraThreadHandler != null) {
|
|
||||||
cameraThreadHandler.removeCallbacksAndMessages(this /* token */);
|
|
||||||
cameraThreadHandler = null;
|
|
||||||
}
|
|
||||||
surfaceHelper = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (cameraStatistics != null) {
|
if (cameraStatistics != null) {
|
||||||
cameraStatistics.release();
|
cameraStatistics.release();
|
||||||
@ -545,33 +532,22 @@ public class VideoCapturerAndroid implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void switchCameraOnCameraThread() {
|
private void switchCameraOnCameraThread() {
|
||||||
synchronized (handlerLock) {
|
checkIsOnCameraThread();
|
||||||
if (cameraThreadHandler == null) {
|
if (!isCameraRunning.get()) {
|
||||||
Logging.e(TAG, "switchCameraOnCameraThread: Camera is stopped");
|
Logging.e(TAG, "switchCameraOnCameraThread: Camera is stopped");
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
checkIsOnCameraThread();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Logging.d(TAG, "switchCameraOnCameraThread");
|
Logging.d(TAG, "switchCameraOnCameraThread");
|
||||||
stopCaptureOnCameraThread(false /* stopHandler */);
|
stopCaptureOnCameraThread(false /* stopHandler */);
|
||||||
synchronized (cameraIdLock) {
|
synchronized (cameraIdLock) {
|
||||||
id = (id + 1) % android.hardware.Camera.getNumberOfCameras();
|
id = (id + 1) % android.hardware.Camera.getNumberOfCameras();
|
||||||
}
|
}
|
||||||
startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramerate, frameObserver,
|
startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramerate);
|
||||||
applicationContext);
|
|
||||||
Logging.d(TAG, "switchCameraOnCameraThread done");
|
Logging.d(TAG, "switchCameraOnCameraThread done");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onOutputFormatRequestOnCameraThread(int width, int height, int framerate) {
|
private void onOutputFormatRequestOnCameraThread(int width, int height, int framerate) {
|
||||||
synchronized (handlerLock) {
|
checkIsOnCameraThread();
|
||||||
if (cameraThreadHandler == null || camera == null) {
|
|
||||||
Logging.e(TAG, "onOutputFormatRequestOnCameraThread: Camera is stopped");
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
checkIsOnCameraThread();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Logging.d(TAG, "onOutputFormatRequestOnCameraThread: " + width + "x" + height +
|
Logging.d(TAG, "onOutputFormatRequestOnCameraThread: " + width + "x" + height +
|
||||||
"@" + framerate);
|
"@" + framerate);
|
||||||
frameObserver.onOutputFormatRequest(width, height, framerate);
|
frameObserver.onOutputFormatRequest(width, height, framerate);
|
||||||
@ -611,13 +587,10 @@ public class VideoCapturerAndroid implements
|
|||||||
// Called on cameraThread so must not "synchronized".
|
// Called on cameraThread so must not "synchronized".
|
||||||
@Override
|
@Override
|
||||||
public void onPreviewFrame(byte[] data, android.hardware.Camera callbackCamera) {
|
public void onPreviewFrame(byte[] data, android.hardware.Camera callbackCamera) {
|
||||||
synchronized (handlerLock) {
|
checkIsOnCameraThread();
|
||||||
if (cameraThreadHandler == null) {
|
if (!isCameraRunning.get()) {
|
||||||
Logging.e(TAG, "onPreviewFrame: Camera is stopped");
|
Logging.e(TAG, "onPreviewFrame: Camera is stopped");
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
checkIsOnCameraThread();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!queuedBuffers.contains(data)) {
|
if (!queuedBuffers.contains(data)) {
|
||||||
// |data| is an old invalid buffer.
|
// |data| is an old invalid buffer.
|
||||||
@ -644,14 +617,11 @@ public class VideoCapturerAndroid implements
|
|||||||
@Override
|
@Override
|
||||||
public void onTextureFrameAvailable(
|
public void onTextureFrameAvailable(
|
||||||
int oesTextureId, float[] transformMatrix, long timestampNs) {
|
int oesTextureId, float[] transformMatrix, long timestampNs) {
|
||||||
synchronized (handlerLock) {
|
checkIsOnCameraThread();
|
||||||
if (cameraThreadHandler == null) {
|
if (!isCameraRunning.get()) {
|
||||||
Logging.e(TAG, "onTextureFrameAvailable: Camera is stopped");
|
Logging.e(TAG, "onTextureFrameAvailable: Camera is stopped");
|
||||||
surfaceHelper.returnTextureFrame();
|
surfaceHelper.returnTextureFrame();
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
checkIsOnCameraThread();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (eventsHandler != null && !firstFrameReported) {
|
if (eventsHandler != null && !firstFrameReported) {
|
||||||
eventsHandler.onFirstFrameAvailable();
|
eventsHandler.onFirstFrameAvailable();
|
||||||
|
|||||||
@ -43,6 +43,21 @@ AndroidVideoCapturerJni::AndroidVideoCapturerJni(JNIEnv* jni,
|
|||||||
jni, "Camera SurfaceTextureHelper", j_egl_context)),
|
jni, "Camera SurfaceTextureHelper", j_egl_context)),
|
||||||
capturer_(nullptr) {
|
capturer_(nullptr) {
|
||||||
LOG(LS_INFO) << "AndroidVideoCapturerJni ctor";
|
LOG(LS_INFO) << "AndroidVideoCapturerJni ctor";
|
||||||
|
jobject j_frame_observer =
|
||||||
|
jni->NewObject(*j_observer_class_,
|
||||||
|
GetMethodID(jni, *j_observer_class_, "<init>", "(J)V"),
|
||||||
|
jlongFromPointer(this));
|
||||||
|
CHECK_EXCEPTION(jni) << "error during NewObject";
|
||||||
|
jni->CallVoidMethod(
|
||||||
|
*j_video_capturer_,
|
||||||
|
GetMethodID(jni, *j_video_capturer_class_, "initialize",
|
||||||
|
"(Lorg/webrtc/SurfaceTextureHelper;Landroid/content/"
|
||||||
|
"Context;Lorg/webrtc/VideoCapturer$CapturerObserver;)V"),
|
||||||
|
surface_texture_helper_
|
||||||
|
? surface_texture_helper_->GetJavaSurfaceTextureHelper()
|
||||||
|
: nullptr,
|
||||||
|
application_context_, j_frame_observer);
|
||||||
|
CHECK_EXCEPTION(jni) << "error during VideoCapturer.initialize()";
|
||||||
thread_checker_.DetachFromThread();
|
thread_checker_.DetachFromThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,22 +80,9 @@ void AndroidVideoCapturerJni::Start(int width, int height, int framerate,
|
|||||||
capturer_ = capturer;
|
capturer_ = capturer;
|
||||||
invoker_.reset(new rtc::GuardedAsyncInvoker());
|
invoker_.reset(new rtc::GuardedAsyncInvoker());
|
||||||
}
|
}
|
||||||
jobject j_frame_observer =
|
jmethodID m =
|
||||||
jni()->NewObject(*j_observer_class_,
|
GetMethodID(jni(), *j_video_capturer_class_, "startCapture", "(III)V");
|
||||||
GetMethodID(jni(), *j_observer_class_, "<init>", "(J)V"),
|
jni()->CallVoidMethod(*j_video_capturer_, m, width, height, framerate);
|
||||||
jlongFromPointer(this));
|
|
||||||
CHECK_EXCEPTION(jni()) << "error during NewObject";
|
|
||||||
|
|
||||||
jmethodID m = GetMethodID(
|
|
||||||
jni(), *j_video_capturer_class_, "startCapture",
|
|
||||||
"(IIILorg/webrtc/SurfaceTextureHelper;Landroid/content/Context;"
|
|
||||||
"Lorg/webrtc/VideoCapturer$CapturerObserver;)V");
|
|
||||||
jni()->CallVoidMethod(
|
|
||||||
*j_video_capturer_, m, width, height, framerate,
|
|
||||||
surface_texture_helper_
|
|
||||||
? surface_texture_helper_->GetJavaSurfaceTextureHelper()
|
|
||||||
: nullptr,
|
|
||||||
application_context_, j_frame_observer);
|
|
||||||
CHECK_EXCEPTION(jni()) << "error during VideoCapturer.startCapture";
|
CHECK_EXCEPTION(jni()) << "error during VideoCapturer.startCapture";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -307,19 +307,23 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Internal helper methods
|
// Internal helper methods
|
||||||
private CapturerInstance createCapturer(String name) {
|
private CapturerInstance createCapturer(String name, boolean initialize) {
|
||||||
CapturerInstance instance = new CapturerInstance();
|
CapturerInstance instance = new CapturerInstance();
|
||||||
instance.cameraEvents = new CameraEvents();
|
instance.cameraEvents = new CameraEvents();
|
||||||
instance.capturer = testObjectFactory.createCapturer(name, instance.cameraEvents);
|
instance.capturer = testObjectFactory.createCapturer(name, instance.cameraEvents);
|
||||||
instance.surfaceTextureHelper = SurfaceTextureHelper.create(
|
instance.surfaceTextureHelper = SurfaceTextureHelper.create(
|
||||||
"SurfaceTextureHelper test" /* threadName */, null /* sharedContext */);
|
"SurfaceTextureHelper test" /* threadName */, null /* sharedContext */);
|
||||||
instance.observer = new FakeCapturerObserver();
|
instance.observer = new FakeCapturerObserver();
|
||||||
|
if (initialize) {
|
||||||
|
instance.capturer.initialize(
|
||||||
|
instance.surfaceTextureHelper, testObjectFactory.getAppContext(), instance.observer);
|
||||||
|
}
|
||||||
instance.supportedFormats = instance.capturer.getSupportedFormats();
|
instance.supportedFormats = instance.capturer.getSupportedFormats();
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CapturerInstance createCapturer() {
|
private CapturerInstance createCapturer(boolean initialize) {
|
||||||
return createCapturer("");
|
return createCapturer("", initialize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startCapture(CapturerInstance instance) {
|
private void startCapture(CapturerInstance instance) {
|
||||||
@ -330,8 +334,7 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
final CameraEnumerationAndroid.CaptureFormat format =
|
final CameraEnumerationAndroid.CaptureFormat format =
|
||||||
instance.supportedFormats.get(formatIndex);
|
instance.supportedFormats.get(formatIndex);
|
||||||
|
|
||||||
instance.capturer.startCapture(format.width, format.height, format.framerate.max,
|
instance.capturer.startCapture(format.width, format.height, format.framerate.max);
|
||||||
instance.surfaceTextureHelper, testObjectFactory.getAppContext(), instance.observer);
|
|
||||||
instance.format = format;
|
instance.format = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,7 +393,7 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final CapturerInstance capturerInstance = createCapturer(name);
|
final CapturerInstance capturerInstance = createCapturer(name, false /* initialize */);
|
||||||
final VideoTrackWithRenderer videoTrackWithRenderer =
|
final VideoTrackWithRenderer videoTrackWithRenderer =
|
||||||
createVideoTrackWithRenderer(capturerInstance.capturer);
|
createVideoTrackWithRenderer(capturerInstance.capturer);
|
||||||
assertTrue(videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender() > 0);
|
assertTrue(videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender() > 0);
|
||||||
@ -400,12 +403,12 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
|
|
||||||
// Test methods
|
// Test methods
|
||||||
public void createCapturerAndDispose() {
|
public void createCapturerAndDispose() {
|
||||||
disposeCapturer(createCapturer());
|
disposeCapturer(createCapturer(true /* initialize */));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createNonExistingCamera() {
|
public void createNonExistingCamera() {
|
||||||
try {
|
try {
|
||||||
disposeCapturer(createCapturer("non-existing camera"));
|
disposeCapturer(createCapturer("non-existing camera", false /* initialize */));
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -432,7 +435,7 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final CapturerInstance capturerInstance = createCapturer();
|
final CapturerInstance capturerInstance = createCapturer(false /* initialize */);
|
||||||
final VideoTrackWithRenderer videoTrackWithRenderer =
|
final VideoTrackWithRenderer videoTrackWithRenderer =
|
||||||
createVideoTrackWithRenderer(capturerInstance.capturer);
|
createVideoTrackWithRenderer(capturerInstance.capturer);
|
||||||
|
|
||||||
@ -463,7 +466,7 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void cameraEventsInvoked() throws InterruptedException {
|
public void cameraEventsInvoked() throws InterruptedException {
|
||||||
final CapturerInstance capturerInstance = createCapturer();
|
final CapturerInstance capturerInstance = createCapturer(true /* initialize */);
|
||||||
startCapture(capturerInstance);
|
startCapture(capturerInstance);
|
||||||
// Make sure camera is started and first frame is received and then stop it.
|
// Make sure camera is started and first frame is received and then stop it.
|
||||||
assertTrue(capturerInstance.observer.waitForCapturerToStart());
|
assertTrue(capturerInstance.observer.waitForCapturerToStart());
|
||||||
@ -476,7 +479,7 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void cameraCallsAfterStop() throws InterruptedException {
|
public void cameraCallsAfterStop() throws InterruptedException {
|
||||||
final CapturerInstance capturerInstance = createCapturer();
|
final CapturerInstance capturerInstance = createCapturer(true /* initialize */);
|
||||||
startCapture(capturerInstance);
|
startCapture(capturerInstance);
|
||||||
// Make sure camera is started and then stop it.
|
// Make sure camera is started and then stop it.
|
||||||
assertTrue(capturerInstance.observer.waitForCapturerToStart());
|
assertTrue(capturerInstance.observer.waitForCapturerToStart());
|
||||||
@ -492,7 +495,7 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void stopRestartVideoSource() throws InterruptedException {
|
public void stopRestartVideoSource() throws InterruptedException {
|
||||||
final CapturerInstance capturerInstance = createCapturer();
|
final CapturerInstance capturerInstance = createCapturer(false /* initialize */);
|
||||||
final VideoTrackWithRenderer videoTrackWithRenderer =
|
final VideoTrackWithRenderer videoTrackWithRenderer =
|
||||||
createVideoTrackWithRenderer(capturerInstance.capturer);
|
createVideoTrackWithRenderer(capturerInstance.capturer);
|
||||||
|
|
||||||
@ -511,7 +514,7 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void startStopWithDifferentResolutions() throws InterruptedException {
|
public void startStopWithDifferentResolutions() throws InterruptedException {
|
||||||
final CapturerInstance capturerInstance = createCapturer();
|
final CapturerInstance capturerInstance = createCapturer(true /* initialize */);
|
||||||
|
|
||||||
for(int i = 0; i < 3 ; ++i) {
|
for(int i = 0; i < 3 ; ++i) {
|
||||||
startCapture(capturerInstance, i);
|
startCapture(capturerInstance, i);
|
||||||
@ -544,7 +547,7 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void returnBufferLate() throws InterruptedException {
|
public void returnBufferLate() throws InterruptedException {
|
||||||
final CapturerInstance capturerInstance = createCapturer();
|
final CapturerInstance capturerInstance = createCapturer(true /* initialize */);
|
||||||
startCapture(capturerInstance);
|
startCapture(capturerInstance);
|
||||||
assertTrue(capturerInstance.observer.waitForCapturerToStart());
|
assertTrue(capturerInstance.observer.waitForCapturerToStart());
|
||||||
|
|
||||||
@ -568,7 +571,7 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
|
|
||||||
public void returnBufferLateEndToEnd()
|
public void returnBufferLateEndToEnd()
|
||||||
throws InterruptedException {
|
throws InterruptedException {
|
||||||
final CapturerInstance capturerInstance = createCapturer();
|
final CapturerInstance capturerInstance = createCapturer(false /* initialize */);
|
||||||
final VideoTrackWithRenderer videoTrackWithRenderer =
|
final VideoTrackWithRenderer videoTrackWithRenderer =
|
||||||
createVideoTrackWithFakeAsyncRenderer(capturerInstance.capturer);
|
createVideoTrackWithFakeAsyncRenderer(capturerInstance.capturer);
|
||||||
// Wait for at least one frame that has not been returned.
|
// Wait for at least one frame that has not been returned.
|
||||||
@ -596,7 +599,7 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void cameraFreezedEventOnBufferStarvation() throws InterruptedException {
|
public void cameraFreezedEventOnBufferStarvation() throws InterruptedException {
|
||||||
final CapturerInstance capturerInstance = createCapturer();
|
final CapturerInstance capturerInstance = createCapturer(true /* initialize */);
|
||||||
startCapture(capturerInstance);
|
startCapture(capturerInstance);
|
||||||
// Make sure camera is started.
|
// Make sure camera is started.
|
||||||
assertTrue(capturerInstance.observer.waitForCapturerToStart());
|
assertTrue(capturerInstance.observer.waitForCapturerToStart());
|
||||||
@ -610,7 +613,7 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void scaleCameraOutput() throws InterruptedException {
|
public void scaleCameraOutput() throws InterruptedException {
|
||||||
final CapturerInstance capturerInstance = createCapturer();
|
final CapturerInstance capturerInstance = createCapturer(false /* initialize */);
|
||||||
final VideoTrackWithRenderer videoTrackWithRenderer =
|
final VideoTrackWithRenderer videoTrackWithRenderer =
|
||||||
createVideoTrackWithRenderer(capturerInstance.capturer);
|
createVideoTrackWithRenderer(capturerInstance.capturer);
|
||||||
assertTrue(videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender() > 0);
|
assertTrue(videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender() > 0);
|
||||||
@ -644,7 +647,7 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
public void startWhileCameraIsAlreadyOpen() throws InterruptedException {
|
public void startWhileCameraIsAlreadyOpen() throws InterruptedException {
|
||||||
final String cameraName = testObjectFactory.getNameOfBackFacingDevice();
|
final String cameraName = testObjectFactory.getNameOfBackFacingDevice();
|
||||||
// At this point camera is not actually opened.
|
// At this point camera is not actually opened.
|
||||||
final CapturerInstance capturerInstance = createCapturer(cameraName);
|
final CapturerInstance capturerInstance = createCapturer(cameraName, true /* initialize */);
|
||||||
|
|
||||||
final Object competingCamera = testObjectFactory.rawOpenCamera(cameraName);
|
final Object competingCamera = testObjectFactory.rawOpenCamera(cameraName);
|
||||||
|
|
||||||
@ -665,7 +668,7 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
public void startWhileCameraIsAlreadyOpenAndCloseCamera() throws InterruptedException {
|
public void startWhileCameraIsAlreadyOpenAndCloseCamera() throws InterruptedException {
|
||||||
final String cameraName = testObjectFactory.getNameOfBackFacingDevice();
|
final String cameraName = testObjectFactory.getNameOfBackFacingDevice();
|
||||||
// At this point camera is not actually opened.
|
// At this point camera is not actually opened.
|
||||||
final CapturerInstance capturerInstance = createCapturer(cameraName);
|
final CapturerInstance capturerInstance = createCapturer(cameraName, false /* initialize */);
|
||||||
|
|
||||||
Logging.d(TAG, "startWhileCameraIsAlreadyOpenAndCloseCamera: Opening competing camera.");
|
Logging.d(TAG, "startWhileCameraIsAlreadyOpenAndCloseCamera: Opening competing camera.");
|
||||||
final Object competingCamera = testObjectFactory.rawOpenCamera(cameraName);
|
final Object competingCamera = testObjectFactory.rawOpenCamera(cameraName);
|
||||||
@ -689,7 +692,7 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
public void startWhileCameraIsAlreadyOpenAndStop() throws InterruptedException {
|
public void startWhileCameraIsAlreadyOpenAndStop() throws InterruptedException {
|
||||||
final String cameraName = testObjectFactory.getNameOfBackFacingDevice();
|
final String cameraName = testObjectFactory.getNameOfBackFacingDevice();
|
||||||
// At this point camera is not actually opened.
|
// At this point camera is not actually opened.
|
||||||
final CapturerInstance capturerInstance = createCapturer(cameraName);
|
final CapturerInstance capturerInstance = createCapturer(cameraName, true /* initialize */);
|
||||||
|
|
||||||
final Object competingCamera = testObjectFactory.rawOpenCamera(cameraName);
|
final Object competingCamera = testObjectFactory.rawOpenCamera(cameraName);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user