Index: talk/app/webrtc/androidtests/src/org/webrtc/SurfaceTextureHelperTest.java |
diff --git a/talk/app/webrtc/androidtests/src/org/webrtc/SurfaceTextureHelperTest.java b/talk/app/webrtc/androidtests/src/org/webrtc/SurfaceTextureHelperTest.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7053171233448fa1cc3b7a9521bbd345c81c0d40 |
--- /dev/null |
+++ b/talk/app/webrtc/androidtests/src/org/webrtc/SurfaceTextureHelperTest.java |
@@ -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(); |
+ } |
+} |