Android SurfaceTextureHelper: Add stopListening() function
This CL replaces the function SurfaceTextureHelper.setListener() that could only be called once with the functions startListening() and stopListening() that can be called multiple times. This is necessary when the SurfaceTextureHelper will be passed to the VideoCapturerAndroid in startCapture(). startListening() will be called in startCapture() and stopListening() in stopCapture(). BUG=webrtc:5519 Review URL: https://codereview.webrtc.org/1755573002 Cr-Commit-Position: refs/heads/master@{#11855}
This commit is contained in:
@ -256,7 +256,7 @@ public final class GlRectDrawerTest extends ActivityTestCase {
|
||||
eglBase.getEglBaseContext(), surfaceTextureHelper.getSurfaceTexture(), WIDTH, HEIGHT);
|
||||
final SurfaceTextureHelperTest.MockTextureListener listener =
|
||||
new SurfaceTextureHelperTest.MockTextureListener();
|
||||
surfaceTextureHelper.setListener(listener);
|
||||
surfaceTextureHelper.startListening(listener);
|
||||
|
||||
// Create RGB byte buffer plane with random content.
|
||||
final ByteBuffer rgbPlane = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 3);
|
||||
@ -284,7 +284,7 @@ public final class GlRectDrawerTest extends ActivityTestCase {
|
||||
drawer.release();
|
||||
surfaceTextureHelper.returnTextureFrame();
|
||||
oesProducer.release();
|
||||
surfaceTextureHelper.disconnect();
|
||||
surfaceTextureHelper.dispose();
|
||||
eglBase.release();
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import android.test.suitebuilder.annotation.MediumTest;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public final class SurfaceTextureHelperTest extends ActivityTestCase {
|
||||
/**
|
||||
@ -104,7 +105,7 @@ public final class SurfaceTextureHelperTest extends ActivityTestCase {
|
||||
final SurfaceTextureHelper surfaceTextureHelper =
|
||||
SurfaceTextureHelper.create(eglBase.getEglBaseContext());
|
||||
final MockTextureListener listener = new MockTextureListener();
|
||||
surfaceTextureHelper.setListener(listener);
|
||||
surfaceTextureHelper.startListening(listener);
|
||||
surfaceTextureHelper.getSurfaceTexture().setDefaultBufferSize(width, height);
|
||||
|
||||
// Create resources for stubbing an OES texture producer. |eglOesBase| has the SurfaceTexture in
|
||||
@ -150,12 +151,12 @@ public final class SurfaceTextureHelperTest extends ActivityTestCase {
|
||||
}
|
||||
|
||||
drawer.release();
|
||||
surfaceTextureHelper.disconnect();
|
||||
surfaceTextureHelper.dispose();
|
||||
eglBase.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test disconnecting the SurfaceTextureHelper while holding a pending texture frame. The pending
|
||||
* Test disposing the SurfaceTextureHelper while holding a pending texture frame. The pending
|
||||
* texture frame should still be valid, and this is tested by drawing the texture frame to a pixel
|
||||
* buffer and reading it back with glReadPixels().
|
||||
*/
|
||||
@ -171,7 +172,7 @@ public final class SurfaceTextureHelperTest extends ActivityTestCase {
|
||||
final SurfaceTextureHelper surfaceTextureHelper =
|
||||
SurfaceTextureHelper.create(eglBase.getEglBaseContext());
|
||||
final MockTextureListener listener = new MockTextureListener();
|
||||
surfaceTextureHelper.setListener(listener);
|
||||
surfaceTextureHelper.startListening(listener);
|
||||
surfaceTextureHelper.getSurfaceTexture().setDefaultBufferSize(width, height);
|
||||
|
||||
// Create resources for stubbing an OES texture producer. |eglOesBase| has the SurfaceTexture in
|
||||
@ -196,7 +197,7 @@ public final class SurfaceTextureHelperTest extends ActivityTestCase {
|
||||
// Wait for OES texture frame.
|
||||
listener.waitForNewFrame();
|
||||
// Diconnect while holding the frame.
|
||||
surfaceTextureHelper.disconnect();
|
||||
surfaceTextureHelper.dispose();
|
||||
|
||||
// Draw the pending texture frame onto the pixel buffer.
|
||||
eglBase.makeCurrent();
|
||||
@ -217,21 +218,21 @@ public final class SurfaceTextureHelperTest extends ActivityTestCase {
|
||||
assertEquals(rgbaData.get() & 0xFF, blue);
|
||||
assertEquals(rgbaData.get() & 0xFF, 255);
|
||||
}
|
||||
// Late frame return after everything has been disconnected and released.
|
||||
// Late frame return after everything has been disposed and released.
|
||||
surfaceTextureHelper.returnTextureFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test disconnecting the SurfaceTextureHelper, but keep trying to produce more texture frames. No
|
||||
* Test disposing the SurfaceTextureHelper, but keep trying to produce more texture frames. No
|
||||
* frames should be delivered to the listener.
|
||||
*/
|
||||
@MediumTest
|
||||
public static void testDisconnect() throws InterruptedException {
|
||||
public static void testDispose() throws InterruptedException {
|
||||
// Create SurfaceTextureHelper and listener.
|
||||
final SurfaceTextureHelper surfaceTextureHelper =
|
||||
SurfaceTextureHelper.create(null);
|
||||
final MockTextureListener listener = new MockTextureListener();
|
||||
surfaceTextureHelper.setListener(listener);
|
||||
surfaceTextureHelper.startListening(listener);
|
||||
// Create EglBase with the SurfaceTexture as target EGLSurface.
|
||||
final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PLAIN);
|
||||
eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
|
||||
@ -245,13 +246,13 @@ public final class SurfaceTextureHelperTest extends ActivityTestCase {
|
||||
listener.waitForNewFrame();
|
||||
surfaceTextureHelper.returnTextureFrame();
|
||||
|
||||
// Disconnect - we should not receive any textures after this.
|
||||
surfaceTextureHelper.disconnect();
|
||||
// Dispose - we should not receive any textures after this.
|
||||
surfaceTextureHelper.dispose();
|
||||
|
||||
// Draw one frame.
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||
eglBase.swapBuffers();
|
||||
// swapBuffers() should not trigger onTextureFrameAvailable() because we are disconnected.
|
||||
// swapBuffers() should not trigger onTextureFrameAvailable() because disposed has been called.
|
||||
// Assert that no OES texture was delivered.
|
||||
assertFalse(listener.waitForNewFrame(500));
|
||||
|
||||
@ -259,14 +260,125 @@ public final class SurfaceTextureHelperTest extends ActivityTestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test disconnecting the SurfaceTextureHelper immediately after is has been setup to use a
|
||||
* Test disposing 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() {
|
||||
public static void testDisposeImmediately() {
|
||||
final SurfaceTextureHelper surfaceTextureHelper =
|
||||
SurfaceTextureHelper.create(null);
|
||||
surfaceTextureHelper.disconnect();
|
||||
surfaceTextureHelper.dispose();
|
||||
}
|
||||
|
||||
// Helper method to call stopListening() on correct thread.
|
||||
private static void stopListeningOnHandlerThread(final SurfaceTextureHelper surfaceTextureHelper)
|
||||
throws InterruptedException {
|
||||
final CountDownLatch barrier = new CountDownLatch(1);
|
||||
surfaceTextureHelper.getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
surfaceTextureHelper.stopListening();
|
||||
barrier.countDown();
|
||||
}
|
||||
});
|
||||
barrier.await();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call stopListening(), but keep trying to produce more texture frames. No frames should be
|
||||
* delivered to the listener.
|
||||
*/
|
||||
@MediumTest
|
||||
public static void testStopListening() throws InterruptedException {
|
||||
// Create SurfaceTextureHelper and listener.
|
||||
final SurfaceTextureHelper surfaceTextureHelper =
|
||||
SurfaceTextureHelper.create(null);
|
||||
final MockTextureListener listener = new MockTextureListener();
|
||||
surfaceTextureHelper.startListening(listener);
|
||||
// Create EglBase with the SurfaceTexture as target EGLSurface.
|
||||
final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PLAIN);
|
||||
eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
|
||||
eglBase.makeCurrent();
|
||||
// Assert no frame has been received yet.
|
||||
assertFalse(listener.waitForNewFrame(1));
|
||||
// Draw and wait for one frame.
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||
// swapBuffers() will ultimately trigger onTextureFrameAvailable().
|
||||
eglBase.swapBuffers();
|
||||
listener.waitForNewFrame();
|
||||
surfaceTextureHelper.returnTextureFrame();
|
||||
|
||||
// Stop listening - we should not receive any textures after this.
|
||||
stopListeningOnHandlerThread(surfaceTextureHelper);
|
||||
|
||||
// Draw one frame.
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||
eglBase.swapBuffers();
|
||||
// swapBuffers() should not trigger onTextureFrameAvailable() because disposed has been called.
|
||||
// Assert that no OES texture was delivered.
|
||||
assertFalse(listener.waitForNewFrame(500));
|
||||
|
||||
surfaceTextureHelper.dispose();
|
||||
eglBase.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test stopListening() immediately after the SurfaceTextureHelper has been setup.
|
||||
*/
|
||||
@SmallTest
|
||||
public static void testStopListeningImmediately() throws InterruptedException {
|
||||
final SurfaceTextureHelper surfaceTextureHelper =
|
||||
SurfaceTextureHelper.create(null);
|
||||
final MockTextureListener listener = new MockTextureListener();
|
||||
surfaceTextureHelper.startListening(listener);
|
||||
stopListeningOnHandlerThread(surfaceTextureHelper);
|
||||
surfaceTextureHelper.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test calling startListening() with a new listener after stopListening() has been called.
|
||||
*/
|
||||
@MediumTest
|
||||
public static void testRestartListeningWithNewListener() throws InterruptedException {
|
||||
// Create SurfaceTextureHelper and listener.
|
||||
final SurfaceTextureHelper surfaceTextureHelper =
|
||||
SurfaceTextureHelper.create(null);
|
||||
final MockTextureListener listener1 = new MockTextureListener();
|
||||
surfaceTextureHelper.startListening(listener1);
|
||||
// Create EglBase with the SurfaceTexture as target EGLSurface.
|
||||
final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PLAIN);
|
||||
eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture());
|
||||
eglBase.makeCurrent();
|
||||
// Assert no frame has been received yet.
|
||||
assertFalse(listener1.waitForNewFrame(1));
|
||||
// Draw and wait for one frame.
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||
// swapBuffers() will ultimately trigger onTextureFrameAvailable().
|
||||
eglBase.swapBuffers();
|
||||
listener1.waitForNewFrame();
|
||||
surfaceTextureHelper.returnTextureFrame();
|
||||
|
||||
// Stop listening - |listener1| should not receive any textures after this.
|
||||
stopListeningOnHandlerThread(surfaceTextureHelper);
|
||||
|
||||
// Connect different listener.
|
||||
final MockTextureListener listener2 = new MockTextureListener();
|
||||
surfaceTextureHelper.startListening(listener2);
|
||||
// Assert no frame has been received yet.
|
||||
assertFalse(listener2.waitForNewFrame(1));
|
||||
|
||||
// Draw one frame.
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||
eglBase.swapBuffers();
|
||||
|
||||
// Check that |listener2| received the frame, and not |listener1|.
|
||||
listener2.waitForNewFrame();
|
||||
assertFalse(listener1.waitForNewFrame(1));
|
||||
|
||||
surfaceTextureHelper.returnTextureFrame();
|
||||
|
||||
surfaceTextureHelper.dispose();
|
||||
eglBase.release();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@ -280,7 +392,7 @@ public final class SurfaceTextureHelperTest extends ActivityTestCase {
|
||||
final SurfaceTextureHelper surfaceTextureHelper =
|
||||
SurfaceTextureHelper.create(eglBase.getEglBaseContext());
|
||||
final MockTextureListener listener = new MockTextureListener();
|
||||
surfaceTextureHelper.setListener(listener);
|
||||
surfaceTextureHelper.startListening(listener);
|
||||
surfaceTextureHelper.getSurfaceTexture().setDefaultBufferSize(width, height);
|
||||
|
||||
// Create resources for stubbing an OES texture producer. |eglBase| has the SurfaceTexture in
|
||||
@ -342,7 +454,7 @@ public final class SurfaceTextureHelperTest extends ActivityTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
surfaceTextureHelper.disconnect();
|
||||
surfaceTextureHelper.dispose();
|
||||
eglBase.release();
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,8 +28,8 @@ import java.util.concurrent.TimeUnit;
|
||||
* Helper class to create and synchronize access to a SurfaceTexture. The caller will get notified
|
||||
* of new frames in onTextureFrameAvailable(), and should call returnTextureFrame() when done with
|
||||
* the frame. Only one texture frame can be in flight at once, so returnTextureFrame() must be
|
||||
* called in order to receive a new frame. Call disconnect() to stop receiveing new frames and
|
||||
* release all resources.
|
||||
* called in order to receive a new frame. Call stopListening() to stop receiveing new frames. Call
|
||||
* dispose to release all resources once the texture frame is returned.
|
||||
* Note that there is a C++ counter part of this class that optionally can be used. It is used for
|
||||
* wrapping texture frames into webrtc::VideoFrames and also handles calling returnTextureFrame()
|
||||
* when the webrtc::VideoFrame is no longer used.
|
||||
@ -287,6 +287,7 @@ class SurfaceTextureHelper {
|
||||
private final int oesTextureId;
|
||||
private YuvConverter yuvConverter;
|
||||
|
||||
// These variables are only accessed from the |handler| thread.
|
||||
private OnTextureFrameAvailableListener listener;
|
||||
// The possible states of this class.
|
||||
private boolean hasPendingTexture = false;
|
||||
@ -305,6 +306,13 @@ class SurfaceTextureHelper {
|
||||
|
||||
oesTextureId = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
|
||||
surfaceTexture = new SurfaceTexture(oesTextureId);
|
||||
surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
|
||||
@Override
|
||||
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
|
||||
hasPendingTexture = true;
|
||||
tryDeliverTextureFrame();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private YuvConverter getYuvConverter() {
|
||||
@ -320,23 +328,35 @@ class SurfaceTextureHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Start to stream textures to the given |listener|.
|
||||
* A Listener can only be set once.
|
||||
* Start to stream textures to the given |listener|. If you need to change listener, you need to
|
||||
* call stopListening() first.
|
||||
*/
|
||||
public void setListener(OnTextureFrameAvailableListener listener) {
|
||||
public void startListening(final OnTextureFrameAvailableListener listener) {
|
||||
if (this.listener != null) {
|
||||
throw new IllegalStateException("SurfaceTextureHelper listener has already been set.");
|
||||
}
|
||||
this.listener = listener;
|
||||
surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
|
||||
hasPendingTexture = true;
|
||||
public void run() {
|
||||
SurfaceTextureHelper.this.listener = listener;
|
||||
// May alredy have a pending frame - try delivering it.
|
||||
tryDeliverTextureFrame();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop listening. The listener set in startListening() is guaranteded to not receive any more
|
||||
* onTextureFrameAvailable() callbacks after this function returns. This function must be called
|
||||
* on the getHandler() thread.
|
||||
*/
|
||||
public void stopListening() {
|
||||
if (handler.getLooper().getThread() != Thread.currentThread()) {
|
||||
throw new IllegalStateException("Wrong thread.");
|
||||
}
|
||||
this.listener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying SurfaceTexture. The SurfaceTexture should be passed in to a video
|
||||
* producer such as a camera or decoder.
|
||||
@ -347,7 +367,7 @@ class SurfaceTextureHelper {
|
||||
|
||||
/**
|
||||
* Retrieve the handler that calls onTextureFrameAvailable(). This handler is valid until
|
||||
* disconnect() is called.
|
||||
* dispose() is called.
|
||||
*/
|
||||
public Handler getHandler() {
|
||||
return handler;
|
||||
@ -380,7 +400,7 @@ class SurfaceTextureHelper {
|
||||
* stopped 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() {
|
||||
public void dispose() {
|
||||
if (handler.getLooper().getThread() == Thread.currentThread()) {
|
||||
isQuitting = true;
|
||||
if (!isTextureInUse) {
|
||||
@ -413,7 +433,7 @@ class SurfaceTextureHelper {
|
||||
if (handler.getLooper().getThread() != Thread.currentThread()) {
|
||||
throw new IllegalStateException("Wrong thread.");
|
||||
}
|
||||
if (isQuitting || !hasPendingTexture || isTextureInUse) {
|
||||
if (isQuitting || !hasPendingTexture || isTextureInUse || listener == null) {
|
||||
return;
|
||||
}
|
||||
isTextureInUse = true;
|
||||
|
||||
@ -306,9 +306,6 @@ public class VideoCapturerAndroid implements
|
||||
surfaceHelper = SurfaceTextureHelper.create(sharedContext);
|
||||
cameraThreadHandler = surfaceHelper.getHandler();
|
||||
cameraThread = cameraThreadHandler.getLooper().getThread();
|
||||
if (isCapturingToTexture) {
|
||||
surfaceHelper.setListener(this);
|
||||
}
|
||||
Logging.d(TAG, "VideoCapturerAndroid isCapturingToTexture : " + isCapturingToTexture);
|
||||
}
|
||||
|
||||
@ -352,7 +349,7 @@ public class VideoCapturerAndroid implements
|
||||
}
|
||||
}
|
||||
});
|
||||
surfaceHelper.disconnect();
|
||||
surfaceHelper.dispose();
|
||||
cameraThread = null;
|
||||
}
|
||||
|
||||
@ -436,6 +433,9 @@ public class VideoCapturerAndroid implements
|
||||
camera.setErrorCallback(cameraErrorCallback);
|
||||
startPreviewOnCameraThread(width, height, framerate);
|
||||
frameObserver.onCapturerStarted(true);
|
||||
if (isCapturingToTexture) {
|
||||
surfaceHelper.startListening(this);
|
||||
}
|
||||
|
||||
// Start camera observer.
|
||||
cameraThreadHandler.postDelayed(cameraObserver, CAMERA_OBSERVER_PERIOD_MS);
|
||||
@ -544,6 +544,8 @@ public class VideoCapturerAndroid implements
|
||||
final CountDownLatch barrier = new CountDownLatch(1);
|
||||
cameraThreadHandler.post(new Runnable() {
|
||||
@Override public void run() {
|
||||
// Make sure onTextureFrameAvailable() is not called anymore.
|
||||
surfaceHelper.stopListening();
|
||||
stopCaptureOnCameraThread();
|
||||
barrier.countDown();
|
||||
}
|
||||
@ -669,12 +671,10 @@ public class VideoCapturerAndroid implements
|
||||
@Override
|
||||
public void onTextureFrameAvailable(
|
||||
int oesTextureId, float[] transformMatrix, long timestampNs) {
|
||||
checkIsOnCameraThread();
|
||||
if (camera == null) {
|
||||
// Camera is stopped, we need to return the buffer immediately.
|
||||
surfaceHelper.returnTextureFrame();
|
||||
return;
|
||||
throw new RuntimeException("onTextureFrameAvailable() called after stopCapture().");
|
||||
}
|
||||
checkIsOnCameraThread();
|
||||
if (dropNextFrame) {
|
||||
surfaceHelper.returnTextureFrame();
|
||||
dropNextFrame = false;
|
||||
|
||||
@ -469,7 +469,7 @@ public class MediaCodecVideoDecoder {
|
||||
|
||||
public TextureListener(SurfaceTextureHelper surfaceTextureHelper) {
|
||||
this.surfaceTextureHelper = surfaceTextureHelper;
|
||||
surfaceTextureHelper.setListener(this);
|
||||
surfaceTextureHelper.startListening(this);
|
||||
}
|
||||
|
||||
public void addBufferToRender(DecodedOutputBuffer buffer) {
|
||||
@ -525,10 +525,10 @@ public class MediaCodecVideoDecoder {
|
||||
}
|
||||
|
||||
public void release() {
|
||||
// SurfaceTextureHelper.disconnect() will block until any onTextureFrameAvailable() in
|
||||
// progress is done. Therefore, the call to disconnect() must be outside any synchronized
|
||||
// SurfaceTextureHelper.dispose() will block until any onTextureFrameAvailable() in
|
||||
// progress is done. Therefore, the call to dispose() must be outside any synchronized
|
||||
// statement that is also used in the onTextureFrameAvailable() above to avoid deadlocks.
|
||||
surfaceTextureHelper.disconnect();
|
||||
surfaceTextureHelper.dispose();
|
||||
synchronized (newFrameLock) {
|
||||
if (renderedBuffer != null) {
|
||||
surfaceTextureHelper.returnTextureFrame();
|
||||
|
||||
Reference in New Issue
Block a user