diff --git a/talk/app/webrtc/androidtests/src/org/webrtc/VideoCapturerAndroidTest.java b/talk/app/webrtc/androidtests/src/org/webrtc/VideoCapturerAndroidTest.java index dc78ddd657..d14b5cd359 100644 --- a/talk/app/webrtc/androidtests/src/org/webrtc/VideoCapturerAndroidTest.java +++ b/talk/app/webrtc/androidtests/src/org/webrtc/VideoCapturerAndroidTest.java @@ -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 { diff --git a/talk/app/webrtc/androidtests/src/org/webrtc/VideoCapturerAndroidTestFixtures.java b/talk/app/webrtc/androidtests/src/org/webrtc/VideoCapturerAndroidTestFixtures.java index 7e24f8c0dc..11b3ce98a0 100644 --- a/talk/app/webrtc/androidtests/src/org/webrtc/VideoCapturerAndroidTestFixtures.java +++ b/talk/app/webrtc/androidtests/src/org/webrtc/VideoCapturerAndroidTestFixtures.java @@ -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 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 formats = capturer.getSupportedFormats(); diff --git a/talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java b/talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java index 50fbdf9f75..00ac2e50fa 100644 --- a/talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java +++ b/talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java @@ -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,