Bug 1818599 - pt5 - remove third_party/libwebrtc/sdk/android/instrumentationtests r=ng,webrtc-reviewers

Depends on D173346

Differential Revision: https://phabricator.services.mozilla.com/D173347
This commit is contained in:
Michael Froman 2023-03-23 19:11:41 +00:00
parent 1a75e5633c
commit c1ebba1d9f
38 changed files with 0 additions and 8216 deletions

View File

@ -1,38 +0,0 @@
<!--
* Copyright 2017 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.
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.webrtc">
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.RUN_INSTRUMENTATION" />
<application>
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="org.chromium.base.test.BaseChromiumAndroidJUnitRunner"
tools:ignore="MissingPrefix"
android:targetPackage="org.webrtc"
android:label="Tests for WebRTC Android SDK"/>
</manifest>

View File

@ -1,18 +0,0 @@
# This file is used to override default values used by the Ant build system.
#
# This file must be checked into Version Control Systems, as it is
# integral to the build system of your project.
# This file is only used by the Ant script.
# You can use this to override default values such as
# 'source.dir' for the location of your java source folder and
# 'out.dir' for the location of your output folder.
# You can also use it define how the release builds are signed by declaring
# the following properties:
# 'key.store' for the location of your keystore and
# 'key.alias' for the name of the key to use.
# The password will be asked during the build when you use the 'release' target.
source.dir=../java/testcommon/src;src

View File

@ -1,92 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="libjingle_peerconnection_android_unittest" default="help">
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<property file="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
'android' tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
Properties related to the SDK location or the project target should
be updated using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="ant.properties" />
<!-- if sdk.dir was not set from one of the property file, then
get it from the ANDROID_HOME env var.
This must be done before we load project.properties since
the proguard config can use sdk.dir -->
<property environment="env" />
<condition property="sdk.dir" value="${env.ANDROID_SDK_ROOT}">
<isset property="env.ANDROID_SDK_ROOT" />
</condition>
<!-- The project.properties file is created and updated by the 'android'
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir"
/>
<!--
Import per project custom build rules if present at the root of the project.
This is the place to put custom intermediary targets such as:
-pre-build
-pre-compile
-post-compile (This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir})
-post-package
-post-build
-pre-clean
-->
<import file="custom_rules.xml" optional="true" />
<!-- Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<!-- version-tag: 1 -->
<import file="${sdk.dir}/tools/ant/build.xml" />
</project>

View File

@ -1,31 +0,0 @@
/*
* Copyright 2018 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.
*/
#include <memory>
#include "rtc_base/logging.h"
#include "sdk/android/native_api/jni/java_types.h"
#include "sdk/android/src/jni/jni_helpers.h"
namespace webrtc {
namespace jni {
JNI_FUNCTION_DECLARATION(void,
LoggableTest_nativeLogInfoTestMessage,
JNIEnv* jni,
jclass,
jstring j_message) {
std::string message =
JavaToNativeString(jni, JavaParamRef<jstring>(j_message));
RTC_LOG(LS_INFO) << message;
}
} // namespace jni
} // namespace webrtc

View File

@ -1,16 +0,0 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-22
java.compilerargs=-Xlint:all -Werror

View File

@ -1,200 +0,0 @@
/*
* Copyright 2017 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
/** Unit tests for {@link AndroidVideoDecoder}. */
@RunWith(Parameterized.class)
public final class AndroidVideoDecoderInstrumentationTest {
@Parameters(name = "{0};useEglContext={1}")
public static Collection<Object[]> parameters() {
return Arrays.asList(new Object[] {/*codecName=*/"VP8", /*useEglContext=*/false},
new Object[] {/*codecName=*/"VP8", /*useEglContext=*/true},
new Object[] {/*codecName=*/"H264", /*useEglContext=*/false},
new Object[] {/*codecName=*/"H264", /*useEglContext=*/true});
}
private final VideoCodecInfo codecType;
private final boolean useEglContext;
public AndroidVideoDecoderInstrumentationTest(String codecName, boolean useEglContext) {
if (codecName.equals("H264")) {
this.codecType = H264Utils.DEFAULT_H264_BASELINE_PROFILE_CODEC;
} else {
this.codecType = new VideoCodecInfo(codecName, new HashMap<>());
}
this.useEglContext = useEglContext;
}
private static final String TAG = "AndroidVideoDecoderInstrumentationTest";
private static final int TEST_FRAME_COUNT = 10;
private static final int TEST_FRAME_WIDTH = 640;
private static final int TEST_FRAME_HEIGHT = 360;
private VideoFrame.I420Buffer[] TEST_FRAMES;
private static final boolean ENABLE_INTEL_VP8_ENCODER = true;
private static final boolean ENABLE_H264_HIGH_PROFILE = true;
private static final VideoEncoder.Settings ENCODER_SETTINGS = new VideoEncoder.Settings(
1 /* core */,
getAlignedNumber(TEST_FRAME_WIDTH, HardwareVideoEncoderTest.getPixelAlignmentRequired()),
getAlignedNumber(TEST_FRAME_HEIGHT, HardwareVideoEncoderTest.getPixelAlignmentRequired()),
300 /* kbps */, 30 /* fps */, 1 /* numberOfSimulcastStreams */, true /* automaticResizeOn */,
/* capabilities= */ new VideoEncoder.Capabilities(false /* lossNotification */));
private static final int DECODE_TIMEOUT_MS = 1000;
private static final VideoDecoder.Settings SETTINGS = new VideoDecoder.Settings(1 /* core */,
getAlignedNumber(TEST_FRAME_WIDTH, HardwareVideoEncoderTest.getPixelAlignmentRequired()),
getAlignedNumber(TEST_FRAME_HEIGHT, HardwareVideoEncoderTest.getPixelAlignmentRequired()));
private static class MockDecodeCallback implements VideoDecoder.Callback {
private BlockingQueue<VideoFrame> frameQueue = new LinkedBlockingQueue<>();
@Override
public void onDecodedFrame(VideoFrame frame, Integer decodeTimeMs, Integer qp) {
assertNotNull(frame);
frameQueue.offer(frame);
}
public void assertFrameDecoded(EncodedImage testImage, VideoFrame.I420Buffer testBuffer) {
VideoFrame decodedFrame = poll();
VideoFrame.Buffer decodedBuffer = decodedFrame.getBuffer();
assertEquals(testImage.encodedWidth, decodedBuffer.getWidth());
assertEquals(testImage.encodedHeight, decodedBuffer.getHeight());
// TODO(sakal): Decoder looses the nanosecond precision. This is not a problem in practice
// because C++ EncodedImage stores the timestamp in milliseconds.
assertEquals(testImage.captureTimeNs / 1000, decodedFrame.getTimestampNs() / 1000);
assertEquals(testImage.rotation, decodedFrame.getRotation());
}
public VideoFrame poll() {
try {
VideoFrame frame = frameQueue.poll(DECODE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
assertNotNull("Timed out waiting for the frame to be decoded.", frame);
return frame;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
private static VideoFrame.I420Buffer[] generateTestFrames() {
VideoFrame.I420Buffer[] result = new VideoFrame.I420Buffer[TEST_FRAME_COUNT];
for (int i = 0; i < TEST_FRAME_COUNT; i++) {
result[i] = JavaI420Buffer.allocate(
getAlignedNumber(TEST_FRAME_WIDTH, HardwareVideoEncoderTest.getPixelAlignmentRequired()),
getAlignedNumber(
TEST_FRAME_HEIGHT, HardwareVideoEncoderTest.getPixelAlignmentRequired()));
// TODO(sakal): Generate content for the test frames.
}
return result;
}
private final EncodedImage[] encodedTestFrames = new EncodedImage[TEST_FRAME_COUNT];
private EglBase14 eglBase;
private VideoDecoderFactory createDecoderFactory(EglBase.Context eglContext) {
return new HardwareVideoDecoderFactory(eglContext);
}
private @Nullable VideoDecoder createDecoder() {
VideoDecoderFactory factory =
createDecoderFactory(useEglContext ? eglBase.getEglBaseContext() : null);
return factory.createDecoder(codecType);
}
private void encodeTestFrames() {
VideoEncoderFactory encoderFactory = new HardwareVideoEncoderFactory(
eglBase.getEglBaseContext(), ENABLE_INTEL_VP8_ENCODER, ENABLE_H264_HIGH_PROFILE);
VideoEncoder encoder = encoderFactory.createEncoder(codecType);
HardwareVideoEncoderTest.MockEncoderCallback encodeCallback =
new HardwareVideoEncoderTest.MockEncoderCallback();
assertEquals(VideoCodecStatus.OK, encoder.initEncode(ENCODER_SETTINGS, encodeCallback));
long lastTimestampNs = 0;
for (int i = 0; i < TEST_FRAME_COUNT; i++) {
lastTimestampNs += TimeUnit.SECONDS.toNanos(1) / ENCODER_SETTINGS.maxFramerate;
VideoEncoder.EncodeInfo info = new VideoEncoder.EncodeInfo(
new EncodedImage.FrameType[] {EncodedImage.FrameType.VideoFrameDelta});
HardwareVideoEncoderTest.testEncodeFrame(
encoder, new VideoFrame(TEST_FRAMES[i], 0 /* rotation */, lastTimestampNs), info);
encodedTestFrames[i] = encodeCallback.poll();
}
assertEquals(VideoCodecStatus.OK, encoder.release());
}
private static int getAlignedNumber(int number, int alignment) {
return (number / alignment) * alignment;
}
@Before
public void setUp() {
NativeLibrary.initialize(new NativeLibrary.DefaultLoader(), TestConstants.NATIVE_LIBRARY);
TEST_FRAMES = generateTestFrames();
eglBase = EglBase.createEgl14(EglBase.CONFIG_PLAIN);
eglBase.createDummyPbufferSurface();
eglBase.makeCurrent();
encodeTestFrames();
}
@After
public void tearDown() {
eglBase.release();
}
@Test
@SmallTest
public void testInitialize() {
VideoDecoder decoder = createDecoder();
assertEquals(VideoCodecStatus.OK, decoder.initDecode(SETTINGS, null /* decodeCallback */));
assertEquals(VideoCodecStatus.OK, decoder.release());
}
@Test
@SmallTest
public void testDecode() {
VideoDecoder decoder = createDecoder();
MockDecodeCallback callback = new MockDecodeCallback();
assertEquals(VideoCodecStatus.OK, decoder.initDecode(SETTINGS, callback));
for (int i = 0; i < TEST_FRAME_COUNT; i++) {
assertEquals(VideoCodecStatus.OK,
decoder.decode(encodedTestFrames[i],
new VideoDecoder.DecodeInfo(false /* isMissingFrames */, 0 /* renderTimeMs */)));
callback.assertFrameDecoded(encodedTestFrames[i], TEST_FRAMES[i]);
}
assertEquals(VideoCodecStatus.OK, decoder.release());
}
}

View File

@ -1,54 +0,0 @@
/*
* Copyright 2018 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 com.google.common.truth.Truth.assertThat;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
public final class BuiltinAudioCodecsFactoryFactoryTest {
@Before
public void setUp() {
System.loadLibrary(TestConstants.NATIVE_LIBRARY);
}
@Test
@SmallTest
public void testAudioEncoderFactoryFactoryTest() throws Exception {
BuiltinAudioEncoderFactoryFactory factory = new BuiltinAudioEncoderFactoryFactory();
long aef = 0;
try {
aef = factory.createNativeAudioEncoderFactory();
assertThat(aef).isNotEqualTo(0);
} finally {
if (aef != 0) {
JniCommon.nativeReleaseRef(aef);
}
}
}
@Test
@SmallTest
public void testAudioDecoderFactoryFactoryTest() throws Exception {
BuiltinAudioDecoderFactoryFactory factory = new BuiltinAudioDecoderFactoryFactory();
long adf = 0;
try {
adf = factory.createNativeAudioDecoderFactory();
assertThat(adf).isNotEqualTo(0);
} finally {
if (adf != 0) {
JniCommon.nativeReleaseRef(adf);
}
}
}
}

View File

@ -1,205 +0,0 @@
/*
* 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 android.content.Context;
import android.support.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class Camera1CapturerUsingByteBufferTest {
static final String TAG = "Camera1CapturerUsingByteBufferTest";
private static class TestObjectFactory extends CameraVideoCapturerTestFixtures.TestObjectFactory {
@Override
public boolean isCapturingToTexture() {
return false;
}
@Override
public CameraEnumerator getCameraEnumerator() {
return new Camera1Enumerator(false);
}
@Override
public Context getAppContext() {
return InstrumentationRegistry.getTargetContext();
}
@SuppressWarnings("deprecation")
@Override
public Object rawOpenCamera(String cameraName) {
return android.hardware.Camera.open(Camera1Enumerator.getCameraIndex(cameraName));
}
@SuppressWarnings("deprecation")
@Override
public void rawCloseCamera(Object camera) {
((android.hardware.Camera) camera).release();
}
}
private CameraVideoCapturerTestFixtures fixtures;
@Before
public void setUp() {
fixtures = new CameraVideoCapturerTestFixtures(new TestObjectFactory());
}
@After
public void tearDown() {
fixtures.dispose();
}
@Test
@SmallTest
public void testCreateAndDispose() throws InterruptedException {
fixtures.createCapturerAndDispose();
}
@Test
@SmallTest
public void testCreateNonExistingCamera() throws InterruptedException {
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.
@Test
@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.
@Test
@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.
@Test
@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.
@Test
@MediumTest
public void testSwitchVideoCapturer() throws InterruptedException {
fixtures.switchCamera();
}
@Test
@MediumTest
public void testSwitchVideoCapturerToSpecificCameraName() throws InterruptedException {
fixtures.switchCamera(true /* specifyCameraName */);
}
@Test
@MediumTest
public void testCameraEvents() throws InterruptedException {
fixtures.cameraEventsInvoked();
}
// Test what happens when attempting to call e.g. switchCamera() after camera has been stopped.
@Test
@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.
@Test
@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.
@Test
@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.
@Test
@LargeTest
public void testReturnBufferLate() throws InterruptedException {
fixtures.returnBufferLate();
}
// 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.
@Test
@MediumTest
public void testReturnBufferLateEndToEnd() throws InterruptedException {
fixtures.returnBufferLateEndToEnd();
}
// This test that frames forwarded to a renderer is scaled if adaptOutputFormat is
// called. This test both Java and C++ parts of of the stack.
@Test
@MediumTest
public void testScaleCameraOutput() throws InterruptedException {
fixtures.scaleCameraOutput();
}
// This test that frames forwarded to a renderer is cropped to a new orientation if
// adaptOutputFormat is called in such a way. This test both Java and C++ parts of of the stack.
@Test
@MediumTest
public void testCropCameraOutput() throws InterruptedException {
fixtures.cropCameraOutput();
}
// This test that an error is reported if the camera is already opened
// when CameraVideoCapturer is started.
@Test
@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.
@Test
@LargeTest
public void testStartWhileCameraIsAlreadyOpenAndCloseCamera() throws InterruptedException {
fixtures.startWhileCameraIsAlreadyOpenAndCloseCamera();
}
// This test that CameraVideoCapturer.stop can be called while CameraVideoCapturer is
// re-trying to start.
@Test
@MediumTest
public void testStartWhileCameraIsAlreadyOpenAndStop() throws InterruptedException {
fixtures.startWhileCameraIsAlreadyOpenAndStop();
}
}

View File

@ -1,208 +0,0 @@
/*
* 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 android.content.Context;
import android.support.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class Camera1CapturerUsingTextureTest {
static final String TAG = "Camera1CapturerUsingTextureTest";
private static class TestObjectFactory extends CameraVideoCapturerTestFixtures.TestObjectFactory {
@Override
public CameraEnumerator getCameraEnumerator() {
return new Camera1Enumerator();
}
@Override
public Context getAppContext() {
return InstrumentationRegistry.getTargetContext();
}
@SuppressWarnings("deprecation")
@Override
public Object rawOpenCamera(String cameraName) {
return android.hardware.Camera.open(Camera1Enumerator.getCameraIndex(cameraName));
}
@SuppressWarnings("deprecation")
@Override
public void rawCloseCamera(Object camera) {
((android.hardware.Camera) camera).release();
}
}
private CameraVideoCapturerTestFixtures fixtures;
@Before
public void setUp() {
fixtures = new CameraVideoCapturerTestFixtures(new TestObjectFactory());
}
@After
public void tearDown() {
fixtures.dispose();
}
@Test
@SmallTest
public void testCreateAndDispose() throws InterruptedException {
fixtures.createCapturerAndDispose();
}
@Test
@SmallTest
public void testCreateNonExistingCamera() throws InterruptedException {
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.
@Test
@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.
@Test
@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.
@Test
@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.
@Test
@MediumTest
public void testSwitchVideoCapturer() throws InterruptedException {
fixtures.switchCamera();
}
@Test
@MediumTest
public void testSwitchVideoCapturerToSpecificCameraName() throws InterruptedException {
fixtures.switchCamera(true /* specifyCameraName */);
}
@Test
@MediumTest
public void testCameraEvents() throws InterruptedException {
fixtures.cameraEventsInvoked();
}
// Test what happens when attempting to call e.g. switchCamera() after camera has been stopped.
@Test
@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.
@Test
@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.
@Test
@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.
@Test
@LargeTest
public void testReturnBufferLate() throws InterruptedException {
fixtures.returnBufferLate();
}
// 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.
@Test
@MediumTest
public void testReturnBufferLateEndToEnd() throws InterruptedException {
fixtures.returnBufferLateEndToEnd();
}
// This test that CameraEventsHandler.onError is triggered if video buffers are not returned to
// the capturer.
@Test
@LargeTest
public void testCameraFreezedEventOnBufferStarvation() throws InterruptedException {
fixtures.cameraFreezedEventOnBufferStarvation();
}
// This test that frames forwarded to a renderer is scaled if adaptOutputFormat is
// called. This test both Java and C++ parts of of the stack.
@Test
@MediumTest
public void testScaleCameraOutput() throws InterruptedException {
fixtures.scaleCameraOutput();
}
// This test that frames forwarded to a renderer is cropped to a new orientation if
// adaptOutputFormat is called in such a way. This test both Java and C++ parts of of the stack.
@Test
@MediumTest
public void testCropCameraOutput() throws InterruptedException {
fixtures.cropCameraOutput();
}
// This test that an error is reported if the camera is already opened
// when CameraVideoCapturer is started.
@Test
@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.
@Test
@LargeTest
public void testStartWhileCameraIsAlreadyOpenAndCloseCamera() throws InterruptedException {
fixtures.startWhileCameraIsAlreadyOpenAndCloseCamera();
}
// This test that CameraVideoCapturer.stop can be called while CameraVideoCapturer is
// re-trying to start.
@Test
@MediumTest
public void testStartWhileCameraIsAlreadyOpenAndStop() throws InterruptedException {
fixtures.startWhileCameraIsAlreadyOpenAndStop();
}
}

View File

@ -1,334 +0,0 @@
/*
* 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 static org.junit.Assert.fail;
import android.content.Context;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.os.Handler;
import android.os.Looper;
import android.support.test.InstrumentationRegistry;
import androidx.annotation.Nullable;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import java.util.concurrent.CountDownLatch;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class Camera2CapturerTest {
static final String TAG = "Camera2CapturerTest";
/**
* Simple camera2 implementation that only knows how to open the camera and close it.
*/
private class SimpleCamera2 {
final CameraManager cameraManager;
final LooperThread looperThread;
final CountDownLatch openDoneSignal;
final Object cameraDeviceLock;
@Nullable CameraDevice cameraDevice; // Guarded by cameraDeviceLock
boolean openSucceeded; // Guarded by cameraDeviceLock
private class LooperThread extends Thread {
final CountDownLatch startedSignal = new CountDownLatch(1);
private Handler handler;
@Override
public void run() {
Looper.prepare();
handler = new Handler();
startedSignal.countDown();
Looper.loop();
}
public void waitToStart() {
ThreadUtils.awaitUninterruptibly(startedSignal);
}
public void requestStop() {
handler.getLooper().quit();
}
public Handler getHandler() {
return handler;
}
}
private class CameraStateCallback extends CameraDevice.StateCallback {
@Override
public void onClosed(CameraDevice cameraDevice) {
Logging.d(TAG, "Simple camera2 closed.");
synchronized (cameraDeviceLock) {
SimpleCamera2.this.cameraDevice = null;
}
}
@Override
public void onDisconnected(CameraDevice cameraDevice) {
Logging.d(TAG, "Simple camera2 disconnected.");
synchronized (cameraDeviceLock) {
SimpleCamera2.this.cameraDevice = null;
}
}
@Override
public void onError(CameraDevice cameraDevice, int errorCode) {
Logging.w(TAG, "Simple camera2 error: " + errorCode);
synchronized (cameraDeviceLock) {
SimpleCamera2.this.cameraDevice = cameraDevice;
openSucceeded = false;
}
openDoneSignal.countDown();
}
@Override
public void onOpened(CameraDevice cameraDevice) {
Logging.d(TAG, "Simple camera2 opened.");
synchronized (cameraDeviceLock) {
SimpleCamera2.this.cameraDevice = cameraDevice;
openSucceeded = true;
}
openDoneSignal.countDown();
}
}
SimpleCamera2(Context context, String deviceName) {
cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
looperThread = new LooperThread();
looperThread.start();
looperThread.waitToStart();
cameraDeviceLock = new Object();
openDoneSignal = new CountDownLatch(1);
cameraDevice = null;
Logging.d(TAG, "Opening simple camera2.");
try {
cameraManager.openCamera(deviceName, new CameraStateCallback(), looperThread.getHandler());
} catch (CameraAccessException e) {
fail("Simple camera2 CameraAccessException: " + e.getMessage());
}
Logging.d(TAG, "Waiting for simple camera2 to open.");
ThreadUtils.awaitUninterruptibly(openDoneSignal);
synchronized (cameraDeviceLock) {
if (!openSucceeded) {
fail("Opening simple camera2 failed.");
}
}
}
public void close() {
Logging.d(TAG, "Closing simple camera2.");
synchronized (cameraDeviceLock) {
if (cameraDevice != null) {
cameraDevice.close();
}
}
looperThread.requestStop();
ThreadUtils.joinUninterruptibly(looperThread);
}
}
private class TestObjectFactory extends CameraVideoCapturerTestFixtures.TestObjectFactory {
@Override
public CameraEnumerator getCameraEnumerator() {
return new Camera2Enumerator(getAppContext());
}
@Override
public Context getAppContext() {
return InstrumentationRegistry.getTargetContext();
}
@SuppressWarnings("deprecation")
@Override
public Object rawOpenCamera(String cameraName) {
return new SimpleCamera2(getAppContext(), cameraName);
}
@SuppressWarnings("deprecation")
@Override
public void rawCloseCamera(Object camera) {
((SimpleCamera2) camera).close();
}
}
private CameraVideoCapturerTestFixtures fixtures;
@Before
public void setUp() {
fixtures = new CameraVideoCapturerTestFixtures(new TestObjectFactory());
}
@After
public void tearDown() {
fixtures.dispose();
}
@Test
@SmallTest
public void testCreateAndDispose() throws InterruptedException {
fixtures.createCapturerAndDispose();
}
@Test
@SmallTest
public void testCreateNonExistingCamera() throws InterruptedException {
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.
@Test
@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.
@Test
@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.
@Test
@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.
@Test
@MediumTest
public void testSwitchVideoCapturer() throws InterruptedException {
fixtures.switchCamera();
}
@Test
@MediumTest
public void testSwitchVideoCapturerToSpecificCameraName() throws InterruptedException {
fixtures.switchCamera(true /* specifyCameraName */);
}
@Test
@MediumTest
public void testCameraEvents() throws InterruptedException {
fixtures.cameraEventsInvoked();
}
// Test what happens when attempting to call e.g. switchCamera() after camera has been stopped.
@Test
@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.
@Test
@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.
@Test
@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.
@Test
@LargeTest
public void testReturnBufferLate() throws InterruptedException {
fixtures.returnBufferLate();
}
// 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.
@Test
@MediumTest
public void testReturnBufferLateEndToEnd() throws InterruptedException {
fixtures.returnBufferLateEndToEnd();
}
// This test that CameraEventsHandler.onError is triggered if video buffers are not returned to
// the capturer.
@Test
@LargeTest
public void testCameraFreezedEventOnBufferStarvation() throws InterruptedException {
fixtures.cameraFreezedEventOnBufferStarvation();
}
// This test that frames forwarded to a renderer is scaled if adaptOutputFormat is
// called. This test both Java and C++ parts of of the stack.
@Test
@MediumTest
public void testScaleCameraOutput() throws InterruptedException {
fixtures.scaleCameraOutput();
}
// This test that frames forwarded to a renderer is cropped to a new orientation if
// adaptOutputFormat is called in such a way. This test both Java and C++ parts of of the stack.
@Test
@MediumTest
public void testCropCameraOutput() throws InterruptedException {
fixtures.cropCameraOutput();
}
// This test that an error is reported if the camera is already opened
// when CameraVideoCapturer is started.
@Test
@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.
@Test
@LargeTest
public void testStartWhileCameraIsAlreadyOpenAndCloseCamera() throws InterruptedException {
fixtures.startWhileCameraIsAlreadyOpenAndCloseCamera();
}
// This test that CameraVideoCapturer.stop can be called while CameraVideoCapturer is
// re-trying to start.
@Test
@MediumTest
public void testStartWhileCameraIsAlreadyOpenAndStop() throws InterruptedException {
fixtures.startWhileCameraIsAlreadyOpenAndStop();
}
}

