OpenGL ES 画中画(PIP)预览+录制

Android OpenGL ES 实现画中画及MediaCodec录制
该博客详细介绍了如何在Android中利用OpenGL ES实现画中画效果以及结合MediaCodec进行视频录制。通过创建FBO Drawer和Screen Drawer类,将两个视频纹理绘制到FBO,然后在预览界面和编码器Surface上共享数据。整个流程包括EGL上下文的初始化、线程绑定、渲染循环和资源销毁。此外,还提供了RecordGLRenderer类用于MediaCodec的OpenGL渲染。
本文章已经生成可运行项目,

上一节提到画中画实现,实际上就是将两个视频纹理绘制到 FBO,那么预览、录制,其实就是将 FBO 绘制到预览界面,并共享 egl 将数据同时绘制到编码器 Surface 一份就可以了。

下图是整个架构的大体流程。
在这里插入图片描述

先来建立一个用来绘制预览和录制的 Drawer,重点看片段着色器的代码,输入的纹理类型是 sampler2D,这是 FBO 输出的纹理类型。其中录制 surface 是 MediaCodec 提供的,录制界面绘制共享了预览用的 egl(mPreviewEGLContext)。

/**
 * author : liuhongwei
 * e-mail : 
 * date   : 2021/7/26 18:41
 * desc   : 屏幕绘制
 * version: 1.0
 */
public class ScreenDrawer {

    private static final String TAG = "ScreenDrawer";

    private final float[] mVertexCoors = new float[]{
            -1.0F, -1.0F,
            1.0F, -1.0F,
            -1.0F, 1.0F,
            1.0F, 1.0F};
    private final float[] mTextureCoors = new float[]{
            0.0F, 1.0F,
            1.0F, 1.0F,
            0.0F, 0.0F,
            1.0F, 0.0F};

    private int mProgram = -1;
    private int mVertexPosHandler = -1;
    private int mVertexMatrixHandler = -1;
    private int mTexturePosHandler = -1;
    private int mTextureHandler = -1;
    private FloatBuffer mVertexBuffer;
    private FloatBuffer mTextureBuffer;

    private final float[] mMatrix = new float[16];

    private int mViewPortWidth, mViewPortHeight;

    public ScreenDrawer(){
        initPos();
        Matrix.setIdentityM(mMatrix, 0);
        // Y轴翻转
        Matrix.scaleM(mMatrix, 0, 1, -1, 1);
    }

