blob: 0b5bcf805f592ca773f50f90d251accde2999985 [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;
nissec490e012015-12-10 06:23:33 -080021import java.nio.ByteBuffer;
Magnus Jedvert747c1bc2015-10-12 09:27:48 +020022import java.util.concurrent.Callable;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010023import javax.annotation.Nullable;
Magnus Jedvert6199a372017-11-14 13:03:08 +010024import org.webrtc.EglBase;
Bjorn Mellem8fb23612017-07-18 11:33:39 -070025import org.webrtc.VideoFrame.TextureBuffer;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020026
27/**
28 * Helper class to create and synchronize access to a SurfaceTexture. The caller will get notified
29 * of new frames in onTextureFrameAvailable(), and should call returnTextureFrame() when done with
30 * the frame. Only one texture frame can be in flight at once, so returnTextureFrame() must be
magjed81e8e372016-03-03 02:11:44 -080031 * called in order to receive a new frame. Call stopListening() to stop receiveing new frames. Call
32 * dispose to release all resources once the texture frame is returned.
Per3e9eb4b2015-09-28 10:52:22 +020033 * Note that there is a C++ counter part of this class that optionally can be used. It is used for
34 * wrapping texture frames into webrtc::VideoFrames and also handles calling returnTextureFrame()
35 * when the webrtc::VideoFrame is no longer used.
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020036 */
skvladed6e0772016-11-30 14:40:18 -080037public class SurfaceTextureHelper {
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020038 private static final String TAG = "SurfaceTextureHelper";
39 /**
40 * Callback interface for being notified that a new texture frame is available. The calls will be
Jonathan Yu22384412017-08-16 13:25:45 -070041 * made on the SurfaceTextureHelper handler thread, with a bound EGLContext. The callee is not
42 * allowed to make another EGLContext current on the calling thread.
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020043 */
44 public interface OnTextureFrameAvailableListener {
45 abstract void onTextureFrameAvailable(
46 int oesTextureId, float[] transformMatrix, long timestampNs);
47 }
48
Magnus Jedvert747c1bc2015-10-12 09:27:48 +020049 /**
magjed9e69dfd2016-02-26 07:45:44 -080050 * Construct a new SurfaceTextureHelper sharing OpenGL resources with |sharedContext|. A dedicated
magjed2aa84262016-05-09 08:28:45 -070051 * thread and handler is created for handling the SurfaceTexture. May return null if EGL fails to
52 * initialize a pixel buffer surface and make it current.
Magnus Jedvert747c1bc2015-10-12 09:27:48 +020053 */
magjed82b750b2016-03-31 00:54:15 -070054 public static SurfaceTextureHelper create(
55 final String threadName, final EglBase.Context sharedContext) {
56 final HandlerThread thread = new HandlerThread(threadName);
magjed9e69dfd2016-02-26 07:45:44 -080057 thread.start();
58 final Handler handler = new Handler(thread.getLooper());
59
Magnus Jedvert747c1bc2015-10-12 09:27:48 +020060 // The onFrameAvailable() callback will be executed on the SurfaceTexture ctor thread. See:
61 // http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/graphics/SurfaceTexture.java#195.
62 // Therefore, in order to control the callback thread on API lvl < 21, the SurfaceTextureHelper
63 // is constructed on the |handler| thread.
nissea44e72c2016-05-27 00:27:59 -070064 return ThreadUtils.invokeAtFrontUninterruptibly(handler, new Callable<SurfaceTextureHelper>() {
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010065 @Nullable
magjed9e69dfd2016-02-26 07:45:44 -080066 @Override
67 public SurfaceTextureHelper call() {
magjed2aa84262016-05-09 08:28:45 -070068 try {
69 return new SurfaceTextureHelper(sharedContext, handler);
70 } catch (RuntimeException e) {
71 Logging.e(TAG, threadName + " create failure", e);
72 return null;
73 }
Magnus Jedvert747c1bc2015-10-12 09:27:48 +020074 }
75 });
76 }
77
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020078 private final Handler handler;
79 private final EglBase eglBase;
80 private final SurfaceTexture surfaceTexture;
81 private final int oesTextureId;
Magnus Jedvert1d270f82018-04-16 16:28:29 +020082 private final YuvConverter yuvConverter = new YuvConverter();
nissec490e012015-12-10 06:23:33 -080083
magjed81e8e372016-03-03 02:11:44 -080084 // These variables are only accessed from the |handler| thread.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010085 @Nullable private OnTextureFrameAvailableListener listener;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020086 // The possible states of this class.
87 private boolean hasPendingTexture = false;
perkj88518a22015-12-18 00:37:06 -080088 private volatile boolean isTextureInUse = false;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020089 private boolean isQuitting = false;
magjedd8ddb792016-03-17 03:13:43 -070090 // |pendingListener| is set in setListener() and the runnable is posted to the handler thread.
91 // setListener() is not allowed to be called again before stopListening(), so this is thread safe.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010092 @Nullable private OnTextureFrameAvailableListener pendingListener;
magjedd8ddb792016-03-17 03:13:43 -070093 final Runnable setListenerRunnable = new Runnable() {
94 @Override
95 public void run() {
96 Logging.d(TAG, "Setting listener to " + pendingListener);
97 listener = pendingListener;
98 pendingListener = null;
Magnus Jedvert181310f2016-05-23 16:26:47 +020099 // May have a pending frame from the previous capture session - drop it.
100 if (hasPendingTexture) {
101 // Calling updateTexImage() is neccessary in order to receive new frames.
102 updateTexImage();
103 hasPendingTexture = false;
104 }
magjedd8ddb792016-03-17 03:13:43 -0700105 }
106 };
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200107
magjed9e69dfd2016-02-26 07:45:44 -0800108 private SurfaceTextureHelper(EglBase.Context sharedContext, Handler handler) {
Alex Glazneva5b62d92015-10-12 13:56:20 -0700109 if (handler.getLooper().getThread() != Thread.currentThread()) {
Magnus Jedvert747c1bc2015-10-12 09:27:48 +0200110 throw new IllegalStateException("SurfaceTextureHelper must be created on the handler thread");
perkj1b33da12015-10-05 21:06:41 +0200111 }
Magnus Jedvert747c1bc2015-10-12 09:27:48 +0200112 this.handler = handler;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200113
nisse03f80eb2015-12-07 01:17:16 -0800114 eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER);
magjed2aa84262016-05-09 08:28:45 -0700115 try {
116 // Both these statements have been observed to fail on rare occasions, see BUG=webrtc:5682.
117 eglBase.createDummyPbufferSurface();
118 eglBase.makeCurrent();
119 } catch (RuntimeException e) {
120 // Clean up before rethrowing the exception.
121 eglBase.release();
122 handler.getLooper().quit();
123 throw e;
124 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200125
126 oesTextureId = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
127 surfaceTexture = new SurfaceTexture(oesTextureId);
Jonathan Yu22384412017-08-16 13:25:45 -0700128 setOnFrameAvailableListener(surfaceTexture, (SurfaceTexture st) -> {
129 hasPendingTexture = true;
130 tryDeliverTextureFrame();
131 }, handler);
132 }
133
134 @TargetApi(21)
135 private static void setOnFrameAvailableListener(SurfaceTexture surfaceTexture,
136 SurfaceTexture.OnFrameAvailableListener listener, Handler handler) {
137 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
138 surfaceTexture.setOnFrameAvailableListener(listener, handler);
139 } else {
140 // The documentation states that the listener will be called on an arbitrary thread, but in
141 // pratice, it is always the thread on which the SurfaceTexture was constructed. There are
142 // assertions in place in case this ever changes. For API >= 21, we use the new API to
143 // explicitly specify the handler.
144 surfaceTexture.setOnFrameAvailableListener(listener);
145 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200146 }
147
148 /**
magjed81e8e372016-03-03 02:11:44 -0800149 * Start to stream textures to the given |listener|. If you need to change listener, you need to
150 * call stopListening() first.
Per3e9eb4b2015-09-28 10:52:22 +0200151 */
magjed81e8e372016-03-03 02:11:44 -0800152 public void startListening(final OnTextureFrameAvailableListener listener) {
magjedd8ddb792016-03-17 03:13:43 -0700153 if (this.listener != null || this.pendingListener != null) {
Per3e9eb4b2015-09-28 10:52:22 +0200154 throw new IllegalStateException("SurfaceTextureHelper listener has already been set.");
155 }
magjedd8ddb792016-03-17 03:13:43 -0700156 this.pendingListener = listener;
157 handler.post(setListenerRunnable);
Per3e9eb4b2015-09-28 10:52:22 +0200158 }
159
160 /**
magjed81e8e372016-03-03 02:11:44 -0800161 * Stop listening. The listener set in startListening() is guaranteded to not receive any more
nissea44e72c2016-05-27 00:27:59 -0700162 * onTextureFrameAvailable() callbacks after this function returns.
magjed81e8e372016-03-03 02:11:44 -0800163 */
164 public void stopListening() {
magjedd8ddb792016-03-17 03:13:43 -0700165 Logging.d(TAG, "stopListening()");
166 handler.removeCallbacks(setListenerRunnable);
nissea44e72c2016-05-27 00:27:59 -0700167 ThreadUtils.invokeAtFrontUninterruptibly(handler, new Runnable() {
168 @Override
169 public void run() {
170 listener = null;
171 pendingListener = null;
172 }
173 });
magjed81e8e372016-03-03 02:11:44 -0800174 }
175
176 /**
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200177 * Retrieve the underlying SurfaceTexture. The SurfaceTexture should be passed in to a video
178 * producer such as a camera or decoder.
179 */
180 public SurfaceTexture getSurfaceTexture() {
181 return surfaceTexture;
182 }
183
184 /**
magjed9e69dfd2016-02-26 07:45:44 -0800185 * Retrieve the handler that calls onTextureFrameAvailable(). This handler is valid until
magjed81e8e372016-03-03 02:11:44 -0800186 * dispose() is called.
magjed9e69dfd2016-02-26 07:45:44 -0800187 */
188 public Handler getHandler() {
189 return handler;
190 }
191
192 /**
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200193 * Call this function to signal that you are done with the frame received in
194 * onTextureFrameAvailable(). Only one texture frame can be in flight at once, so you must call
195 * this function in order to receive a new frame.
196 */
197 public void returnTextureFrame() {
198 handler.post(new Runnable() {
sakalb6760f92016-09-29 04:12:44 -0700199 @Override
200 public void run() {
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200201 isTextureInUse = false;
202 if (isQuitting) {
203 release();
204 } else {
205 tryDeliverTextureFrame();
206 }
207 }
208 });
209 }
210
perkj88518a22015-12-18 00:37:06 -0800211 public boolean isTextureInUse() {
212 return isTextureInUse;
213 }
214
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200215 /**
magjed9e69dfd2016-02-26 07:45:44 -0800216 * Call disconnect() to stop receiving frames. OpenGL resources are released and the handler is
217 * stopped when the texture frame has been returned by a call to returnTextureFrame(). You are
218 * guaranteed to not receive any more onTextureFrameAvailable() after this function returns.
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200219 */
magjed81e8e372016-03-03 02:11:44 -0800220 public void dispose() {
magjedd8ddb792016-03-17 03:13:43 -0700221 Logging.d(TAG, "dispose()");
nissea44e72c2016-05-27 00:27:59 -0700222 ThreadUtils.invokeAtFrontUninterruptibly(handler, new Runnable() {
223 @Override
224 public void run() {
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200225 isQuitting = true;
226 if (!isTextureInUse) {
227 release();
228 }
229 }
230 });
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200231 }
232
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200233 /**
Magnus Jedvertc040dae2017-11-17 16:50:17 +0100234 * Posts to the correct thread to convert |textureBuffer| to I420.
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200235 */
236 public VideoFrame.I420Buffer textureToYuv(final TextureBuffer textureBuffer) {
Magnus Jedvert1d270f82018-04-16 16:28:29 +0200237 return ThreadUtils.invokeAtFrontUninterruptibly(
238 handler, () -> yuvConverter.convert(textureBuffer));
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200239 }
240
Magnus Jedvert181310f2016-05-23 16:26:47 +0200241 private void updateTexImage() {
242 // SurfaceTexture.updateTexImage apparently can compete and deadlock with eglSwapBuffers,
243 // as observed on Nexus 5. Therefore, synchronize it with the EGL functions.
244 // See https://bugs.chromium.org/p/webrtc/issues/detail?id=5702 for more info.
245 synchronized (EglBase.lock) {
246 surfaceTexture.updateTexImage();
247 }
248 }
249
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200250 private void tryDeliverTextureFrame() {
Alex Glazneva5b62d92015-10-12 13:56:20 -0700251 if (handler.getLooper().getThread() != Thread.currentThread()) {
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200252 throw new IllegalStateException("Wrong thread.");
253 }
magjed81e8e372016-03-03 02:11:44 -0800254 if (isQuitting || !hasPendingTexture || isTextureInUse || listener == null) {
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200255 return;
256 }
257 isTextureInUse = true;
258 hasPendingTexture = false;
259
Magnus Jedvert181310f2016-05-23 16:26:47 +0200260 updateTexImage();
perkj1b33da12015-10-05 21:06:41 +0200261
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200262 final float[] transformMatrix = new float[16];
263 surfaceTexture.getTransformMatrix(transformMatrix);
sakalbee4ff82017-05-02 08:33:52 -0700264 final long timestampNs = surfaceTexture.getTimestamp();
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200265 listener.onTextureFrameAvailable(oesTextureId, transformMatrix, timestampNs);
266 }
267
268 private void release() {
Alex Glazneva5b62d92015-10-12 13:56:20 -0700269 if (handler.getLooper().getThread() != Thread.currentThread()) {
Magnus Jedvert1ab271c2015-09-28 11:05:44 +0200270 throw new IllegalStateException("Wrong thread.");
271 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200272 if (isTextureInUse || !isQuitting) {
273 throw new IllegalStateException("Unexpected release.");
274 }
Magnus Jedvert1d270f82018-04-16 16:28:29 +0200275 yuvConverter.release();
Magnus Jedvert1ab271c2015-09-28 11:05:44 +0200276 GLES20.glDeleteTextures(1, new int[] {oesTextureId}, 0);
277 surfaceTexture.release();
278 eglBase.release();
perkj92375592015-11-24 03:03:13 -0800279 handler.getLooper().quit();
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200280 }
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700281
282 /**
283 * Creates a VideoFrame buffer backed by this helper's texture. The |width| and |height| should
284 * match the dimensions of the data placed in the texture. The correct |transformMatrix| may be
285 * obtained from callbacks to OnTextureFrameAvailableListener.
286 *
287 * The returned TextureBuffer holds a reference to the SurfaceTextureHelper that created it. The
288 * buffer calls returnTextureFrame() when it is released.
289 */
sakal836f60c2017-07-28 07:12:23 -0700290 public TextureBuffer createTextureBuffer(int width, int height, Matrix transformMatrix) {
Magnus Jedvert1d270f82018-04-16 16:28:29 +0200291 return new TextureBufferImpl(width, height, TextureBuffer.Type.OES, oesTextureId,
292 transformMatrix, handler, yuvConverter, this ::returnTextureFrame);
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700293 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200294}