diff --git a/talk/app/webrtc/androidtests/src/org/webrtc/SurfaceTextureHelperTest.java b/talk/app/webrtc/androidtests/src/org/webrtc/SurfaceTextureHelperTest.java index 7053171233..611693bbd0 100644 --- a/talk/app/webrtc/androidtests/src/org/webrtc/SurfaceTextureHelperTest.java +++ b/talk/app/webrtc/androidtests/src/org/webrtc/SurfaceTextureHelperTest.java @@ -28,10 +28,11 @@ package org.webrtc; import android.test.ActivityTestCase; import android.test.suitebuilder.annotation.MediumTest; - +import android.test.suitebuilder.annotation.SmallTest; import android.graphics.SurfaceTexture; import android.opengl.EGL14; import android.opengl.GLES20; +import android.os.HandlerThread; import android.os.SystemClock; import java.nio.ByteBuffer; @@ -45,10 +46,23 @@ public final class SurfaceTextureHelperTest extends ActivityTestCase { public int oesTextureId; public float[] transformMatrix; private boolean hasNewFrame = false; + // Thread where frames are expected to be received on. + private final Thread expectedThread; + + MockTextureListener() { + this.expectedThread = null; + } + + MockTextureListener(Thread expectedThread) { + this.expectedThread = expectedThread; + } @Override public synchronized void onTextureFrameAvailable( int oesTextureId, float[] transformMatrix, long timestampNs) { + if (expectedThread != null && Thread.currentThread() != expectedThread) { + throw new IllegalStateException("onTextureFrameAvailable called on wrong thread."); + } this.oesTextureId = oesTextureId; this.transformMatrix = transformMatrix; hasNewFrame = true; @@ -252,4 +266,51 @@ public final class SurfaceTextureHelperTest extends ActivityTestCase { eglBase.release(); } + + /** + * Test disconnecting the SurfaceTextureHelper immediately after is has been setup to use a + * shared context. No frames should be delivered to the listener. + */ + @SmallTest + public static void testDisconnectImmediately() { + final SurfaceTextureHelper surfaceTextureHelper = + new SurfaceTextureHelper(EGL14.EGL_NO_CONTEXT); + surfaceTextureHelper.disconnect(); + } + + /** + * Test use SurfaceTextureHelper on a separate thread. A uniform texture frame is created and + * received on a thread separate from the test thread. + */ + @MediumTest + public static void testFrameOnSeparateThread() throws InterruptedException { + final HandlerThread thread = new HandlerThread("SurfaceTextureHelperTestThread"); + thread.start(); + + // Create SurfaceTextureHelper and listener. + final SurfaceTextureHelper surfaceTextureHelper = + new SurfaceTextureHelper(EGL14.EGL_NO_CONTEXT, thread); + // Create a mock listener and expect frames to be delivered on |thread|. + final MockTextureListener listener = new MockTextureListener(thread); + surfaceTextureHelper.setListener(listener); + + // Create resources for stubbing an OES texture producer. |eglOesBase| has the + // SurfaceTexture in |surfaceTextureHelper| as the target EGLSurface. + final EglBase eglOesBase = new EglBase(EGL14.EGL_NO_CONTEXT, EglBase.ConfigType.PLAIN); + eglOesBase.createSurface(surfaceTextureHelper.getSurfaceTexture()); + eglOesBase.makeCurrent(); + // Draw a frame onto the SurfaceTexture. + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + // swapBuffers() will ultimately trigger onTextureFrameAvailable(). + eglOesBase.swapBuffers(); + eglOesBase.release(); + + // Wait for an OES texture to arrive. + listener.waitForNewFrame(); + + // Return the frame from this thread. + surfaceTextureHelper.returnTextureFrame(); + surfaceTextureHelper.disconnect(); + thread.quitSafely(); + } } diff --git a/talk/app/webrtc/java/android/org/webrtc/SurfaceTextureHelper.java b/talk/app/webrtc/java/android/org/webrtc/SurfaceTextureHelper.java index a20a7391c2..4d5a2e62f1 100644 --- a/talk/app/webrtc/java/android/org/webrtc/SurfaceTextureHelper.java +++ b/talk/app/webrtc/java/android/org/webrtc/SurfaceTextureHelper.java @@ -65,6 +65,7 @@ final class SurfaceTextureHelper { private final HandlerThread thread; private final Handler handler; + private final boolean isOwningThread; private final EglBase eglBase; private final SurfaceTexture surfaceTexture; private final int oesTextureId; @@ -78,9 +79,19 @@ final class SurfaceTextureHelper { * Construct a new SurfaceTextureHelper sharing OpenGL resources with |sharedContext|. */ public SurfaceTextureHelper(EGLContext sharedContext) { - thread = new HandlerThread(TAG); - thread.start(); - handler = new Handler(thread.getLooper()); + this(sharedContext, null); + } + + public SurfaceTextureHelper(EGLContext sharedContext, HandlerThread thread) { + if (thread == null) { + this.thread = new HandlerThread(TAG); + this.thread.start(); + this.isOwningThread = true; + } else { + this.thread = thread; + this.isOwningThread = false; + } + this.handler = new Handler(this.thread.getLooper()); eglBase = new EglBase(sharedContext, EglBase.ConfigType.PIXEL_BUFFER); eglBase.createDummyPbufferSurface(); @@ -89,13 +100,8 @@ final class SurfaceTextureHelper { oesTextureId = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES); surfaceTexture = new SurfaceTexture(oesTextureId); - // Reattach EGL context to private thread. + // The EGL context will be re-attached to the private thread. eglBase.detachCurrent(); - handler.post(new Runnable() { - @Override public void run() { - eglBase.makeCurrent(); - } - }); } /** @@ -148,6 +154,13 @@ final class SurfaceTextureHelper { * onTextureFrameAvailable() after this function returns. */ public void disconnect() { + if (Thread.currentThread() == thread) { + isQuitting = true; + if (!isTextureInUse) { + release(); + } + return; + } final CountDownLatch barrier = new CountDownLatch(1); handler.postAtFrontOfQueue(new Runnable() { @Override public void run() { @@ -171,7 +184,9 @@ final class SurfaceTextureHelper { isTextureInUse = true; hasPendingTexture = false; + eglBase.makeCurrent(); surfaceTexture.updateTexImage(); + final float[] transformMatrix = new float[16]; surfaceTexture.getTransformMatrix(transformMatrix); final long timestampNs = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) @@ -187,9 +202,12 @@ final class SurfaceTextureHelper { if (isTextureInUse || !isQuitting) { throw new IllegalStateException("Unexpected release."); } + eglBase.makeCurrent(); GLES20.glDeleteTextures(1, new int[] {oesTextureId}, 0); surfaceTexture.release(); eglBase.release(); - thread.quitSafely(); + if (isOwningThread) { + thread.quitSafely(); + } } }