diff --git a/demos/gl/src/main/java/androidx/media3/demo/gl/BitmapOverlayVideoProcessor.java b/demos/gl/src/main/java/androidx/media3/demo/gl/BitmapOverlayVideoProcessor.java index 735abf4cfa..793e1fd493 100644 --- a/demos/gl/src/main/java/androidx/media3/demo/gl/BitmapOverlayVideoProcessor.java +++ b/demos/gl/src/main/java/androidx/media3/demo/gl/BitmapOverlayVideoProcessor.java @@ -29,6 +29,7 @@ import android.opengl.GLUtils; import androidx.media3.common.C; import androidx.media3.common.util.GlProgram; import androidx.media3.common.util.GlUtil; +import androidx.media3.common.util.Log; import java.io.IOException; import java.util.Locale; import javax.microedition.khronos.opengles.GL10; @@ -41,6 +42,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /* package */ final class BitmapOverlayVideoProcessor implements VideoProcessingGLSurfaceView.VideoProcessor { + private static final String TAG = "BitmapOverlayVP"; private static final int OVERLAY_WIDTH = 512; private static final int OVERLAY_HEIGHT = 256; @@ -85,6 +87,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl"); } catch (IOException e) { throw new IllegalStateException(e); + } catch (GlUtil.GlException e) { + Log.e(TAG, "Failed to initialize the shader program", e); + return; } program.setBufferAttribute( "aFramePosition", @@ -119,7 +124,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]); GLUtils.texSubImage2D( GL10.GL_TEXTURE_2D, /* level= */ 0, /* xoffset= */ 0, /* yoffset= */ 0, overlayBitmap); - GlUtil.checkGlError(); + try { + GlUtil.checkGlError(); + } catch (GlUtil.GlException e) { + Log.e(TAG, "Failed to populate the texture", e); + } // Run the shader program. GlProgram program = checkNotNull(this.program); @@ -128,16 +137,28 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; program.setFloatUniform("uScaleX", bitmapScaleX); program.setFloatUniform("uScaleY", bitmapScaleY); program.setFloatsUniform("uTexTransform", transformMatrix); - program.bindAttributesAndUniforms(); + try { + program.bindAttributesAndUniforms(); + } catch (GlUtil.GlException e) { + Log.e(TAG, "Failed to update the shader program", e); + } GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); - GlUtil.checkGlError(); + try { + GlUtil.checkGlError(); + } catch (GlUtil.GlException e) { + Log.e(TAG, "Failed to draw a frame", e); + } } @Override public void release() { if (program != null) { - program.delete(); + try { + program.delete(); + } catch (GlUtil.GlException e) { + Log.e(TAG, "Failed to delete the shader program", e); + } } } } diff --git a/demos/gl/src/main/java/androidx/media3/demo/gl/VideoProcessingGLSurfaceView.java b/demos/gl/src/main/java/androidx/media3/demo/gl/VideoProcessingGLSurfaceView.java index 9c95122d4b..c7c0aba58a 100644 --- a/demos/gl/src/main/java/androidx/media3/demo/gl/VideoProcessingGLSurfaceView.java +++ b/demos/gl/src/main/java/androidx/media3/demo/gl/VideoProcessingGLSurfaceView.java @@ -28,6 +28,7 @@ import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.util.Assertions; import androidx.media3.common.util.GlUtil; +import androidx.media3.common.util.Log; import androidx.media3.common.util.TimedValueQueue; import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.video.VideoFrameMetadataListener; @@ -70,6 +71,7 @@ public final class VideoProcessingGLSurfaceView extends GLSurfaceView { } private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0; + private static final String TAG = "VPGlSurfaceView"; private final VideoRenderer renderer; private final Handler mainHandler; @@ -239,7 +241,11 @@ public final class VideoProcessingGLSurfaceView extends GLSurfaceView { @Override public synchronized void onSurfaceCreated(GL10 gl, EGLConfig config) { - texture = GlUtil.createExternalTexture(); + try { + texture = GlUtil.createExternalTexture(); + } catch (GlUtil.GlException e) { + Log.e(TAG, "Failed to create an external texture", e); + } surfaceTexture = new SurfaceTexture(texture); surfaceTexture.setOnFrameAvailableListener( surfaceTexture -> { diff --git a/demos/transformer/src/main/java/androidx/media3/demo/transformer/BitmapOverlayProcessor.java b/demos/transformer/src/main/java/androidx/media3/demo/transformer/BitmapOverlayProcessor.java index b9c0efac37..7e0b0f5112 100644 --- a/demos/transformer/src/main/java/androidx/media3/demo/transformer/BitmapOverlayProcessor.java +++ b/demos/transformer/src/main/java/androidx/media3/demo/transformer/BitmapOverlayProcessor.java @@ -45,9 +45,6 @@ import java.util.Locale; // TODO(b/227625365): Delete this class and use a texture processor from the Transformer library, // once overlaying a bitmap and text is supported in Transformer. /* package */ final class BitmapOverlayProcessor extends SingleFrameGlTextureProcessor { - static { - GlUtil.glAssertionsEnabled = true; - } private static final String VERTEX_SHADER_PATH = "vertex_shader_copy_es2.glsl"; private static final String FRAGMENT_SHADER_PATH = "fragment_shader_bitmap_overlay_es2.glsl"; @@ -67,9 +64,9 @@ import java.util.Locale; /** * Creates a new instance. * - * @throws IOException If a problem occurs while reading shader files. + * @throws FrameProcessingException If a problem occurs while reading shader files. */ - public BitmapOverlayProcessor(Context context) throws IOException { + public BitmapOverlayProcessor(Context context) throws FrameProcessingException { paint = new Paint(); paint.setTextSize(64); paint.setAntiAlias(true); @@ -87,10 +84,14 @@ import java.util.Locale; } catch (PackageManager.NameNotFoundException e) { throw new IllegalStateException(e); } - bitmapTexId = GlUtil.createTexture(BITMAP_WIDTH_HEIGHT, BITMAP_WIDTH_HEIGHT); - GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, overlayBitmap, /* border= */ 0); + try { + bitmapTexId = GlUtil.createTexture(BITMAP_WIDTH_HEIGHT, BITMAP_WIDTH_HEIGHT); + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, overlayBitmap, /* border= */ 0); - glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH); + glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH); + } catch (GlUtil.GlException | IOException e) { + throw new FrameProcessingException(e); + } // Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y. glProgram.setBufferAttribute( "aFramePosition", @@ -141,15 +142,19 @@ import java.util.Locale; GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); GlUtil.checkGlError(); } catch (GlUtil.GlException e) { - throw new FrameProcessingException(e); + throw new FrameProcessingException(e, presentationTimeUs); } } @Override - public void release() { + public void release() throws FrameProcessingException { super.release(); if (glProgram != null) { - glProgram.delete(); + try { + glProgram.delete(); + } catch (GlUtil.GlException e) { + throw new FrameProcessingException(e); + } } } diff --git a/demos/transformer/src/main/java/androidx/media3/demo/transformer/PeriodicVignetteProcessor.java b/demos/transformer/src/main/java/androidx/media3/demo/transformer/PeriodicVignetteProcessor.java index fb00e5f888..9ae281b37b 100644 --- a/demos/transformer/src/main/java/androidx/media3/demo/transformer/PeriodicVignetteProcessor.java +++ b/demos/transformer/src/main/java/androidx/media3/demo/transformer/PeriodicVignetteProcessor.java @@ -31,9 +31,6 @@ import java.io.IOException; * darker the further they are away from the frame center. */ /* package */ final class PeriodicVignetteProcessor extends SingleFrameGlTextureProcessor { - static { - GlUtil.glAssertionsEnabled = true; - } private static final String VERTEX_SHADER_PATH = "vertex_shader_copy_es2.glsl"; private static final String FRAGMENT_SHADER_PATH = "fragment_shader_vignette_es2.glsl"; @@ -60,7 +57,7 @@ import java.io.IOException; * @param minInnerRadius The lower bound of the radius that is unaffected by the effect. * @param maxInnerRadius The upper bound of the radius that is unaffected by the effect. * @param outerRadius The radius after which all pixels are black. - * @throws IOException If a problem occurs while reading shader files. + * @throws FrameProcessingException If a problem occurs while reading shader files. */ public PeriodicVignetteProcessor( Context context, @@ -69,12 +66,16 @@ import java.io.IOException; float minInnerRadius, float maxInnerRadius, float outerRadius) - throws IOException { + throws FrameProcessingException { checkArgument(minInnerRadius <= maxInnerRadius); checkArgument(maxInnerRadius <= outerRadius); this.minInnerRadius = minInnerRadius; this.deltaInnerRadius = maxInnerRadius - minInnerRadius; - glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH); + try { + glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH); + } catch (IOException | GlUtil.GlException e) { + throw new FrameProcessingException(e); + } glProgram.setFloatsUniform("uCenter", new float[] {centerX, centerY}); glProgram.setFloatsUniform("uOuterRadius", new float[] {outerRadius}); // Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y. @@ -102,15 +103,19 @@ import java.io.IOException; // The four-vertex triangle strip forms a quad. GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); } catch (GlUtil.GlException e) { - throw new FrameProcessingException(e); + throw new FrameProcessingException(e, presentationTimeUs); } } @Override - public void release() { + public void release() throws FrameProcessingException { super.release(); if (glProgram != null) { - glProgram.delete(); + try { + glProgram.delete(); + } catch (GlUtil.GlException e) { + throw new FrameProcessingException(e); + } } } } diff --git a/demos/transformer/src/withMediaPipe/java/androidx/media3/demo/transformer/MediaPipeProcessor.java b/demos/transformer/src/withMediaPipe/java/androidx/media3/demo/transformer/MediaPipeProcessor.java index 7d6ddfae5b..2ceb8cf12f 100644 --- a/demos/transformer/src/withMediaPipe/java/androidx/media3/demo/transformer/MediaPipeProcessor.java +++ b/demos/transformer/src/withMediaPipe/java/androidx/media3/demo/transformer/MediaPipeProcessor.java @@ -79,12 +79,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * @param graphName Name of a MediaPipe graph asset to load. * @param inputStreamName Name of the input video stream in the graph. * @param outputStreamName Name of the input video stream in the graph. - * @throws IOException If a problem occurs while reading shader files or initializing MediaPipe - * resources. + * @throws FrameProcessingException If a problem occurs while reading shader files or initializing + * MediaPipe resources. */ public MediaPipeProcessor( Context context, String graphName, String inputStreamName, String outputStreamName) - throws IOException { + throws FrameProcessingException { checkState(LOADER.isAvailable()); frameProcessorConditionVariable = new ConditionVariable(); @@ -104,7 +104,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; frameProcessorPendingError = error; frameProcessorConditionVariable.open(); }); - glProgram = new GlProgram(context, COPY_VERTEX_SHADER_NAME, COPY_FRAGMENT_SHADER_NAME); + try { + glProgram = new GlProgram(context, COPY_VERTEX_SHADER_NAME, COPY_FRAGMENT_SHADER_NAME); + } catch (IOException | GlUtil.GlException e) { + throw new FrameProcessingException(e); + } } @Override @@ -152,14 +156,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); GlUtil.checkGlError(); } catch (GlUtil.GlException e) { - throw new FrameProcessingException(e); + throw new FrameProcessingException(e, presentationTimeUs); } finally { checkStateNotNull(outputFrame).release(); } } @Override - public void release() { + public void release() throws FrameProcessingException { super.release(); checkStateNotNull(frameProcessor).close(); } diff --git a/libraries/common/src/main/java/androidx/media3/common/util/EGLSurfaceTexture.java b/libraries/common/src/main/java/androidx/media3/common/util/EGLSurfaceTexture.java index ee76ed476b..d764882e2e 100644 --- a/libraries/common/src/main/java/androidx/media3/common/util/EGLSurfaceTexture.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/EGLSurfaceTexture.java @@ -79,13 +79,6 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL 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; @Nullable private final TextureImageListener callback; @@ -125,7 +118,7 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL * * @param secureMode The {@link SecureMode} to be used for EGL surface. */ - public void init(@SecureMode int secureMode) { + public void init(@SecureMode int secureMode) throws GlUtil.GlException { display = getDefaultDisplay(); EGLConfig config = chooseEGLConfig(display); context = createEGLContext(display, config, secureMode); @@ -206,22 +199,18 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL } } - private static EGLDisplay getDefaultDisplay() { + private static EGLDisplay getDefaultDisplay() throws GlUtil.GlException { EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); - if (display == null) { - throw new GlException("eglGetDisplay failed"); - } + GlUtil.checkGlException(display != null, "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"); - } + GlUtil.checkGlException(eglInitialized, "eglInitialize failed"); return display; } - private static EGLConfig chooseEGLConfig(EGLDisplay display) { + private static EGLConfig chooseEGLConfig(EGLDisplay display) throws GlUtil.GlException { EGLConfig[] configs = new EGLConfig[1]; int[] numConfigs = new int[1]; boolean success = @@ -234,18 +223,17 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL /* 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])); - } + GlUtil.checkGlException( + success && numConfigs[0] > 0 && configs[0] != null, + 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) { + EGLDisplay display, EGLConfig config, @SecureMode int secureMode) throws GlUtil.GlException { int[] glAttributes; if (secureMode == SECURE_MODE_NONE) { glAttributes = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE}; @@ -262,14 +250,13 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL EGLContext context = EGL14.eglCreateContext( display, config, android.opengl.EGL14.EGL_NO_CONTEXT, glAttributes, 0); - if (context == null) { - throw new GlException("eglCreateContext failed"); - } + GlUtil.checkGlException(context != null, "eglCreateContext failed"); return context; } private static EGLSurface createEGLSurface( - EGLDisplay display, EGLConfig config, EGLContext context, @SecureMode int secureMode) { + EGLDisplay display, EGLConfig config, EGLContext context, @SecureMode int secureMode) + throws GlUtil.GlException { EGLSurface surface; if (secureMode == SECURE_MODE_SURFACELESS_CONTEXT) { surface = EGL14.EGL_NO_SURFACE; @@ -297,20 +284,16 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL }; } surface = EGL14.eglCreatePbufferSurface(display, config, pbufferAttributes, /* offset= */ 0); - if (surface == null) { - throw new GlException("eglCreatePbufferSurface failed"); - } + GlUtil.checkGlException(surface != null, "eglCreatePbufferSurface failed"); } boolean eglMadeCurrent = EGL14.eglMakeCurrent(display, /* draw= */ surface, /* read= */ surface, context); - if (!eglMadeCurrent) { - throw new GlException("eglMakeCurrent failed"); - } + GlUtil.checkGlException(eglMadeCurrent, "eglMakeCurrent failed"); return surface; } - private static void generateTextureIds(int[] textureIdHolder) { + private static void generateTextureIds(int[] textureIdHolder) throws GlUtil.GlException { GLES20.glGenTextures(/* n= */ 1, textureIdHolder, /* offset= */ 0); GlUtil.checkGlError(); } diff --git a/libraries/common/src/main/java/androidx/media3/common/util/GlProgram.java b/libraries/common/src/main/java/androidx/media3/common/util/GlProgram.java index da410f921e..63976b776d 100644 --- a/libraries/common/src/main/java/androidx/media3/common/util/GlProgram.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/GlProgram.java @@ -54,7 +54,7 @@ public final class GlProgram { * @throws IOException When failing to read shader files. */ public GlProgram(Context context, String vertexShaderFilePath, String fragmentShaderFilePath) - throws IOException { + throws IOException, GlUtil.GlException { this( GlUtil.loadAsset(context, vertexShaderFilePath), GlUtil.loadAsset(context, fragmentShaderFilePath)); @@ -69,7 +69,7 @@ public final class GlProgram { * @param vertexShaderGlsl The vertex shader program. * @param fragmentShaderGlsl The fragment shader program. */ - public GlProgram(String vertexShaderGlsl, String fragmentShaderGlsl) { + public GlProgram(String vertexShaderGlsl, String fragmentShaderGlsl) throws GlUtil.GlException { programId = GLES20.glCreateProgram(); GlUtil.checkGlError(); @@ -81,10 +81,9 @@ public final class GlProgram { GLES20.glLinkProgram(programId); int[] linkStatus = new int[] {GLES20.GL_FALSE}; GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, /* offset= */ 0); - if (linkStatus[0] != GLES20.GL_TRUE) { - GlUtil.throwGlException( - "Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(programId)); - } + GlUtil.checkGlException( + linkStatus[0] == GLES20.GL_TRUE, + "Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(programId)); GLES20.glUseProgram(programId); attributeByName = new HashMap<>(); int[] attributeCount = new int[1]; @@ -107,16 +106,15 @@ public final class GlProgram { GlUtil.checkGlError(); } - private static void addShader(int programId, int type, String glsl) { + private static void addShader(int programId, int type, String glsl) throws GlUtil.GlException { int shader = GLES20.glCreateShader(type); GLES20.glShaderSource(shader, glsl); GLES20.glCompileShader(shader); int[] result = new int[] {GLES20.GL_FALSE}; GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, /* offset= */ 0); - if (result[0] != GLES20.GL_TRUE) { - GlUtil.throwGlException(GLES20.glGetShaderInfoLog(shader) + ", source: " + glsl); - } + GlUtil.checkGlException( + result[0] == GLES20.GL_TRUE, GLES20.glGetShaderInfoLog(shader) + ", source: " + glsl); GLES20.glAttachShader(programId, shader); GLES20.glDeleteShader(shader); @@ -146,13 +144,13 @@ public final class GlProgram { * *
Call this in the rendering loop to switch between different programs. */ - public void use() { + public void use() throws GlUtil.GlException { GLES20.glUseProgram(programId); GlUtil.checkGlError(); } /** Deletes the program. Deleted programs cannot be used again. */ - public void delete() { + public void delete() throws GlUtil.GlException { GLES20.glDeleteProgram(programId); GlUtil.checkGlError(); } @@ -161,7 +159,7 @@ public final class GlProgram { * Returns the location of an {@link Attribute}, which has been enabled as a vertex attribute * array. */ - public int getAttributeArrayLocationAndEnable(String attributeName) { + public int getAttributeArrayLocationAndEnable(String attributeName) throws GlUtil.GlException { int location = getAttributeLocation(attributeName); GLES20.glEnableVertexAttribArray(location); GlUtil.checkGlError(); @@ -196,7 +194,7 @@ public final class GlProgram { } /** Binds all attributes and uniforms in the program. */ - public void bindAttributesAndUniforms() { + public void bindAttributesAndUniforms() throws GlUtil.GlException { for (Attribute attribute : attributes) { attribute.bind(); } @@ -277,7 +275,7 @@ public final class GlProgram { * *
Should be called before each drawing call. */ - public void bind() { + public void bind() throws GlUtil.GlException { Buffer buffer = checkNotNull(this.buffer, "call setBuffer before bind"); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, /* buffer= */ 0); GLES20.glVertexAttribPointer( @@ -363,7 +361,7 @@ public final class GlProgram { * *
Should be called before each drawing call.
*/
- public void bind() {
+ public void bind() throws GlUtil.GlException {
switch (type) {
case GLES20.GL_FLOAT:
GLES20.glUniform1fv(location, /* count= */ 1, value, /* offset= */ 0);
diff --git a/libraries/common/src/main/java/androidx/media3/common/util/GlUtil.java b/libraries/common/src/main/java/androidx/media3/common/util/GlUtil.java
index 485f2db986..44f4e648fa 100644
--- a/libraries/common/src/main/java/androidx/media3/common/util/GlUtil.java
+++ b/libraries/common/src/main/java/androidx/media3/common/util/GlUtil.java
@@ -43,19 +43,14 @@ import javax.microedition.khronos.egl.EGL10;
@UnstableApi
public final class GlUtil {
- /** Thrown when an OpenGL error occurs and {@link #glAssertionsEnabled} is {@code true}. */
- public static final class GlException extends RuntimeException {
+ /** Thrown when an OpenGL error occurs. */
+ public static final class GlException extends Exception {
/** Creates an instance with the specified error message. */
public GlException(String message) {
super(message);
}
}
- // TODO(b/231937416): Consider removing this flag, enabling assertions by default, and making
- // GlException checked.
- /** Whether to throw a {@link GlException} in case of an OpenGL error. */
- public static boolean glAssertionsEnabled = false;
-
/** Number of elements in a 3d homogeneous coordinate vector describing a vertex. */
public static final int HOMOGENEOUS_COORDINATE_VECTOR_SIZE = 4;
@@ -184,13 +179,13 @@ public final class GlUtil {
/** Returns an initialized default {@link EGLDisplay}. */
@RequiresApi(17)
- public static EGLDisplay createEglDisplay() {
+ public static EGLDisplay createEglDisplay() throws GlException {
return Api17.createEglDisplay();
}
/** Returns a new {@link EGLContext} for the specified {@link EGLDisplay}. */
@RequiresApi(17)
- public static EGLContext createEglContext(EGLDisplay eglDisplay) {
+ public static EGLContext createEglContext(EGLDisplay eglDisplay) throws GlException {
return Api17.createEglContext(eglDisplay, /* version= */ 2, EGL_CONFIG_ATTRIBUTES_RGBA_8888);
}
@@ -199,7 +194,8 @@ public final class GlUtil {
* RGBA 1010102 config.
*/
@RequiresApi(17)
- public static EGLContext createEglContextEs3Rgba1010102(EGLDisplay eglDisplay) {
+ public static EGLContext createEglContextEs3Rgba1010102(EGLDisplay eglDisplay)
+ throws GlException {
return Api17.createEglContext(eglDisplay, /* version= */ 3, EGL_CONFIG_ATTRIBUTES_RGBA_1010102);
}
@@ -210,7 +206,7 @@ public final class GlUtil {
* @param surface The surface to wrap; must be a surface, surface texture or surface holder.
*/
@RequiresApi(17)
- public static EGLSurface getEglSurface(EGLDisplay eglDisplay, Object surface) {
+ public static EGLSurface getEglSurface(EGLDisplay eglDisplay, Object surface) throws GlException {
return Api17.getEglSurface(
eglDisplay, surface, EGL_CONFIG_ATTRIBUTES_RGBA_8888, EGL_WINDOW_SURFACE_ATTRIBUTES_NONE);
}
@@ -223,7 +219,8 @@ public final class GlUtil {
* @param surface The surface to wrap; must be a surface, surface texture or surface holder.
*/
@RequiresApi(17)
- public static EGLSurface getEglSurfaceBt2020Pq(EGLDisplay eglDisplay, Object surface) {
+ public static EGLSurface getEglSurfaceBt2020Pq(EGLDisplay eglDisplay, Object surface)
+ throws GlException {
return Api17.getEglSurface(
eglDisplay,
surface,
@@ -239,7 +236,8 @@ public final class GlUtil {
* @param height The height of the pixel buffer.
*/
@RequiresApi(17)
- private static EGLSurface createPbufferSurface(EGLDisplay eglDisplay, int width, int height) {
+ private static EGLSurface createPbufferSurface(EGLDisplay eglDisplay, int width, int height)
+ throws GlException {
int[] pbufferAttributes =
new int[] {
EGL14.EGL_WIDTH, width,
@@ -258,7 +256,7 @@ public final class GlUtil {
* @return {@link EGL14#EGL_NO_SURFACE} if supported and a 1x1 pixel buffer surface otherwise.
*/
@RequiresApi(17)
- public static EGLSurface createPlaceholderEglSurface(EGLDisplay eglDisplay) {
+ public static EGLSurface createPlaceholderEglSurface(EGLDisplay eglDisplay) throws GlException {
return isSurfacelessContextExtensionSupported()
? EGL14.EGL_NO_SURFACE
: createPbufferSurface(eglDisplay, /* width= */ 1, /* height= */ 1);
@@ -271,7 +269,8 @@ public final class GlUtil {
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
*/
@RequiresApi(17)
- public static void focusPlaceholderEglSurface(EGLContext eglContext, EGLDisplay eglDisplay) {
+ public static void focusPlaceholderEglSurface(EGLContext eglContext, EGLDisplay eglDisplay)
+ throws GlException {
EGLSurface eglSurface = createPbufferSurface(eglDisplay, /* width= */ 1, /* height= */ 1);
focusEglSurface(eglDisplay, eglContext, eglSurface, /* width= */ 1, /* height= */ 1);
}
@@ -285,7 +284,7 @@ public final class GlUtil {
*/
@RequiresApi(17)
public static void focusPlaceholderEglSurfaceBt2020Pq(
- EGLContext eglContext, EGLDisplay eglDisplay) {
+ EGLContext eglContext, EGLDisplay eglDisplay) throws GlException {
int[] pbufferAttributes =
new int[] {
EGL14.EGL_WIDTH,
@@ -303,10 +302,10 @@ public final class GlUtil {
}
/**
- * If there is an OpenGl error, logs the error and if {@link #glAssertionsEnabled} is true throws
- * a {@link GlException}.
+ * Logs all OpenGL errors that occurred since this method was last called and throws a {@link
+ * GlException} for the last error.
*/
- public static void checkGlError() {
+ public static void checkGlError() throws GlException {
int lastError = GLES20.GL_NO_ERROR;
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
@@ -314,7 +313,7 @@ public final class GlUtil {
lastError = error;
}
if (lastError != GLES20.GL_NO_ERROR) {
- throwGlException("glError: " + gluErrorString(lastError));
+ throw new GlException("glError: " + gluErrorString(lastError));
}
}
@@ -325,7 +324,7 @@ public final class GlUtil {
* @param height The height for a texture.
* @throws GlException If the texture width or height is invalid.
*/
- public static void assertValidTextureSize(int width, int height) {
+ public static void assertValidTextureSize(int width, int height) throws GlException {
// TODO(b/201293185): Consider handling adjustments for sizes > GL_MAX_TEXTURE_SIZE
// (ex. downscaling appropriately) in a texture processor instead of asserting incorrect
// values.
@@ -336,15 +335,16 @@ public final class GlUtil {
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeBuffer, 0);
int maxTextureSize = maxTextureSizeBuffer[0];
if (width < 0 || height < 0) {
- throwGlException("width or height is less than 0");
+ throw new GlException("width or height is less than 0");
}
if (width > maxTextureSize || height > maxTextureSize) {
- throwGlException("width or height is greater than GL_MAX_TEXTURE_SIZE " + maxTextureSize);
+ throw new GlException(
+ "width or height is greater than GL_MAX_TEXTURE_SIZE " + maxTextureSize);
}
}
/** Fills the pixels in the current output render target with (r=0, g=0, b=0, a=0). */
- public static void clearOutputFrame() {
+ public static void clearOutputFrame() throws GlException {
GLES20.glClearColor(/* red= */ 0, /* green= */ 0, /* blue= */ 0, /* alpha= */ 0);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GlUtil.checkGlError();
@@ -356,7 +356,8 @@ public final class GlUtil {
*/
@RequiresApi(17)
public static void focusEglSurface(
- EGLDisplay eglDisplay, EGLContext eglContext, EGLSurface eglSurface, int width, int height) {
+ EGLDisplay eglDisplay, EGLContext eglContext, EGLSurface eglSurface, int width, int height)
+ throws GlException {
Api17.focusRenderTarget(
eglDisplay, eglContext, eglSurface, /* framebuffer= */ 0, width, height);
}
@@ -372,7 +373,8 @@ public final class GlUtil {
EGLSurface eglSurface,
int framebuffer,
int width,
- int height) {
+ int height)
+ throws GlException {
Api17.focusRenderTarget(eglDisplay, eglContext, eglSurface, framebuffer, width, height);
}
@@ -388,7 +390,8 @@ public final class GlUtil {
* @param height The viewport height, in pixels.
*/
@RequiresApi(17)
- public static void focusFramebufferUsingCurrentContext(int framebuffer, int width, int height) {
+ public static void focusFramebufferUsingCurrentContext(int framebuffer, int width, int height)
+ throws GlException {
Api17.focusFramebufferUsingCurrentContext(framebuffer, width, height);
}
@@ -397,7 +400,7 @@ public final class GlUtil {
*
* @param textureId The ID of the texture to delete.
*/
- public static void deleteTexture(int textureId) {
+ public static void deleteTexture(int textureId) throws GlException {
GLES20.glDeleteTextures(/* n= */ 1, new int[] {textureId}, /* offset= */ 0);
checkGlError();
}
@@ -408,7 +411,7 @@ public final class GlUtil {
*/
@RequiresApi(17)
public static void destroyEglContext(
- @Nullable EGLDisplay eglDisplay, @Nullable EGLContext eglContext) {
+ @Nullable EGLDisplay eglDisplay, @Nullable EGLContext eglContext) throws GlException {
Api17.destroyEglContext(eglDisplay, eglContext);
}
@@ -453,7 +456,7 @@ public final class GlUtil {
* Creates a GL_TEXTURE_EXTERNAL_OES with default configuration of GL_LINEAR filtering and
* GL_CLAMP_TO_EDGE wrapping.
*/
- public static int createExternalTexture() {
+ public static int createExternalTexture() throws GlException {
int texId = generateTexture();
bindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
return texId;
@@ -465,7 +468,7 @@ public final class GlUtil {
* @param width of the new texture in pixels
* @param height of the new texture in pixels
*/
- public static int createTexture(int width, int height) {
+ public static int createTexture(int width, int height) throws GlException {
assertValidTextureSize(width, height);
int texId = generateTexture();
bindTexture(GLES20.GL_TEXTURE_2D, texId);
@@ -485,8 +488,8 @@ public final class GlUtil {
}
/** Returns a new GL texture identifier. */
- private static int generateTexture() {
- checkEglException(
+ private static int generateTexture() throws GlException {
+ checkGlException(
!Util.areEqual(EGL14.eglGetCurrentContext(), EGL14.EGL_NO_CONTEXT), "No current context");
int[] texId = new int[1];
@@ -504,7 +507,7 @@ public final class GlUtil {
* GLES20#GL_TEXTURE_2D} for a two-dimensional texture or {@link
* GLES11Ext#GL_TEXTURE_EXTERNAL_OES} for an external texture.
*/
- public static void bindTexture(int textureTarget, int texId) {
+ public static void bindTexture(int textureTarget, int texId) throws GlException {
GLES20.glBindTexture(textureTarget, texId);
checkGlError();
GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
@@ -522,8 +525,8 @@ public final class GlUtil {
*
* @param texId The identifier of the texture to attach to the framebuffer.
*/
- public static int createFboForTexture(int texId) {
- checkEglException(
+ public static int createFboForTexture(int texId) throws GlException {
+ checkGlException(
!Util.areEqual(EGL14.eglGetCurrentContext(), EGL14.EGL_NO_CONTEXT), "No current context");
int[] fboId = new int[1];
@@ -537,23 +540,19 @@ public final class GlUtil {
return fboId[0];
}
- /* package */ static void throwGlException(String errorMsg) {
- if (glAssertionsEnabled) {
- throw new GlException(errorMsg);
- } else {
- Log.e(TAG, errorMsg);
- }
- }
-
- private static void checkEglException(boolean expression, String errorMessage) {
+ /**
+ * Throws a {@link GlException} with the given message if {@code expression} evaluates to {@code
+ * false}.
+ */
+ public static void checkGlException(boolean expression, String errorMessage) throws GlException {
if (!expression) {
- throwGlException(errorMessage);
+ throw new GlException(errorMessage);
}
}
- private static void checkEglException(String errorMessage) {
+ private static void checkEglException(String errorMessage) throws GlException {
int error = EGL14.eglGetError();
- checkEglException(error == EGL14.EGL_SUCCESS, errorMessage + ", error code: " + error);
+ checkGlException(error == EGL14.EGL_SUCCESS, errorMessage + ", error code: " + error);
}
@RequiresApi(17)
@@ -561,24 +560,24 @@ public final class GlUtil {
private Api17() {}
@DoNotInline
- public static EGLDisplay createEglDisplay() {
+ public static EGLDisplay createEglDisplay() throws GlException {
EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
- checkEglException(!eglDisplay.equals(EGL14.EGL_NO_DISPLAY), "No EGL display.");
- if (!EGL14.eglInitialize(
- eglDisplay,
- /* unusedMajor */ new int[1],
- /* majorOffset= */ 0,
- /* unusedMinor */ new int[1],
- /* minorOffset= */ 0)) {
- throwGlException("Error in eglInitialize.");
- }
+ checkGlException(!eglDisplay.equals(EGL14.EGL_NO_DISPLAY), "No EGL display.");
+ checkGlException(
+ EGL14.eglInitialize(
+ eglDisplay,
+ /* unusedMajor */ new int[1],
+ /* majorOffset= */ 0,
+ /* unusedMinor */ new int[1],
+ /* minorOffset= */ 0),
+ "Error in eglInitialize.");
checkGlError();
return eglDisplay;
}
@DoNotInline
public static EGLContext createEglContext(
- EGLDisplay eglDisplay, int version, int[] configAttributes) {
+ EGLDisplay eglDisplay, int version, int[] configAttributes) throws GlException {
int[] contextAttributes = {EGL14.EGL_CONTEXT_CLIENT_VERSION, version, EGL14.EGL_NONE};
EGLContext eglContext =
EGL14.eglCreateContext(
@@ -589,7 +588,7 @@ public final class GlUtil {
/* offset= */ 0);
if (eglContext == null) {
EGL14.eglTerminate(eglDisplay);
- throwGlException(
+ throw new GlException(
"eglCreateContext() failed to create a valid context. The device may not support EGL"
+ " version "
+ version);
@@ -603,7 +602,8 @@ public final class GlUtil {
EGLDisplay eglDisplay,
Object surface,
int[] configAttributes,
- int[] windowSurfaceAttributes) {
+ int[] windowSurfaceAttributes)
+ throws GlException {
EGLSurface eglSurface =
EGL14.eglCreateWindowSurface(
eglDisplay,
@@ -617,7 +617,7 @@ public final class GlUtil {
@DoNotInline
public static EGLSurface createEglPbufferSurface(
- EGLDisplay eglDisplay, int[] configAttributes, int[] pbufferAttributes) {
+ EGLDisplay eglDisplay, int[] configAttributes, int[] pbufferAttributes) throws GlException {
EGLSurface eglSurface =
EGL14.eglCreatePbufferSurface(
eglDisplay,
@@ -635,15 +635,17 @@ public final class GlUtil {
EGLSurface eglSurface,
int framebuffer,
int width,
- int height) {
+ int height)
+ throws GlException {
EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
checkEglException("Error making context current");
focusFramebufferUsingCurrentContext(framebuffer, width, height);
}
@DoNotInline
- public static void focusFramebufferUsingCurrentContext(int framebuffer, int width, int height) {
- checkEglException(
+ public static void focusFramebufferUsingCurrentContext(int framebuffer, int width, int height)
+ throws GlException {
+ checkGlException(
!Util.areEqual(EGL14.eglGetCurrentContext(), EGL14.EGL_NO_CONTEXT), "No current context");
int[] boundFramebuffer = new int[1];
@@ -658,7 +660,7 @@ public final class GlUtil {
@DoNotInline
public static void destroyEglContext(
- @Nullable EGLDisplay eglDisplay, @Nullable EGLContext eglContext) {
+ @Nullable EGLDisplay eglDisplay, @Nullable EGLContext eglContext) throws GlException {
if (eglDisplay == null) {
return;
}
@@ -676,7 +678,8 @@ public final class GlUtil {
}
@DoNotInline
- private static EGLConfig getEglConfig(EGLDisplay eglDisplay, int[] attributes) {
+ private static EGLConfig getEglConfig(EGLDisplay eglDisplay, int[] attributes)
+ throws GlException {
EGLConfig[] eglConfigs = new EGLConfig[1];
if (!EGL14.eglChooseConfig(
eglDisplay,
@@ -687,7 +690,7 @@ public final class GlUtil {
/* config_size= */ 1,
/* unusedNumConfig */ new int[1],
/* num_configOffset= */ 0)) {
- throwGlException("eglChooseConfig failed.");
+ throw new GlException("eglChooseConfig failed.");
}
return eglConfigs[0];
}
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/PlaceholderSurface.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/PlaceholderSurface.java
index e9d1955435..5ee73d52f6 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/PlaceholderSurface.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/PlaceholderSurface.java
@@ -179,6 +179,9 @@ public final class PlaceholderSurface extends Surface {
} catch (RuntimeException e) {
Log.e(TAG, "Failed to initialize placeholder surface", e);
initException = e;
+ } catch (GlUtil.GlException e) {
+ Log.e(TAG, "Failed to initialize placeholder surface", e);
+ initException = new IllegalStateException(e);
} catch (Error e) {
Log.e(TAG, "Failed to initialize placeholder surface", e);
initError = e;
@@ -202,7 +205,7 @@ public final class PlaceholderSurface extends Surface {
}
}
- private void initInternal(@SecureMode int secureMode) {
+ private void initInternal(@SecureMode int secureMode) throws GlUtil.GlException {
Assertions.checkNotNull(eglSurfaceTexture);
eglSurfaceTexture.init(secureMode);
this.surface =
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoDecoderGLSurfaceView.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoDecoderGLSurfaceView.java
index 66e5ad93fb..9ffbf37ae4 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoDecoderGLSurfaceView.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoDecoderGLSurfaceView.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
+import android.util.Log;
import androidx.annotation.Nullable;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.GlProgram;
@@ -48,6 +49,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
public final class VideoDecoderGLSurfaceView extends GLSurfaceView
implements VideoDecoderOutputBufferRenderer {
+ private static final String TAG = "VideoDecoderGLSV";
+
private final Renderer renderer;
/**
@@ -172,22 +175,26 @@ public final class VideoDecoderGLSurfaceView extends GLSurfaceView
@Override
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
- program = new GlProgram(VERTEX_SHADER, FRAGMENT_SHADER);
- int posLocation = program.getAttributeArrayLocationAndEnable("in_pos");
- GLES20.glVertexAttribPointer(
- posLocation,
- 2,
- GLES20.GL_FLOAT,
- /* normalized= */ false,
- /* stride= */ 0,
- TEXTURE_VERTICES);
- texLocations[0] = program.getAttributeArrayLocationAndEnable("in_tc_y");
- texLocations[1] = program.getAttributeArrayLocationAndEnable("in_tc_u");
- texLocations[2] = program.getAttributeArrayLocationAndEnable("in_tc_v");
- colorMatrixLocation = program.getUniformLocation("mColorConversion");
- GlUtil.checkGlError();
- setupTextures();
- GlUtil.checkGlError();
+ try {
+ program = new GlProgram(VERTEX_SHADER, FRAGMENT_SHADER);
+ int posLocation = program.getAttributeArrayLocationAndEnable("in_pos");
+ GLES20.glVertexAttribPointer(
+ posLocation,
+ 2,
+ GLES20.GL_FLOAT,
+ /* normalized= */ false,
+ /* stride= */ 0,
+ TEXTURE_VERTICES);
+ texLocations[0] = program.getAttributeArrayLocationAndEnable("in_tc_y");
+ texLocations[1] = program.getAttributeArrayLocationAndEnable("in_tc_u");
+ texLocations[2] = program.getAttributeArrayLocationAndEnable("in_tc_v");
+ colorMatrixLocation = program.getUniformLocation("mColorConversion");
+ GlUtil.checkGlError();
+ setupTextures();
+ GlUtil.checkGlError();
+ } catch (GlUtil.GlException e) {
+ Log.e(TAG, "Failed to set up the textures and program", e);
+ }
}
@Override
@@ -284,7 +291,11 @@ public final class VideoDecoderGLSurfaceView extends GLSurfaceView
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
- GlUtil.checkGlError();
+ try {
+ GlUtil.checkGlError();
+ } catch (GlUtil.GlException e) {
+ Log.e(TAG, "Failed to draw a frame", e);
+ }
}
public void setOutputBuffer(VideoDecoderOutputBuffer outputBuffer) {
@@ -300,13 +311,17 @@ public final class VideoDecoderGLSurfaceView extends GLSurfaceView
@RequiresNonNull("program")
private void setupTextures() {
- GLES20.glGenTextures(/* n= */ 3, yuvTextures, /* offset= */ 0);
- for (int i = 0; i < 3; i++) {
- GLES20.glUniform1i(program.getUniformLocation(TEXTURE_UNIFORMS[i]), i);
- GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i);
- GlUtil.bindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]);
+ try {
+ GLES20.glGenTextures(/* n= */ 3, yuvTextures, /* offset= */ 0);
+ for (int i = 0; i < 3; i++) {
+ GLES20.glUniform1i(program.getUniformLocation(TEXTURE_UNIFORMS[i]), i);
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i);
+ GlUtil.bindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]);
+ }
+ GlUtil.checkGlError();
+ } catch (GlUtil.GlException e) {
+ Log.e(TAG, "Failed to set up the textures", e);
}
- GlUtil.checkGlError();
}
}
}
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/spherical/ProjectionRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/spherical/ProjectionRenderer.java
index 2503733f36..5c808a8890 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/spherical/ProjectionRenderer.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/spherical/ProjectionRenderer.java
@@ -19,6 +19,7 @@ import static androidx.media3.common.util.GlUtil.checkGlError;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
+import android.util.Log;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.util.GlProgram;
@@ -45,6 +46,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
&& rightMesh.getSubMesh(0).textureId == Projection.SubMesh.VIDEO_TEXTURE_ID;
}
+ private static final String TAG = "ProjectionRenderer";
+
// Basic vertex & fragment shaders to render a mesh with 3D position & 2D texture data.
private static final String VERTEX_SHADER =
"uniform mat4 uMvpMatrix;\n"
@@ -115,12 +118,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Initializes of the GL components. */
public void init() {
- program = new GlProgram(VERTEX_SHADER, FRAGMENT_SHADER);
- mvpMatrixHandle = program.getUniformLocation("uMvpMatrix");
- uTexMatrixHandle = program.getUniformLocation("uTexMatrix");
- positionHandle = program.getAttributeArrayLocationAndEnable("aPosition");
- texCoordsHandle = program.getAttributeArrayLocationAndEnable("aTexCoords");
- textureHandle = program.getUniformLocation("uTexture");
+ try {
+ program = new GlProgram(VERTEX_SHADER, FRAGMENT_SHADER);
+ mvpMatrixHandle = program.getUniformLocation("uMvpMatrix");
+ uTexMatrixHandle = program.getUniformLocation("uTexMatrix");
+ positionHandle = program.getAttributeArrayLocationAndEnable("aPosition");
+ texCoordsHandle = program.getAttributeArrayLocationAndEnable("aTexCoords");
+ textureHandle = program.getUniformLocation("uTexture");
+ } catch (GlUtil.GlException e) {
+ Log.e(TAG, "Failed to initialize the program", e);
+ }
}
/**
@@ -154,7 +161,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
GLES20.glUniform1i(textureHandle, 0);
- checkGlError();
+ try {
+ checkGlError();
+ } catch (GlUtil.GlException e) {
+ Log.e(TAG, "Failed to bind uniforms", e);
+ }
// Load position data.
GLES20.glVertexAttribPointer(
@@ -164,7 +175,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
false,
Projection.POSITION_COORDS_PER_VERTEX * C.BYTES_PER_FLOAT,
meshData.vertexBuffer);
- checkGlError();
+ try {
+ checkGlError();
+ } catch (GlUtil.GlException e) {
+ Log.e(TAG, "Failed to load position data", e);
+ }
// Load texture data.
GLES20.glVertexAttribPointer(
@@ -174,17 +189,29 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
false,
Projection.TEXTURE_COORDS_PER_VERTEX * C.BYTES_PER_FLOAT,
meshData.textureBuffer);
- checkGlError();
+ try {
+ checkGlError();
+ } catch (GlUtil.GlException e) {
+ Log.e(TAG, "Failed to load texture data", e);
+ }
// Render.
GLES20.glDrawArrays(meshData.drawMode, /* first= */ 0, meshData.vertexCount);
- checkGlError();
+ try {
+ checkGlError();
+ } catch (GlUtil.GlException e) {
+ Log.e(TAG, "Failed to render", e);
+ }
}
/** Cleans up GL resources. */
public void shutdown() {
if (program != null) {
- program.delete();
+ try {
+ program.delete();
+ } catch (GlUtil.GlException e) {
+ Log.e(TAG, "Failed to delete the shader program", e);
+ }
}
}
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/spherical/SceneRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/spherical/SceneRenderer.java
index b2828a465e..5d262acd3e 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/spherical/SceneRenderer.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/spherical/SceneRenderer.java
@@ -26,6 +26,7 @@ import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.GlUtil;
+import androidx.media3.common.util.Log;
import androidx.media3.common.util.TimedValueQueue;
import androidx.media3.exoplayer.video.VideoFrameMetadataListener;
import java.util.Arrays;
@@ -36,6 +37,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* package */ final class SceneRenderer
implements VideoFrameMetadataListener, CameraMotionListener {
+ private static final String TAG = "SceneRenderer";
+
private final AtomicBoolean frameAvailable;
private final AtomicBoolean resetRotationAtNextFrame;
private final ProjectionRenderer projectionRenderer;
@@ -83,14 +86,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Initializes the renderer. */
public SurfaceTexture init() {
- // Set the background frame color. This is only visible if the display mesh isn't a full sphere.
- GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
- checkGlError();
+ try {
+ // Set the background frame color. This is only visible if the display mesh isn't a full
+ // sphere.
+ GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
+ checkGlError();
- projectionRenderer.init();
- checkGlError();
+ projectionRenderer.init();
+ checkGlError();
- textureId = GlUtil.createExternalTexture();
+ textureId = GlUtil.createExternalTexture();
+ } catch (GlUtil.GlException e) {
+ Log.e(TAG, "Failed to initialize the renderer", e);
+ }
surfaceTexture = new SurfaceTexture(textureId);
surfaceTexture.setOnFrameAvailableListener(surfaceTexture -> frameAvailable.set(true));
return surfaceTexture;
@@ -107,11 +115,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// glClear isn't strictly necessary when rendering fully spherical panoramas, but it can improve
// performance on tiled renderers by causing the GPU to discard previous data.
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
- checkGlError();
+ try {
+ checkGlError();
+ } catch (GlUtil.GlException e) {
+ Log.e(TAG, "Failed to draw a frame", e);
+ }
if (frameAvailable.compareAndSet(true, false)) {
Assertions.checkNotNull(surfaceTexture).updateTexImage();
- checkGlError();
+ try {
+ checkGlError();
+ } catch (GlUtil.GlException e) {
+ Log.e(TAG, "Failed to draw a frame", e);
+ }
if (resetRotationAtNextFrame.compareAndSet(true, false)) {
Matrix.setIdentityM(rotationMatrix, 0);
}
diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/BitmapTestUtil.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/BitmapTestUtil.java
index fda61200d1..2f38910bd8 100644
--- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/BitmapTestUtil.java
+++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/BitmapTestUtil.java
@@ -186,7 +186,8 @@ public class BitmapTestUtil {
* @param height The height of the pixel rectangle to read.
* @return A {@link Bitmap} with the framebuffer's values.
*/
- public static Bitmap createArgb8888BitmapFromCurrentGlFramebuffer(int width, int height) {
+ public static Bitmap createArgb8888BitmapFromCurrentGlFramebuffer(int width, int height)
+ throws GlUtil.GlException {
ByteBuffer rgba8888Buffer = ByteBuffer.allocateDirect(width * height * 4);
GLES20.glReadPixels(
0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, rgba8888Buffer);
@@ -206,7 +207,7 @@ public class BitmapTestUtil {
* @param bitmap A {@link Bitmap}.
* @return The identifier of the newly created texture.
*/
- public static int createGlTextureFromBitmap(Bitmap bitmap) {
+ public static int createGlTextureFromBitmap(Bitmap bitmap) throws GlUtil.GlException {
int texId = GlUtil.createTexture(bitmap.getWidth(), bitmap.getHeight());
// Put the flipped bitmap in the OpenGL texture as the bitmap's positive y-axis points down
// while OpenGL's positive y-axis points up.
diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CropPixelTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CropPixelTest.java
index f4830f1ab1..b0de6571fb 100644
--- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CropPixelTest.java
+++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CropPixelTest.java
@@ -52,13 +52,9 @@ public final class CropPixelTest {
public static final String CROP_LARGER_PNG_ASSET_PATH =
"media/bitmap/sample_mp4_first_frame/crop_larger.png";
- static {
- GlUtil.glAssertionsEnabled = true;
- }
-
- private final Context context = getApplicationContext();
- private final EGLDisplay eglDisplay = GlUtil.createEglDisplay();
- private final EGLContext eglContext = GlUtil.createEglContext(eglDisplay);
+ private Context context = getApplicationContext();
+ private @MonotonicNonNull EGLDisplay eglDisplay;
+ private @MonotonicNonNull EGLContext eglContext;
private @MonotonicNonNull SingleFrameGlTextureProcessor cropTextureProcessor;
private @MonotonicNonNull EGLSurface placeholderEglSurface;
private int inputTexId;
@@ -67,7 +63,9 @@ public final class CropPixelTest {
private int inputHeight;
@Before
- public void createTextures() throws IOException {
+ public void createGlObjects() throws IOException, GlUtil.GlException {
+ eglDisplay = GlUtil.createEglDisplay();
+ eglContext = GlUtil.createEglContext(eglDisplay);
Bitmap inputBitmap = BitmapTestUtil.readBitmap(ORIGINAL_PNG_ASSET_PATH);
inputWidth = inputBitmap.getWidth();
inputHeight = inputBitmap.getHeight();
@@ -77,11 +75,13 @@ public final class CropPixelTest {
}
@After
- public void release() {
+ public void release() throws GlUtil.GlException, FrameProcessingException {
if (cropTextureProcessor != null) {
cropTextureProcessor.release();
}
- GlUtil.destroyEglContext(eglDisplay, eglContext);
+ if (eglContext != null && eglDisplay != null) {
+ GlUtil.destroyEglContext(eglDisplay, eglContext);
+ }
}
@Test
@@ -156,12 +156,12 @@ public final class CropPixelTest {
assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
}
- private void setupOutputTexture(int outputWidth, int outputHeight) {
+ private void setupOutputTexture(int outputWidth, int outputHeight) throws GlUtil.GlException {
outputTexId = GlUtil.createTexture(outputWidth, outputHeight);
int frameBuffer = GlUtil.createFboForTexture(outputTexId);
GlUtil.focusFramebuffer(
- eglDisplay,
- eglContext,
+ checkNotNull(eglDisplay),
+ checkNotNull(eglContext),
checkNotNull(placeholderEglSurface),
frameBuffer,
outputWidth,
diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/MatrixTransformationProcessorPixelTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/MatrixTransformationProcessorPixelTest.java
index f25c09a16b..3255c093cb 100644
--- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/MatrixTransformationProcessorPixelTest.java
+++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/MatrixTransformationProcessorPixelTest.java
@@ -53,13 +53,9 @@ public final class MatrixTransformationProcessorPixelTest {
public static final String ROTATE_90_PNG_ASSET_PATH =
"media/bitmap/sample_mp4_first_frame/rotate90.png";
- static {
- GlUtil.glAssertionsEnabled = true;
- }
-
private final Context context = getApplicationContext();
- private final EGLDisplay eglDisplay = GlUtil.createEglDisplay();
- private final EGLContext eglContext = GlUtil.createEglContext(eglDisplay);
+ private @MonotonicNonNull EGLDisplay eglDisplay;
+ private @MonotonicNonNull EGLContext eglContext;
private @MonotonicNonNull SingleFrameGlTextureProcessor matrixTransformationFrameProcessor;
private int inputTexId;
private int outputTexId;
@@ -67,7 +63,9 @@ public final class MatrixTransformationProcessorPixelTest {
private int height;
@Before
- public void createTextures() throws IOException {
+ public void createGlObjects() throws IOException, GlUtil.GlException {
+ eglDisplay = GlUtil.createEglDisplay();
+ eglContext = GlUtil.createEglContext(eglDisplay);
Bitmap inputBitmap = BitmapTestUtil.readBitmap(ORIGINAL_PNG_ASSET_PATH);
width = inputBitmap.getWidth();
height = inputBitmap.getHeight();
@@ -81,11 +79,13 @@ public final class MatrixTransformationProcessorPixelTest {
}
@After
- public void release() {
+ public void release() throws GlUtil.GlException, FrameProcessingException {
if (matrixTransformationFrameProcessor != null) {
matrixTransformationFrameProcessor.release();
}
- GlUtil.destroyEglContext(eglDisplay, eglContext);
+ if (eglContext != null && eglDisplay != null) {
+ GlUtil.destroyEglContext(eglDisplay, eglContext);
+ }
}
@Test
diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/PresentationPixelTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/PresentationPixelTest.java
index 9465cd631c..867aed28ff 100644
--- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/PresentationPixelTest.java
+++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/PresentationPixelTest.java
@@ -60,13 +60,9 @@ public final class PresentationPixelTest {
public static final String ASPECT_RATIO_STRETCH_TO_FIT_WIDE_PNG_ASSET_PATH =
"media/bitmap/sample_mp4_first_frame/aspect_ratio_stretch_to_fit_wide.png";
- static {
- GlUtil.glAssertionsEnabled = true;
- }
-
- private final Context context = getApplicationContext();
- private final EGLDisplay eglDisplay = GlUtil.createEglDisplay();
- private final EGLContext eglContext = GlUtil.createEglContext(eglDisplay);
+ private Context context = getApplicationContext();
+ private @MonotonicNonNull EGLDisplay eglDisplay;
+ private @MonotonicNonNull EGLContext eglContext;
private @MonotonicNonNull SingleFrameGlTextureProcessor presentationTextureProcessor;
private @MonotonicNonNull EGLSurface placeholderEglSurface;
private int inputTexId;
@@ -75,7 +71,9 @@ public final class PresentationPixelTest {
private int inputHeight;
@Before
- public void createTextures() throws IOException {
+ public void createGlObjects() throws IOException, GlUtil.GlException {
+ eglDisplay = GlUtil.createEglDisplay();
+ eglContext = GlUtil.createEglContext(eglDisplay);
Bitmap inputBitmap = BitmapTestUtil.readBitmap(ORIGINAL_PNG_ASSET_PATH);
inputWidth = inputBitmap.getWidth();
inputHeight = inputBitmap.getHeight();
@@ -85,11 +83,13 @@ public final class PresentationPixelTest {
}
@After
- public void release() {
+ public void release() throws GlUtil.GlException, FrameProcessingException {
if (presentationTextureProcessor != null) {
presentationTextureProcessor.release();
}
- GlUtil.destroyEglContext(eglDisplay, eglContext);
+ if (eglContext != null && eglDisplay != null) {
+ GlUtil.destroyEglContext(eglDisplay, eglContext);
+ }
}
@Test
@@ -282,12 +282,12 @@ public final class PresentationPixelTest {
assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
}
- private void setupOutputTexture(int outputWidth, int outputHeight) {
+ private void setupOutputTexture(int outputWidth, int outputHeight) throws GlUtil.GlException {
outputTexId = GlUtil.createTexture(outputWidth, outputHeight);
int frameBuffer = GlUtil.createFboForTexture(outputTexId);
GlUtil.focusFramebuffer(
- eglDisplay,
- eglContext,
+ checkNotNull(eglDisplay),
+ checkNotNull(eglContext),
checkNotNull(placeholderEglSurface),
frameBuffer,
outputWidth,
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/Crop.java b/libraries/transformer/src/main/java/androidx/media3/transformer/Crop.java
index 511a5cf8c9..20f83f105a 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/Crop.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Crop.java
@@ -32,10 +32,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@UnstableApi
public final class Crop implements MatrixTransformation {
- static {
- GlUtil.glAssertionsEnabled = true;
- }
-
private final float left;
private final float right;
private final float bottom;
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderCompatibilityTransformation.java b/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderCompatibilityTransformation.java
index d12e1b5f82..05ff98fe0a 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderCompatibilityTransformation.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderCompatibilityTransformation.java
@@ -23,7 +23,6 @@ import android.graphics.Matrix;
import android.util.Size;
import androidx.media3.common.C;
import androidx.media3.common.Format;
-import androidx.media3.common.util.GlUtil;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
@@ -38,10 +37,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// TODO(b/218488308): Allow reconfiguration of the output size, as encoders may not support the
// requested output resolution.
- static {
- GlUtil.glAssertionsEnabled = true;
- }
-
private int outputRotationDegrees;
private @MonotonicNonNull Matrix transformationMatrix;
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/ExternalTextureProcessor.java b/libraries/transformer/src/main/java/androidx/media3/transformer/ExternalTextureProcessor.java
index 823b883668..693f5533bc 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/ExternalTextureProcessor.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ExternalTextureProcessor.java
@@ -28,10 +28,6 @@ import java.io.IOException;
/** Copies frames from an external texture and applies color transformations for HDR if needed. */
/* package */ class ExternalTextureProcessor extends SingleFrameGlTextureProcessor {
- static {
- GlUtil.glAssertionsEnabled = true;
- }
-
private static final String VERTEX_SHADER_TEX_TRANSFORM_PATH =
"shaders/vertex_shader_tex_transform_es2.glsl";
private static final String VERTEX_SHADER_TEX_TRANSFORM_ES3_PATH =
@@ -54,10 +50,10 @@ import java.io.IOException;
* Creates a new instance.
*
* @param enableExperimentalHdrEditing Whether to attempt to process the input as an HDR signal.
- * @throws IOException If a problem occurs while reading shader files.
+ * @throws FrameProcessingException If a problem occurs while reading shader files.
*/
public ExternalTextureProcessor(Context context, boolean enableExperimentalHdrEditing)
- throws IOException {
+ throws FrameProcessingException {
String vertexShaderFilePath =
enableExperimentalHdrEditing
? VERTEX_SHADER_TEX_TRANSFORM_ES3_PATH
@@ -66,7 +62,11 @@ import java.io.IOException;
enableExperimentalHdrEditing
? FRAGMENT_SHADER_COPY_EXTERNAL_YUV_ES3_PATH
: FRAGMENT_SHADER_COPY_EXTERNAL_PATH;
- glProgram = new GlProgram(context, vertexShaderFilePath, fragmentShaderFilePath);
+ try {
+ glProgram = new GlProgram(context, vertexShaderFilePath, fragmentShaderFilePath);
+ } catch (IOException | GlUtil.GlException e) {
+ throw new FrameProcessingException(e);
+ }
// Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y.
glProgram.setBufferAttribute(
"aFramePosition",
@@ -109,15 +109,19 @@ import java.io.IOException;
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
GlUtil.checkGlError();
} catch (GlUtil.GlException e) {
- throw new FrameProcessingException(e);
+ throw new FrameProcessingException(e, presentationTimeUs);
}
}
@Override
- public void release() {
+ public void release() throws FrameProcessingException {
super.release();
if (glProgram != null) {
- glProgram.delete();
+ try {
+ glProgram.delete();
+ } catch (GlUtil.GlException e) {
+ throw new FrameProcessingException(e);
+ }
}
}
}
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessingException.java b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessingException.java
index 6d413fd38a..1d1d5d47ed 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessingException.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessingException.java
@@ -22,6 +22,26 @@ import androidx.media3.common.util.UnstableApi;
@UnstableApi
public final class FrameProcessingException extends Exception {
+ /**
+ * Wraps the given exception in a {@code FrameProcessingException} if it is not already a {@code
+ * FrameProcessingException} and returns the exception otherwise.
+ */
+ public static FrameProcessingException from(Exception exception) {
+ return from(exception, /* presentationTimeUs= */ C.TIME_UNSET);
+ }
+
+ /**
+ * Wraps the given exception in a {@code FrameProcessingException} with the given timestamp if it
+ * is not already a {@code FrameProcessingException} and returns the exception otherwise.
+ */
+ public static FrameProcessingException from(Exception exception, long presentationTimeUs) {
+ if (exception instanceof FrameProcessingException) {
+ return (FrameProcessingException) exception;
+ } else {
+ return new FrameProcessingException(exception, presentationTimeUs);
+ }
+ }
+
/**
* The microsecond timestamp of the frame being processed while the exception occurred or {@link
* C#TIME_UNSET} if unknown.
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessorChain.java b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessorChain.java
index c4ded83fd9..0b2bea5a34 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessorChain.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessorChain.java
@@ -41,7 +41,6 @@ import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
-import java.io.IOException;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
@@ -65,10 +64,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// TODO(b/227625423): Factor out FrameProcessor interface and rename this class to GlFrameProcessor.
/* package */ final class FrameProcessorChain {
- static {
- GlUtil.glAssertionsEnabled = true;
- }
-
/**
* Listener for asynchronous frame processing events.
*
@@ -150,7 +145,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
List