blob: dbdf2684d69634ae5eb6b663933c987069dcb691 [file] [log] [blame]
Magnus Jedvert4ae28a12015-09-15 09:44:07 +02001/*
kjellanderb24317b2016-02-10 07:54:43 -08002 * Copyright 2015 The WebRTC project authors. All Rights Reserved.
Magnus Jedvert4ae28a12015-09-15 09:44:07 +02003 *
kjellanderb24317b2016-02-10 07:54:43 -08004 * 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.
Magnus Jedvert4ae28a12015-09-15 09:44:07 +02009 */
10
11package org.webrtc;
12
Jonathan Yu22384412017-08-16 13:25:45 -070013import android.annotation.TargetApi;
sakal836f60c2017-07-28 07:12:23 -070014import android.graphics.Matrix;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020015import android.graphics.SurfaceTexture;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020016import android.opengl.GLES11Ext;
17import android.opengl.GLES20;
18import android.os.Build;
19import android.os.Handler;
20import android.os.HandlerThread;
21import android.os.SystemClock;
nissec490e012015-12-10 06:23:33 -080022import java.nio.ByteBuffer;
23import java.nio.FloatBuffer;
Magnus Jedvert747c1bc2015-10-12 09:27:48 +020024import java.util.concurrent.Callable;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020025import java.util.concurrent.TimeUnit;
Magnus Jedvert6199a372017-11-14 13:03:08 +010026import org.webrtc.EglBase;
Bjorn Mellem8fb23612017-07-18 11:33:39 -070027import org.webrtc.VideoFrame.I420Buffer;
28import org.webrtc.VideoFrame.TextureBuffer;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020029
30/**
31 * Helper class to create and synchronize access to a SurfaceTexture. The caller will get notified
32 * of new frames in onTextureFrameAvailable(), and should call returnTextureFrame() when done with
33 * the frame. Only one texture frame can be in flight at once, so returnTextureFrame() must be
magjed81e8e372016-03-03 02:11:44 -080034 * called in order to receive a new frame. Call stopListening() to stop receiveing new frames. Call
35 * dispose to release all resources once the texture frame is returned.
Per3e9eb4b2015-09-28 10:52:22 +020036 * Note that there is a C++ counter part of this class that optionally can be used. It is used for
37 * wrapping texture frames into webrtc::VideoFrames and also handles calling returnTextureFrame()
38 * when the webrtc::VideoFrame is no longer used.
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020039 */
skvladed6e0772016-11-30 14:40:18 -080040public class SurfaceTextureHelper {
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020041 private static final String TAG = "SurfaceTextureHelper";
42 /**
43 * Callback interface for being notified that a new texture frame is available. The calls will be
Jonathan Yu22384412017-08-16 13:25:45 -070044 * made on the SurfaceTextureHelper handler thread, with a bound EGLContext. The callee is not
45 * allowed to make another EGLContext current on the calling thread.
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020046 */
47 public interface OnTextureFrameAvailableListener {
48 abstract void onTextureFrameAvailable(
49 int oesTextureId, float[] transformMatrix, long timestampNs);
50 }
51
Magnus Jedvert747c1bc2015-10-12 09:27:48 +020052 /**
magjed9e69dfd2016-02-26 07:45:44 -080053 * Construct a new SurfaceTextureHelper sharing OpenGL resources with |sharedContext|. A dedicated
magjed2aa84262016-05-09 08:28:45 -070054 * thread and handler is created for handling the SurfaceTexture. May return null if EGL fails to
55 * initialize a pixel buffer surface and make it current.
Magnus Jedvert747c1bc2015-10-12 09:27:48 +020056 */
Magnus Jedvert6199a372017-11-14 13:03:08 +010057 @CalledByNative
magjed82b750b2016-03-31 00:54:15 -070058 public static SurfaceTextureHelper create(
59 final String threadName, final EglBase.Context sharedContext) {
60 final HandlerThread thread = new HandlerThread(threadName);
magjed9e69dfd2016-02-26 07:45:44 -080061 thread.start();
62 final Handler handler = new Handler(thread.getLooper());
63
Magnus Jedvert747c1bc2015-10-12 09:27:48 +020064 // The onFrameAvailable() callback will be executed on the SurfaceTexture ctor thread. See:
65 // http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/graphics/SurfaceTexture.java#195.
66 // Therefore, in order to control the callback thread on API lvl < 21, the SurfaceTextureHelper
67 // is constructed on the |handler| thread.
nissea44e72c2016-05-27 00:27:59 -070068 return ThreadUtils.invokeAtFrontUninterruptibly(handler, new Callable<SurfaceTextureHelper>() {
magjed9e69dfd2016-02-26 07:45:44 -080069 @Override
70 public SurfaceTextureHelper call() {
magjed2aa84262016-05-09 08:28:45 -070071 try {
72 return new SurfaceTextureHelper(sharedContext, handler);
73 } catch (RuntimeException e) {
74 Logging.e(TAG, threadName + " create failure", e);
75 return null;
76 }
Magnus Jedvert747c1bc2015-10-12 09:27:48 +020077 }
78 });
79 }
80
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020081 private final Handler handler;
82 private final EglBase eglBase;
83 private final SurfaceTexture surfaceTexture;
84 private final int oesTextureId;
nissec490e012015-12-10 06:23:33 -080085 private YuvConverter yuvConverter;
86
magjed81e8e372016-03-03 02:11:44 -080087 // These variables are only accessed from the |handler| thread.
Per3e9eb4b2015-09-28 10:52:22 +020088 private OnTextureFrameAvailableListener listener;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020089 // The possible states of this class.
90 private boolean hasPendingTexture = false;
perkj88518a22015-12-18 00:37:06 -080091 private volatile boolean isTextureInUse = false;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020092 private boolean isQuitting = false;
magjedd8ddb792016-03-17 03:13:43 -070093 // |pendingListener| is set in setListener() and the runnable is posted to the handler thread.
94 // setListener() is not allowed to be called again before stopListening(), so this is thread safe.
95 private OnTextureFrameAvailableListener pendingListener;
96 final Runnable setListenerRunnable = new Runnable() {
97 @Override
98 public void run() {
99 Logging.d(TAG, "Setting listener to " + pendingListener);
100 listener = pendingListener;
101 pendingListener = null;
Magnus Jedvert181310f2016-05-23 16:26:47 +0200102 // May have a pending frame from the previous capture session - drop it.
103 if (hasPendingTexture) {
104 // Calling updateTexImage() is neccessary in order to receive new frames.
105 updateTexImage();
106 hasPendingTexture = false;
107 }
magjedd8ddb792016-03-17 03:13:43 -0700108 }
109 };
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200110
magjed9e69dfd2016-02-26 07:45:44 -0800111 private SurfaceTextureHelper(EglBase.Context sharedContext, Handler handler) {
Alex Glazneva5b62d92015-10-12 13:56:20 -0700112 if (handler.getLooper().getThread() != Thread.currentThread()) {
Magnus Jedvert747c1bc2015-10-12 09:27:48 +0200113 throw new IllegalStateException("SurfaceTextureHelper must be created on the handler thread");
perkj1b33da12015-10-05 21:06:41 +0200114 }
Magnus Jedvert747c1bc2015-10-12 09:27:48 +0200115 this.handler = handler;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200116
nisse03f80eb2015-12-07 01:17:16 -0800117 eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER);
magjed2aa84262016-05-09 08:28:45 -0700118 try {
119 // Both these statements have been observed to fail on rare occasions, see BUG=webrtc:5682.
120 eglBase.createDummyPbufferSurface();
121 eglBase.makeCurrent();
122 } catch (RuntimeException e) {
123 // Clean up before rethrowing the exception.
124 eglBase.release();
125 handler.getLooper().quit();
126 throw e;
127 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200128
129 oesTextureId = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
130 surfaceTexture = new SurfaceTexture(oesTextureId);
Jonathan Yu22384412017-08-16 13:25:45 -0700131 setOnFrameAvailableListener(surfaceTexture, (SurfaceTexture st) -> {
132 hasPendingTexture = true;
133 tryDeliverTextureFrame();
134 }, handler);
135 }
136
137 @TargetApi(21)
138 private static void setOnFrameAvailableListener(SurfaceTexture surfaceTexture,
139 SurfaceTexture.OnFrameAvailableListener listener, Handler handler) {
140 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
141 surfaceTexture.setOnFrameAvailableListener(listener, handler);
142 } else {
143 // The documentation states that the listener will be called on an arbitrary thread, but in
144 // pratice, it is always the thread on which the SurfaceTexture was constructed. There are
145 // assertions in place in case this ever changes. For API >= 21, we use the new API to
146 // explicitly specify the handler.
147 surfaceTexture.setOnFrameAvailableListener(listener);
148 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200149 }
150
151 /**
magjed81e8e372016-03-03 02:11:44 -0800152 * Start to stream textures to the given |listener|. If you need to change listener, you need to
153 * call stopListening() first.
Per3e9eb4b2015-09-28 10:52:22 +0200154 */
magjed81e8e372016-03-03 02:11:44 -0800155 public void startListening(final OnTextureFrameAvailableListener listener) {
magjedd8ddb792016-03-17 03:13:43 -0700156 if (this.listener != null || this.pendingListener != null) {
Per3e9eb4b2015-09-28 10:52:22 +0200157 throw new IllegalStateException("SurfaceTextureHelper listener has already been set.");
158 }
magjedd8ddb792016-03-17 03:13:43 -0700159 this.pendingListener = listener;
160 handler.post(setListenerRunnable);
Per3e9eb4b2015-09-28 10:52:22 +0200161 }
162
163 /**
magjed81e8e372016-03-03 02:11:44 -0800164 * Stop listening. The listener set in startListening() is guaranteded to not receive any more
nissea44e72c2016-05-27 00:27:59 -0700165 * onTextureFrameAvailable() callbacks after this function returns.
magjed81e8e372016-03-03 02:11:44 -0800166 */
167 public void stopListening() {
magjedd8ddb792016-03-17 03:13:43 -0700168 Logging.d(TAG, "stopListening()");
169 handler.removeCallbacks(setListenerRunnable);
nissea44e72c2016-05-27 00:27:59 -0700170 ThreadUtils.invokeAtFrontUninterruptibly(handler, new Runnable() {
171 @Override
172 public void run() {
173 listener = null;
174 pendingListener = null;
175 }
176 });
magjed81e8e372016-03-03 02:11:44 -0800177 }
178
179 /**
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200180 * Retrieve the underlying SurfaceTexture. The SurfaceTexture should be passed in to a video
181 * producer such as a camera or decoder.
182 */
183 public SurfaceTexture getSurfaceTexture() {
184 return surfaceTexture;
185 }
186
187 /**
magjed9e69dfd2016-02-26 07:45:44 -0800188 * Retrieve the handler that calls onTextureFrameAvailable(). This handler is valid until
magjed81e8e372016-03-03 02:11:44 -0800189 * dispose() is called.
magjed9e69dfd2016-02-26 07:45:44 -0800190 */
191 public Handler getHandler() {
192 return handler;
193 }
194
195 /**
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200196 * Call this function to signal that you are done with the frame received in
197 * onTextureFrameAvailable(). Only one texture frame can be in flight at once, so you must call
198 * this function in order to receive a new frame.
199 */
Magnus Jedvert6199a372017-11-14 13:03:08 +0100200 @CalledByNative
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200201 public void returnTextureFrame() {
202 handler.post(new Runnable() {
sakalb6760f92016-09-29 04:12:44 -0700203 @Override
204 public void run() {
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200205 isTextureInUse = false;
206 if (isQuitting) {
207 release();
208 } else {
209 tryDeliverTextureFrame();
210 }
211 }
212 });
213 }
214
perkj88518a22015-12-18 00:37:06 -0800215 public boolean isTextureInUse() {
216 return isTextureInUse;
217 }
218
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200219 /**
magjed9e69dfd2016-02-26 07:45:44 -0800220 * Call disconnect() to stop receiving frames. OpenGL resources are released and the handler is
221 * stopped when the texture frame has been returned by a call to returnTextureFrame(). You are
222 * guaranteed to not receive any more onTextureFrameAvailable() after this function returns.
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200223 */
Magnus Jedvert6199a372017-11-14 13:03:08 +0100224 @CalledByNative
magjed81e8e372016-03-03 02:11:44 -0800225 public void dispose() {
magjedd8ddb792016-03-17 03:13:43 -0700226 Logging.d(TAG, "dispose()");
nissea44e72c2016-05-27 00:27:59 -0700227 ThreadUtils.invokeAtFrontUninterruptibly(handler, new Runnable() {
228 @Override
229 public void run() {
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200230 isQuitting = true;
231 if (!isTextureInUse) {
232 release();
233 }
234 }
235 });
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200236 }
237
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200238 /** Deprecated, use textureToYuv. */
239 @Deprecated
240 @SuppressWarnings("deprecation") // yuvConverter.convert is deprecated
241 void textureToYUV(final ByteBuffer buf, final int width, final int height, final int stride,
242 final int textureId, final float[] transformMatrix) {
magjed1cb48232016-10-20 03:19:16 -0700243 if (textureId != oesTextureId) {
nissec490e012015-12-10 06:23:33 -0800244 throw new IllegalStateException("textureToByteBuffer called with unexpected textureId");
magjed1cb48232016-10-20 03:19:16 -0700245 }
nissec490e012015-12-10 06:23:33 -0800246
magjed1cb48232016-10-20 03:19:16 -0700247 ThreadUtils.invokeAtFrontUninterruptibly(handler, new Runnable() {
248 @Override
249 public void run() {
250 if (yuvConverter == null) {
251 yuvConverter = new YuvConverter();
252 }
253 yuvConverter.convert(buf, width, height, stride, textureId, transformMatrix);
254 }
255 });
nissec490e012015-12-10 06:23:33 -0800256 }
257
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200258 /**
259 * Posts to the correct thread to convert |textureBuffer| to I420. Must only be called with
260 * textures generated by this SurfaceTextureHelper.
261 */
262 public VideoFrame.I420Buffer textureToYuv(final TextureBuffer textureBuffer) {
263 if (textureBuffer.getTextureId() != oesTextureId) {
264 throw new IllegalStateException("textureToByteBuffer called with unexpected textureId");
265 }
266
267 final VideoFrame.I420Buffer[] result = new VideoFrame.I420Buffer[1];
268 ThreadUtils.invokeAtFrontUninterruptibly(handler, () -> {
269 if (yuvConverter == null) {
270 yuvConverter = new YuvConverter();
271 }
272 result[0] = yuvConverter.convert(textureBuffer);
273 });
274 return result[0];
275 }
276
Magnus Jedvert181310f2016-05-23 16:26:47 +0200277 private void updateTexImage() {
278 // SurfaceTexture.updateTexImage apparently can compete and deadlock with eglSwapBuffers,
279 // as observed on Nexus 5. Therefore, synchronize it with the EGL functions.
280 // See https://bugs.chromium.org/p/webrtc/issues/detail?id=5702 for more info.
281 synchronized (EglBase.lock) {
282 surfaceTexture.updateTexImage();
283 }
284 }
285
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200286 private void tryDeliverTextureFrame() {
Alex Glazneva5b62d92015-10-12 13:56:20 -0700287 if (handler.getLooper().getThread() != Thread.currentThread()) {
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200288 throw new IllegalStateException("Wrong thread.");
289 }
magjed81e8e372016-03-03 02:11:44 -0800290 if (isQuitting || !hasPendingTexture || isTextureInUse || listener == null) {
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200291 return;
292 }
293 isTextureInUse = true;
294 hasPendingTexture = false;
295
Magnus Jedvert181310f2016-05-23 16:26:47 +0200296 updateTexImage();
perkj1b33da12015-10-05 21:06:41 +0200297
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200298 final float[] transformMatrix = new float[16];
299 surfaceTexture.getTransformMatrix(transformMatrix);
sakalbee4ff82017-05-02 08:33:52 -0700300 final long timestampNs = surfaceTexture.getTimestamp();
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200301 listener.onTextureFrameAvailable(oesTextureId, transformMatrix, timestampNs);
302 }
303
304 private void release() {
Alex Glazneva5b62d92015-10-12 13:56:20 -0700305 if (handler.getLooper().getThread() != Thread.currentThread()) {
Magnus Jedvert1ab271c2015-09-28 11:05:44 +0200306 throw new IllegalStateException("Wrong thread.");
307 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200308 if (isTextureInUse || !isQuitting) {
309 throw new IllegalStateException("Unexpected release.");
310 }
magjed1cb48232016-10-20 03:19:16 -0700311 if (yuvConverter != null) {
312 yuvConverter.release();
nissec490e012015-12-10 06:23:33 -0800313 }
Magnus Jedvert1ab271c2015-09-28 11:05:44 +0200314 GLES20.glDeleteTextures(1, new int[] {oesTextureId}, 0);
315 surfaceTexture.release();
316 eglBase.release();
perkj92375592015-11-24 03:03:13 -0800317 handler.getLooper().quit();
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200318 }
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700319
320 /**
321 * Creates a VideoFrame buffer backed by this helper's texture. The |width| and |height| should
322 * match the dimensions of the data placed in the texture. The correct |transformMatrix| may be
323 * obtained from callbacks to OnTextureFrameAvailableListener.
324 *
325 * The returned TextureBuffer holds a reference to the SurfaceTextureHelper that created it. The
326 * buffer calls returnTextureFrame() when it is released.
327 */
sakal836f60c2017-07-28 07:12:23 -0700328 public TextureBuffer createTextureBuffer(int width, int height, Matrix transformMatrix) {
329 return new TextureBufferImpl(
330 width, height, TextureBuffer.Type.OES, oesTextureId, transformMatrix, this, new Runnable() {
331 @Override
332 public void run() {
333 returnTextureFrame();
334 }
335 });
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700336 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200337}