Android 视频分屏性能优化——GLContext共享

23 阅读10分钟

场景

在Android平台。在做分屏应用时,特别是低端设备上,为了实现极致的性能优化,就需要利用GLContext共享来实现。

假设有2个Surface组件,一个是GLSurfaceView,另一个也是GLSurfaceView,前一个我们叫它A,后一个我们称它B,为了实现后台渲染,我们利用EGL实现了一个EGLContext和EGL Texture。

我们的需求是,在视频解码后,能够实现A、B极致的性能渲染。

这是一个非常经典且高级的应用场景!利用“星形”共享架构来实现视频分屏渲染,是性能最高、内存占用最低的做法。

在视频播放场景中,核心链路是: 视频播放器 (解码)

→ Surface → SurfaceTexture → OES 纹理 (Root Context) → 共享给 A 和 B 渲染。

这里有三个极其重要的 OpenGL 机制需要处理:

  • OES 纹理:Android 视频解码出来的数据通常是 YUV 格式,不能直接用普通的 GL_TEXTURE_2D 渲染,必须使用 GL_TEXTURE_EXTERNAL_OES。
  • SurfaceTexture 的线程限制:SurfaceTexture.updateTexImage() 必须在创建它的 EGLContext 所在的线程调用。因此,我们需要为 Root Context 开启一个后台线程。
  • 纹理矩阵(Transform Matrix):视频解码到纹理后,方向通常是颠倒或错位的,必须使用 SurfaceTexture 提供的矩阵进行坐标变换。

OES纹理

要实现分屏渲染,首先是离屏能力必须具备

public class VideoTextureManager implements Handler.Callback{
  private HandlerThread mGLThread;
  private Handler mGLHandler;
  
  private SurfaceTexture mSurfaceTexture;
  private Surface mSurface;
  private int mOesTextureId;
  private float[] mTransformMatrix = new float[16];
    private int MSG_UPDATE_TEXTURE = 0x01;

