Refactor VideoCapturerAndroid tests in WebRTC.
Camera1 tests are now separated from general CameraVideoCapturer tests. Main motivation behind these changes is that Camera2 implementation can be tested using the same tests. CL also reduces code duplication on tests using textures. BUG=webrtc:5519 Review-Url: https://codereview.webrtc.org/2024843002 Cr-Commit-Position: refs/heads/master@{#13130}
This commit is contained in:
parent
1c7eef652b
commit
79ede033f6
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
package org.webrtc;
|
||||
|
||||
import org.webrtc.CameraEnumerationAndroid.CaptureFormat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.test.suitebuilder.annotation.MediumTest;
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
|
||||
public class Camera1CapturerUsingByteBufferTest extends InstrumentationTestCase {
|
||||
static final String TAG = "Camera1CapturerUsingByteBufferTest";
|
||||
|
||||
private class TestObjectFactory
|
||||
implements CameraVideoCapturerTestFixtures.TestObjectFactory {
|
||||
@Override
|
||||
public CameraVideoCapturer createCapturer(
|
||||
String name,
|
||||
CameraVideoCapturer.CameraEventsHandler eventsHandler) {
|
||||
return new VideoCapturerAndroid(name, eventsHandler, isCapturingToTexture());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNameOfFrontFacingDevice() {
|
||||
return CameraEnumerationAndroid.getNameOfFrontFacingDevice();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNameOfBackFacingDevice() {
|
||||
return CameraEnumerationAndroid.getNameOfBackFacingDevice();
|
||||
}
|
||||
|
||||
// Return true if the device under test have at least two cameras.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public boolean haveTwoCameras() {
|
||||
return (android.hardware.Camera.getNumberOfCameras() >= 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCapturingToTexture() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getAppContext() {
|
||||
return getInstrumentation().getTargetContext();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public Object rawOpenCamera(String cameraName) {
|
||||
return android.hardware.Camera.open(CameraEnumerationAndroid.getCameraIndex(cameraName));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void rawCloseCamera(Object camera) {
|
||||
((android.hardware.Camera) camera).release();
|
||||
}
|
||||
}
|
||||
|
||||
private CameraVideoCapturerTestFixtures fixtures;
|
||||
|
||||
@Override
|
||||
protected void setUp() {
|
||||
fixtures = new CameraVideoCapturerTestFixtures(new TestObjectFactory());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() {
|
||||
fixtures.dispose();
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testCreateAndDispose() {
|
||||
fixtures.createCapturerAndDispose();
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testCreateNonExistingCamera() {
|
||||
fixtures.createNonExistingCamera();
|
||||
}
|
||||
|
||||
// This test that the camera can be started and that the frames are forwarded
|
||||
// to a Java video renderer using a "default" capturer.
|
||||
// It tests both the Java and the C++ layer.
|
||||
@MediumTest
|
||||
public void testCreateCapturerAndRender() throws InterruptedException {
|
||||
fixtures.createCapturerAndRender();
|
||||
}
|
||||
|
||||
// This test that the camera can be started and that the frames are forwarded
|
||||
// to a Java video renderer using the front facing video capturer.
|
||||
// It tests both the Java and the C++ layer.
|
||||
@MediumTest
|
||||
public void testStartFrontFacingVideoCapturer() throws InterruptedException {
|
||||
fixtures.createFrontFacingCapturerAndRender();
|
||||
}
|
||||
|
||||
// This test that the camera can be started and that the frames are forwarded
|
||||
// to a Java video renderer using the back facing video capturer.
|
||||
// It tests both the Java and the C++ layer.
|
||||
@MediumTest
|
||||
public void testStartBackFacingVideoCapturer() throws InterruptedException {
|
||||
fixtures.createBackFacingCapturerAndRender();
|
||||
}
|
||||
|
||||
// This test that the default camera can be started and that the camera can
|
||||
// later be switched to another camera.
|
||||
// It tests both the Java and the C++ layer.
|
||||
@MediumTest
|
||||
public void testSwitchVideoCapturer() throws InterruptedException {
|
||||
fixtures.switchCamera();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testCameraEvents() throws InterruptedException {
|
||||
fixtures.cameraEventsInvoked();
|
||||
}
|
||||
|
||||
// Test what happens when attempting to call e.g. switchCamera() after camera has been stopped.
|
||||
@MediumTest
|
||||
public void testCameraCallsAfterStop() throws InterruptedException {
|
||||
fixtures.cameraCallsAfterStop();
|
||||
}
|
||||
|
||||
// This test that the VideoSource that the CameraVideoCapturer is connected to can
|
||||
// be stopped and restarted. It tests both the Java and the C++ layer.
|
||||
@LargeTest
|
||||
public void testStopRestartVideoSource() throws InterruptedException {
|
||||
fixtures.stopRestartVideoSource();
|
||||
}
|
||||
|
||||
// This test that the camera can be started at different resolutions.
|
||||
// It does not test or use the C++ layer.
|
||||
@LargeTest
|
||||
public void testStartStopWithDifferentResolutions() throws InterruptedException {
|
||||
fixtures.startStopWithDifferentResolutions();
|
||||
}
|
||||
|
||||
// This test what happens if buffers are returned after the capturer have
|
||||
// been stopped and restarted. It does not test or use the C++ layer.
|
||||
@LargeTest
|
||||
public void testReturnBufferLate() throws InterruptedException {
|
||||
fixtures.returnBufferLateEndToEnd();
|
||||
}
|
||||
|
||||
// This test that we can capture frames, keep the frames in a local renderer, stop capturing,
|
||||
// and then return the frames. The difference between the test testReturnBufferLate() is that we
|
||||
// also test the JNI and C++ AndroidVideoCapturer parts.
|
||||
@MediumTest
|
||||
public void testReturnBufferLateEndToEnd() throws InterruptedException {
|
||||
fixtures.returnBufferLateEndToEnd();
|
||||
}
|
||||
|
||||
// This test that frames forwarded to a renderer is scaled if onOutputFormatRequest is
|
||||
// called. This test both Java and C++ parts of of the stack.
|
||||
@MediumTest
|
||||
public void testScaleCameraOutput() throws InterruptedException {
|
||||
fixtures.scaleCameraOutput();
|
||||
}
|
||||
|
||||
// This test that an error is reported if the camera is already opened
|
||||
// when CameraVideoCapturer is started.
|
||||
@LargeTest
|
||||
public void testStartWhileCameraIsAlreadyOpen() throws InterruptedException {
|
||||
fixtures.startWhileCameraIsAlreadyOpen();
|
||||
}
|
||||
|
||||
// This test that CameraVideoCapturer can be started, even if the camera is already opened
|
||||
// if the camera is closed while CameraVideoCapturer is re-trying to start.
|
||||
@LargeTest
|
||||
public void testStartWhileCameraIsAlreadyOpenAndCloseCamera() throws InterruptedException {
|
||||
fixtures.startWhileCameraIsAlreadyOpenAndCloseCamera();
|
||||
}
|
||||
|
||||
// This test that CameraVideoCapturer.stop can be called while CameraVideoCapturer is
|
||||
// re-trying to start.
|
||||
@MediumTest
|
||||
public void testStartWhileCameraIsAlreadyOpenAndStop() throws InterruptedException {
|
||||
fixtures.startWhileCameraIsAlreadyOpenAndStop();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
package org.webrtc;
|
||||
|
||||
import org.webrtc.CameraEnumerationAndroid.CaptureFormat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.test.suitebuilder.annotation.MediumTest;
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
|
||||
public class Camera1CapturerUsingTextureTest extends InstrumentationTestCase {
|
||||
static final String TAG = "Camera1CapturerUsingTextureTest";
|
||||
|
||||
private class TestObjectFactory
|
||||
implements CameraVideoCapturerTestFixtures.TestObjectFactory {
|
||||
@Override
|
||||
public CameraVideoCapturer createCapturer(
|
||||
String name,
|
||||
CameraVideoCapturer.CameraEventsHandler eventsHandler) {
|
||||
return new VideoCapturerAndroid(name, eventsHandler, isCapturingToTexture());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNameOfFrontFacingDevice() {
|
||||
return CameraEnumerationAndroid.getNameOfFrontFacingDevice();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNameOfBackFacingDevice() {
|
||||
return CameraEnumerationAndroid.getNameOfBackFacingDevice();
|
||||
}
|
||||
|
||||
// Return true if the device under test have at least two cameras.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public boolean haveTwoCameras() {
|
||||
return (android.hardware.Camera.getNumberOfCameras() >= 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCapturingToTexture() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getAppContext() {
|
||||
return getInstrumentation().getTargetContext();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public Object rawOpenCamera(String cameraName) {
|
||||
return android.hardware.Camera.open(CameraEnumerationAndroid.getCameraIndex(cameraName));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void rawCloseCamera(Object camera) {
|
||||
((android.hardware.Camera) camera).release();
|
||||
}
|
||||
}
|
||||
|
||||
private CameraVideoCapturerTestFixtures fixtures;
|
||||
|
||||
@Override
|
||||
protected void setUp() {
|
||||
fixtures = new CameraVideoCapturerTestFixtures(new TestObjectFactory());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() {
|
||||
fixtures.dispose();
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testCreateAndDispose() {
|
||||
fixtures.createCapturerAndDispose();
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testCreateNonExistingCamera() {
|
||||
fixtures.createNonExistingCamera();
|
||||
}
|
||||
|
||||
// This test that the camera can be started and that the frames are forwarded
|
||||
// to a Java video renderer using a "default" capturer.
|
||||
// It tests both the Java and the C++ layer.
|
||||
@MediumTest
|
||||
public void testCreateCapturerAndRender() throws InterruptedException {
|
||||
fixtures.createCapturerAndRender();
|
||||
}
|
||||
|
||||
// This test that the camera can be started and that the frames are forwarded
|
||||
// to a Java video renderer using the front facing video capturer.
|
||||
// It tests both the Java and the C++ layer.
|
||||
@MediumTest
|
||||
public void testStartFrontFacingVideoCapturer() throws InterruptedException {
|
||||
fixtures.createFrontFacingCapturerAndRender();
|
||||
}
|
||||
|
||||
// This test that the camera can be started and that the frames are forwarded
|
||||
// to a Java video renderer using the back facing video capturer.
|
||||
// It tests both the Java and the C++ layer.
|
||||
@MediumTest
|
||||
public void testStartBackFacingVideoCapturer() throws InterruptedException {
|
||||
fixtures.createBackFacingCapturerAndRender();
|
||||
}
|
||||
|
||||
// This test that the default camera can be started and that the camera can
|
||||
// later be switched to another camera.
|
||||
// It tests both the Java and the C++ layer.
|
||||
@MediumTest
|
||||
public void testSwitchVideoCapturer() throws InterruptedException {
|
||||
fixtures.switchCamera();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testCameraEvents() throws InterruptedException {
|
||||
fixtures.cameraEventsInvoked();
|
||||
}
|
||||
|
||||
// Test what happens when attempting to call e.g. switchCamera() after camera has been stopped.
|
||||
@MediumTest
|
||||
public void testCameraCallsAfterStop() throws InterruptedException {
|
||||
fixtures.cameraCallsAfterStop();
|
||||
}
|
||||
|
||||
// This test that the VideoSource that the CameraVideoCapturer is connected to can
|
||||
// be stopped and restarted. It tests both the Java and the C++ layer.
|
||||
@LargeTest
|
||||
public void testStopRestartVideoSource() throws InterruptedException {
|
||||
fixtures.stopRestartVideoSource();
|
||||
}
|
||||
|
||||
// This test that the camera can be started at different resolutions.
|
||||
// It does not test or use the C++ layer.
|
||||
@LargeTest
|
||||
public void testStartStopWithDifferentResolutions() throws InterruptedException {
|
||||
fixtures.startStopWithDifferentResolutions();
|
||||
}
|
||||
|
||||
// This test what happens if buffers are returned after the capturer have
|
||||
// been stopped and restarted. It does not test or use the C++ layer.
|
||||
@LargeTest
|
||||
public void testReturnBufferLate() throws InterruptedException {
|
||||
fixtures.returnBufferLateEndToEnd();
|
||||
}
|
||||
|
||||
// This test that we can capture frames, keep the frames in a local renderer, stop capturing,
|
||||
// and then return the frames. The difference between the test testReturnBufferLate() is that we
|
||||
// also test the JNI and C++ AndroidVideoCapturer parts.
|
||||
@MediumTest
|
||||
public void testReturnBufferLateEndToEnd() throws InterruptedException {
|
||||
fixtures.returnBufferLateEndToEnd();
|
||||
}
|
||||
|
||||
// This test that CameraEventsHandler.onError is triggered if video buffers are not returned to
|
||||
// the capturer.
|
||||
@LargeTest
|
||||
public void testCameraFreezedEventOnBufferStarvation() throws InterruptedException {
|
||||
fixtures.cameraFreezedEventOnBufferStarvation();
|
||||
}
|
||||
|
||||
// This test that frames forwarded to a renderer is scaled if onOutputFormatRequest is
|
||||
// called. This test both Java and C++ parts of of the stack.
|
||||
@MediumTest
|
||||
public void testScaleCameraOutput() throws InterruptedException {
|
||||
fixtures.scaleCameraOutput();
|
||||
}
|
||||
|
||||
// This test that an error is reported if the camera is already opened
|
||||
// when CameraVideoCapturer is started.
|
||||
@LargeTest
|
||||
public void testStartWhileCameraIsAlreadyOpen() throws InterruptedException {
|
||||
fixtures.startWhileCameraIsAlreadyOpen();
|
||||
}
|
||||
|
||||
// This test that CameraVideoCapturer can be started, even if the camera is already opened
|
||||
// if the camera is closed while CameraVideoCapturer is re-trying to start.
|
||||
@LargeTest
|
||||
public void testStartWhileCameraIsAlreadyOpenAndCloseCamera() throws InterruptedException {
|
||||
fixtures.startWhileCameraIsAlreadyOpenAndCloseCamera();
|
||||
}
|
||||
|
||||
// This test that CameraVideoCapturer.stop can be called while CameraVideoCapturer is
|
||||
// re-trying to start.
|
||||
@MediumTest
|
||||
public void testStartWhileCameraIsAlreadyOpenAndStop() throws InterruptedException {
|
||||
fixtures.startWhileCameraIsAlreadyOpenAndStop();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,667 @@
|
||||
/*
|
||||
* Copyright 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
package org.webrtc;
|
||||
|
||||
import static junit.framework.Assert.*;
|
||||
|
||||
import org.webrtc.CameraEnumerationAndroid.CaptureFormat;
|
||||
import org.webrtc.VideoRenderer.I420Frame;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
class CameraVideoCapturerTestFixtures {
|
||||
static final String TAG = "CameraVideoCapturerTestFixtures";
|
||||
|
||||
static private class RendererCallbacks implements VideoRenderer.Callbacks {
|
||||
private int framesRendered = 0;
|
||||
private Object frameLock = 0;
|
||||
private int width = 0;
|
||||
private int height = 0;
|
||||
|
||||
@Override
|
||||
public void renderFrame(I420Frame frame) {
|
||||
synchronized (frameLock) {
|
||||
++framesRendered;
|
||||
width = frame.rotatedWidth();
|
||||
height = frame.rotatedHeight();
|
||||
frameLock.notify();
|
||||
}
|
||||
VideoRenderer.renderFrameDone(frame);
|
||||
}
|
||||
|
||||
public int frameWidth() {
|
||||
synchronized (frameLock) {
|
||||
return width;
|
||||
}
|
||||
}
|
||||
|
||||
public int frameHeight() {
|
||||
synchronized (frameLock) {
|
||||
return height;
|
||||
}
|
||||
}
|
||||
|
||||
public int waitForNextFrameToRender() throws InterruptedException {
|
||||
Logging.d(TAG, "Waiting for the next frame to render");
|
||||
synchronized (frameLock) {
|
||||
frameLock.wait();
|
||||
return framesRendered;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static private class FakeAsyncRenderer implements VideoRenderer.Callbacks {
|
||||
private final List<I420Frame> pendingFrames = new ArrayList<I420Frame>();
|
||||
|
||||
@Override
|
||||
public void renderFrame(I420Frame frame) {
|
||||
synchronized (pendingFrames) {
|
||||
pendingFrames.add(frame);
|
||||
pendingFrames.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until at least one frame have been received, before returning them.
|
||||
public List<I420Frame> waitForPendingFrames() throws InterruptedException {
|
||||
Logging.d(TAG, "Waiting for pending frames");
|
||||
synchronized (pendingFrames) {
|
||||
while (pendingFrames.isEmpty()) {
|
||||
pendingFrames.wait();
|
||||
}
|
||||
return new ArrayList<I420Frame>(pendingFrames);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static private class FakeCapturerObserver implements CameraVideoCapturer.CapturerObserver {
|
||||
private int framesCaptured = 0;
|
||||
private int frameSize = 0;
|
||||
private int frameWidth = 0;
|
||||
private int frameHeight = 0;
|
||||
final private Object frameLock = new Object();
|
||||
final private Object capturerStartLock = new Object();
|
||||
private boolean capturerStartResult = false;
|
||||
final private List<Long> timestamps = new ArrayList<Long>();
|
||||
|
||||
@Override
|
||||
public void onCapturerStarted(boolean success) {
|
||||
Logging.d(TAG, "onCapturerStarted: " + success);
|
||||
|
||||
synchronized (capturerStartLock) {
|
||||
capturerStartResult = success;
|
||||
capturerStartLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onByteBufferFrameCaptured(byte[] frame, int width, int height, int rotation,
|
||||
long timeStamp) {
|
||||
synchronized (frameLock) {
|
||||
++framesCaptured;
|
||||
frameSize = frame.length;
|
||||
frameWidth = width;
|
||||
frameHeight = height;
|
||||
timestamps.add(timeStamp);
|
||||
frameLock.notify();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onTextureFrameCaptured(
|
||||
int width, int height, int oesTextureId, float[] transformMatrix, int rotation,
|
||||
long timeStamp) {
|
||||
synchronized (frameLock) {
|
||||
++framesCaptured;
|
||||
frameWidth = width;
|
||||
frameHeight = height;
|
||||
frameSize = 0;
|
||||
timestamps.add(timeStamp);
|
||||
frameLock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOutputFormatRequest(int width, int height, int fps) {}
|
||||
|
||||
public boolean waitForCapturerToStart() throws InterruptedException {
|
||||
Logging.d(TAG, "Waiting for the capturer to start");
|
||||
synchronized (capturerStartLock) {
|
||||
capturerStartLock.wait();
|
||||
return capturerStartResult;
|
||||
}
|
||||
}
|
||||
|
||||
public int waitForNextCapturedFrame() throws InterruptedException {
|
||||
Logging.d(TAG, "Waiting for the next captured frame");
|
||||
synchronized (frameLock) {
|
||||
frameLock.wait();
|
||||
return framesCaptured;
|
||||
}
|
||||
}
|
||||
|
||||
int frameSize() {
|
||||
synchronized (frameLock) {
|
||||
return frameSize;
|
||||
}
|
||||
}
|
||||
|
||||
int frameWidth() {
|
||||
synchronized (frameLock) {
|
||||
return frameWidth;
|
||||
}
|
||||
}
|
||||
|
||||
int frameHeight() {
|
||||
synchronized (frameLock) {
|
||||
return frameHeight;
|
||||
}
|
||||
}
|
||||
|
||||
List<Long> getCopyAndResetListOftimeStamps() {
|
||||
synchronized (frameLock) {
|
||||
ArrayList<Long> list = new ArrayList<Long>(timestamps);
|
||||
timestamps.clear();
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class CameraEvents implements
|
||||
CameraVideoCapturer.CameraEventsHandler {
|
||||
public boolean onCameraOpeningCalled;
|
||||
public boolean onFirstFrameAvailableCalled;
|
||||
public final Object onCameraFreezedLock = new Object();
|
||||
private String onCameraFreezedDescription;
|
||||
|
||||
@Override
|
||||
public void onCameraError(String errorDescription) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraFreezed(String errorDescription) {
|
||||
synchronized (onCameraFreezedLock) {
|
||||
onCameraFreezedDescription = errorDescription;
|
||||
onCameraFreezedLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraOpening(int cameraId) {
|
||||
onCameraOpeningCalled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFirstFrameAvailable() {
|
||||
onFirstFrameAvailableCalled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraClosed() { }
|
||||
|
||||
public String waitForCameraFreezed() throws InterruptedException {
|
||||
Logging.d(TAG, "Waiting for the camera to freeze");
|
||||
synchronized (onCameraFreezedLock) {
|
||||
onCameraFreezedLock.wait();
|
||||
return onCameraFreezedDescription;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to collect all classes related to single capturer instance.
|
||||
*/
|
||||
static private class CapturerInstance {
|
||||
public CameraVideoCapturer capturer;
|
||||
public CameraEvents cameraEvents;
|
||||
public SurfaceTextureHelper surfaceTextureHelper;
|
||||
public FakeCapturerObserver observer;
|
||||
public List<CaptureFormat> supportedFormats;
|
||||
public CaptureFormat format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class used for collecting a VideoSource, a VideoTrack and a renderer. The class
|
||||
* is used for testing local rendering from a capturer.
|
||||
*/
|
||||
static private class VideoTrackWithRenderer {
|
||||
public VideoSource source;
|
||||
public VideoTrack track;
|
||||
public RendererCallbacks rendererCallbacks;
|
||||
public FakeAsyncRenderer fakeAsyncRenderer;
|
||||
}
|
||||
|
||||
public interface TestObjectFactory {
|
||||
CameraVideoCapturer createCapturer(
|
||||
String name, CameraVideoCapturer.CameraEventsHandler eventsHandler);
|
||||
String getNameOfFrontFacingDevice();
|
||||
String getNameOfBackFacingDevice();
|
||||
boolean haveTwoCameras();
|
||||
boolean isCapturingToTexture();
|
||||
Context getAppContext();
|
||||
|
||||
// CameraVideoCapturer API is too slow for some of our tests where we need to open a competing
|
||||
// camera. These methods are used instead.
|
||||
Object rawOpenCamera(String cameraName);
|
||||
void rawCloseCamera(Object camera);
|
||||
}
|
||||
|
||||
private PeerConnectionFactory peerConnectionFactory;
|
||||
private TestObjectFactory testObjectFactory;
|
||||
|
||||
CameraVideoCapturerTestFixtures(TestObjectFactory testObjectFactory) {
|
||||
PeerConnectionFactory.initializeAndroidGlobals(
|
||||
testObjectFactory.getAppContext(), true, true, true);
|
||||
|
||||
this.peerConnectionFactory = new PeerConnectionFactory(null /* options */);
|
||||
this.testObjectFactory = testObjectFactory;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
this.peerConnectionFactory.dispose();
|
||||
}
|
||||
|
||||
// Internal helper methods
|
||||
private CapturerInstance createCapturer(String name) {
|
||||
CapturerInstance instance = new CapturerInstance();
|
||||
instance.cameraEvents = new CameraEvents();
|
||||
instance.capturer = testObjectFactory.createCapturer(name, instance.cameraEvents);
|
||||
instance.surfaceTextureHelper = SurfaceTextureHelper.create(
|
||||
"SurfaceTextureHelper test" /* threadName */, null /* sharedContext */);
|
||||
instance.observer = new FakeCapturerObserver();
|
||||
instance.supportedFormats = instance.capturer.getSupportedFormats();
|
||||
return instance;
|
||||
}
|
||||
|
||||
private CapturerInstance createCapturer() {
|
||||
return createCapturer("");
|
||||
}
|
||||
|
||||
private void startCapture(CapturerInstance instance) {
|
||||
startCapture(instance, 0);
|
||||
}
|
||||
|
||||
private void startCapture(CapturerInstance instance, int formatIndex) {
|
||||
final CameraEnumerationAndroid.CaptureFormat format =
|
||||
instance.supportedFormats.get(formatIndex);
|
||||
|
||||
instance.capturer.startCapture(format.width, format.height, format.framerate.max,
|
||||
instance.surfaceTextureHelper, testObjectFactory.getAppContext(), instance.observer);
|
||||
instance.format = format;
|
||||
}
|
||||
|
||||
private void disposeCapturer(CapturerInstance instance) {
|
||||
instance.capturer.dispose();
|
||||
instance.surfaceTextureHelper.returnTextureFrame();
|
||||
instance.surfaceTextureHelper.dispose();
|
||||
}
|
||||
|
||||
private VideoTrackWithRenderer createVideoTrackWithRenderer(CameraVideoCapturer capturer,
|
||||
VideoRenderer.Callbacks rendererCallbacks) {
|
||||
VideoTrackWithRenderer videoTrackWithRenderer = new VideoTrackWithRenderer();
|
||||
videoTrackWithRenderer.source =
|
||||
peerConnectionFactory.createVideoSource(capturer, new MediaConstraints());
|
||||
videoTrackWithRenderer.track =
|
||||
peerConnectionFactory.createVideoTrack("dummy", videoTrackWithRenderer.source);
|
||||
videoTrackWithRenderer.track.addRenderer(new VideoRenderer(rendererCallbacks));
|
||||
return videoTrackWithRenderer;
|
||||
}
|
||||
|
||||
private VideoTrackWithRenderer createVideoTrackWithRenderer(CameraVideoCapturer capturer) {
|
||||
RendererCallbacks rendererCallbacks = new RendererCallbacks();
|
||||
VideoTrackWithRenderer videoTrackWithRenderer =
|
||||
createVideoTrackWithRenderer(capturer, rendererCallbacks);
|
||||
videoTrackWithRenderer.rendererCallbacks = rendererCallbacks;
|
||||
return videoTrackWithRenderer;
|
||||
}
|
||||
|
||||
private VideoTrackWithRenderer createVideoTrackWithFakeAsyncRenderer(
|
||||
CameraVideoCapturer capturer) {
|
||||
FakeAsyncRenderer fakeAsyncRenderer = new FakeAsyncRenderer();
|
||||
VideoTrackWithRenderer videoTrackWithRenderer =
|
||||
createVideoTrackWithRenderer(capturer, fakeAsyncRenderer);
|
||||
videoTrackWithRenderer.fakeAsyncRenderer = fakeAsyncRenderer;
|
||||
return videoTrackWithRenderer;
|
||||
}
|
||||
|
||||
private void disposeVideoTrackWithRenderer(VideoTrackWithRenderer videoTrackWithRenderer) {
|
||||
videoTrackWithRenderer.track.dispose();
|
||||
videoTrackWithRenderer.source.dispose();
|
||||
}
|
||||
|
||||
private void waitUntilIdle(CapturerInstance capturerInstance) throws InterruptedException {
|
||||
final CountDownLatch barrier = new CountDownLatch(1);
|
||||
capturerInstance.surfaceTextureHelper.getHandler().post(new Runnable() {
|
||||
@Override public void run() {
|
||||
barrier.countDown();
|
||||
}
|
||||
});
|
||||
barrier.await();
|
||||
}
|
||||
|
||||
private void createCapturerAndRender(String name) throws InterruptedException {
|
||||
if (name == null) {
|
||||
Logging.w(TAG, "Skipping video capturer test because device name is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
final CapturerInstance capturerInstance = createCapturer(name);
|
||||
final VideoTrackWithRenderer videoTrackWithRenderer =
|
||||
createVideoTrackWithRenderer(capturerInstance.capturer);
|
||||
assertTrue(videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender() > 0);
|
||||
disposeVideoTrackWithRenderer(videoTrackWithRenderer);
|
||||
disposeCapturer(capturerInstance);
|
||||
}
|
||||
|
||||
// Test methods
|
||||
public void createCapturerAndDispose() {
|
||||
disposeCapturer(createCapturer());
|
||||
}
|
||||
|
||||
public void createNonExistingCamera() {
|
||||
try {
|
||||
disposeCapturer(createCapturer("non-existing camera"));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
fail("Expected illegal argument exception when creating non-existing camera.");
|
||||
}
|
||||
|
||||
public void createCapturerAndRender() throws InterruptedException {
|
||||
createCapturerAndRender("");
|
||||
}
|
||||
|
||||
public void createFrontFacingCapturerAndRender() throws InterruptedException {
|
||||
createCapturerAndRender(testObjectFactory.getNameOfFrontFacingDevice());
|
||||
}
|
||||
|
||||
public void createBackFacingCapturerAndRender() throws InterruptedException {
|
||||
createCapturerAndRender(testObjectFactory.getNameOfBackFacingDevice());
|
||||
}
|
||||
|
||||
public void switchCamera() throws InterruptedException {
|
||||
if (!testObjectFactory.haveTwoCameras()) {
|
||||
Logging.w(TAG,
|
||||
"Skipping test switch video capturer because the device doesn't have two cameras.");
|
||||
return;
|
||||
}
|
||||
|
||||
final CapturerInstance capturerInstance = createCapturer();
|
||||
final VideoTrackWithRenderer videoTrackWithRenderer =
|
||||
createVideoTrackWithRenderer(capturerInstance.capturer);
|
||||
|
||||
// Array with one element to avoid final problem in nested classes.
|
||||
final boolean[] cameraSwitchSuccessful = new boolean[1];
|
||||
final CountDownLatch barrier = new CountDownLatch(1);
|
||||
capturerInstance.capturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() {
|
||||
@Override
|
||||
public void onCameraSwitchDone(boolean isFrontCamera) {
|
||||
cameraSwitchSuccessful[0] = true;
|
||||
barrier.countDown();
|
||||
}
|
||||
@Override
|
||||
public void onCameraSwitchError(String errorDescription) {
|
||||
cameraSwitchSuccessful[0] = false;
|
||||
barrier.countDown();
|
||||
}
|
||||
});
|
||||
// Wait until the camera has been switched.
|
||||
barrier.await();
|
||||
|
||||
// Check result.
|
||||
assertTrue(cameraSwitchSuccessful[0]);
|
||||
// Ensure that frames are received.
|
||||
assertTrue(videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender() > 0);
|
||||
disposeVideoTrackWithRenderer(videoTrackWithRenderer);
|
||||
disposeCapturer(capturerInstance);
|
||||
}
|
||||
|
||||
public void cameraEventsInvoked() throws InterruptedException {
|
||||
final CapturerInstance capturerInstance = createCapturer();
|
||||
startCapture(capturerInstance);
|
||||
// Make sure camera is started and first frame is received and then stop it.
|
||||
assertTrue(capturerInstance.observer.waitForCapturerToStart());
|
||||
capturerInstance.observer.waitForNextCapturedFrame();
|
||||
capturerInstance.capturer.stopCapture();
|
||||
disposeCapturer(capturerInstance);
|
||||
|
||||
assertTrue(capturerInstance.cameraEvents.onCameraOpeningCalled);
|
||||
assertTrue(capturerInstance.cameraEvents.onFirstFrameAvailableCalled);
|
||||
}
|
||||
|
||||
public void cameraCallsAfterStop() throws InterruptedException {
|
||||
final CapturerInstance capturerInstance = createCapturer();
|
||||
startCapture(capturerInstance);
|
||||
// Make sure camera is started and then stop it.
|
||||
assertTrue(capturerInstance.observer.waitForCapturerToStart());
|
||||
capturerInstance.capturer.stopCapture();
|
||||
capturerInstance.surfaceTextureHelper.returnTextureFrame();
|
||||
|
||||
// We can't change |capturer| at this point, but we should not crash.
|
||||
capturerInstance.capturer.switchCamera(null /* switchEventsHandler */);
|
||||
capturerInstance.capturer.onOutputFormatRequest(640, 480, 15);
|
||||
capturerInstance.capturer.changeCaptureFormat(640, 480, 15);
|
||||
|
||||
disposeCapturer(capturerInstance);
|
||||
}
|
||||
|
||||
public void stopRestartVideoSource() throws InterruptedException {
|
||||
final CapturerInstance capturerInstance = createCapturer();
|
||||
final VideoTrackWithRenderer videoTrackWithRenderer =
|
||||
createVideoTrackWithRenderer(capturerInstance.capturer);
|
||||
|
||||
assertTrue(videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender() > 0);
|
||||
assertEquals(MediaSource.State.LIVE, videoTrackWithRenderer.source.state());
|
||||
|
||||
videoTrackWithRenderer.source.stop();
|
||||
assertEquals(MediaSource.State.ENDED, videoTrackWithRenderer.source.state());
|
||||
|
||||
videoTrackWithRenderer.source.restart();
|
||||
assertTrue(videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender() > 0);
|
||||
assertEquals(MediaSource.State.LIVE, videoTrackWithRenderer.source.state());
|
||||
|
||||
disposeVideoTrackWithRenderer(videoTrackWithRenderer);
|
||||
disposeCapturer(capturerInstance);
|
||||
}
|
||||
|
||||
public void startStopWithDifferentResolutions() throws InterruptedException {
|
||||
final CapturerInstance capturerInstance = createCapturer();
|
||||
|
||||
for(int i = 0; i < 3 ; ++i) {
|
||||
startCapture(capturerInstance, i);
|
||||
assertTrue(capturerInstance.observer.waitForCapturerToStart());
|
||||
capturerInstance.observer.waitForNextCapturedFrame();
|
||||
|
||||
// Check the frame size. The actual width and height depend on how the capturer is mounted.
|
||||
final boolean identicalResolution = (
|
||||
capturerInstance.observer.frameWidth() == capturerInstance.format.width
|
||||
&& capturerInstance.observer.frameHeight() == capturerInstance.format.height);
|
||||
final boolean flippedResolution = (
|
||||
capturerInstance.observer.frameWidth() == capturerInstance.format.height
|
||||
&& capturerInstance.observer.frameHeight() == capturerInstance.format.width);
|
||||
if (!identicalResolution && !flippedResolution) {
|
||||
fail("Wrong resolution, got: "
|
||||
+ capturerInstance.observer.frameWidth() + "x" + capturerInstance.observer.frameHeight()
|
||||
+ " expected: "+ capturerInstance.format.width + "x" + capturerInstance.format.height
|
||||
+ " or " + capturerInstance.format.height + "x" + capturerInstance.format.width);
|
||||
}
|
||||
|
||||
if (testObjectFactory.isCapturingToTexture()) {
|
||||
assertEquals(0, capturerInstance.observer.frameSize());
|
||||
} else {
|
||||
assertTrue(capturerInstance.format.frameSize() <= capturerInstance.observer.frameSize());
|
||||
}
|
||||
capturerInstance.capturer.stopCapture();
|
||||
capturerInstance.surfaceTextureHelper.returnTextureFrame();
|
||||
}
|
||||
disposeCapturer(capturerInstance);
|
||||
}
|
||||
|
||||
public void returnBufferLate() throws InterruptedException {
|
||||
final CapturerInstance capturerInstance = createCapturer();
|
||||
startCapture(capturerInstance);
|
||||
assertTrue(capturerInstance.observer.waitForCapturerToStart());
|
||||
|
||||
capturerInstance.observer.waitForNextCapturedFrame();
|
||||
capturerInstance.capturer.stopCapture();
|
||||
List<Long> listOftimestamps = capturerInstance.observer.getCopyAndResetListOftimeStamps();
|
||||
assertTrue(listOftimestamps.size() >= 1);
|
||||
|
||||
startCapture(capturerInstance, 1);
|
||||
capturerInstance.observer.waitForCapturerToStart();
|
||||
capturerInstance.surfaceTextureHelper.returnTextureFrame();
|
||||
|
||||
capturerInstance.observer.waitForNextCapturedFrame();
|
||||
capturerInstance.capturer.stopCapture();
|
||||
|
||||
listOftimestamps = capturerInstance.observer.getCopyAndResetListOftimeStamps();
|
||||
assertTrue(listOftimestamps.size() >= 1);
|
||||
|
||||
disposeCapturer(capturerInstance);
|
||||
}
|
||||
|
||||
public void returnBufferLateEndToEnd()
|
||||
throws InterruptedException {
|
||||
final CapturerInstance capturerInstance = createCapturer();
|
||||
final VideoTrackWithRenderer videoTrackWithRenderer =
|
||||
createVideoTrackWithFakeAsyncRenderer(capturerInstance.capturer);
|
||||
// Wait for at least one frame that has not been returned.
|
||||
assertFalse(videoTrackWithRenderer.fakeAsyncRenderer.waitForPendingFrames().isEmpty());
|
||||
|
||||
capturerInstance.capturer.stopCapture();
|
||||
|
||||
// Dispose everything.
|
||||
disposeVideoTrackWithRenderer(videoTrackWithRenderer);
|
||||
disposeCapturer(capturerInstance);
|
||||
|
||||
// Return the frame(s), on a different thread out of spite.
|
||||
final List<I420Frame> pendingFrames =
|
||||
videoTrackWithRenderer.fakeAsyncRenderer.waitForPendingFrames();
|
||||
final Thread returnThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (I420Frame frame : pendingFrames) {
|
||||
VideoRenderer.renderFrameDone(frame);
|
||||
}
|
||||
}
|
||||
});
|
||||
returnThread.start();
|
||||
returnThread.join();
|
||||
}
|
||||
|
||||
public void cameraFreezedEventOnBufferStarvation() throws InterruptedException {
|
||||
final CapturerInstance capturerInstance = createCapturer();
|
||||
startCapture(capturerInstance);
|
||||
// Make sure camera is started.
|
||||
assertTrue(capturerInstance.observer.waitForCapturerToStart());
|
||||
// Since we don't return the buffer, we should get a starvation message if we are
|
||||
// capturing to a texture.
|
||||
assertEquals("Camera failure. Client must return video buffers.",
|
||||
capturerInstance.cameraEvents.waitForCameraFreezed());
|
||||
|
||||
capturerInstance.capturer.stopCapture();
|
||||
disposeCapturer(capturerInstance);
|
||||
}
|
||||
|
||||
public void scaleCameraOutput() throws InterruptedException {
|
||||
final CapturerInstance capturerInstance = createCapturer();
|
||||
final VideoTrackWithRenderer videoTrackWithRenderer =
|
||||
createVideoTrackWithRenderer(capturerInstance.capturer);
|
||||
assertTrue(videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender() > 0);
|
||||
|
||||
final int startWidth = videoTrackWithRenderer.rendererCallbacks.frameWidth();
|
||||
final int startHeight = videoTrackWithRenderer.rendererCallbacks.frameHeight();
|
||||
final int frameRate = 30;
|
||||
final int scaledWidth = startWidth / 2;
|
||||
final int scaledHeight = startHeight / 2;
|
||||
|
||||
// Request the captured frames to be scaled.
|
||||
capturerInstance.capturer.onOutputFormatRequest(scaledWidth, scaledHeight, frameRate);
|
||||
|
||||
boolean gotExpectedResolution = false;
|
||||
int numberOfInspectedFrames = 0;
|
||||
|
||||
do {
|
||||
videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender();
|
||||
++numberOfInspectedFrames;
|
||||
|
||||
gotExpectedResolution = (videoTrackWithRenderer.rendererCallbacks.frameWidth() == scaledWidth
|
||||
&& videoTrackWithRenderer.rendererCallbacks.frameHeight() == scaledHeight);
|
||||
} while (!gotExpectedResolution && numberOfInspectedFrames < 30);
|
||||
|
||||
disposeVideoTrackWithRenderer(videoTrackWithRenderer);
|
||||
disposeCapturer(capturerInstance);
|
||||
|
||||
assertTrue(gotExpectedResolution);
|
||||
}
|
||||
|
||||
public void startWhileCameraIsAlreadyOpen() throws InterruptedException {
|
||||
final String cameraName = testObjectFactory.getNameOfBackFacingDevice();
|
||||
// At this point camera is not actually opened.
|
||||
final CapturerInstance capturerInstance = createCapturer(cameraName);
|
||||
|
||||
final Object competingCamera = testObjectFactory.rawOpenCamera(cameraName);
|
||||
|
||||
startCapture(capturerInstance);
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
|
||||
// The first opened camera client will be evicted.
|
||||
assertTrue(capturerInstance.observer.waitForCapturerToStart());
|
||||
capturerInstance.capturer.stopCapture();
|
||||
} else {
|
||||
assertFalse(capturerInstance.observer.waitForCapturerToStart());
|
||||
}
|
||||
|
||||
testObjectFactory.rawCloseCamera(competingCamera);
|
||||
disposeCapturer(capturerInstance);
|
||||
}
|
||||
|
||||
public void startWhileCameraIsAlreadyOpenAndCloseCamera() throws InterruptedException {
|
||||
final String cameraName = testObjectFactory.getNameOfBackFacingDevice();
|
||||
// At this point camera is not actually opened.
|
||||
final CapturerInstance capturerInstance = createCapturer(cameraName);
|
||||
|
||||
Logging.d(TAG, "startWhileCameraIsAlreadyOpenAndCloseCamera: Opening competing camera.");
|
||||
final Object competingCamera = testObjectFactory.rawOpenCamera(cameraName);
|
||||
|
||||
Logging.d(TAG, "startWhileCameraIsAlreadyOpenAndCloseCamera: Opening camera.");
|
||||
final VideoTrackWithRenderer videoTrackWithRenderer =
|
||||
createVideoTrackWithRenderer(capturerInstance.capturer);
|
||||
waitUntilIdle(capturerInstance);
|
||||
|
||||
Logging.d(TAG, "startWhileCameraIsAlreadyOpenAndCloseCamera: Closing competing camera.");
|
||||
testObjectFactory.rawCloseCamera(competingCamera);
|
||||
|
||||
// Make sure camera is started and first frame is received and then stop it.
|
||||
Logging.d(TAG, "startWhileCameraIsAlreadyOpenAndCloseCamera: Waiting for capture to start.");
|
||||
videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender();
|
||||
Logging.d(TAG, "startWhileCameraIsAlreadyOpenAndCloseCamera: Stopping capture.");
|
||||
capturerInstance.capturer.stopCapture();
|
||||
disposeCapturer(capturerInstance);
|
||||
}
|
||||
|
||||
public void startWhileCameraIsAlreadyOpenAndStop() throws InterruptedException {
|
||||
final String cameraName = testObjectFactory.getNameOfBackFacingDevice();
|
||||
// At this point camera is not actually opened.
|
||||
final CapturerInstance capturerInstance = createCapturer(cameraName);
|
||||
|
||||
final Object competingCamera = testObjectFactory.rawOpenCamera(cameraName);
|
||||
|
||||
startCapture(capturerInstance);
|
||||
|
||||
capturerInstance.capturer.stopCapture();
|
||||
disposeCapturer(capturerInstance);
|
||||
|
||||
testObjectFactory.rawCloseCamera(competingCamera);
|
||||
}
|
||||
}
|
||||
@ -1,313 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
package org.webrtc;
|
||||
|
||||
import android.content.Context;
|
||||
import android.test.ActivityTestCase;
|
||||
import android.test.suitebuilder.annotation.MediumTest;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.util.Size;
|
||||
|
||||
import org.webrtc.CameraEnumerationAndroid.CaptureFormat;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class VideoCapturerAndroidTest extends ActivityTestCase {
|
||||
static final String TAG = "VideoCapturerAndroidTest";
|
||||
|
||||
@Override
|
||||
protected void setUp() {
|
||||
assertTrue(PeerConnectionFactory.initializeAndroidGlobals(
|
||||
getInstrumentation().getContext(), true, true, true));
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
// Test that enumerating formats using android.hardware.camera2 will give the same formats as
|
||||
// android.hardware.camera in the range 320x240 to 1280x720. Often the camera2 API may contain
|
||||
// some high resolutions that are not supported in camera1, but it may also be the other way
|
||||
// around in some cases. Supported framerates may also differ, so don't compare those.
|
||||
public void testCamera2Enumerator() {
|
||||
if (!Camera2Enumerator.isSupported()) {
|
||||
return;
|
||||
}
|
||||
final Context context = getInstrumentation().getContext();
|
||||
|
||||
for (int i = 0; i < CameraEnumerationAndroid.getDeviceCount(); ++i) {
|
||||
final Set<Size> resolutions1 = new HashSet<Size>();
|
||||
for (CaptureFormat format : CameraEnumerator.getSupportedFormats(i)) {
|
||||
resolutions1.add(new Size(format.width, format.height));
|
||||
}
|
||||
final Set<Size> resolutions2 = new HashSet<Size>();
|
||||
for (CaptureFormat format :
|
||||
Camera2Enumerator.getSupportedFormats(context, Integer.toString(i))) {
|
||||
resolutions2.add(new Size(format.width, format.height));
|
||||
}
|
||||
for (Size size : resolutions1) {
|
||||
if (size.getWidth() >= 320 && size.getHeight() >= 240
|
||||
&& size.getWidth() <= 1280 && size.getHeight() <= 720) {
|
||||
assertTrue(resolutions2.contains(size));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testCreateAndRelease() {
|
||||
VideoCapturerAndroidTestFixtures.release(VideoCapturerAndroid.create("", null));
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testCreateAndReleaseUsingTextures() {
|
||||
VideoCapturerAndroidTestFixtures.release(
|
||||
VideoCapturerAndroid.create("", null, true /* captureToTexture */));
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testCreateNonExistingCamera() {
|
||||
VideoCapturerAndroid capturer = VideoCapturerAndroid.create(
|
||||
"non-existing camera", null);
|
||||
assertNull(capturer);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
// This test that the camera can be started and that the frames are forwarded
|
||||
// to a Java video renderer using a "default" capturer.
|
||||
// It tests both the Java and the C++ layer.
|
||||
public void testStartVideoCapturer() throws InterruptedException {
|
||||
VideoCapturerAndroid capturer =
|
||||
VideoCapturerAndroid.create("", null);
|
||||
VideoCapturerAndroidTestFixtures.startCapturerAndRender(capturer);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testStartVideoCapturerUsingTextures() throws InterruptedException {
|
||||
VideoCapturerAndroid capturer =
|
||||
VideoCapturerAndroid.create("", null, true /* captureToTexture */);
|
||||
VideoCapturerAndroidTestFixtures.startCapturerAndRender(capturer);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
// This test that the camera can be started and that the frames are forwarded
|
||||
// to a Java video renderer using the front facing video capturer.
|
||||
// It tests both the Java and the C++ layer.
|
||||
public void testStartFrontFacingVideoCapturer() throws InterruptedException {
|
||||
String deviceName = CameraEnumerationAndroid.getNameOfFrontFacingDevice();
|
||||
VideoCapturerAndroid capturer =
|
||||
VideoCapturerAndroid.create(deviceName, null);
|
||||
VideoCapturerAndroidTestFixtures.startCapturerAndRender(capturer);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
// This test that the camera can be started and that the frames are forwarded
|
||||
// to a Java video renderer using the back facing video capturer.
|
||||
// It tests both the Java and the C++ layer.
|
||||
public void testStartBackFacingVideoCapturer() throws InterruptedException {
|
||||
if (!VideoCapturerAndroidTestFixtures.HaveTwoCameras()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String deviceName = CameraEnumerationAndroid.getNameOfBackFacingDevice();
|
||||
VideoCapturerAndroid capturer =
|
||||
VideoCapturerAndroid.create(deviceName, null);
|
||||
VideoCapturerAndroidTestFixtures.startCapturerAndRender(capturer);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
// This test that the default camera can be started and that the camera can
|
||||
// later be switched to another camera.
|
||||
// It tests both the Java and the C++ layer.
|
||||
public void testSwitchVideoCapturer() throws InterruptedException {
|
||||
VideoCapturerAndroid capturer = VideoCapturerAndroid.create("", null);
|
||||
VideoCapturerAndroidTestFixtures.switchCamera(capturer);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testSwitchVideoCapturerUsingTextures() throws InterruptedException {
|
||||
VideoCapturerAndroid capturer =
|
||||
VideoCapturerAndroid.create("", null, true /* captureToTexture */);
|
||||
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
|
||||
public void testCameraEventsUsingTextures() throws InterruptedException {
|
||||
VideoCapturerAndroidTestFixtures.CameraEvents cameraEvents =
|
||||
VideoCapturerAndroidTestFixtures.createCameraEvents();
|
||||
VideoCapturerAndroid capturer =
|
||||
VideoCapturerAndroid.create("", cameraEvents, true /* captureToTexture */);
|
||||
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 {
|
||||
final String deviceName = CameraEnumerationAndroid.getDeviceName(0);
|
||||
final VideoCapturerAndroid capturer = VideoCapturerAndroid.create(deviceName, null);
|
||||
|
||||
VideoCapturerAndroidTestFixtures.cameraCallsAfterStop(capturer,
|
||||
getInstrumentation().getContext());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testCameraCallsAfterStopUsingTextures() throws InterruptedException {
|
||||
final String deviceName = CameraEnumerationAndroid.getDeviceName(0);
|
||||
final VideoCapturerAndroid capturer = VideoCapturerAndroid.create(deviceName, null,
|
||||
true /* captureToTexture */);
|
||||
|
||||
VideoCapturerAndroidTestFixtures.cameraCallsAfterStop(capturer,
|
||||
getInstrumentation().getContext());
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
// This test that the VideoSource that the VideoCapturer is connected to can
|
||||
// be stopped and restarted. It tests both the Java and the C++ layer.
|
||||
public void testStopRestartVideoSource() throws InterruptedException {
|
||||
VideoCapturerAndroid capturer = VideoCapturerAndroid.create("", null);
|
||||
VideoCapturerAndroidTestFixtures.stopRestartVideoSource(capturer);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testStopRestartVideoSourceUsingTextures() throws InterruptedException {
|
||||
VideoCapturerAndroid capturer =
|
||||
VideoCapturerAndroid.create("", null, true /* captureToTexture */);
|
||||
VideoCapturerAndroidTestFixtures.stopRestartVideoSource(capturer);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
// This test that the camera can be started at different resolutions.
|
||||
// It does not test or use the C++ layer.
|
||||
public void testStartStopWithDifferentResolutions() throws InterruptedException {
|
||||
String deviceName = CameraEnumerationAndroid.getDeviceName(0);
|
||||
VideoCapturerAndroid capturer =
|
||||
VideoCapturerAndroid.create(deviceName, null);
|
||||
VideoCapturerAndroidTestFixtures.startStopWithDifferentResolutions(capturer,
|
||||
getInstrumentation().getContext());
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testStartStopWithDifferentResolutionsUsingTextures() throws InterruptedException {
|
||||
String deviceName = CameraEnumerationAndroid.getDeviceName(0);
|
||||
VideoCapturerAndroid capturer =
|
||||
VideoCapturerAndroid.create(deviceName, null, true /* captureToTexture */);
|
||||
VideoCapturerAndroidTestFixtures.startStopWithDifferentResolutions(capturer,
|
||||
getInstrumentation().getContext());
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
// This test that an error is reported if the camera is already opened
|
||||
// when VideoCapturerAndroid is started.
|
||||
public void testStartWhileCameraAlreadyOpened() throws InterruptedException {
|
||||
String deviceName = CameraEnumerationAndroid.getDeviceName(0);
|
||||
VideoCapturerAndroid capturer =
|
||||
VideoCapturerAndroid.create(deviceName, null);
|
||||
VideoCapturerAndroidTestFixtures.startWhileCameraIsAlreadyOpen(
|
||||
capturer, getInstrumentation().getContext());
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
// This test that VideoCapturerAndroid can be started, even if the camera is already opened
|
||||
// if the camera is closed while VideoCapturerAndroid is re-trying to start.
|
||||
public void testStartWhileCameraIsAlreadyOpenAndCloseCamera() throws InterruptedException {
|
||||
String deviceName = CameraEnumerationAndroid.getDeviceName(0);
|
||||
VideoCapturerAndroid capturer =
|
||||
VideoCapturerAndroid.create(deviceName, null);
|
||||
VideoCapturerAndroidTestFixtures.startWhileCameraIsAlreadyOpenAndCloseCamera(
|
||||
capturer, getInstrumentation().getContext());
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
// This test that VideoCapturerAndroid.stop can be called while VideoCapturerAndroid is
|
||||
// re-trying to start.
|
||||
public void startWhileCameraIsAlreadyOpenAndStop() throws InterruptedException {
|
||||
String deviceName = CameraEnumerationAndroid.getDeviceName(0);
|
||||
VideoCapturerAndroid capturer =
|
||||
VideoCapturerAndroid.create(deviceName, null);
|
||||
VideoCapturerAndroidTestFixtures.startWhileCameraIsAlreadyOpenAndStop(
|
||||
capturer, getInstrumentation().getContext());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@SmallTest
|
||||
// This test what happens if buffers are returned after the capturer have
|
||||
// been stopped and restarted. It does not test or use the C++ layer.
|
||||
public void testReturnBufferLate() throws InterruptedException {
|
||||
String deviceName = CameraEnumerationAndroid.getDeviceName(0);
|
||||
VideoCapturerAndroid capturer =
|
||||
VideoCapturerAndroid.create(deviceName, null);
|
||||
VideoCapturerAndroidTestFixtures.returnBufferLate(capturer,
|
||||
getInstrumentation().getContext());
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testReturnBufferLateUsingTextures() throws InterruptedException {
|
||||
String deviceName = CameraEnumerationAndroid.getDeviceName(0);
|
||||
VideoCapturerAndroid capturer =
|
||||
VideoCapturerAndroid.create(deviceName, null, true /* captureToTexture */);
|
||||
VideoCapturerAndroidTestFixtures.returnBufferLate(capturer,
|
||||
getInstrumentation().getContext());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
// This test that we can capture frames, keep the frames in a local renderer, stop capturing,
|
||||
// and then return the frames. The difference between the test testReturnBufferLate() is that we
|
||||
// also test the JNI and C++ AndroidVideoCapturer parts.
|
||||
public void testReturnBufferLateEndToEnd() throws InterruptedException {
|
||||
final VideoCapturerAndroid capturer = VideoCapturerAndroid.create("", null);
|
||||
VideoCapturerAndroidTestFixtures.returnBufferLateEndToEnd(capturer);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testReturnBufferLateEndToEndUsingTextures() throws InterruptedException {
|
||||
final VideoCapturerAndroid capturer =
|
||||
VideoCapturerAndroid.create("", null, true /* captureToTexture */);
|
||||
VideoCapturerAndroidTestFixtures.returnBufferLateEndToEnd(capturer);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
// This test that CameraEventsHandler.onError is triggered if video buffers are not returned to
|
||||
// the capturer.
|
||||
public void testCameraFreezedEventOnBufferStarvationUsingTextures() throws InterruptedException {
|
||||
VideoCapturerAndroidTestFixtures.CameraEvents cameraEvents =
|
||||
VideoCapturerAndroidTestFixtures.createCameraEvents();
|
||||
VideoCapturerAndroid capturer = VideoCapturerAndroid.create("", cameraEvents,
|
||||
true /* captureToTexture */);
|
||||
VideoCapturerAndroidTestFixtures.cameraFreezedEventOnBufferStarvationUsingTextures(capturer,
|
||||
cameraEvents, getInstrumentation().getContext());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
// This test that frames forwarded to a renderer is scaled if onOutputFormatRequest is
|
||||
// called. This test both Java and C++ parts of of the stack.
|
||||
public void testScaleCameraOutput() throws InterruptedException {
|
||||
VideoCapturerAndroid capturer = VideoCapturerAndroid.create("", null);
|
||||
VideoCapturerAndroidTestFixtures.scaleCameraOutput(capturer);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
// This test that frames forwarded to a renderer is scaled if onOutputFormatRequest is
|
||||
// called. This test both Java and C++ parts of of the stack.
|
||||
public void testScaleCameraOutputUsingTextures() throws InterruptedException {
|
||||
VideoCapturerAndroid capturer =
|
||||
VideoCapturerAndroid.create("", null, true /* captureToTexture */);
|
||||
VideoCapturerAndroidTestFixtures.scaleCameraOutput(capturer);
|
||||
}
|
||||
}
|
||||
@ -1,604 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
package org.webrtc;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.webrtc.VideoCapturerAndroidTestFixtures;
|
||||
import org.webrtc.CameraEnumerationAndroid.CaptureFormat;
|
||||
import org.webrtc.VideoRenderer.I420Frame;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static junit.framework.Assert.*;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class VideoCapturerAndroidTestFixtures {
|
||||
static class RendererCallbacks implements VideoRenderer.Callbacks {
|
||||
private int framesRendered = 0;
|
||||
private Object frameLock = 0;
|
||||
private int width = 0;
|
||||
private int height = 0;
|
||||
|
||||
@Override
|
||||
public void renderFrame(I420Frame frame) {
|
||||
synchronized (frameLock) {
|
||||
++framesRendered;
|
||||
width = frame.rotatedWidth();
|
||||
height = frame.rotatedHeight();
|
||||
frameLock.notify();
|
||||
}
|
||||
VideoRenderer.renderFrameDone(frame);
|
||||
}
|
||||
|
||||
public int frameWidth() {
|
||||
synchronized (frameLock) {
|
||||
return width;
|
||||
}
|
||||
}
|
||||
|
||||
public int frameHeight() {
|
||||
synchronized (frameLock) {
|
||||
return height;
|
||||
}
|
||||
}
|
||||
|
||||
public int WaitForNextFrameToRender() throws InterruptedException {
|
||||
synchronized (frameLock) {
|
||||
frameLock.wait();
|
||||
return framesRendered;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class FakeAsyncRenderer implements VideoRenderer.Callbacks {
|
||||
private final List<I420Frame> pendingFrames = new ArrayList<I420Frame>();
|
||||
|
||||
@Override
|
||||
public void renderFrame(I420Frame frame) {
|
||||
synchronized (pendingFrames) {
|
||||
pendingFrames.add(frame);
|
||||
pendingFrames.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until at least one frame have been received, before returning them.
|
||||
public List<I420Frame> waitForPendingFrames() throws InterruptedException {
|
||||
synchronized (pendingFrames) {
|
||||
while (pendingFrames.isEmpty()) {
|
||||
pendingFrames.wait();
|
||||
}
|
||||
return new ArrayList<I420Frame>(pendingFrames);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class FakeCapturerObserver implements VideoCapturer.CapturerObserver {
|
||||
private int framesCaptured = 0;
|
||||
private int frameSize = 0;
|
||||
private int frameWidth = 0;
|
||||
private int frameHeight = 0;
|
||||
private Object frameLock = 0;
|
||||
private Object capturerStartLock = 0;
|
||||
private boolean captureStartResult = false;
|
||||
private List<Long> timestamps = new ArrayList<Long>();
|
||||
|
||||
@Override
|
||||
public void onCapturerStarted(boolean success) {
|
||||
synchronized (capturerStartLock) {
|
||||
captureStartResult = success;
|
||||
capturerStartLock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onByteBufferFrameCaptured(byte[] frame, int width, int height, int rotation,
|
||||
long timeStamp) {
|
||||
synchronized (frameLock) {
|
||||
++framesCaptured;
|
||||
frameSize = frame.length;
|
||||
frameWidth = width;
|
||||
frameHeight = height;
|
||||
timestamps.add(timeStamp);
|
||||
frameLock.notify();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onTextureFrameCaptured(
|
||||
int width, int height, int oesTextureId, float[] transformMatrix, int rotation,
|
||||
long timeStamp) {
|
||||
synchronized (frameLock) {
|
||||
++framesCaptured;
|
||||
frameWidth = width;
|
||||
frameHeight = height;
|
||||
frameSize = 0;
|
||||
timestamps.add(timeStamp);
|
||||
frameLock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOutputFormatRequest(int width, int height, int fps) {}
|
||||
|
||||
public boolean WaitForCapturerToStart() throws InterruptedException {
|
||||
synchronized (capturerStartLock) {
|
||||
capturerStartLock.wait();
|
||||
return captureStartResult;
|
||||
}
|
||||
}
|
||||
|
||||
public int WaitForNextCapturedFrame() throws InterruptedException {
|
||||
synchronized (frameLock) {
|
||||
frameLock.wait();
|
||||
return framesCaptured;
|
||||
}
|
||||
}
|
||||
|
||||
int frameSize() {
|
||||
synchronized (frameLock) {
|
||||
return frameSize;
|
||||
}
|
||||
}
|
||||
|
||||
int frameWidth() {
|
||||
synchronized (frameLock) {
|
||||
return frameWidth;
|
||||
}
|
||||
}
|
||||
|
||||
int frameHeight() {
|
||||
synchronized (frameLock) {
|
||||
return frameHeight;
|
||||
}
|
||||
}
|
||||
|
||||
List<Long> getCopyAndResetListOftimeStamps() {
|
||||
synchronized (frameLock) {
|
||||
ArrayList<Long> list = new ArrayList<Long>(timestamps);
|
||||
timestamps.clear();
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class CameraEvents implements
|
||||
VideoCapturerAndroid.CameraEventsHandler {
|
||||
public boolean onCameraOpeningCalled;
|
||||
public boolean onFirstFrameAvailableCalled;
|
||||
public final Object onCameraFreezedLock = new Object();
|
||||
private String onCameraFreezedDescription;
|
||||
|
||||
@Override
|
||||
public void onCameraError(String errorDescription) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraFreezed(String errorDescription) {
|
||||
synchronized (onCameraFreezedLock) {
|
||||
onCameraFreezedDescription = errorDescription;
|
||||
onCameraFreezedLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraOpening(int cameraId) {
|
||||
onCameraOpeningCalled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFirstFrameAvailable() {
|
||||
onFirstFrameAvailableCalled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraClosed() { }
|
||||
|
||||
public String WaitForCameraFreezed() throws InterruptedException {
|
||||
synchronized (onCameraFreezedLock) {
|
||||
onCameraFreezedLock.wait();
|
||||
return onCameraFreezedDescription;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
return (android.hardware.Camera.getNumberOfCameras() >= 2);
|
||||
}
|
||||
|
||||
static public void release(VideoCapturerAndroid capturer) {
|
||||
assertNotNull(capturer);
|
||||
capturer.dispose();
|
||||
}
|
||||
|
||||
static public void startCapturerAndRender(VideoCapturerAndroid capturer)
|
||||
throws InterruptedException {
|
||||
PeerConnectionFactory factory = new PeerConnectionFactory(null /* options */);
|
||||
VideoSource source =
|
||||
factory.createVideoSource(capturer, new MediaConstraints());
|
||||
VideoTrack track = factory.createVideoTrack("dummy", source);
|
||||
RendererCallbacks callbacks = new RendererCallbacks();
|
||||
track.addRenderer(new VideoRenderer(callbacks));
|
||||
assertTrue(callbacks.WaitForNextFrameToRender() > 0);
|
||||
track.dispose();
|
||||
source.dispose();
|
||||
factory.dispose();
|
||||
}
|
||||
|
||||
static public void switchCamera(VideoCapturerAndroid capturer) throws InterruptedException {
|
||||
PeerConnectionFactory factory = new PeerConnectionFactory(null /* options */);
|
||||
VideoSource source =
|
||||
factory.createVideoSource(capturer, new MediaConstraints());
|
||||
VideoTrack track = factory.createVideoTrack("dummy", source);
|
||||
|
||||
// Array with one element to avoid final problem in nested classes.
|
||||
final boolean[] cameraSwitchSuccessful = new boolean[1];
|
||||
final CountDownLatch barrier = new CountDownLatch(1);
|
||||
capturer.switchCamera(new VideoCapturerAndroid.CameraSwitchHandler() {
|
||||
@Override
|
||||
public void onCameraSwitchDone(boolean isFrontCamera) {
|
||||
cameraSwitchSuccessful[0] = true;
|
||||
barrier.countDown();
|
||||
}
|
||||
@Override
|
||||
public void onCameraSwitchError(String errorDescription) {
|
||||
cameraSwitchSuccessful[0] = false;
|
||||
barrier.countDown();
|
||||
}
|
||||
});
|
||||
// Wait until the camera has been switched.
|
||||
barrier.await();
|
||||
|
||||
// Check result.
|
||||
if (HaveTwoCameras()) {
|
||||
assertTrue(cameraSwitchSuccessful[0]);
|
||||
} else {
|
||||
assertFalse(cameraSwitchSuccessful[0]);
|
||||
}
|
||||
// Ensure that frames are received.
|
||||
RendererCallbacks callbacks = new RendererCallbacks();
|
||||
track.addRenderer(new VideoRenderer(callbacks));
|
||||
assertTrue(callbacks.WaitForNextFrameToRender() > 0);
|
||||
track.dispose();
|
||||
source.dispose();
|
||||
factory.dispose();
|
||||
}
|
||||
|
||||
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 SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(
|
||||
"SurfaceTextureHelper test" /* threadName */, null /* sharedContext */);
|
||||
final FakeCapturerObserver observer = new FakeCapturerObserver();
|
||||
capturer.startCapture(format.width, format.height, format.framerate.max,
|
||||
surfaceTextureHelper, appContext, observer);
|
||||
// Make sure camera is started and first frame is received and then stop it.
|
||||
assertTrue(observer.WaitForCapturerToStart());
|
||||
observer.WaitForNextCapturedFrame();
|
||||
capturer.stopCapture();
|
||||
if (capturer.isCapturingToTexture()) {
|
||||
surfaceTextureHelper.returnTextureFrame();
|
||||
}
|
||||
release(capturer);
|
||||
surfaceTextureHelper.dispose();
|
||||
|
||||
assertTrue(events.onCameraOpeningCalled);
|
||||
assertTrue(events.onFirstFrameAvailableCalled);
|
||||
}
|
||||
|
||||
static public void cameraCallsAfterStop(
|
||||
VideoCapturerAndroid capturer, Context appContext) throws InterruptedException {
|
||||
final List<CaptureFormat> formats = capturer.getSupportedFormats();
|
||||
final CameraEnumerationAndroid.CaptureFormat format = formats.get(0);
|
||||
|
||||
final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(
|
||||
"SurfaceTextureHelper test" /* threadName */, null /* sharedContext */);
|
||||
final FakeCapturerObserver observer = new FakeCapturerObserver();
|
||||
capturer.startCapture(format.width, format.height, format.framerate.max,
|
||||
surfaceTextureHelper, appContext, observer);
|
||||
// Make sure camera is started and then stop it.
|
||||
assertTrue(observer.WaitForCapturerToStart());
|
||||
capturer.stopCapture();
|
||||
if (capturer.isCapturingToTexture()) {
|
||||
surfaceTextureHelper.returnTextureFrame();
|
||||
}
|
||||
|
||||
// We can't change |capturer| at this point, but we should not crash.
|
||||
capturer.switchCamera(null);
|
||||
capturer.onOutputFormatRequest(640, 480, 15);
|
||||
capturer.changeCaptureFormat(640, 480, 15);
|
||||
|
||||
release(capturer);
|
||||
surfaceTextureHelper.dispose();
|
||||
}
|
||||
|
||||
static public void stopRestartVideoSource(VideoCapturerAndroid capturer)
|
||||
throws InterruptedException {
|
||||
PeerConnectionFactory factory = new PeerConnectionFactory(null /* options */);
|
||||
VideoSource source =
|
||||
factory.createVideoSource(capturer, new MediaConstraints());
|
||||
VideoTrack track = factory.createVideoTrack("dummy", source);
|
||||
RendererCallbacks callbacks = new RendererCallbacks();
|
||||
track.addRenderer(new VideoRenderer(callbacks));
|
||||
assertTrue(callbacks.WaitForNextFrameToRender() > 0);
|
||||
assertEquals(MediaSource.State.LIVE, source.state());
|
||||
|
||||
source.stop();
|
||||
assertEquals(MediaSource.State.ENDED, source.state());
|
||||
|
||||
source.restart();
|
||||
assertTrue(callbacks.WaitForNextFrameToRender() > 0);
|
||||
assertEquals(MediaSource.State.LIVE, source.state());
|
||||
track.dispose();
|
||||
source.dispose();
|
||||
factory.dispose();
|
||||
}
|
||||
|
||||
static public void startStopWithDifferentResolutions(VideoCapturerAndroid capturer,
|
||||
Context appContext) throws InterruptedException {
|
||||
final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(
|
||||
"SurfaceTextureHelper test" /* threadName */, null /* sharedContext */);
|
||||
FakeCapturerObserver observer = new FakeCapturerObserver();
|
||||
List<CaptureFormat> formats = capturer.getSupportedFormats();
|
||||
|
||||
for(int i = 0; i < 3 ; ++i) {
|
||||
CameraEnumerationAndroid.CaptureFormat format = formats.get(i);
|
||||
capturer.startCapture(format.width, format.height, format.framerate.max,
|
||||
surfaceTextureHelper, appContext, observer);
|
||||
assertTrue(observer.WaitForCapturerToStart());
|
||||
observer.WaitForNextCapturedFrame();
|
||||
|
||||
// Check the frame size. The actual width and height depend on how the capturer is mounted.
|
||||
final boolean identicalResolution = (observer.frameWidth() == format.width
|
||||
&& observer.frameHeight() == format.height);
|
||||
final boolean flippedResolution = (observer.frameWidth() == format.height
|
||||
&& observer.frameHeight() == format.width);
|
||||
if (!identicalResolution && !flippedResolution) {
|
||||
fail("Wrong resolution, got: " + observer.frameWidth() + "x" + observer.frameHeight()
|
||||
+ " expected: " + format.width + "x" + format.height + " or " + format.height + "x"
|
||||
+ format.width);
|
||||
}
|
||||
|
||||
if (capturer.isCapturingToTexture()) {
|
||||
assertEquals(0, observer.frameSize());
|
||||
} else {
|
||||
assertTrue(format.frameSize() <= observer.frameSize());
|
||||
}
|
||||
capturer.stopCapture();
|
||||
if (capturer.isCapturingToTexture()) {
|
||||
surfaceTextureHelper.returnTextureFrame();
|
||||
}
|
||||
}
|
||||
release(capturer);
|
||||
surfaceTextureHelper.dispose();
|
||||
}
|
||||
|
||||
static void waitUntilIdle(VideoCapturerAndroid capturer) throws InterruptedException {
|
||||
final CountDownLatch barrier = new CountDownLatch(1);
|
||||
capturer.getCameraThreadHandler().post(new Runnable() {
|
||||
@Override public void run() {
|
||||
barrier.countDown();
|
||||
}
|
||||
});
|
||||
barrier.await();
|
||||
}
|
||||
|
||||
static public void startWhileCameraIsAlreadyOpen(
|
||||
VideoCapturerAndroid capturer, Context appContext) throws InterruptedException {
|
||||
final List<CaptureFormat> formats = capturer.getSupportedFormats();
|
||||
final CameraEnumerationAndroid.CaptureFormat format = formats.get(0);
|
||||
android.hardware.Camera camera = android.hardware.Camera.open(capturer.getCurrentCameraId());
|
||||
|
||||
final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(
|
||||
"SurfaceTextureHelper test" /* threadName */, null /* sharedContext */);
|
||||
final FakeCapturerObserver observer = new FakeCapturerObserver();
|
||||
capturer.startCapture(format.width, format.height, format.framerate.max,
|
||||
surfaceTextureHelper, appContext, observer);
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
|
||||
// The first opened camera client will be evicted.
|
||||
assertTrue(observer.WaitForCapturerToStart());
|
||||
capturer.stopCapture();
|
||||
} else {
|
||||
assertFalse(observer.WaitForCapturerToStart());
|
||||
}
|
||||
|
||||
release(capturer);
|
||||
camera.release();
|
||||
surfaceTextureHelper.dispose();
|
||||
}
|
||||
|
||||
static public void startWhileCameraIsAlreadyOpenAndCloseCamera(
|
||||
VideoCapturerAndroid capturer, Context appContext) throws InterruptedException {
|
||||
final PeerConnectionFactory factory = new PeerConnectionFactory(null /* options */);
|
||||
final List<CaptureFormat> formats = capturer.getSupportedFormats();
|
||||
final CameraEnumerationAndroid.CaptureFormat format = formats.get(0);
|
||||
android.hardware.Camera camera = android.hardware.Camera.open(capturer.getCurrentCameraId());
|
||||
|
||||
final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(
|
||||
"SurfaceTextureHelper test" /* threadName */, null /* sharedContext */);
|
||||
final VideoSource source = factory.createVideoSource(capturer, new MediaConstraints());
|
||||
final VideoTrack track = factory.createVideoTrack("dummy", source);
|
||||
final RendererCallbacks callbacks = new RendererCallbacks();
|
||||
track.addRenderer(new VideoRenderer(callbacks));
|
||||
waitUntilIdle(capturer);
|
||||
|
||||
camera.release();
|
||||
|
||||
// Make sure camera is started and first frame is received and then stop it.
|
||||
callbacks.WaitForNextFrameToRender();
|
||||
capturer.stopCapture();
|
||||
release(capturer);
|
||||
surfaceTextureHelper.dispose();
|
||||
}
|
||||
|
||||
static public void startWhileCameraIsAlreadyOpenAndStop(
|
||||
VideoCapturerAndroid capturer, Context appContext) throws InterruptedException {
|
||||
final List<CaptureFormat> formats = capturer.getSupportedFormats();
|
||||
final CameraEnumerationAndroid.CaptureFormat format = formats.get(0);
|
||||
android.hardware.Camera camera = android.hardware.Camera.open(capturer.getCurrentCameraId());
|
||||
|
||||
final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(
|
||||
"SurfaceTextureHelper test" /* threadName */, null /* sharedContext */);
|
||||
final FakeCapturerObserver observer = new FakeCapturerObserver();
|
||||
capturer.startCapture(format.width, format.height, format.framerate.max,
|
||||
surfaceTextureHelper, appContext, observer);
|
||||
capturer.stopCapture();
|
||||
release(capturer);
|
||||
camera.release();
|
||||
surfaceTextureHelper.dispose();
|
||||
}
|
||||
|
||||
static public void returnBufferLate(VideoCapturerAndroid capturer,
|
||||
Context appContext) throws InterruptedException {
|
||||
final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(
|
||||
"SurfaceTextureHelper test" /* threadName */, null /* sharedContext */);
|
||||
FakeCapturerObserver observer = new FakeCapturerObserver();
|
||||
|
||||
List<CaptureFormat> formats = capturer.getSupportedFormats();
|
||||
CameraEnumerationAndroid.CaptureFormat format = formats.get(0);
|
||||
capturer.startCapture(format.width, format.height, format.framerate.max,
|
||||
surfaceTextureHelper, appContext, observer);
|
||||
assertTrue(observer.WaitForCapturerToStart());
|
||||
|
||||
observer.WaitForNextCapturedFrame();
|
||||
capturer.stopCapture();
|
||||
List<Long> listOftimestamps = observer.getCopyAndResetListOftimeStamps();
|
||||
assertTrue(listOftimestamps.size() >= 1);
|
||||
|
||||
format = formats.get(1);
|
||||
capturer.startCapture(format.width, format.height, format.framerate.max,
|
||||
surfaceTextureHelper, appContext, observer);
|
||||
observer.WaitForCapturerToStart();
|
||||
if (capturer.isCapturingToTexture()) {
|
||||
surfaceTextureHelper.returnTextureFrame();
|
||||
}
|
||||
|
||||
observer.WaitForNextCapturedFrame();
|
||||
capturer.stopCapture();
|
||||
|
||||
listOftimestamps = observer.getCopyAndResetListOftimeStamps();
|
||||
assertTrue(listOftimestamps.size() >= 1);
|
||||
if (capturer.isCapturingToTexture()) {
|
||||
surfaceTextureHelper.returnTextureFrame();
|
||||
}
|
||||
|
||||
release(capturer);
|
||||
surfaceTextureHelper.dispose();
|
||||
}
|
||||
|
||||
static public void returnBufferLateEndToEnd(VideoCapturerAndroid capturer)
|
||||
throws InterruptedException {
|
||||
final PeerConnectionFactory factory = new PeerConnectionFactory(null /* options */);
|
||||
final VideoSource source = factory.createVideoSource(capturer, new MediaConstraints());
|
||||
final VideoTrack track = factory.createVideoTrack("dummy", source);
|
||||
final FakeAsyncRenderer renderer = new FakeAsyncRenderer();
|
||||
|
||||
track.addRenderer(new VideoRenderer(renderer));
|
||||
// Wait for at least one frame that has not been returned.
|
||||
assertFalse(renderer.waitForPendingFrames().isEmpty());
|
||||
|
||||
capturer.stopCapture();
|
||||
|
||||
// Dispose everything.
|
||||
track.dispose();
|
||||
source.dispose();
|
||||
factory.dispose();
|
||||
|
||||
// Return the frame(s), on a different thread out of spite.
|
||||
final List<I420Frame> pendingFrames = renderer.waitForPendingFrames();
|
||||
final Thread returnThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (I420Frame frame : pendingFrames) {
|
||||
VideoRenderer.renderFrameDone(frame);
|
||||
}
|
||||
}
|
||||
});
|
||||
returnThread.start();
|
||||
returnThread.join();
|
||||
}
|
||||
|
||||
static public void cameraFreezedEventOnBufferStarvationUsingTextures(
|
||||
VideoCapturerAndroid capturer,
|
||||
CameraEvents events, Context appContext) throws InterruptedException {
|
||||
assertTrue("Not capturing to textures.", capturer.isCapturingToTexture());
|
||||
|
||||
final List<CaptureFormat> formats = capturer.getSupportedFormats();
|
||||
final CameraEnumerationAndroid.CaptureFormat format = formats.get(0);
|
||||
|
||||
final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(
|
||||
"SurfaceTextureHelper test" /* threadName */, null /* sharedContext */);
|
||||
final FakeCapturerObserver observer = new FakeCapturerObserver();
|
||||
capturer.startCapture(format.width, format.height, format.framerate.max,
|
||||
surfaceTextureHelper, appContext, observer);
|
||||
// Make sure camera is started.
|
||||
assertTrue(observer.WaitForCapturerToStart());
|
||||
// Since we don't return the buffer, we should get a starvation message if we are
|
||||
// capturing to a texture.
|
||||
assertEquals("Camera failure. Client must return video buffers.",
|
||||
events.WaitForCameraFreezed());
|
||||
|
||||
capturer.stopCapture();
|
||||
if (capturer.isCapturingToTexture()) {
|
||||
surfaceTextureHelper.returnTextureFrame();
|
||||
}
|
||||
|
||||
release(capturer);
|
||||
surfaceTextureHelper.dispose();
|
||||
}
|
||||
|
||||
static public void scaleCameraOutput(VideoCapturerAndroid capturer) throws InterruptedException {
|
||||
PeerConnectionFactory factory = new PeerConnectionFactory(null /* options */);
|
||||
VideoSource source =
|
||||
factory.createVideoSource(capturer, new MediaConstraints());
|
||||
VideoTrack track = factory.createVideoTrack("dummy", source);
|
||||
RendererCallbacks renderer = new RendererCallbacks();
|
||||
track.addRenderer(new VideoRenderer(renderer));
|
||||
assertTrue(renderer.WaitForNextFrameToRender() > 0);
|
||||
|
||||
final int startWidth = renderer.frameWidth();
|
||||
final int startHeight = renderer.frameHeight();
|
||||
final int frameRate = 30;
|
||||
final int scaledWidth = startWidth / 2;
|
||||
final int scaledHeight = startHeight / 2;
|
||||
|
||||
// Request the captured frames to be scaled.
|
||||
capturer.onOutputFormatRequest(scaledWidth, scaledHeight, frameRate);
|
||||
|
||||
boolean gotExpectedResolution = false;
|
||||
int numberOfInspectedFrames = 0;
|
||||
|
||||
do {
|
||||
renderer.WaitForNextFrameToRender();
|
||||
++numberOfInspectedFrames;
|
||||
|
||||
gotExpectedResolution = (renderer.frameWidth() == scaledWidth
|
||||
&& renderer.frameHeight() == scaledHeight);
|
||||
} while (!gotExpectedResolution && numberOfInspectedFrames < 30);
|
||||
|
||||
source.stop();
|
||||
track.dispose();
|
||||
source.dispose();
|
||||
factory.dispose();
|
||||
|
||||
assertTrue(gotExpectedResolution);
|
||||
}
|
||||
|
||||
}
|
||||
@ -11,10 +11,11 @@
|
||||
package org.webrtc;
|
||||
|
||||
import static java.lang.Math.abs;
|
||||
import android.graphics.ImageFormat;
|
||||
|
||||
import org.webrtc.Logging;
|
||||
|
||||
import android.graphics.ImageFormat;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
@ -137,6 +138,18 @@ public class CameraEnumerationAndroid {
|
||||
+ ", Orientation " + info.orientation;
|
||||
}
|
||||
|
||||
// Returns the camera index for camera with name |deviceName|, or throws IllegalArgumentException
|
||||
// if no such camera can be found.
|
||||
public static int getCameraIndex(String deviceName) {
|
||||
Logging.d(TAG, "getCameraIndex: " + deviceName);
|
||||
for (int i = 0; i < android.hardware.Camera.getNumberOfCameras(); ++i) {
|
||||
if (deviceName.equals(CameraEnumerationAndroid.getDeviceName(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No such camera: " + deviceName);
|
||||
}
|
||||
|
||||
// Returns the name of the front facing camera. Returns null if the
|
||||
// camera can not be used or does not exist.
|
||||
public static String getNameOfFrontFacingDevice() {
|
||||
|
||||
@ -10,15 +10,14 @@
|
||||
|
||||
package org.webrtc;
|
||||
|
||||
import org.webrtc.CameraEnumerationAndroid.CaptureFormat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.view.Surface;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.webrtc.CameraEnumerationAndroid.CaptureFormat;
|
||||
import org.webrtc.Logging;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashSet;
|
||||
@ -199,7 +198,7 @@ public class VideoCapturerAndroid implements
|
||||
|
||||
// Helper function to retrieve the current camera id synchronously. Note that the camera id might
|
||||
// change at any point by switchCamera() calls.
|
||||
int getCurrentCameraId() {
|
||||
private int getCurrentCameraId() {
|
||||
synchronized (cameraIdLock) {
|
||||
return id;
|
||||
}
|
||||
@ -223,7 +222,7 @@ public class VideoCapturerAndroid implements
|
||||
if (cameraName == null || cameraName == "") {
|
||||
this.id = 0;
|
||||
} else {
|
||||
this.id = getCameraIndex(cameraName);
|
||||
this.id = CameraEnumerationAndroid.getCameraIndex(cameraName);
|
||||
}
|
||||
this.eventsHandler = eventsHandler;
|
||||
isCapturingToTexture = captureToTexture;
|
||||
@ -240,18 +239,6 @@ public class VideoCapturerAndroid implements
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the camera index for camera with name |deviceName|, or throws IllegalArgumentException
|
||||
// if no such camera can be found.
|
||||
private static int getCameraIndex(String deviceName) {
|
||||
Logging.d(TAG, "getCameraIndex: " + deviceName);
|
||||
for (int i = 0; i < android.hardware.Camera.getNumberOfCameras(); ++i) {
|
||||
if (deviceName.equals(CameraEnumerationAndroid.getDeviceName(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No such camera: " + deviceName);
|
||||
}
|
||||
|
||||
private boolean maybePostOnCameraThread(Runnable runnable) {
|
||||
return maybePostDelayedOnCameraThread(0 /* delayMs */, runnable);
|
||||
}
|
||||
|
||||
@ -15,7 +15,6 @@ import android.content.Context;
|
||||
import java.util.List;
|
||||
|
||||
// Base interface for all VideoCapturers to implement.
|
||||
// TODO(magjed): Simplify and improve this interface.
|
||||
public interface VideoCapturer {
|
||||
// Interface used for providing callbacks to an observer.
|
||||
public interface CapturerObserver {
|
||||
@ -83,7 +82,6 @@ public interface VideoCapturer {
|
||||
int width, int height, int framerate);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a list with all the formats this VideoCapturer supports.
|
||||
*/
|
||||
@ -104,6 +102,10 @@ public interface VideoCapturer {
|
||||
*/
|
||||
void stopCapture() throws InterruptedException;
|
||||
|
||||
void onOutputFormatRequest(int width, int height, int framerate);
|
||||
|
||||
void changeCaptureFormat(int width, int height, int framerate);
|
||||
|
||||
/**
|
||||
* Perform any final cleanup here. No more capturing will be done after this call.
|
||||
*/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user