blob: 0dd45cfc938993040f965e0b16a88aa6f2c9edb0 [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;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020014import android.graphics.SurfaceTexture;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020015import android.opengl.GLES11Ext;
16import android.opengl.GLES20;
17import android.os.Build;
18import android.os.Handler;
19import android.os.HandlerThread;
Artem Titarenko69540f42018-12-10 12:30:46 +010020import android.support.annotation.Nullable;
Magnus Jedvert747c1bc2015-10-12 09:27:48 +020021import java.util.concurrent.Callable;
Sami Kalliomäki066b42f2019-08-30 11:20:42 +020022import org.webrtc.EglBase.Context;
23import org.webrtc.TextureBufferImpl.RefCountMonitor;
Bjorn Mellem8fb23612017-07-18 11:33:39 -070024import org.webrtc.VideoFrame.TextureBuffer;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +020025
26/**
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +020027 * 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 Jedvert4ae28a12015-09-15 09:44:07 +020032 */
skvladed6e0772016-11-30 14:40:18 -080033public class SurfaceTextureHelper {
Sami Kalliomäki066b42f2019-08-30 11:20:42 +020034 /**
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 Jedvert4ae28a12015-09-15 09:44:07 +020049 private static final String TAG = "SurfaceTextureHelper";
50 /**
magjed9e69dfd2016-02-26 07:45:44 -080051 * Construct a new SurfaceTextureHelper sharing OpenGL resources with |sharedContext|. A dedicated
magjed2aa84262016-05-09 08:28:45 -070052 * thread and handler is created for handling the SurfaceTexture. May return null if EGL fails to
Magnus Jedvert95140712018-11-15 12:07:32 +010053 * 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 Jedvert747c1bc2015-10-12 09:27:48 +020058 */
Åsa Perssonf2889bb2019-02-25 16:20:01 +010059 public static SurfaceTextureHelper create(final String threadName,
Sami Kalliomäki066b42f2019-08-30 11:20:42 +020060 final EglBase.Context sharedContext, boolean alignTimestamps, final YuvConverter yuvConverter,
61 FrameRefMonitor frameRefMonitor) {
magjed82b750b2016-03-31 00:54:15 -070062 final HandlerThread thread = new HandlerThread(threadName);
magjed9e69dfd2016-02-26 07:45:44 -080063 thread.start();
64 final Handler handler = new Handler(thread.getLooper());
65
Magnus Jedvert747c1bc2015-10-12 09:27:48 +020066 // 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.
nissea44e72c2016-05-27 00:27:59 -070070 return ThreadUtils.invokeAtFrontUninterruptibly(handler, new Callable<SurfaceTextureHelper>() {
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010071 @Nullable
magjed9e69dfd2016-02-26 07:45:44 -080072 @Override
73 public SurfaceTextureHelper call() {
magjed2aa84262016-05-09 08:28:45 -070074 try {
Sami Kalliomäki066b42f2019-08-30 11:20:42 +020075 return new SurfaceTextureHelper(
76 sharedContext, handler, alignTimestamps, yuvConverter, frameRefMonitor);
magjed2aa84262016-05-09 08:28:45 -070077 } catch (RuntimeException e) {
78 Logging.e(TAG, threadName + " create failure", e);
79 return null;
80 }
Magnus Jedvert747c1bc2015-10-12 09:27:48 +020081 }
82 });
83 }
84
Magnus Jedvert95140712018-11-15 12:07:32 +010085 /**
Åsa Perssonf2889bb2019-02-25 16:20:01 +010086 * Same as above with alignTimestamps set to false and yuvConverter set to new YuvConverter.
Magnus Jedvert95140712018-11-15 12:07:32 +010087 *
Sami Kalliomäki066b42f2019-08-30 11:20:42 +020088 * @see #create(String, EglBase.Context, boolean, YuvConverter, FrameRefMonitor)
Magnus Jedvert95140712018-11-15 12:07:32 +010089 */
90 public static SurfaceTextureHelper create(
91 final String threadName, final EglBase.Context sharedContext) {
Sami Kalliomäki066b42f2019-08-30 11:20:42 +020092 return create(threadName, sharedContext, /* alignTimestamps= */ false, new YuvConverter(),
93 /*frameRefMonitor=*/null);
Åsa Perssonf2889bb2019-02-25 16:20:01 +010094 }
95
96 /**
97 * Same as above with yuvConverter set to new YuvConverter.
98 *
Sami Kalliomäki066b42f2019-08-30 11:20:42 +020099 * @see #create(String, EglBase.Context, boolean, YuvConverter, FrameRefMonitor)
Åsa Perssonf2889bb2019-02-25 16:20:01 +0100100 */
101 public static SurfaceTextureHelper create(
102 final String threadName, final EglBase.Context sharedContext, boolean alignTimestamps) {
Sami Kalliomäki066b42f2019-08-30 11:20:42 +0200103 return create(
104 threadName, sharedContext, alignTimestamps, new YuvConverter(), /*frameRefMonitor=*/null);
Magnus Jedvert95140712018-11-15 12:07:32 +0100105 }
106
Sami Kalliomäki066b42f2019-08-30 11:20:42 +0200107 /**
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 Jedvert4ae28a12015-09-15 09:44:07 +0200142 private final Handler handler;
143 private final EglBase eglBase;
144 private final SurfaceTexture surfaceTexture;
145 private final int oesTextureId;
Åsa Perssonf2889bb2019-02-25 16:20:01 +0100146 private final YuvConverter yuvConverter;
Magnus Jedvert95140712018-11-15 12:07:32 +0100147 @Nullable private final TimestampAligner timestampAligner;
Sami Kalliomäki066b42f2019-08-30 11:20:42 +0200148 private final FrameRefMonitor frameRefMonitor;
nissec490e012015-12-10 06:23:33 -0800149
magjed81e8e372016-03-03 02:11:44 -0800150 // These variables are only accessed from the |handler| thread.
Magnus Jedvert814f99c2018-08-03 11:15:14 +0200151 @Nullable private VideoSink listener;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200152 // The possible states of this class.
Sami Kalliomäki3d50a312018-09-11 11:11:47 +0200153 private boolean hasPendingTexture;
154 private volatile boolean isTextureInUse;
155 private boolean isQuitting;
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200156 private int frameRotation;
157 private int textureWidth;
158 private int textureHeight;
magjedd8ddb792016-03-17 03:13:43 -0700159 // |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 Jedvert814f99c2018-08-03 11:15:14 +0200161 @Nullable private VideoSink pendingListener;
magjedd8ddb792016-03-17 03:13:43 -0700162 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 Jedvert181310f2016-05-23 16:26:47 +0200168 // 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 }
magjedd8ddb792016-03-17 03:13:43 -0700174 }
175 };
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200176
Sami Kalliomäki066b42f2019-08-30 11:20:42 +0200177 private SurfaceTextureHelper(Context sharedContext, Handler handler, boolean alignTimestamps,
178 YuvConverter yuvConverter, FrameRefMonitor frameRefMonitor) {
Alex Glazneva5b62d92015-10-12 13:56:20 -0700179 if (handler.getLooper().getThread() != Thread.currentThread()) {
Magnus Jedvert747c1bc2015-10-12 09:27:48 +0200180 throw new IllegalStateException("SurfaceTextureHelper must be created on the handler thread");
perkj1b33da12015-10-05 21:06:41 +0200181 }
Magnus Jedvert747c1bc2015-10-12 09:27:48 +0200182 this.handler = handler;
Magnus Jedvert95140712018-11-15 12:07:32 +0100183 this.timestampAligner = alignTimestamps ? new TimestampAligner() : null;
Åsa Perssonf2889bb2019-02-25 16:20:01 +0100184 this.yuvConverter = yuvConverter;
Sami Kalliomäki066b42f2019-08-30 11:20:42 +0200185 this.frameRefMonitor = frameRefMonitor;
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200186
nisse03f80eb2015-12-07 01:17:16 -0800187 eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER);
magjed2aa84262016-05-09 08:28:45 -0700188 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 Jedvert4ae28a12015-09-15 09:44:07 +0200198
199 oesTextureId = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
200 surfaceTexture = new SurfaceTexture(oesTextureId);
Jonathan Yu22384412017-08-16 13:25:45 -0700201 setOnFrameAvailableListener(surfaceTexture, (SurfaceTexture st) -> {
Sami Kalliomäki9d9d10c2020-08-14 13:46:04 +0200202 if (hasPendingTexture) {
203 Logging.d(TAG, "A frame is already pending, dropping frame.");
204 }
205
Jonathan Yu22384412017-08-16 13:25:45 -0700206 hasPendingTexture = true;
207 tryDeliverTextureFrame();
208 }, handler);
209 }
210
211 @TargetApi(21)
212 private static void setOnFrameAvailableListener(SurfaceTexture surfaceTexture,
213 SurfaceTexture.OnFrameAvailableListener listener, Handler handler) {
214 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
215 surfaceTexture.setOnFrameAvailableListener(listener, handler);
216 } else {
217 // The documentation states that the listener will be called on an arbitrary thread, but in
218 // pratice, it is always the thread on which the SurfaceTexture was constructed. There are
219 // assertions in place in case this ever changes. For API >= 21, we use the new API to
220 // explicitly specify the handler.
221 surfaceTexture.setOnFrameAvailableListener(listener);
222 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200223 }
224
225 /**
magjed81e8e372016-03-03 02:11:44 -0800226 * Start to stream textures to the given |listener|. If you need to change listener, you need to
227 * call stopListening() first.
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200228 */
229 public void startListening(final VideoSink listener) {
magjedd8ddb792016-03-17 03:13:43 -0700230 if (this.listener != null || this.pendingListener != null) {
Per3e9eb4b2015-09-28 10:52:22 +0200231 throw new IllegalStateException("SurfaceTextureHelper listener has already been set.");
232 }
magjedd8ddb792016-03-17 03:13:43 -0700233 this.pendingListener = listener;
234 handler.post(setListenerRunnable);
Per3e9eb4b2015-09-28 10:52:22 +0200235 }
236
237 /**
magjed81e8e372016-03-03 02:11:44 -0800238 * Stop listening. The listener set in startListening() is guaranteded to not receive any more
Magnus Jedvert814f99c2018-08-03 11:15:14 +0200239 * onFrame() callbacks after this function returns.
magjed81e8e372016-03-03 02:11:44 -0800240 */
241 public void stopListening() {
magjedd8ddb792016-03-17 03:13:43 -0700242 Logging.d(TAG, "stopListening()");
243 handler.removeCallbacks(setListenerRunnable);
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200244 ThreadUtils.invokeAtFrontUninterruptibly(handler, () -> {
245 listener = null;
246 pendingListener = null;
nissea44e72c2016-05-27 00:27:59 -0700247 });
magjed81e8e372016-03-03 02:11:44 -0800248 }
249
250 /**
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200251 * Use this function to set the texture size. Note, do not call setDefaultBufferSize() yourself
252 * since this class needs to be aware of the texture size.
253 */
254 public void setTextureSize(int textureWidth, int textureHeight) {
255 if (textureWidth <= 0) {
256 throw new IllegalArgumentException("Texture width must be positive, but was " + textureWidth);
257 }
258 if (textureHeight <= 0) {
259 throw new IllegalArgumentException(
260 "Texture height must be positive, but was " + textureHeight);
261 }
262 surfaceTexture.setDefaultBufferSize(textureWidth, textureHeight);
263 handler.post(() -> {
264 this.textureWidth = textureWidth;
265 this.textureHeight = textureHeight;
Magnus Jedvert31f18e12019-06-04 11:11:11 +0200266 tryDeliverTextureFrame();
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200267 });
268 }
269
Xavier Lepaulf0ab6a02020-06-11 14:03:56 +0200270 /**
271 * Forces a frame to be produced. If no new frame is available, the last frame is sent to the
272 * listener again.
273 */
274 public void forceFrame() {
275 handler.post(() -> {
276 hasPendingTexture = true;
277 tryDeliverTextureFrame();
278 });
279 }
280
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200281 /** Set the rotation of the delivered frames. */
282 public void setFrameRotation(int rotation) {
283 handler.post(() -> this.frameRotation = rotation);
284 }
285
286 /**
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200287 * Retrieve the underlying SurfaceTexture. The SurfaceTexture should be passed in to a video
288 * producer such as a camera or decoder.
289 */
290 public SurfaceTexture getSurfaceTexture() {
291 return surfaceTexture;
292 }
293
Magnus Jedvert814f99c2018-08-03 11:15:14 +0200294 /** Retrieve the handler that calls onFrame(). This handler is valid until dispose() is called. */
magjed9e69dfd2016-02-26 07:45:44 -0800295 public Handler getHandler() {
296 return handler;
297 }
298
299 /**
Magnus Jedvert814f99c2018-08-03 11:15:14 +0200300 * This function is called when the texture frame is released. Only one texture frame can be in
301 * flight at once, so this function must be called before a new frame is delivered.
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200302 */
Magnus Jedvert814f99c2018-08-03 11:15:14 +0200303 private void returnTextureFrame() {
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200304 handler.post(() -> {
305 isTextureInUse = false;
306 if (isQuitting) {
307 release();
308 } else {
309 tryDeliverTextureFrame();
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200310 }
311 });
312 }
313
perkj88518a22015-12-18 00:37:06 -0800314 public boolean isTextureInUse() {
315 return isTextureInUse;
316 }
317
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200318 /**
magjed9e69dfd2016-02-26 07:45:44 -0800319 * Call disconnect() to stop receiving frames. OpenGL resources are released and the handler is
Magnus Jedvert814f99c2018-08-03 11:15:14 +0200320 * stopped when the texture frame has been released. You are guaranteed to not receive any more
321 * onFrame() after this function returns.
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200322 */
magjed81e8e372016-03-03 02:11:44 -0800323 public void dispose() {
magjedd8ddb792016-03-17 03:13:43 -0700324 Logging.d(TAG, "dispose()");
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200325 ThreadUtils.invokeAtFrontUninterruptibly(handler, () -> {
326 isQuitting = true;
327 if (!isTextureInUse) {
328 release();
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200329 }
330 });
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200331 }
332
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200333 /**
Magnus Jedvertc040dae2017-11-17 16:50:17 +0100334 * Posts to the correct thread to convert |textureBuffer| to I420.
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200335 *
336 * @deprecated Use toI420() instead.
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200337 */
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200338 @Deprecated
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200339 public VideoFrame.I420Buffer textureToYuv(final TextureBuffer textureBuffer) {
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200340 return textureBuffer.toI420();
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200341 }
342
Magnus Jedvert181310f2016-05-23 16:26:47 +0200343 private void updateTexImage() {
344 // SurfaceTexture.updateTexImage apparently can compete and deadlock with eglSwapBuffers,
345 // as observed on Nexus 5. Therefore, synchronize it with the EGL functions.
346 // See https://bugs.chromium.org/p/webrtc/issues/detail?id=5702 for more info.
347 synchronized (EglBase.lock) {
348 surfaceTexture.updateTexImage();
349 }
350 }
351
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200352 private void tryDeliverTextureFrame() {
Alex Glazneva5b62d92015-10-12 13:56:20 -0700353 if (handler.getLooper().getThread() != Thread.currentThread()) {
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200354 throw new IllegalStateException("Wrong thread.");
355 }
magjed81e8e372016-03-03 02:11:44 -0800356 if (isQuitting || !hasPendingTexture || isTextureInUse || listener == null) {
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200357 return;
358 }
Magnus Jedvert31f18e12019-06-04 11:11:11 +0200359 if (textureWidth == 0 || textureHeight == 0) {
360 // Information about the resolution needs to be provided by a call to setTextureSize() before
361 // frames are produced.
362 Logging.w(TAG, "Texture size has not been set.");
363 return;
364 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200365 isTextureInUse = true;
366 hasPendingTexture = false;
367
Magnus Jedvert181310f2016-05-23 16:26:47 +0200368 updateTexImage();
perkj1b33da12015-10-05 21:06:41 +0200369
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200370 final float[] transformMatrix = new float[16];
371 surfaceTexture.getTransformMatrix(transformMatrix);
Magnus Jedvert95140712018-11-15 12:07:32 +0100372 long timestampNs = surfaceTexture.getTimestamp();
373 if (timestampAligner != null) {
374 timestampNs = timestampAligner.translateTimestamp(timestampNs);
375 }
Sami Kalliomäki066b42f2019-08-30 11:20:42 +0200376 final VideoFrame.TextureBuffer buffer =
Magnus Jedvert814f99c2018-08-03 11:15:14 +0200377 new TextureBufferImpl(textureWidth, textureHeight, TextureBuffer.Type.OES, oesTextureId,
378 RendererCommon.convertMatrixToAndroidGraphicsMatrix(transformMatrix), handler,
Sami Kalliomäki066b42f2019-08-30 11:20:42 +0200379 yuvConverter, textureRefCountMonitor);
380 if (frameRefMonitor != null) {
381 frameRefMonitor.onNewBuffer(buffer);
382 }
Magnus Jedvert814f99c2018-08-03 11:15:14 +0200383 final VideoFrame frame = new VideoFrame(buffer, frameRotation, timestampNs);
Sami Kalliomäki066b42f2019-08-30 11:20:42 +0200384 listener.onFrame(frame);
Magnus Jedvert814f99c2018-08-03 11:15:14 +0200385 frame.release();
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200386 }
387
388 private void release() {
Alex Glazneva5b62d92015-10-12 13:56:20 -0700389 if (handler.getLooper().getThread() != Thread.currentThread()) {
Magnus Jedvert1ab271c2015-09-28 11:05:44 +0200390 throw new IllegalStateException("Wrong thread.");
391 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200392 if (isTextureInUse || !isQuitting) {
393 throw new IllegalStateException("Unexpected release.");
394 }
Magnus Jedvert1d270f82018-04-16 16:28:29 +0200395 yuvConverter.release();
Magnus Jedvert1ab271c2015-09-28 11:05:44 +0200396 GLES20.glDeleteTextures(1, new int[] {oesTextureId}, 0);
397 surfaceTexture.release();
398 eglBase.release();
perkj92375592015-11-24 03:03:13 -0800399 handler.getLooper().quit();
Magnus Jedvert95140712018-11-15 12:07:32 +0100400 if (timestampAligner != null) {
401 timestampAligner.dispose();
402 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200403 }
Magnus Jedvert4ae28a12015-09-15 09:44:07 +0200404}