OLD | NEW |
| (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 } | |
OLD | NEW |