    @Override
    public boolean handleMessage(@NonNull Message msg) {
        if(msg.what == MSG_UPDATE_TEXTURE){
            try {
                SurfaceTexture surfaceTexture = (SurfaceTexture) msg.obj;
                surfaceTexture.updateTexImage();
                surfaceTexture.getTransformMatrix(mTransformMatrix);
                GLES20.glFlush();
                // 通知 A 和 B 渲染
                if (mListener != null) {
                    mListener.onFrameUpdated(mOesTextureId, mTransformMatrix);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return false;
    }


    // 回调接口,通知 View A 和 B 刷新
  public interface OnFrameUpdatedListener {
      void onFrameUpdated(int textureId, float[] matrix);
  }
  private OnFrameUpdatedListener mListener;

  public VideoTextureManager(OnFrameUpdatedListener listener) {
      this.mListener = listener;
      // 1. 启动一个后台 GL 线程
      mGLThread = new HandlerThread("RootGLThread");
      mGLThread.start();
      mGLHandler = new Handler(mGLThread.getLooper());

      // 2. 在后台线程初始化 EGL 环境并创建纹理
      mGLHandler.post(this::initGLAndSurface);
  }

  private void initGLAndSurface() {
      EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
      EGLContext rootContext = RootEGLManager.getInstance().getRootContext();

      // 为了让 Root Context 能在这个线程 makeCurrent,我们需要创建一个 1x1 的离屏 Pbuffer Surface
      int[] pbufferAttribs = {
              EGL14.EGL_WIDTH, 1,
              EGL14.EGL_HEIGHT, 1,
              EGL10.EGL_NONE
      };
      // 注意:这里需要获取 RootEGLManager 中的 EGLConfig,为了简便,已经在 RootEGLManager 中暴露了 getConfig()
      EGLConfig config = RootEGLManager.getInstance().getConfig();
      EGLSurface pbufferSurface = EGL14.eglCreatePbufferSurface(display, config, pbufferAttribs, 0);

      // 绑定 Root Context 到当前后台线程
      EGL14.eglMakeCurrent(display, pbufferSurface, pbufferSurface, rootContext);

      // 3. 创建 OES 纹理
      int[] textures = new int[1];
      GLES20.glGenTextures(1, textures, 0);
      mOesTextureId = textures[0];
      GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mOesTextureId);
      GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
      GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
      GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
      GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

      // 4. 创建 SurfaceTexture 和 Surface
      mSurfaceTexture = new SurfaceTexture(mOesTextureId);
      mSurface = new Surface(mSurfaceTexture);

      // 5. 监听视频帧到达
      mSurfaceTexture.setOnFrameAvailableListener(surfaceTexture -> {
          // 必须在当前 GL 线程调用 updateTexImage
          Message.obtain(mGLHandler, MSG_UPDATE_TEXTURE,surfaceTexture).sendToTarget();
      });
  }

  // 提供给播放器 (如 MediaPlayer / ExoPlayer)
  public Surface getSurface() {
      return mSurface;
  }

  public void release() {
      mGLHandler.post(() -> {
          if (mSurface != null) mSurface.release();
          if (mSurfaceTexture != null) mSurfaceTexture.release();
          mGLThread.quitSafely();
      });
  }
}

第二步:编写支持 OES 的 Renderer (A 和 B 可以共用同一个类) 因为是视频渲染,着色器必须使用 #extension GL_OES_EGL_image_external : require。

public class VideoRenderer implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {

    // --- 接口回调:用于通知外部 Surface 已经准备好,可以传给播放器了 ---
    public interface OnSurfaceReadyListener {
        void onSurfaceReady(Surface surface);
    }
    private OnSurfaceReadyListener mSurfaceReadyListener;

    // --- OpenGL 核心组件 ---
    private int mProgram;
    private int mTextureID;
    private SurfaceTexture mSurfaceTexture;
    private Surface mSurface;
    private GLSurfaceView mGLSurfaceView;

    // --- 矩阵与顶点数据 ---
    private float[] mSTMatrix = new float[16]; // SurfaceTexture 的变换矩阵
    private float[] mMVPMatrix = new float[16]; // 投影与视图矩阵 (用于缩放/旋转)

    private FloatBuffer mVertexBuffer;
    private FloatBuffer mTextureBuffer;

    // 顶点坐标 (全屏)
    private final float[] VERTEX_DATA = {
            -1.0f, -1.0f,
             1.0f, -1.0f,
            -1.0f,  1.0f,
             1.0f,  1.0f
    };

    // 纹理坐标 (标准坐标系)
    private final float[] TEXTURE_DATA = {
            0.0f, 0.0f,
            1.0f, 0.0f,
            0.0f, 1.0f,
            1.0f, 1.0f
    };

    // --- 着色器代码 ---
    // 顶点着色器
    private static final String VERTEX_SHADER =
            "uniform mat4 uMVPMatrix;\n" +
            "uniform mat4 uSTMatrix;\n" +
            "attribute vec4 aPosition;\n" +
            "attribute vec4 aTextureCoord;\n" +
            "varying vec2 vTextureCoord;\n" +
            "void main() {\n" +
            "  gl_Position = uMVPMatrix * aPosition;\n" +
            "  vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
            "}\n";

    // 片段着色器 (注意:必须使用 GL_OES_EGL_image_external 扩展来渲染视频帧)
    private static final String FRAGMENT_SHADER =
            "#extension GL_OES_EGL_image_external : require\n" +
            "precision mediump float;\n" +
            "varying vec2 vTextureCoord;\n" +
            "uniform samplerExternalOES sTexture;\n" +
            "void main() {\n" +
            "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
            "}\n";

    private int muMVPMatrixHandle;
    private int muSTMatrixHandle;
    private int maPositionHandle;
    private int maTextureHandle;

    private boolean mUpdateSurface = false;

    public VideoRenderer(GLSurfaceView glSurfaceView, OnSurfaceReadyListener listener) {
        mGLSurfaceView = glSurfaceView;
        mSurfaceReadyListener = listener;

        // 初始化 Buffer
        mVertexBuffer = ByteBuffer.allocateDirect(VERTEX_DATA.length * 4)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mVertexBuffer.put(VERTEX_DATA).position(0);

        mTextureBuffer = ByteBuffer.allocateDirect(TEXTURE_DATA.length * 4)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mTextureBuffer.put(TEXTURE_DATA).position(0);

        Matrix.setIdentityM(mSTMatrix, 0);
        Matrix.setIdentityM(mMVPMatrix, 0);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // 1. 编译着色器并链接 Program
        mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
        if (mProgram == 0) {
            throw new RuntimeException("Failed to create program");
        }

        // 获取 Shader 中的变量句柄
        maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
        maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
        muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");

        // 2. 创建 OES 纹理 (专门用于接收视频帧)
        int[] textures = new int[1];
        GLES20.glGenTextures(1, textures, 0);
        mTextureID = textures[0];
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
        // 设置纹理过滤参数
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

        // 3. 将 OES 纹理绑定到 SurfaceTexture
        mSurfaceTexture = new SurfaceTexture(mTextureID);
        mSurfaceTexture.setOnFrameAvailableListener(this);

        // 4. 创建 Surface 并回调给外部播放器
        mSurface = new Surface(mSurfaceTexture);
        if (mSurfaceReadyListener != null) {
            // 注意:这里是在 GL 线程回调的,如果外部需要操作 UI,请切换到主线程
            mSurfaceReadyListener.onSurfaceReady(mSurface);
        }
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
        // 这里可以根据视频的宽高比和屏幕的宽高比,计算 mMVPMatrix 来实现 CenterCrop 或 FitCenter
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        synchronized (this) {
            if (mUpdateSurface) {
                // 核心:从图像流中更新纹理图像到最新的帧
                mSurfaceTexture.updateTexImage();
                // 获取最新的变换矩阵(处理视频方向、镜像等)
                mSurfaceTexture.getTransformMatrix(mSTMatrix);
                mUpdateSurface = false;
            }
        }

        // 清屏为黑色
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        // 使用着色器程序
        GLES20.glUseProgram(mProgram);

        // 激活纹理单元并绑定 OES 纹理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);

        // 传入顶点坐标
        mVertexBuffer.position(0);
        GLES20.glVertexAttribPointer(maPositionHandle, 2, GLES20.GL_FLOAT, false, 0, mVertexBuffer);
        GLES20.glEnableVertexAttribArray(maPositionHandle);

        // 传入纹理坐标
        mTextureBuffer.position(0);
        GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false, 0, mTextureBuffer);
        GLES20.glEnableVertexAttribArray(maTextureHandle);

        // 传入矩阵
        GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
        GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);

        // 绘制矩形 (视频画面)
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    }

