| 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 |