View File

@ -1,793 +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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.Context;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.webrtc.CameraEnumerationAndroid.CaptureFormat;
import org.webrtc.VideoFrame;
class CameraVideoCapturerTestFixtures {
static final String TAG = "CameraVideoCapturerTestFixtures";
// Default values used for starting capturing
static final int DEFAULT_WIDTH = 640;
static final int DEFAULT_HEIGHT = 480;
static final int DEFAULT_FPS = 15;
static private class RendererCallbacks implements VideoSink {
private final Object frameLock = new Object();
private int framesRendered;
private int width;
private int height;
@Override
public void onFrame(VideoFrame frame) {
synchronized (frameLock) {
++framesRendered;
width = frame.getRotatedWidth();
height = frame.getRotatedHeight();
frameLock.notify();
}
}
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) {
final int framesRenderedStart = framesRendered;
while (framesRendered == framesRenderedStart) {
frameLock.wait();
}
return framesRendered;
}
}
}
static private class FakeAsyncRenderer implements VideoSink {
private final List<VideoFrame> pendingFrames = new ArrayList<VideoFrame>();
@Override
public void onFrame(VideoFrame frame) {
synchronized (pendingFrames) {
frame.retain();
pendingFrames.add(frame);
pendingFrames.notifyAll();
}
}
// Wait until at least one frame have been received, before returning them.
public List<VideoFrame> waitForPendingFrames() throws InterruptedException {
Logging.d(TAG, "Waiting for pending frames");
synchronized (pendingFrames) {
while (pendingFrames.isEmpty()) {
pendingFrames.wait();
}
return new ArrayList<VideoFrame>(pendingFrames);
}
}
}
static private class FakeCapturerObserver implements CapturerObserver {
private int framesCaptured;
private @Nullable VideoFrame videoFrame;
final private Object frameLock = new Object();
final private Object capturerStartLock = new Object();
private Boolean capturerStartResult;
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 onCapturerStopped() {
Logging.d(TAG, "onCapturerStopped");
}
@Override
public void onFrameCaptured(VideoFrame frame) {
synchronized (frameLock) {
++framesCaptured;
if (videoFrame != null) {
videoFrame.release();
}
videoFrame = frame;
videoFrame.retain();
timestamps.add(videoFrame.getTimestampNs());
frameLock.notify();
}
}
public boolean waitForCapturerToStart() throws InterruptedException {
Logging.d(TAG, "Waiting for the capturer to start");
synchronized (capturerStartLock) {
while (capturerStartResult == null) {
capturerStartLock.wait();
}
return capturerStartResult;
}
}
public int waitForNextCapturedFrame() throws InterruptedException {
Logging.d(TAG, "Waiting for the next captured frame");
synchronized (frameLock) {
final int framesCapturedStart = framesCaptured;
while (framesCaptured == framesCapturedStart) {
frameLock.wait();
}
return framesCaptured;
}
}
int frameWidth() {
synchronized (frameLock) {
return videoFrame.getBuffer().getWidth();
}
}
int frameHeight() {
synchronized (frameLock) {
return videoFrame.getBuffer().getHeight();
}
}
void releaseFrame() {
synchronized (frameLock) {
if (videoFrame != null) {
videoFrame.release();
videoFrame = null;
}
}
}
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;
private final Object onCameraFreezedLock = new Object();
private String onCameraFreezedDescription;
private final Object cameraClosedLock = new Object();
private boolean cameraClosed = true;
@Override
public void onCameraError(String errorDescription) {
Logging.w(TAG, "Camera error: " + errorDescription);
cameraClosed = true;
}
@Override
public void onCameraDisconnected() {}
@Override
public void onCameraFreezed(String errorDescription) {
synchronized (onCameraFreezedLock) {
onCameraFreezedDescription = errorDescription;
onCameraFreezedLock.notifyAll();
}
}
@Override
public void onCameraOpening(String cameraName) {
onCameraOpeningCalled = true;
synchronized (cameraClosedLock) {
cameraClosed = false;
}
}
@Override
public void onFirstFrameAvailable() {
onFirstFrameAvailableCalled = true;
}
@Override
public void onCameraClosed() {
synchronized (cameraClosedLock) {
cameraClosed = true;
cameraClosedLock.notifyAll();
}
}
public String waitForCameraFreezed() throws InterruptedException {
Logging.d(TAG, "Waiting for the camera to freeze");
synchronized (onCameraFreezedLock) {
while (onCameraFreezedDescription == null) {
onCameraFreezedLock.wait();
}
return onCameraFreezedDescription;
}
}
public void waitForCameraClosed() throws InterruptedException {
synchronized (cameraClosedLock) {
while (!cameraClosed) {
Logging.d(TAG, "Waiting for the camera to close.");
cameraClosedLock.wait();
}
}
}
}
/**
* 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 SurfaceTextureHelper surfaceTextureHelper;
public VideoSource source;
public VideoTrack track;
public RendererCallbacks rendererCallbacks;
public FakeAsyncRenderer fakeAsyncRenderer;
}
public abstract static class TestObjectFactory {
final CameraEnumerator cameraEnumerator;
TestObjectFactory() {
cameraEnumerator = getCameraEnumerator();
}
public CameraVideoCapturer createCapturer(
String name, CameraVideoCapturer.CameraEventsHandler eventsHandler) {
return cameraEnumerator.createCapturer(name, eventsHandler);
}
public @Nullable String getNameOfFrontFacingDevice() {
for (String deviceName : cameraEnumerator.getDeviceNames()) {
if (cameraEnumerator.isFrontFacing(deviceName)) {
return deviceName;
}
}
return null;
}
public @Nullable String getNameOfBackFacingDevice() {
for (String deviceName : cameraEnumerator.getDeviceNames()) {
if (cameraEnumerator.isBackFacing(deviceName)) {
return deviceName;
}
}
return null;
}
public boolean haveTwoCameras() {
return cameraEnumerator.getDeviceNames().length >= 2;
}
public boolean isCapturingToTexture() {
// In the future, we plan to only support capturing to texture, so default to true
return true;
}
abstract public CameraEnumerator getCameraEnumerator();
abstract public 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.
abstract public Object rawOpenCamera(String cameraName);
abstract public void rawCloseCamera(Object camera);
}
private PeerConnectionFactory peerConnectionFactory;
private TestObjectFactory testObjectFactory;
CameraVideoCapturerTestFixtures(TestObjectFactory testObjectFactory) {
PeerConnectionFactory.initialize(
PeerConnectionFactory.InitializationOptions.builder(testObjectFactory.getAppContext())
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
this.peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory();
this.testObjectFactory = testObjectFactory;
}
public void dispose() {
this.peerConnectionFactory.dispose();
}
// Internal helper methods
private CapturerInstance createCapturer(String name, boolean initialize) {
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();
if (initialize) {
instance.capturer.initialize(
instance.surfaceTextureHelper, testObjectFactory.getAppContext(), instance.observer);
}
instance.supportedFormats = testObjectFactory.cameraEnumerator.getSupportedFormats(name);
return instance;
}
private CapturerInstance createCapturer(boolean initialize) {
String name = testObjectFactory.cameraEnumerator.getDeviceNames()[0];
return createCapturer(name, initialize);
}
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.format = format;
}
private void disposeCapturer(CapturerInstance instance) throws InterruptedException {
instance.capturer.stopCapture();
instance.cameraEvents.waitForCameraClosed();
instance.capturer.dispose();
instance.observer.releaseFrame();
instance.surfaceTextureHelper.dispose();
}
private VideoTrackWithRenderer createVideoTrackWithRenderer(
CameraVideoCapturer capturer, VideoSink rendererCallbacks) {
VideoTrackWithRenderer videoTrackWithRenderer = new VideoTrackWithRenderer();
videoTrackWithRenderer.surfaceTextureHelper = SurfaceTextureHelper.create(
"SurfaceTextureHelper test" /* threadName */, null /* sharedContext */);
videoTrackWithRenderer.source =
peerConnectionFactory.createVideoSource(/* isScreencast= */ false);
capturer.initialize(videoTrackWithRenderer.surfaceTextureHelper,
testObjectFactory.getAppContext(), videoTrackWithRenderer.source.getCapturerObserver());
capturer.startCapture(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FPS);
videoTrackWithRenderer.track =
peerConnectionFactory.createVideoTrack("dummy", videoTrackWithRenderer.source);
videoTrackWithRenderer.track.addSink(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, false /* initialize */);
final VideoTrackWithRenderer videoTrackWithRenderer =
createVideoTrackWithRenderer(capturerInstance.capturer);
assertTrue(videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender() > 0);
disposeCapturer(capturerInstance);
disposeVideoTrackWithRenderer(videoTrackWithRenderer);
}
// Test methods
public void createCapturerAndDispose() throws InterruptedException {
disposeCapturer(createCapturer(true /* initialize */));
}
public void createNonExistingCamera() throws InterruptedException {
try {
disposeCapturer(createCapturer("non-existing camera", false /* initialize */));
} catch (IllegalArgumentException e) {
return;
}
fail("Expected illegal argument exception when creating non-existing camera.");
}
public void createCapturerAndRender() throws InterruptedException {
String name = testObjectFactory.cameraEnumerator.getDeviceNames()[0];
createCapturerAndRender(name);
}
public void createFrontFacingCapturerAndRender() throws InterruptedException {
createCapturerAndRender(testObjectFactory.getNameOfFrontFacingDevice());
}
public void createBackFacingCapturerAndRender() throws InterruptedException {
createCapturerAndRender(testObjectFactory.getNameOfBackFacingDevice());
}
public void switchCamera() throws InterruptedException {
switchCamera(false /* specifyCameraName */);
}
public void switchCamera(boolean specifyCameraName) 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(false /* initialize */);
final VideoTrackWithRenderer videoTrackWithRenderer =
createVideoTrackWithRenderer(capturerInstance.capturer);
// Wait for the camera to start so we can switch it
assertTrue(videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender() > 0);
// Array with one element to avoid final problem in nested classes.
final boolean[] cameraSwitchSuccessful = new boolean[1];
final CountDownLatch barrier = new CountDownLatch(1);
final CameraVideoCapturer.CameraSwitchHandler cameraSwitchHandler =
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();
}
};
if (specifyCameraName) {
String expectedCameraName = testObjectFactory.cameraEnumerator.getDeviceNames()[1];
capturerInstance.capturer.switchCamera(cameraSwitchHandler, expectedCameraName);
} else {
capturerInstance.capturer.switchCamera(cameraSwitchHandler);
}
// Wait until the camera has been switched.
barrier.await();
// Check result.
assertTrue(cameraSwitchSuccessful[0]);
// Ensure that frames are received.
assertTrue(videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender() > 0);
disposeCapturer(capturerInstance);
disposeVideoTrackWithRenderer(videoTrackWithRenderer);
}
public void cameraEventsInvoked() throws InterruptedException {
final CapturerInstance capturerInstance = createCapturer(true /* initialize */);
startCapture(capturerInstance);
// Make sure camera is started and first frame is received and then stop it.
assertTrue(capturerInstance.observer.waitForCapturerToStart());
capturerInstance.observer.waitForNextCapturedFrame();
disposeCapturer(capturerInstance);
assertTrue(capturerInstance.cameraEvents.onCameraOpeningCalled);
assertTrue(capturerInstance.cameraEvents.onFirstFrameAvailableCalled);
}
public void cameraCallsAfterStop() throws InterruptedException {
final CapturerInstance capturerInstance = createCapturer(true /* initialize */);
startCapture(capturerInstance);
// Make sure camera is started and then stop it.
assertTrue(capturerInstance.observer.waitForCapturerToStart());
capturerInstance.capturer.stopCapture();
capturerInstance.observer.releaseFrame();
// We can't change `capturer` at this point, but we should not crash.
capturerInstance.capturer.switchCamera(null /* switchEventsHandler */);
capturerInstance.capturer.changeCaptureFormat(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FPS);
disposeCapturer(capturerInstance);
}
public void stopRestartVideoSource() throws InterruptedException {
final CapturerInstance capturerInstance = createCapturer(false /* initialize */);
final VideoTrackWithRenderer videoTrackWithRenderer =
createVideoTrackWithRenderer(capturerInstance.capturer);
assertTrue(videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender() > 0);
assertEquals(MediaSource.State.LIVE, videoTrackWithRenderer.source.state());
capturerInstance.capturer.stopCapture();
assertEquals(MediaSource.State.ENDED, videoTrackWithRenderer.source.state());
startCapture(capturerInstance);
assertTrue(videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender() > 0);
assertEquals(MediaSource.State.LIVE, videoTrackWithRenderer.source.state());
disposeCapturer(capturerInstance);
disposeVideoTrackWithRenderer(videoTrackWithRenderer);
}
public void startStopWithDifferentResolutions() throws InterruptedException {
final CapturerInstance capturerInstance = createCapturer(true /* initialize */);
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);
}
capturerInstance.capturer.stopCapture();
capturerInstance.observer.releaseFrame();
}
disposeCapturer(capturerInstance);
}
public void returnBufferLate() throws InterruptedException {
final CapturerInstance capturerInstance = createCapturer(true /* initialize */);
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.observer.releaseFrame();
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(false /* initialize */);
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.
disposeCapturer(capturerInstance);
disposeVideoTrackWithRenderer(videoTrackWithRenderer);
// Return the frame(s), on a different thread out of spite.
final List<VideoFrame> pendingFrames =
videoTrackWithRenderer.fakeAsyncRenderer.waitForPendingFrames();
final Thread returnThread = new Thread(new Runnable() {
@Override
public void run() {
for (VideoFrame frame : pendingFrames) {
frame.release();
}
}
});
returnThread.start();
returnThread.join();
}
public void cameraFreezedEventOnBufferStarvation() throws InterruptedException {
final CapturerInstance capturerInstance = createCapturer(true /* initialize */);
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(false /* initialize */);
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.
videoTrackWithRenderer.source.adaptOutputFormat(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);
disposeCapturer(capturerInstance);
disposeVideoTrackWithRenderer(videoTrackWithRenderer);
assertTrue(gotExpectedResolution);
}
public void cropCameraOutput() throws InterruptedException {
final CapturerInstance capturerInstance = createCapturer(false /* initialize */);
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 cropWidth;
final int cropHeight;
if (startWidth > startHeight) {
// Landscape input, request portrait output.
cropWidth = 360;
cropHeight = 640;
} else {
// Portrait input, request landscape output.
cropWidth = 640;
cropHeight = 630;
}
// Request different output orientation than input.
videoTrackWithRenderer.source.adaptOutputFormat(
cropWidth, cropHeight, cropWidth, cropHeight, frameRate);
boolean gotExpectedOrientation = false;
int numberOfInspectedFrames = 0;
do {
videoTrackWithRenderer.rendererCallbacks.waitForNextFrameToRender();
++numberOfInspectedFrames;
gotExpectedOrientation = (cropWidth > cropHeight)
== (videoTrackWithRenderer.rendererCallbacks.frameWidth()
> videoTrackWithRenderer.rendererCallbacks.frameHeight());
} while (!gotExpectedOrientation && numberOfInspectedFrames < 30);
disposeCapturer(capturerInstance);
disposeVideoTrackWithRenderer(videoTrackWithRenderer);
assertTrue(gotExpectedOrientation);
}
public void startWhileCameraIsAlreadyOpen() throws InterruptedException {
final String cameraName = testObjectFactory.getNameOfBackFacingDevice();
// At this point camera is not actually opened.
final CapturerInstance capturerInstance = createCapturer(cameraName, true /* initialize */);
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());
} 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, false /* initialize */);
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.");
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, true /* initialize */);
final Object competingCamera = testObjectFactory.rawOpenCamera(cameraName);
startCapture(capturerInstance);
disposeCapturer(capturerInstance);
testObjectFactory.rawCloseCamera(competingCamera);
}
}

View File

@ -1,77 +0,0 @@
/*
* Copyright 2017 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 org.junit.Assert.assertEquals;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import java.util.ArrayList;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
/** Unit tests for {@link DefaultVideoEncoderFactory}. */
public class DefaultVideoEncoderFactoryTest {
static class CustomHardwareVideoEncoderFactory implements VideoEncoderFactory {
private VideoCodecInfo supportedCodec;
public CustomHardwareVideoEncoderFactory(VideoCodecInfo supportedCodec) {
this.supportedCodec = supportedCodec;
}
@Override
public @Nullable VideoEncoder createEncoder(VideoCodecInfo info) {
return null;
}
@Override
public VideoCodecInfo[] getSupportedCodecs() {
return new VideoCodecInfo[] {supportedCodec};
}
}
@Before
public void setUp() {
NativeLibrary.initialize(new NativeLibrary.DefaultLoader(), TestConstants.NATIVE_LIBRARY);
}
@SmallTest
@Test
public void getSupportedCodecs_hwVp8SameParamsAsSwVp8_oneVp8() {
VideoCodecInfo hwVp8Encoder = new VideoCodecInfo("VP8", new HashMap<>());
VideoEncoderFactory hwFactory = new CustomHardwareVideoEncoderFactory(hwVp8Encoder);
DefaultVideoEncoderFactory defFactory = new DefaultVideoEncoderFactory(hwFactory);
VideoCodecInfo[] supportedCodecs = defFactory.getSupportedCodecs();
assertEquals(3, supportedCodecs.length);
assertEquals("VP8", supportedCodecs[0].name);
assertEquals("AV1", supportedCodecs[1].name);
assertEquals("VP9", supportedCodecs[2].name);
}
@SmallTest
@Test
public void getSupportedCodecs_hwVp8WithDifferentParams_twoVp8() {
VideoCodecInfo hwVp8Encoder = new VideoCodecInfo("VP8", new HashMap<String, String>() {
{ put("param", "value"); }
});
VideoEncoderFactory hwFactory = new CustomHardwareVideoEncoderFactory(hwVp8Encoder);
DefaultVideoEncoderFactory defFactory = new DefaultVideoEncoderFactory(hwFactory);
VideoCodecInfo[] supportedCodecs = defFactory.getSupportedCodecs();
assertEquals(4, supportedCodecs.length);
assertEquals("VP8", supportedCodecs[0].name);
assertEquals("AV1", supportedCodecs[1].name);
assertEquals("VP9", supportedCodecs[2].name);
assertEquals("VP8", supportedCodecs[3].name);
assertEquals(1, supportedCodecs[3].params.size());
assertEquals("value", supportedCodecs[3].params.get("param"));
}
}

View File