    private void initPos() {
        ByteBuffer bb = ByteBuffer.allocateDirect(mVertexCoors.length * 4);
        bb.order(ByteOrder.nativeOrder());
        //将坐标数据转换为FloatBuffer,用以传入给OpenGL ES程序
        mVertexBuffer = bb.asFloatBuffer();
        mVertexBuffer.put(mVertexCoors);
        mVertexBuffer.position(0);

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

    private void createGLPrg() {
        if (mProgram == -1) {
            int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, getVertexShader());
            int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, getFragmentShader());
            //创建OpenGL ES程序,注意:需要在OpenGL渲染线程中创建,否则无法渲染
            mProgram = GLES20.glCreateProgram();
            //将顶点着色器加入到程序
            GLES20.glAttachShader(mProgram, vertexShader);
            //将片元着色器加入到程序中
            GLES20.glAttachShader(mProgram, fragmentShader);
            //连接到着色器程序
            GLES20.glLinkProgram(mProgram);

            mVertexPosHandler = GLES20.glGetAttribLocation(mProgram, "aPosition");
            mVertexMatrixHandler = GLES20.glGetUniformLocation(mProgram, "uMatrix");
            mTextureHandler = GLES20.glGetUniformLocation(mProgram, "uTexture");
            mTexturePosHandler = GLES20.glGetAttribLocation(mProgram, "aCoordinate");
        }
        //使用OpenGL程序
        GLES20.glUseProgram(mProgram);
    }

    private void activateTexture(int textureId) {
        //激活指定纹理单元
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        //绑定纹理ID到纹理单元
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
        //将激活的纹理单元传递到着色器里面
        GLES20.glUniform1i(mTextureHandler, 0);
        //配置边缘过渡参数
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR * 1.0f);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR * 1.0f);
        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);
    }

    public void setViewWH(int viewPortWidth, int viewPortHeight) {
        mViewPortWidth = viewPortWidth;
        mViewPortHeight = viewPortHeight;
    }

    public void doDraw(int textureId) {
        GLES20.glViewport(0, 0, mViewPortWidth, mViewPortHeight);
        createGLPrg();
        activateTexture(textureId);
        //启用顶点的句柄
        GLES20.glEnableVertexAttribArray(mVertexPosHandler);
        GLES20.glEnableVertexAttribArray(mTexturePosHandler);
        GLES20.glUniformMatrix4fv(mVertexMatrixHandler, 1, false, mMatrix, 0);
        //设置着色器参数, 第二个参数表示一个顶点包含的数据数量,这里为xy,所以为2
        GLES20.glVertexAttribPointer(mVertexPosHandler, 2, GLES20.GL_FLOAT, false, 0, mVertexBuffer);
        GLES20.glVertexAttribPointer(mTexturePosHandler, 2, GLES20.GL_FLOAT, false, 0, mTextureBuffer);
        //开始绘制
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    }

    public void release() {
        GLES20.glDisableVertexAttribArray(mVertexPosHandler);
        GLES20.glDisableVertexAttribArray(mTexturePosHandler);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        GLES20.glDeleteProgram(mProgram);
    }

    private String getVertexShader() {
        return "attribute vec4 aPosition;" +
                "precision mediump float;" +
                "uniform mat4 uMatrix;" +
                "attribute vec2 aCoordinate;" +
                "varying vec2 vCoordinate;" +
                "void main() {" +
                "    gl_Position = uMatrix*aPosition;" +
                "    vCoordinate = vec2(aCoordinate.x, aCoordinate.y);" +
                "}";
    }

    private String getFragmentShader() {
        return "precision mediump float;" +
                "varying vec2 vCoordinate;" +
                "uniform sampler2D uTexture;" +
                "void main() {" +
                "  vec4 color = texture2D(uTexture, vCoordinate);" +
                "  gl_FragColor = color;" +
                "}";
    }

    private int loadShader(int type, String shaderCode) {
        //根据type创建顶点着色器或者片元着色器
        int shader = GLES20.glCreateShader(type);
        //将资源加入到着色器中,并编译
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }

}

所有的 Drawer 开发完毕,现在是时候构建预览+录制封装类了。

package com.ty.media.opengl.egl;

import android.graphics.SurfaceTexture;
import android.opengl.EGLContext;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.Surface;

import com.ty.media.opengl.drawer.FBODrawer;
import com.ty.media.opengl.drawer.ScreenDrawer;

/**
 * author : liuhongwei
 * e-mail : 
 * date   : 2021/7/22 18:56
 * desc   : 自定义的OpenGL渲染器,包含EGL的初始化,线程与OpenGL上下文绑定、渲染循环、资源销毁等
 * version: 1.0
 */
public class PreviewGLRenderer {
    private static final String TAG = "PreviewGLRenderer";

    private static final int MSG_ID_INIT_PREVIEW_SURFACE = 1;
    private static final int MSG_ID_DE_INIT_PREVIEW_SURFACE = MSG_ID_INIT_PREVIEW_SURFACE + 1;
    private static final int MSG_ID_RENDERING = MSG_ID_DE_INIT_PREVIEW_SURFACE + 1;

    private RenderHandler mRenderHandler;
    private RenderThread mRenderThread;
    private Looper mRenderLooper;
    private volatile boolean isRenderThreadRunning;
    private volatile boolean isSurfaceDestroyed = true;

    private RenderEngine mRenderEngine;

    private EGLSurfaceHolder mPreviewEGLSurfaceHolder;
    private EGLContext mPreviewEGLContext;

