This CL also includes the changes required to fix build errors on Android builds. Change log:03a29cf406..0549de0c2dFull diff:03a29cf406..0549de0c2dChanged dependencies * src/base:58887e6f2a..fe7fd7b2be* src/build:4638e26758..8aa210e09b* src/buildtools/linux64: git_revision:d565aa3e72dd9e81da9595ee8c9d7b24cb45c48b..git_revision:c0a2d23c21e87f27f5af3e5dc2a99f2ef3480b9e * src/buildtools/mac: git_revision:d565aa3e72dd9e81da9595ee8c9d7b24cb45c48b..git_revision:c0a2d23c21e87f27f5af3e5dc2a99f2ef3480b9e * src/buildtools/third_party/libc++abi/trunk:e8bf577fbf..24e92c2bee* src/buildtools/third_party/libunwind/trunk:d7b11d7989..cdb04dc77c* src/buildtools/win: git_revision:d565aa3e72dd9e81da9595ee8c9d7b24cb45c48b..git_revision:c0a2d23c21e87f27f5af3e5dc2a99f2ef3480b9e * src/ios:c172f6d55d..dafd1d1517* src/testing:e6a8848e04..45ed21d9c9* src/third_party:43300033c5..be994fedb5* src/third_party/android_deps/libs/org_jetbrains_kotlin_kotlin_stdlib: version:2@1.5.10.cr0..version:2@1.5.21.cr0 * src/third_party/android_deps/libs/org_jetbrains_kotlin_kotlin_stdlib_common: version:2@1.5.10.cr0..version:2@1.5.21.cr0 * src/third_party/android_sdk/public: 8LZujEmLjSh0g3JciDA3cslSptxKs9HOa_iUPXkOeYQC..tRoD45SCi7UleQqSV7MrMQO1_e5P8ysphkCcj6z_cCQC * src/third_party/android_sdk/public: V__2Ycej-H2-6AcXX5A3gi7sIk74SuN44PBm2uC_N1sC..ZT3JmI6GMG4YVcZ1OtECRVMOLLJAWAdPbi-OclubJLMC * src/third_party/android_sdk/public: A4EvXZUIuQho0QRDJopMUpgyp6NA3aiDQjGKPUKbowMC..gMHhUuoQRKfxr-MBn3fNNXZtkAVXtOwMwT7kfx8jkIgC * src/third_party/android_sdk/public: 8tF0AOj7Dwlv4j7_nfkhxWB0jzrvWWYjEIpirt8FIWYC..qi_k82nm6j9nz4dQosOoqXew4_TFAy8rcGOHDLptx1sC * src/third_party/android_sdk/public: YMUu9EHNZ__2Xcxl-KsaSf-dI5TMt_P62IseUVsxktMC..lL3IGexKjYlwjO_1Ga-xwxgwbE_w-lmi2Zi1uOlWUIAC * src/third_party/android_sdk/public: 4gxhM8E62bvZpQs7Q3d0DinQaW0RLCIefhXrQBFkNy8C..n7svc8KYah-i4s8zwkVa85SI3_H0WFOniP0mpwNdFO0C * src/third_party/androidx: hKpaz7h0NioWOEg8YP2hSuRaZ6uRCUEUcmNcvkxr9aYC..HHo3GgWHTJyTdX1eY15wQ40-pIfmMojudjRzMFoE8JoC * src/third_party/boringssl/src: https://boringssl.googlesource.com/boringssl.git/+log/a10017c548..519c2986c7 * src/third_party/catapult: https://chromium.googlesource.com/catapult.git/+log/9ac1fdf373..10f6e4b89b * src/third_party/freetype/src:801cd842e2..47b1a541cb* src/third_party/googletest/src:4ec4cd23f4..2d924d7a97* src/third_party/libaom/source/libaom: https://aomedia.googlesource.com/aom.git/+log/aba245dde3..d80b8cecab * src/third_party/libvpx/source/libvpx:eebc5cd487..977e77006e* src/third_party/perfetto:13482fe8f9..9edd589321* src/tools:2f3bb89f4e..f7be2d25c7* src/tools/luci-go: git_revision:9ee8b1d719c0d3c268e0e19282351ca78024af2d..git_revision:63874080a20260642c8df82d4f4885ff30b33fb6 * src/tools/luci-go: git_revision:9ee8b1d719c0d3c268e0e19282351ca78024af2d..git_revision:63874080a20260642c8df82d4f4885ff30b33fb6 * src/tools/luci-go: git_revision:9ee8b1d719c0d3c268e0e19282351ca78024af2d..git_revision:63874080a20260642c8df82d4f4885ff30b33fb6 DEPS diff:03a29cf406..0549de0c2d/DEPS Clang version changed llvmorg-13-init-15163-g98033fdc:llvmorg-13-init-15561-gf98ed74f Details:03a29cf406..0549de0c2d/tools/clang/scripts/update.py TBR=xalep@webrtc.org,marpan@webrtc.org, jianj@chromium.org, BUG=None No-Try: True Change-Id: Iceca2600ab3aeff37d325ec68565c06f9db5b0fd Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/227280 Reviewed-by: Andrey Logvin <landrey@webrtc.org> Reviewed-by: Artem Titov <titovartem@webrtc.org> Commit-Queue: Mirko Bonadei <mbonadei@webrtc.org> Cr-Commit-Position: refs/heads/master@{#34599}
522 lines
21 KiB
Java
522 lines
21 KiB
Java
/*
|
|
* 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 android.support.annotation.Nullable;
|
|
import androidx.test.filters.MediumTest;
|
|
import androidx.test.filters.SmallTest;
|
|
import java.nio.ByteBuffer;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import org.chromium.base.test.BaseJUnit4ClassRunner;
|
|
import org.junit.Before;
|
|
import org.junit.Test;
|
|
import org.junit.runner.RunWith;
|
|
|
|
@RunWith(BaseJUnit4ClassRunner.class)
|
|
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();
|
|
}
|
|
}
|