@ -1,366 +0,0 @@
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.graphics.Bitmap;
import android.graphics.SurfaceTexture;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.support.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
// EmptyActivity is needed for the surface.
public class EglRendererTest {
private final static String TAG = "EglRendererTest";
private final static int RENDER_WAIT_MS = 1000;
private final static int SURFACE_WAIT_MS = 1000;
private final static int TEST_FRAME_WIDTH = 4;
private final static int TEST_FRAME_HEIGHT = 4;
private final static int REMOVE_FRAME_LISTENER_RACY_NUM_TESTS = 10;
// Some arbitrary frames.
private final static byte[][][] TEST_FRAMES_DATA = {
{
new byte[] {
-99, -93, -88, -83, -78, -73, -68, -62, -56, -52, -46, -41, -36, -31, -26, -20},
new byte[] {110, 113, 116, 118}, new byte[] {31, 45, 59, 73},
},
{
new byte[] {
-108, -103, -98, -93, -87, -82, -77, -72, -67, -62, -56, -50, -45, -40, -35, -30},
new byte[] {120, 123, 125, -127}, new byte[] {87, 100, 114, 127},
},
{
new byte[] {
-117, -112, -107, -102, -97, -92, -87, -81, -75, -71, -65, -60, -55, -50, -44, -39},
new byte[] {113, 116, 118, 120}, new byte[] {45, 59, 73, 87},
},
};
private final static ByteBuffer[][] TEST_FRAMES =
copyTestDataToDirectByteBuffers(TEST_FRAMES_DATA);
private static class TestFrameListener implements EglRenderer.FrameListener {
final private ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>();
boolean bitmapReceived;
Bitmap storedBitmap;
@Override
// TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
@SuppressWarnings("NoSynchronizedMethodCheck")
public synchronized void onFrame(Bitmap bitmap) {
if (bitmapReceived) {
fail("Unexpected bitmap was received.");
}
bitmapReceived = true;
storedBitmap = bitmap;
notify();
}
// TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
@SuppressWarnings("NoSynchronizedMethodCheck")
public synchronized boolean waitForBitmap(int timeoutMs) throws InterruptedException {
final long endTimeMs = System.currentTimeMillis() + timeoutMs;
while (!bitmapReceived) {
final long waitTimeMs = endTimeMs - System.currentTimeMillis();
if (waitTimeMs < 0) {
return false;
}
wait(timeoutMs);
}
return true;
}
// TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
@SuppressWarnings("NoSynchronizedMethodCheck")
public synchronized Bitmap resetAndGetBitmap() {
bitmapReceived = false;
return storedBitmap;
}
}
final TestFrameListener testFrameListener = new TestFrameListener();
EglRenderer eglRenderer;
CountDownLatch surfaceReadyLatch = new CountDownLatch(1);
int oesTextureId;
SurfaceTexture surfaceTexture;
@Before
public void setUp() throws Exception {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
eglRenderer = new EglRenderer("TestRenderer: ");
eglRenderer.init(null /* sharedContext */, EglBase.CONFIG_RGBA, new GlRectDrawer());
oesTextureId = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
surfaceTexture = new SurfaceTexture(oesTextureId);
surfaceTexture.setDefaultBufferSize(1 /* width */, 1 /* height */);
eglRenderer.createEglSurface(surfaceTexture);
}
@After
public void tearDown() {
surfaceTexture.release();
GLES20.glDeleteTextures(1 /* n */, new int[] {oesTextureId}, 0 /* offset */);
eglRenderer.release();
}
/** Checks the bitmap is not null and the correct size. */
private static void checkBitmap(Bitmap bitmap, float scale) {
assertNotNull(bitmap);
assertEquals((int) (TEST_FRAME_WIDTH * scale), bitmap.getWidth());
assertEquals((int) (TEST_FRAME_HEIGHT * scale), bitmap.getHeight());
}
/**
* Does linear sampling on U/V plane of test data.
*
* @param data Plane data to be sampled from.
* @param planeWidth Width of the plane data. This is also assumed to be the stride.
* @param planeHeight Height of the plane data.
* @param x X-coordinate in range [0, 1].
* @param y Y-coordinate in range [0, 1].
*/
private static float linearSample(
ByteBuffer plane, int planeWidth, int planeHeight, float x, float y) {
final int stride = planeWidth;
final float coordX = x * planeWidth;
final float coordY = y * planeHeight;
int lowIndexX = (int) Math.floor(coordX - 0.5f);
int lowIndexY = (int) Math.floor(coordY - 0.5f);
int highIndexX = lowIndexX + 1;
int highIndexY = lowIndexY + 1;
final float highWeightX = coordX - lowIndexX - 0.5f;
final float highWeightY = coordY - lowIndexY - 0.5f;
final float lowWeightX = 1f - highWeightX;
final float lowWeightY = 1f - highWeightY;
// Clamp on the edges.
lowIndexX = Math.max(0, lowIndexX);
lowIndexY = Math.max(0, lowIndexY);
highIndexX = Math.min(planeWidth - 1, highIndexX);
highIndexY = Math.min(planeHeight - 1, highIndexY);
float lowYValue = (plane.get(lowIndexY * stride + lowIndexX) & 0xFF) * lowWeightX
+ (plane.get(lowIndexY * stride + highIndexX) & 0xFF) * highWeightX;
float highYValue = (plane.get(highIndexY * stride + lowIndexX) & 0xFF) * lowWeightX
+ (plane.get(highIndexY * stride + highIndexX) & 0xFF) * highWeightX;
return lowWeightY * lowYValue + highWeightY * highYValue;
}
private static byte saturatedFloatToByte(float c) {
return (byte) Math.round(255f * Math.max(0f, Math.min(1f, c)));
}
/**
* Converts test data YUV frame to expected RGBA frame. Tries to match the behavior of OpenGL
* YUV drawer shader. Does linear sampling on the U- and V-planes.
*
* @param yuvFrame Array of size 3 containing Y-, U-, V-planes for image of size
* (TEST_FRAME_WIDTH, TEST_FRAME_HEIGHT). U- and V-planes should be half the size
* of the Y-plane.
*/
private static byte[] convertYUVFrameToRGBA(ByteBuffer[] yuvFrame) {
final byte[] argbFrame = new byte[TEST_FRAME_WIDTH * TEST_FRAME_HEIGHT * 4];
final int argbStride = TEST_FRAME_WIDTH * 4;
final int yStride = TEST_FRAME_WIDTH;
final int vStride = TEST_FRAME_WIDTH / 2;
for (int y = 0; y < TEST_FRAME_HEIGHT; y++) {
for (int x = 0; x < TEST_FRAME_WIDTH; x++) {
final float yC = ((yuvFrame[0].get(y * yStride + x) & 0xFF) - 16f) / 219f;
final float uC = (linearSample(yuvFrame[1], TEST_FRAME_WIDTH / 2, TEST_FRAME_HEIGHT / 2,
(x + 0.5f) / TEST_FRAME_WIDTH, (y + 0.5f) / TEST_FRAME_HEIGHT)
- 16f)
/ 224f
- 0.5f;
final float vC = (linearSample(yuvFrame[2], TEST_FRAME_WIDTH / 2, TEST_FRAME_HEIGHT / 2,
(x + 0.5f) / TEST_FRAME_WIDTH, (y + 0.5f) / TEST_FRAME_HEIGHT)
- 16f)
/ 224f
- 0.5f;
final float rC = yC + 1.403f * vC;
final float gC = yC - 0.344f * uC - 0.714f * vC;
final float bC = yC + 1.77f * uC;
argbFrame[y * argbStride + x * 4 + 0] = saturatedFloatToByte(rC);
argbFrame[y * argbStride + x * 4 + 1] = saturatedFloatToByte(gC);
argbFrame[y * argbStride + x * 4 + 2] = saturatedFloatToByte(bC);
argbFrame[y * argbStride + x * 4 + 3] = (byte) 255;
}
}
return argbFrame;
}
/** Checks that the bitmap content matches the test frame with the given index. */
// TODO(titovartem) make correct fix during webrtc:9175
@SuppressWarnings("ByteBufferBackingArray")
private static void checkBitmapContent(Bitmap bitmap, int frame) {
checkBitmap(bitmap, 1f);
byte[] expectedRGBA = convertYUVFrameToRGBA(TEST_FRAMES[frame]);
ByteBuffer bitmapBuffer = ByteBuffer.allocateDirect(bitmap.getByteCount());
bitmap.copyPixelsToBuffer(bitmapBuffer);
for (int i = 0; i < expectedRGBA.length; i++) {
int expected = expectedRGBA[i] & 0xFF;
int value = bitmapBuffer.get(i) & 0xFF;
// Due to unknown conversion differences check value matches +-1.
if (Math.abs(value - expected) > 1) {
Logging.d(TAG, "Expected bitmap content: " + Arrays.toString(expectedRGBA));
Logging.d(TAG, "Bitmap content: " + Arrays.toString(bitmapBuffer.array()));
fail("Frame doesn't match original frame on byte " + i + ". Expected: " + expected
+ " Result: " + value);
}
}
}
/** Tells eglRenderer to render test frame with given index. */
private void feedFrame(int i) {
final VideoFrame.I420Buffer buffer = JavaI420Buffer.wrap(TEST_FRAME_WIDTH, TEST_FRAME_HEIGHT,
TEST_FRAMES[i][0], TEST_FRAME_WIDTH, TEST_FRAMES[i][1], TEST_FRAME_WIDTH / 2,
TEST_FRAMES[i][2], TEST_FRAME_WIDTH / 2, null /* releaseCallback */);
final VideoFrame frame = new VideoFrame(buffer, 0 /* rotation */, 0 /* timestamp */);
eglRenderer.onFrame(frame);
frame.release();
}
@Test
@SmallTest
public void testAddFrameListener() throws Exception {
eglRenderer.addFrameListener(testFrameListener, 0f /* scaleFactor */);
feedFrame(0);
assertTrue(testFrameListener.waitForBitmap(RENDER_WAIT_MS));
assertNull(testFrameListener.resetAndGetBitmap());
eglRenderer.addFrameListener(testFrameListener, 0f /* scaleFactor */);
feedFrame(1);
assertTrue(testFrameListener.waitForBitmap(RENDER_WAIT_MS));
assertNull(testFrameListener.resetAndGetBitmap());
feedFrame(2);
// Check we get no more bitmaps than two.
assertFalse(testFrameListener.waitForBitmap(RENDER_WAIT_MS));
}
@Test
@SmallTest
public void testAddFrameListenerBitmap() throws Exception {
eglRenderer.addFrameListener(testFrameListener, 1f /* scaleFactor */);
feedFrame(0);
assertTrue(testFrameListener.waitForBitmap(RENDER_WAIT_MS));
checkBitmapContent(testFrameListener.resetAndGetBitmap(), 0);
eglRenderer.addFrameListener(testFrameListener, 1f /* scaleFactor */);
feedFrame(1);
assertTrue(testFrameListener.waitForBitmap(RENDER_WAIT_MS));
checkBitmapContent(testFrameListener.resetAndGetBitmap(), 1);
}
@Test
@SmallTest
public void testAddFrameListenerBitmapScale() throws Exception {
for (int i = 0; i < 3; ++i) {
float scale = i * 0.5f + 0.5f;
eglRenderer.addFrameListener(testFrameListener, scale);
feedFrame(i);
assertTrue(testFrameListener.waitForBitmap(RENDER_WAIT_MS));
checkBitmap(testFrameListener.resetAndGetBitmap(), scale);
}
}
/**
* Checks that the frame listener will not be called with a frame that was delivered before the
* frame listener was added.
*/
@Test
@SmallTest
public void testFrameListenerNotCalledWithOldFrames() throws Exception {
feedFrame(0);
eglRenderer.addFrameListener(testFrameListener, 0f);
// Check the old frame does not trigger frame listener.
assertFalse(testFrameListener.waitForBitmap(RENDER_WAIT_MS));
}
/** Checks that the frame listener will not be called after it is removed. */
@Test
@SmallTest
public void testRemoveFrameListenerNotRacy() throws Exception {
for (int i = 0; i < REMOVE_FRAME_LISTENER_RACY_NUM_TESTS; i++) {
feedFrame(0);
eglRenderer.addFrameListener(testFrameListener, 0f);
eglRenderer.removeFrameListener(testFrameListener);
feedFrame(1);
}
// Check the frame listener hasn't triggered.
assertFalse(testFrameListener.waitForBitmap(RENDER_WAIT_MS));
}
@Test
@SmallTest
public void testFrameListenersFpsReduction() throws Exception {
// Test that normal frame listeners receive frames while the renderer is paused.
eglRenderer.pauseVideo();
eglRenderer.addFrameListener(testFrameListener, 1f /* scaleFactor */);
feedFrame(0);
assertTrue(testFrameListener.waitForBitmap(RENDER_WAIT_MS));
checkBitmapContent(testFrameListener.resetAndGetBitmap(), 0);
// Test that frame listeners with FPS reduction applied receive frames while the renderer is not
// paused.
eglRenderer.disableFpsReduction();
eglRenderer.addFrameListener(
testFrameListener, 1f /* scaleFactor */, null, true /* applyFpsReduction */);
feedFrame(1);
assertTrue(testFrameListener.waitForBitmap(RENDER_WAIT_MS));
checkBitmapContent(testFrameListener.resetAndGetBitmap(), 1);
// Test that frame listeners with FPS reduction applied will not receive frames while the
// renderer is paused.
eglRenderer.pauseVideo();
eglRenderer.addFrameListener(
testFrameListener, 1f /* scaleFactor */, null, true /* applyFpsReduction */);
feedFrame(1);
assertFalse(testFrameListener.waitForBitmap(RENDER_WAIT_MS));
}
private static ByteBuffer[][] copyTestDataToDirectByteBuffers(byte[][][] testData) {
final ByteBuffer[][] result = new ByteBuffer[testData.length][];
for (int i = 0; i < testData.length; i++) {
result[i] = new ByteBuffer[testData[i].length];
for (int j = 0; j < testData[i].length; j++) {
result[i][j] = ByteBuffer.allocateDirect(testData[i][j].length);
result[i][j].put(testData[i][j]);
result[i][j].rewind();
}
}
return result;
}
}

View File

@ -1,129 +0,0 @@
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.os.Environment;
import androidx.test.filters.SmallTest;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import org.junit.Before;
import org.junit.Test;
public class FileVideoCapturerTest {
public static class MockCapturerObserver implements CapturerObserver {
private final ArrayList<VideoFrame> frames = new ArrayList<VideoFrame>();
@Override
public void onCapturerStarted(boolean success) {
assertTrue(success);
}
@Override
public void onCapturerStopped() {
// Empty on purpose.
}
@Override
// TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
@SuppressWarnings("NoSynchronizedMethodCheck")
public synchronized void onFrameCaptured(VideoFrame frame) {
frame.retain();
frames.add(frame);
notify();
}
// TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
@SuppressWarnings("NoSynchronizedMethodCheck")
public synchronized ArrayList<VideoFrame> getMinimumFramesBlocking(int minFrames)
throws InterruptedException {
while (frames.size() < minFrames) {
wait();
}
return new ArrayList<VideoFrame>(frames);
}
}
@Before
public void setUp() {
NativeLibrary.initialize(new NativeLibrary.DefaultLoader(), TestConstants.NATIVE_LIBRARY);
}
@Test
@SmallTest
public void testVideoCaptureFromFile() throws InterruptedException, IOException {
final int FRAME_WIDTH = 4;
final int FRAME_HEIGHT = 4;
final int FRAME_CHROMA_WIDTH = (FRAME_WIDTH + 1) / 2;
final int FRAME_CHROMA_HEIGHT = (FRAME_HEIGHT + 1) / 2;
final int FRAME_SIZE_Y = FRAME_WIDTH * FRAME_HEIGHT;
final int FRAME_SIZE_CHROMA = FRAME_CHROMA_WIDTH * FRAME_CHROMA_HEIGHT;
final FileVideoCapturer fileVideoCapturer =
new FileVideoCapturer(Environment.getExternalStorageDirectory().getPath()
+ "/chromium_tests_root/sdk/android/instrumentationtests/src/org/webrtc/"
+ "capturetestvideo.y4m");
final MockCapturerObserver capturerObserver = new MockCapturerObserver();
fileVideoCapturer.initialize(
null /* surfaceTextureHelper */, null /* applicationContext */, capturerObserver);
fileVideoCapturer.startCapture(FRAME_WIDTH, FRAME_HEIGHT, 33 /* fps */);
final String[] expectedFrames = {
"THIS IS JUST SOME TEXT x", "THE SECOND FRAME qwerty.", "HERE IS THE THRID FRAME!"};
final ArrayList<VideoFrame> frames =
capturerObserver.getMinimumFramesBlocking(expectedFrames.length);
assertEquals(expectedFrames.length, frames.size());
fileVideoCapturer.stopCapture();
fileVideoCapturer.dispose();
// Check the content of the frames.
for (int i = 0; i < expectedFrames.length; ++i) {
final VideoFrame frame = frames.get(i);
final VideoFrame.Buffer buffer = frame.getBuffer();
assertTrue(buffer instanceof VideoFrame.I420Buffer);
final VideoFrame.I420Buffer i420Buffer = (VideoFrame.I420Buffer) buffer;
assertEquals(FRAME_WIDTH, i420Buffer.getWidth());
assertEquals(FRAME_HEIGHT, i420Buffer.getHeight());
final ByteBuffer dataY = i420Buffer.getDataY();
final ByteBuffer dataU = i420Buffer.getDataU();
final ByteBuffer dataV = i420Buffer.getDataV();
assertEquals(FRAME_SIZE_Y, dataY.remaining());
assertEquals(FRAME_SIZE_CHROMA, dataU.remaining());
assertEquals(FRAME_SIZE_CHROMA, dataV.remaining());
ByteBuffer frameContents = ByteBuffer.allocate(FRAME_SIZE_Y + 2 * FRAME_SIZE_CHROMA);
frameContents.put(dataY);
frameContents.put(dataU);
frameContents.put(dataV);
frameContents.rewind(); // Move back to the beginning.
assertByteBufferContents(
expectedFrames[i].getBytes(Charset.forName("US-ASCII")), frameContents);
frame.release();
}
}
private static void assertByteBufferContents(byte[] expected, ByteBuffer actual) {
assertEquals("Unexpected ByteBuffer size.", expected.length, actual.remaining());
for (int i = 0; i < expected.length; i++) {
assertEquals("Unexpected byte at index: " + i, expected[i], actual.get());
}
}
}

View File

@ -1,318 +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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.opengl.GLES20;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import java.nio.ByteBuffer;
import java.util.Random;
import org.junit.Test;
public class GlRectDrawerTest {
// Resolution of the test image.
private static final int WIDTH = 16;
private static final int HEIGHT = 16;
// Seed for random pixel creation.
private static final int SEED = 42;
// When comparing pixels, allow some slack for float arithmetic and integer rounding.
private static final float MAX_DIFF = 1.5f;
// clang-format off
private static final float[] IDENTITY_MATRIX = {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1};
// clang-format on
private static float normalizedByte(byte b) {
return (b & 0xFF) / 255.0f;
}
private static float saturatedConvert(float c) {
return 255.0f * Math.max(0, Math.min(c, 1));
}
// Assert RGB ByteBuffers are pixel perfect identical.
private static void assertByteBufferEquals(
int width, int height, ByteBuffer actual, ByteBuffer expected) {
actual.rewind();
expected.rewind();
assertEquals(actual.remaining(), width * height * 3);
assertEquals(expected.remaining(), width * height * 3);
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
final int actualR = actual.get() & 0xFF;
final int actualG = actual.get() & 0xFF;
final int actualB = actual.get() & 0xFF;
final int expectedR = expected.get() & 0xFF;
final int expectedG = expected.get() & 0xFF;
final int expectedB = expected.get() & 0xFF;
if (actualR != expectedR || actualG != expectedG || actualB != expectedB) {
fail("ByteBuffers of size " + width + "x" + height + " not equal at position "
+ "(" + x + ", " + y + "). Expected color (R,G,B): "
+ "(" + expectedR + ", " + expectedG + ", " + expectedB + ")"
+ " but was: "
+ "(" + actualR + ", " + actualG + ", " + actualB + ").");
}
}
}
}
// Convert RGBA ByteBuffer to RGB ByteBuffer.
private static ByteBuffer stripAlphaChannel(ByteBuffer rgbaBuffer) {
rgbaBuffer.rewind();
assertEquals(rgbaBuffer.remaining() % 4, 0);
final int numberOfPixels = rgbaBuffer.remaining() / 4;
final ByteBuffer rgbBuffer = ByteBuffer.allocateDirect(numberOfPixels * 3);
while (rgbaBuffer.hasRemaining()) {
// Copy RGB.
for (int channel = 0; channel < 3; ++channel) {
rgbBuffer.put(rgbaBuffer.get());
}
// Drop alpha.
rgbaBuffer.get();
}
return rgbBuffer;
}
// TODO(titovartem) make correct fix during webrtc:9175
@SuppressWarnings("ByteBufferBackingArray")
@Test
@SmallTest
public void testRgbRendering() {
// Create EGL base with a pixel buffer as display output.
final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PIXEL_BUFFER);
eglBase.createPbufferSurface(WIDTH, HEIGHT);
eglBase.makeCurrent();
// Create RGB byte buffer plane with random content.
final ByteBuffer rgbPlane = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 3);
final Random random = new Random(SEED);
random.nextBytes(rgbPlane.array());
// Upload the RGB byte buffer data as a texture.
final int rgbTexture = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, rgbTexture);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, WIDTH, HEIGHT, 0, GLES20.GL_RGB,
GLES20.GL_UNSIGNED_BYTE, rgbPlane);
GlUtil.checkNoGLES2Error("glTexImage2D");
// Draw the RGB frame onto the pixel buffer.
final GlRectDrawer drawer = new GlRectDrawer();
drawer.drawRgb(rgbTexture, IDENTITY_MATRIX, WIDTH, HEIGHT, 0 /* viewportX */, 0 /* viewportY */,
WIDTH, HEIGHT);
// Download the pixels in the pixel buffer as RGBA. Not all platforms support RGB, e.g. Nexus 9.
final ByteBuffer rgbaData = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 4);
GLES20.glReadPixels(0, 0, WIDTH, HEIGHT, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, rgbaData);
GlUtil.checkNoGLES2Error("glReadPixels");
// Assert rendered image is pixel perfect to source RGB.
assertByteBufferEquals(WIDTH, HEIGHT, stripAlphaChannel(rgbaData), rgbPlane);
drawer.release();
GLES20.glDeleteTextures(1, new int[] {rgbTexture}, 0);
eglBase.release();
}
// TODO(titovartem) make correct fix during webrtc:9175
@SuppressWarnings("ByteBufferBackingArray")
@Test
@SmallTest
public void testYuvRendering() {
// Create EGL base with a pixel buffer as display output.
EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PIXEL_BUFFER);
eglBase.createPbufferSurface(WIDTH, HEIGHT);
eglBase.makeCurrent();
// Create YUV byte buffer planes with random content.
final ByteBuffer[] yuvPlanes = new ByteBuffer[3];
final Random random = new Random(SEED);
for (int i = 0; i < 3; ++i) {
yuvPlanes[i] = ByteBuffer.allocateDirect(WIDTH * HEIGHT);
random.nextBytes(yuvPlanes[i].array());
}
// Generate 3 texture ids for Y/U/V.
final int yuvTextures[] = new int[3];
for (int i = 0; i < 3; i++) {
yuvTextures[i] = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D);
}
// Upload the YUV byte buffer data as textures.
for (int i = 0; i < 3; ++i) {
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, WIDTH, HEIGHT, 0,
GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, yuvPlanes[i]);
GlUtil.checkNoGLES2Error("glTexImage2D");
}
// Draw the YUV frame onto the pixel buffer.
final GlRectDrawer drawer = new GlRectDrawer();
drawer.drawYuv(yuvTextures, IDENTITY_MATRIX, WIDTH, HEIGHT, 0 /* viewportX */,
0 /* viewportY */, WIDTH, HEIGHT);
// Download the pixels in the pixel buffer as RGBA. Not all platforms support RGB, e.g. Nexus 9.
final ByteBuffer data = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 4);
GLES20.glReadPixels(0, 0, WIDTH, HEIGHT, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, data);
GlUtil.checkNoGLES2Error("glReadPixels");
// Compare the YUV data with the RGBA result.
for (int y = 0; y < HEIGHT; ++y) {
for (int x = 0; x < WIDTH; ++x) {
// YUV color space. Y in [0, 1], UV in [-0.5, 0.5]. The constants are taken from the YUV
// fragment shader code in GlGenericDrawer.
final float y_luma = normalizedByte(yuvPlanes[0].get());
final float u_chroma = normalizedByte(yuvPlanes[1].get());
final float v_chroma = normalizedByte(yuvPlanes[2].get());
// Expected color in unrounded RGB [0.0f, 255.0f].
final float expectedRed =
saturatedConvert(1.16438f * y_luma + 1.59603f * v_chroma - 0.874202f);
final float expectedGreen = saturatedConvert(
1.16438f * y_luma - 0.391762f * u_chroma - 0.812968f * v_chroma + 0.531668f);
final float expectedBlue =
saturatedConvert(1.16438f * y_luma + 2.01723f * u_chroma - 1.08563f);
// Actual color in RGB8888.
final int actualRed = data.get() & 0xFF;
final int actualGreen = data.get() & 0xFF;
final int actualBlue = data.get() & 0xFF;
final int actualAlpha = data.get() & 0xFF;
// Assert rendered image is close to pixel perfect from source YUV.
assertTrue(Math.abs(actualRed - expectedRed) < MAX_DIFF);
assertTrue(Math.abs(actualGreen - expectedGreen) < MAX_DIFF);
assertTrue(Math.abs(actualBlue - expectedBlue) < MAX_DIFF);
assertEquals(actualAlpha, 255);
}
}
drawer.release();
GLES20.glDeleteTextures(3, yuvTextures, 0);
eglBase.release();
}
/**
* The purpose here is to test GlRectDrawer.oesDraw(). Unfortunately, there is no easy way to
* create an OES texture, which is needed for input to oesDraw(). Most of the test is concerned
* with creating OES textures in the following way:
* - Create SurfaceTexture with help from SurfaceTextureHelper.
* - Create an EglBase with the SurfaceTexture as EGLSurface.
* - Upload RGB texture with known content.
* - Draw the RGB texture onto the EglBase with the SurfaceTexture as target.
* - Wait for an OES texture to be produced.
* The actual oesDraw() test is this:
* - Create an EglBase with a pixel buffer as target.
* - Render the OES texture onto the pixel buffer.
* - Read back the pixel buffer and compare it with the known RGB data.
*/
// TODO(titovartem) make correct fix during webrtc:9175
@SuppressWarnings("ByteBufferBackingArray")
@Test
@MediumTest
public void testOesRendering() throws InterruptedException {
/**
* Stub class to convert RGB ByteBuffers to OES textures by drawing onto a SurfaceTexture.
*/
class StubOesTextureProducer {
private final EglBase eglBase;
private final GlRectDrawer drawer;
private final int rgbTexture;
public StubOesTextureProducer(EglBase.Context sharedContext,
SurfaceTextureHelper surfaceTextureHelper, int width, int height) {
eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PLAIN);
surfaceTextureHelper.setTextureSize(width, height);
eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
assertEquals(eglBase.surfaceWidth(), width);
assertEquals(eglBase.surfaceHeight(), height);
drawer = new GlRectDrawer();
eglBase.makeCurrent();
rgbTexture = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D);
}
public void draw(ByteBuffer rgbPlane) {
eglBase.makeCurrent();
// Upload RGB data to texture.
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, rgbTexture);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, WIDTH, HEIGHT, 0, GLES20.GL_RGB,
GLES20.GL_UNSIGNED_BYTE, rgbPlane);
// Draw the RGB data onto the SurfaceTexture.
drawer.drawRgb(rgbTexture, IDENTITY_MATRIX, WIDTH, HEIGHT, 0 /* viewportX */,
0 /* viewportY */, WIDTH, HEIGHT);
eglBase.swapBuffers();
}
public void release() {
eglBase.makeCurrent();
drawer.release();
GLES20.glDeleteTextures(1, new int[] {rgbTexture}, 0);
eglBase.release();
}
}
// Create EGL base with a pixel buffer as display output.
final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PIXEL_BUFFER);
eglBase.createPbufferSurface(WIDTH, HEIGHT);
// Create resources for generating OES textures.
final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(
"SurfaceTextureHelper test" /* threadName */, eglBase.getEglBaseContext());
final StubOesTextureProducer oesProducer = new StubOesTextureProducer(
eglBase.getEglBaseContext(), surfaceTextureHelper, WIDTH, HEIGHT);
final SurfaceTextureHelperTest.MockTextureListener listener =
new SurfaceTextureHelperTest.MockTextureListener();
surfaceTextureHelper.startListening(listener);
// Create RGB byte buffer plane with random content.
final ByteBuffer rgbPlane = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 3);
final Random random = new Random(SEED);
random.nextBytes(rgbPlane.array());
// Draw the frame and block until an OES texture is delivered.
oesProducer.draw(rgbPlane);
final VideoFrame.TextureBuffer textureBuffer = listener.waitForTextureBuffer();
// Real test starts here.
// Draw the OES texture on the pixel buffer.
eglBase.makeCurrent();
final GlRectDrawer drawer = new GlRectDrawer();
drawer.drawOes(textureBuffer.getTextureId(),
RendererCommon.convertMatrixFromAndroidGraphicsMatrix(textureBuffer.getTransformMatrix()),
WIDTH, HEIGHT, 0 /* viewportX */, 0 /* viewportY */, WIDTH, HEIGHT);
// Download the pixels in the pixel buffer as RGBA. Not all platforms support RGB, e.g. Nexus 9.
final ByteBuffer rgbaData = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 4);
GLES20.glReadPixels(0, 0, WIDTH, HEIGHT, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, rgbaData);
GlUtil.checkNoGLES2Error("glReadPixels");
// Assert rendered image is pixel perfect to source RGB.
assertByteBufferEquals(WIDTH, HEIGHT, stripAlphaChannel(rgbaData), rgbPlane);
drawer.release();
textureBuffer.release();
oesProducer.release();
surfaceTextureHelper.dispose();
eglBase.release();
}
}

View File

