场景
在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。
-
编写一个通用的自定义 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();
}
}
}
-
在 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 信号) 和音频时间戳,实现完美的音视频同步。