blob: 3aaa4e2846f783ce6fac7272f1c0a549e1155ba8 [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;
Magnus Jedvert6199a372017-11-14 13:03:08 +010023import org.webrtc.EglBase;
Bjorn Mellem8fb23612017-07-18 11:33:39 -070024import org.webrtc.VideoFrame.TextureBuffer;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020025
26/**
27 * Helper class to create and synchronize access to a SurfaceTexture. The caller will get notified
28 * of new frames in onTextureFrameAvailable(), and should call returnTextureFrame() when done with
29 * the frame. Only one texture frame can be in flight at once, so returnTextureFrame() must be
magjed81e8e372016-03-03 02:11:44 -080030 * called in order to receive a new frame. Call stopListening() to stop receiveing new frames. Call
31 * dispose to release all resources once the texture frame is returned.
Per3e9eb4b2015-09-28 10:52:22 +020032 * Note that there is a C++ counter part of this class that optionally can be used. It is used for
33 * wrapping texture frames into webrtc::VideoFrames and also handles calling returnTextureFrame()
34 * when the webrtc::VideoFrame is no longer used.
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020035 */
skvladed6e0772016-11-30 14:40:18 -080036public class SurfaceTextureHelper {
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020037 private static final String TAG = "SurfaceTextureHelper";
38 /**
39 * Callback interface for being notified that a new texture frame is available. The calls will be
Jonathan Yu22384412017-08-16 13:25:45 -070040 * made on the SurfaceTextureHelper handler thread, with a bound EGLContext. The callee is not
41 * allowed to make another EGLContext current on the calling thread.
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020042 */
43 public interface OnTextureFrameAvailableListener {
44 abstract void onTextureFrameAvailable(
45 int oesTextureId, float[] transformMatrix, long timestampNs);
46 }
47
Magnus Jedvert747c1bc2015-10-12 09:27:48 +020048 /**
magjed9e69dfd2016-02-26 07:45:44 -080049 * Construct a new SurfaceTextureHelper sharing OpenGL resources with |sharedContext|. A dedicated
magjed2aa84262016-05-09 08:28:45 -070050 * thread and handler is created for handling the SurfaceTexture. May return null if EGL fails to
51 * initialize a pixel buffer surface and make it current.
Magnus Jedvert747c1bc2015-10-12 09:27:48 +020052 */
Magnus Jedvert6199a372017-11-14 13:03:08 +010053 @CalledByNative
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>() {
magjed9e69dfd2016-02-26 07:45:44 -080065 @Override
66 public SurfaceTextureHelper call() {
magjed2aa84262016-05-09 08:28:45 -070067 try {
68 return new SurfaceTextureHelper(sharedContext, handler);
69 } catch (RuntimeException e) {
70 Logging.e(TAG, threadName + " create failure", e);
71 return null;
72 }
Magnus Jedvert747c1bc2015-10-12 09:27:48 +020073 }
74 });
75 }
76
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020077 private final Handler handler;
78 private final EglBase eglBase;
79 private final SurfaceTexture surfaceTexture;
80 private final int oesTextureId;
nissec490e012015-12-10 06:23:33 -080081 private YuvConverter yuvConverter;
82
magjed81e8e372016-03-03 02:11:44 -080083 // These variables are only accessed from the |handler| thread.
Per3e9eb4b2015-09-28 10:52:22 +020084 private OnTextureFrameAvailableListener listener;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020085 // The possible states of this class.
86 private boolean hasPendingTexture = false;
perkj88518a22015-12-18 00:37:06 -080087 private volatile boolean isTextureInUse = false;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020088 private boolean isQuitting = false;
magjedd8ddb792016-03-17 03:13:43 -070089 // |pendingListener| is set in setListener() and the runnable is posted to the handler thread.
90 // setListener() is not allowed to be called again before stopListening(), so this is thread safe.
91 private OnTextureFrameAvailableListener pendingListener;
92 final Runnable setListenerRunnable = new Runnable() {
93 @Override
94 public void run() {
95 Logging.d(TAG, "Setting listener to " + pendingListener);
96 listener = pendingListener;
97 pendingListener = null;
Magnus Jedvert181310f2016-05-23 16:26:47 +020098 // May have a pending frame from the previous capture session - drop it.
99 if (hasPendingTexture) {
100 // Calling updateTexImage() is neccessary in order to receive new frames.
101 updateTexImage();
102 hasPendingTexture = false;
103 }
magjedd8ddb792016-03-17 03:13:43 -0700104 }
105 };
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200106
magjed9e69dfd2016-02-26 07:45:44 -0800107 private SurfaceTextureHelper(EglBase.Context sharedContext, Handler handler) {
Alex Glazneva5b62d92015-10-12 13:56:20 -0700108 if (handler.getLooper().getThread() != Thread.currentThread()) {
Magnus Jedvert747c1bc2015-10-12 09:27:48 +0200109 throw new IllegalStateException("SurfaceTextureHelper must be created on the handler thread");
perkj1b33da12015-10-05 21:06:41 +0200110 }
Magnus Jedvert747c1bc2015-10-12 09:27:48 +0200111 this.handler = handler;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200112
nisse03f80eb2015-12-07 01:17:16 -0800113 eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER);
magjed2aa84262016-05-09 08:28:45 -0700114 try {
115 // Both these statements have been observed to fail on rare occasions, see BUG=webrtc:5682.
116 eglBase.createDummyPbufferSurface();
117 eglBase.makeCurrent();
118 } catch (RuntimeException e) {
119 // Clean up before rethrowing the exception.
120 eglBase.release();
121 handler.getLooper().quit();
122 throw e;
123 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200124
125 oesTextureId = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
126 surfaceTexture = new SurfaceTexture(oesTextureId);
Jonathan Yu22384412017-08-16 13:25:45 -0700127 setOnFrameAvailableListener(surfaceTexture, (SurfaceTexture st) -> {
128 hasPendingTexture = true;
129 tryDeliverTextureFrame();
130 }, handler);
131 }
132
133 @TargetApi(21)
134 private static void setOnFrameAvailableListener(SurfaceTexture surfaceTexture,
135 SurfaceTexture.OnFrameAvailableListener listener, Handler handler) {
136 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
137 surfaceTexture.setOnFrameAvailableListener(listener, handler);
138 } else {
139 // The documentation states that the listener will be called on an arbitrary thread, but in
140 // pratice, it is always the thread on which the SurfaceTexture was constructed. There are
141 // assertions in place in case this ever changes. For API >= 21, we use the new API to
142 // explicitly specify the handler.
143 surfaceTexture.setOnFrameAvailableListener(listener);
144 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200145 }
146
147 /**
magjed81e8e372016-03-03 02:11:44 -0800148 * Start to stream textures to the given |listener|. If you need to change listener, you need to
149 * call stopListening() first.
Per3e9eb4b2015-09-28 10:52:22 +0200150 */
magjed81e8e372016-03-03 02:11:44 -0800151 public void startListening(final OnTextureFrameAvailableListener listener) {
magjedd8ddb792016-03-17 03:13:43 -0700152 if (this.listener != null || this.pendingListener != null) {
Per3e9eb4b2015-09-28 10:52:22 +0200153 throw new IllegalStateException("SurfaceTextureHelper listener has already been set.");
154 }
magjedd8ddb792016-03-17 03:13:43 -0700155 this.pendingListener = listener;
156 handler.post(setListenerRunnable);
Per3e9eb4b2015-09-28 10:52:22 +0200157 }
158
159 /**
magjed81e8e372016-03-03 02:11:44 -0800160 * Stop listening. The listener set in startListening() is guaranteded to not receive any more
nissea44e72c2016-05-27 00:27:59 -0700161 * onTextureFrameAvailable() callbacks after this function returns.
magjed81e8e372016-03-03 02:11:44 -0800162 */
163 public void stopListening() {
magjedd8ddb792016-03-17 03:13:43 -0700164 Logging.d(TAG, "stopListening()");
165 handler.removeCallbacks(setListenerRunnable);
nissea44e72c2016-05-27 00:27:59 -0700166 ThreadUtils.invokeAtFrontUninterruptibly(handler, new Runnable() {
167 @Override
168 public void run() {
169 listener = null;
170 pendingListener = null;
171 }
172 });
magjed81e8e372016-03-03 02:11:44 -0800173 }
174
175 /**
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200176 * Retrieve the underlying SurfaceTexture. The SurfaceTexture should be passed in to a video
177 * producer such as a camera or decoder.
178 */
179 public SurfaceTexture getSurfaceTexture() {
180 return surfaceTexture;
181 }
182
183 /**
magjed9e69dfd2016-02-26 07:45:44 -0800184 * Retrieve the handler that calls onTextureFrameAvailable(). This handler is valid until
magjed81e8e372016-03-03 02:11:44 -0800185 * dispose() is called.
magjed9e69dfd2016-02-26 07:45:44 -0800186 */
187 public Handler getHandler() {
188 return handler;
189 }
190
191 /**
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200192 * Call this function to signal that you are done with the frame received in
193 * onTextureFrameAvailable(). Only one texture frame can be in flight at once, so you must call
194 * this function in order to receive a new frame.
195 */
Magnus Jedvert6199a372017-11-14 13:03:08 +0100196 @CalledByNative
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200197 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 */
Magnus Jedvert6199a372017-11-14 13:03:08 +0100220 @CalledByNative
magjed81e8e372016-03-03 02:11:44 -0800221 public void dispose() {
magjedd8ddb792016-03-17 03:13:43 -0700222 Logging.d(TAG, "dispose()");
nissea44e72c2016-05-27 00:27:59 -0700223 ThreadUtils.invokeAtFrontUninterruptibly(handler, new Runnable() {
224 @Override
225 public void run() {
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200226 isQuitting = true;
227 if (!isTextureInUse) {
228 release();
229 }
230 }
231 });
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200232 }
233
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200234 /** Deprecated, use textureToYuv. */
235 @Deprecated
236 @SuppressWarnings("deprecation") // yuvConverter.convert is deprecated
Magnus Jedvert9060eb12017-12-12 12:52:54 +0100237 @CalledByNative
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200238 void textureToYUV(final ByteBuffer buf, final int width, final int height, final int stride,
239 final int textureId, final float[] transformMatrix) {
magjed1cb48232016-10-20 03:19:16 -0700240 if (textureId != oesTextureId) {
nissec490e012015-12-10 06:23:33 -0800241 throw new IllegalStateException("textureToByteBuffer called with unexpected textureId");
magjed1cb48232016-10-20 03:19:16 -0700242 }
nissec490e012015-12-10 06:23:33 -0800243
magjed1cb48232016-10-20 03:19:16 -0700244 ThreadUtils.invokeAtFrontUninterruptibly(handler, new Runnable() {
245 @Override
246 public void run() {
247 if (yuvConverter == null) {
248 yuvConverter = new YuvConverter();
249 }
250 yuvConverter.convert(buf, width, height, stride, textureId, transformMatrix);
251 }
252 });
nissec490e012015-12-10 06:23:33 -0800253 }
254
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200255 /**
Magnus Jedvertc040dae2017-11-17 16:50:17 +0100256 * Posts to the correct thread to convert |textureBuffer| to I420.
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200257 */
258 public VideoFrame.I420Buffer textureToYuv(final TextureBuffer textureBuffer) {
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200259 final VideoFrame.I420Buffer[] result = new VideoFrame.I420Buffer[1];
260 ThreadUtils.invokeAtFrontUninterruptibly(handler, () -> {
261 if (yuvConverter == null) {
262 yuvConverter = new YuvConverter();
263 }
264 result[0] = yuvConverter.convert(textureBuffer);
265 });
266 return result[0];
267 }
268
Magnus Jedvert181310f2016-05-23 16:26:47 +0200269 private void updateTexImage() {
270 // SurfaceTexture.updateTexImage apparently can compete and deadlock with eglSwapBuffers,
271 // as observed on Nexus 5. Therefore, synchronize it with the EGL functions.
272 // See https://bugs.chromium.org/p/webrtc/issues/detail?id=5702 for more info.
273 synchronized (EglBase.lock) {
274 surfaceTexture.updateTexImage();
275 }
276 }
277
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200278 private void tryDeliverTextureFrame() {
Alex Glazneva5b62d92015-10-12 13:56:20 -0700279 if (handler.getLooper().getThread() != Thread.currentThread()) {
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200280 throw new IllegalStateException("Wrong thread.");
281 }
magjed81e8e372016-03-03 02:11:44 -0800282 if (isQuitting || !hasPendingTexture || isTextureInUse || listener == null) {
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200283 return;
284 }
285 isTextureInUse = true;
286 hasPendingTexture = false;
287
Magnus Jedvert181310f2016-05-23 16:26:47 +0200288 updateTexImage();
perkj1b33da12015-10-05 21:06:41 +0200289
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200290 final float[] transformMatrix = new float[16];
291 surfaceTexture.getTransformMatrix(transformMatrix);
sakalbee4ff82017-05-02 08:33:52 -0700292 final long timestampNs = surfaceTexture.getTimestamp();
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200293 listener.onTextureFrameAvailable(oesTextureId, transformMatrix, timestampNs);
294 }
295
296 private void release() {
Alex Glazneva5b62d92015-10-12 13:56:20 -0700297 if (handler.getLooper().getThread() != Thread.currentThread()) {
Magnus Jedvert1ab271c2015-09-28 11:05:44 +0200298 throw new IllegalStateException("Wrong thread.");
299 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200300 if (isTextureInUse || !isQuitting) {
301 throw new IllegalStateException("Unexpected release.");
302 }
magjed1cb48232016-10-20 03:19:16 -0700303 if (yuvConverter != null) {
304 yuvConverter.release();
nissec490e012015-12-10 06:23:33 -0800305 }
Magnus Jedvert1ab271c2015-09-28 11:05:44 +0200306 GLES20.glDeleteTextures(1, new int[] {oesTextureId}, 0);
307 surfaceTexture.release();
308 eglBase.release();
perkj92375592015-11-24 03:03:13 -0800309 handler.getLooper().quit();
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200310 }
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700311
312 /**
313 * Creates a VideoFrame buffer backed by this helper's texture. The |width| and |height| should
314 * match the dimensions of the data placed in the texture. The correct |transformMatrix| may be
315 * obtained from callbacks to OnTextureFrameAvailableListener.
316 *
317 * The returned TextureBuffer holds a reference to the SurfaceTextureHelper that created it. The
318 * buffer calls returnTextureFrame() when it is released.
319 */
sakal836f60c2017-07-28 07:12:23 -0700320 public TextureBuffer createTextureBuffer(int width, int height, Matrix transformMatrix) {
321 return new TextureBufferImpl(
322 width, height, TextureBuffer.Type.OES, oesTextureId, transformMatrix, this, new Runnable() {
323 @Override
324 public void run() {
325 returnTextureFrame();
326 }
327 });
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700328 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200329}