Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 1 | /* |
kjellander | b24317b | 2016-02-10 07:54:43 -0800 | [diff] [blame] | 2 | * Copyright 2015 The WebRTC project authors. All Rights Reserved. |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 3 | * |
kjellander | b24317b | 2016-02-10 07:54:43 -0800 | [diff] [blame] | 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. |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 9 | */ |
| 10 | |
| 11 | package org.webrtc; |
| 12 | |
Jonathan Yu | 2238441 | 2017-08-16 13:25:45 -0700 | [diff] [blame] | 13 | import android.annotation.TargetApi; |
sakal | 836f60c | 2017-07-28 07:12:23 -0700 | [diff] [blame] | 14 | import android.graphics.Matrix; |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 15 | import android.graphics.SurfaceTexture; |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 16 | import android.opengl.GLES11Ext; |
| 17 | import android.opengl.GLES20; |
| 18 | import android.os.Build; |
| 19 | import android.os.Handler; |
| 20 | import android.os.HandlerThread; |
nisse | c490e01 | 2015-12-10 06:23:33 -0800 | [diff] [blame] | 21 | import java.nio.ByteBuffer; |
Magnus Jedvert | 747c1bc | 2015-10-12 09:27:48 +0200 | [diff] [blame] | 22 | import java.util.concurrent.Callable; |
Magnus Jedvert | 6199a37 | 2017-11-14 13:03:08 +0100 | [diff] [blame] | 23 | import org.webrtc.EglBase; |
Bjorn Mellem | 8fb2361 | 2017-07-18 11:33:39 -0700 | [diff] [blame] | 24 | import org.webrtc.VideoFrame.TextureBuffer; |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 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 returnTextureFrame() when done with |
| 29 | * the frame. Only one texture frame can be in flight at once, so returnTextureFrame() must be |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 30 | * 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. |
Per | 3e9eb4b | 2015-09-28 10:52:22 +0200 | [diff] [blame] | 32 | * 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 Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 35 | */ |
skvlad | ed6e077 | 2016-11-30 14:40:18 -0800 | [diff] [blame] | 36 | public class SurfaceTextureHelper { |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 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 |
Jonathan Yu | 2238441 | 2017-08-16 13:25:45 -0700 | [diff] [blame] | 40 | * 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 Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 42 | */ |
| 43 | public interface OnTextureFrameAvailableListener { |
| 44 | abstract void onTextureFrameAvailable( |
| 45 | int oesTextureId, float[] transformMatrix, long timestampNs); |
| 46 | } |
| 47 | |
Magnus Jedvert | 747c1bc | 2015-10-12 09:27:48 +0200 | [diff] [blame] | 48 | /** |
magjed | 9e69dfd | 2016-02-26 07:45:44 -0800 | [diff] [blame] | 49 | * Construct a new SurfaceTextureHelper sharing OpenGL resources with |sharedContext|. A dedicated |
magjed | 2aa8426 | 2016-05-09 08:28:45 -0700 | [diff] [blame] | 50 | * 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 Jedvert | 747c1bc | 2015-10-12 09:27:48 +0200 | [diff] [blame] | 52 | */ |
Magnus Jedvert | 6199a37 | 2017-11-14 13:03:08 +0100 | [diff] [blame] | 53 | @CalledByNative |
magjed | 82b750b | 2016-03-31 00:54:15 -0700 | [diff] [blame] | 54 | public static SurfaceTextureHelper create( |
| 55 | final String threadName, final EglBase.Context sharedContext) { |
| 56 | final HandlerThread thread = new HandlerThread(threadName); |
magjed | 9e69dfd | 2016-02-26 07:45:44 -0800 | [diff] [blame] | 57 | thread.start(); |
| 58 | final Handler handler = new Handler(thread.getLooper()); |
| 59 | |
Magnus Jedvert | 747c1bc | 2015-10-12 09:27:48 +0200 | [diff] [blame] | 60 | // 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. |
nisse | a44e72c | 2016-05-27 00:27:59 -0700 | [diff] [blame] | 64 | return ThreadUtils.invokeAtFrontUninterruptibly(handler, new Callable<SurfaceTextureHelper>() { |
magjed | 9e69dfd | 2016-02-26 07:45:44 -0800 | [diff] [blame] | 65 | @Override |
| 66 | public SurfaceTextureHelper call() { |
magjed | 2aa8426 | 2016-05-09 08:28:45 -0700 | [diff] [blame] | 67 | try { |
| 68 | return new SurfaceTextureHelper(sharedContext, handler); |
| 69 | } catch (RuntimeException e) { |
| 70 | Logging.e(TAG, threadName + " create failure", e); |
| 71 | return null; |
| 72 | } |
Magnus Jedvert | 747c1bc | 2015-10-12 09:27:48 +0200 | [diff] [blame] | 73 | } |
| 74 | }); |
| 75 | } |
| 76 | |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 77 | private final Handler handler; |
| 78 | private final EglBase eglBase; |
| 79 | private final SurfaceTexture surfaceTexture; |
| 80 | private final int oesTextureId; |
nisse | c490e01 | 2015-12-10 06:23:33 -0800 | [diff] [blame] | 81 | private YuvConverter yuvConverter; |
| 82 | |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 83 | // These variables are only accessed from the |handler| thread. |
Per | 3e9eb4b | 2015-09-28 10:52:22 +0200 | [diff] [blame] | 84 | private OnTextureFrameAvailableListener listener; |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 85 | // The possible states of this class. |
| 86 | private boolean hasPendingTexture = false; |
perkj | 88518a2 | 2015-12-18 00:37:06 -0800 | [diff] [blame] | 87 | private volatile boolean isTextureInUse = false; |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 88 | private boolean isQuitting = false; |
magjed | d8ddb79 | 2016-03-17 03:13:43 -0700 | [diff] [blame] | 89 | // |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 Jedvert | 181310f | 2016-05-23 16:26:47 +0200 | [diff] [blame] | 98 | // 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 | } |
magjed | d8ddb79 | 2016-03-17 03:13:43 -0700 | [diff] [blame] | 104 | } |
| 105 | }; |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 106 | |
magjed | 9e69dfd | 2016-02-26 07:45:44 -0800 | [diff] [blame] | 107 | private SurfaceTextureHelper(EglBase.Context sharedContext, Handler handler) { |
Alex Glaznev | a5b62d9 | 2015-10-12 13:56:20 -0700 | [diff] [blame] | 108 | if (handler.getLooper().getThread() != Thread.currentThread()) { |
Magnus Jedvert | 747c1bc | 2015-10-12 09:27:48 +0200 | [diff] [blame] | 109 | throw new IllegalStateException("SurfaceTextureHelper must be created on the handler thread"); |
perkj | 1b33da1 | 2015-10-05 21:06:41 +0200 | [diff] [blame] | 110 | } |
Magnus Jedvert | 747c1bc | 2015-10-12 09:27:48 +0200 | [diff] [blame] | 111 | this.handler = handler; |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 112 | |
nisse | 03f80eb | 2015-12-07 01:17:16 -0800 | [diff] [blame] | 113 | eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER); |
magjed | 2aa8426 | 2016-05-09 08:28:45 -0700 | [diff] [blame] | 114 | 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 Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 124 | |
| 125 | oesTextureId = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES); |
| 126 | surfaceTexture = new SurfaceTexture(oesTextureId); |
Jonathan Yu | 2238441 | 2017-08-16 13:25:45 -0700 | [diff] [blame] | 127 | 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 Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 145 | } |
| 146 | |
| 147 | /** |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 148 | * Start to stream textures to the given |listener|. If you need to change listener, you need to |
| 149 | * call stopListening() first. |
Per | 3e9eb4b | 2015-09-28 10:52:22 +0200 | [diff] [blame] | 150 | */ |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 151 | public void startListening(final OnTextureFrameAvailableListener listener) { |
magjed | d8ddb79 | 2016-03-17 03:13:43 -0700 | [diff] [blame] | 152 | if (this.listener != null || this.pendingListener != null) { |
Per | 3e9eb4b | 2015-09-28 10:52:22 +0200 | [diff] [blame] | 153 | throw new IllegalStateException("SurfaceTextureHelper listener has already been set."); |
| 154 | } |
magjed | d8ddb79 | 2016-03-17 03:13:43 -0700 | [diff] [blame] | 155 | this.pendingListener = listener; |
| 156 | handler.post(setListenerRunnable); |
Per | 3e9eb4b | 2015-09-28 10:52:22 +0200 | [diff] [blame] | 157 | } |
| 158 | |
| 159 | /** |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 160 | * Stop listening. The listener set in startListening() is guaranteded to not receive any more |
nisse | a44e72c | 2016-05-27 00:27:59 -0700 | [diff] [blame] | 161 | * onTextureFrameAvailable() callbacks after this function returns. |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 162 | */ |
| 163 | public void stopListening() { |
magjed | d8ddb79 | 2016-03-17 03:13:43 -0700 | [diff] [blame] | 164 | Logging.d(TAG, "stopListening()"); |
| 165 | handler.removeCallbacks(setListenerRunnable); |
nisse | a44e72c | 2016-05-27 00:27:59 -0700 | [diff] [blame] | 166 | ThreadUtils.invokeAtFrontUninterruptibly(handler, new Runnable() { |
| 167 | @Override |
| 168 | public void run() { |
| 169 | listener = null; |
| 170 | pendingListener = null; |
| 171 | } |
| 172 | }); |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 173 | } |
| 174 | |
| 175 | /** |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 176 | * 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 | /** |
magjed | 9e69dfd | 2016-02-26 07:45:44 -0800 | [diff] [blame] | 184 | * Retrieve the handler that calls onTextureFrameAvailable(). This handler is valid until |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 185 | * dispose() is called. |
magjed | 9e69dfd | 2016-02-26 07:45:44 -0800 | [diff] [blame] | 186 | */ |
| 187 | public Handler getHandler() { |
| 188 | return handler; |
| 189 | } |
| 190 | |
| 191 | /** |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 192 | * 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 Jedvert | 6199a37 | 2017-11-14 13:03:08 +0100 | [diff] [blame] | 196 | @CalledByNative |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 197 | public void returnTextureFrame() { |
| 198 | handler.post(new Runnable() { |
sakal | b6760f9 | 2016-09-29 04:12:44 -0700 | [diff] [blame] | 199 | @Override |
| 200 | public void run() { |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 201 | isTextureInUse = false; |
| 202 | if (isQuitting) { |
| 203 | release(); |
| 204 | } else { |
| 205 | tryDeliverTextureFrame(); |
| 206 | } |
| 207 | } |
| 208 | }); |
| 209 | } |
| 210 | |
perkj | 88518a2 | 2015-12-18 00:37:06 -0800 | [diff] [blame] | 211 | public boolean isTextureInUse() { |
| 212 | return isTextureInUse; |
| 213 | } |
| 214 | |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 215 | /** |
magjed | 9e69dfd | 2016-02-26 07:45:44 -0800 | [diff] [blame] | 216 | * 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 Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 219 | */ |
Magnus Jedvert | 6199a37 | 2017-11-14 13:03:08 +0100 | [diff] [blame] | 220 | @CalledByNative |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 221 | public void dispose() { |
magjed | d8ddb79 | 2016-03-17 03:13:43 -0700 | [diff] [blame] | 222 | Logging.d(TAG, "dispose()"); |
nisse | a44e72c | 2016-05-27 00:27:59 -0700 | [diff] [blame] | 223 | ThreadUtils.invokeAtFrontUninterruptibly(handler, new Runnable() { |
| 224 | @Override |
| 225 | public void run() { |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 226 | isQuitting = true; |
| 227 | if (!isTextureInUse) { |
| 228 | release(); |
| 229 | } |
| 230 | } |
| 231 | }); |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 232 | } |
| 233 | |
Sami Kalliomäki | cb98b11 | 2017-10-16 11:20:26 +0200 | [diff] [blame] | 234 | /** Deprecated, use textureToYuv. */ |
| 235 | @Deprecated |
| 236 | @SuppressWarnings("deprecation") // yuvConverter.convert is deprecated |
Magnus Jedvert | 9060eb1 | 2017-12-12 12:52:54 +0100 | [diff] [blame] | 237 | @CalledByNative |
Sami Kalliomäki | cb98b11 | 2017-10-16 11:20:26 +0200 | [diff] [blame] | 238 | void textureToYUV(final ByteBuffer buf, final int width, final int height, final int stride, |
| 239 | final int textureId, final float[] transformMatrix) { |
magjed | 1cb4823 | 2016-10-20 03:19:16 -0700 | [diff] [blame] | 240 | if (textureId != oesTextureId) { |
nisse | c490e01 | 2015-12-10 06:23:33 -0800 | [diff] [blame] | 241 | throw new IllegalStateException("textureToByteBuffer called with unexpected textureId"); |
magjed | 1cb4823 | 2016-10-20 03:19:16 -0700 | [diff] [blame] | 242 | } |
nisse | c490e01 | 2015-12-10 06:23:33 -0800 | [diff] [blame] | 243 | |
magjed | 1cb4823 | 2016-10-20 03:19:16 -0700 | [diff] [blame] | 244 | 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 | }); |
nisse | c490e01 | 2015-12-10 06:23:33 -0800 | [diff] [blame] | 253 | } |
| 254 | |
Sami Kalliomäki | cb98b11 | 2017-10-16 11:20:26 +0200 | [diff] [blame] | 255 | /** |
Magnus Jedvert | c040dae | 2017-11-17 16:50:17 +0100 | [diff] [blame] | 256 | * Posts to the correct thread to convert |textureBuffer| to I420. |
Sami Kalliomäki | cb98b11 | 2017-10-16 11:20:26 +0200 | [diff] [blame] | 257 | */ |
| 258 | public VideoFrame.I420Buffer textureToYuv(final TextureBuffer textureBuffer) { |
Sami Kalliomäki | cb98b11 | 2017-10-16 11:20:26 +0200 | [diff] [blame] | 259 | 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 Jedvert | 181310f | 2016-05-23 16:26:47 +0200 | [diff] [blame] | 269 | 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 Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 278 | private void tryDeliverTextureFrame() { |
Alex Glaznev | a5b62d9 | 2015-10-12 13:56:20 -0700 | [diff] [blame] | 279 | if (handler.getLooper().getThread() != Thread.currentThread()) { |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 280 | throw new IllegalStateException("Wrong thread."); |
| 281 | } |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 282 | if (isQuitting || !hasPendingTexture || isTextureInUse || listener == null) { |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 283 | return; |
| 284 | } |
| 285 | isTextureInUse = true; |
| 286 | hasPendingTexture = false; |
| 287 | |
Magnus Jedvert | 181310f | 2016-05-23 16:26:47 +0200 | [diff] [blame] | 288 | updateTexImage(); |
perkj | 1b33da1 | 2015-10-05 21:06:41 +0200 | [diff] [blame] | 289 | |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 290 | final float[] transformMatrix = new float[16]; |
| 291 | surfaceTexture.getTransformMatrix(transformMatrix); |
sakal | bee4ff8 | 2017-05-02 08:33:52 -0700 | [diff] [blame] | 292 | final long timestampNs = surfaceTexture.getTimestamp(); |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 293 | listener.onTextureFrameAvailable(oesTextureId, transformMatrix, timestampNs); |
| 294 | } |
| 295 | |
| 296 | private void release() { |
Alex Glaznev | a5b62d9 | 2015-10-12 13:56:20 -0700 | [diff] [blame] | 297 | if (handler.getLooper().getThread() != Thread.currentThread()) { |
Magnus Jedvert | 1ab271c | 2015-09-28 11:05:44 +0200 | [diff] [blame] | 298 | throw new IllegalStateException("Wrong thread."); |
| 299 | } |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 300 | if (isTextureInUse || !isQuitting) { |
| 301 | throw new IllegalStateException("Unexpected release."); |
| 302 | } |
magjed | 1cb4823 | 2016-10-20 03:19:16 -0700 | [diff] [blame] | 303 | if (yuvConverter != null) { |
| 304 | yuvConverter.release(); |
nisse | c490e01 | 2015-12-10 06:23:33 -0800 | [diff] [blame] | 305 | } |
Magnus Jedvert | 1ab271c | 2015-09-28 11:05:44 +0200 | [diff] [blame] | 306 | GLES20.glDeleteTextures(1, new int[] {oesTextureId}, 0); |
| 307 | surfaceTexture.release(); |
| 308 | eglBase.release(); |
perkj | 9237559 | 2015-11-24 03:03:13 -0800 | [diff] [blame] | 309 | handler.getLooper().quit(); |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 310 | } |
Bjorn Mellem | 8fb2361 | 2017-07-18 11:33:39 -0700 | [diff] [blame] | 311 | |
| 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 | */ |
sakal | 836f60c | 2017-07-28 07:12:23 -0700 | [diff] [blame] | 320 | 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 Mellem | 8fb2361 | 2017-07-18 11:33:39 -0700 | [diff] [blame] | 328 | } |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 329 | } |