    private FBODrawer mFBODrawer;
    private ScreenDrawer mScreenDrawer;

    private RenderThreadCb mRenderThreadCb;

    private RecordGLRenderer mRecordGLRenderer;

    public PreviewGLRenderer() {

    }

    /**
     * 开启预览渲染线程
     */
    public void startRenderThread() {
        if (isRenderThreadRunning) {
            return;
        }

        mRenderThread = new RenderThread();
        mRenderThread.start();

        mRecordGLRenderer = new RecordGLRenderer();
        mRecordGLRenderer.startRenderThread();
    }

    /**
     * 关闭预览渲染线程
     */
    public void stopRenderThread() {
        if (mRenderLooper != null) {
            mRenderLooper.quitSafely();
            mRenderHandler = null;
            isRenderThreadRunning = false;
        }

        mRecordGLRenderer.stopRenderThread();
    }

    /**
     * 设置渲染线程回调
     *
     * @param renderThreadCb
     */
    public void setRenderThreadCb(RenderThreadCb renderThreadCb) {
        mRenderThreadCb = renderThreadCb;
    }

    /**
     * 设置单画面或画中画模式
     *
     * @param isPip 是否画中画
     */
    public void setPipMode(boolean isPip) {
        if (mFBODrawer != null) {
            mFBODrawer.setMode(isPip ? FBODrawer.Mode.PIP : FBODrawer.Mode.SINGLE);
        }
    }

    /**
     * 创建预览窗口
     *
     * @param surface 窗口
     * @param w       宽
     * @param h       高
     */
    public void createPreview(Surface surface, int w, int h) {
        if (null != mRenderHandler) {
            Message m = new Message();
            m.what = MSG_ID_INIT_PREVIEW_SURFACE;
            m.obj = surface;
            m.arg1 = w;
            m.arg2 = h;
            mRenderHandler.sendMessage(m);
        }
    }

    /**
     * 销毁预览窗口
     */
    public void destroyPreview() {
        if (null != mRenderHandler) {
            Message m = new Message();
            m.what = MSG_ID_DE_INIT_PREVIEW_SURFACE;
            mRenderHandler.sendMessage(m);
        }
    }

    /**
     * 创建录制窗口
     */
    public void createRecord(Surface surface, int w, int h) {
        mRecordGLRenderer.createRecord(surface, mPreviewEGLContext, w, h);
    }

    /**
     * 销毁录制窗口
     */
    public void destroyRecord() {
        mRecordGLRenderer.destroyRecord();
    }

    private void render() {
        if (null != mRenderHandler) {
            Message m = new Message();
            m.what = MSG_ID_RENDERING;
            mRenderHandler.sendMessage(m);
        }
    }

    private void createPreviewEGLSurface(Surface surface) {
        mPreviewEGLSurfaceHolder = new EGLSurfaceHolder();
        mPreviewEGLContext = mPreviewEGLSurfaceHolder.init(null, EGLCore.EGL_RECORDABLE_ANDROID);
        mPreviewEGLSurfaceHolder.createEGLSurface(surface, -1, -1);
        mPreviewEGLSurfaceHolder.makeCurrent();
    }

    private void render(long timeMs) {

        if (null != mPreviewEGLSurfaceHolder) {

            mFBODrawer.getBgSurfaceTexture().updateTexImage();
            if (mFBODrawer.getMode() == FBODrawer.Mode.PIP) {
                mFBODrawer.getFgSurfaceTexture().updateTexImage();
            }

            int textureId = mFBODrawer.doDraw();

            mScreenDrawer.doDraw(textureId);

            mPreviewEGLSurfaceHolder.setTimestamp(timeMs);
            mPreviewEGLSurfaceHolder.swapBuffers();

            if (mRecordGLRenderer.isRecording()) {
                mRecordGLRenderer.render(textureId);
            }
        }
    }

