OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright 2015 The WebRTC project authors. All Rights Reserved. | |
3 * | |
4 * 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. | |
9 */ | |
10 | |
11 package org.webrtc; | |
12 | |
13 import android.graphics.SurfaceTexture; | |
14 import android.opengl.GLES11Ext; | |
15 import android.opengl.GLES20; | |
16 import android.os.Build; | |
17 import android.os.Handler; | |
18 import android.os.HandlerThread; | |
19 import android.os.SystemClock; | |
20 | |
21 import java.nio.ByteBuffer; | |
22 import java.nio.FloatBuffer; | |
23 import java.util.concurrent.Callable; | |
24 import java.util.concurrent.TimeUnit; | |
25 | |
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 returnTextureFram
e() when done with | |
29 * the frame. Only one texture frame can be in flight at once, so returnTextureF
rame() must be | |
30 * called in order to receive a new frame. Call stopListening() to stop receivei
ng new frames. Call | |
31 * dispose to release all resources once the texture frame is returned. | |
32 * Note that there is a C++ counter part of this class that optionally can be us
ed. It is used for | |
33 * wrapping texture frames into webrtc::VideoFrames and also handles calling ret
urnTextureFrame() | |
34 * when the webrtc::VideoFrame is no longer used. | |
35 */ | |
36 public class SurfaceTextureHelper { | |
37 private static final String TAG = "SurfaceTextureHelper"; | |
38 /** | |
39 * Callback interface for being notified that a new texture frame is available
. The calls will be | |
40 * made on a dedicated thread with a bound EGLContext. The thread will be the
same throughout the | |
41 * lifetime of the SurfaceTextureHelper instance, but different from the threa
d calling the | |
42 * SurfaceTextureHelper constructor. The callee is not allowed to make another
EGLContext current | |
43 * on the calling thread. | |
44 */ | |
45 public interface OnTextureFrameAvailableListener { | |
46 abstract void onTextureFrameAvailable( | |
47 int oesTextureId, float[] transformMatrix, long timestampNs); | |
48 } | |
49 | |
50 /** | |
51 * Construct a new SurfaceTextureHelper sharing OpenGL resources with |sharedC
ontext|. A dedicated | |
52 * thread and handler is created for handling the SurfaceTexture. May return n
ull if EGL fails to | |
53 * initialize a pixel buffer surface and make it current. | |
54 */ | |
55 public static SurfaceTextureHelper create( | |
56 final String threadName, final EglBase.Context sharedContext) { | |
57 final HandlerThread thread = new HandlerThread(threadName); | |
58 thread.start(); | |
59 final Handler handler = new Handler(thread.getLooper()); | |
60 | |
61 // The onFrameAvailable() callback will be executed on the SurfaceTexture ct
or thread. See: | |
62 // http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.andr
oid/android/5.1.1_r1/android/graphics/SurfaceTexture.java#195. | |
63 // Therefore, in order to control the callback thread on API lvl < 21, the S
urfaceTextureHelper | |
64 // is constructed on the |handler| thread. | |
65 return ThreadUtils.invokeAtFrontUninterruptibly(handler, new Callable<Surfac
eTextureHelper>() { | |
66 @Override | |
67 public SurfaceTextureHelper call() { | |
68 try { | |
69 return new SurfaceTextureHelper(sharedContext, handler); | |
70 } catch (RuntimeException e) { | |
71 Logging.e(TAG, threadName + " create failure", e); | |
72 return null; | |
73 } | |
74 } | |
75 }); | |
76 } | |
77 | |
78 private final Handler handler; | |
79 private final EglBase eglBase; | |
80 private final SurfaceTexture surfaceTexture; | |
81 private final int oesTextureId; | |
82 private YuvConverter yuvConverter; | |
83 | |
84 // These variables are only accessed from the |handler| thread. | |
85 private OnTextureFrameAvailableListener listener; | |
86 // The possible states of this class. | |
87 private boolean hasPendingTexture = false; | |
88 private volatile boolean isTextureInUse = false; | |
89 private boolean isQuitting = false; | |
90 // |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. | |
92 private OnTextureFrameAvailableListener pendingListener; | |
93 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; | |
99 // 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 } | |
105 } | |
106 }; | |
107 | |
108 private SurfaceTextureHelper(EglBase.Context sharedContext, Handler handler) { | |
109 if (handler.getLooper().getThread() != Thread.currentThread()) { | |
110 throw new IllegalStateException("SurfaceTextureHelper must be created on t
he handler thread"); | |
111 } | |
112 this.handler = handler; | |
113 | |
114 eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER); | |
115 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 } | |
125 | |
126 oesTextureId = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES); | |
127 surfaceTexture = new SurfaceTexture(oesTextureId); | |
128 surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailab
leListener() { | |
129 @Override | |
130 public void onFrameAvailable(SurfaceTexture surfaceTexture) { | |
131 hasPendingTexture = true; | |
132 tryDeliverTextureFrame(); | |
133 } | |
134 }); | |
135 } | |
136 | |
137 /** | |
138 * Start to stream textures to the given |listener|. If you need to change lis
tener, you need to | |
139 * call stopListening() first. | |
140 */ | |
141 public void startListening(final OnTextureFrameAvailableListener listener) { | |
142 if (this.listener != null || this.pendingListener != null) { | |
143 throw new IllegalStateException("SurfaceTextureHelper listener has already
been set."); | |
144 } | |
145 this.pendingListener = listener; | |
146 handler.post(setListenerRunnable); | |
147 } | |
148 | |
149 /** | |
150 * Stop listening. The listener set in startListening() is guaranteded to not
receive any more | |
151 * onTextureFrameAvailable() callbacks after this function returns. | |
152 */ | |
153 public void stopListening() { | |
154 Logging.d(TAG, "stopListening()"); | |
155 handler.removeCallbacks(setListenerRunnable); | |
156 ThreadUtils.invokeAtFrontUninterruptibly(handler, new Runnable() { | |
157 @Override | |
158 public void run() { | |
159 listener = null; | |
160 pendingListener = null; | |
161 } | |
162 }); | |
163 } | |
164 | |
165 /** | |
166 * Retrieve the underlying SurfaceTexture. The SurfaceTexture should be passed
in to a video | |
167 * producer such as a camera or decoder. | |
168 */ | |
169 public SurfaceTexture getSurfaceTexture() { | |
170 return surfaceTexture; | |
171 } | |
172 | |
173 /** | |
174 * Retrieve the handler that calls onTextureFrameAvailable(). This handler is
valid until | |
175 * dispose() is called. | |
176 */ | |
177 public Handler getHandler() { | |
178 return handler; | |
179 } | |
180 | |
181 /** | |
182 * Call this function to signal that you are done with the frame received in | |
183 * onTextureFrameAvailable(). Only one texture frame can be in flight at once,
so you must call | |
184 * this function in order to receive a new frame. | |
185 */ | |
186 public void returnTextureFrame() { | |
187 handler.post(new Runnable() { | |
188 @Override | |
189 public void run() { | |
190 isTextureInUse = false; | |
191 if (isQuitting) { | |
192 release(); | |
193 } else { | |
194 tryDeliverTextureFrame(); | |
195 } | |
196 } | |
197 }); | |
198 } | |
199 | |
200 public boolean isTextureInUse() { | |
201 return isTextureInUse; | |
202 } | |
203 | |
204 /** | |
205 * Call disconnect() to stop receiving frames. OpenGL resources are released a
nd the handler is | |
206 * stopped when the texture frame has been returned by a call to returnTexture
Frame(). You are | |
207 * guaranteed to not receive any more onTextureFrameAvailable() after this fun
ction returns. | |
208 */ | |
209 public void dispose() { | |
210 Logging.d(TAG, "dispose()"); | |
211 ThreadUtils.invokeAtFrontUninterruptibly(handler, new Runnable() { | |
212 @Override | |
213 public void run() { | |
214 isQuitting = true; | |
215 if (!isTextureInUse) { | |
216 release(); | |
217 } | |
218 } | |
219 }); | |
220 } | |
221 | |
222 public void textureToYUV(final ByteBuffer buf, final int width, final int heig
ht, | |
223 final int stride, final int textureId, final float[] transformMatrix) { | |
224 if (textureId != oesTextureId) { | |
225 throw new IllegalStateException("textureToByteBuffer called with unexpecte
d textureId"); | |
226 } | |
227 | |
228 ThreadUtils.invokeAtFrontUninterruptibly(handler, new Runnable() { | |
229 @Override | |
230 public void run() { | |
231 if (yuvConverter == null) { | |
232 yuvConverter = new YuvConverter(); | |
233 } | |
234 yuvConverter.convert(buf, width, height, stride, textureId, transformMat
rix); | |
235 } | |
236 }); | |
237 } | |
238 | |
239 private void updateTexImage() { | |
240 // SurfaceTexture.updateTexImage apparently can compete and deadlock with eg
lSwapBuffers, | |
241 // as observed on Nexus 5. Therefore, synchronize it with the EGL functions. | |
242 // See https://bugs.chromium.org/p/webrtc/issues/detail?id=5702 for more inf
o. | |
243 synchronized (EglBase.lock) { | |
244 surfaceTexture.updateTexImage(); | |
245 } | |
246 } | |
247 | |
248 private void tryDeliverTextureFrame() { | |
249 if (handler.getLooper().getThread() != Thread.currentThread()) { | |
250 throw new IllegalStateException("Wrong thread."); | |
251 } | |
252 if (isQuitting || !hasPendingTexture || isTextureInUse || listener == null)
{ | |
253 return; | |
254 } | |
255 isTextureInUse = true; | |
256 hasPendingTexture = false; | |
257 | |
258 updateTexImage(); | |
259 | |
260 final float[] transformMatrix = new float[16]; | |
261 surfaceTexture.getTransformMatrix(transformMatrix); | |
262 final long timestampNs = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_C
REAM_SANDWICH) | |
263 ? surfaceTexture.getTimestamp() | |
264 : TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime()); | |
265 listener.onTextureFrameAvailable(oesTextureId, transformMatrix, timestampNs)
; | |
266 } | |
267 | |
268 private void release() { | |
269 if (handler.getLooper().getThread() != Thread.currentThread()) { | |
270 throw new IllegalStateException("Wrong thread."); | |
271 } | |
272 if (isTextureInUse || !isQuitting) { | |
273 throw new IllegalStateException("Unexpected release."); | |
274 } | |
275 if (yuvConverter != null) { | |
276 yuvConverter.release(); | |
277 } | |
278 GLES20.glDeleteTextures(1, new int[] {oesTextureId}, 0); | |
279 surfaceTexture.release(); | |
280 eglBase.release(); | |
281 handler.getLooper().quit(); | |
282 } | |
283 } | |
OLD | NEW |