Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(161)

Side by Side Diff: webrtc/api/androidtests/src/org/webrtc/SurfaceTextureHelperTest.java

Issue 2541823002: Move webrtc/api/androidtests to webrtc/sdk/android/instrumentationtests (Closed)
Patch Set: Make include for webrtc.gni absolute Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * Copyright 2015 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10 package org.webrtc;
11
12 import android.graphics.SurfaceTexture;
13 import android.opengl.GLES20;
14 import android.os.SystemClock;
15 import android.test.ActivityTestCase;
16 import android.test.suitebuilder.annotation.MediumTest;
17 import android.test.suitebuilder.annotation.SmallTest;
18
19 import java.nio.ByteBuffer;
20 import java.util.concurrent.CountDownLatch;
21
22 public final class SurfaceTextureHelperTest extends ActivityTestCase {
23 /**
24 * Mock texture listener with blocking wait functionality.
25 */
26 public static final class MockTextureListener
27 implements SurfaceTextureHelper.OnTextureFrameAvailableListener {
28 public int oesTextureId;
29 public float[] transformMatrix;
30 private boolean hasNewFrame = false;
31 // Thread where frames are expected to be received on.
32 private final Thread expectedThread;
33
34 MockTextureListener() {
35 this.expectedThread = null;
36 }
37
38 MockTextureListener(Thread expectedThread) {
39 this.expectedThread = expectedThread;
40 }
41
42 @Override
43 public synchronized void onTextureFrameAvailable(
44 int oesTextureId, float[] transformMatrix, long timestampNs) {
45 if (expectedThread != null && Thread.currentThread() != expectedThread) {
46 throw new IllegalStateException("onTextureFrameAvailable called on wrong thread.");
47 }
48 this.oesTextureId = oesTextureId;
49 this.transformMatrix = transformMatrix;
50 hasNewFrame = true;
51 notifyAll();
52 }
53
54 /**
55 * Wait indefinitely for a new frame.
56 */
57 public synchronized void waitForNewFrame() throws InterruptedException {
58 while (!hasNewFrame) {
59 wait();
60 }
61 hasNewFrame = false;
62 }
63
64 /**
65 * Wait for a new frame, or until the specified timeout elapses. Returns tru e if a new frame was
66 * received before the timeout.
67 */
68 public synchronized boolean waitForNewFrame(final long timeoutMs) throws Int erruptedException {
69 final long startTimeMs = SystemClock.elapsedRealtime();
70 long timeRemainingMs = timeoutMs;
71 while (!hasNewFrame && timeRemainingMs > 0) {
72 wait(timeRemainingMs);
73 final long elapsedTimeMs = SystemClock.elapsedRealtime() - startTimeMs;
74 timeRemainingMs = timeoutMs - elapsedTimeMs;
75 }
76 final boolean didReceiveFrame = hasNewFrame;
77 hasNewFrame = false;
78 return didReceiveFrame;
79 }
80 }
81
82 /** Assert that two integers are close, with difference at most
83 * {@code threshold}. */
84 public static void assertClose(int threshold, int expected, int actual) {
85 if (Math.abs(expected - actual) <= threshold)
86 return;
87 failNotEquals("Not close enough, threshold " + threshold, expected, actual);
88 }
89
90 /**
91 * Test normal use by receiving three uniform texture frames. Texture frames a re returned as early
92 * as possible. The texture pixel values are inspected by drawing the texture frame to a pixel
93 * buffer and reading it back with glReadPixels().
94 */
95 @MediumTest
96 public static void testThreeConstantColorFrames() throws InterruptedException {
97 final int width = 16;
98 final int height = 16;
99 // Create EGL base with a pixel buffer as display output.
100 final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PIXEL_BUFFER);
101 eglBase.createPbufferSurface(width, height);
102 final GlRectDrawer drawer = new GlRectDrawer();
103
104 // Create SurfaceTextureHelper and listener.
105 final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.creat e(
106 "SurfaceTextureHelper test" /* threadName */, eglBase.getEglBaseContext( ));
107 final MockTextureListener listener = new MockTextureListener();
108 surfaceTextureHelper.startListening(listener);
109 surfaceTextureHelper.getSurfaceTexture().setDefaultBufferSize(width, height) ;
110
111 // Create resources for stubbing an OES texture producer. |eglOesBase| has t he SurfaceTexture in
112 // |surfaceTextureHelper| as the target EGLSurface.
113 final EglBase eglOesBase = EglBase.create(eglBase.getEglBaseContext(), EglBa se.CONFIG_PLAIN);
114 eglOesBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
115 assertEquals(eglOesBase.surfaceWidth(), width);
116 assertEquals(eglOesBase.surfaceHeight(), height);
117
118 final int red[] = new int[] {79, 144, 185};
119 final int green[] = new int[] {66, 210, 162};
120 final int blue[] = new int[] {161, 117, 158};
121 // Draw three frames.
122 for (int i = 0; i < 3; ++i) {
123 // Draw a constant color frame onto the SurfaceTexture.
124 eglOesBase.makeCurrent();
125 GLES20.glClearColor(red[i] / 255.0f, green[i] / 255.0f, blue[i] / 255.0f, 1.0f);
126 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
127 // swapBuffers() will ultimately trigger onTextureFrameAvailable().
128 eglOesBase.swapBuffers();
129
130 // Wait for an OES texture to arrive and draw it onto the pixel buffer.
131 listener.waitForNewFrame();
132 eglBase.makeCurrent();
133 drawer.drawOes(
134 listener.oesTextureId, listener.transformMatrix, width, height, 0, 0, width, height);
135
136 surfaceTextureHelper.returnTextureFrame();
137
138 // Download the pixels in the pixel buffer as RGBA. Not all platforms supp ort RGB, e.g.
139 // Nexus 9.
140 final ByteBuffer rgbaData = ByteBuffer.allocateDirect(width * height * 4);
141 GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNE D_BYTE, rgbaData);
142 GlUtil.checkNoGLES2Error("glReadPixels");
143
144 // Assert rendered image is expected constant color.
145 while (rgbaData.hasRemaining()) {
146 assertEquals(rgbaData.get() & 0xFF, red[i]);
147 assertEquals(rgbaData.get() & 0xFF, green[i]);
148 assertEquals(rgbaData.get() & 0xFF, blue[i]);
149 assertEquals(rgbaData.get() & 0xFF, 255);
150 }
151 }
152
153 drawer.release();
154 surfaceTextureHelper.dispose();
155 eglBase.release();
156 }
157
158 /**
159 * Test disposing the SurfaceTextureHelper while holding a pending texture fra me. The pending
160 * texture frame should still be valid, and this is tested by drawing the text ure frame to a pixel
161 * buffer and reading it back with glReadPixels().
162 */
163 @MediumTest
164 public static void testLateReturnFrame() throws InterruptedException {
165 final int width = 16;
166 final int height = 16;
167 // Create EGL base with a pixel buffer as display output.
168 final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PIXEL_BUFFER);
169 eglBase.createPbufferSurface(width, height);
170
171 // Create SurfaceTextureHelper and listener.
172 final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.creat e(
173 "SurfaceTextureHelper test" /* threadName */, eglBase.getEglBaseContext( ));
174 final MockTextureListener listener = new MockTextureListener();
175 surfaceTextureHelper.startListening(listener);
176 surfaceTextureHelper.getSurfaceTexture().setDefaultBufferSize(width, height) ;
177
178 // Create resources for stubbing an OES texture producer. |eglOesBase| has t he SurfaceTexture in
179 // |surfaceTextureHelper| as the target EGLSurface.
180 final EglBase eglOesBase = EglBase.create(eglBase.getEglBaseContext(), EglBa se.CONFIG_PLAIN);
181 eglOesBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
182 assertEquals(eglOesBase.surfaceWidth(), width);
183 assertEquals(eglOesBase.surfaceHeight(), height);
184
185 final int red = 79;
186 final int green = 66;
187 final int blue = 161;
188 // Draw a constant color frame onto the SurfaceTexture.
189 eglOesBase.makeCurrent();
190 GLES20.glClearColor(red / 255.0f, green / 255.0f, blue / 255.0f, 1.0f);
191 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
192 // swapBuffers() will ultimately trigger onTextureFrameAvailable().
193 eglOesBase.swapBuffers();
194 eglOesBase.release();
195
196 // Wait for OES texture frame.
197 listener.waitForNewFrame();
198 // Diconnect while holding the frame.
199 surfaceTextureHelper.dispose();
200
201 // Draw the pending texture frame onto the pixel buffer.
202 eglBase.makeCurrent();
203 final GlRectDrawer drawer = new GlRectDrawer();
204 drawer.drawOes(
205 listener.oesTextureId, listener.transformMatrix, width, height, 0, 0, wi dth, height);
206 drawer.release();
207
208 // Download the pixels in the pixel buffer as RGBA. Not all platforms suppor t RGB, e.g. Nexus 9.
209 final ByteBuffer rgbaData = ByteBuffer.allocateDirect(width * height * 4);
210 GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_ BYTE, rgbaData);
211 GlUtil.checkNoGLES2Error("glReadPixels");
212 eglBase.release();
213
214 // Assert rendered image is expected constant color.
215 while (rgbaData.hasRemaining()) {
216 assertEquals(rgbaData.get() & 0xFF, red);
217 assertEquals(rgbaData.get() & 0xFF, green);
218 assertEquals(rgbaData.get() & 0xFF, blue);
219 assertEquals(rgbaData.get() & 0xFF, 255);
220 }
221 // Late frame return after everything has been disposed and released.
222 surfaceTextureHelper.returnTextureFrame();
223 }
224
225 /**
226 * Test disposing the SurfaceTextureHelper, but keep trying to produce more te xture frames. No
227 * frames should be delivered to the listener.
228 */
229 @MediumTest
230 public static void testDispose() throws InterruptedException {
231 // Create SurfaceTextureHelper and listener.
232 final SurfaceTextureHelper surfaceTextureHelper =
233 SurfaceTextureHelper.create("SurfaceTextureHelper test" /* threadName */ , null);
234 final MockTextureListener listener = new MockTextureListener();
235 surfaceTextureHelper.startListening(listener);
236 // Create EglBase with the SurfaceTexture as target EGLSurface.
237 final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PLAIN);
238 eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
239 eglBase.makeCurrent();
240 // Assert no frame has been received yet.
241 assertFalse(listener.waitForNewFrame(1));
242 // Draw and wait for one frame.
243 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
244 // swapBuffers() will ultimately trigger onTextureFrameAvailable().
245 eglBase.swapBuffers();
246 listener.waitForNewFrame();
247 surfaceTextureHelper.returnTextureFrame();
248
249 // Dispose - we should not receive any textures after this.
250 surfaceTextureHelper.dispose();
251
252 // Draw one frame.
253 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
254 eglBase.swapBuffers();
255 // swapBuffers() should not trigger onTextureFrameAvailable() because dispos ed has been called.
256 // Assert that no OES texture was delivered.
257 assertFalse(listener.waitForNewFrame(500));
258
259 eglBase.release();
260 }
261
262 /**
263 * Test disposing the SurfaceTextureHelper immediately after is has been setup to use a
264 * shared context. No frames should be delivered to the listener.
265 */
266 @SmallTest
267 public static void testDisposeImmediately() {
268 final SurfaceTextureHelper surfaceTextureHelper =
269 SurfaceTextureHelper.create("SurfaceTextureHelper test" /* threadName */ , null);
270 surfaceTextureHelper.dispose();
271 }
272
273 /**
274 * Call stopListening(), but keep trying to produce more texture frames. No fr ames should be
275 * delivered to the listener.
276 */
277 @MediumTest
278 public static void testStopListening() throws InterruptedException {
279 // Create SurfaceTextureHelper and listener.
280 final SurfaceTextureHelper surfaceTextureHelper =
281 SurfaceTextureHelper.create("SurfaceTextureHelper test" /* threadName */ , null);
282 final MockTextureListener listener = new MockTextureListener();
283 surfaceTextureHelper.startListening(listener);
284 // Create EglBase with the SurfaceTexture as target EGLSurface.
285 final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PLAIN);
286 eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
287 eglBase.makeCurrent();
288 // Assert no frame has been received yet.
289 assertFalse(listener.waitForNewFrame(1));
290 // Draw and wait for one frame.
291 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
292 // swapBuffers() will ultimately trigger onTextureFrameAvailable().
293 eglBase.swapBuffers();
294 listener.waitForNewFrame();
295 surfaceTextureHelper.returnTextureFrame();
296
297 // Stop listening - we should not receive any textures after this.
298 surfaceTextureHelper.stopListening();
299
300 // Draw one frame.
301 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
302 eglBase.swapBuffers();
303 // swapBuffers() should not trigger onTextureFrameAvailable() because dispos ed has been called.
304 // Assert that no OES texture was delivered.
305 assertFalse(listener.waitForNewFrame(500));
306
307 surfaceTextureHelper.dispose();
308 eglBase.release();
309 }
310
311 /**
312 * Test stopListening() immediately after the SurfaceTextureHelper has been se tup.
313 */
314 @SmallTest
315 public static void testStopListeningImmediately() throws InterruptedException {
316 final SurfaceTextureHelper surfaceTextureHelper =
317 SurfaceTextureHelper.create("SurfaceTextureHelper test" /* threadName */ , null);
318 final MockTextureListener listener = new MockTextureListener();
319 surfaceTextureHelper.startListening(listener);
320 surfaceTextureHelper.stopListening();
321 surfaceTextureHelper.dispose();
322 }
323
324 /**
325 * Test stopListening() immediately after the SurfaceTextureHelper has been se tup on the handler
326 * thread.
327 */
328 @SmallTest
329 public static void testStopListeningImmediatelyOnHandlerThread() throws Interr uptedException {
330 final SurfaceTextureHelper surfaceTextureHelper =
331 SurfaceTextureHelper.create("SurfaceTextureHelper test" /* threadName */ , null);
332 final MockTextureListener listener = new MockTextureListener();
333
334 final CountDownLatch stopListeningBarrier = new CountDownLatch(1);
335 final CountDownLatch stopListeningBarrierDone = new CountDownLatch(1);
336 // Start by posting to the handler thread to keep it occupied.
337 surfaceTextureHelper.getHandler().post(new Runnable() {
338 @Override
339 public void run() {
340 ThreadUtils.awaitUninterruptibly(stopListeningBarrier);
341 surfaceTextureHelper.stopListening();
342 stopListeningBarrierDone.countDown();
343 }
344 });
345
346 // startListening() is asynchronous and will post to the occupied handler th read.
347 surfaceTextureHelper.startListening(listener);
348 // Wait for stopListening() to be called on the handler thread.
349 stopListeningBarrier.countDown();
350 stopListeningBarrierDone.await();
351 // Wait until handler thread is idle to try to catch late startListening() c all.
352 final CountDownLatch barrier = new CountDownLatch(1);
353 surfaceTextureHelper.getHandler().post(new Runnable() {
354 @Override
355 public void run() {
356 barrier.countDown();
357 }
358 });
359 ThreadUtils.awaitUninterruptibly(barrier);
360 // Previous startListening() call should never have taken place and it shoul d be ok to call it
361 // again.
362 surfaceTextureHelper.startListening(listener);
363
364 surfaceTextureHelper.dispose();
365 }
366
367 /**
368 * Test calling startListening() with a new listener after stopListening() has been called.
369 */
370 @MediumTest
371 public static void testRestartListeningWithNewListener() throws InterruptedExc eption {
372 // Create SurfaceTextureHelper and listener.
373 final SurfaceTextureHelper surfaceTextureHelper =
374 SurfaceTextureHelper.create("SurfaceTextureHelper test" /* threadName */ , null);
375 final MockTextureListener listener1 = new MockTextureListener();
376 surfaceTextureHelper.startListening(listener1);
377 // Create EglBase with the SurfaceTexture as target EGLSurface.
378 final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PLAIN);
379 eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
380 eglBase.makeCurrent();
381 // Assert no frame has been received yet.
382 assertFalse(listener1.waitForNewFrame(1));
383 // Draw and wait for one frame.
384 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
385 // swapBuffers() will ultimately trigger onTextureFrameAvailable().
386 eglBase.swapBuffers();
387 listener1.waitForNewFrame();
388 surfaceTextureHelper.returnTextureFrame();
389
390 // Stop listening - |listener1| should not receive any textures after this.
391 surfaceTextureHelper.stopListening();
392
393 // Connect different listener.
394 final MockTextureListener listener2 = new MockTextureListener();
395 surfaceTextureHelper.startListening(listener2);
396 // Assert no frame has been received yet.
397 assertFalse(listener2.waitForNewFrame(1));
398
399 // Draw one frame.
400 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
401 eglBase.swapBuffers();
402
403 // Check that |listener2| received the frame, and not |listener1|.
404 listener2.waitForNewFrame();
405 assertFalse(listener1.waitForNewFrame(1));
406
407 surfaceTextureHelper.returnTextureFrame();
408
409 surfaceTextureHelper.dispose();
410 eglBase.release();
411 }
412
413 @MediumTest
414 public static void testTexturetoYUV() throws InterruptedException {
415 final int width = 16;
416 final int height = 16;
417
418 final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PLAIN);
419
420 // Create SurfaceTextureHelper and listener.
421 final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.creat e(
422 "SurfaceTextureHelper test" /* threadName */, eglBase.getEglBaseContext( ));
423 final MockTextureListener listener = new MockTextureListener();
424 surfaceTextureHelper.startListening(listener);
425 surfaceTextureHelper.getSurfaceTexture().setDefaultBufferSize(width, height) ;
426
427 // Create resources for stubbing an OES texture producer. |eglBase| has the SurfaceTexture in
428 // |surfaceTextureHelper| as the target EGLSurface.
429
430 eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
431 assertEquals(eglBase.surfaceWidth(), width);
432 assertEquals(eglBase.surfaceHeight(), height);
433
434 final int red[] = new int[] {79, 144, 185};
435 final int green[] = new int[] {66, 210, 162};
436 final int blue[] = new int[] {161, 117, 158};
437
438 final int ref_y[] = new int[] {81, 180, 168};
439 final int ref_u[] = new int[] {173, 93, 122};
440 final int ref_v[] = new int[] {127, 103, 140};
441
442 // Draw three frames.
443 for (int i = 0; i < 3; ++i) {
444 // Draw a constant color frame onto the SurfaceTexture.
445 eglBase.makeCurrent();
446 GLES20.glClearColor(red[i] / 255.0f, green[i] / 255.0f, blue[i] / 255.0f, 1.0f);
447 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
448 // swapBuffers() will ultimately trigger onTextureFrameAvailable().
449 eglBase.swapBuffers();
450
451 // Wait for an OES texture to arrive.
452 listener.waitForNewFrame();
453
454 // Memory layout: Lines are 16 bytes. First 16 lines are
455 // the Y data. These are followed by 8 lines with 8 bytes of U
456 // data on the left and 8 bytes of V data on the right.
457 //
458 // Offset
459 // 0 YYYYYYYY YYYYYYYY
460 // 16 YYYYYYYY YYYYYYYY
461 // ...
462 // 240 YYYYYYYY YYYYYYYY
463 // 256 UUUUUUUU VVVVVVVV
464 // 272 UUUUUUUU VVVVVVVV
465 // ...
466 // 368 UUUUUUUU VVVVVVVV
467 // 384 buffer end
468 ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 3 / 2);
469 surfaceTextureHelper.textureToYUV(
470 buffer, width, height, width, listener.oesTextureId, listener.transfor mMatrix);
471
472 surfaceTextureHelper.returnTextureFrame();
473
474 // Allow off-by-one differences due to different rounding.
475 while (buffer.position() < width * height) {
476 assertClose(1, buffer.get() & 0xff, ref_y[i]);
477 }
478 while (buffer.hasRemaining()) {
479 if (buffer.position() % width < width / 2)
480 assertClose(1, buffer.get() & 0xff, ref_u[i]);
481 else
482 assertClose(1, buffer.get() & 0xff, ref_v[i]);
483 }
484 }
485
486 surfaceTextureHelper.dispose();
487 eglBase.release();
488 }
489 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698