Java SurfaceTextureHelper: Remove support for external thread

Currently, VideoCapturerAndroid owns a dedicated tread, and
SurfaceTextureHelper get this thread passed in the ctor. In
VideoCapturerAndroid.dispose(), ownership of the thread is passed to
SurfaceTextureHelper so that we can return directly instead of waiting
for the last frame to return.

This CL makes the SurfaceTextureHelper own the thread the whole time
instead, and VideoCapturerAndroid calls getHandler() to get it instead.

BUG=webrtc:5519

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

Cr-Commit-Position: refs/heads/master@{#11790}
This commit is contained in:
magjed 2016-02-26 07:45:44 -08:00 committed by Commit bot
parent 54ebfca934
commit 9e69dfdfd5
3 changed files with 29 additions and 126 deletions

View File

@ -11,8 +11,6 @@ package org.webrtc;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
import android.opengl.GLES20; import android.opengl.GLES20;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock; import android.os.SystemClock;
import android.test.ActivityTestCase; import android.test.ActivityTestCase;
import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.MediumTest;
@ -271,78 +269,6 @@ public final class SurfaceTextureHelperTest extends ActivityTestCase {
surfaceTextureHelper.disconnect(); 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();
final Handler handler = new Handler(thread.getLooper());
// Create SurfaceTextureHelper and listener.
final SurfaceTextureHelper surfaceTextureHelper =
SurfaceTextureHelper.create(null, handler);
// 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 = EglBase.create(null, EglBase.CONFIG_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(handler);
}
/**
* Test use SurfaceTextureHelper on a separate thread. A uniform texture frame is created and
* received on a thread separate from the test thread and returned after disconnect.
*/
@MediumTest
public static void testLateReturnFrameOnSeparateThread() throws InterruptedException {
final HandlerThread thread = new HandlerThread("SurfaceTextureHelperTestThread");
thread.start();
final Handler handler = new Handler(thread.getLooper());
// Create SurfaceTextureHelper and listener.
final SurfaceTextureHelper surfaceTextureHelper =
SurfaceTextureHelper.create(null, handler);
// 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 = EglBase.create(null, EglBase.CONFIG_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();
surfaceTextureHelper.disconnect(handler);
surfaceTextureHelper.returnTextureFrame();
}
@MediumTest @MediumTest
public static void testTexturetoYUV() throws InterruptedException { public static void testTexturetoYUV() throws InterruptedException {
final int width = 16; final int width = 16;

View File

@ -48,32 +48,23 @@ class SurfaceTextureHelper {
int oesTextureId, float[] transformMatrix, long timestampNs); int oesTextureId, float[] transformMatrix, long timestampNs);
} }
public static SurfaceTextureHelper create(EglBase.Context sharedContext) {
return create(sharedContext, null);
}
/** /**
* Construct a new SurfaceTextureHelper sharing OpenGL resources with |sharedContext|. If * Construct a new SurfaceTextureHelper sharing OpenGL resources with |sharedContext|. A dedicated
* |handler| is non-null, the callback will be executed on that handler's thread. If |handler| is * thread and handler is created for handling the SurfaceTexture.
* null, a dedicated private thread is created for the callbacks.
*/ */
public static SurfaceTextureHelper create(final EglBase.Context sharedContext, public static SurfaceTextureHelper create(final EglBase.Context sharedContext) {
final Handler handler) { final HandlerThread thread = new HandlerThread(TAG);
final Handler finalHandler; thread.start();
if (handler != null) { final Handler handler = new Handler(thread.getLooper());
finalHandler = handler;
} else {
final HandlerThread thread = new HandlerThread(TAG);
thread.start();
finalHandler = new Handler(thread.getLooper());
}
// The onFrameAvailable() callback will be executed on the SurfaceTexture ctor thread. See: // The onFrameAvailable() callback will be executed on the SurfaceTexture ctor thread. See:
// http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/graphics/SurfaceTexture.java#195. // http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/graphics/SurfaceTexture.java#195.
// Therefore, in order to control the callback thread on API lvl < 21, the SurfaceTextureHelper // Therefore, in order to control the callback thread on API lvl < 21, the SurfaceTextureHelper
// is constructed on the |handler| thread. // is constructed on the |handler| thread.
return ThreadUtils.invokeUninterruptibly(finalHandler, new Callable<SurfaceTextureHelper>() { return ThreadUtils.invokeUninterruptibly(handler, new Callable<SurfaceTextureHelper>() {
@Override public SurfaceTextureHelper call() { @Override
return new SurfaceTextureHelper(sharedContext, finalHandler, (handler == null)); public SurfaceTextureHelper call() {
return new SurfaceTextureHelper(sharedContext, handler);
} }
}); });
} }
@ -291,7 +282,6 @@ class SurfaceTextureHelper {
} }
private final Handler handler; private final Handler handler;
private boolean isOwningThread;
private final EglBase eglBase; private final EglBase eglBase;
private final SurfaceTexture surfaceTexture; private final SurfaceTexture surfaceTexture;
private final int oesTextureId; private final int oesTextureId;
@ -303,13 +293,11 @@ class SurfaceTextureHelper {
private volatile boolean isTextureInUse = false; private volatile boolean isTextureInUse = false;
private boolean isQuitting = false; private boolean isQuitting = false;
private SurfaceTextureHelper(EglBase.Context sharedContext, private SurfaceTextureHelper(EglBase.Context sharedContext, Handler handler) {
Handler handler, boolean isOwningThread) {
if (handler.getLooper().getThread() != Thread.currentThread()) { if (handler.getLooper().getThread() != Thread.currentThread()) {
throw new IllegalStateException("SurfaceTextureHelper must be created on the handler thread"); throw new IllegalStateException("SurfaceTextureHelper must be created on the handler thread");
} }
this.handler = handler; this.handler = handler;
this.isOwningThread = isOwningThread;
eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER); eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER);
eglBase.createDummyPbufferSurface(); eglBase.createDummyPbufferSurface();
@ -357,6 +345,14 @@ class SurfaceTextureHelper {
return surfaceTexture; return surfaceTexture;
} }
/**
* Retrieve the handler that calls onTextureFrameAvailable(). This handler is valid until
* disconnect() is called.
*/
public Handler getHandler() {
return handler;
}
/** /**
* Call this function to signal that you are done with the frame received in * Call this function to signal that you are done with the frame received in
* onTextureFrameAvailable(). Only one texture frame can be in flight at once, so you must call * onTextureFrameAvailable(). Only one texture frame can be in flight at once, so you must call
@ -380,14 +376,11 @@ class SurfaceTextureHelper {
} }
/** /**
* Call disconnect() to stop receiving frames. Resources are released when the texture frame has * Call disconnect() to stop receiving frames. OpenGL resources are released and the handler is
* been returned by a call to returnTextureFrame(). You are guaranteed to not receive any more * stopped when the texture frame has been returned by a call to returnTextureFrame(). You are
* onTextureFrameAvailable() after this function returns. * guaranteed to not receive any more onTextureFrameAvailable() after this function returns.
*/ */
public void disconnect() { public void disconnect() {
if (!isOwningThread) {
throw new IllegalStateException("Must call disconnect(handler).");
}
if (handler.getLooper().getThread() == Thread.currentThread()) { if (handler.getLooper().getThread() == Thread.currentThread()) {
isQuitting = true; isQuitting = true;
if (!isTextureInUse) { if (!isTextureInUse) {
@ -408,20 +401,6 @@ class SurfaceTextureHelper {
ThreadUtils.awaitUninterruptibly(barrier); ThreadUtils.awaitUninterruptibly(barrier);
} }
/**
* Call disconnect() to stop receiving frames and quit the looper used by |handler|.
* Resources are released when the texture frame has been returned by a call to
* returnTextureFrame(). You are guaranteed to not receive any more
* onTextureFrameAvailable() after this function returns.
*/
public void disconnect(Handler handler) {
if (this.handler != handler) {
throw new IllegalStateException("Wrong handler.");
}
isOwningThread = true;
disconnect();
}
public void textureToYUV(ByteBuffer buf, public void textureToYUV(ByteBuffer buf,
int width, int height, int stride, int textureId, float [] transformMatrix) { int width, int height, int stride, int textureId, float [] transformMatrix) {
if (textureId != oesTextureId) if (textureId != oesTextureId)

View File

@ -12,7 +12,6 @@ package org.webrtc;
import android.content.Context; import android.content.Context;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock; import android.os.SystemClock;
import android.view.Surface; import android.view.Surface;
import android.view.WindowManager; import android.view.WindowManager;
@ -53,7 +52,7 @@ public class VideoCapturerAndroid implements
private final static int CAMERA_FREEZE_REPORT_TIMOUT_MS = 6000; private final static int CAMERA_FREEZE_REPORT_TIMOUT_MS = 6000;
private android.hardware.Camera camera; // Only non-null while capturing. private android.hardware.Camera camera; // Only non-null while capturing.
private HandlerThread cameraThread; private Thread cameraThread;
private final Handler cameraThreadHandler; private final Handler cameraThreadHandler;
private Context applicationContext; private Context applicationContext;
// Synchronization lock for |id|. // Synchronization lock for |id|.
@ -302,12 +301,11 @@ public class VideoCapturerAndroid implements
EglBase.Context sharedContext) { EglBase.Context sharedContext) {
this.id = cameraId; this.id = cameraId;
this.eventsHandler = eventsHandler; this.eventsHandler = eventsHandler;
cameraThread = new HandlerThread(TAG);
cameraThread.start();
cameraThreadHandler = new Handler(cameraThread.getLooper());
isCapturingToTexture = (sharedContext != null); isCapturingToTexture = (sharedContext != null);
cameraStatistics = new CameraStatistics(); cameraStatistics = new CameraStatistics();
surfaceHelper = SurfaceTextureHelper.create(sharedContext, cameraThreadHandler); surfaceHelper = SurfaceTextureHelper.create(sharedContext);
cameraThreadHandler = surfaceHelper.getHandler();
cameraThread = cameraThreadHandler.getLooper().getThread();
if (isCapturingToTexture) { if (isCapturingToTexture) {
surfaceHelper.setListener(this); surfaceHelper.setListener(this);
} }
@ -354,11 +352,11 @@ public class VideoCapturerAndroid implements
} }
} }
}); });
surfaceHelper.disconnect(cameraThreadHandler); surfaceHelper.disconnect();
cameraThread = null; cameraThread = null;
} }
// Used for testing purposes to check if release() has been called. // Used for testing purposes to check if dispose() has been called.
public boolean isDisposed() { public boolean isDisposed() {
return (cameraThread == null); return (cameraThread == null);
} }