From 0fce0a0bcbe36cfb5e8bb3c557b6d0e867e2447e Mon Sep 17 00:00:00 2001 From: hoangtc Date: Fri, 11 May 2018 04:33:34 -0700 Subject: [PATCH] Refactor DummySurfaceThread, move code to generate SurfaceTexture to new class This makes way for reusing EGLSurfaceTexture in other places, such as metadata and frame retriever. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=196240576 --- .../exoplayer2/util/EGLSurfaceTexture.java | 259 ++++++++++++++++++ .../exoplayer2/video/DummySurface.java | 163 ++--------- 2 files changed, 285 insertions(+), 137 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java new file mode 100644 index 0000000000..6fe76b9b2c --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.util; + +import android.annotation.TargetApi; +import android.graphics.SurfaceTexture; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; +import android.opengl.GLES20; +import android.os.Handler; +import android.support.annotation.IntDef; +import android.support.annotation.Nullable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** Generates a {@link SurfaceTexture} using EGL/GLES functions. */ +@TargetApi(17) +public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableListener, Runnable { + + /** Secure mode to be used by the EGL surface and context. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({SECURE_MODE_NONE, SECURE_MODE_SURFACELESS_CONTEXT, SECURE_MODE_PROTECTED_PBUFFER}) + public @interface SecureMode {} + + /** No secure EGL surface and context required. */ + public static final int SECURE_MODE_NONE = 0; + /** Creating a surfaceless, secured EGL context. */ + public static final int SECURE_MODE_SURFACELESS_CONTEXT = 1; + /** Creating a secure surface backed by a pixel buffer. */ + public static final int SECURE_MODE_PROTECTED_PBUFFER = 2; + + private static final int[] EGL_CONFIG_ATTRIBUTES = + new int[] { + EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_ALPHA_SIZE, 8, + EGL14.EGL_DEPTH_SIZE, 0, + EGL14.EGL_CONFIG_CAVEAT, EGL14.EGL_NONE, + EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT, + EGL14.EGL_NONE + }; + + private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0; + + /** A runtime exception to be thrown if some EGL operations failed. */ + public static final class GlException extends RuntimeException { + private GlException(String msg) { + super(msg); + } + } + + private final Handler handler; + private final int[] textureIdHolder; + + private @Nullable EGLDisplay display; + private @Nullable EGLContext context; + private @Nullable EGLSurface surface; + private @Nullable SurfaceTexture texture; + + /** + * @param handler The {@link Handler} that will be used to call {@link + * SurfaceTexture#updateTexImage()} to update images on the {@link SurfaceTexture}. Note that + * {@link #init(int)} has to be called on the same looper thread as the {@link Handler}'s + * looper. + */ + public EGLSurfaceTexture(Handler handler) { + this.handler = handler; + textureIdHolder = new int[1]; + } + + /** + * Initializes required EGL parameters and creates the {@link SurfaceTexture}. + * + * @param secureMode The {@link SecureMode} to be used for EGL surface. + */ + public void init(@SecureMode int secureMode) { + display = getDefaultDisplay(); + EGLConfig config = chooseEGLConfig(display); + context = createEGLContext(display, config, secureMode); + surface = createEGLSurface(display, config, context, secureMode); + generateTextureIds(textureIdHolder); + texture = new SurfaceTexture(textureIdHolder[0]); + texture.setOnFrameAvailableListener(this); + } + + /** Releases all allocated resources. */ + @SuppressWarnings({"nullness:argument.type.incompatible"}) + public void release() { + handler.removeCallbacks(this); + try { + if (texture != null) { + texture.release(); + GLES20.glDeleteTextures(1, textureIdHolder, 0); + } + } finally { + if (surface != null && !surface.equals(EGL14.EGL_NO_SURFACE)) { + EGL14.eglDestroySurface(display, surface); + } + if (context != null) { + EGL14.eglDestroyContext(display, context); + } + display = null; + context = null; + surface = null; + texture = null; + } + } + + /** + * Returns the wrapped {@link SurfaceTexture}. This can only be called after {@link #init(int)}. + */ + public SurfaceTexture getSurfaceTexture() { + return Assertions.checkNotNull(texture); + } + + // SurfaceTexture.OnFrameAvailableListener + + @Override + public void onFrameAvailable(SurfaceTexture surfaceTexture) { + handler.post(this); + } + + // Runnable + + @Override + public void run() { + if (texture != null) { + texture.updateTexImage(); + } + } + + private static EGLDisplay getDefaultDisplay() { + EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + if (display == null) { + throw new GlException("eglGetDisplay failed"); + } + + int[] version = new int[2]; + boolean eglInitialized = + EGL14.eglInitialize(display, version, /* majorOffset= */ 0, version, /* minorOffset= */ 1); + if (!eglInitialized) { + throw new GlException("eglInitialize failed"); + } + return display; + } + + private static EGLConfig chooseEGLConfig(EGLDisplay display) { + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfigs = new int[1]; + boolean success = + EGL14.eglChooseConfig( + display, + EGL_CONFIG_ATTRIBUTES, + /* attrib_listOffset= */ 0, + configs, + /* configsOffset= */ 0, + /* config_size= */ 1, + numConfigs, + /* num_configOffset= */ 0); + if (!success || numConfigs[0] <= 0 || configs[0] == null) { + throw new GlException( + Util.formatInvariant( + /* format= */ "eglChooseConfig failed: success=%b, numConfigs[0]=%d, configs[0]=%s", + success, numConfigs[0], configs[0])); + } + + return configs[0]; + } + + private static EGLContext createEGLContext( + EGLDisplay display, EGLConfig config, @SecureMode int secureMode) { + int[] glAttributes; + if (secureMode == SECURE_MODE_NONE) { + glAttributes = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE}; + } else { + glAttributes = + new int[] { + EGL14.EGL_CONTEXT_CLIENT_VERSION, + 2, + EGL_PROTECTED_CONTENT_EXT, + EGL14.EGL_TRUE, + EGL14.EGL_NONE + }; + } + EGLContext context = + EGL14.eglCreateContext( + display, config, android.opengl.EGL14.EGL_NO_CONTEXT, glAttributes, 0); + if (context == null) { + throw new GlException("eglCreateContext failed"); + } + return context; + } + + private static EGLSurface createEGLSurface( + EGLDisplay display, EGLConfig config, EGLContext context, @SecureMode int secureMode) { + EGLSurface surface; + if (secureMode == SECURE_MODE_SURFACELESS_CONTEXT) { + surface = EGL14.EGL_NO_SURFACE; + } else { + int[] pbufferAttributes; + if (secureMode == SECURE_MODE_PROTECTED_PBUFFER) { + pbufferAttributes = + new int[] { + EGL14.EGL_WIDTH, + 1, + EGL14.EGL_HEIGHT, + 1, + EGL_PROTECTED_CONTENT_EXT, + EGL14.EGL_TRUE, + EGL14.EGL_NONE + }; + } else { + pbufferAttributes = + new int[] { + EGL14.EGL_WIDTH, 1, + EGL14.EGL_HEIGHT, 1, + EGL14.EGL_NONE + }; + } + surface = EGL14.eglCreatePbufferSurface(display, config, pbufferAttributes, /* offset= */ 0); + if (surface == null) { + throw new GlException("eglCreatePbufferSurface failed"); + } + } + + boolean eglMadeCurrent = + EGL14.eglMakeCurrent(display, /* draw= */ surface, /* read= */ surface, context); + if (!eglMadeCurrent) { + throw new GlException("eglMakeCurrent failed"); + } + return surface; + } + + private static void generateTextureIds(int[] textureIdHolder) { + GLES20.glGenTextures(/* n= */ 1, textureIdHolder, /* offset= */ 0); + int errorCode = GLES20.glGetError(); + if (errorCode != GLES20.GL_NO_ERROR) { + throw new GlException("glGenTextures failed. Error: " + Integer.toHexString(errorCode)); + } + } +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java b/library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java index fc31a33097..2f41831a5e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java @@ -15,29 +15,29 @@ */ package com.google.android.exoplayer2.video; +import static com.google.android.exoplayer2.util.EGLSurfaceTexture.SECURE_MODE_NONE; +import static com.google.android.exoplayer2.util.EGLSurfaceTexture.SECURE_MODE_PROTECTED_PBUFFER; +import static com.google.android.exoplayer2.util.EGLSurfaceTexture.SECURE_MODE_SURFACELESS_CONTEXT; + import android.annotation.TargetApi; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.SurfaceTexture; -import android.graphics.SurfaceTexture.OnFrameAvailableListener; import android.opengl.EGL14; -import android.opengl.EGLConfig; -import android.opengl.EGLContext; import android.opengl.EGLDisplay; -import android.opengl.EGLSurface; -import android.opengl.GLES20; import android.os.Handler; import android.os.Handler.Callback; import android.os.HandlerThread; import android.os.Message; -import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import android.util.Log; import android.view.Surface; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.EGLSurfaceTexture; +import com.google.android.exoplayer2.util.EGLSurfaceTexture.SecureMode; import com.google.android.exoplayer2.util.Util; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import javax.microedition.khronos.egl.EGL10; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * A dummy {@link Surface}. @@ -50,16 +50,6 @@ public final class DummySurface extends Surface { private static final String EXTENSION_PROTECTED_CONTENT = "EGL_EXT_protected_content"; private static final String EXTENSION_SURFACELESS_CONTEXT = "EGL_KHR_surfaceless_context"; - private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({SECURE_MODE_NONE, SECURE_MODE_SURFACELESS_CONTEXT, SECURE_MODE_PROTECTED_PBUFFER}) - private @interface SecureMode {} - - private static final int SECURE_MODE_NONE = 0; - private static final int SECURE_MODE_SURFACELESS_CONTEXT = 1; - private static final int SECURE_MODE_PROTECTED_PBUFFER = 2; - /** * Whether the surface is secure. */ @@ -161,32 +151,25 @@ public final class DummySurface extends Surface { : SECURE_MODE_PROTECTED_PBUFFER; } - private static class DummySurfaceThread extends HandlerThread implements OnFrameAvailableListener, - Callback { + private static class DummySurfaceThread extends HandlerThread implements Callback { private static final int MSG_INIT = 1; - private static final int MSG_UPDATE_TEXTURE = 2; - private static final int MSG_RELEASE = 3; + private static final int MSG_RELEASE = 2; - private final int[] textureIdHolder; - private EGLDisplay display; - private EGLContext context; - private EGLSurface pbuffer; - private Handler handler; - private SurfaceTexture surfaceTexture; - - private Error initError; - private RuntimeException initException; - private DummySurface surface; + private @MonotonicNonNull EGLSurfaceTexture eglSurfaceTexure; + private @MonotonicNonNull Handler handler; + private @Nullable Error initError; + private @Nullable RuntimeException initException; + private @Nullable DummySurface surface; public DummySurfaceThread() { super("dummySurface"); - textureIdHolder = new int[1]; } public DummySurface init(@SecureMode int secureMode) { start(); - handler = new Handler(getLooper(), this); + handler = new Handler(getLooper(), /* callback= */ this); + eglSurfaceTexure = new EGLSurfaceTexture(handler); boolean wasInterrupted = false; synchronized (this) { handler.obtainMessage(MSG_INIT, secureMode, 0).sendToTarget(); @@ -207,19 +190,15 @@ public final class DummySurface extends Surface { } else if (initError != null) { throw initError; } else { - return surface; + return Assertions.checkNotNull(surface); } } public void release() { + Assertions.checkNotNull(handler); handler.sendEmptyMessage(MSG_RELEASE); } - @Override - public void onFrameAvailable(SurfaceTexture surfaceTexture) { - handler.sendEmptyMessage(MSG_UPDATE_TEXTURE); - } - @Override public boolean handleMessage(Message msg) { switch (msg.what) { @@ -238,9 +217,6 @@ public final class DummySurface extends Surface { } } return true; - case MSG_UPDATE_TEXTURE: - surfaceTexture.updateTexImage(); - return true; case MSG_RELEASE: try { releaseInternal(); @@ -256,103 +232,16 @@ public final class DummySurface extends Surface { } private void initInternal(@SecureMode int secureMode) { - display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); - Assertions.checkState(display != null, "eglGetDisplay failed"); - - int[] version = new int[2]; - boolean eglInitialized = EGL14.eglInitialize(display, version, 0, version, 1); - Assertions.checkState(eglInitialized, "eglInitialize failed"); - - int[] eglAttributes = - new int[] { - EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, - EGL14.EGL_RED_SIZE, 8, - EGL14.EGL_GREEN_SIZE, 8, - EGL14.EGL_BLUE_SIZE, 8, - EGL14.EGL_ALPHA_SIZE, 8, - EGL14.EGL_DEPTH_SIZE, 0, - EGL14.EGL_CONFIG_CAVEAT, EGL14.EGL_NONE, - EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT, - EGL14.EGL_NONE - }; - EGLConfig[] configs = new EGLConfig[1]; - int[] numConfigs = new int[1]; - boolean eglChooseConfigSuccess = - EGL14.eglChooseConfig(display, eglAttributes, 0, configs, 0, 1, numConfigs, 0); - Assertions.checkState(eglChooseConfigSuccess && numConfigs[0] > 0 && configs[0] != null, - "eglChooseConfig failed"); - - EGLConfig config = configs[0]; - int[] glAttributes; - if (secureMode == SECURE_MODE_NONE) { - glAttributes = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE}; - } else { - glAttributes = - new int[] { - EGL14.EGL_CONTEXT_CLIENT_VERSION, - 2, - EGL_PROTECTED_CONTENT_EXT, - EGL14.EGL_TRUE, - EGL14.EGL_NONE - }; - } - context = - EGL14.eglCreateContext( - display, config, android.opengl.EGL14.EGL_NO_CONTEXT, glAttributes, 0); - Assertions.checkState(context != null, "eglCreateContext failed"); - - EGLSurface surface; - if (secureMode == SECURE_MODE_SURFACELESS_CONTEXT) { - surface = EGL14.EGL_NO_SURFACE; - } else { - int[] pbufferAttributes; - if (secureMode == SECURE_MODE_PROTECTED_PBUFFER) { - pbufferAttributes = - new int[] { - EGL14.EGL_WIDTH, - 1, - EGL14.EGL_HEIGHT, - 1, - EGL_PROTECTED_CONTENT_EXT, - EGL14.EGL_TRUE, - EGL14.EGL_NONE - }; - } else { - pbufferAttributes = new int[] {EGL14.EGL_WIDTH, 1, EGL14.EGL_HEIGHT, 1, EGL14.EGL_NONE}; - } - pbuffer = EGL14.eglCreatePbufferSurface(display, config, pbufferAttributes, 0); - Assertions.checkState(pbuffer != null, "eglCreatePbufferSurface failed"); - surface = pbuffer; - } - - boolean eglMadeCurrent = EGL14.eglMakeCurrent(display, surface, surface, context); - Assertions.checkState(eglMadeCurrent, "eglMakeCurrent failed"); - - GLES20.glGenTextures(1, textureIdHolder, 0); - surfaceTexture = new SurfaceTexture(textureIdHolder[0]); - surfaceTexture.setOnFrameAvailableListener(this); - this.surface = new DummySurface(this, surfaceTexture, secureMode != SECURE_MODE_NONE); + Assertions.checkNotNull(eglSurfaceTexure); + eglSurfaceTexure.init(secureMode); + this.surface = + new DummySurface( + this, eglSurfaceTexure.getSurfaceTexture(), secureMode != SECURE_MODE_NONE); } private void releaseInternal() { - try { - if (surfaceTexture != null) { - surfaceTexture.release(); - GLES20.glDeleteTextures(1, textureIdHolder, 0); - } - } finally { - if (pbuffer != null) { - EGL14.eglDestroySurface(display, pbuffer); - } - if (context != null) { - EGL14.eglDestroyContext(display, context); - } - pbuffer = null; - context = null; - display = null; - surface = null; - surfaceTexture = null; - } + Assertions.checkNotNull(eglSurfaceTexure); + eglSurfaceTexure.release(); } }