    @Override
    public synchronized void onFrameAvailable(SurfaceTexture surfaceTexture) {
        // 当播放器解码出新的一帧时,会回调这里
        mUpdateSurface = true;
        // 通知 GLSurfaceView 请求重绘 (触发 onDrawFrame)
        mGLSurfaceView.requestRender();
    }

    // --- 辅助方法:编译 Shader ---
    private int createProgram(String vertexSource, String fragmentSource) {
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
        int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
        int program = GLES20.glCreateProgram();
        if (program != 0) {
            GLES20.glAttachShader(program, vertexShader);
            GLES20.glAttachShader(program, pixelShader);
            GLES20.glLinkProgram(program);
            int[] linkStatus = new int[1];
            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
            if (linkStatus[0] != GLES20.GL_TRUE) {
                GLES20.glDeleteProgram(program);
                program = 0;
            }
        }
        return program;
    }

    private int loadShader(int shaderType, String source) {
        int shader = GLES20.glCreateShader(shaderType);
        if (shader != 0) {
            GLES20.glShaderSource(shader, source);
            GLES20.glCompileShader(shader);
        }
        return shader;
    }

    // --- 资源释放 ---
    public void release() {
        if (mSurface != null) {
            mSurface.release();
            mSurface = null;
        }
        if (mSurfaceTexture != null) {
            mSurfaceTexture.release();
            mSurfaceTexture = null;
        }
    }
}

