Add new Android camera events.

Add events to track when camera is requested to open,
when first camera frame is available and when camera is
closed.

BUG=b/24271359
R=perkj@webrtc.org

Review URL: https://codereview.webrtc.org/1398793005 .

Cr-Commit-Position: refs/heads/master@{#10306}
This commit is contained in:
Alex Glaznev
2015-10-16 13:10:14 -07:00
parent be16f79818
commit 9781152e04
3 changed files with 95 additions and 18 deletions

View File

@ -170,6 +170,15 @@ public class VideoCapturerAndroidTest extends ActivityTestCase {
VideoCapturerAndroidTestFixtures.switchCamera(capturer);
}
@MediumTest
public void testCameraEvents() throws InterruptedException {
VideoCapturerAndroidTestFixtures.CameraEvents cameraEvents =
VideoCapturerAndroidTestFixtures.createCameraEvents();
VideoCapturerAndroid capturer = VideoCapturerAndroid.create("", cameraEvents);
VideoCapturerAndroidTestFixtures.cameraEventsInvoked(
capturer, cameraEvents, getInstrumentation().getContext());
}
@MediumTest
// Test what happens when attempting to call e.g. switchCamera() after camera has been stopped.
public void testCameraCallsAfterStop() throws InterruptedException {

View File

@ -170,6 +170,32 @@ public class VideoCapturerAndroidTestFixtures {
}
}
static class CameraEvents implements
VideoCapturerAndroid.CameraEventsHandler {
public boolean onCameraOpeningCalled;
public boolean onFirstFrameAvailableCalled;
@Override
public void onCameraError(String errorDescription) { }
@Override
public void onCameraOpening(int cameraId) {
onCameraOpeningCalled = true;
}
@Override
public void onFirstFrameAvailable() {
onFirstFrameAvailableCalled = true;
}
@Override
public void onCameraClosed() { }
}
static public CameraEvents createCameraEvents() {
return new CameraEvents();
}
// Return true if the device under test have at least two cameras.
@SuppressWarnings("deprecation")
static public boolean HaveTwoCameras() {
@ -237,6 +263,28 @@ public class VideoCapturerAndroidTestFixtures {
assertTrue(capturer.isReleased());
}
static public void cameraEventsInvoked(VideoCapturerAndroid capturer, CameraEvents events,
Context appContext) throws InterruptedException {
final List<CaptureFormat> formats = capturer.getSupportedFormats();
final CameraEnumerationAndroid.CaptureFormat format = formats.get(0);
final FakeCapturerObserver observer = new FakeCapturerObserver();
capturer.startCapture(format.width, format.height, format.maxFramerate,
appContext, observer);
// Make sure camera is started and first frame is received and then stop it.
assertTrue(observer.WaitForCapturerToStart());
observer.WaitForNextCapturedFrame();
capturer.stopCapture();
for (long timeStamp : observer.getCopyAndResetListOftimeStamps()) {
capturer.returnBuffer(timeStamp);
}
capturer.dispose();
assertTrue(capturer.isReleased());
assertTrue(events.onCameraOpeningCalled);
assertTrue(events.onFirstFrameAvailableCalled);
}
static public void cameraCallsAfterStop(
VideoCapturerAndroid capturer, Context appContext) throws InterruptedException {
final List<CaptureFormat> formats = capturer.getSupportedFormats();

View File

@ -31,7 +31,6 @@ import android.content.Context;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.opengl.EGL14;
import android.opengl.EGLContext;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
@ -93,7 +92,8 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
private final Object pendingCameraSwitchLock = new Object();
private volatile boolean pendingCameraSwitch;
private CapturerObserver frameObserver = null;
private final CameraErrorHandler errorHandler;
private final CameraEventsHandler eventsHandler;
private boolean firstFrameReported;
private final boolean isCapturingToTexture;
// |cameraGlTexture| is used with setPreviewTexture if the capturer is capturing to
// ByteBuffers.
@ -120,8 +120,8 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
errorMessage = "Camera error: " + error;
}
Logging.e(TAG, errorMessage);
if (errorHandler != null) {
errorHandler.onCameraError(errorMessage);
if (eventsHandler != null) {
eventsHandler.onCameraError(errorMessage);
}
}
};
@ -138,8 +138,8 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
". Pending buffers: " + cameraStatistics.pendingFramesTimeStamps());
if (cameraFramesCount == 0) {
Logging.e(TAG, "Camera freezed.");
if (errorHandler != null) {
errorHandler.onCameraError("Camera failure.");
if (eventsHandler != null) {
eventsHandler.onCameraError("Camera failure.");
}
} else {
cameraThreadHandler.postDelayed(this, CAMERA_OBSERVER_PERIOD_MS);
@ -194,10 +194,19 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
}
}
// Camera error handler - invoked when camera stops receiving frames
// or any camera exception happens on camera thread.
public static interface CameraErrorHandler {
public void onCameraError(String errorDescription);
public static interface CameraEventsHandler {
// Camera error handler - invoked when camera stops receiving frames
// or any camera exception happens on camera thread.
void onCameraError(String errorDescription);
// Callback invoked when camera is opening.
void onCameraOpening(int cameraId);
// Callback invoked when first camera frame is available after camera is opened.
void onFirstFrameAvailable();
// Callback invoked when camera closed.
void onCameraClosed();
}
// Camera switch handler - one of these functions are invoked with the result of switchCamera().
@ -210,18 +219,18 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
}
public static VideoCapturerAndroid create(String name,
CameraErrorHandler errorHandler) {
return VideoCapturerAndroid.create(name, errorHandler, null);
CameraEventsHandler eventsHandler) {
return VideoCapturerAndroid.create(name, eventsHandler, null);
}
public static VideoCapturerAndroid create(String name,
CameraErrorHandler errorHandler, Object sharedEglContext) {
CameraEventsHandler eventsHandler, Object sharedEglContext) {
final int cameraId = lookupDeviceName(name);
if (cameraId == -1) {
return null;
}
final VideoCapturerAndroid capturer = new VideoCapturerAndroid(cameraId, errorHandler,
final VideoCapturerAndroid capturer = new VideoCapturerAndroid(cameraId, eventsHandler,
sharedEglContext);
capturer.setNativeCapturer(nativeCreateVideoCapturer(capturer));
return capturer;
@ -327,11 +336,11 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
this(cameraId, null, null);
}
private VideoCapturerAndroid(int cameraId, CameraErrorHandler errorHandler,
private VideoCapturerAndroid(int cameraId, CameraEventsHandler eventsHandler,
Object sharedContext) {
Logging.d(TAG, "VideoCapturerAndroid");
this.id = cameraId;
this.errorHandler = errorHandler;
this.eventsHandler = eventsHandler;
cameraThread = new HandlerThread(TAG);
cameraThread.start();
cameraThreadHandler = new Handler(cameraThread.getLooper());
@ -437,6 +446,10 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
try {
synchronized (cameraIdLock) {
Logging.d(TAG, "Opening camera " + id);
firstFrameReported = false;
if (eventsHandler != null) {
eventsHandler.onCameraOpening(id);
}
camera = Camera.open(id);
info = new Camera.CameraInfo();
Camera.getCameraInfo(id, info);
@ -469,8 +482,8 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
Logging.e(TAG, "startCapture failed", error);
stopCaptureOnCameraThread();
frameObserver.onCapturerStarted(false);
if (errorHandler != null) {
errorHandler.onCameraError("Camera can not be started.");
if (eventsHandler != null) {
eventsHandler.onCameraError("Camera can not be started.");
}
return;
}
@ -588,6 +601,9 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
Logging.d(TAG, "Release camera.");
camera.release();
camera = null;
if (eventsHandler != null) {
eventsHandler.onCameraClosed();
}
if (cameraGlTexture != 0) {
GLES20.glDeleteTextures(1, new int[] {cameraGlTexture}, 0);
@ -680,6 +696,10 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
final long captureTimeNs =
TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime());
if (eventsHandler != null && !firstFrameReported) {
eventsHandler.onFirstFrameAvailable();
firstFrameReported = true;
}
// Mark the frame owning |data| as used.
// Note that since data is directBuffer,