-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
467 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
275 changes: 275 additions & 0 deletions
275
...ideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/placeholder/EGLSurfaceTexture.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,275 @@ | ||
package com.shuyu.gsyvideoplayer.placeholder; | ||
|
||
import static java.lang.annotation.ElementType.TYPE_USE; | ||
|
||
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.Build; | ||
import android.os.Handler; | ||
import androidx.annotation.IntDef; | ||
import androidx.annotation.Nullable; | ||
import androidx.annotation.RequiresApi; | ||
import java.lang.annotation.Documented; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
/** Generates a {@link SurfaceTexture} using EGL/GLES functions. */ | ||
@RequiresApi(17) | ||
public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableListener, Runnable { | ||
|
||
/** Listener to be called when the texture image on {@link SurfaceTexture} has been updated. */ | ||
public interface TextureImageListener { | ||
/** Called when the {@link SurfaceTexture} receives a new frame from its image producer. */ | ||
void onFrameAvailable(); | ||
} | ||
|
||
/** | ||
* Secure mode to be used by the EGL surface and context. One of {@link #SECURE_MODE_NONE}, {@link | ||
* #SECURE_MODE_SURFACELESS_CONTEXT} or {@link #SECURE_MODE_PROTECTED_PBUFFER}. | ||
*/ | ||
@Documented | ||
@Retention(RetentionPolicy.SOURCE) | ||
@Target(TYPE_USE) | ||
@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_SURFACE_WIDTH = 1; | ||
private static final int EGL_SURFACE_HEIGHT = 1; | ||
|
||
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; | ||
|
||
private final Handler handler; | ||
private final int[] textureIdHolder; | ||
@Nullable private final TextureImageListener callback; | ||
|
||
@Nullable private EGLDisplay display; | ||
@Nullable private EGLContext context; | ||
@Nullable private EGLSurface surface; | ||
@Nullable private 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, /* callback= */ null); | ||
} | ||
|
||
/** | ||
* @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 looper of the {@link | ||
* Handler}. | ||
* @param callback The {@link TextureImageListener} to be called when the texture image on {@link | ||
* SurfaceTexture} has been updated. This callback will be called on the same handler thread | ||
* as the {@code handler}. | ||
*/ | ||
public EGLSurfaceTexture(Handler handler, @Nullable TextureImageListener callback) { | ||
this.handler = handler; | ||
this.callback = callback; | ||
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") | ||
public void release() { | ||
handler.removeCallbacks(this); | ||
try { | ||
if (texture != null) { | ||
texture.release(); | ||
GLES20.glDeleteTextures(1, textureIdHolder, 0); | ||
} | ||
} finally { | ||
if (display != null && !display.equals(EGL14.EGL_NO_DISPLAY)) { | ||
EGL14.eglMakeCurrent( | ||
display, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); | ||
} | ||
if (surface != null && !surface.equals(EGL14.EGL_NO_SURFACE)) { | ||
EGL14.eglDestroySurface(display, surface); | ||
} | ||
if (context != null) { | ||
EGL14.eglDestroyContext(display, context); | ||
} | ||
// EGL14.eglReleaseThread could crash before Android K (see [internal: b/11327779]). | ||
if (Build.VERSION.SDK_INT >= 19) { | ||
EGL14.eglReleaseThread(); | ||
} | ||
if (display != null && !display.equals(EGL14.EGL_NO_DISPLAY)) { | ||
// Android is unusual in that it uses a reference-counted EGLDisplay. So for | ||
// every eglInitialize() we need an eglTerminate(). | ||
EGL14.eglTerminate(display); | ||
} | ||
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 texture; | ||
} | ||
|
||
// SurfaceTexture.OnFrameAvailableListener | ||
|
||
@Override | ||
public void onFrameAvailable(SurfaceTexture surfaceTexture) { | ||
handler.post(this); | ||
} | ||
|
||
// Runnable | ||
|
||
@Override | ||
public void run() { | ||
// Run on the provided handler thread when a new image frame is available. | ||
dispatchOnFrameAvailable(); | ||
if (texture != null) { | ||
try { | ||
texture.updateTexImage(); | ||
} catch (RuntimeException e) { | ||
// Ignore | ||
} | ||
} | ||
} | ||
|
||
private void dispatchOnFrameAvailable() { | ||
if (callback != null) { | ||
callback.onFrameAvailable(); | ||
} | ||
} | ||
|
||
private static EGLDisplay getDefaultDisplay() { | ||
EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); | ||
|
||
int[] version = new int[2]; | ||
boolean eglInitialized = | ||
EGL14.eglInitialize(display, version, /* majorOffset= */ 0, version, /* minorOffset= */ 1); | ||
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); | ||
|
||
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); | ||
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, | ||
EGL_SURFACE_WIDTH, | ||
EGL14.EGL_HEIGHT, | ||
EGL_SURFACE_HEIGHT, | ||
EGL_PROTECTED_CONTENT_EXT, | ||
EGL14.EGL_TRUE, | ||
EGL14.EGL_NONE | ||
}; | ||
} else { | ||
pbufferAttributes = | ||
new int[] { | ||
EGL14.EGL_WIDTH, | ||
EGL_SURFACE_WIDTH, | ||
EGL14.EGL_HEIGHT, | ||
EGL_SURFACE_HEIGHT, | ||
EGL14.EGL_NONE | ||
}; | ||
} | ||
surface = EGL14.eglCreatePbufferSurface(display, config, pbufferAttributes, /* offset= */ 0); | ||
} | ||
|
||
boolean eglMadeCurrent = | ||
EGL14.eglMakeCurrent(display, /* draw= */ surface, /* read= */ surface, context); | ||
return surface; | ||
} | ||
|
||
private static void generateTextureIds(int[] textureIdHolder) { | ||
GLES20.glGenTextures(/* n= */ 1, textureIdHolder, /* offset= */ 0); | ||
} | ||
} |
Oops, something went wrong.