package tv.example; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Point; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.graphics.YuvImage; import android.opengl.EGL14; import android.opengl.EGLConfig; import android.opengl.EGLContext; import android.opengl.EGLDisplay; import android.opengl.EGLExt; import android.opengl.EGLSurface; import android.opengl.GLES20; import android.opengl.GLUtils; import android.os.Handler; import android.os.HandlerThread; import android.util.AttributeSet; import android.util.Log; import android.view.Surface; import android.view.TextureView; import com.twilio.video.I420Frame; import com.twilio.video.VideoRenderer; import org.webrtc.GlUtil; import org.webrtc.RendererCommon; import org.webrtc.ThreadUtils; import org.webrtc.YuvConverter; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import static android.graphics.ImageFormat.NV21; // // Example to use // SimpleTextureViewRenderer textureView = findViewById(R.id.renderer); // textureView.init(new SimpleVideoDrawer()); // textureView.setMirror(false); // ... // public class SimpleTextureViewRenderer extends TextureView implements VideoRenderer, TextureView.SurfaceTextureListener { public final class EglCore { private static final String TAG = "EglCore"; public static final int FLAG_RECORDABLE = 0x01; public static final int FLAG_TRY_GLES3 = 0x02; private static final int EGL_RECORDABLE_ANDROID = 0x3142; private EGLDisplay eglDisplay = EGL14.EGL_NO_DISPLAY; private EGLContext eglContext = EGL14.EGL_NO_CONTEXT; private EGLConfig eglConfig = null; private int glVersion = -1; public EglCore() { this(null, 0); } public EglCore(EGLContext sharedContext, int flags) { if (eglDisplay != EGL14.EGL_NO_DISPLAY) { throw new RuntimeException("EGL already set up"); } if (sharedContext == null) { sharedContext = EGL14.EGL_NO_CONTEXT; } eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); if (eglDisplay == EGL14.EGL_NO_DISPLAY) { throw new RuntimeException("unable to get EGL14 display"); } int[] version = new int[2]; if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) { eglDisplay = null; throw new RuntimeException("unable to initialize EGL14"); } if ((flags & FLAG_TRY_GLES3) != 0) { EGLConfig config = getConfig(flags, 3); if (config != null) { int[] attrib3_list = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE }; EGLContext context = EGL14.eglCreateContext(eglDisplay, config, sharedContext, attrib3_list, 0); if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) { eglConfig = config; eglContext = context; glVersion = 3; } } } if (eglContext == EGL14.EGL_NO_CONTEXT) { EGLConfig config = getConfig(flags, 2); if (config == null) { throw new RuntimeException("Unable to find a suitable EGLConfig"); } int[] attrib2_list = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE }; EGLContext context = EGL14.eglCreateContext(eglDisplay, config, sharedContext, attrib2_list, 0); checkEglError("eglCreateContext"); eglConfig = config; eglContext = context; glVersion = 2; } int[] values = new int[1]; EGL14.eglQueryContext(eglDisplay, eglContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, values, 0); Log.d(TAG, "EGLContext created, client version " + values[0]); } private EGLConfig getConfig(int flags, int version) { int renderableType = EGL14.EGL_OPENGL_ES2_BIT; if (version >= 3) { renderableType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR; } int[] attribList = { EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8, EGL14.EGL_ALPHA_SIZE, 8, EGL14.EGL_RENDERABLE_TYPE, renderableType, EGL14.EGL_NONE, 0, EGL14.EGL_NONE }; if ((flags & FLAG_RECORDABLE) != 0) { attribList[attribList.length - 3] = EGL_RECORDABLE_ANDROID; attribList[attribList.length - 2] = 1; } EGLConfig[] configs = new EGLConfig[1]; int[] numConfigs = new int[1]; if (!EGL14.eglChooseConfig(eglDisplay, attribList, 0, configs, 0, configs.length, numConfigs, 0)) { Log.w(TAG, "unable to find RGB8888 / " + version + " EGLConfig"); return null; } return configs[0]; } public void release() { if (eglDisplay != EGL14.EGL_NO_DISPLAY) { EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); EGL14.eglDestroyContext(eglDisplay, eglContext); EGL14.eglReleaseThread(); EGL14.eglTerminate(eglDisplay); } eglDisplay = EGL14.EGL_NO_DISPLAY; eglContext = EGL14.EGL_NO_CONTEXT; eglConfig = null; } @Override protected void finalize() throws Throwable { try { if (eglDisplay != EGL14.EGL_NO_DISPLAY) { Log.w(TAG, "WARNING: EglCore was not explicitly released -- state may be leaked"); release(); } } finally { super.finalize(); } } public void releaseSurface(EGLSurface eglSurface) { EGL14.eglDestroySurface(eglDisplay, eglSurface); } public EGLSurface createWindowSurface(Object surface) { if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) { throw new RuntimeException("invalid surface: " + surface); } int[] surfaceAttribs = { EGL14.EGL_NONE }; EGLSurface eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, surface, surfaceAttribs, 0); checkEglError("eglCreateWindowSurface"); if (eglSurface == null) { throw new RuntimeException("surface was null"); } return eglSurface; } public EGLSurface createOffscreenSurface(int width, int height) { int[] surfaceAttribs = { EGL14.EGL_WIDTH, width, EGL14.EGL_HEIGHT, height, EGL14.EGL_NONE }; EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttribs, 0); checkEglError("eglCreatePbufferSurface"); if (eglSurface == null) { throw new RuntimeException("surface was null"); } return eglSurface; } public void makeCurrent(EGLSurface eglSurface) { if (eglDisplay == EGL14.EGL_NO_DISPLAY) { Log.d(TAG, "NOTE: makeCurrent w/o display"); } if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) { throw new RuntimeException("eglMakeCurrent failed"); } } public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) { if (eglDisplay == EGL14.EGL_NO_DISPLAY) { Log.d(TAG, "NOTE: makeCurrent w/o display"); } if (!EGL14.eglMakeCurrent(eglDisplay, drawSurface, readSurface, eglContext)) { throw new RuntimeException("eglMakeCurrent(draw,read) failed"); } } public void makeNothingCurrent() { if (!EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT)) { throw new RuntimeException("eglMakeCurrent failed"); } } public boolean swapBuffers(EGLSurface eglSurface) { return EGL14.eglSwapBuffers(eglDisplay, eglSurface); } public void setPresentationTime(EGLSurface eglSurface, long nsecs) { EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, nsecs); } public boolean isCurrent(EGLSurface eglSurface) { return eglContext.equals(EGL14.eglGetCurrentContext()) && eglSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW)); } public int querySurface(EGLSurface eglSurface, int what) { int[] value = new int[1]; EGL14.eglQuerySurface(eglDisplay, eglSurface, what, value, 0); return value[0]; } public String queryString(int what) { return EGL14.eglQueryString(eglDisplay, what); } public int getGlVersion() { return glVersion; } private void checkEglError(String msg) { int error; if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); } } } class WindowSurface { protected SimpleTextureViewRenderer.EglCore eglCore; private EGLSurface eglSurface = EGL14.EGL_NO_SURFACE; private int width = -1; private int height = -1; private Surface surface; private boolean releaseSurface; public WindowSurface(SimpleTextureViewRenderer.EglCore eglCore, Surface surface, boolean releaseSurface) { this.eglCore = eglCore; createWindowSurface(surface); this.surface = surface; this.releaseSurface = releaseSurface; } public WindowSurface(EglCore eglCore, SurfaceTexture surfaceTexture) { this.eglCore = eglCore; createWindowSurface(surfaceTexture); } public void createWindowSurface(Object surface) { if (eglSurface != EGL14.EGL_NO_SURFACE) { throw new IllegalStateException("surface already created"); } eglSurface = eglCore.createWindowSurface(surface); } public void createOffscreenSurface(int width, int height) { if (eglSurface != EGL14.EGL_NO_SURFACE) { throw new IllegalStateException("surface already created"); } eglSurface = eglCore.createOffscreenSurface(width, height); this.width = width; this.height = height; } public int getWidth() { if (width < 0) { return eglCore.querySurface(eglSurface, EGL14.EGL_WIDTH); } else { return width; } } public int getHeight() { if (height < 0) { return eglCore.querySurface(eglSurface, EGL14.EGL_HEIGHT); } else { return height; } } public void releaseEglSurface() { eglCore.releaseSurface(eglSurface); eglSurface = EGL14.EGL_NO_SURFACE; width = height = -1; } public void makeCurrent() { eglCore.makeCurrent(eglSurface); } public void makeCurrentReadFrom(WindowSurface readSurface) { eglCore.makeCurrent(eglSurface, readSurface.eglSurface); } public boolean swapBuffers() { boolean result = eglCore.swapBuffers(eglSurface); if (!result) { Log.d(TAG, "WARNING: swapBuffers() failed"); } return result; } public void release() { releaseEglSurface(); if (surface != null) { if (releaseSurface) { surface.release(); } surface = null; } } public void recreate(EglCore newEglCore) { if (surface == null) { throw new RuntimeException("not yet implemented for SurfaceTexture"); } eglCore = newEglCore; createWindowSurface(surface); } } private static final String TAG = SimpleTextureViewRenderer.class.getCanonicalName(); private final RendererCommon.YuvUploader yuvUploader = new RendererCommon.YuvUploader(); private final Runnable renderFrameRunnable; private Handler renderThreadHandler; private HandlerThread renderThread; private final Object frameLock = new Object(); private final Object layoutLock = new Object(); private final Object handlerLock = new Object(); private RendererCommon.GlDrawer drawer; private EglCore eglCore; private WindowSurface windowSurface; private SurfaceTexture surfaceTexture; private YuvConverter yuvConverter = null; private ByteBuffer outputFrameBuffer = null; private I420Frame pendingFrame; private int rgbTexture = 0; private int[] yuvTextures = null; private float[] texMatrix; private boolean isSurfaceCreated; private boolean mirror; private int frameWidth; private int frameHeight; private int frameRotation; private final Point layoutSize = new Point(); private final Point surfaceSize = new Point(); private Point desiredLayoutSize = new Point(); public SimpleTextureViewRenderer(Context context) { super(context); this.renderFrameRunnable = new Runnable() { public void run() { SimpleTextureViewRenderer.this.renderFrameOnRenderThread(); } }; setSurfaceTextureListener(this); setOpaque(false); } public SimpleTextureViewRenderer(Context context, AttributeSet attrs) { super(context, attrs); this.renderFrameRunnable = new Runnable() { public void run() { SimpleTextureViewRenderer.this.renderFrameOnRenderThread(); } }; setSurfaceTextureListener(this); setOpaque(false); } public void init(RendererCommon.GlDrawer drawer) { Object lockObject = this.handlerLock; synchronized(this.handlerLock) { if (this.renderThreadHandler != null) { throw new IllegalStateException(TAG + " Already initialized"); } this.drawer = drawer; this.renderThread = new HandlerThread(TAG); this.renderThread.start(); this.renderThreadHandler = new Handler(this.renderThread.getLooper()); } createEglSurface(); } private void createEglSurface() { this.runOnRenderThread(new Runnable() { public void run() { synchronized(SimpleTextureViewRenderer.this.layoutLock) { if (SimpleTextureViewRenderer.this.isSurfaceCreated) { eglCore = new EglCore(null, EglCore.FLAG_TRY_GLES3); windowSurface = new WindowSurface(eglCore, surfaceTexture); windowSurface.makeCurrent(); GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); } } } }); } public void release() { final CountDownLatch eglCleanupBarrier = new CountDownLatch(1); Object lockObject = this.handlerLock; synchronized(this.handlerLock) { if (this.renderThreadHandler == null) { return; } this.renderThreadHandler.postAtFrontOfQueue(new Runnable() { public void run() { if (yuvConverter != null) { yuvConverter.release(); } SimpleTextureViewRenderer.this.drawer.release(); SimpleTextureViewRenderer.this.drawer = null; if(SimpleTextureViewRenderer.this.yuvTextures != null) { GLES20.glDeleteTextures(3, SimpleTextureViewRenderer.this.yuvTextures, 0); SimpleTextureViewRenderer.this.yuvTextures = null; } SimpleTextureViewRenderer.this.windowSurface.release(); SimpleTextureViewRenderer.this.eglCore.release(); Log.i(TAG, "Releasing SurfaceTexture in renderer thread"); surfaceTexture.release(); eglCleanupBarrier.countDown(); } }); this.renderThreadHandler = null; } ThreadUtils.awaitUninterruptibly(eglCleanupBarrier); this.renderThread.quit(); lockObject = this.frameLock; synchronized(this.frameLock) { if(this.pendingFrame != null) { this.pendingFrame.release(); this.pendingFrame = null; } } ThreadUtils.joinUninterruptibly(this.renderThread); this.renderThread = null; lockObject = this.layoutLock; synchronized(this.layoutLock) { this.frameWidth = 0; this.frameHeight = 0; this.frameRotation = 0; } } public void setMirror(boolean mirror) { Object lockObject = this.layoutLock; synchronized(this.layoutLock) { this.mirror = mirror; } } public void renderFrame(I420Frame frame) { Object handleLockObject = this.handlerLock; synchronized(this.handlerLock) { if (this.renderThreadHandler == null) { frame.release(); } else { Object frameLockObject = this.frameLock; synchronized(this.frameLock) { if(this.pendingFrame != null) { this.pendingFrame.release(); } this.pendingFrame = frame; if (frame != null) { int width = frame.rotatedWidth(); int height = frame.rotatedHeight(); int stride = width; float aspectRatio = this.frameAspectRatio(); Object layoutLockObject = this.layoutLock; synchronized(this.layoutLock) { float[] rotatedSamplingMatrix = RendererCommon.rotateTextureMatrix( frame.samplingMatrix, (float)frame.rotationDegree); float[] layoutMatrix = RendererCommon.getLayoutMatrix( this.mirror, aspectRatio, (float)this.layoutSize.x / (float)this.layoutSize.y); this.texMatrix = RendererCommon.multiplyMatrices( rotatedSamplingMatrix, layoutMatrix); } if (frame.yuvPlanes == null && width > 0 && height > 0 && frame.textureId > 0) { int outputFrameSize = width * height * 3 / 2; if (this.outputFrameBuffer == null) { this.outputFrameBuffer = ByteBuffer.allocateDirect(outputFrameSize); } if (yuvConverter == null) { yuvConverter = new YuvConverter(); } yuvConverter.convert(this.outputFrameBuffer, width, height, stride, frame.textureId, texMatrix); } } updateFrameDimensions(frame); renderThreadHandler.post(this.renderFrameRunnable); } } } } private void runOnRenderThread(Runnable runnable) { Object lockObject = this.handlerLock; synchronized(this.handlerLock) { if (this.renderThreadHandler != null) { this.renderThreadHandler.post(runnable); } } } private void renderFrameOnRenderThread() { if (Thread.currentThread() != this.renderThread) { throw new IllegalStateException(TAG + " Wrong thread."); } else { Object startTimeNs = this.frameLock; I420Frame frame; synchronized(this.frameLock) { if(this.pendingFrame == null) { return; } frame = this.pendingFrame; this.pendingFrame = null; } int width = frame.rotatedWidth(); int height = frame.rotatedHeight(); int stride = width; float aspectRatio = this.frameAspectRatio(); if (this.windowSurface != null) { if (!this.checkConsistentLayout()) { frame.release(); } else { Object lockObject = this.layoutLock; if (frame.yuvPlanes != null) { if (texMatrix != null) { draw(frame.width, frame.height, frame.yuvStrides, frame.yuvPlanes, texMatrix, null); } } else if (frame.yuvPlanes == null && width > 0 && height > 0 && frame.textureId > 0) { if (this.outputFrameBuffer != null) { ByteBuffer[] yuvPlanes; int[] yuvStrides; lockObject = this.layoutLock; synchronized (this.layoutLock) { byte[] data = this.outputFrameBuffer.array(); int offset = this.outputFrameBuffer.arrayOffset(); yuvPlanes = new ByteBuffer[]{ ByteBuffer.allocateDirect(width * height), ByteBuffer.allocateDirect(width * height / 4), ByteBuffer.allocateDirect(width * height / 4) }; yuvStrides = new int[]{ width, (width + 1) / 2, (width + 1) / 2 }; yuvPlanes[0].put(data, offset, width * height); for (int r = height; r < height * 3 / 2; ++r) { yuvPlanes[1].put(data, offset + r * stride, stride / 2); } for (int r = height; r < height * 3 / 2; ++r) { yuvPlanes[2].put(data, offset + r * stride + stride / 2, stride / 2); } } YuvImage yuvImage = i420ToYuvImage(yuvPlanes, yuvStrides, width, height); Rect rect = new Rect(0, 0, yuvImage.getWidth(), yuvImage.getHeight()); ByteArrayOutputStream stream = new ByteArrayOutputStream(); yuvImage.compressToJpeg(rect, 100, stream); byte[] imageBytes = stream.toByteArray(); final Bitmap bm = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length); lockObject = this.layoutLock; float[] tm; synchronized (this.layoutLock) { final float[] rotatedSamplingMatrix = RendererCommon.rotateTextureMatrix( frame.samplingMatrix, 270f); final float[] layoutMatrix = RendererCommon.getLayoutMatrix( false, aspectRatio, (float) this.layoutSize.x / (float) this.layoutSize.y); tm = RendererCommon.multiplyMatrices( rotatedSamplingMatrix, layoutMatrix); } draw(width, height, yuvStrides, yuvPlanes, tm, bm); } } this.windowSurface.swapBuffers(); frame.release(); } } else { frame.release(); } } } private void draw(int width, int height, int[] yuvStrides, ByteBuffer[] yuvPlanes, float[] texMatrix, Bitmap bitmap) { if (bitmap == null) { if (this.yuvTextures == null) { this.yuvTextures = new int[3]; for (int i = 0; i < 3; ++i) { this.yuvTextures[i] = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D); } } } else if (rgbTexture == 0) { rgbTexture = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D); } if (bitmap == null) { this.yuvUploader.uploadYuvData(this.yuvTextures, width, height, yuvStrides, yuvPlanes); } else { GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + 0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, rgbTexture); GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, bitmap, 0); } GLES20.glClearColor(0f, 0f, 0f, 0f); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); if (bitmap == null) { this.drawer.drawYuv( this.yuvTextures, texMatrix, 0, 0, 0, 0, this.surfaceSize.x, this.surfaceSize.y); } else { this.drawer.drawRgb( rgbTexture, texMatrix, this.frameWidth, this.frameHeight, 0, 0, this.surfaceSize.x, this.surfaceSize.y); } } private YuvImage i420ToYuvImage(ByteBuffer[] yuvPlanes, int[] yuvStrides, int width, int height) { if (yuvStrides[0] != width) { return fastI420ToYuvImage(yuvPlanes, yuvStrides, width, height); } if (yuvStrides[1] != width / 2) { return fastI420ToYuvImage(yuvPlanes, yuvStrides, width, height); } if (yuvStrides[2] != width / 2) { return fastI420ToYuvImage(yuvPlanes, yuvStrides, width, height); } byte[] bytes = new byte[yuvStrides[0] * height + yuvStrides[1] * height / 2 + yuvStrides[2] * height / 2]; ByteBuffer tmp = ByteBuffer.wrap(bytes, 0, width * height); copyPlane(yuvPlanes[0], tmp); byte[] tmpBytes = new byte[width / 2 * height / 2]; tmp = ByteBuffer.wrap(tmpBytes, 0, width / 2 * height / 2); copyPlane(yuvPlanes[2], tmp); for (int row = 0 ; row < height / 2 ; row++) { for (int col = 0 ; col < width / 2 ; col++) { bytes[width * height + row * width + col * 2] = tmpBytes[row * width / 2 + col]; } } copyPlane(yuvPlanes[1], tmp); for (int row = 0 ; row < height / 2 ; row++) { for (int col = 0 ; col < width / 2 ; col++) { bytes[width * height + row * width + col * 2 + 1] = tmpBytes[row * width / 2 + col]; } } return new YuvImage(bytes, NV21, width, height, null); } private YuvImage fastI420ToYuvImage(ByteBuffer[] yuvPlanes, int[] yuvStrides, int width, int height) { byte[] bytes = new byte[width * height * 3 / 2]; int i = 0; for (int row = 0 ; row < height ; row++) { for (int col = 0 ; col < width ; col++) { bytes[i++] = yuvPlanes[0].get(col + row * yuvStrides[0]); } } for (int row = 0 ; row < height / 2 ; row++) { for (int col = 0 ; col < width / 2; col++) { bytes[i++] = yuvPlanes[2].get(col + row * yuvStrides[2]); bytes[i++] = yuvPlanes[1].get(col + row * yuvStrides[1]); } } return new YuvImage(bytes, NV21, width, height, null); } private void copyPlane(ByteBuffer src, ByteBuffer dst) { src.position(0).limit(src.capacity()); dst.put(src); dst.position(0).limit(dst.capacity()); } private boolean checkConsistentLayout() { if (Thread.currentThread() != this.renderThread) { throw new IllegalStateException(TAG + " Wrong thread."); } else { Object lockObject = this.layoutLock; synchronized(this.layoutLock) { return this.layoutSize.equals(this.desiredLayoutSize) && this.surfaceSize.equals(this.layoutSize); } } } private float frameAspectRatio() { Object lockObject = this.layoutLock; synchronized (this.layoutLock) { return (this.frameWidth != 0 && this.frameHeight != 0) ? ((this.frameRotation % 180 == 0) ? ((float)this.frameWidth / (float)this.frameHeight) : ((float)this.frameHeight / (float)this.frameWidth)) : 0.0F; } } private void updateFrameDimensions(I420Frame frame) { Object var2 = this.layoutLock; synchronized(this.layoutLock) { if (this.frameWidth != frame.width || this.frameHeight != frame.height || this.frameRotation != frame.rotationDegree) { this.frameWidth = frame.width; this.frameHeight = frame.height; this.frameRotation = frame.rotationDegree; this.post(new Runnable() { public void run() { SimpleTextureViewRenderer.this.requestLayout(); } }); } } } @Override public void onSurfaceTextureAvailable(SurfaceTexture st, int width, int height) { Object lockObject = this.layoutLock; synchronized (this.layoutLock) { this.surfaceTexture = st; this.isSurfaceCreated = true; this.surfaceSize.x = width; this.surfaceSize.y = height; } createEglSurface(); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture st, int width, int height) { Object lockObject = this.layoutLock; synchronized(this.layoutLock) { this.surfaceSize.x = width; this.surfaceSize.y = height; } runOnRenderThread(this.renderFrameRunnable); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture st) { Object lockObject = this.layoutLock; synchronized (this.layoutLock) { this.surfaceTexture = null; this.surfaceSize.x = 0; this.surfaceSize.y = 0; } return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture st) { } }