第三步:在 Activity 中组装 (播放器 + 共享渲染) 在这里,我们将 GLSurfaceView 设置为 按需渲染 (RENDERMODE_WHEN_DIRTY)。只有当视频解码出新的一帧时,才通知 A 和 B 刷新,这样极其省电。

public class VideoSplitActivity extends AppCompatActivity {

    private GLSurfaceView glViewA;
    private GLSurfaceView glViewB;
    private VideoRenderer rendererA;
    private VideoRenderer rendererB;
    
    private VideoTextureManager mVideoTextureManager;
    private MediaPlayer mMediaPlayer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_split);

        glViewA = findViewById(R.id.gl_view_a);
        glViewB = findViewById(R.id.gl_view_b);

        // 1. 初始化 Root Context (确保在 View 初始化前调用)
        EGLContext rootContext = RootEGLManager.getInstance().getRootContext();

        // 2. 配置 View A
        rendererA = new VideoRenderer();
        glViewA.setEGLContextClientVersion(2);
        glViewA.setEGLContextFactory(new SharedEGLContextFactory(rootContext));
        glViewA.setRenderer(rendererA);
        glViewA.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 按需渲染

        // 3. 配置 View B
        rendererB = new VideoRenderer();
        glViewB.setEGLContextClientVersion(2);
        glViewB.setEGLContextFactory(new SharedEGLContextFactory(rootContext));
        glViewB.setRenderer(rendererB);
        glViewB.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 按需渲染

        // 4. 初始化视频纹理管理器
        mVideoTextureManager = new VideoTextureManager((textureId, matrix) -> {
            // 当视频有一帧新数据到达时:
            // 更新 A 和 B 的数据
            rendererA.updateFrame(textureId, matrix);
            rendererB.updateFrame(textureId, matrix);
            
            // 唤醒 A 和 B 进行绘制
            glViewA.requestRender();
            glViewB.requestRender();
        });

        // 5. 延迟一点时间等待 Surface 创建完成,然后初始化播放器
        glViewA.postDelayed(this::setupPlayer, 500);
    }

    private void setupPlayer() {
        mMediaPlayer = new MediaPlayer();
        try {
            // 设置视频源 (替换为你的视频路径)
            mMediaPlayer.setDataSource("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4");
            
            // 将 Root Context 生成的 Surface 交给播放器!
            mMediaPlayer.setSurface(mVideoTextureManager.getSurface());
            
            mMediaPlayer.setLooping(true);
            mMediaPlayer.prepareAsync();
            mMediaPlayer.setOnPreparedListener(MediaPlayer::start);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mMediaPlayer != null) {
            mMediaPlayer.release();
        }
        if (mVideoTextureManager != null) {
            mVideoTextureManager.release();
        }
        RootEGLManager.getInstance().release();
    }
}

架构优势总结

  • 零拷贝 (Zero-Copy):视频解码器直接将数据写入显存(OES纹理),View A 和 View B 直接读取同一块显存进行渲染。整个过程 CPU 完全不参与像素搬运,性能极高。
  • 完美的同步:利用 SurfaceTexture.setOnFrameAvailableListener 配合 GLSurfaceView.requestRender(),实现了视频帧驱动渲染。视频每解码一帧,屏幕才刷新一次,避免了无效的 GPU 绘制。
  • 各自独立的后处理:虽然 A 和 B 共享了同一个视频纹理,但它们有各自的 VideoRenderer。这意味着我们可以给 A 加一个黑白滤镜(修改 A 的 Fragment Shader),给 B 加一个镜像翻转(修改 B 的 Vertex Shader),两者互不干扰!