@ -1,507 +0,0 @@
/*
* Copyright 2017 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.graphics.Matrix;
import android.opengl.GLES11Ext;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class HardwareVideoEncoderTest {
@Parameters(name = "textures={0};eglContext={1}")
public static Collection<Object[]> parameters() {
return Arrays.asList(new Object[] {/*textures=*/false, /*eglContext=*/false},
new Object[] {/*textures=*/true, /*eglContext=*/false},
new Object[] {/*textures=*/true, /*eglContext=*/true});
}
private final boolean useTextures;
private final boolean useEglContext;
public HardwareVideoEncoderTest(boolean useTextures, boolean useEglContext) {
this.useTextures = useTextures;
this.useEglContext = useEglContext;
}
final static String TAG = "HwVideoEncoderTest";
private static final boolean ENABLE_INTEL_VP8_ENCODER = true;
private static final boolean ENABLE_H264_HIGH_PROFILE = true;
private static final VideoEncoder.Settings SETTINGS =
new VideoEncoder.Settings(1 /* core */, 640 /* width */, 480 /* height */, 300 /* kbps */,
30 /* fps */, 1 /* numberOfSimulcastStreams */, true /* automaticResizeOn */,
/* capabilities= */ new VideoEncoder.Capabilities(false /* lossNotification */));
private static final int ENCODE_TIMEOUT_MS = 1000;
private static final int NUM_TEST_FRAMES = 10;
private static final int NUM_ENCODE_TRIES = 100;
private static final int ENCODE_RETRY_SLEEP_MS = 1;
private static final int PIXEL_ALIGNMENT_REQUIRED = 16;
private static final boolean APPLY_ALIGNMENT_TO_ALL_SIMULCAST_LAYERS = false;
// # Mock classes
/**
* Mock encoder callback that allows easy verification of the general properties of the encoded
* frame such as width and height. Also used from AndroidVideoDecoderInstrumentationTest.
*/
static class MockEncoderCallback implements VideoEncoder.Callback {
private BlockingQueue<EncodedImage> frameQueue = new LinkedBlockingQueue<>();
@Override
public void onEncodedFrame(EncodedImage frame, VideoEncoder.CodecSpecificInfo info) {
assertNotNull(frame);
assertNotNull(info);
// Make a copy because keeping a reference to the buffer is not allowed.
final ByteBuffer bufferCopy = ByteBuffer.allocateDirect(frame.buffer.remaining());
bufferCopy.put(frame.buffer);
bufferCopy.rewind();
frameQueue.offer(EncodedImage.builder()
.setBuffer(bufferCopy, null)
.setEncodedWidth(frame.encodedWidth)
.setEncodedHeight(frame.encodedHeight)
.setCaptureTimeNs(frame.captureTimeNs)
.setFrameType(frame.frameType)
.setRotation(frame.rotation)
.setQp(frame.qp)
.createEncodedImage());
}
public EncodedImage poll() {
try {
EncodedImage image = frameQueue.poll(ENCODE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
assertNotNull("Timed out waiting for the frame to be encoded.", image);
return image;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public void assertFrameEncoded(VideoFrame frame) {
final VideoFrame.Buffer buffer = frame.getBuffer();
final EncodedImage image = poll();
assertTrue(image.buffer.capacity() > 0);
assertEquals(image.encodedWidth, buffer.getWidth());
assertEquals(image.encodedHeight, buffer.getHeight());
assertEquals(image.captureTimeNs, frame.getTimestampNs());
assertEquals(image.rotation, frame.getRotation());
}
}
/** A common base class for the texture and I420 buffer that implements reference counting. */
private static abstract class MockBufferBase implements VideoFrame.Buffer {
protected final int width;
protected final int height;
private final Runnable releaseCallback;
private final Object refCountLock = new Object();
private int refCount = 1;
public MockBufferBase(int width, int height, Runnable releaseCallback) {
this.width = width;
this.height = height;
this.releaseCallback = releaseCallback;
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
@Override
public void retain() {
synchronized (refCountLock) {
assertTrue("Buffer retained after being destroyed.", refCount > 0);
++refCount;
}
}
@Override
public void release() {
synchronized (refCountLock) {
assertTrue("Buffer released too many times.", --refCount >= 0);
if (refCount == 0) {
releaseCallback.run();
}
}
}
}
private static class MockTextureBuffer
extends MockBufferBase implements VideoFrame.TextureBuffer {
private final int textureId;
public MockTextureBuffer(int textureId, int width, int height, Runnable releaseCallback) {
super(width, height, releaseCallback);
this.textureId = textureId;
}
@Override
public VideoFrame.TextureBuffer.Type getType() {
return VideoFrame.TextureBuffer.Type.OES;
}
@Override
public int getTextureId() {
return textureId;
}
@Override
public Matrix getTransformMatrix() {
return new Matrix();
}
@Override
public VideoFrame.I420Buffer toI420() {
return JavaI420Buffer.allocate(width, height);
}
@Override
public VideoFrame.Buffer cropAndScale(
int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) {
retain();
return new MockTextureBuffer(textureId, scaleWidth, scaleHeight, this ::release);
}
}
private static class MockI420Buffer extends MockBufferBase implements VideoFrame.I420Buffer {
private final JavaI420Buffer realBuffer;
public MockI420Buffer(int width, int height, Runnable releaseCallback) {
super(width, height, releaseCallback);
realBuffer = JavaI420Buffer.allocate(width, height);
}
@Override
public ByteBuffer getDataY() {
return realBuffer.getDataY();
}
@Override
public ByteBuffer getDataU() {
return realBuffer.getDataU();
}
@Override
public ByteBuffer getDataV() {
return realBuffer.getDataV();
}
@Override
public int getStrideY() {
return realBuffer.getStrideY();
}
@Override
public int getStrideU() {
return realBuffer.getStrideU();
}
@Override
public int getStrideV() {
return realBuffer.getStrideV();
}
@Override
public VideoFrame.I420Buffer toI420() {
retain();
return this;
}
@Override
public void retain() {
super.retain();
realBuffer.retain();
}
@Override
public void release() {
super.release();
realBuffer.release();
}
@Override
public VideoFrame.Buffer cropAndScale(
int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) {
return realBuffer.cropAndScale(cropX, cropY, cropWidth, cropHeight, scaleWidth, scaleHeight);
}
}
// # Test fields
private final Object referencedFramesLock = new Object();
private int referencedFrames;
private Runnable releaseFrameCallback = new Runnable() {
@Override
public void run() {
synchronized (referencedFramesLock) {
--referencedFrames;
}
}
};
private EglBase14 eglBase;
private long lastTimestampNs;
// # Helper methods
private VideoEncoderFactory createEncoderFactory(EglBase.Context eglContext) {
return new HardwareVideoEncoderFactory(
eglContext, ENABLE_INTEL_VP8_ENCODER, ENABLE_H264_HIGH_PROFILE);
}
private @Nullable VideoEncoder createEncoder() {
VideoEncoderFactory factory =
createEncoderFactory(useEglContext ? eglBase.getEglBaseContext() : null);
VideoCodecInfo[] supportedCodecs = factory.getSupportedCodecs();
return factory.createEncoder(supportedCodecs[0]);
}
private VideoFrame generateI420Frame(int width, int height) {
synchronized (referencedFramesLock) {
++referencedFrames;
}
lastTimestampNs += TimeUnit.SECONDS.toNanos(1) / SETTINGS.maxFramerate;
VideoFrame.Buffer buffer = new MockI420Buffer(width, height, releaseFrameCallback);
return new VideoFrame(buffer, 0 /* rotation */, lastTimestampNs);
}
private VideoFrame generateTextureFrame(int width, int height) {
synchronized (referencedFramesLock) {
++referencedFrames;
}
final int textureId = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
lastTimestampNs += TimeUnit.SECONDS.toNanos(1) / SETTINGS.maxFramerate;
VideoFrame.Buffer buffer =
new MockTextureBuffer(textureId, width, height, releaseFrameCallback);
return new VideoFrame(buffer, 0 /* rotation */, lastTimestampNs);
}
private VideoFrame generateFrame(int width, int height) {
return useTextures ? generateTextureFrame(width, height) : generateI420Frame(width, height);
}
static VideoCodecStatus testEncodeFrame(
VideoEncoder encoder, VideoFrame frame, VideoEncoder.EncodeInfo info) {
int numTries = 0;
// It takes a while for the encoder to become ready so try until it accepts the frame.
while (true) {
++numTries;
final VideoCodecStatus returnValue = encoder.encode(frame, info);
switch (returnValue) {
case OK: // Success
// Fall through
case ERR_SIZE: // Wrong size
return returnValue;
case NO_OUTPUT:
if (numTries >= NUM_ENCODE_TRIES) {
fail("encoder.encode keeps returning NO_OUTPUT");
}
try {
Thread.sleep(ENCODE_RETRY_SLEEP_MS); // Try again.
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
break;
default:
fail("encoder.encode returned: " + returnValue); // Error
}
}
}
private static int getAlignedNumber(int number, int alignment) {
return (number / alignment) * alignment;
}
public static int getPixelAlignmentRequired() {
return PIXEL_ALIGNMENT_REQUIRED;
}
// # Tests
@Before
public void setUp() {
NativeLibrary.initialize(new NativeLibrary.DefaultLoader(), TestConstants.NATIVE_LIBRARY);
eglBase = EglBase.createEgl14(EglBase.CONFIG_PLAIN);
eglBase.createDummyPbufferSurface();
eglBase.makeCurrent();
lastTimestampNs = System.nanoTime();
}
@After
public void tearDown() {
eglBase.release();
synchronized (referencedFramesLock) {
assertEquals("All frames were not released", 0, referencedFrames);
}
}
@Test
@SmallTest
public void testInitialize() {
VideoEncoder encoder = createEncoder();
assertEquals(VideoCodecStatus.OK, encoder.initEncode(SETTINGS, null));
assertEquals(VideoCodecStatus.OK, encoder.release());
}
@Test
@SmallTest
public void testEncode() {
VideoEncoder encoder = createEncoder();
MockEncoderCallback callback = new MockEncoderCallback();
assertEquals(VideoCodecStatus.OK, encoder.initEncode(SETTINGS, callback));
for (int i = 0; i < NUM_TEST_FRAMES; i++) {
Log.d(TAG, "Test frame: " + i);
VideoFrame frame = generateFrame(SETTINGS.width, SETTINGS.height);
VideoEncoder.EncodeInfo info = new VideoEncoder.EncodeInfo(
new EncodedImage.FrameType[] {EncodedImage.FrameType.VideoFrameDelta});
testEncodeFrame(encoder, frame, info);
callback.assertFrameEncoded(frame);
frame.release();
}
assertEquals(VideoCodecStatus.OK, encoder.release());
}
@Test
@SmallTest
public void testEncodeAltenatingBuffers() {
VideoEncoder encoder = createEncoder();
MockEncoderCallback callback = new MockEncoderCallback();
assertEquals(VideoCodecStatus.OK, encoder.initEncode(SETTINGS, callback));
for (int i = 0; i < NUM_TEST_FRAMES; i++) {
Log.d(TAG, "Test frame: " + i);
VideoFrame frame;
VideoEncoder.EncodeInfo info = new VideoEncoder.EncodeInfo(
new EncodedImage.FrameType[] {EncodedImage.FrameType.VideoFrameDelta});
frame = generateTextureFrame(SETTINGS.width, SETTINGS.height);
testEncodeFrame(encoder, frame, info);
callback.assertFrameEncoded(frame);
frame.release();
frame = generateI420Frame(SETTINGS.width, SETTINGS.height);
testEncodeFrame(encoder, frame, info);
callback.assertFrameEncoded(frame);
frame.release();
}
assertEquals(VideoCodecStatus.OK, encoder.release());
}
@Test
@SmallTest
public void testEncodeDifferentSizes() {
VideoEncoder encoder = createEncoder();
MockEncoderCallback callback = new MockEncoderCallback();
assertEquals(VideoCodecStatus.OK, encoder.initEncode(SETTINGS, callback));
VideoFrame frame;
VideoEncoder.EncodeInfo info = new VideoEncoder.EncodeInfo(
new EncodedImage.FrameType[] {EncodedImage.FrameType.VideoFrameDelta});
frame = generateFrame(SETTINGS.width / 2, SETTINGS.height / 2);
testEncodeFrame(encoder, frame, info);
callback.assertFrameEncoded(frame);
frame.release();
frame = generateFrame(SETTINGS.width, SETTINGS.height);
testEncodeFrame(encoder, frame, info);
callback.assertFrameEncoded(frame);
frame.release();
// Android MediaCodec only guarantees of proper operation with 16-pixel-aligned input frame.
// Force the size of input frame with the greatest multiple of 16 below the original size.
frame = generateFrame(getAlignedNumber(SETTINGS.width / 4, PIXEL_ALIGNMENT_REQUIRED),
getAlignedNumber(SETTINGS.height / 4, PIXEL_ALIGNMENT_REQUIRED));
testEncodeFrame(encoder, frame, info);
callback.assertFrameEncoded(frame);
frame.release();
assertEquals(VideoCodecStatus.OK, encoder.release());
}
@Test
@SmallTest
public void testEncodeAlignmentCheck() {
VideoEncoder encoder = createEncoder();
org.webrtc.HardwareVideoEncoderTest.MockEncoderCallback callback =
new org.webrtc.HardwareVideoEncoderTest.MockEncoderCallback();
assertEquals(VideoCodecStatus.OK, encoder.initEncode(SETTINGS, callback));
VideoFrame frame;
VideoEncoder.EncodeInfo info = new VideoEncoder.EncodeInfo(
new EncodedImage.FrameType[] {EncodedImage.FrameType.VideoFrameDelta});
frame = generateFrame(SETTINGS.width / 2, SETTINGS.height / 2);
assertEquals(VideoCodecStatus.OK, testEncodeFrame(encoder, frame, info));
frame.release();
// Android MediaCodec only guarantees of proper operation with 16-pixel-aligned input frame.
// Following input frame with non-aligned size would return ERR_SIZE.
frame = generateFrame(SETTINGS.width / 4, SETTINGS.height / 4);
assertNotEquals(VideoCodecStatus.OK, testEncodeFrame(encoder, frame, info));
frame.release();
// Since our encoder has returned with an error, we reinitialize the encoder.
assertEquals(VideoCodecStatus.OK, encoder.release());
assertEquals(VideoCodecStatus.OK, encoder.initEncode(SETTINGS, callback));
frame = generateFrame(getAlignedNumber(SETTINGS.width / 4, PIXEL_ALIGNMENT_REQUIRED),
getAlignedNumber(SETTINGS.height / 4, PIXEL_ALIGNMENT_REQUIRED));
assertEquals(VideoCodecStatus.OK, testEncodeFrame(encoder, frame, info));
frame.release();
assertEquals(VideoCodecStatus.OK, encoder.release());
}
@Test
@SmallTest
public void testGetEncoderInfo() {
VideoEncoder encoder = createEncoder();
assertEquals(VideoCodecStatus.OK, encoder.initEncode(SETTINGS, null));
VideoEncoder.EncoderInfo info = encoder.getEncoderInfo();
assertEquals(PIXEL_ALIGNMENT_REQUIRED, info.getRequestedResolutionAlignment());
assertEquals(
APPLY_ALIGNMENT_TO_ALL_SIMULCAST_LAYERS, info.getApplyAlignmentToAllSimulcastLayers());
assertEquals(VideoCodecStatus.OK, encoder.release());
}
}

View File

@ -1,161 +0,0 @@
/*
* Copyright 2018 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 org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.support.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import java.util.ArrayList;
import org.junit.Test;
import org.webrtc.Loggable;
import org.webrtc.Logging.Severity;
import org.webrtc.PeerConnectionFactory;
public class LoggableTest {
private static String TAG = "LoggableTest";
private static String NATIVE_FILENAME_TAG = "loggable_test.cc";
private static class MockLoggable implements Loggable {
private ArrayList<String> messages = new ArrayList<>();
private ArrayList<Severity> sevs = new ArrayList<>();
private ArrayList<String> tags = new ArrayList<>();
@Override
public void onLogMessage(String message, Severity sev, String tag) {
messages.add(message);
sevs.add(sev);
tags.add(tag);
}
public boolean isMessageReceived(String message) {
for (int i = 0; i < messages.size(); i++) {
if (messages.get(i).contains(message)) {
return true;
}
}
return false;
}
public boolean isMessageReceived(String message, Severity sev, String tag) {
for (int i = 0; i < messages.size(); i++) {
if (messages.get(i).contains(message) && sevs.get(i) == sev && tags.get(i).equals(tag)) {
return true;
}
}
return false;
}
}
private final MockLoggable mockLoggable = new MockLoggable();
@Test
@SmallTest
public void testLoggableSetWithoutError() throws InterruptedException {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setInjectableLogger(mockLoggable, Severity.LS_INFO)
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
}
@Test
@SmallTest
public void testMessageIsLoggedCorrectly() throws InterruptedException {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setInjectableLogger(mockLoggable, Severity.LS_INFO)
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
String msg = "Message that should be logged";
Logging.d(TAG, msg);
assertTrue(mockLoggable.isMessageReceived(msg, Severity.LS_INFO, TAG));
}
@Test
@SmallTest
public void testLowSeverityIsFiltered() throws InterruptedException {
// Set severity to LS_WARNING to filter out LS_INFO and below.
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setInjectableLogger(mockLoggable, Severity.LS_WARNING)
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
String msg = "Message that should NOT be logged";
Logging.d(TAG, msg);
assertFalse(mockLoggable.isMessageReceived(msg));
}
@Test
@SmallTest
public void testLoggableDoesNotReceiveMessagesAfterUnsetting() {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setInjectableLogger(mockLoggable, Severity.LS_INFO)
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
// Reinitialize without Loggable
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
String msg = "Message that should NOT be logged";
Logging.d(TAG, msg);
assertFalse(mockLoggable.isMessageReceived(msg));
}
@Test
@SmallTest
public void testNativeMessageIsLoggedCorrectly() throws InterruptedException {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setInjectableLogger(mockLoggable, Severity.LS_INFO)
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
String msg = "Message that should be logged";
nativeLogInfoTestMessage(msg);
assertTrue(mockLoggable.isMessageReceived(msg, Severity.LS_INFO, NATIVE_FILENAME_TAG));
}
@Test
@SmallTest
public void testNativeLowSeverityIsFiltered() throws InterruptedException {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setInjectableLogger(mockLoggable, Severity.LS_WARNING)
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
String msg = "Message that should NOT be logged";
nativeLogInfoTestMessage(msg);
assertFalse(mockLoggable.isMessageReceived(msg));
}
@Test
@SmallTest
public void testNativeLoggableDoesNotReceiveMessagesAfterUnsetting() {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setInjectableLogger(mockLoggable, Severity.LS_INFO)
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
// Reinitialize without Loggable
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
String msg = "Message that should NOT be logged";
nativeLogInfoTestMessage(msg);
assertFalse(mockLoggable.isMessageReceived(msg));
}
private static native void nativeLogInfoTestMessage(String message);
}

View File

@ -1,411 +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 static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.support.test.InstrumentationRegistry;
import androidx.annotation.Nullable;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.webrtc.NetworkChangeDetector.ConnectionType;
import org.webrtc.NetworkChangeDetector.NetworkInformation;
import org.webrtc.NetworkMonitorAutoDetect.ConnectivityManagerDelegate;
import org.webrtc.NetworkMonitorAutoDetect.NetworkState;
import org.webrtc.NetworkMonitorAutoDetect.SimpleNetworkCallback;
/**
* Tests for org.webrtc.NetworkMonitor.
*
* TODO(deadbeef): These tests don't cover the interaction between
* NetworkManager.java and androidnetworkmonitor.cc, which is how this
* class is used in practice in WebRTC.
*/
@SuppressLint("NewApi")
public class NetworkMonitorTest {
private static final long INVALID_NET_ID = -1;
private NetworkChangeDetector detector;
private String fieldTrialsString = "";
/**
* Listens for alerts fired by the NetworkMonitor when network status changes.
*/
private static class NetworkMonitorTestObserver implements NetworkMonitor.NetworkObserver {
private boolean receivedNotification;
@Override
public void onConnectionTypeChanged(ConnectionType connectionType) {
receivedNotification = true;
}
public boolean hasReceivedNotification() {
return receivedNotification;
}
public void resetHasReceivedNotification() {
receivedNotification = false;
}
}
/**
* Mocks out calls to the ConnectivityManager.
*/
private static class MockConnectivityManagerDelegate extends ConnectivityManagerDelegate {
private boolean activeNetworkExists;
private int networkType;
private int networkSubtype;
private int underlyingNetworkTypeForVpn;
private int underlyingNetworkSubtypeForVpn;
MockConnectivityManagerDelegate() {
this(new HashSet<>(), "");
}
MockConnectivityManagerDelegate(Set<Network> availableNetworks, String fieldTrialsString) {
super((ConnectivityManager) null, availableNetworks, fieldTrialsString);
}
@Override
public NetworkState getNetworkState() {
return new NetworkState(activeNetworkExists, networkType, networkSubtype,
underlyingNetworkTypeForVpn, underlyingNetworkSubtypeForVpn);
}
// Dummy implementations to avoid NullPointerExceptions in default implementations:
@Override
public long getDefaultNetId() {
return INVALID_NET_ID;
}
@Override
public Network[] getAllNetworks() {
return new Network[0];
}
@Override
public NetworkState getNetworkState(Network network) {
return new NetworkState(false, -1, -1, -1, -1);
}
public void setActiveNetworkExists(boolean networkExists) {
activeNetworkExists = networkExists;
}
public void setNetworkType(int networkType) {
this.networkType = networkType;
}
public void setNetworkSubtype(int networkSubtype) {
this.networkSubtype = networkSubtype;
}
public void setUnderlyingNetworkType(int underlyingNetworkTypeForVpn) {
this.underlyingNetworkTypeForVpn = underlyingNetworkTypeForVpn;
}
public void setUnderlyingNetworkSubype(int underlyingNetworkSubtypeForVpn) {
this.underlyingNetworkSubtypeForVpn = underlyingNetworkSubtypeForVpn;
}
}
/**
* Mocks out calls to the WifiManager.
*/
private static class MockWifiManagerDelegate
extends NetworkMonitorAutoDetect.WifiManagerDelegate {
private String wifiSSID;
@Override
public String getWifiSSID() {
return wifiSSID;
}
public void setWifiSSID(String wifiSSID) {
this.wifiSSID = wifiSSID;
}
}
// A dummy NetworkMonitorAutoDetect.Observer.
private static class TestNetworkMonitorAutoDetectObserver
extends NetworkMonitorAutoDetect.Observer {
final String fieldTrialsString;
TestNetworkMonitorAutoDetectObserver(String fieldTrialsString) {
this.fieldTrialsString = fieldTrialsString;
}
@Override
public void onConnectionTypeChanged(ConnectionType newConnectionType) {}
@Override
public void onNetworkConnect(NetworkInformation networkInfo) {}
@Override
public void onNetworkDisconnect(long networkHandle) {}
@Override
public void onNetworkPreference(List<ConnectionType> types, @NetworkPreference int preference) {
}
// @Override
// public String getFieldTrialsString() {
// return fieldTrialsString;
// }
}
private NetworkMonitorAutoDetect receiver;
private MockConnectivityManagerDelegate connectivityDelegate;
private MockWifiManagerDelegate wifiDelegate;
/**
* Helper method to create a network monitor and delegates for testing.
*/
private void createTestMonitor() {
Context context = InstrumentationRegistry.getTargetContext();
NetworkMonitor.getInstance().setNetworkChangeDetectorFactory(
new NetworkChangeDetectorFactory() {
@Override
public NetworkChangeDetector create(
NetworkChangeDetector.Observer observer, Context context) {
detector = new NetworkMonitorAutoDetect(observer, context);
return detector;
}
});
receiver = NetworkMonitor.createAndSetAutoDetectForTest(context, fieldTrialsString);
assertNotNull(receiver);
connectivityDelegate = new MockConnectivityManagerDelegate();
connectivityDelegate.setActiveNetworkExists(true);
receiver.setConnectivityManagerDelegateForTests(connectivityDelegate);
wifiDelegate = new MockWifiManagerDelegate();
receiver.setWifiManagerDelegateForTests(wifiDelegate);
wifiDelegate.setWifiSSID("foo");
}
private NetworkMonitorAutoDetect.ConnectionType getCurrentConnectionType() {
final NetworkMonitorAutoDetect.NetworkState networkState = receiver.getCurrentNetworkState();
return NetworkMonitorAutoDetect.getConnectionType(networkState);
}
@Before
public void setUp() {
ContextUtils.initialize(InstrumentationRegistry.getTargetContext());
createTestMonitor();
}
/**
* Tests that the receiver registers for connectivity intents during construction.
*/
@Test
@SmallTest
public void testNetworkMonitorRegistersInConstructor() throws InterruptedException {
Context context = InstrumentationRegistry.getTargetContext();
NetworkMonitorAutoDetect.Observer observer =
new TestNetworkMonitorAutoDetectObserver(fieldTrialsString);
NetworkMonitorAutoDetect receiver = new NetworkMonitorAutoDetect(observer, context);
assertTrue(receiver.isReceiverRegisteredForTesting());
}
/**
* Tests that when there is an intent indicating a change in network connectivity, it sends a
* notification to Java observers.
*/
@Test
@MediumTest
public void testNetworkMonitorJavaObservers() throws InterruptedException {
// Initialize the NetworkMonitor with a connection.
Intent connectivityIntent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
receiver.onReceive(InstrumentationRegistry.getTargetContext(), connectivityIntent);
// We shouldn't be re-notified if the connection hasn't actually changed.
NetworkMonitorTestObserver observer = new NetworkMonitorTestObserver();
NetworkMonitor.addNetworkObserver(observer);
receiver.onReceive(InstrumentationRegistry.getTargetContext(), connectivityIntent);
assertFalse(observer.hasReceivedNotification());
// We shouldn't be notified if we're connected to non-Wifi and the Wifi SSID changes.
wifiDelegate.setWifiSSID("bar");
receiver.onReceive(InstrumentationRegistry.getTargetContext(), connectivityIntent);
assertFalse(observer.hasReceivedNotification());
// We should be notified when we change to Wifi.
connectivityDelegate.setNetworkType(ConnectivityManager.TYPE_WIFI);
receiver.onReceive(InstrumentationRegistry.getTargetContext(), connectivityIntent);
assertTrue(observer.hasReceivedNotification());
observer.resetHasReceivedNotification();
// We should be notified when the Wifi SSID changes.
wifiDelegate.setWifiSSID("foo");
receiver.onReceive(InstrumentationRegistry.getTargetContext(), connectivityIntent);
assertTrue(observer.hasReceivedNotification());
observer.resetHasReceivedNotification();
// We shouldn't be re-notified if the Wifi SSID hasn't actually changed.
receiver.onReceive(InstrumentationRegistry.getTargetContext(), connectivityIntent);
assertFalse(observer.hasReceivedNotification());
// Mimic that connectivity has been lost and ensure that the observer gets the notification.
connectivityDelegate.setActiveNetworkExists(false);
Intent noConnectivityIntent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
receiver.onReceive(InstrumentationRegistry.getTargetContext(), noConnectivityIntent);
assertTrue(observer.hasReceivedNotification());
}
/**
* Tests that ConnectivityManagerDelegate doesn't crash. This test cannot rely on having any
* active network connections so it cannot usefully check results, but it can at least check
* that the functions don't crash.
*/
@Test
@SmallTest
public void testConnectivityManagerDelegateDoesNotCrash() {
ConnectivityManagerDelegate delegate = new ConnectivityManagerDelegate(
InstrumentationRegistry.getTargetContext(), new HashSet<>(), fieldTrialsString);
delegate.getNetworkState();
Network[] networks = delegate.getAllNetworks();
if (networks.length >= 1) {
delegate.getNetworkState(networks[0]);
delegate.hasInternetCapability(networks[0]);
}
delegate.getDefaultNetId();
}
/** Tests that ConnectivityManagerDelegate preferentially reads from the cache */
@Test
@SmallTest
public void testConnectivityManagerDelegatePreferentiallyReadsFromCache() {
final Set<Network> availableNetworks = new HashSet<>();
ConnectivityManagerDelegate delegate = new ConnectivityManagerDelegate(
(ConnectivityManager) InstrumentationRegistry.getTargetContext().getSystemService(
Context.CONNECTIVITY_SERVICE),
availableNetworks, "getAllNetworksFromCache:true");
Network[] networks = delegate.getAllNetworks();
assertTrue(networks.length == 0);
final Network mockNetwork = mock(Network.class);
availableNetworks.add(mockNetwork);
assertArrayEquals(new Network[] {mockNetwork}, delegate.getAllNetworks());
}
/** Tests field trial parsing */
@Test
@SmallTest
public void testConnectivityManager_requestVPN_disabled() {
NetworkRequest request =
getNetworkRequestForFieldTrials("anyothertext,requestVPN:false,anyothertext");
assertTrue(request.equals(new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()));
}
@Test
@SmallTest
public void testConnectivityManager_requestVPN_enabled() {
NetworkRequest request = getNetworkRequestForFieldTrials("requestVPN:true");
assertTrue(request.equals(new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build()));
}
@Test
@SmallTest
public void testConnectivityManager_includeOtherUidNetworks_disabled() {
NetworkRequest request = getNetworkRequestForFieldTrials("includeOtherUidNetworks:false");
assertTrue(request.equals(new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()));
}
@Test
@SmallTest
public void testConnectivityManager_includeOtherUidNetworks_enabled() {
NetworkRequest request = getNetworkRequestForFieldTrials("includeOtherUidNetworks:true");
NetworkRequest.Builder builder =
new NetworkRequest.Builder().addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
builder.setIncludeOtherUidNetworks(true);
}
assertTrue(request.equals(builder.build()));
}
private NetworkRequest getNetworkRequestForFieldTrials(String fieldTrialsString) {
return new ConnectivityManagerDelegate(
(ConnectivityManager) null, new HashSet<>(), fieldTrialsString)
.createNetworkRequest();
}
/**
* Tests that NetworkMonitorAutoDetect queryable APIs don't crash. This test cannot rely
* on having any active network connections so it cannot usefully check results, but it can at
* least check that the functions don't crash.
*/
@Test
@SmallTest
public void testQueryableAPIsDoNotCrash() {
NetworkMonitorAutoDetect.Observer observer =
new TestNetworkMonitorAutoDetectObserver(fieldTrialsString);
NetworkMonitorAutoDetect ncn =
new NetworkMonitorAutoDetect(observer, InstrumentationRegistry.getTargetContext());
ncn.getDefaultNetId();
}
/**
* Tests startMonitoring and stopMonitoring correctly set the autoDetect and number of observers.
*/
@Test
@SmallTest
public void testStartStopMonitoring() {
NetworkMonitor networkMonitor = NetworkMonitor.getInstance();
Context context = ContextUtils.getApplicationContext();
networkMonitor.startMonitoring(context, fieldTrialsString);
assertEquals(1, networkMonitor.getNumObservers());
assertEquals(detector, networkMonitor.getNetworkChangeDetector());
networkMonitor.stopMonitoring();
assertEquals(0, networkMonitor.getNumObservers());
assertNull(networkMonitor.getNetworkChangeDetector());
}
}

View File

@ -1,65 +0,0 @@
/*
* Copyright 2017 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.support.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import org.junit.Test;
public class PeerConnectionFactoryTest {
@SmallTest
@Test
public void testInitialize() {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
}
@SmallTest
@Test
public void testInitializeTwice() {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
}
@SmallTest
@Test
public void testInitializeTwiceWithTracer() {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setEnableInternalTracer(true)
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setEnableInternalTracer(true)
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
}
@SmallTest
@Test
public void testInitializeWithTracerAndShutdown() {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setEnableInternalTracer(true)
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
PeerConnectionFactory.shutdownInternalTracer();
}
}

View File

@ -1,215 +0,0 @@
/*
* Copyright 2013 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 java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import android.support.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.webrtc.PeerConnection.TlsCertPolicy;
/** Unit tests for {@link PeerConnection}. */
public class PeerConnectionTest {
@Before
public void setUp() {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
}
@Test
@SmallTest
public void testIceServerChanged() throws Exception {
PeerConnection.IceServer iceServer1 =
PeerConnection.IceServer.builder("turn:fake.example.com")
.setUsername("fakeUsername")
.setPassword("fakePassword")
.setTlsCertPolicy(TlsCertPolicy.TLS_CERT_POLICY_SECURE)
.setHostname("fakeHostname")
.setTlsAlpnProtocols(singletonList("fakeTlsAlpnProtocol"))
.setTlsEllipticCurves(singletonList("fakeTlsEllipticCurve"))
.createIceServer();
// Same as iceServer1.
PeerConnection.IceServer iceServer2 =
PeerConnection.IceServer.builder("turn:fake.example.com")
.setUsername("fakeUsername")
.setPassword("fakePassword")
.setTlsCertPolicy(TlsCertPolicy.TLS_CERT_POLICY_SECURE)
.setHostname("fakeHostname")
.setTlsAlpnProtocols(singletonList("fakeTlsAlpnProtocol"))
.setTlsEllipticCurves(singletonList("fakeTlsEllipticCurve"))
.createIceServer();
// Differs from iceServer1 by the url.
PeerConnection.IceServer iceServer3 =
PeerConnection.IceServer.builder("turn:fake.example2.com")
.setUsername("fakeUsername")
.setPassword("fakePassword")
.setTlsCertPolicy(TlsCertPolicy.TLS_CERT_POLICY_SECURE)
.setHostname("fakeHostname")
.setTlsAlpnProtocols(singletonList("fakeTlsAlpnProtocol"))
.setTlsEllipticCurves(singletonList("fakeTlsEllipticCurve"))
.createIceServer();
// Differs from iceServer1 by the username.
PeerConnection.IceServer iceServer4 =
PeerConnection.IceServer.builder("turn:fake.example.com")
.setUsername("fakeUsername2")
.setPassword("fakePassword")
.setTlsCertPolicy(TlsCertPolicy.TLS_CERT_POLICY_SECURE)
.setHostname("fakeHostname")
.setTlsAlpnProtocols(singletonList("fakeTlsAlpnProtocol"))
.setTlsEllipticCurves(singletonList("fakeTlsEllipticCurve"))
.createIceServer();
// Differs from iceServer1 by the password.
PeerConnection.IceServer iceServer5 =
PeerConnection.IceServer.builder("turn:fake.example.com")
.setUsername("fakeUsername")
.setPassword("fakePassword2")
.setTlsCertPolicy(TlsCertPolicy.TLS_CERT_POLICY_SECURE)
.setHostname("fakeHostname")
.setTlsAlpnProtocols(singletonList("fakeTlsAlpnProtocol"))
.setTlsEllipticCurves(singletonList("fakeTlsEllipticCurve"))
.createIceServer();
// Differs from iceServer1 by the TLS certificate policy.
PeerConnection.IceServer iceServer6 =
PeerConnection.IceServer.builder("turn:fake.example.com")
.setUsername("fakeUsername")
.setPassword("fakePassword")
.setTlsCertPolicy(TlsCertPolicy.TLS_CERT_POLICY_INSECURE_NO_CHECK)
.setHostname("fakeHostname")
.setTlsAlpnProtocols(singletonList("fakeTlsAlpnProtocol"))
.setTlsEllipticCurves(singletonList("fakeTlsEllipticCurve"))
.createIceServer();
// Differs from iceServer1 by the hostname.
PeerConnection.IceServer iceServer7 =
PeerConnection.IceServer.builder("turn:fake.example.com")
.setUsername("fakeUsername")
.setPassword("fakePassword")
.setTlsCertPolicy(TlsCertPolicy.TLS_CERT_POLICY_INSECURE_NO_CHECK)
.setHostname("fakeHostname2")
.setTlsAlpnProtocols(singletonList("fakeTlsAlpnProtocol"))
.setTlsEllipticCurves(singletonList("fakeTlsEllipticCurve"))
.createIceServer();
// Differs from iceServer1 by the TLS ALPN.
PeerConnection.IceServer iceServer8 =
PeerConnection.IceServer.builder("turn:fake.example.com")
.setUsername("fakeUsername")
.setPassword("fakePassword")
.setTlsCertPolicy(TlsCertPolicy.TLS_CERT_POLICY_INSECURE_NO_CHECK)
.setHostname("fakeHostname")
.setTlsAlpnProtocols(singletonList("fakeTlsAlpnProtocol2"))
.setTlsEllipticCurves(singletonList("fakeTlsEllipticCurve"))
.createIceServer();
// Differs from iceServer1 by the TLS elliptic curve.
PeerConnection.IceServer iceServer9 =
PeerConnection.IceServer.builder("turn:fake.example.com")
.setUsername("fakeUsername")
.setPassword("fakePassword")
.setTlsCertPolicy(TlsCertPolicy.TLS_CERT_POLICY_INSECURE_NO_CHECK)
.setHostname("fakeHostname")
.setTlsAlpnProtocols(singletonList("fakeTlsAlpnProtocol"))
.setTlsEllipticCurves(singletonList("fakeTlsEllipticCurve2"))
.createIceServer();
assertTrue(iceServer1.equals(iceServer2));
assertFalse(iceServer1.equals(iceServer3));
assertFalse(iceServer1.equals(iceServer4));
assertFalse(iceServer1.equals(iceServer5));
assertFalse(iceServer1.equals(iceServer6));
assertFalse(iceServer1.equals(iceServer7));
assertFalse(iceServer1.equals(iceServer8));
assertFalse(iceServer1.equals(iceServer9));
}
// TODO(fischman) MOAR test ideas:
// - Test that PC.removeStream() works; requires a second
// createOffer/createAnswer dance.
// - audit each place that uses `constraints` for specifying non-trivial
// constraints (and ensure they're honored).
// - test error cases
// - ensure reasonable coverage of jni code is achieved. Coverage is
// extra-important because of all the free-text (class/method names, etc)
// in JNI-style programming; make sure no typos!
// - Test that shutdown mid-interaction is crash-free.
// Tests that the JNI glue between Java and C++ does not crash when creating a PeerConnection.
@Test
@SmallTest
public void testCreationWithConfig() throws Exception {
PeerConnectionFactory factory = PeerConnectionFactory.builder().createPeerConnectionFactory();
List<PeerConnection.IceServer> iceServers = Arrays.asList(
PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer(),
PeerConnection.IceServer.builder("turn:fake.example.com")
.setUsername("fakeUsername")
.setPassword("fakePassword")
.createIceServer());
PeerConnection.RTCConfiguration config = new PeerConnection.RTCConfiguration(iceServers);
config.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
// Test configuration options.
config.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY;
PeerConnection offeringPC =
factory.createPeerConnection(config, mock(PeerConnection.Observer.class));
assertNotNull(offeringPC);
}
@Test
@SmallTest
public void testCreationWithCertificate() throws Exception {
PeerConnectionFactory factory = PeerConnectionFactory.builder().createPeerConnectionFactory();
PeerConnection.RTCConfiguration config = new PeerConnection.RTCConfiguration(Arrays.asList());
config.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
// Test certificate.
RtcCertificatePem originalCert = RtcCertificatePem.generateCertificate();
config.certificate = originalCert;
PeerConnection offeringPC =
factory.createPeerConnection(config, mock(PeerConnection.Observer.class));
RtcCertificatePem restoredCert = offeringPC.getCertificate();
assertEquals(originalCert.privateKey, restoredCert.privateKey);
assertEquals(originalCert.certificate, restoredCert.certificate);
}
@Test
@SmallTest
public void testCreationWithCryptoOptions() throws Exception {
PeerConnectionFactory factory = PeerConnectionFactory.builder().createPeerConnectionFactory();
PeerConnection.RTCConfiguration config = new PeerConnection.RTCConfiguration(Arrays.asList());
config.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
assertNull(config.cryptoOptions);
CryptoOptions cryptoOptions = CryptoOptions.builder()
.setEnableGcmCryptoSuites(true)
.setEnableAes128Sha1_32CryptoCipher(true)
.setEnableEncryptedRtpHeaderExtensions(true)
.setRequireFrameEncryption(true)
.createCryptoOptions();
config.cryptoOptions = cryptoOptions;
PeerConnection offeringPC =
factory.createPeerConnection(config, mock(PeerConnection.Observer.class));
assertNotNull(offeringPC);
}
}

View File

@ -1,150 +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 static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.webrtc.RendererCommon.ScalingType.SCALE_ASPECT_BALANCED;
import static org.webrtc.RendererCommon.ScalingType.SCALE_ASPECT_FILL;
import static org.webrtc.RendererCommon.ScalingType.SCALE_ASPECT_FIT;
import static org.webrtc.RendererCommon.getDisplaySize;
import static org.webrtc.RendererCommon.getLayoutMatrix;
import android.graphics.Point;
import androidx.test.filters.SmallTest;
import org.junit.Test;
public class RendererCommonTest {
@Test
@SmallTest
public void testDisplaySizeNoFrame() {
assertEquals(new Point(0, 0), getDisplaySize(SCALE_ASPECT_FIT, 0.0f, 0, 0));
assertEquals(new Point(0, 0), getDisplaySize(SCALE_ASPECT_FILL, 0.0f, 0, 0));
assertEquals(new Point(0, 0), getDisplaySize(SCALE_ASPECT_BALANCED, 0.0f, 0, 0));
}
@Test
@SmallTest
public void testDisplaySizeDegenerateAspectRatio() {
assertEquals(new Point(1280, 720), getDisplaySize(SCALE_ASPECT_FIT, 0.0f, 1280, 720));
assertEquals(new Point(1280, 720), getDisplaySize(SCALE_ASPECT_FILL, 0.0f, 1280, 720));
assertEquals(new Point(1280, 720), getDisplaySize(SCALE_ASPECT_BALANCED, 0.0f, 1280, 720));
}
@Test
@SmallTest
public void testZeroDisplaySize() {
assertEquals(new Point(0, 0), getDisplaySize(SCALE_ASPECT_FIT, 16.0f / 9, 0, 0));
assertEquals(new Point(0, 0), getDisplaySize(SCALE_ASPECT_FILL, 16.0f / 9, 0, 0));
assertEquals(new Point(0, 0), getDisplaySize(SCALE_ASPECT_BALANCED, 16.0f / 9, 0, 0));
}
@Test
@SmallTest
public void testDisplaySizePerfectFit() {
assertEquals(new Point(1280, 720), getDisplaySize(SCALE_ASPECT_FIT, 16.0f / 9, 1280, 720));
assertEquals(new Point(1280, 720), getDisplaySize(SCALE_ASPECT_FILL, 16.0f / 9, 1280, 720));
assertEquals(new Point(1280, 720), getDisplaySize(SCALE_ASPECT_BALANCED, 16.0f / 9, 1280, 720));
assertEquals(new Point(720, 1280), getDisplaySize(SCALE_ASPECT_FIT, 9.0f / 16, 720, 1280));
assertEquals(new Point(720, 1280), getDisplaySize(SCALE_ASPECT_FILL, 9.0f / 16, 720, 1280));
assertEquals(new Point(720, 1280), getDisplaySize(SCALE_ASPECT_BALANCED, 9.0f / 16, 720, 1280));
}
@Test
@SmallTest
public void testLandscapeVideoInPortraitDisplay() {
assertEquals(new Point(720, 405), getDisplaySize(SCALE_ASPECT_FIT, 16.0f / 9, 720, 1280));
assertEquals(new Point(720, 1280), getDisplaySize(SCALE_ASPECT_FILL, 16.0f / 9, 720, 1280));
assertEquals(new Point(720, 720), getDisplaySize(SCALE_ASPECT_BALANCED, 16.0f / 9, 720, 1280));
}
@Test
@SmallTest
public void testPortraitVideoInLandscapeDisplay() {
assertEquals(new Point(405, 720), getDisplaySize(SCALE_ASPECT_FIT, 9.0f / 16, 1280, 720));
assertEquals(new Point(1280, 720), getDisplaySize(SCALE_ASPECT_FILL, 9.0f / 16, 1280, 720));
assertEquals(new Point(720, 720), getDisplaySize(SCALE_ASPECT_BALANCED, 9.0f / 16, 1280, 720));
}
@Test
@SmallTest
public void testFourToThreeVideoInSixteenToNineDisplay() {
assertEquals(new Point(960, 720), getDisplaySize(SCALE_ASPECT_FIT, 4.0f / 3, 1280, 720));
assertEquals(new Point(1280, 720), getDisplaySize(SCALE_ASPECT_FILL, 4.0f / 3, 1280, 720));
assertEquals(new Point(1280, 720), getDisplaySize(SCALE_ASPECT_BALANCED, 4.0f / 3, 1280, 720));
}
// Only keep 2 rounded decimals to make float comparison robust.
private static double[] round(float[] array) {
assertEquals(16, array.length);
final double[] doubleArray = new double[16];
for (int i = 0; i < 16; ++i) {
doubleArray[i] = Math.round(100 * array[i]) / 100.0;
}
return doubleArray;
}
// Brief summary about matrix transformations:
// A coordinate p = [u, v, 0, 1] is transformed by matrix m like this p' = [u', v', 0, 1] = m * p.
// OpenGL uses column-major order, so:
// u' = u * m[0] + v * m[4] + m[12].
// v' = u * m[1] + v * m[5] + m[13].
@Test
@SmallTest
public void testLayoutMatrixDefault() {
final float layoutMatrix[] = getLayoutMatrix(false, 1.0f, 1.0f);
// Assert:
// u' = u.
// v' = v.
// clang-format off
assertArrayEquals(new double[] {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1}, round(layoutMatrix), 0.0);
// clang-format on
}
@Test
@SmallTest
public void testLayoutMatrixMirror() {
final float layoutMatrix[] = getLayoutMatrix(true, 1.0f, 1.0f);
// Assert:
// u' = 1 - u.
// v' = v.
// clang-format off
assertArrayEquals(new double[] {
-1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
1, 0, 0, 1}, round(layoutMatrix), 0.0);
// clang-format on
}
@Test
@SmallTest
public void testLayoutMatrixScale() {
// Video has aspect ratio 2, but layout is square. This will cause only the center part of the
// video to be visible, i.e. the u coordinate will go from 0.25 to 0.75 instead of from 0 to 1.
final float layoutMatrix[] = getLayoutMatrix(false, 2.0f, 1.0f);
// Assert:
// u' = 0.25 + 0.5 u.
// v' = v.
// clang-format off
assertArrayEquals(new double[] {
0.5, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0.25, 0, 0, 1}, round(layoutMatrix), 0.0);
// clang-format on
}
}

View File

@ -1,70 +0,0 @@
/*
* Copyright 2018 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 com.google.common.truth.Truth.assertThat;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.webrtc.PeerConnection;
import org.webrtc.RtcCertificatePem;
/** Tests for RtcCertificatePem.java. */
public class RtcCertificatePemTest {
@Before
public void setUp() {
System.loadLibrary(TestConstants.NATIVE_LIBRARY);
}
@Test
@SmallTest
public void testConstructor() {
RtcCertificatePem original = RtcCertificatePem.generateCertificate();
RtcCertificatePem recreated = new RtcCertificatePem(original.privateKey, original.certificate);
assertThat(original.privateKey).isEqualTo(recreated.privateKey);
assertThat(original.certificate).isEqualTo(recreated.certificate);
}
@Test
@SmallTest
public void testGenerateCertificateDefaults() {
RtcCertificatePem rtcCertificate = RtcCertificatePem.generateCertificate();
assertThat(rtcCertificate.privateKey).isNotEmpty();
assertThat(rtcCertificate.certificate).isNotEmpty();
}
@Test
@SmallTest
public void testGenerateCertificateCustomKeyTypeDefaultExpires() {
RtcCertificatePem rtcCertificate =
RtcCertificatePem.generateCertificate(PeerConnection.KeyType.RSA);
assertThat(rtcCertificate.privateKey).isNotEmpty();
assertThat(rtcCertificate.certificate).isNotEmpty();
}
@Test
@SmallTest
public void testGenerateCertificateCustomExpiresDefaultKeyType() {
RtcCertificatePem rtcCertificate = RtcCertificatePem.generateCertificate(60 * 60 * 24);
assertThat(rtcCertificate.privateKey).isNotEmpty();
assertThat(rtcCertificate.certificate).isNotEmpty();
}
@Test
@SmallTest
public void testGenerateCertificateCustomKeyTypeAndExpires() {
RtcCertificatePem rtcCertificate =
RtcCertificatePem.generateCertificate(PeerConnection.KeyType.RSA, 60 * 60 * 24);
assertThat(rtcCertificate.privateKey).isNotEmpty();
assertThat(rtcCertificate.certificate).isNotEmpty();
}
}

View File

@ -1,77 +0,0 @@
/*
* Copyright 2020 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import android.support.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.webrtc.RtpParameters.DegradationPreference;
/** Unit-tests for {@link RtpSender}. */
public class RtpSenderTest {
private PeerConnectionFactory factory;
private PeerConnection pc;
@Before
public void setUp() {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
factory = PeerConnectionFactory.builder().createPeerConnectionFactory();
PeerConnection.RTCConfiguration config = new PeerConnection.RTCConfiguration(Arrays.asList());
// RtpTranceiver is part of new unified plan semantics.
config.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
pc = factory.createPeerConnection(config, mock(PeerConnection.Observer.class));
}
/** Test checking the enum values for DegradationPreference stay consistent */
@Test
@SmallTest
public void testSetDegradationPreference() throws Exception {
RtpTransceiver transceiver = pc.addTransceiver(MediaStreamTrack.MediaType.MEDIA_TYPE_VIDEO);
RtpSender sender = transceiver.getSender();
RtpParameters parameters = sender.getParameters();
assertNotNull(parameters);
assertNull(parameters.degradationPreference);
parameters.degradationPreference = DegradationPreference.MAINTAIN_FRAMERATE;
assertTrue(sender.setParameters(parameters));
parameters = sender.getParameters();
assertEquals(DegradationPreference.MAINTAIN_FRAMERATE, parameters.degradationPreference);
parameters.degradationPreference = DegradationPreference.MAINTAIN_RESOLUTION;
assertTrue(sender.setParameters(parameters));
parameters = sender.getParameters();
assertEquals(DegradationPreference.MAINTAIN_RESOLUTION, parameters.degradationPreference);
parameters.degradationPreference = DegradationPreference.BALANCED;
assertTrue(sender.setParameters(parameters));
parameters = sender.getParameters();
assertEquals(DegradationPreference.BALANCED, parameters.degradationPreference);
parameters.degradationPreference = DegradationPreference.DISABLED;
assertTrue(sender.setParameters(parameters));
parameters = sender.getParameters();
assertEquals(DegradationPreference.DISABLED, parameters.degradationPreference);
}
}

View File

@ -1,67 +0,0 @@
/*
* Copyright 2013 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 org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import android.support.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.webrtc.RtpParameters.Encoding;
import org.webrtc.RtpTransceiver.RtpTransceiverInit;
/** Unit-tests for {@link RtpTransceiver}. */
public class RtpTransceiverTest {
private PeerConnectionFactory factory;
private PeerConnection pc;
@Before
public void setUp() {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
factory = PeerConnectionFactory.builder().createPeerConnectionFactory();
PeerConnection.RTCConfiguration config = new PeerConnection.RTCConfiguration(Arrays.asList());
// RtpTranceiver is part of new unified plan semantics.
config.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
pc = factory.createPeerConnection(config, mock(PeerConnection.Observer.class));
}
/** Test that RIDs get set in the RTP sender when passed in through an RtpTransceiverInit. */
@Test
@SmallTest
public void testSetRidInSimulcast() throws Exception {
List<Encoding> encodings = new ArrayList<Encoding>();
encodings.add(new Encoding("F", true, null));
encodings.add(new Encoding("H", true, null));
RtpTransceiverInit init = new RtpTransceiverInit(
RtpTransceiver.RtpTransceiverDirection.SEND_ONLY, Collections.emptyList(), encodings);
RtpTransceiver transceiver =
pc.addTransceiver(MediaStreamTrack.MediaType.MEDIA_TYPE_VIDEO, init);
RtpSender sender = transceiver.getSender();
RtpParameters parameters = sender.getParameters();
List<Encoding> sendEncodings = parameters.getEncodings();
assertEquals(2, sendEncodings.size());
assertEquals("F", sendEncodings.get(0).getRid());
assertEquals("H", sendEncodings.get(1).getRid());
}
}

View File

@ -1,62 +0,0 @@
/*
* Copyright 2022 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 com.google.common.truth.Truth.assertThat;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
/** Unit tests for {@link SoftwareVideoDecoderFactory}. */
public class SoftwareVideoDecoderFactoryTest {
@Before
public void setUp() {
NativeLibrary.initialize(new NativeLibrary.DefaultLoader(), TestConstants.NATIVE_LIBRARY);
}
@SmallTest
@Test
public void getSupportedCodecs_returnsDefaultCodecs() {
VideoDecoderFactory factory = new SoftwareVideoDecoderFactory();
VideoCodecInfo[] codecs = factory.getSupportedCodecs();
assertThat(codecs.length).isEqualTo(6);
assertThat(codecs[0].name).isEqualTo("VP8");
assertThat(codecs[1].name).isEqualTo("VP9");
assertThat(codecs[2].name).isEqualTo("VP9");
assertThat(codecs[3].name).isEqualTo("VP9");
assertThat(codecs[4].name).isEqualTo("AV1");
assertThat(codecs[5].name).isEqualTo("AV1");
}
@SmallTest
@Test
public void createDecoder_supportedCodec_returnsNotNull() {
VideoDecoderFactory factory = new SoftwareVideoDecoderFactory();
VideoCodecInfo[] codecs = factory.getSupportedCodecs();
assertThat(codecs.length).isGreaterThan(0);
for (VideoCodecInfo codec : codecs) {
VideoDecoder decoder = factory.createDecoder(codec);
assertThat(decoder).isNotNull();
}
}
@SmallTest
@Test
public void createDecoder_unsupportedCodec_returnsNull() {
VideoDecoderFactory factory = new SoftwareVideoDecoderFactory();
VideoCodecInfo codec = new VideoCodecInfo("unsupported", new HashMap<String, String>());
VideoDecoder decoder = factory.createDecoder(codec);
assertThat(decoder).isNull();
}
}

View File

@ -1,59 +0,0 @@
/*
* Copyright 2022 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 com.google.common.truth.Truth.assertThat;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
/** Unit tests for {@link SoftwareVideoEncoderFactory}. */
public class SoftwareVideoEncoderFactoryTest {
@Before
public void setUp() {
NativeLibrary.initialize(new NativeLibrary.DefaultLoader(), TestConstants.NATIVE_LIBRARY);
}
@SmallTest
@Test
public void getSupportedCodecs_returnsDefaultCodecs() {
VideoEncoderFactory factory = new SoftwareVideoEncoderFactory();
VideoCodecInfo[] codecs = factory.getSupportedCodecs();
assertThat(codecs.length).isEqualTo(3);
assertThat(codecs[0].name).isEqualTo("VP8");
assertThat(codecs[1].name).isEqualTo("AV1");
assertThat(codecs[2].name).isEqualTo("VP9");
}
@SmallTest
@Test
public void createEncoder_supportedCodec_returnsNotNull() {
VideoEncoderFactory factory = new SoftwareVideoEncoderFactory();
VideoCodecInfo[] codecs = factory.getSupportedCodecs();
assertThat(codecs.length).isGreaterThan(0);
for (VideoCodecInfo codec : codecs) {
VideoEncoder encoder = factory.createEncoder(codec);
assertThat(encoder).isNotNull();
}
}
@SmallTest
@Test
public void createEncoder_unsupportedCodec_returnsNull() {
VideoEncoderFactory factory = new SoftwareVideoEncoderFactory();
VideoCodecInfo codec = new VideoCodecInfo("unsupported", new HashMap<String, String>());
VideoEncoder encoder = factory.createEncoder(codec);
assertThat(encoder).isNull();
}
}

View File

@ -1,518 +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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.opengl.GLES20;
import android.os.SystemClock;
import androidx.annotation.Nullable;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import org.junit.Before;
import org.junit.Test;
public class SurfaceTextureHelperTest {
/**
* Mock texture listener with blocking wait functionality.
*/
public static final class MockTextureListener implements VideoSink {
private final Object lock = new Object();
private @Nullable VideoFrame.TextureBuffer textureBuffer;
// Thread where frames are expected to be received on.
private final @Nullable Thread expectedThread;
MockTextureListener() {
this.expectedThread = null;
}
MockTextureListener(Thread expectedThread) {
this.expectedThread = expectedThread;
}
@Override
public void onFrame(VideoFrame frame) {
if (expectedThread != null && Thread.currentThread() != expectedThread) {
throw new IllegalStateException("onTextureFrameAvailable called on wrong thread.");
}
synchronized (lock) {
this.textureBuffer = (VideoFrame.TextureBuffer) frame.getBuffer();
textureBuffer.retain();
lock.notifyAll();
}
}
/** Wait indefinitely for a new textureBuffer. */
public VideoFrame.TextureBuffer waitForTextureBuffer() throws InterruptedException {
synchronized (lock) {
while (true) {
final VideoFrame.TextureBuffer textureBufferToReturn = textureBuffer;
if (textureBufferToReturn != null) {
textureBuffer = null;
return textureBufferToReturn;
}
lock.wait();
}
}
}
/** Make sure we get no frame in the specified time period. */
public void assertNoFrameIsDelivered(final long waitPeriodMs) throws InterruptedException {
final long startTimeMs = SystemClock.elapsedRealtime();
long timeRemainingMs = waitPeriodMs;
synchronized (lock) {
while (textureBuffer == null && timeRemainingMs > 0) {
lock.wait(timeRemainingMs);
final long elapsedTimeMs = SystemClock.elapsedRealtime() - startTimeMs;
timeRemainingMs = waitPeriodMs - elapsedTimeMs;
}
assertTrue(textureBuffer == null);
}
}
}
/** Assert that two integers are close, with difference at most
* {@code threshold}. */
public static void assertClose(int threshold, int expected, int actual) {
if (Math.abs(expected - actual) <= threshold)
return;
fail("Not close enough, threshold " + threshold + ". Expected: " + expected + " Actual: "
+ actual);
}
@Before
public void setUp() {
// Load the JNI library for textureToYuv.
NativeLibrary.initialize(new NativeLibrary.DefaultLoader(), TestConstants.NATIVE_LIBRARY);
}
/**
* Test normal use by receiving three uniform texture frames. Texture frames are returned as early
* as possible. The texture pixel values are inspected by drawing the texture frame to a pixel
* buffer and reading it back with glReadPixels().
*/
@Test
@MediumTest
public void testThreeConstantColorFrames() throws InterruptedException {
final int width = 16;
final int height = 16;
// Create EGL base with a pixel buffer as display output.
final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PIXEL_BUFFER);
eglBase.createPbufferSurface(width, height);
final GlRectDrawer drawer = new GlRectDrawer();
// Create SurfaceTextureHelper and listener.
final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(
"SurfaceTextureHelper test" /* threadName */, eglBase.getEglBaseContext());
final MockTextureListener listener = new MockTextureListener();
surfaceTextureHelper.startListening(listener);
surfaceTextureHelper.setTextureSize(width, height);
// Create resources for stubbing an OES texture producer. `eglOesBase` has the SurfaceTexture in
// `surfaceTextureHelper` as the target EGLSurface.
final EglBase eglOesBase = EglBase.create(eglBase.getEglBaseContext(), EglBase.CONFIG_PLAIN);
eglOesBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
assertEquals(eglOesBase.surfaceWidth(), width);
assertEquals(eglOesBase.surfaceHeight(), height);
final int red[] = new int[] {79, 144, 185};
final int green[] = new int[] {66, 210, 162};
final int blue[] = new int[] {161, 117, 158};
// Draw three frames.
for (int i = 0; i < 3; ++i) {
// Draw a constant color frame onto the SurfaceTexture.
eglOesBase.makeCurrent();
GLES20.glClearColor(red[i] / 255.0f, green[i] / 255.0f, blue[i] / 255.0f, 1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// swapBuffers() will ultimately trigger onTextureFrameAvailable().
eglOesBase.swapBuffers();
// Wait for an OES texture to arrive and draw it onto the pixel buffer.
final VideoFrame.TextureBuffer textureBuffer = listener.waitForTextureBuffer();
eglBase.makeCurrent();
drawer.drawOes(textureBuffer.getTextureId(),
RendererCommon.convertMatrixFromAndroidGraphicsMatrix(textureBuffer.getTransformMatrix()),
width, height, 0, 0, width, height);
textureBuffer.release();
// Download the pixels in the pixel buffer as RGBA. Not all platforms support RGB, e.g.
// Nexus 9.
final ByteBuffer rgbaData = ByteBuffer.allocateDirect(width * height * 4);
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, rgbaData);
GlUtil.checkNoGLES2Error("glReadPixels");
// Assert rendered image is expected constant color.
while (rgbaData.hasRemaining()) {
assertEquals(rgbaData.get() & 0xFF, red[i]);
assertEquals(rgbaData.get() & 0xFF, green[i]);
assertEquals(rgbaData.get() & 0xFF, blue[i]);
assertEquals(rgbaData.get() & 0xFF, 255);
}
}
drawer.release();
surfaceTextureHelper.dispose();
eglBase.release();
}
/**
* Test disposing the SurfaceTextureHelper while holding a pending texture frame. The pending
* texture frame should still be valid, and this is tested by drawing the texture frame to a pixel
* buffer and reading it back with glReadPixels().
*/
@Test
@MediumTest
public void testLateReturnFrame() throws InterruptedException {
final int width = 16;
final int height = 16;
// Create EGL base with a pixel buffer as display output.
final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PIXEL_BUFFER);
eglBase.createPbufferSurface(width, height);
// Create SurfaceTextureHelper and listener.
final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(
"SurfaceTextureHelper test" /* threadName */, eglBase.getEglBaseContext());
final MockTextureListener listener = new MockTextureListener();
surfaceTextureHelper.startListening(listener);
surfaceTextureHelper.setTextureSize(width, height);
// Create resources for stubbing an OES texture producer. `eglOesBase` has the SurfaceTexture in
// `surfaceTextureHelper` as the target EGLSurface.
final EglBase eglOesBase = EglBase.create(eglBase.getEglBaseContext(), EglBase.CONFIG_PLAIN);
eglOesBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
assertEquals(eglOesBase.surfaceWidth(), width);
assertEquals(eglOesBase.surfaceHeight(), height);
final int red = 79;
final int green = 66;
final int blue = 161;
// Draw a constant color frame onto the SurfaceTexture.
eglOesBase.makeCurrent();
GLES20.glClearColor(red / 255.0f, green / 255.0f, blue / 255.0f, 1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// swapBuffers() will ultimately trigger onTextureFrameAvailable().
eglOesBase.swapBuffers();
eglOesBase.release();
// Wait for OES texture frame.
final VideoFrame.TextureBuffer textureBuffer = listener.waitForTextureBuffer();
// Diconnect while holding the frame.
surfaceTextureHelper.dispose();
// Draw the pending texture frame onto the pixel buffer.
eglBase.makeCurrent();
final GlRectDrawer drawer = new GlRectDrawer();
drawer.drawOes(textureBuffer.getTextureId(),
RendererCommon.convertMatrixFromAndroidGraphicsMatrix(textureBuffer.getTransformMatrix()),
width, height, 0, 0, width, height);
drawer.release();
// Download the pixels in the pixel buffer as RGBA. Not all platforms support RGB, e.g. Nexus 9.
final ByteBuffer rgbaData = ByteBuffer.allocateDirect(width * height * 4);
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, rgbaData);
GlUtil.checkNoGLES2Error("glReadPixels");
eglBase.release();
// Assert rendered image is expected constant color.
while (rgbaData.hasRemaining()) {
assertEquals(rgbaData.get() & 0xFF, red);
assertEquals(rgbaData.get() & 0xFF, green);
assertEquals(rgbaData.get() & 0xFF, blue);
assertEquals(rgbaData.get() & 0xFF, 255);
}
// Late frame return after everything has been disposed and released.
textureBuffer.release();
}
/**
* Test disposing the SurfaceTextureHelper, but keep trying to produce more texture frames. No
* frames should be delivered to the listener.
*/
@Test
@MediumTest
public void testDispose() throws InterruptedException {
// Create SurfaceTextureHelper and listener.
final SurfaceTextureHelper surfaceTextureHelper =
SurfaceTextureHelper.create("SurfaceTextureHelper test" /* threadName */, null);
final MockTextureListener listener = new MockTextureListener();
surfaceTextureHelper.startListening(listener);
// Create EglBase with the SurfaceTexture as target EGLSurface.
final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PLAIN);
surfaceTextureHelper.setTextureSize(/* textureWidth= */ 32, /* textureHeight= */ 32);
eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
eglBase.makeCurrent();
// Assert no frame has been received yet.
listener.assertNoFrameIsDelivered(/* waitPeriodMs= */ 1);
// Draw and wait for one frame.
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// swapBuffers() will ultimately trigger onTextureFrameAvailable().
eglBase.swapBuffers();
listener.waitForTextureBuffer().release();
// Dispose - we should not receive any textures after this.
surfaceTextureHelper.dispose();
// Draw one frame.
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
eglBase.swapBuffers();
// swapBuffers() should not trigger onTextureFrameAvailable() because disposed has been called.
// Assert that no OES texture was delivered.
listener.assertNoFrameIsDelivered(/* waitPeriodMs= */ 500);
eglBase.release();
}
/**
* Test disposing the SurfaceTextureHelper immediately after is has been setup to use a
* shared context. No frames should be delivered to the listener.
*/
@Test
@SmallTest
public void testDisposeImmediately() {
final SurfaceTextureHelper surfaceTextureHelper =
SurfaceTextureHelper.create("SurfaceTextureHelper test" /* threadName */, null);
surfaceTextureHelper.dispose();
}
/**
* Call stopListening(), but keep trying to produce more texture frames. No frames should be
* delivered to the listener.
*/
@Test
@MediumTest
public void testStopListening() throws InterruptedException {
// Create SurfaceTextureHelper and listener.
final SurfaceTextureHelper surfaceTextureHelper =
SurfaceTextureHelper.create("SurfaceTextureHelper test" /* threadName */, null);
surfaceTextureHelper.setTextureSize(/* textureWidth= */ 32, /* textureHeight= */ 32);
final MockTextureListener listener = new MockTextureListener();
surfaceTextureHelper.startListening(listener);
// Create EglBase with the SurfaceTexture as target EGLSurface.
final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PLAIN);
eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
eglBase.makeCurrent();
// Assert no frame has been received yet.
listener.assertNoFrameIsDelivered(/* waitPeriodMs= */ 1);
// Draw and wait for one frame.
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// swapBuffers() will ultimately trigger onTextureFrameAvailable().
eglBase.swapBuffers();
listener.waitForTextureBuffer().release();
// Stop listening - we should not receive any textures after this.
surfaceTextureHelper.stopListening();
// Draw one frame.
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
eglBase.swapBuffers();
// swapBuffers() should not trigger onTextureFrameAvailable() because disposed has been called.
// Assert that no OES texture was delivered.
listener.assertNoFrameIsDelivered(/* waitPeriodMs= */ 500);
surfaceTextureHelper.dispose();
eglBase.release();
}
/**
* Test stopListening() immediately after the SurfaceTextureHelper has been setup.
*/
@Test
@SmallTest
public void testStopListeningImmediately() throws InterruptedException {
final SurfaceTextureHelper surfaceTextureHelper =
SurfaceTextureHelper.create("SurfaceTextureHelper test" /* threadName */, null);
final MockTextureListener listener = new MockTextureListener();
surfaceTextureHelper.startListening(listener);
surfaceTextureHelper.stopListening();
surfaceTextureHelper.dispose();
}
/**
* Test stopListening() immediately after the SurfaceTextureHelper has been setup on the handler
* thread.
*/
@Test
@SmallTest
public void testStopListeningImmediatelyOnHandlerThread() throws InterruptedException {
final SurfaceTextureHelper surfaceTextureHelper =
SurfaceTextureHelper.create("SurfaceTextureHelper test" /* threadName */, null);
final MockTextureListener listener = new MockTextureListener();
final CountDownLatch stopListeningBarrier = new CountDownLatch(1);
final CountDownLatch stopListeningBarrierDone = new CountDownLatch(1);
// Start by posting to the handler thread to keep it occupied.
surfaceTextureHelper.getHandler().post(new Runnable() {
@Override
public void run() {
ThreadUtils.awaitUninterruptibly(stopListeningBarrier);
surfaceTextureHelper.stopListening();
stopListeningBarrierDone.countDown();
}
});
// startListening() is asynchronous and will post to the occupied handler thread.
surfaceTextureHelper.startListening(listener);
// Wait for stopListening() to be called on the handler thread.
stopListeningBarrier.countDown();
stopListeningBarrierDone.await();
// Wait until handler thread is idle to try to catch late startListening() call.
final CountDownLatch barrier = new CountDownLatch(1);
surfaceTextureHelper.getHandler().post(new Runnable() {
@Override
public void run() {
barrier.countDown();
}
});
ThreadUtils.awaitUninterruptibly(barrier);
// Previous startListening() call should never have taken place and it should be ok to call it
// again.
surfaceTextureHelper.startListening(listener);
surfaceTextureHelper.dispose();
}
/**
* Test calling startListening() with a new listener after stopListening() has been called.
*/
@Test
@MediumTest
public void testRestartListeningWithNewListener() throws InterruptedException {
// Create SurfaceTextureHelper and listener.
final SurfaceTextureHelper surfaceTextureHelper =
SurfaceTextureHelper.create("SurfaceTextureHelper test" /* threadName */, null);
surfaceTextureHelper.setTextureSize(/* textureWidth= */ 32, /* textureHeight= */ 32);
final MockTextureListener listener1 = new MockTextureListener();
surfaceTextureHelper.startListening(listener1);
// Create EglBase with the SurfaceTexture as target EGLSurface.
final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PLAIN);
eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
eglBase.makeCurrent();
// Assert no frame has been received yet.
listener1.assertNoFrameIsDelivered(/* waitPeriodMs= */ 1);
// Draw and wait for one frame.
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// swapBuffers() will ultimately trigger onTextureFrameAvailable().
eglBase.swapBuffers();
listener1.waitForTextureBuffer().release();
// Stop listening - `listener1` should not receive any textures after this.
surfaceTextureHelper.stopListening();
// Connect different listener.
final MockTextureListener listener2 = new MockTextureListener();
surfaceTextureHelper.startListening(listener2);
// Assert no frame has been received yet.
listener2.assertNoFrameIsDelivered(/* waitPeriodMs= */ 1);
// Draw one frame.
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
eglBase.swapBuffers();
// Check that `listener2` received the frame, and not `listener1`.
listener2.waitForTextureBuffer().release();
listener1.assertNoFrameIsDelivered(/* waitPeriodMs= */ 1);
surfaceTextureHelper.dispose();
eglBase.release();
}
@Test
@MediumTest
public void testTexturetoYuv() throws InterruptedException {
final int width = 16;
final int height = 16;
final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PLAIN);
// Create SurfaceTextureHelper and listener.
final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(
"SurfaceTextureHelper test" /* threadName */, eglBase.getEglBaseContext());
final MockTextureListener listener = new MockTextureListener();
surfaceTextureHelper.startListening(listener);
surfaceTextureHelper.setTextureSize(width, height);
// Create resources for stubbing an OES texture producer. `eglBase` has the SurfaceTexture in
// `surfaceTextureHelper` as the target EGLSurface.
eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
assertEquals(eglBase.surfaceWidth(), width);
assertEquals(eglBase.surfaceHeight(), height);
final int red[] = new int[] {79, 144, 185};
final int green[] = new int[] {66, 210, 162};
final int blue[] = new int[] {161, 117, 158};
final int ref_y[] = new int[] {85, 170, 161};
final int ref_u[] = new int[] {168, 97, 123};
final int ref_v[] = new int[] {127, 106, 138};
// Draw three frames.
for (int i = 0; i < 3; ++i) {
// Draw a constant color frame onto the SurfaceTexture.
eglBase.makeCurrent();
GLES20.glClearColor(red[i] / 255.0f, green[i] / 255.0f, blue[i] / 255.0f, 1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// swapBuffers() will ultimately trigger onTextureFrameAvailable().
eglBase.swapBuffers();
// Wait for an OES texture to arrive.
final VideoFrame.TextureBuffer textureBuffer = listener.waitForTextureBuffer();
final VideoFrame.I420Buffer i420 = textureBuffer.toI420();
textureBuffer.release();
// Memory layout: Lines are 16 bytes. First 16 lines are
// the Y data. These are followed by 8 lines with 8 bytes of U
// data on the left and 8 bytes of V data on the right.
//
// Offset
// 0 YYYYYYYY YYYYYYYY
// 16 YYYYYYYY YYYYYYYY
// ...
// 240 YYYYYYYY YYYYYYYY
// 256 UUUUUUUU VVVVVVVV
// 272 UUUUUUUU VVVVVVVV
// ...
// 368 UUUUUUUU VVVVVVVV
// 384 buffer end
// Allow off-by-one differences due to different rounding.
final ByteBuffer dataY = i420.getDataY();
final int strideY = i420.getStrideY();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
assertClose(1, ref_y[i], dataY.get(y * strideY + x) & 0xFF);
}
}
final int chromaWidth = width / 2;
final int chromaHeight = height / 2;
final ByteBuffer dataU = i420.getDataU();
final ByteBuffer dataV = i420.getDataV();
final int strideU = i420.getStrideU();
final int strideV = i420.getStrideV();
for (int y = 0; y < chromaHeight; y++) {
for (int x = 0; x < chromaWidth; x++) {
assertClose(1, ref_u[i], dataU.get(y * strideU + x) & 0xFF);
assertClose(1, ref_v[i], dataV.get(y * strideV + x) & 0xFF);
}
}
i420.release();
}
surfaceTextureHelper.dispose();
eglBase.release();
}
}