    private void destroyPreviewEGLSurface() {
        if (null != mPreviewEGLSurfaceHolder) {
            mPreviewEGLSurfaceHolder.destroyEGLSurface();
            mPreviewEGLSurfaceHolder.release();
            mPreviewEGLSurfaceHolder = null;
        }
    }

    private class RenderHandler extends Handler {

        public RenderHandler(Looper looper) {
            super(looper);
        }

        public void handleMessage(Message msg) {

            switch (msg.what) {
                case MSG_ID_INIT_PREVIEW_SURFACE:
                    createPreviewEGLSurface((Surface) (msg.obj));

                    mFBODrawer = new FBODrawer();
                    mScreenDrawer = new ScreenDrawer();

                    mFBODrawer.prepareDraw(1920, 1080);
                    mScreenDrawer.setViewWH(msg.arg1, msg.arg2);

                    if (mRenderThreadCb != null) {
                        mRenderThreadCb.bgTextureAvail(mFBODrawer.getBgSurfaceTexture());
                        mRenderThreadCb.fgTextureAvail(mFBODrawer.getFgSurfaceTexture());
                    }
                    isSurfaceDestroyed = false;

                    mRenderEngine = new RenderEngine();
                    mRenderEngine.start();
                    break;

                case MSG_ID_DE_INIT_PREVIEW_SURFACE:
                    isSurfaceDestroyed = true;

                    destroyPreviewEGLSurface();
                    mFBODrawer.destroyDraw();
                    mScreenDrawer.release();

                    mRenderEngine.setRunning(false);
                    mRenderEngine.interrupt();
                    try {
                        mRenderEngine.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    break;

                case MSG_ID_RENDERING:
                    if (!isSurfaceDestroyed) {
                        render(System.currentTimeMillis());
                    }
                    break;

                default:

            }
        }

    }

    private class RenderThread extends Thread {

        @Override
        public void run() {
            Looper.prepare();//必须在Looper.myLooper()之前
            mRenderLooper = Looper.myLooper();
            mRenderHandler = new RenderHandler(mRenderLooper);
            isRenderThreadRunning = true;
            Looper.loop();
        }
    }

    /**
     * 驱动绘制 25 Hz 1000 / 25 = 40 ms
     */
    private class RenderEngine extends Thread {

        private volatile boolean isRunning = true;

