public final class

EGLSurfaceTexture

extends java.lang.Object

implements java.lang.Runnable

 java.lang.Object

↳androidx.media3.common.util.EGLSurfaceTexture

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-common', version: '1.0.0-alpha03'

  • groupId: androidx.media3
  • artifactId: media3-common
  • version: 1.0.0-alpha03

Artifact androidx.media3:media3-common:1.0.0-alpha03 it located at Google repository (https://maven.google.com/)

Overview

Generates a SurfaceTexture using EGL/GLES functions.

Summary

Fields
public static final intSECURE_MODE_NONE

No secure EGL surface and context required.

public static final intSECURE_MODE_PROTECTED_PBUFFER

Creating a secure surface backed by a pixel buffer.

public static final intSECURE_MODE_SURFACELESS_CONTEXT

Creating a surfaceless, secured EGL context.

Constructors
publicEGLSurfaceTexture(Handler handler)

publicEGLSurfaceTexture(Handler handler, EGLSurfaceTexture.TextureImageListener callback)

Methods
public SurfaceTexturegetSurfaceTexture()

Returns the wrapped SurfaceTexture.

public voidinit(int secureMode)

Initializes required EGL parameters and creates the SurfaceTexture.

public voidonFrameAvailable(SurfaceTexture surfaceTexture)

public voidrelease()

Releases all allocated resources.

public voidrun()

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final int SECURE_MODE_NONE

No secure EGL surface and context required.

public static final int SECURE_MODE_SURFACELESS_CONTEXT

Creating a surfaceless, secured EGL context.

public static final int SECURE_MODE_PROTECTED_PBUFFER

Creating a secure surface backed by a pixel buffer.

Constructors

public EGLSurfaceTexture(Handler handler)

Parameters:

handler: The Handler that will be used to call SurfaceTexture to update images on the SurfaceTexture. Note that EGLSurfaceTexture.init(int) has to be called on the same looper thread as the Handler's looper.

public EGLSurfaceTexture(Handler handler, EGLSurfaceTexture.TextureImageListener callback)

Parameters:

handler: The Handler that will be used to call SurfaceTexture to update images on the SurfaceTexture. Note that EGLSurfaceTexture.init(int) has to be called on the same looper thread as the looper of the Handler.
callback: The EGLSurfaceTexture.TextureImageListener to be called when the texture image on SurfaceTexture has been updated. This callback will be called on the same handler thread as the handler.

Methods

public void init(int secureMode)

Initializes required EGL parameters and creates the SurfaceTexture.

Parameters:

secureMode: The EGLSurfaceTexture.SecureMode to be used for EGL surface.

public void release()

Releases all allocated resources.

public SurfaceTexture getSurfaceTexture()

Returns the wrapped SurfaceTexture. This can only be called after EGLSurfaceTexture.init(int).

public void onFrameAvailable(SurfaceTexture surfaceTexture)

public void run()

Source

/*
 * 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 androidx.media3.common.util;

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.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)
@UnstableApi
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;

  /** 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;

  @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 (Util.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 Assertions.checkNotNull(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);
    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,
              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);
      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);
    GlUtil.checkGlError();
  }
}