View File

@ -1,241 +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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import android.annotation.SuppressLint;
import android.graphics.Point;
import android.support.test.InstrumentationRegistry;
import android.support.test.annotation.UiThreadTest;
import android.support.test.rule.UiThreadTestRule;
import android.view.View.MeasureSpec;
import androidx.test.filters.MediumTest;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
public class SurfaceViewRendererOnMeasureTest {
@Rule public final UiThreadTestRule uiThreadRule = new UiThreadTestRule();
/**
* List with all possible scaling types.
*/
private static final List<RendererCommon.ScalingType> scalingTypes = Arrays.asList(
RendererCommon.ScalingType.SCALE_ASPECT_FIT, RendererCommon.ScalingType.SCALE_ASPECT_FILL,
RendererCommon.ScalingType.SCALE_ASPECT_BALANCED);
/**
* List with MeasureSpec modes.
*/
private static final List<Integer> measureSpecModes =
Arrays.asList(MeasureSpec.EXACTLY, MeasureSpec.AT_MOST);
/**
* Returns a dummy YUV frame.
*/
static VideoFrame createFrame(int width, int height, int rotationDegree) {
final int[] yuvStrides = new int[] {width, (width + 1) / 2, (width + 1) / 2};
final int[] yuvHeights = new int[] {height, (height + 1) / 2, (height + 1) / 2};
final ByteBuffer[] yuvPlanes = new ByteBuffer[3];
for (int i = 0; i < 3; ++i) {
yuvPlanes[i] = ByteBuffer.allocateDirect(yuvStrides[i] * yuvHeights[i]);
}
final VideoFrame.I420Buffer buffer =
JavaI420Buffer.wrap(width, height, yuvPlanes[0], yuvStrides[0], yuvPlanes[1], yuvStrides[1],
yuvPlanes[2], yuvStrides[2], null /* releaseCallback */);
return new VideoFrame(buffer, rotationDegree, 0 /* timestamp */);
}
/**
* Assert onMeasure() with given parameters will result in expected measured size.
*/
@SuppressLint("WrongCall")
private static void assertMeasuredSize(SurfaceViewRenderer surfaceViewRenderer,
RendererCommon.ScalingType scalingType, String frameDimensions, int expectedWidth,
int expectedHeight, int widthSpec, int heightSpec) {
surfaceViewRenderer.setScalingType(scalingType);
surfaceViewRenderer.onMeasure(widthSpec, heightSpec);
final int measuredWidth = surfaceViewRenderer.getMeasuredWidth();
final int measuredHeight = surfaceViewRenderer.getMeasuredHeight();
if (measuredWidth != expectedWidth || measuredHeight != expectedHeight) {
fail("onMeasure(" + MeasureSpec.toString(widthSpec) + ", " + MeasureSpec.toString(heightSpec)
+ ")"
+ " with scaling type " + scalingType + " and frame: " + frameDimensions
+ " expected measured size " + expectedWidth + "x" + expectedHeight + ", but was "
+ measuredWidth + "x" + measuredHeight);
}
}
/**
* Test how SurfaceViewRenderer.onMeasure() behaves when no frame has been delivered.
*/
@Test
@UiThreadTest
@MediumTest
public void testNoFrame() {
final SurfaceViewRenderer surfaceViewRenderer =
new SurfaceViewRenderer(InstrumentationRegistry.getContext());
final String frameDimensions = "null";
// Test behaviour before SurfaceViewRenderer.init() is called.
for (RendererCommon.ScalingType scalingType : scalingTypes) {
for (int measureSpecMode : measureSpecModes) {
final int zeroMeasureSize = MeasureSpec.makeMeasureSpec(0, measureSpecMode);
assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, 0, 0, zeroMeasureSize,
zeroMeasureSize);
assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, 1280, 720,
MeasureSpec.makeMeasureSpec(1280, measureSpecMode),
MeasureSpec.makeMeasureSpec(720, measureSpecMode));
}
}
// Test behaviour after SurfaceViewRenderer.init() is called, but still no frame.
surfaceViewRenderer.init((EglBase.Context) null, null);
for (RendererCommon.ScalingType scalingType : scalingTypes) {
for (int measureSpecMode : measureSpecModes) {
final int zeroMeasureSize = MeasureSpec.makeMeasureSpec(0, measureSpecMode);
assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, 0, 0, zeroMeasureSize,
zeroMeasureSize);
assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, 1280, 720,
MeasureSpec.makeMeasureSpec(1280, measureSpecMode),
MeasureSpec.makeMeasureSpec(720, measureSpecMode));
}
}
surfaceViewRenderer.release();
}
/**
* Test how SurfaceViewRenderer.onMeasure() behaves with a 1280x720 frame.
*/
@Test
@UiThreadTest
@MediumTest
public void testFrame1280x720() throws InterruptedException {
final SurfaceViewRenderer surfaceViewRenderer =
new SurfaceViewRenderer(InstrumentationRegistry.getContext());
/**
* Mock renderer events with blocking wait functionality for frame size changes.
*/
class MockRendererEvents implements RendererCommon.RendererEvents {
private int frameWidth;
private int frameHeight;
private int rotation;
// TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
@SuppressWarnings("NoSynchronizedMethodCheck")
public synchronized void waitForFrameSize(int frameWidth, int frameHeight, int rotation)
throws InterruptedException {
while (this.frameWidth != frameWidth || this.frameHeight != frameHeight
|| this.rotation != rotation) {
wait();
}
}
@Override
public void onFirstFrameRendered() {}
@Override
// TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
@SuppressWarnings("NoSynchronizedMethodCheck")
public synchronized void onFrameResolutionChanged(
int frameWidth, int frameHeight, int rotation) {
this.frameWidth = frameWidth;
this.frameHeight = frameHeight;
this.rotation = rotation;
notifyAll();
}
}
final MockRendererEvents rendererEvents = new MockRendererEvents();
surfaceViewRenderer.init((EglBase.Context) null, rendererEvents);
// Test different rotation degress, but same rotated size.
for (int rotationDegree : new int[] {0, 90, 180, 270}) {
final int rotatedWidth = 1280;
final int rotatedHeight = 720;
final int unrotatedWidth = (rotationDegree % 180 == 0 ? rotatedWidth : rotatedHeight);
final int unrotatedHeight = (rotationDegree % 180 == 0 ? rotatedHeight : rotatedWidth);
final VideoFrame frame = createFrame(unrotatedWidth, unrotatedHeight, rotationDegree);
assertEquals(rotatedWidth, frame.getRotatedWidth());
assertEquals(rotatedHeight, frame.getRotatedHeight());
final String frameDimensions =
unrotatedWidth + "x" + unrotatedHeight + " with rotation " + rotationDegree;
surfaceViewRenderer.onFrame(frame);
frame.release();
rendererEvents.waitForFrameSize(unrotatedWidth, unrotatedHeight, rotationDegree);
// Test forcing to zero size.
for (RendererCommon.ScalingType scalingType : scalingTypes) {
for (int measureSpecMode : measureSpecModes) {
final int zeroMeasureSize = MeasureSpec.makeMeasureSpec(0, measureSpecMode);
assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, 0, 0,
zeroMeasureSize, zeroMeasureSize);
}
}
// Test perfect fit.
for (RendererCommon.ScalingType scalingType : scalingTypes) {
for (int measureSpecMode : measureSpecModes) {
assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, rotatedWidth,
rotatedHeight, MeasureSpec.makeMeasureSpec(rotatedWidth, measureSpecMode),
MeasureSpec.makeMeasureSpec(rotatedHeight, measureSpecMode));
}
}
// Force spec size with different aspect ratio than frame aspect ratio.
for (RendererCommon.ScalingType scalingType : scalingTypes) {
assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, 720, 1280,
MeasureSpec.makeMeasureSpec(720, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(1280, MeasureSpec.EXACTLY));
}
final float videoAspectRatio = (float) rotatedWidth / rotatedHeight;
{
// Relax both width and height constraints.
final int widthSpec = MeasureSpec.makeMeasureSpec(720, MeasureSpec.AT_MOST);
final int heightSpec = MeasureSpec.makeMeasureSpec(1280, MeasureSpec.AT_MOST);
for (RendererCommon.ScalingType scalingType : scalingTypes) {
final Point expectedSize =
RendererCommon.getDisplaySize(scalingType, videoAspectRatio, 720, 1280);
assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, expectedSize.x,
expectedSize.y, widthSpec, heightSpec);
}
}
{
// Force width to 720, but relax height constraint. This will give the same result as
// above, because width is already the limiting factor and will be maxed out.
final int widthSpec = MeasureSpec.makeMeasureSpec(720, MeasureSpec.EXACTLY);
final int heightSpec = MeasureSpec.makeMeasureSpec(1280, MeasureSpec.AT_MOST);
for (RendererCommon.ScalingType scalingType : scalingTypes) {
final Point expectedSize =
RendererCommon.getDisplaySize(scalingType, videoAspectRatio, 720, 1280);
assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, expectedSize.x,
expectedSize.y, widthSpec, heightSpec);
}
}
{
// Force height, but relax width constraint. This will force a bad layout size.
final int widthSpec = MeasureSpec.makeMeasureSpec(720, MeasureSpec.AT_MOST);
final int heightSpec = MeasureSpec.makeMeasureSpec(1280, MeasureSpec.EXACTLY);
for (RendererCommon.ScalingType scalingType : scalingTypes) {
assertMeasuredSize(
surfaceViewRenderer, scalingType, frameDimensions, 720, 1280, widthSpec, heightSpec);
}
}
}
surfaceViewRenderer.release();
}
}