        @Override
        public void run() {
            while (isRunning) {
                render();
                try {
                    Thread.sleep(40);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        public void setRunning(boolean running) {
            isRunning = running;
        }
    }

    public interface RenderThreadCb {

        public void fgTextureAvail(SurfaceTexture textureId);

        public void bgTextureAvail(SurfaceTexture textureId);
    }

}

下面是录制 MediaCodec OpenGL 渲染器代码。

/**
 * author : liuhongwei
 * e-mail : 
 * date   : 2021/7/27 08:50
 * desc   : 录制 MediaCodec OpenGL 渲染器
 * version: 1.0
 */
public class RecordGLRenderer {
    private static final String TAG = "RecordGLRenderer";

    private static final int MSG_ID_RENDERING = 1;
    private static final int MSG_ID_INIT_RECORD_SURFACE = MSG_ID_RENDERING + 1;
    private static final int MSG_ID_DE_INIT_RECORD_SURFACE = MSG_ID_INIT_RECORD_SURFACE + 1;
    private static final String KEY_SURFACE = "SURFACE";

    private RenderHandler mRenderHandler;
    private RenderThread mRenderThread;
    private Looper mRenderLooper;
    private volatile boolean isRenderThreadRunning;

    private EGLSurfaceHolder mRecordEGLSurfaceHolder;

    private ScreenDrawer mRecordDrawer;
    private volatile boolean isRecording = false;

    public RecordGLRenderer(){

    }

    public void startRenderThread() {
        if (isRenderThreadRunning) {
            return;
        }

        mRenderThread = new RenderThread();
        mRenderThread.start();
    }

    public void stopRenderThread() {
        if (mRenderLooper != null) {
            mRenderLooper.quitSafely();
            mRenderHandler = null;
            isRenderThreadRunning = false;
        }
    }

    public boolean isRecording() {
        return isRecording;
    }

    public void createRecord(Surface surface, EGLContext eglContext, int w, int h) {
        if (null != mRenderHandler) {
            Message m = new Message();
            m.what = MSG_ID_INIT_RECORD_SURFACE;
            m.obj = eglContext;

            Bundle b = new Bundle();
            b.putParcelable(KEY_SURFACE, surface);
            m.setData(b);

            m.arg1 = w;
            m.arg2 = h;

            mRenderHandler.sendMessage(m);
        }
    }

    public void destroyRecord() {
        if (null != mRenderHandler) {
            Message m = new Message();
            m.what = MSG_ID_DE_INIT_RECORD_SURFACE;
            mRenderHandler.sendMessage(m);
        }
    }

    public void render(int textureId) {
        if (null != mRenderHandler) {
            Message m = new Message();
            m.what = MSG_ID_RENDERING;
            m.arg1 = textureId;
            mRenderHandler.sendMessage(m);
        }
    }

    private void createRecordEGLSurface(Surface surface, EGLContext eglContext) {
        mRecordEGLSurfaceHolder = new EGLSurfaceHolder();
        mRecordEGLSurfaceHolder.init(eglContext, EGLCore.EGL_RECORDABLE_ANDROID);
        mRecordEGLSurfaceHolder.createEGLSurface(surface, -1, -1);
        mRecordEGLSurfaceHolder.makeCurrent();
    }


    private void renderInner(int textureId) {
        if (null != mRecordEGLSurfaceHolder) {
            mRecordDrawer.doDraw(textureId);
            //这句代码会导致无法在 MediaCodec 的 InputSurface 上绘制
            //mRecordEGLSurfaceHolder.setTimestamp(System.currentTimeMillis());
            mRecordEGLSurfaceHolder.swapBuffers();
        }
    }

    private void destroyRecordEGLSurface() {
        if (null != mRecordEGLSurfaceHolder) {
            mRecordEGLSurfaceHolder.destroyEGLSurface();
            mRecordEGLSurfaceHolder.release();
        }
    }

    private class RenderHandler extends Handler {

        public RenderHandler(Looper looper) {
            super(looper);
        }

        public void handleMessage(Message msg) {

            switch (msg.what) {
                case MSG_ID_RENDERING:
                    renderInner(msg.arg1);
                    break;

                case MSG_ID_INIT_RECORD_SURFACE:
                    Bundle b = msg.getData();
                    createRecordEGLSurface(b.getParcelable(KEY_SURFACE), (EGLContext) (msg.obj));

                    mRecordDrawer = new ScreenDrawer();
                    mRecordDrawer.setViewWH(msg.arg1, msg.arg2);

                    isRecording = true;
                    break;

                case MSG_ID_DE_INIT_RECORD_SURFACE:
                    destroyRecordEGLSurface();
                    mRecordDrawer.release();
                    isRecording = false;
                    break;

                default:

            }
        }

    }

    private class RenderThread extends Thread {

        @Override
        public void run() {
            Looper.prepare();//必须在Looper.myLooper()之前
            mRenderLooper = Looper.myLooper();
            mRenderHandler = new RenderHandler(mRenderLooper);
            isRenderThreadRunning = true;
            Looper.loop();
        }
    }

}

最后是 egl 封装类,用于方便调用 egl 接口。

/**
 * author : liuhongwei
 * e-mail : 
 * date   : 2021/7/22 17:16
 * desc   : EGL基础封装
 * version: 1.0
 */
public class EGLCore {
    private static final String TAG = "EGLCore";
    private static final int FLAG_RECORDABLE = 0x01;

    /**
     * Android 指定的标志
     * 告诉EGL它创建的surface必须和视频编解码器兼容。
     * 没有这个标志,EGL可能会使用一个MediaCodec不能理解的Buffer
     * 这个变量在api26以后系统才自带有,为了兼容,我们自己写好这个值0x3142
     */
    public static final int EGL_RECORDABLE_ANDROID = 0x3142;

    // EGL相关变量
    private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
    private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
    private EGLConfig mEGLConfig;

    /**
     * 初始化EGLDisplay
     *
     * @param eglContext 共享上下文
     */
    public EGLContext init(EGLContext eglContext, int flags) {
        if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
            throw new RuntimeException("EGL already set up");
        }

        EGLContext sharedContext = EGL14.EGL_NO_CONTEXT;
        if (null != eglContext) {
            sharedContext = eglContext;
        }

        mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
        if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
            throw new RuntimeException("Unable to get EGL14 display");
        }

        int[] version = new int[2];
        if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
            mEGLDisplay = EGL14.EGL_NO_DISPLAY;
            throw new RuntimeException("unable to initialize EGL14");
        }

