Android: Add unittests for SurfaceTextureHelper
BUG=webrtc:4993 R=hbos@webrtc.org Review URL: https://codereview.webrtc.org/1371643002 . Cr-Commit-Position: refs/heads/master@{#10099}
This commit is contained in:
@ -0,0 +1,255 @@
|
||||
/*
|
||||
* libjingle
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.webrtc;
|
||||
|
||||
import android.test.ActivityTestCase;
|
||||
import android.test.suitebuilder.annotation.MediumTest;
|
||||
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.opengl.EGL14;
|
||||
import android.opengl.GLES20;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class SurfaceTextureHelperTest extends ActivityTestCase {
|
||||
/**
|
||||
* Mock texture listener with blocking wait functionality.
|
||||
*/
|
||||
public static final class MockTextureListener
|
||||
implements SurfaceTextureHelper.OnTextureFrameAvailableListener {
|
||||
public int oesTextureId;
|
||||
public float[] transformMatrix;
|
||||
private boolean hasNewFrame = false;
|
||||
|
||||
@Override
|
||||
public synchronized void onTextureFrameAvailable(
|
||||
int oesTextureId, float[] transformMatrix, long timestampNs) {
|
||||
this.oesTextureId = oesTextureId;
|
||||
this.transformMatrix = transformMatrix;
|
||||
hasNewFrame = true;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait indefinitely for a new frame.
|
||||
*/
|
||||
public synchronized void waitForNewFrame() throws InterruptedException {
|
||||
while (!hasNewFrame) {
|
||||
wait();
|
||||
}
|
||||
hasNewFrame = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a new frame, or until the specified timeout elapses. Returns true if a new frame was
|
||||
* received before the timeout.
|
||||
*/
|
||||
public synchronized boolean waitForNewFrame(final long timeoutMs) throws InterruptedException {
|
||||
final long startTimeMs = SystemClock.elapsedRealtime();
|
||||
long timeRemainingMs = timeoutMs;
|
||||
while (!hasNewFrame && timeRemainingMs > 0) {
|
||||
wait(timeRemainingMs);
|
||||
final long elapsedTimeMs = SystemClock.elapsedRealtime() - startTimeMs;
|
||||
timeRemainingMs = timeoutMs - elapsedTimeMs;
|
||||
}
|
||||
final boolean didReceiveFrame = hasNewFrame;
|
||||
hasNewFrame = false;
|
||||
return didReceiveFrame;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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().
|
||||
*/
|
||||
@MediumTest
|
||||
public static 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 = new EglBase(EGL14.EGL_NO_CONTEXT, EglBase.ConfigType.PIXEL_BUFFER);
|
||||
eglBase.createPbufferSurface(width, height);
|
||||
final GlRectDrawer drawer = new GlRectDrawer();
|
||||
|
||||
// Create SurfaceTextureHelper and listener.
|
||||
final SurfaceTextureHelper surfaceTextureHelper =
|
||||
new SurfaceTextureHelper(eglBase.getContext());
|
||||
final MockTextureListener listener = new MockTextureListener();
|
||||
surfaceTextureHelper.setListener(listener);
|
||||
surfaceTextureHelper.getSurfaceTexture().setDefaultBufferSize(width, height);
|
||||
|
||||
// Create resources for stubbing an OES texture producer. |eglOesBase| has the SurfaceTexture in
|
||||
// |surfaceTextureHelper| as the target EGLSurface.
|
||||
final EglBase eglOesBase = new EglBase(eglBase.getContext(), EglBase.ConfigType.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.
|
||||
listener.waitForNewFrame();
|
||||
eglBase.makeCurrent();
|
||||
drawer.drawOes(listener.oesTextureId, listener.transformMatrix);
|
||||
|
||||
surfaceTextureHelper.returnTextureFrame();
|
||||
|
||||
// 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.disconnect();
|
||||
eglBase.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test disconnecting 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().
|
||||
*/
|
||||
@MediumTest
|
||||
public static 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 = new EglBase(EGL14.EGL_NO_CONTEXT, EglBase.ConfigType.PIXEL_BUFFER);
|
||||
eglBase.createPbufferSurface(width, height);
|
||||
|
||||
// Create SurfaceTextureHelper and listener.
|
||||
final SurfaceTextureHelper surfaceTextureHelper =
|
||||
new SurfaceTextureHelper(eglBase.getContext());
|
||||
final MockTextureListener listener = new MockTextureListener();
|
||||
surfaceTextureHelper.setListener(listener);
|
||||
surfaceTextureHelper.getSurfaceTexture().setDefaultBufferSize(width, height);
|
||||
|
||||
// Create resources for stubbing an OES texture producer. |eglOesBase| has the SurfaceTexture in
|
||||
// |surfaceTextureHelper| as the target EGLSurface.
|
||||
final EglBase eglOesBase = new EglBase(eglBase.getContext(), EglBase.ConfigType.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.
|
||||
listener.waitForNewFrame();
|
||||
// Diconnect while holding the frame.
|
||||
surfaceTextureHelper.disconnect();
|
||||
|
||||
// Draw the pending texture frame onto the pixel buffer.
|
||||
eglBase.makeCurrent();
|
||||
final GlRectDrawer drawer = new GlRectDrawer();
|
||||
drawer.drawOes(listener.oesTextureId, listener.transformMatrix);
|
||||
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 disconnected and released.
|
||||
surfaceTextureHelper.returnTextureFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test disconnecting the SurfaceTextureHelper, but keep trying to produce more texture frames. No
|
||||
* frames should be delivered to the listener.
|
||||
*/
|
||||
@MediumTest
|
||||
public static void testDisconnect() throws InterruptedException {
|
||||
// Create SurfaceTextureHelper and listener.
|
||||
final SurfaceTextureHelper surfaceTextureHelper =
|
||||
new SurfaceTextureHelper(EGL14.EGL_NO_CONTEXT);
|
||||
final MockTextureListener listener = new MockTextureListener();
|
||||
surfaceTextureHelper.setListener(listener);
|
||||
// Create EglBase with the SurfaceTexture as target EGLSurface.
|
||||
final EglBase eglBase = new EglBase(EGL14.EGL_NO_CONTEXT, EglBase.ConfigType.PLAIN);
|
||||
eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
|
||||
eglBase.makeCurrent();
|
||||
// Assert no frame has been received yet.
|
||||
assertFalse(listener.waitForNewFrame(1));
|
||||
// Draw and wait for one frame.
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||
// swapBuffers() will ultimately trigger onTextureFrameAvailable().
|
||||
eglBase.swapBuffers();
|
||||
listener.waitForNewFrame();
|
||||
surfaceTextureHelper.returnTextureFrame();
|
||||
|
||||
// Disconnect - we should not receive any textures after this.
|
||||
surfaceTextureHelper.disconnect();
|
||||
|
||||
// Draw one frame.
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||
eglBase.swapBuffers();
|
||||
// swapBuffers() should not trigger onTextureFrameAvailable() because we are disconnected.
|
||||
// Assert that no OES texture was delivered.
|
||||
assertFalse(listener.waitForNewFrame(500));
|
||||
|
||||
eglBase.release();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user