View File

@ -1,15 +0,0 @@
/*
* Copyright 2018 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;
class TestConstants {
public static final String NATIVE_LIBRARY = "jingle_peerconnection_instrumentationtests_so";
}

View File

@ -1,43 +0,0 @@
/*
* Copyright 2018 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 androidx.test.filters.SmallTest;
import org.junit.BeforeClass;
import org.junit.Test;
public class TimestampAlignerTest {
@BeforeClass
public static void setUp() {
System.loadLibrary(TestConstants.NATIVE_LIBRARY);
}
@Test
@SmallTest
public void testGetRtcTimeNanos() {
TimestampAligner.getRtcTimeNanos();
}
@Test
@SmallTest
public void testDispose() {
final TimestampAligner timestampAligner = new TimestampAligner();
timestampAligner.dispose();
}
@Test
@SmallTest
public void testTranslateTimestamp() {
final TimestampAligner timestampAligner = new TimestampAligner();
timestampAligner.translateTimestamp(/* cameraTimeNs= */ 123);
timestampAligner.dispose();
}
}

View File

@ -1,88 +0,0 @@
/*
* 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 static org.junit.Assert.assertEquals;
import android.os.Environment;
import androidx.test.filters.SmallTest;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import org.junit.Before;
import org.junit.Test;
public class VideoFileRendererTest {
@Before
public void setUp() {
NativeLibrary.initialize(new NativeLibrary.DefaultLoader(), TestConstants.NATIVE_LIBRARY);
}
@Test
@SmallTest
public void testYuvRenderingToFile() throws InterruptedException, IOException {
EglBase eglBase = EglBase.create();
final String videoOutPath = Environment.getExternalStorageDirectory().getPath()
+ "/chromium_tests_root/testvideoout.y4m";
int frameWidth = 4;
int frameHeight = 4;
VideoFileRenderer videoFileRenderer =
new VideoFileRenderer(videoOutPath, frameWidth, frameHeight, eglBase.getEglBaseContext());
String[] frames = {
"THIS IS JUST SOME TEXT x", "THE SECOND FRAME qwerty.", "HERE IS THE THRID FRAME!"};
for (String frameStr : frames) {
int[] planeSizes = {
frameWidth * frameWidth, frameWidth * frameHeight / 4, frameWidth * frameHeight / 4};
int[] yuvStrides = {frameWidth, frameWidth / 2, frameWidth / 2};
ByteBuffer[] yuvPlanes = new ByteBuffer[3];
byte[] frameBytes = frameStr.getBytes(Charset.forName("US-ASCII"));
int pos = 0;
for (int i = 0; i < 3; i++) {
yuvPlanes[i] = ByteBuffer.allocateDirect(planeSizes[i]);
yuvPlanes[i].put(frameBytes, pos, planeSizes[i]);
yuvPlanes[i].rewind();
pos += planeSizes[i];
}
VideoFrame.I420Buffer buffer =
JavaI420Buffer.wrap(frameWidth, frameHeight, yuvPlanes[0], yuvStrides[0], yuvPlanes[1],
yuvStrides[1], yuvPlanes[2], yuvStrides[2], null /* releaseCallback */);
VideoFrame frame = new VideoFrame(buffer, 0 /* rotation */, 0 /* timestampNs */);
videoFileRenderer.onFrame(frame);
frame.release();
}
videoFileRenderer.release();
RandomAccessFile writtenFile = new RandomAccessFile(videoOutPath, "r");
try {
int length = (int) writtenFile.length();
byte[] data = new byte[length];
writtenFile.readFully(data);
String fileContent = new String(data, Charset.forName("US-ASCII"));
String expected = "YUV4MPEG2 C420 W4 H4 Ip F30:1 A1:1\n"
+ "FRAME\n"
+ "THIS IS JUST SOME TEXT xFRAME\n"
+ "THE SECOND FRAME qwerty.FRAME\n"
+ "HERE IS THE THRID FRAME!";
assertEquals(expected, fileContent);
} finally {
writtenFile.close();
}
new File(videoOutPath).delete();
}
}