SurfaceView和TextureView适配

当前,我们仅仅实现了GLSurfaceView的渲染性能优化,那SurfaceView和TextureView是否也可以优化呢?

其实是支持的,在很多音视频应用中,几乎全都是用普通的 SurfaceView 或 TextureView,很少是 GLSurfaceView。

之所以会有这个疑问,是因为前面的代码使用了 GLSurfaceView 提供的便捷 API (setEGLContextFactory) 来实现共享。普通的 SurfaceView 和 TextureView 确实没有这个 API。

但是,GLSurfaceView 本质上只是 SurfaceView + 一个自带的后台 EGL 线程。 只要我们自己接管 EGL 的初始化和线程管理,就可以把任何共享的 OpenGL 画面渲染到普通的 SurfaceView 或 TextureView 上。

方案

在普通的 View 上实现星形共享架构。

核心原理:万物皆可 eglCreateWindowSurface 在 OpenGL ES 的世界里,它根本不知道什么是 SurfaceView 或 TextureView。它只认识一个东西:NativeWindow(原生窗口)。

在 Android 中,无论是 SurfaceView 提供的 Surface,还是 TextureView 提供的 SurfaceTexture,都可以作为 NativeWindow 传给 EGL,用来创建一个 EGLSurface(OpenGL 的画布)。

SurfaceView:通过 surfaceHolder.getSurface() 获取。 TextureView:通过 onSurfaceTextureAvailable 回调拿到 SurfaceTexture,然后 new Surface(surfaceTexture) 获取。 如何实现?(自定义 EGL 渲染线程) 既然不用 GLSurfaceView,我们需要为 View A 和 View B 各自写一个简单的后台渲染线程。这个线程接收 Root Context 和目标 View 的 Surface。

  1. 编写一个通用的自定义 GL 渲染线程 这个线程的核心任务是:拿着 Root Context 作为共享源,拿着 View 的 Surface 作为画布,建立自己的 EGL 环境并循环渲染。

public class CustomGLRenderThread extends Thread {
    private Surface mSurface;           // 来自 SurfaceView 或 TextureView
    private EGLContext mRootContext;    // 我们的星形架构中心节点
    private VideoRenderer mRenderer;    // 之前写的渲染逻辑
    
    private boolean isRunning = true;
    private final Object mLock = new Object();
    private boolean mHasNewFrame = false;

    public CustomGLRenderThread(Surface surface, EGLContext rootContext) {
        this.mSurface = surface;
        this.mRootContext = rootContext;
        this.mRenderer = new VideoRenderer();
    }

    @Override
    public void run() {
        // 1. 初始化 EGL 环境 (核心:共享 RootContext,绑定目标 Surface)
        EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
        EGL14.eglInitialize(display, null, 0, null, 0);

        // 获取配置 (省略具体的 chooseConfig 代码,需与 RootContext 保持一致)
        EGLConfig config = getConfig(display); 

        // 【关键点 1】:创建 Context 时,传入 mRootContext 作为共享上下文!
        int[] ctxAttribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE };
        EGLContext myContext = EGL14.eglCreateContext(display, config, mRootContext, ctxAttribs, 0);

        // 【关键点 2】:将 Android 的 Surface 变成 OpenGL 的画布
        int[] surfaceAttribs = { EGL14.EGL_NONE };
        EGLSurface eglSurface = EGL14.eglCreateWindowSurface(display, config, mSurface, surfaceAttribs, 0);

        // 绑定当前线程
        EGL14.eglMakeCurrent(display, eglSurface, eglSurface, myContext);

        // 2. 初始化渲染器 (编译着色器等)
        mRenderer.onSurfaceCreated(null, null);
        // 假设宽高已知,或者通过外部传入
        mRenderer.onSurfaceChanged(null, 1080, 1920); 