        if (mEGLContext == EGL14.EGL_NO_CONTEXT) {
            EGLConfig config = getConfig(flags, 2);
            if (config == null) {
                throw new RuntimeException("Unable to find a suitable EGLConfig");
            }
            int[] attr2List = new int[]{EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
            EGLContext context = EGL14.eglCreateContext(
                    mEGLDisplay, config, sharedContext,
                    attr2List, 0
            );
            mEGLConfig = config;
            mEGLContext = context;
        }

        return mEGLContext;
    }

    /**
     * 获取EGL配置信息
     *
     * @param flags   初始化标记
     * @param version EGL版本
     */
    private EGLConfig getConfig(int flags, int version) {
        int renderableType = EGL14.EGL_OPENGL_ES2_BIT;
        if (version >= 3) {
            // 配置EGL 3
            renderableType = renderableType | EGLExt.EGL_OPENGL_ES3_BIT_KHR;
        }

        // The actual surface is generally RGBA or RGBX, so situationally omitting alpha
        // doesn't really help.  It can also lead to a huge performance hit on glReadPixels()
        // when reading into a GL_RGBA buffer.
        int[] attrList = new int[]{
                EGL14.EGL_RED_SIZE, 8,
                EGL14.EGL_GREEN_SIZE, 8,
                EGL14.EGL_BLUE_SIZE, 8,
                EGL14.EGL_ALPHA_SIZE, 8,
                EGL14.EGL_SURFACE_TYPE,
                EGL14.EGL_WINDOW_BIT,
                EGL14.EGL_RENDERABLE_TYPE, renderableType,
                EGL14.EGL_NONE, 0, // placeholder for recordable [@-3]
                EGL14.EGL_NONE
        };
        //配置Android指定的标记
        if ((flags & FLAG_RECORDABLE) != 0) {
            attrList[attrList.length - 3] = EGL_RECORDABLE_ANDROID;
            attrList[attrList.length - 2] = 1;
        }
        EGLConfig[] configs = new EGLConfig[1];
        int[] numConfigs = new int[1];

        //获取EGL配置
        if (!EGL14.eglChooseConfig(mEGLDisplay, attrList, 0,
                configs, 0, configs.length,
                numConfigs, 0)) {
            Log.w(TAG, "Unable to find RGB8888 EGLConfig");
            return null;
        }
        //使用系统推荐的第一个配置
        return configs[0];
    }

    /**
     * 创建可显示的渲染缓存
     *
     * @param surface 渲染窗口的surface
     */
    public EGLSurface createWindowSurface(Object surface) {
        if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) {
            throw new RuntimeException("Invalid surface");
        }

        int[] surfaceAttr = new int[]{EGL14.EGL_NONE};

        EGLSurface eglSurface = EGL14.eglCreateWindowSurface(
                mEGLDisplay, mEGLConfig, surface,
                surfaceAttr, 0);

        if (eglSurface == null) {
            throw new RuntimeException("Surface was null");
        }

        return eglSurface;
    }