View File

@ -1,530 +0,0 @@
/*
* Copyright 2018 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 org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import android.graphics.Matrix;
import android.opengl.GLES20;
import android.os.Handler;
import android.os.HandlerThread;
import androidx.test.filters.SmallTest;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.webrtc.VideoFrame;
/**
* Test VideoFrame buffers of different kind of formats: I420, RGB, OES, NV12, NV21, and verify
* toI420() and cropAndScale() behavior. Creating RGB/OES textures involves VideoFrameDrawer and
* GlRectDrawer and we are testing the full chain I420 -> OES/RGB texture -> I420, with and without
* cropping in the middle. Reading textures back to I420 also exercises the YuvConverter code.
*/
@RunWith(Parameterized.class)
public class VideoFrameBufferTest {
/**
* These tests are parameterized on this enum which represents the different VideoFrame.Buffers.
*/
private static enum BufferType { I420_JAVA, I420_NATIVE, RGB_TEXTURE, OES_TEXTURE, NV21, NV12 }
@Parameters(name = "{0}")
public static Collection<BufferType> parameters() {
return Arrays.asList(BufferType.values());
}
@BeforeClass
public static void setUp() {
// Needed for JniCommon.nativeAllocateByteBuffer() to work, which is used from JavaI420Buffer.
System.loadLibrary(TestConstants.NATIVE_LIBRARY);
}
private final BufferType bufferType;
public VideoFrameBufferTest(BufferType bufferType) {
this.bufferType = bufferType;
}
/**
* Create a VideoFrame.Buffer of the given type with the same pixel content as the given I420
* buffer.
*/
private static VideoFrame.Buffer createBufferWithType(
BufferType bufferType, VideoFrame.I420Buffer i420Buffer) {
VideoFrame.Buffer buffer;
switch (bufferType) {
case I420_JAVA:
buffer = i420Buffer;
buffer.retain();
assertEquals(VideoFrameBufferType.I420, buffer.getBufferType());
assertEquals(VideoFrameBufferType.I420, nativeGetBufferType(buffer));
return buffer;
case I420_NATIVE:
buffer = nativeGetNativeI420Buffer(i420Buffer);
assertEquals(VideoFrameBufferType.I420, buffer.getBufferType());
assertEquals(VideoFrameBufferType.I420, nativeGetBufferType(buffer));
return buffer;
case RGB_TEXTURE:
buffer = createRgbTextureBuffer(/* eglContext= */ null, i420Buffer);
assertEquals(VideoFrameBufferType.NATIVE, buffer.getBufferType());
assertEquals(VideoFrameBufferType.NATIVE, nativeGetBufferType(buffer));
return buffer;
case OES_TEXTURE:
buffer = createOesTextureBuffer(/* eglContext= */ null, i420Buffer);
assertEquals(VideoFrameBufferType.NATIVE, buffer.getBufferType());
assertEquals(VideoFrameBufferType.NATIVE, nativeGetBufferType(buffer));
return buffer;
case NV21:
buffer = createNV21Buffer(i420Buffer);
assertEquals(VideoFrameBufferType.NATIVE, buffer.getBufferType());
assertEquals(VideoFrameBufferType.NATIVE, nativeGetBufferType(buffer));
return buffer;
case NV12:
buffer = createNV12Buffer(i420Buffer);
assertEquals(VideoFrameBufferType.NATIVE, buffer.getBufferType());
assertEquals(VideoFrameBufferType.NATIVE, nativeGetBufferType(buffer));
return buffer;
default:
throw new IllegalArgumentException("Unknown buffer type: " + bufferType);
}
}
private VideoFrame.Buffer createBufferToTest(VideoFrame.I420Buffer i420Buffer) {
return createBufferWithType(this.bufferType, i420Buffer);
}
/**
* Creates a 16x16 I420 buffer that varies smoothly and spans all RGB values.
*/
public static VideoFrame.I420Buffer createTestI420Buffer() {
final int width = 16;
final int height = 16;
final int[] yData = new int[] {156, 162, 167, 172, 177, 182, 187, 193, 199, 203, 209, 214, 219,
224, 229, 235, 147, 152, 157, 162, 168, 173, 178, 183, 188, 193, 199, 205, 210, 215, 220,
225, 138, 143, 148, 153, 158, 163, 168, 174, 180, 184, 190, 195, 200, 205, 211, 216, 128,
133, 138, 144, 149, 154, 159, 165, 170, 175, 181, 186, 191, 196, 201, 206, 119, 124, 129,
134, 140, 145, 150, 156, 161, 166, 171, 176, 181, 187, 192, 197, 109, 114, 119, 126, 130,
136, 141, 146, 151, 156, 162, 167, 172, 177, 182, 187, 101, 105, 111, 116, 121, 126, 132,
137, 142, 147, 152, 157, 162, 168, 173, 178, 90, 96, 101, 107, 112, 117, 122, 127, 132, 138,
143, 148, 153, 158, 163, 168, 82, 87, 92, 97, 102, 107, 113, 118, 123, 128, 133, 138, 144,
149, 154, 159, 72, 77, 83, 88, 93, 98, 103, 108, 113, 119, 124, 129, 134, 139, 144, 150, 63,
68, 73, 78, 83, 89, 94, 99, 104, 109, 114, 119, 125, 130, 135, 140, 53, 58, 64, 69, 74, 79,
84, 89, 95, 100, 105, 110, 115, 120, 126, 131, 44, 49, 54, 59, 64, 70, 75, 80, 85, 90, 95,
101, 106, 111, 116, 121, 34, 40, 45, 50, 55, 60, 65, 71, 76, 81, 86, 91, 96, 101, 107, 113,
25, 30, 35, 40, 46, 51, 56, 61, 66, 71, 77, 82, 87, 92, 98, 103, 16, 21, 26, 31, 36, 41, 46,
52, 57, 62, 67, 72, 77, 83, 89, 94};
final int[] uData = new int[] {110, 113, 116, 118, 120, 123, 125, 128, 113, 116, 118, 120, 123,
125, 128, 130, 116, 118, 120, 123, 125, 128, 130, 132, 118, 120, 123, 125, 128, 130, 132,
135, 120, 123, 125, 128, 130, 132, 135, 138, 123, 125, 128, 130, 132, 135, 138, 139, 125,
128, 130, 132, 135, 138, 139, 142, 128, 130, 132, 135, 138, 139, 142, 145};
final int[] vData = new int[] {31, 45, 59, 73, 87, 100, 114, 127, 45, 59, 73, 87, 100, 114, 128,
141, 59, 73, 87, 100, 114, 127, 141, 155, 73, 87, 100, 114, 127, 141, 155, 168, 87, 100,
114, 128, 141, 155, 168, 182, 100, 114, 128, 141, 155, 168, 182, 197, 114, 127, 141, 155,
168, 182, 196, 210, 127, 141, 155, 168, 182, 196, 210, 224};
return JavaI420Buffer.wrap(width, height, toByteBuffer(yData),
/* strideY= */ width, toByteBuffer(uData), /* strideU= */ width / 2, toByteBuffer(vData),
/* strideV= */ width / 2,
/* releaseCallback= */ null);
}
/**
* Create an RGB texture buffer available in `eglContext` with the same pixel content as the given
* I420 buffer.
*/
public static VideoFrame.TextureBuffer createRgbTextureBuffer(
EglBase.Context eglContext, VideoFrame.I420Buffer i420Buffer) {
final int width = i420Buffer.getWidth();
final int height = i420Buffer.getHeight();
final HandlerThread renderThread = new HandlerThread("RGB texture thread");
renderThread.start();
final Handler renderThreadHandler = new Handler(renderThread.getLooper());
return ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, () -> {
// Create EGL base with a pixel buffer as display output.
final EglBase eglBase = EglBase.create(eglContext, EglBase.CONFIG_PIXEL_BUFFER);
eglBase.createDummyPbufferSurface();
eglBase.makeCurrent();
final GlTextureFrameBuffer textureFrameBuffer = new GlTextureFrameBuffer(GLES20.GL_RGBA);
textureFrameBuffer.setSize(width, height);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, textureFrameBuffer.getFrameBufferId());
drawI420Buffer(i420Buffer);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
final YuvConverter yuvConverter = new YuvConverter();
return new TextureBufferImpl(width, height, VideoFrame.TextureBuffer.Type.RGB,
textureFrameBuffer.getTextureId(),
/* transformMatrix= */ new Matrix(), renderThreadHandler, yuvConverter,
/* releaseCallback= */ () -> renderThreadHandler.post(() -> {
textureFrameBuffer.release();
yuvConverter.release();
eglBase.release();
renderThread.quit();
}));
});
}
/**
* Create an OES texture buffer available in `eglContext` with the same pixel content as the given
* I420 buffer.
*/
public static VideoFrame.TextureBuffer createOesTextureBuffer(
EglBase.Context eglContext, VideoFrame.I420Buffer i420Buffer) {
final int width = i420Buffer.getWidth();
final int height = i420Buffer.getHeight();
// Create resources for generating OES textures.
final SurfaceTextureHelper surfaceTextureHelper =
SurfaceTextureHelper.create("SurfaceTextureHelper test", eglContext);
surfaceTextureHelper.setTextureSize(width, height);
final HandlerThread renderThread = new HandlerThread("OES texture thread");
renderThread.start();
final Handler renderThreadHandler = new Handler(renderThread.getLooper());
final VideoFrame.TextureBuffer oesBuffer =
ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, () -> {
// Create EGL base with the SurfaceTexture as display output.
final EglBase eglBase = EglBase.create(eglContext, EglBase.CONFIG_PLAIN);
eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
eglBase.makeCurrent();
assertEquals(width, eglBase.surfaceWidth());
assertEquals(height, eglBase.surfaceHeight());
final SurfaceTextureHelperTest.MockTextureListener listener =
new SurfaceTextureHelperTest.MockTextureListener();
surfaceTextureHelper.startListening(listener);
// Draw the frame and block until an OES texture is delivered.
drawI420Buffer(i420Buffer);
eglBase.swapBuffers();
final VideoFrame.TextureBuffer textureBuffer = listener.waitForTextureBuffer();
surfaceTextureHelper.stopListening();
surfaceTextureHelper.dispose();
return textureBuffer;
});
renderThread.quit();
return oesBuffer;
}
/** Create an NV21Buffer with the same pixel content as the given I420 buffer. */
public static NV21Buffer createNV21Buffer(VideoFrame.I420Buffer i420Buffer) {
final int width = i420Buffer.getWidth();
final int height = i420Buffer.getHeight();
final int chromaStride = width;
final int chromaWidth = (width + 1) / 2;
final int chromaHeight = (height + 1) / 2;
final int ySize = width * height;
final ByteBuffer nv21Buffer = ByteBuffer.allocateDirect(ySize + chromaStride * chromaHeight);
// We don't care what the array offset is since we only want an array that is direct.
@SuppressWarnings("ByteBufferBackingArray") final byte[] nv21Data = nv21Buffer.array();
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
final byte yValue = i420Buffer.getDataY().get(y * i420Buffer.getStrideY() + x);
nv21Data[y * width + x] = yValue;
}
}
for (int y = 0; y < chromaHeight; ++y) {
for (int x = 0; x < chromaWidth; ++x) {
final byte uValue = i420Buffer.getDataU().get(y * i420Buffer.getStrideU() + x);
final byte vValue = i420Buffer.getDataV().get(y * i420Buffer.getStrideV() + x);
nv21Data[ySize + y * chromaStride + 2 * x + 0] = vValue;
nv21Data[ySize + y * chromaStride + 2 * x + 1] = uValue;
}
}
return new NV21Buffer(nv21Data, width, height, /* releaseCallback= */ null);
}
/** Create an NV12Buffer with the same pixel content as the given I420 buffer. */
public static NV12Buffer createNV12Buffer(VideoFrame.I420Buffer i420Buffer) {
final int width = i420Buffer.getWidth();
final int height = i420Buffer.getHeight();
final int chromaStride = width;
final int chromaWidth = (width + 1) / 2;
final int chromaHeight = (height + 1) / 2;
final int ySize = width * height;
final ByteBuffer nv12Buffer = ByteBuffer.allocateDirect(ySize + chromaStride * chromaHeight);
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
final byte yValue = i420Buffer.getDataY().get(y * i420Buffer.getStrideY() + x);
nv12Buffer.put(y * width + x, yValue);
}
}
for (int y = 0; y < chromaHeight; ++y) {
for (int x = 0; x < chromaWidth; ++x) {
final byte uValue = i420Buffer.getDataU().get(y * i420Buffer.getStrideU() + x);
final byte vValue = i420Buffer.getDataV().get(y * i420Buffer.getStrideV() + x);
nv12Buffer.put(ySize + y * chromaStride + 2 * x + 0, uValue);
nv12Buffer.put(ySize + y * chromaStride + 2 * x + 1, vValue);
}
}
return new NV12Buffer(width, height, /* stride= */ width, /* sliceHeight= */ height, nv12Buffer,
/* releaseCallback */ null);
}
/** Print the ByteBuffer plane to the StringBuilder. */
private static void printPlane(
StringBuilder stringBuilder, int width, int height, ByteBuffer plane, int stride) {
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
final int value = plane.get(y * stride + x) & 0xFF;
if (x != 0) {
stringBuilder.append(", ");
}
stringBuilder.append(value);
}
stringBuilder.append("\n");
}
}
/** Convert the pixel content of an I420 buffer to a string representation. */
private static String i420BufferToString(VideoFrame.I420Buffer buffer) {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(
"I420 buffer with size: " + buffer.getWidth() + "x" + buffer.getHeight() + ".\n");
stringBuilder.append("Y-plane:\n");
printPlane(stringBuilder, buffer.getWidth(), buffer.getHeight(), buffer.getDataY(),
buffer.getStrideY());
final int chromaWidth = (buffer.getWidth() + 1) / 2;
final int chromaHeight = (buffer.getHeight() + 1) / 2;
stringBuilder.append("U-plane:\n");
printPlane(stringBuilder, chromaWidth, chromaHeight, buffer.getDataU(), buffer.getStrideU());
stringBuilder.append("V-plane:\n");
printPlane(stringBuilder, chromaWidth, chromaHeight, buffer.getDataV(), buffer.getStrideV());
return stringBuilder.toString();
}
/**
* Assert that the given I420 buffers are almost identical, allowing for some difference due to
* numerical errors. It has limits for both overall PSNR and maximum individual pixel difference.
*/
public static void assertAlmostEqualI420Buffers(
VideoFrame.I420Buffer bufferA, VideoFrame.I420Buffer bufferB) {
final int diff = maxDiff(bufferA, bufferB);
assertThat("Pixel difference too high: " + diff + "."
+ "\nBuffer A: " + i420BufferToString(bufferA)
+ "Buffer B: " + i420BufferToString(bufferB),
diff, lessThanOrEqualTo(4));
final double psnr = calculatePsnr(bufferA, bufferB);
assertThat("PSNR too low: " + psnr + "."
+ "\nBuffer A: " + i420BufferToString(bufferA)
+ "Buffer B: " + i420BufferToString(bufferB),
psnr, greaterThanOrEqualTo(50.0));
}
/** Returns a flattened list of pixel differences for two ByteBuffer planes. */
private static List<Integer> getPixelDiffs(
int width, int height, ByteBuffer planeA, int strideA, ByteBuffer planeB, int strideB) {
List<Integer> res = new ArrayList<>();
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
final int valueA = planeA.get(y * strideA + x) & 0xFF;
final int valueB = planeB.get(y * strideB + x) & 0xFF;
res.add(Math.abs(valueA - valueB));
}
}
return res;
}
/** Returns a flattened list of pixel differences for two I420 buffers. */
private static List<Integer> getPixelDiffs(
VideoFrame.I420Buffer bufferA, VideoFrame.I420Buffer bufferB) {
assertEquals(bufferA.getWidth(), bufferB.getWidth());
assertEquals(bufferA.getHeight(), bufferB.getHeight());
final int width = bufferA.getWidth();
final int height = bufferA.getHeight();
final int chromaWidth = (width + 1) / 2;
final int chromaHeight = (height + 1) / 2;
final List<Integer> diffs = getPixelDiffs(width, height, bufferA.getDataY(),
bufferA.getStrideY(), bufferB.getDataY(), bufferB.getStrideY());
diffs.addAll(getPixelDiffs(chromaWidth, chromaHeight, bufferA.getDataU(), bufferA.getStrideU(),
bufferB.getDataU(), bufferB.getStrideU()));
diffs.addAll(getPixelDiffs(chromaWidth, chromaHeight, bufferA.getDataV(), bufferA.getStrideV(),
bufferB.getDataV(), bufferB.getStrideV()));
return diffs;
}
/** Returns the maximum pixel difference from any of the Y/U/V planes in the given buffers. */
private static int maxDiff(VideoFrame.I420Buffer bufferA, VideoFrame.I420Buffer bufferB) {
return Collections.max(getPixelDiffs(bufferA, bufferB));
}
/**
* Returns the PSNR given a sum of squared error and the number of measurements that were added.
*/
private static double sseToPsnr(long sse, int count) {
if (sse == 0) {
return Double.POSITIVE_INFINITY;
}
final double meanSquaredError = (double) sse / (double) count;
final double maxPixelValue = 255.0;
return 10.0 * Math.log10(maxPixelValue * maxPixelValue / meanSquaredError);
}
/** Returns the PSNR of the given I420 buffers. */
private static double calculatePsnr(
VideoFrame.I420Buffer bufferA, VideoFrame.I420Buffer bufferB) {
final List<Integer> pixelDiffs = getPixelDiffs(bufferA, bufferB);
long sse = 0;
for (int pixelDiff : pixelDiffs) {
sse += pixelDiff * pixelDiff;
}
return sseToPsnr(sse, pixelDiffs.size());
}
/**
* Convert an int array to a byte array and make sure the values are within the range [0, 255].
*/
private static byte[] toByteArray(int[] array) {
final byte[] res = new byte[array.length];
for (int i = 0; i < array.length; ++i) {
final int value = array[i];
assertThat(value, greaterThanOrEqualTo(0));
assertThat(value, lessThanOrEqualTo(255));
res[i] = (byte) value;
}
return res;
}
/** Convert a byte array to a direct ByteBuffer. */
private static ByteBuffer toByteBuffer(int[] array) {
final ByteBuffer buffer = ByteBuffer.allocateDirect(array.length);
buffer.put(toByteArray(array));
buffer.rewind();
return buffer;
}
/**
* Draw an I420 buffer on the currently bound frame buffer, allocating and releasing any
* resources necessary.
*/
private static void drawI420Buffer(VideoFrame.I420Buffer i420Buffer) {
final GlRectDrawer drawer = new GlRectDrawer();
final VideoFrameDrawer videoFrameDrawer = new VideoFrameDrawer();
videoFrameDrawer.drawFrame(
new VideoFrame(i420Buffer, /* rotation= */ 0, /* timestampNs= */ 0), drawer);
videoFrameDrawer.release();
drawer.release();
}
/**
* Helper function that tests cropAndScale() with the given cropping and scaling parameters, and
* compares the pixel content against a reference I420 buffer.
*/
private void testCropAndScale(
int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) {
final VideoFrame.I420Buffer referenceI420Buffer = createTestI420Buffer();
final VideoFrame.Buffer bufferToTest = createBufferToTest(referenceI420Buffer);
final VideoFrame.Buffer croppedReferenceBuffer = referenceI420Buffer.cropAndScale(
cropX, cropY, cropWidth, cropHeight, scaleWidth, scaleHeight);
referenceI420Buffer.release();
final VideoFrame.I420Buffer croppedReferenceI420Buffer = croppedReferenceBuffer.toI420();
croppedReferenceBuffer.release();
final VideoFrame.Buffer croppedBufferToTest =
bufferToTest.cropAndScale(cropX, cropY, cropWidth, cropHeight, scaleWidth, scaleHeight);
bufferToTest.release();
final VideoFrame.I420Buffer croppedOutputI420Buffer = croppedBufferToTest.toI420();
croppedBufferToTest.release();
assertAlmostEqualI420Buffers(croppedReferenceI420Buffer, croppedOutputI420Buffer);
croppedReferenceI420Buffer.release();
croppedOutputI420Buffer.release();
}
@Test
@SmallTest
/** Test calling toI420() and comparing pixel content against I420 reference. */
public void testToI420() {
final VideoFrame.I420Buffer referenceI420Buffer = createTestI420Buffer();
final VideoFrame.Buffer bufferToTest = createBufferToTest(referenceI420Buffer);
final VideoFrame.I420Buffer outputI420Buffer = bufferToTest.toI420();
bufferToTest.release();
assertEquals(VideoFrameBufferType.I420, nativeGetBufferType(outputI420Buffer));
assertAlmostEqualI420Buffers(referenceI420Buffer, outputI420Buffer);
referenceI420Buffer.release();
outputI420Buffer.release();
}
@Test
@SmallTest
/** Pure 2x scaling with no cropping. */
public void testScale2x() {
testCropAndScale(0 /* cropX= */, 0 /* cropY= */, /* cropWidth= */ 16, /* cropHeight= */ 16,
/* scaleWidth= */ 8, /* scaleHeight= */ 8);
}
@Test
@SmallTest
/** Test cropping only X direction, with no scaling. */
public void testCropX() {
testCropAndScale(8 /* cropX= */, 0 /* cropY= */, /* cropWidth= */ 8, /* cropHeight= */ 16,
/* scaleWidth= */ 8, /* scaleHeight= */ 16);
}
@Test
@SmallTest
/** Test cropping only Y direction, with no scaling. */
public void testCropY() {
testCropAndScale(0 /* cropX= */, 8 /* cropY= */, /* cropWidth= */ 16, /* cropHeight= */ 8,
/* scaleWidth= */ 16, /* scaleHeight= */ 8);
}
@Test
@SmallTest
/** Test center crop, with no scaling. */
public void testCenterCrop() {
testCropAndScale(4 /* cropX= */, 4 /* cropY= */, /* cropWidth= */ 8, /* cropHeight= */ 8,
/* scaleWidth= */ 8, /* scaleHeight= */ 8);
}
@Test
@SmallTest
/** Test non-center crop for right bottom corner, with no scaling. */
public void testRightBottomCornerCrop() {
testCropAndScale(8 /* cropX= */, 8 /* cropY= */, /* cropWidth= */ 8, /* cropHeight= */ 8,
/* scaleWidth= */ 8, /* scaleHeight= */ 8);
}
@Test
@SmallTest
/** Test combined cropping and scaling. */
public void testCropAndScale() {
testCropAndScale(4 /* cropX= */, 4 /* cropY= */, /* cropWidth= */ 12, /* cropHeight= */ 12,
/* scaleWidth= */ 8, /* scaleHeight= */ 8);
}
@VideoFrameBufferType private static native int nativeGetBufferType(VideoFrame.Buffer buffer);
/** Returns the copy of I420Buffer using WrappedNativeI420Buffer. */
private static native VideoFrame.Buffer nativeGetNativeI420Buffer(
VideoFrame.I420Buffer i420Buffer);
}