        // 3. 进入渲染循环
        while (isRunning) {
            synchronized (mLock) {
                while (!mHasNewFrame && isRunning) {
                    try {
                        mLock.wait(); // 等待视频帧到来的唤醒信号
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                mHasNewFrame = false;
            }

            if (!isRunning) break;

            // 绘制画面 (直接读取共享的 OES 纹理)
            mRenderer.onDrawFrame(null);

            // 【关键点 3】:将画好的内容提交到屏幕显示!
            // 这相当于 GLSurfaceView 内部自动调用的方法
            EGL14.eglSwapBuffers(display, eglSurface); 
        }

        // 4. 退出清理
        EGL14.eglMakeCurrent(display, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
        EGL14.eglDestroySurface(display, eglSurface);
        EGL14.eglDestroyContext(display, myContext);
        mSurface.release();
    }

    // 供外部 (VideoTextureManager) 调用,通知有新画面了
    public void requestRender(int textureId, float[] matrix) {
        mRenderer.updateFrame(textureId, matrix);
        synchronized (mLock) {
            mHasNewFrame = true;
            mLock.notifyAll(); // 唤醒渲染线程
        }
    }

    public void release() {
        isRunning = false;
        synchronized (mLock) {
            mLock.notifyAll();
        }
    }
}
  1. 在 Activity 中结合普通的 SurfaceView / TextureView 使用 现在,我们可以完全抛弃 GLSurfaceView 了。

public class VideoSplitActivity extends AppCompatActivity {
    private SurfaceView surfaceViewA;
    private TextureView textureViewB;
    
    private CustomGLRenderThread threadA;
    private CustomGLRenderThread threadB;
    private VideoTextureManager mVideoTextureManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_split);

        surfaceViewA = findViewById(R.id.surface_view_a);
        textureViewB = findViewById(R.id.texture_view_b);

        EGLContext rootContext = RootEGLManager.getInstance().getRootContext();

        // 监听 SurfaceView A 的创建
        surfaceViewA.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                // 拿到 Surface,启动 A 的专属渲染线程
                threadA = new CustomGLRenderThread(holder.getSurface(), rootContext);
                threadA.start();
            }
            // ... 省略 changed 和 destroyed
        });

        // 监听 TextureView B 的创建
        textureViewB.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                // 拿到 SurfaceTexture 包装成 Surface,启动 B 的专属渲染线程
                Surface bSurface = new Surface(surface);
                threadB = new CustomGLRenderThread(bSurface, rootContext);
                threadB.start();
            }
            // ... 省略其他回调
        });

        // 初始化视频纹理管理器 (和之前一样)
        mVideoTextureManager = new VideoTextureManager((textureId, matrix) -> {
            // 视频解码出新帧了,通知 A 和 B 的线程去画!
            if (threadA != null) threadA.requestRender(textureId, matrix);
            if (threadB != null) threadB.requestRender(textureId, matrix);
        });
        
        // ... 初始化 MediaPlayer 绑定 mVideoTextureManager.getSurface()
    }
}

虽然写 CustomGLRenderThread 看起来代码变多了,但它带来了巨大的好处:

  • 生命周期解耦:GLSurfaceView 的 EGL Context 生命周期死死绑定在 View 上。View 销毁(比如切后台、屏幕旋转),Context 就没了,纹理全得重新加载。自己管理 EGL 线程,可以让 EGL Context 活在后台,View 销毁时只销毁 EGLSurface,View 重建时重新 eglCreateWindowSurface 即可,实现真正的无缝黑屏切换。
  • 支持 TextureView:GLSurfaceView 只能基于 SurfaceView。如果需要对视频画面做复杂的 View 动画(平移、缩放、透明度、放进 ScrollView 里滑动),SurfaceView 表现很差(会穿透、黑边),而 TextureView 表现完美。自己管理 EGL,就可以轻松把画面渲染到 TextureView 上。
  • 精准的音视频同步: 在while(isRunning) 循环中,可以极其精确地控制 eglSwapBuffers 的调用时机,配合 Choreographer (VSync 信号) 和音频时间戳,实现完美的音视频同步。