SurfaceTextureHelper fixes

Fixed a problem where eglBase.makecurrent() could be called after the context had been released if SurfaceTextureHelper was first created and immedately disconnected.

Add the possibility to inject a thread to use instead of creating a new.

BUG= webrtc:4993
R=magjed@webrtc.org

Review URL: https://codereview.webrtc.org/1384923002 .

Cr-Commit-Position: refs/heads/master@{#10174}
This commit is contained in:
perkj 2015-10-05 21:06:41 +02:00
parent 418503275c
commit 1b33da1298
2 changed files with 90 additions and 11 deletions

View File

@ -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();
}
}

View File

@ -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();
}
}
}