    /**
     * 创建离屏渲染缓存
     *
     * @param width  缓存窗口宽
     * @param height 缓存窗口高
     */
    public EGLSurface createOffscreenSurface(int width, int height) {
        int[] surfaceAttr = new int[]{EGL14.EGL_WIDTH, width,
                EGL14.EGL_HEIGHT, height,
                EGL14.EGL_NONE};

        EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(
                mEGLDisplay, mEGLConfig,
                surfaceAttr, 0);

        if (eglSurface == null) {
            throw new RuntimeException("Surface was null");
        }

        return eglSurface;
    }

    /**
     * 将当前线程与上下文进行绑定
     */
    public void makeCurrent(EGLSurface eglSurface) {
        if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
            throw new RuntimeException("EGLDisplay is null, call init first");
        }
        if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) {
            throw new RuntimeException("makeCurrent(eglSurface) failed");
        }
    }

    /**
     * 将当前线程与上下文进行绑定
     */
    public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) {
        if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
            throw new RuntimeException("EGLDisplay is null, call init first");
        }
        if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) {
            throw new RuntimeException("eglMakeCurrent(draw,read) failed");
        }
    }

    /**
     * 将缓存图像数据发送到设备进行显示
     */
    public boolean swapBuffers(EGLSurface eglSurface) {
        return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface);
    }

    /**
     * 设置当前帧的时间,单位:纳秒
     */
    public void setPresentationTime(EGLSurface eglSurface, long nsecs) {
        EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs);
    }

    /**
     * 销毁EGLSurface,并解除上下文绑定
     */
    public void destroySurface(EGLSurface elg_surface) {
        EGL14.eglMakeCurrent(
                mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
                EGL14.EGL_NO_CONTEXT
        );
        EGL14.eglDestroySurface(mEGLDisplay, elg_surface);
    }

    /**
     * 释放资源
     */
    public void release() {
        if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
            // Android is unusual in that it uses a reference-counted EGLDisplay.  So for
            // every eglInitialize() we need an eglTerminate().
            EGL14.eglMakeCurrent(
                    mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
                    EGL14.EGL_NO_CONTEXT
            );
            EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
            EGL14.eglReleaseThread();
            EGL14.eglTerminate(mEGLDisplay);
        }

        mEGLDisplay = EGL14.EGL_NO_DISPLAY;
        mEGLContext = EGL14.EGL_NO_CONTEXT;
        mEGLConfig = null;
    }

}

下面是 EGLSurfaceHolder 详细实现。

/**
 * author : liuhongwei
 * e-mail : 
 * date   : 2021/7/22 18:45
 * desc   : EGLSurface 持有者
 * version: 1.0
 */
public class EGLSurfaceHolder {
    private static final String TAG = "EGLSurfaceHolder";

    private EGLCore mEGLCore;
    private EGLSurface mEGLSurface;

    public EGLContext init(EGLContext shareContext, int flags) {
        mEGLCore = new EGLCore();
        return mEGLCore.init(shareContext, flags);
    }

    public void createEGLSurface(Object surface, int width, int height) {
        if (surface != null) {
            mEGLSurface = mEGLCore.createWindowSurface(surface);
        } else {
            mEGLSurface = mEGLCore.createOffscreenSurface(width, height);
        }
    }

    public void makeCurrent() {
        if (mEGLSurface != null) {
            mEGLCore.makeCurrent(mEGLSurface);
        }
    }

    public void swapBuffers() {
        if (mEGLSurface != null) {
            mEGLCore.swapBuffers(mEGLSurface);
        }
    }

    public void setTimestamp(long timeMs) {
        if (mEGLSurface != null) {
            mEGLCore.setPresentationTime(mEGLSurface, timeMs * 1000);
        }
    }

    public void destroyEGLSurface() {
        if (mEGLSurface != null) {
            mEGLCore.destroySurface(mEGLSurface);
            mEGLSurface = null;
        }
    }

    public void release() {
        mEGLCore.release();
    }
}
本文已生成可运行项目
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TYYJ-洪伟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值