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; |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 14 | import android.graphics.SurfaceTexture; |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 15 | import android.opengl.GLES11Ext; |
| 16 | import android.opengl.GLES20; |
| 17 | import android.os.Build; |
| 18 | import android.os.Handler; |
| 19 | import android.os.HandlerThread; |
Artem Titarenko | 69540f4 | 2018-12-10 12:30:46 +0100 | [diff] [blame] | 20 | import android.support.annotation.Nullable; |
Magnus Jedvert | 747c1bc | 2015-10-12 09:27:48 +0200 | [diff] [blame] | 21 | import java.util.concurrent.Callable; |
Sami Kalliomäki | 066b42f | 2019-08-30 11:20:42 +0200 | [diff] [blame^] | 22 | import org.webrtc.EglBase.Context; |
| 23 | import org.webrtc.TextureBufferImpl.RefCountMonitor; |
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 | /** |
Magnus Jedvert | 80e7a7f | 2018-07-06 11:15:13 +0200 | [diff] [blame] | 27 | * Helper class for using a SurfaceTexture to create WebRTC VideoFrames. In order to create WebRTC |
| 28 | * VideoFrames, render onto the SurfaceTexture. The frames will be delivered to the listener. Only |
| 29 | * one texture frame can be in flight at once, so the frame must be released in order to receive a |
| 30 | * new frame. Call stopListening() to stop receiveing new frames. Call dispose to release all |
| 31 | * resources once the texture frame is released. |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 32 | */ |
skvlad | ed6e077 | 2016-11-30 14:40:18 -0800 | [diff] [blame] | 33 | public class SurfaceTextureHelper { |
Sami Kalliomäki | 066b42f | 2019-08-30 11:20:42 +0200 | [diff] [blame^] | 34 | /** |
| 35 | * Interface for monitoring texture buffers created from this SurfaceTexture. Since only one |
| 36 | * texture buffer can exist at a time, this can be used to monitor for stuck frames. |
| 37 | */ |
| 38 | public interface FrameRefMonitor { |
| 39 | /** A new frame was created. New frames start with ref count of 1. */ |
| 40 | void onNewBuffer(TextureBuffer textureBuffer); |
| 41 | /** Ref count of the frame was incremented by the calling thread. */ |
| 42 | void onRetainBuffer(TextureBuffer textureBuffer); |
| 43 | /** Ref count of the frame was decremented by the calling thread. */ |
| 44 | void onReleaseBuffer(TextureBuffer textureBuffer); |
| 45 | /** Frame was destroyed (ref count reached 0). */ |
| 46 | void onDestroyBuffer(TextureBuffer textureBuffer); |
| 47 | } |
| 48 | |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 49 | private static final String TAG = "SurfaceTextureHelper"; |
| 50 | /** |
magjed | 9e69dfd | 2016-02-26 07:45:44 -0800 | [diff] [blame] | 51 | * Construct a new SurfaceTextureHelper sharing OpenGL resources with |sharedContext|. A dedicated |
magjed | 2aa8426 | 2016-05-09 08:28:45 -0700 | [diff] [blame] | 52 | * thread and handler is created for handling the SurfaceTexture. May return null if EGL fails to |
Magnus Jedvert | 9514071 | 2018-11-15 12:07:32 +0100 | [diff] [blame] | 53 | * initialize a pixel buffer surface and make it current. If alignTimestamps is true, the frame |
| 54 | * timestamps will be aligned to rtc::TimeNanos(). If frame timestamps are aligned to |
| 55 | * rtc::TimeNanos() there is no need for aligning timestamps again in |
| 56 | * PeerConnectionFactory.createVideoSource(). This makes the timestamps more accurate and |
| 57 | * closer to actual creation time. |
Magnus Jedvert | 747c1bc | 2015-10-12 09:27:48 +0200 | [diff] [blame] | 58 | */ |
Åsa Persson | f2889bb | 2019-02-25 16:20:01 +0100 | [diff] [blame] | 59 | public static SurfaceTextureHelper create(final String threadName, |
Sami Kalliomäki | 066b42f | 2019-08-30 11:20:42 +0200 | [diff] [blame^] | 60 | final EglBase.Context sharedContext, boolean alignTimestamps, final YuvConverter yuvConverter, |
| 61 | FrameRefMonitor frameRefMonitor) { |
magjed | 82b750b | 2016-03-31 00:54:15 -0700 | [diff] [blame] | 62 | final HandlerThread thread = new HandlerThread(threadName); |
magjed | 9e69dfd | 2016-02-26 07:45:44 -0800 | [diff] [blame] | 63 | thread.start(); |
| 64 | final Handler handler = new Handler(thread.getLooper()); |
| 65 | |
Magnus Jedvert | 747c1bc | 2015-10-12 09:27:48 +0200 | [diff] [blame] | 66 | // The onFrameAvailable() callback will be executed on the SurfaceTexture ctor thread. See: |
| 67 | // http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/graphics/SurfaceTexture.java#195. |
| 68 | // Therefore, in order to control the callback thread on API lvl < 21, the SurfaceTextureHelper |
| 69 | // is constructed on the |handler| thread. |
nisse | a44e72c | 2016-05-27 00:27:59 -0700 | [diff] [blame] | 70 | return ThreadUtils.invokeAtFrontUninterruptibly(handler, new Callable<SurfaceTextureHelper>() { |
Sami Kalliomäki | e7592d8 | 2018-03-22 13:32:44 +0100 | [diff] [blame] | 71 | @Nullable |
magjed | 9e69dfd | 2016-02-26 07:45:44 -0800 | [diff] [blame] | 72 | @Override |
| 73 | public SurfaceTextureHelper call() { |
magjed | 2aa8426 | 2016-05-09 08:28:45 -0700 | [diff] [blame] | 74 | try { |
Sami Kalliomäki | 066b42f | 2019-08-30 11:20:42 +0200 | [diff] [blame^] | 75 | return new SurfaceTextureHelper( |
| 76 | sharedContext, handler, alignTimestamps, yuvConverter, frameRefMonitor); |
magjed | 2aa8426 | 2016-05-09 08:28:45 -0700 | [diff] [blame] | 77 | } catch (RuntimeException e) { |
| 78 | Logging.e(TAG, threadName + " create failure", e); |
| 79 | return null; |
| 80 | } |
Magnus Jedvert | 747c1bc | 2015-10-12 09:27:48 +0200 | [diff] [blame] | 81 | } |
| 82 | }); |
| 83 | } |
| 84 | |
Magnus Jedvert | 9514071 | 2018-11-15 12:07:32 +0100 | [diff] [blame] | 85 | /** |
Åsa Persson | f2889bb | 2019-02-25 16:20:01 +0100 | [diff] [blame] | 86 | * Same as above with alignTimestamps set to false and yuvConverter set to new YuvConverter. |
Magnus Jedvert | 9514071 | 2018-11-15 12:07:32 +0100 | [diff] [blame] | 87 | * |
Sami Kalliomäki | 066b42f | 2019-08-30 11:20:42 +0200 | [diff] [blame^] | 88 | * @see #create(String, EglBase.Context, boolean, YuvConverter, FrameRefMonitor) |
Magnus Jedvert | 9514071 | 2018-11-15 12:07:32 +0100 | [diff] [blame] | 89 | */ |
| 90 | public static SurfaceTextureHelper create( |
| 91 | final String threadName, final EglBase.Context sharedContext) { |
Sami Kalliomäki | 066b42f | 2019-08-30 11:20:42 +0200 | [diff] [blame^] | 92 | return create(threadName, sharedContext, /* alignTimestamps= */ false, new YuvConverter(), |
| 93 | /*frameRefMonitor=*/null); |
Åsa Persson | f2889bb | 2019-02-25 16:20:01 +0100 | [diff] [blame] | 94 | } |
| 95 | |
| 96 | /** |
| 97 | * Same as above with yuvConverter set to new YuvConverter. |
| 98 | * |
Sami Kalliomäki | 066b42f | 2019-08-30 11:20:42 +0200 | [diff] [blame^] | 99 | * @see #create(String, EglBase.Context, boolean, YuvConverter, FrameRefMonitor) |
Åsa Persson | f2889bb | 2019-02-25 16:20:01 +0100 | [diff] [blame] | 100 | */ |
| 101 | public static SurfaceTextureHelper create( |
| 102 | final String threadName, final EglBase.Context sharedContext, boolean alignTimestamps) { |
Sami Kalliomäki | 066b42f | 2019-08-30 11:20:42 +0200 | [diff] [blame^] | 103 | return create( |
| 104 | threadName, sharedContext, alignTimestamps, new YuvConverter(), /*frameRefMonitor=*/null); |
Magnus Jedvert | 9514071 | 2018-11-15 12:07:32 +0100 | [diff] [blame] | 105 | } |
| 106 | |
Sami Kalliomäki | 066b42f | 2019-08-30 11:20:42 +0200 | [diff] [blame^] | 107 | /** |
| 108 | * Create a SurfaceTextureHelper without frame ref monitor. |
| 109 | * |
| 110 | * @see #create(String, EglBase.Context, boolean, YuvConverter, FrameRefMonitor) |
| 111 | */ |
| 112 | public static SurfaceTextureHelper create(final String threadName, |
| 113 | final EglBase.Context sharedContext, boolean alignTimestamps, YuvConverter yuvConverter) { |
| 114 | return create( |
| 115 | threadName, sharedContext, alignTimestamps, yuvConverter, /*frameRefMonitor=*/null); |
| 116 | } |
| 117 | |
| 118 | private final RefCountMonitor textureRefCountMonitor = new RefCountMonitor() { |
| 119 | @Override |
| 120 | public void onRetain(TextureBufferImpl textureBuffer) { |
| 121 | if (frameRefMonitor != null) { |
| 122 | frameRefMonitor.onRetainBuffer(textureBuffer); |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | @Override |
| 127 | public void onRelease(TextureBufferImpl textureBuffer) { |
| 128 | if (frameRefMonitor != null) { |
| 129 | frameRefMonitor.onReleaseBuffer(textureBuffer); |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | @Override |
| 134 | public void onDestroy(TextureBufferImpl textureBuffer) { |
| 135 | returnTextureFrame(); |
| 136 | if (frameRefMonitor != null) { |
| 137 | frameRefMonitor.onDestroyBuffer(textureBuffer); |
| 138 | } |
| 139 | } |
| 140 | }; |
| 141 | |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 142 | private final Handler handler; |
| 143 | private final EglBase eglBase; |
| 144 | private final SurfaceTexture surfaceTexture; |
| 145 | private final int oesTextureId; |
Åsa Persson | f2889bb | 2019-02-25 16:20:01 +0100 | [diff] [blame] | 146 | private final YuvConverter yuvConverter; |
Magnus Jedvert | 9514071 | 2018-11-15 12:07:32 +0100 | [diff] [blame] | 147 | @Nullable private final TimestampAligner timestampAligner; |
Sami Kalliomäki | 066b42f | 2019-08-30 11:20:42 +0200 | [diff] [blame^] | 148 | private final FrameRefMonitor frameRefMonitor; |
nisse | c490e01 | 2015-12-10 06:23:33 -0800 | [diff] [blame] | 149 | |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 150 | // These variables are only accessed from the |handler| thread. |
Magnus Jedvert | 814f99c | 2018-08-03 11:15:14 +0200 | [diff] [blame] | 151 | @Nullable private VideoSink listener; |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 152 | // The possible states of this class. |
Sami Kalliomäki | 3d50a31 | 2018-09-11 11:11:47 +0200 | [diff] [blame] | 153 | private boolean hasPendingTexture; |
| 154 | private volatile boolean isTextureInUse; |
| 155 | private boolean isQuitting; |
Magnus Jedvert | 80e7a7f | 2018-07-06 11:15:13 +0200 | [diff] [blame] | 156 | private int frameRotation; |
| 157 | private int textureWidth; |
| 158 | private int textureHeight; |
magjed | d8ddb79 | 2016-03-17 03:13:43 -0700 | [diff] [blame] | 159 | // |pendingListener| is set in setListener() and the runnable is posted to the handler thread. |
| 160 | // setListener() is not allowed to be called again before stopListening(), so this is thread safe. |
Magnus Jedvert | 814f99c | 2018-08-03 11:15:14 +0200 | [diff] [blame] | 161 | @Nullable private VideoSink pendingListener; |
magjed | d8ddb79 | 2016-03-17 03:13:43 -0700 | [diff] [blame] | 162 | final Runnable setListenerRunnable = new Runnable() { |
| 163 | @Override |
| 164 | public void run() { |
| 165 | Logging.d(TAG, "Setting listener to " + pendingListener); |
| 166 | listener = pendingListener; |
| 167 | pendingListener = null; |
Magnus Jedvert | 181310f | 2016-05-23 16:26:47 +0200 | [diff] [blame] | 168 | // May have a pending frame from the previous capture session - drop it. |
| 169 | if (hasPendingTexture) { |
| 170 | // Calling updateTexImage() is neccessary in order to receive new frames. |
| 171 | updateTexImage(); |
| 172 | hasPendingTexture = false; |
| 173 | } |
magjed | d8ddb79 | 2016-03-17 03:13:43 -0700 | [diff] [blame] | 174 | } |
| 175 | }; |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 176 | |
Sami Kalliomäki | 066b42f | 2019-08-30 11:20:42 +0200 | [diff] [blame^] | 177 | private SurfaceTextureHelper(Context sharedContext, Handler handler, boolean alignTimestamps, |
| 178 | YuvConverter yuvConverter, FrameRefMonitor frameRefMonitor) { |
Alex Glaznev | a5b62d9 | 2015-10-12 13:56:20 -0700 | [diff] [blame] | 179 | if (handler.getLooper().getThread() != Thread.currentThread()) { |
Magnus Jedvert | 747c1bc | 2015-10-12 09:27:48 +0200 | [diff] [blame] | 180 | throw new IllegalStateException("SurfaceTextureHelper must be created on the handler thread"); |
perkj | 1b33da1 | 2015-10-05 21:06:41 +0200 | [diff] [blame] | 181 | } |
Magnus Jedvert | 747c1bc | 2015-10-12 09:27:48 +0200 | [diff] [blame] | 182 | this.handler = handler; |
Magnus Jedvert | 9514071 | 2018-11-15 12:07:32 +0100 | [diff] [blame] | 183 | this.timestampAligner = alignTimestamps ? new TimestampAligner() : null; |
Åsa Persson | f2889bb | 2019-02-25 16:20:01 +0100 | [diff] [blame] | 184 | this.yuvConverter = yuvConverter; |
Sami Kalliomäki | 066b42f | 2019-08-30 11:20:42 +0200 | [diff] [blame^] | 185 | this.frameRefMonitor = frameRefMonitor; |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 186 | |
nisse | 03f80eb | 2015-12-07 01:17:16 -0800 | [diff] [blame] | 187 | eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER); |
magjed | 2aa8426 | 2016-05-09 08:28:45 -0700 | [diff] [blame] | 188 | try { |
| 189 | // Both these statements have been observed to fail on rare occasions, see BUG=webrtc:5682. |
| 190 | eglBase.createDummyPbufferSurface(); |
| 191 | eglBase.makeCurrent(); |
| 192 | } catch (RuntimeException e) { |
| 193 | // Clean up before rethrowing the exception. |
| 194 | eglBase.release(); |
| 195 | handler.getLooper().quit(); |
| 196 | throw e; |
| 197 | } |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 198 | |
| 199 | oesTextureId = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES); |
| 200 | surfaceTexture = new SurfaceTexture(oesTextureId); |
Jonathan Yu | 2238441 | 2017-08-16 13:25:45 -0700 | [diff] [blame] | 201 | setOnFrameAvailableListener(surfaceTexture, (SurfaceTexture st) -> { |
| 202 | hasPendingTexture = true; |
| 203 | tryDeliverTextureFrame(); |
| 204 | }, handler); |
| 205 | } |
| 206 | |
| 207 | @TargetApi(21) |
| 208 | private static void setOnFrameAvailableListener(SurfaceTexture surfaceTexture, |
| 209 | SurfaceTexture.OnFrameAvailableListener listener, Handler handler) { |
| 210 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
| 211 | surfaceTexture.setOnFrameAvailableListener(listener, handler); |
| 212 | } else { |
| 213 | // The documentation states that the listener will be called on an arbitrary thread, but in |
| 214 | // pratice, it is always the thread on which the SurfaceTexture was constructed. There are |
| 215 | // assertions in place in case this ever changes. For API >= 21, we use the new API to |
| 216 | // explicitly specify the handler. |
| 217 | surfaceTexture.setOnFrameAvailableListener(listener); |
| 218 | } |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 219 | } |
| 220 | |
| 221 | /** |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 222 | * Start to stream textures to the given |listener|. If you need to change listener, you need to |
| 223 | * call stopListening() first. |
Magnus Jedvert | 80e7a7f | 2018-07-06 11:15:13 +0200 | [diff] [blame] | 224 | */ |
| 225 | public void startListening(final VideoSink listener) { |
magjed | d8ddb79 | 2016-03-17 03:13:43 -0700 | [diff] [blame] | 226 | if (this.listener != null || this.pendingListener != null) { |
Per | 3e9eb4b | 2015-09-28 10:52:22 +0200 | [diff] [blame] | 227 | throw new IllegalStateException("SurfaceTextureHelper listener has already been set."); |
| 228 | } |
magjed | d8ddb79 | 2016-03-17 03:13:43 -0700 | [diff] [blame] | 229 | this.pendingListener = listener; |
| 230 | handler.post(setListenerRunnable); |
Per | 3e9eb4b | 2015-09-28 10:52:22 +0200 | [diff] [blame] | 231 | } |
| 232 | |
| 233 | /** |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 234 | * Stop listening. The listener set in startListening() is guaranteded to not receive any more |
Magnus Jedvert | 814f99c | 2018-08-03 11:15:14 +0200 | [diff] [blame] | 235 | * onFrame() callbacks after this function returns. |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 236 | */ |
| 237 | public void stopListening() { |
magjed | d8ddb79 | 2016-03-17 03:13:43 -0700 | [diff] [blame] | 238 | Logging.d(TAG, "stopListening()"); |
| 239 | handler.removeCallbacks(setListenerRunnable); |
Magnus Jedvert | 80e7a7f | 2018-07-06 11:15:13 +0200 | [diff] [blame] | 240 | ThreadUtils.invokeAtFrontUninterruptibly(handler, () -> { |
| 241 | listener = null; |
| 242 | pendingListener = null; |
nisse | a44e72c | 2016-05-27 00:27:59 -0700 | [diff] [blame] | 243 | }); |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 244 | } |
| 245 | |
| 246 | /** |
Magnus Jedvert | 80e7a7f | 2018-07-06 11:15:13 +0200 | [diff] [blame] | 247 | * Use this function to set the texture size. Note, do not call setDefaultBufferSize() yourself |
| 248 | * since this class needs to be aware of the texture size. |
| 249 | */ |
| 250 | public void setTextureSize(int textureWidth, int textureHeight) { |
| 251 | if (textureWidth <= 0) { |
| 252 | throw new IllegalArgumentException("Texture width must be positive, but was " + textureWidth); |
| 253 | } |
| 254 | if (textureHeight <= 0) { |
| 255 | throw new IllegalArgumentException( |
| 256 | "Texture height must be positive, but was " + textureHeight); |
| 257 | } |
| 258 | surfaceTexture.setDefaultBufferSize(textureWidth, textureHeight); |
| 259 | handler.post(() -> { |
| 260 | this.textureWidth = textureWidth; |
| 261 | this.textureHeight = textureHeight; |
Magnus Jedvert | 31f18e1 | 2019-06-04 11:11:11 +0200 | [diff] [blame] | 262 | tryDeliverTextureFrame(); |
Magnus Jedvert | 80e7a7f | 2018-07-06 11:15:13 +0200 | [diff] [blame] | 263 | }); |
| 264 | } |
| 265 | |
| 266 | /** Set the rotation of the delivered frames. */ |
| 267 | public void setFrameRotation(int rotation) { |
| 268 | handler.post(() -> this.frameRotation = rotation); |
| 269 | } |
| 270 | |
| 271 | /** |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 272 | * Retrieve the underlying SurfaceTexture. The SurfaceTexture should be passed in to a video |
| 273 | * producer such as a camera or decoder. |
| 274 | */ |
| 275 | public SurfaceTexture getSurfaceTexture() { |
| 276 | return surfaceTexture; |
| 277 | } |
| 278 | |
Magnus Jedvert | 814f99c | 2018-08-03 11:15:14 +0200 | [diff] [blame] | 279 | /** Retrieve the handler that calls onFrame(). This handler is valid until dispose() is called. */ |
magjed | 9e69dfd | 2016-02-26 07:45:44 -0800 | [diff] [blame] | 280 | public Handler getHandler() { |
| 281 | return handler; |
| 282 | } |
| 283 | |
| 284 | /** |
Magnus Jedvert | 814f99c | 2018-08-03 11:15:14 +0200 | [diff] [blame] | 285 | * This function is called when the texture frame is released. Only one texture frame can be in |
| 286 | * flight at once, so this function must be called before a new frame is delivered. |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 287 | */ |
Magnus Jedvert | 814f99c | 2018-08-03 11:15:14 +0200 | [diff] [blame] | 288 | private void returnTextureFrame() { |
Magnus Jedvert | 80e7a7f | 2018-07-06 11:15:13 +0200 | [diff] [blame] | 289 | handler.post(() -> { |
| 290 | isTextureInUse = false; |
| 291 | if (isQuitting) { |
| 292 | release(); |
| 293 | } else { |
| 294 | tryDeliverTextureFrame(); |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 295 | } |
| 296 | }); |
| 297 | } |
| 298 | |
perkj | 88518a2 | 2015-12-18 00:37:06 -0800 | [diff] [blame] | 299 | public boolean isTextureInUse() { |
| 300 | return isTextureInUse; |
| 301 | } |
| 302 | |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 303 | /** |
magjed | 9e69dfd | 2016-02-26 07:45:44 -0800 | [diff] [blame] | 304 | * Call disconnect() to stop receiving frames. OpenGL resources are released and the handler is |
Magnus Jedvert | 814f99c | 2018-08-03 11:15:14 +0200 | [diff] [blame] | 305 | * stopped when the texture frame has been released. You are guaranteed to not receive any more |
| 306 | * onFrame() after this function returns. |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 307 | */ |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 308 | public void dispose() { |
magjed | d8ddb79 | 2016-03-17 03:13:43 -0700 | [diff] [blame] | 309 | Logging.d(TAG, "dispose()"); |
Magnus Jedvert | 80e7a7f | 2018-07-06 11:15:13 +0200 | [diff] [blame] | 310 | ThreadUtils.invokeAtFrontUninterruptibly(handler, () -> { |
| 311 | isQuitting = true; |
| 312 | if (!isTextureInUse) { |
| 313 | release(); |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 314 | } |
| 315 | }); |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 316 | } |
| 317 | |
Sami Kalliomäki | cb98b11 | 2017-10-16 11:20:26 +0200 | [diff] [blame] | 318 | /** |
Magnus Jedvert | c040dae | 2017-11-17 16:50:17 +0100 | [diff] [blame] | 319 | * Posts to the correct thread to convert |textureBuffer| to I420. |
Magnus Jedvert | 80e7a7f | 2018-07-06 11:15:13 +0200 | [diff] [blame] | 320 | * |
| 321 | * @deprecated Use toI420() instead. |
Sami Kalliomäki | cb98b11 | 2017-10-16 11:20:26 +0200 | [diff] [blame] | 322 | */ |
Magnus Jedvert | 80e7a7f | 2018-07-06 11:15:13 +0200 | [diff] [blame] | 323 | @Deprecated |
Sami Kalliomäki | cb98b11 | 2017-10-16 11:20:26 +0200 | [diff] [blame] | 324 | public VideoFrame.I420Buffer textureToYuv(final TextureBuffer textureBuffer) { |
Magnus Jedvert | 80e7a7f | 2018-07-06 11:15:13 +0200 | [diff] [blame] | 325 | return textureBuffer.toI420(); |
Sami Kalliomäki | cb98b11 | 2017-10-16 11:20:26 +0200 | [diff] [blame] | 326 | } |
| 327 | |
Magnus Jedvert | 181310f | 2016-05-23 16:26:47 +0200 | [diff] [blame] | 328 | private void updateTexImage() { |
| 329 | // SurfaceTexture.updateTexImage apparently can compete and deadlock with eglSwapBuffers, |
| 330 | // as observed on Nexus 5. Therefore, synchronize it with the EGL functions. |
| 331 | // See https://bugs.chromium.org/p/webrtc/issues/detail?id=5702 for more info. |
| 332 | synchronized (EglBase.lock) { |
| 333 | surfaceTexture.updateTexImage(); |
| 334 | } |
| 335 | } |
| 336 | |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 337 | private void tryDeliverTextureFrame() { |
Alex Glaznev | a5b62d9 | 2015-10-12 13:56:20 -0700 | [diff] [blame] | 338 | if (handler.getLooper().getThread() != Thread.currentThread()) { |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 339 | throw new IllegalStateException("Wrong thread."); |
| 340 | } |
magjed | 81e8e37 | 2016-03-03 02:11:44 -0800 | [diff] [blame] | 341 | if (isQuitting || !hasPendingTexture || isTextureInUse || listener == null) { |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 342 | return; |
| 343 | } |
Magnus Jedvert | 31f18e1 | 2019-06-04 11:11:11 +0200 | [diff] [blame] | 344 | if (textureWidth == 0 || textureHeight == 0) { |
| 345 | // Information about the resolution needs to be provided by a call to setTextureSize() before |
| 346 | // frames are produced. |
| 347 | Logging.w(TAG, "Texture size has not been set."); |
| 348 | return; |
| 349 | } |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 350 | isTextureInUse = true; |
| 351 | hasPendingTexture = false; |
| 352 | |
Magnus Jedvert | 181310f | 2016-05-23 16:26:47 +0200 | [diff] [blame] | 353 | updateTexImage(); |
perkj | 1b33da1 | 2015-10-05 21:06:41 +0200 | [diff] [blame] | 354 | |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 355 | final float[] transformMatrix = new float[16]; |
| 356 | surfaceTexture.getTransformMatrix(transformMatrix); |
Magnus Jedvert | 9514071 | 2018-11-15 12:07:32 +0100 | [diff] [blame] | 357 | long timestampNs = surfaceTexture.getTimestamp(); |
| 358 | if (timestampAligner != null) { |
| 359 | timestampNs = timestampAligner.translateTimestamp(timestampNs); |
| 360 | } |
Sami Kalliomäki | 066b42f | 2019-08-30 11:20:42 +0200 | [diff] [blame^] | 361 | final VideoFrame.TextureBuffer buffer = |
Magnus Jedvert | 814f99c | 2018-08-03 11:15:14 +0200 | [diff] [blame] | 362 | new TextureBufferImpl(textureWidth, textureHeight, TextureBuffer.Type.OES, oesTextureId, |
| 363 | RendererCommon.convertMatrixToAndroidGraphicsMatrix(transformMatrix), handler, |
Sami Kalliomäki | 066b42f | 2019-08-30 11:20:42 +0200 | [diff] [blame^] | 364 | yuvConverter, textureRefCountMonitor); |
| 365 | if (frameRefMonitor != null) { |
| 366 | frameRefMonitor.onNewBuffer(buffer); |
| 367 | } |
Magnus Jedvert | 814f99c | 2018-08-03 11:15:14 +0200 | [diff] [blame] | 368 | final VideoFrame frame = new VideoFrame(buffer, frameRotation, timestampNs); |
Sami Kalliomäki | 066b42f | 2019-08-30 11:20:42 +0200 | [diff] [blame^] | 369 | listener.onFrame(frame); |
Magnus Jedvert | 814f99c | 2018-08-03 11:15:14 +0200 | [diff] [blame] | 370 | frame.release(); |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 371 | } |
| 372 | |
| 373 | private void release() { |
Alex Glaznev | a5b62d9 | 2015-10-12 13:56:20 -0700 | [diff] [blame] | 374 | if (handler.getLooper().getThread() != Thread.currentThread()) { |
Magnus Jedvert | 1ab271c | 2015-09-28 11:05:44 +0200 | [diff] [blame] | 375 | throw new IllegalStateException("Wrong thread."); |
| 376 | } |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 377 | if (isTextureInUse || !isQuitting) { |
| 378 | throw new IllegalStateException("Unexpected release."); |
| 379 | } |
Magnus Jedvert | 1d270f8 | 2018-04-16 16:28:29 +0200 | [diff] [blame] | 380 | yuvConverter.release(); |
Magnus Jedvert | 1ab271c | 2015-09-28 11:05:44 +0200 | [diff] [blame] | 381 | GLES20.glDeleteTextures(1, new int[] {oesTextureId}, 0); |
| 382 | surfaceTexture.release(); |
| 383 | eglBase.release(); |
perkj | 9237559 | 2015-11-24 03:03:13 -0800 | [diff] [blame] | 384 | handler.getLooper().quit(); |
Magnus Jedvert | 9514071 | 2018-11-15 12:07:32 +0100 | [diff] [blame] | 385 | if (timestampAligner != null) { |
| 386 | timestampAligner.dispose(); |
| 387 | } |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 388 | } |
Magnus Jedvert | 4ae28a1 | 2015-09-15 09:44:07 +0200 | [diff] [blame] | 389 | } |