mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 19:35:51 +00:00
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:
parent
1a75e5633c
commit
c1ebba1d9f
@ -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>
|
@ -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
|
@ -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>
|
@ -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
|
@ -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
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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"));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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!
|
@ -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
|
Loading…
Reference in New Issue
Block a user