View File

@ -1,112 +0,0 @@
/*
* Copyright 2013 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import android.support.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
/** Unit tests for {@link VideoTrack}. */
public class VideoTrackTest {
private PeerConnectionFactory factory;
private VideoSource videoSource;
private VideoTrack videoTrack;
@Before
public void setUp() {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
factory = PeerConnectionFactory.builder().createPeerConnectionFactory();
videoSource = factory.createVideoSource(/* isScreencast= */ false);
videoTrack = factory.createVideoTrack("video", videoSource);
}
@Test
@SmallTest
public void testAddingNullVideoSink() {
try {
videoTrack.addSink(/* sink= */ null);
fail("Should have thrown an IllegalArgumentException.");
} catch (IllegalArgumentException e) {
// Expected path.
}
}
@Test
@SmallTest
public void testRemovingNullVideoSink() {
videoTrack.removeSink(/* sink= */ null);
}
@Test
@SmallTest
public void testRemovingNonExistantVideoSink() {
final VideoSink videoSink = new VideoSink() {
@Override
public void onFrame(VideoFrame frame) {}
};
videoTrack.removeSink(videoSink);
}
@Test
@SmallTest
public void testAddingSameVideoSinkMultipleTimes() {
class FrameCounter implements VideoSink {
private int count;
public int getCount() {
return count;
}
@Override
public void onFrame(VideoFrame frame) {
count += 1;
}
}
final FrameCounter frameCounter = new FrameCounter();
final VideoFrame videoFrame = new VideoFrame(
JavaI420Buffer.allocate(/* width= */ 32, /* height= */ 32), /* rotation= */ 0,
/* timestampNs= */ 0);
videoTrack.addSink(frameCounter);
videoTrack.addSink(frameCounter);
videoSource.getCapturerObserver().onFrameCaptured(videoFrame);
// Even though we called addSink() multiple times, we should only get one frame out.
assertEquals(1, frameCounter.count);
}
@Test
@SmallTest
public void testAddingAndRemovingVideoSink() {
final VideoFrame videoFrame = new VideoFrame(
JavaI420Buffer.allocate(/* width= */ 32, /* height= */ 32), /* rotation= */ 0,
/* timestampNs= */ 0);
final VideoSink failSink = new VideoSink() {
@Override
public void onFrame(VideoFrame frame) {
fail("onFrame() should not be called on removed sink");
}
};
videoTrack.addSink(failSink);
videoTrack.removeSink(failSink);
videoSource.getCapturerObserver().onFrameCaptured(videoFrame);
}
}

View File

@ -1,31 +0,0 @@
/*
* 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 android.support.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.webrtc.PeerConnectionFactory;
// This test is intended to run on ARM and catch LoadLibrary errors when we load the WebRTC
// JNI. It can't really be setting up calls since ARM emulators are too slow, but instantiating
// a peer connection isn't timing-sensitive, so we can at least do that.
public class WebRtcJniBootTest {
@Test
@SmallTest
public void testJniLoadsWithoutError() throws InterruptedException {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(InstrumentationRegistry.getTargetContext())
.setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
.createInitializationOptions());
PeerConnectionFactory.builder().createPeerConnectionFactory();
}
}

View File

@ -1,207 +0,0 @@
/*
* Copyright 2017 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import androidx.test.filters.SmallTest;
import java.nio.ByteBuffer;
import org.junit.Before;
import org.junit.Test;
public class YuvHelperTest {
private static final int TEST_WIDTH = 3;
private static final int TEST_HEIGHT = 3;
private static final int TEST_CHROMA_WIDTH = 2;
private static final int TEST_CHROMA_HEIGHT = 2;
private static final int TEST_I420_STRIDE_Y = 3;
private static final int TEST_I420_STRIDE_V = 2;
private static final int TEST_I420_STRIDE_U = 4;
private static final ByteBuffer TEST_I420_Y = getTestY();
private static final ByteBuffer TEST_I420_U = getTestU();
private static final ByteBuffer TEST_I420_V = getTestV();
private static ByteBuffer getTestY() {
final ByteBuffer testY = ByteBuffer.allocateDirect(TEST_HEIGHT * TEST_I420_STRIDE_Y);
testY.put(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9});
return testY;
}
private static ByteBuffer getTestU() {
final ByteBuffer testU = ByteBuffer.allocateDirect(TEST_CHROMA_HEIGHT * TEST_I420_STRIDE_V);
testU.put(new byte[] {51, 52, 53, 54});
return testU;
}
private static ByteBuffer getTestV() {
final ByteBuffer testV = ByteBuffer.allocateDirect(TEST_CHROMA_HEIGHT * TEST_I420_STRIDE_U);
testV.put(new byte[] {101, 102, 103, 104, 105, 106, 107, 108});
return testV;
}
@Before
public void setUp() {
NativeLibrary.initialize(new NativeLibrary.DefaultLoader(), TestConstants.NATIVE_LIBRARY);
}
@SmallTest
@Test
public void testCopyPlane() {
final int dstStride = TEST_WIDTH;
final ByteBuffer dst = ByteBuffer.allocateDirect(TEST_HEIGHT * dstStride);
YuvHelper.copyPlane(TEST_I420_Y, TEST_I420_STRIDE_Y, dst, dstStride, TEST_WIDTH, TEST_HEIGHT);
assertByteBufferContentEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9}, dst);
}
@SmallTest
@Test
public void testI420Copy() {
final int dstStrideY = TEST_WIDTH;
final int dstStrideU = TEST_CHROMA_WIDTH;
final int dstStrideV = TEST_CHROMA_WIDTH;
final ByteBuffer dstY = ByteBuffer.allocateDirect(TEST_HEIGHT * dstStrideY);
final ByteBuffer dstU = ByteBuffer.allocateDirect(TEST_CHROMA_HEIGHT * dstStrideU);
final ByteBuffer dstV = ByteBuffer.allocateDirect(TEST_CHROMA_HEIGHT * dstStrideV);
YuvHelper.I420Copy(TEST_I420_Y, TEST_I420_STRIDE_Y, TEST_I420_U, TEST_I420_STRIDE_V,
TEST_I420_V, TEST_I420_STRIDE_U, dstY, dstStrideY, dstU, dstStrideU, dstV, dstStrideV,
TEST_WIDTH, TEST_HEIGHT);
assertByteBufferContentEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9}, dstY);
assertByteBufferContentEquals(new byte[] {51, 52, 53, 54}, dstU);
assertByteBufferContentEquals(new byte[] {101, 102, 105, 106}, dstV);
}
@SmallTest
@Test
public void testI420CopyTight() {
final ByteBuffer dst = ByteBuffer.allocateDirect(
TEST_WIDTH * TEST_HEIGHT + TEST_CHROMA_WIDTH * TEST_CHROMA_HEIGHT * 2);
YuvHelper.I420Copy(TEST_I420_Y, TEST_I420_STRIDE_Y, TEST_I420_U, TEST_I420_STRIDE_V,
TEST_I420_V, TEST_I420_STRIDE_U, dst, TEST_WIDTH, TEST_HEIGHT);
assertByteBufferContentEquals(
new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 51, 52, 53, 54, 101, 102, 105, 106}, dst);
}
@SmallTest
@Test
public void testI420CopyStride() {
final int dstStrideY = 4;
final int dstSliceHeightY = 4;
final int dstStrideU = dstStrideY / 2;
final int dstSliceHeightU = dstSliceHeightY / 2;
final int dstSize = dstStrideY * dstStrideY * 3 / 2;
final ByteBuffer dst = ByteBuffer.allocateDirect(dstSize);
YuvHelper.I420Copy(TEST_I420_Y, TEST_I420_STRIDE_Y, TEST_I420_U, TEST_I420_STRIDE_V,
TEST_I420_V, TEST_I420_STRIDE_U, dst, TEST_WIDTH, TEST_HEIGHT, dstStrideY, dstSliceHeightY,
dstStrideU, dstSliceHeightU);
assertByteBufferContentEquals(new byte[] {1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0, 0, 0, 0, 0, 51,
52, 53, 54, 101, 102, 105, 106},
dst);
}
@SmallTest
@Test
public void testI420ToNV12() {
final int dstStrideY = TEST_WIDTH;
final int dstStrideUV = TEST_CHROMA_WIDTH * 2;
final ByteBuffer dstY = ByteBuffer.allocateDirect(TEST_HEIGHT * dstStrideY);
final ByteBuffer dstUV = ByteBuffer.allocateDirect(2 * TEST_CHROMA_HEIGHT * dstStrideUV);
YuvHelper.I420ToNV12(TEST_I420_Y, TEST_I420_STRIDE_Y, TEST_I420_U, TEST_I420_STRIDE_V,
TEST_I420_V, TEST_I420_STRIDE_U, dstY, dstStrideY, dstUV, dstStrideUV, TEST_WIDTH,
TEST_HEIGHT);
assertByteBufferContentEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9}, dstY);
assertByteBufferContentEquals(new byte[] {51, 101, 52, 102, 53, 105, 54, 106}, dstUV);
}
@SmallTest
@Test
public void testI420ToNV12Tight() {
final int dstStrideY = TEST_WIDTH;
final int dstStrideUV = TEST_CHROMA_WIDTH * 2;
final ByteBuffer dst = ByteBuffer.allocateDirect(
TEST_WIDTH * TEST_HEIGHT + TEST_CHROMA_WIDTH * TEST_CHROMA_HEIGHT * 2);
YuvHelper.I420ToNV12(TEST_I420_Y, TEST_I420_STRIDE_Y, TEST_I420_U, TEST_I420_STRIDE_V,
TEST_I420_V, TEST_I420_STRIDE_U, dst, TEST_WIDTH, TEST_HEIGHT);
assertByteBufferContentEquals(
new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 51, 101, 52, 102, 53, 105, 54, 106}, dst);
}
@SmallTest
@Test
public void testI420ToNV12Stride() {
final int dstStrideY = 4;
final int dstSliceHeightY = 4;
final int dstSize = dstStrideY * dstStrideY * 3 / 2;
final ByteBuffer dst = ByteBuffer.allocateDirect(dstSize);
YuvHelper.I420ToNV12(TEST_I420_Y, TEST_I420_STRIDE_Y, TEST_I420_U, TEST_I420_STRIDE_V,
TEST_I420_V, TEST_I420_STRIDE_U, dst, TEST_WIDTH, TEST_HEIGHT, dstStrideY, dstSliceHeightY);
assertByteBufferContentEquals(new byte[] {1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0, 0, 0, 0, 0, 51,
101, 52, 102, 53, 105, 54, 106},
dst);
}
private static void assertByteBufferContentEquals(byte[] expected, ByteBuffer test) {
assertTrue(
"ByteBuffer is too small. Expected " + expected.length + " but was " + test.capacity(),
test.capacity() >= expected.length);
for (int i = 0; i < expected.length; i++) {
assertEquals("Unexpected ByteBuffer contents at index: " + i, expected[i], test.get(i));
}
}
@SmallTest
@Test
public void testI420Rotate90() {
final int dstStrideY = TEST_HEIGHT;
final int dstStrideU = TEST_CHROMA_HEIGHT;
final int dstStrideV = TEST_CHROMA_HEIGHT;
final ByteBuffer dstY = ByteBuffer.allocateDirect(TEST_WIDTH * dstStrideY);
final ByteBuffer dstU = ByteBuffer.allocateDirect(TEST_CHROMA_WIDTH * dstStrideU);
final ByteBuffer dstV = ByteBuffer.allocateDirect(TEST_CHROMA_WIDTH * dstStrideV);
YuvHelper.I420Rotate(TEST_I420_Y, TEST_I420_STRIDE_Y, TEST_I420_U, TEST_I420_STRIDE_V,
TEST_I420_V, TEST_I420_STRIDE_U, dstY, dstStrideY, dstU, dstStrideU, dstV, dstStrideV,
TEST_WIDTH, TEST_HEIGHT, 90);
assertByteBufferContentEquals(new byte[] {7, 4, 1, 8, 5, 2, 9, 6, 3}, dstY);
assertByteBufferContentEquals(new byte[] {53, 51, 54, 52}, dstU);
assertByteBufferContentEquals(new byte[] {105, 101, 106, 102}, dstV);
}
@SmallTest
@Test
public void testI420Rotate90Tight() {
final ByteBuffer dst = ByteBuffer.allocateDirect(
TEST_WIDTH * TEST_HEIGHT + TEST_CHROMA_WIDTH * TEST_CHROMA_HEIGHT * 2);
YuvHelper.I420Rotate(TEST_I420_Y, TEST_I420_STRIDE_Y, TEST_I420_U, TEST_I420_STRIDE_V,
TEST_I420_V, TEST_I420_STRIDE_U, dst, TEST_WIDTH, TEST_HEIGHT, 90);
assertByteBufferContentEquals(
new byte[] {7, 4, 1, 8, 5, 2, 9, 6, 3, 53, 51, 54, 52, 105, 101, 106, 102}, dst);
}
}

View File

@ -1,5 +0,0 @@
YUV4MPEG2 C420 W4 H4 Ip F30:1 A1:1
FRAME
THIS IS JUST SOME TEXT xFRAME
THE SECOND FRAME qwerty.FRAME
HERE IS THE THRID FRAME!

View File

@ -1,45 +0,0 @@
/*
* Copyright 2021 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.
*/
#include "api/video/i420_buffer.h"
#include "sdk/android/src/jni/jni_helpers.h"
#include "sdk/android/src/jni/video_frame.h"
#include "sdk/android/src/jni/wrapped_native_i420_buffer.h"
namespace webrtc {
namespace jni {
JNI_FUNCTION_DECLARATION(jint,
VideoFrameBufferTest_nativeGetBufferType,
JNIEnv* jni,
jclass,
jobject video_frame_buffer) {
const JavaParamRef<jobject> j_video_frame_buffer(video_frame_buffer);
rtc::scoped_refptr<VideoFrameBuffer> buffer =
JavaToNativeFrameBuffer(jni, j_video_frame_buffer);
return static_cast<jint>(buffer->type());
}
JNI_FUNCTION_DECLARATION(jobject,
VideoFrameBufferTest_nativeGetNativeI420Buffer,
JNIEnv* jni,
jclass,
jobject i420_buffer) {
const JavaParamRef<jobject> j_i420_buffer(i420_buffer);
rtc::scoped_refptr<VideoFrameBuffer> buffer =
JavaToNativeFrameBuffer(jni, j_i420_buffer);
const I420BufferInterface* inputBuffer = buffer->GetI420();
RTC_DCHECK(inputBuffer != nullptr);
rtc::scoped_refptr<I420Buffer> outputBuffer = I420Buffer::Copy(*inputBuffer);
return WrapI420Buffer(jni, outputBuffer).Release();
}
} // namespace jni
} // namespace webrtc