• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * libjingle
3  * Copyright 2015 Google Inc.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  *  1. Redistributions of source code must retain the above copyright notice,
9  *     this list of conditions and the following disclaimer.
10  *  2. Redistributions in binary form must reproduce the above copyright notice,
11  *     this list of conditions and the following disclaimer in the documentation
12  *     and/or other materials provided with the distribution.
13  *  3. The name of the author may not be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 package org.webrtc;
28 
29 import android.graphics.SurfaceTexture;
30 import android.opengl.GLES20;
31 import android.test.ActivityTestCase;
32 import android.test.suitebuilder.annotation.MediumTest;
33 import android.test.suitebuilder.annotation.SmallTest;
34 
35 import java.nio.ByteBuffer;
36 import java.util.Random;
37 
38 public final class GlRectDrawerTest extends ActivityTestCase {
39   // Resolution of the test image.
40   private static final int WIDTH = 16;
41   private static final int HEIGHT = 16;
42   // Seed for random pixel creation.
43   private static final int SEED = 42;
44   // When comparing pixels, allow some slack for float arithmetic and integer rounding.
45   private static final float MAX_DIFF = 1.5f;
46 
normalizedByte(byte b)47   private static float normalizedByte(byte b) {
48     return (b & 0xFF) / 255.0f;
49   }
50 
saturatedConvert(float c)51   private static float saturatedConvert(float c) {
52     return 255.0f * Math.max(0, Math.min(c, 1));
53   }
54 
55   // Assert RGB ByteBuffers are pixel perfect identical.
assertEquals(int width, int height, ByteBuffer actual, ByteBuffer expected)56   private static void assertEquals(int width, int height, ByteBuffer actual, ByteBuffer expected) {
57     actual.rewind();
58     expected.rewind();
59     assertEquals(actual.remaining(), width * height * 3);
60     assertEquals(expected.remaining(), width * height * 3);
61     for (int y = 0; y < height; ++y) {
62       for (int x = 0; x < width; ++x) {
63         final int actualR = actual.get() & 0xFF;
64         final int actualG = actual.get() & 0xFF;
65         final int actualB = actual.get() & 0xFF;
66         final int expectedR = expected.get() & 0xFF;
67         final int expectedG = expected.get() & 0xFF;
68         final int expectedB = expected.get() & 0xFF;
69         if (actualR != expectedR || actualG != expectedG || actualB != expectedB) {
70           fail("ByteBuffers of size " + width + "x" + height + " not equal at position "
71               + "(" +  x + ", " + y + "). Expected color (R,G,B): "
72               + "(" + expectedR + ", " + expectedG + ", " + expectedB + ")"
73               + " but was: " + "(" + actualR + ", " + actualG + ", " + actualB + ").");
74         }
75       }
76     }
77   }
78 
79   // Convert RGBA ByteBuffer to RGB ByteBuffer.
stripAlphaChannel(ByteBuffer rgbaBuffer)80   private static ByteBuffer stripAlphaChannel(ByteBuffer rgbaBuffer) {
81     rgbaBuffer.rewind();
82     assertEquals(rgbaBuffer.remaining() % 4, 0);
83     final int numberOfPixels = rgbaBuffer.remaining() / 4;
84     final ByteBuffer rgbBuffer = ByteBuffer.allocateDirect(numberOfPixels * 3);
85     while (rgbaBuffer.hasRemaining()) {
86       // Copy RGB.
87       for (int channel = 0; channel < 3; ++channel) {
88         rgbBuffer.put(rgbaBuffer.get());
89       }
90       // Drop alpha.
91       rgbaBuffer.get();
92     }
93     return rgbBuffer;
94   }
95 
96   @SmallTest
testRgbRendering()97   public void testRgbRendering() {
98     // Create EGL base with a pixel buffer as display output.
99     final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PIXEL_BUFFER);
100     eglBase.createPbufferSurface(WIDTH, HEIGHT);
101     eglBase.makeCurrent();
102 
103     // Create RGB byte buffer plane with random content.
104     final ByteBuffer rgbPlane = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 3);
105     final Random random = new Random(SEED);
106     random.nextBytes(rgbPlane.array());
107 
108     // Upload the RGB byte buffer data as a texture.
109     final int rgbTexture = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D);
110     GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
111     GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, rgbTexture);
112     GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, WIDTH,
113         HEIGHT, 0, GLES20.GL_RGB, GLES20.GL_UNSIGNED_BYTE, rgbPlane);
114     GlUtil.checkNoGLES2Error("glTexImage2D");
115 
116     // Draw the RGB frame onto the pixel buffer.
117     final GlRectDrawer drawer = new GlRectDrawer();
118     drawer.drawRgb(rgbTexture, RendererCommon.identityMatrix(), 0, 0, WIDTH, HEIGHT);
119 
120     // Download the pixels in the pixel buffer as RGBA. Not all platforms support RGB, e.g. Nexus 9.
121     final ByteBuffer rgbaData = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 4);
122     GLES20.glReadPixels(0, 0, WIDTH, HEIGHT, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, rgbaData);
123     GlUtil.checkNoGLES2Error("glReadPixels");
124 
125     // Assert rendered image is pixel perfect to source RGB.
126     assertEquals(WIDTH, HEIGHT, stripAlphaChannel(rgbaData), rgbPlane);
127 
128     drawer.release();
129     GLES20.glDeleteTextures(1, new int[] {rgbTexture}, 0);
130     eglBase.release();
131   }
132 
133   @SmallTest
testYuvRendering()134   public void testYuvRendering() {
135     // Create EGL base with a pixel buffer as display output.
136     EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PIXEL_BUFFER);
137     eglBase.createPbufferSurface(WIDTH, HEIGHT);
138     eglBase.makeCurrent();
139 
140     // Create YUV byte buffer planes with random content.
141     final ByteBuffer[] yuvPlanes = new ByteBuffer[3];
142     final Random random = new Random(SEED);
143     for (int i = 0; i < 3; ++i) {
144       yuvPlanes[i] = ByteBuffer.allocateDirect(WIDTH * HEIGHT);
145       random.nextBytes(yuvPlanes[i].array());
146     }
147 
148     // Generate 3 texture ids for Y/U/V.
149     final int yuvTextures[] = new int[3];
150     for (int i = 0; i < 3; i++)  {
151       yuvTextures[i] = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D);
152     }
153 
154     // Upload the YUV byte buffer data as textures.
155     for (int i = 0; i < 3; ++i) {
156       GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i);
157       GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]);
158       GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, WIDTH,
159           HEIGHT, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, yuvPlanes[i]);
160       GlUtil.checkNoGLES2Error("glTexImage2D");
161     }
162 
163     // Draw the YUV frame onto the pixel buffer.
164     final GlRectDrawer drawer = new GlRectDrawer();
165     drawer.drawYuv(yuvTextures, RendererCommon.identityMatrix(), 0, 0, WIDTH, HEIGHT);
166 
167     // Download the pixels in the pixel buffer as RGBA. Not all platforms support RGB, e.g. Nexus 9.
168     final ByteBuffer data = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 4);
169     GLES20.glReadPixels(0, 0, WIDTH, HEIGHT, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, data);
170     GlUtil.checkNoGLES2Error("glReadPixels");
171 
172     // Compare the YUV data with the RGBA result.
173     for (int y = 0; y < HEIGHT; ++y) {
174       for (int x = 0; x < WIDTH; ++x) {
175         // YUV color space. Y in [0, 1], UV in [-0.5, 0.5]. The constants are taken from the YUV
176         // fragment shader code in GlRectDrawer.
177         final float y_luma = normalizedByte(yuvPlanes[0].get());
178         final float u_chroma = normalizedByte(yuvPlanes[1].get()) - 0.5f;
179         final float v_chroma = normalizedByte(yuvPlanes[2].get()) - 0.5f;
180         // Expected color in unrounded RGB [0.0f, 255.0f].
181         final float expectedRed = saturatedConvert(y_luma + 1.403f * v_chroma);
182         final float expectedGreen =
183             saturatedConvert(y_luma - 0.344f * u_chroma - 0.714f * v_chroma);
184         final float expectedBlue = saturatedConvert(y_luma + 1.77f * u_chroma);
185 
186         // Actual color in RGB8888.
187         final int actualRed = data.get() & 0xFF;
188         final int actualGreen = data.get() & 0xFF;
189         final int actualBlue = data.get() & 0xFF;
190         final int actualAlpha = data.get() & 0xFF;
191 
192         // Assert rendered image is close to pixel perfect from source YUV.
193         assertTrue(Math.abs(actualRed - expectedRed) < MAX_DIFF);
194         assertTrue(Math.abs(actualGreen - expectedGreen) < MAX_DIFF);
195         assertTrue(Math.abs(actualBlue - expectedBlue) < MAX_DIFF);
196         assertEquals(actualAlpha, 255);
197       }
198     }
199 
200     drawer.release();
201     GLES20.glDeleteTextures(3, yuvTextures, 0);
202     eglBase.release();
203   }
204 
205   /**
206    * The purpose here is to test GlRectDrawer.oesDraw(). Unfortunately, there is no easy way to
207    * create an OES texture, which is needed for input to oesDraw(). Most of the test is concerned
208    * with creating OES textures in the following way:
209    *  - Create SurfaceTexture with help from SurfaceTextureHelper.
210    *  - Create an EglBase with the SurfaceTexture as EGLSurface.
211    *  - Upload RGB texture with known content.
212    *  - Draw the RGB texture onto the EglBase with the SurfaceTexture as target.
213    *  - Wait for an OES texture to be produced.
214    * The actual oesDraw() test is this:
215    *  - Create an EglBase with a pixel buffer as target.
216    *  - Render the OES texture onto the pixel buffer.
217    *  - Read back the pixel buffer and compare it with the known RGB data.
218    */
219   @MediumTest
220   public void testOesRendering() throws InterruptedException {
221     /**
222      * Stub class to convert RGB ByteBuffers to OES textures by drawing onto a SurfaceTexture.
223      */
224     class StubOesTextureProducer {
225       private final EglBase eglBase;
226       private final GlRectDrawer drawer;
227       private final int rgbTexture;
228 
229       public StubOesTextureProducer(
230           EglBase.Context sharedContext, SurfaceTexture surfaceTexture, int width,
231           int height) {
232         eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PLAIN);
233         surfaceTexture.setDefaultBufferSize(width, height);
234         eglBase.createSurface(surfaceTexture);
235         assertEquals(eglBase.surfaceWidth(), width);
236         assertEquals(eglBase.surfaceHeight(), height);
237 
238         drawer = new GlRectDrawer();
239 
240         eglBase.makeCurrent();
241         rgbTexture = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D);
242       }
243 
244       public void draw(ByteBuffer rgbPlane) {
245         eglBase.makeCurrent();
246 
247         // Upload RGB data to texture.
248         GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
249         GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, rgbTexture);
250         GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, WIDTH,
251             HEIGHT, 0, GLES20.GL_RGB, GLES20.GL_UNSIGNED_BYTE, rgbPlane);
252         // Draw the RGB data onto the SurfaceTexture.
253         drawer.drawRgb(rgbTexture, RendererCommon.identityMatrix(), 0, 0, WIDTH, HEIGHT);
254         eglBase.swapBuffers();
255       }
256 
257       public void release() {
258         eglBase.makeCurrent();
259         drawer.release();
260         GLES20.glDeleteTextures(1, new int[] {rgbTexture}, 0);
261         eglBase.release();
262       }
263     }
264 
265     // Create EGL base with a pixel buffer as display output.
266     final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PIXEL_BUFFER);
267     eglBase.createPbufferSurface(WIDTH, HEIGHT);
268 
269     // Create resources for generating OES textures.
270     final SurfaceTextureHelper surfaceTextureHelper =
271         SurfaceTextureHelper.create(eglBase.getEglBaseContext());
272     final StubOesTextureProducer oesProducer = new StubOesTextureProducer(
273         eglBase.getEglBaseContext(), surfaceTextureHelper.getSurfaceTexture(), WIDTH, HEIGHT);
274     final SurfaceTextureHelperTest.MockTextureListener listener =
275         new SurfaceTextureHelperTest.MockTextureListener();
276     surfaceTextureHelper.setListener(listener);
277 
278     // Create RGB byte buffer plane with random content.
279     final ByteBuffer rgbPlane = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 3);
280     final Random random = new Random(SEED);
281     random.nextBytes(rgbPlane.array());
282 
283     // Draw the frame and block until an OES texture is delivered.
284     oesProducer.draw(rgbPlane);
285     listener.waitForNewFrame();
286 
287     // Real test starts here.
288     // Draw the OES texture on the pixel buffer.
289     eglBase.makeCurrent();
290     final GlRectDrawer drawer = new GlRectDrawer();
291     drawer.drawOes(listener.oesTextureId, listener.transformMatrix, 0, 0, WIDTH, HEIGHT);
292 
293     // Download the pixels in the pixel buffer as RGBA. Not all platforms support RGB, e.g. Nexus 9.
294     final ByteBuffer rgbaData = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 4);
295     GLES20.glReadPixels(0, 0, WIDTH, HEIGHT, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, rgbaData);
296     GlUtil.checkNoGLES2Error("glReadPixels");
297 
298     // Assert rendered image is pixel perfect to source RGB.
299     assertEquals(WIDTH, HEIGHT, stripAlphaChannel(rgbaData), rgbPlane);
300 
301     drawer.release();
302     surfaceTextureHelper.returnTextureFrame();
303     oesProducer.release();
304     surfaceTextureHelper.disconnect();
305     eglBase.release();
306   }
307 }
308