基于Android Camera2 OpenGL ES 预览&录像流程

基于Android P版本分析

在分析使用OpenGL ES进行预览&录像之前,首先先区分一下GLSurfaceView和TextureView;

GLSurfaceView

一般情况下,OpenGL ES都是配合GLSurfaceView来实现渲染的。因为GLSurfaceView内部自带了EGL的管理以及渲染线程。同时还定义了用于需要实现的Reader接口,其中EglHelper和GLThread分别实现了上面提及到的管理EGL环境和渲染线程的工作;

但是这种方式也存在一定的缺点:

GLSurfaceView将OpenGL绑定在一起,所以一旦GLSurfaceView被销毁,伴随的OpenGL也一起销毁了,一个OpenGL只能渲染一个GLSurfaceView;

TextureView

这个空间是Android 4.0中引入的,和SurfaceView不同,它不会在WMS中单独创建窗口,而是作为一个普通view,因此可以和其他普通View一样进行移动、旋转、缩放、动画等变化;

缺点:使用这个空间需要自定义实现EGL管理和渲染线程,比较麻烦,但是较为灵活;

TextureView本身内置了一个SurfaceTexture,用来配合EGL来将图像显示到屏幕上;

OpenGL ES 预览

  1. 获取 EGL Display 对象:eglGetDisplay();
  2. 初始化与 EGLDisplay 之间的连接:eglInitialize();
  3. 获取 EGLConfig 对象:eglChooseConfig();
  4. 创建 EGLContext 实例:eglCreateContext();
  5. 创建 EGLSurface 实例:eglCreateWindowSurface();
  6. 连接 EGLContext 和 EGLSurface:eglMakeCurrent();
  7. 使用 OpenGL ES API 绘制图形:gl_*();
  8. 切换 front buffer 和 back buffer 送显:eglSwapBuffer();
  9. 断开并释放与 EGLSurface 关联的 EGLContext 对象:eglRelease();
  10. 删除 EGLSurface 对象;
  11. 删除 EGLContext 对象;
  12. 终止与 EGLDisplay 之间的连接;

获取 EGL Display 对象、初始化与 EGLDisplay 之间的连接

Display代表显示器,在有些系统上可以有多个显示器,也就会有多个Display。获得Display要调用 eglGetDisplay(NativeDisplay display_id),参数一般为 EGL_DEFAULT_DISPLAY ;

调用eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor),该函数会进行一些内部初始化工作,并传回EGL版本号(major.minor)。

首先在主线程中创建一个新的子线程:

csharp 复制代码
public void start() {
    synchronized (lock) {
        if (!mGlThreadFlag) {
            if (mTextureProvider == null) {
                return;
            }
            mGlThreadFlag = true;
            Thread mglThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    openGlRun();
                }
            });
            mglThread.start();
            try {
                lock.wait();
            } catch (InterruptedException exception) {
                exception.printStackTrace();
            }
        }
    }
}
ini 复制代码
private void openGlRun() {
    EglHelper eglHelper = new EglHelper();
    boolean ret = eglHelper.createGlWithSurface(new EglConfigAttrs(), new EglContextAttrs(),
                                                new SurfaceTexture(1));
    if (!ret) {
        LogManager.loge(tag, "createGlWithSurface error");
        return;
    }
​
    LogManager.logd(tag, "Provider Opened . data size (x,y)=" + mPreviewSize.getHeight()
                    + "/" + mPreviewSize.getWidth());
    if (mPreviewSize.getHeight() <= 0 || mPreviewSize.getWidth() <= 0) {
        LogManager.loge(tag, "mPreviewSize error");
        destroyEgl(eglHelper);
        synchronized (lock) {
            lock.notifyAll();
        }
        return;
    }
    final int sourceWidth = mPreviewSize.getHeight();
    final int sourceHeight = mPreviewSize.getWidth();
    synchronized (lock) {
        lock.notifyAll();
    }
    if (mVideoRenderer == null) {
        mVideoRenderer = new WrapRenderer(null);
    }
    FrameBufferUtil frameBufferUtil = new FrameBufferUtil();
    mVideoRenderer.create();
    mVideoRenderer.sizeChanged(sourceWidth, sourceHeight);
    mVideoRenderer.setFlag(mTextureProvider.isLandscape() ? WrapRenderer.TYPE_CAMERA : WrapRenderer.TYPE_MOVE);
​
    RenderBean renderBeanForVideo = new RenderBean();
    renderBeanForVideo.eglHelper = eglHelper;
    renderBeanForVideo.sourceWidth = sourceWidth;
    renderBeanForVideo.sourceHeight = sourceHeight;
    renderBeanForVideo.endFlag = false;
​
    LogManager.logd(tag, "Processor While Loop Entry");
    while (!mTextureProvider.frame() && mGlThreadFlag) {
        mInputSurfaceTexture.updateTexImage();
        mInputSurfaceTexture.getTransformMatrix(mVideoRenderer.getTextureMatrix());
​
        frameBufferUtil.bindFrameBuffer(sourceWidth, sourceHeight);
        GLES20.glViewport(0, 0, sourceWidth, sourceHeight);
        mVideoRenderer.draw(mInputSurfaceTextureId);
        frameBufferUtil.unBindFrameBuffer();
        renderBeanForVideo.textureId = frameBufferUtil.getCacheTextureId();
        renderBeanForVideo.timeStamp = mTextureProvider.getTimeStamp();
        renderBeanForVideo.textureTime = mInputSurfaceTexture.getTimestamp();
        observable.notify(renderBeanForVideo);
    }
    LogManager.logd(tag, "out of gl thread loop");
    synchronized (lock) {
        renderBeanForVideo.endFlag = true;
        observable.notify(renderBeanForVideo);
        mVideoRenderer.destroy();
        destroyEgl(eglHelper);
        lock.notifyAll();
        LogManager.logd(tag, "gl thread exit");
    }
}

这样,OpenGL所在的子线程就可以工作了;

我们需要看一下EGL Display对象的逻辑;

在openGlRun()方法中,首先先new EglHelper对象:

arduino 复制代码
public class EglHelper {
​
    private EGLDisplay mEglDisplay;
    private EGLConfig mEglConfig;
    private EGLContext mEglContext;
    private EGLSurface mEglSurface;
    private static final int MAJOR_OFFSET = 0;
    private static final int MINOR_OFFSET = 1;
    private static final int OFFSET_0 = 0;
    private static final int CONFIG_SIZE = 1;
    private static final int CONFIGS_LENGTH = 1;
    private static String TAG = "EglHelper";
​
    public EglHelper() {
        this(EGL14.EGL_DEFAULT_DISPLAY);
    }
    
    public EglHelper(int display) {
        changeDisplay(display);
    }
    
    private void changeDisplay(int key) {
        // 获取显示设备
        mEglDisplay = EGL14.eglGetDisplay(key);
        int[] versions = new int[2];
        // version中存放EGL 版本号,int[0]为主版本号,int[1]为子版本号
        EGL14.eglInitialize(mEglDisplay, versions, MAJOR_OFFSET, versions, MINOR_OFFSET);
    }
    ..................
}

eglGetDisplay()方法中传入了一个参数:EGL14.EGL_DEFAULT_DISPLAY,默认值为0;

arduino 复制代码
public class EGL14 {
    ..................
    public static final int EGL_DEFAULT_DISPLAY = 0;
    ..................
}

在EglHelper构造方法中,初始化了mEglDisplay,通过调用EGL14.eglInitialize()方法;

获取 EGLConfig 对象

所为Config实际指的是FrameBuffer的参数。

一般用eglChooseConfig(EGLDisplay dpy, const EGLint * attr_list, EGLConfig * config, EGLint config_size, EGLint *num_config),其中attr_list是以EGL_NONE结束的参数数组,通常以id、value依次存放,对于个别标识性的属性可以只有 id,没有value。另一个办法是用EGLboolean eglGetConfigs(EGLDisplay dpy, EGLConfig * config, EGLint config_size, EGLint *num_config) 来获得所有config。这两个函数都会返回不多于config_size个Config,结果保存在config[]中,系统的总Config个数保存 在num_config中。可以利用eglGetConfig()中间两个参数为0来查询系统支持的Config总个数;

Config有众多的Attribute,这些Attribute决定FrameBuffer的格式和能力,通过eglGetConfigAttrib ()来读取,但不能修改;

在new EglHelper()执行完成之后,紧接着会执行eglHelper.createGlWithSurface()方法:

ini 复制代码
public boolean createGlWithSurface(EglConfigAttrs attrs, EglContextAttrs ctxAttrs, Object surface) {
    EGLConfig config = getConfig(attrs.surfaceType(EGL14.EGL_WINDOW_BIT).makeDefault(true));
    if (config == null) {
        LogManager.loge(TAG, "getConfig failed : " + EGL14.eglGetError());
        return false;
    }
    mEglContext = createContext(config, EGL14.EGL_NO_CONTEXT, ctxAttrs.makeDefault(true));
    if (mEglContext == EGL14.EGL_NO_CONTEXT) {
        LogManager.loge(TAG, "createContext failed : " + EGL14.eglGetError());
        return false;
    }
    mEglSurface = createWindowSurface(surface);
    if (mEglSurface == EGL14.EGL_NO_SURFACE) {
        LogManager.loge(TAG, "createWindowSurface failed : " + EGL14.eglGetError());
        return false;
    }
    if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
        LogManager.loge(TAG, "eglMakeCurrent failed : " + EGL14.eglGetError());
        return false;
    }
    return true;
}

首先在该方法中调用了getConfig()方法获取EGLConfig对象:

ini 复制代码
private EGLConfig getConfig(EglConfigAttrs attrs) {
    EGLConfig[] configs = new EGLConfig[1];
    int[] configNum = new int[1];
    EGL14.eglChooseConfig(mEglDisplay, attrs.build(), 0, configs, 0,
                          1, configNum, 0);
    // 判断是否选择到符合传入参数的配置信息
    if (configNum[0] > 0) {
        if (attrs.isDefault()) {
            // 从系统中获取对应属性的配置
            mEglConfig = configs[0];
        }
        return configs[0];
    }
    return null;
}

在getConfig()方法中,执行了EGL14.eglChooseConfig()方法,将eglChooseConfig获取到的config赋值给mEglConfig;

创建 EGLContext 实例

OpenGL的pipeline从程序的角度看就是一个状态机,有当前的颜色、纹理坐标、变换矩阵、绚染模式等一大堆状态,这些状态作用于程序提交的顶点 坐标等图元从而形成帧缓冲内的像素。在OpenGL的编程接口中,Context就代表这个状态机,程序的主要工作就是向Context提供图元、设置状 态,偶尔也从Context里获取一些信息;

在createGlWithSurface()方法中执行完getConfig()方法之后,紧接着调用mEglContext = createContext(config, EGL14.EGL_NO_CONTEXT, ctxAttrs.makeDefault(true))方法:

ini 复制代码
private EGLContext createContext(EGLConfig config, EGLContext share, EglContextAttrs attrs) {
    // native 层方法
    EGLContext context = EGL14.eglCreateContext(mEglDisplay, config, share, attrs.build(), OFFSET_0);
    if (attrs.isDefault()) {
        mEglContext = context;
    }
    return context;
}

这个方法中执行了EGL14.eglCreateContext方法;

创建 EGLSurface 实例

Surface实际上就是一个FrameBuffer,通过 EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig confg, NativeWindow win, EGLint *cfg_attr) 来创建一个可实际显示的Surface。系统通常还支持另外两种Surface:PixmapSurface和PBufferSurface,这两种都不 是可显示的Surface,PixmapSurface是保存在系统内存中的位图,PBuffer则是保存在显存中的帧;

Surface也有一些attribute,基本上都可以故名思意, EGL_HEIGHT EGL_WIDTH EGL_LARGEST_PBUFFER EGL_TEXTURE_FORMAT EGL_TEXTURE_TARGET EGL_MIPMAP_TEXTURE EGL_MIPMAP_LEVEL,通过eglSurfaceAttrib()设置、eglQuerySurface()读取;

在createGlWithSurface()方法中执行完createContext()方法之后,紧接着调用mEglSurface = createWindowSurface(surface)方法:

typescript 复制代码
public EGLSurface createWindowSurface(Object surface) {
    mEglSurface = EGL14.eglCreateWindowSurface(mEglDisplay, mEglConfig, surface,
                                               new int[]{EGL14.EGL_NONE}, OFFSET_0);
    return mEglSurface;
}

在EGL14.eglCreateWindowSurface()方法中,传入了mEglDisplay、mEglConfig和surface,surface其实是SurfaceTexture,在openGlRun()中执行createGlWithSurface方法时传入了new SurfaceTexture(1)就是该处的surface;

分析EGL14类中的eglCreateWindowSurface()方法:

ini 复制代码
public static EGLSurface eglCreateWindowSurface(EGLDisplay dpy,
                                                EGLConfig config,
                                                Object win,
                                                int[] attrib_list,
                                                int offset
                                               ){
    Surface sur = null;
    if (win instanceof SurfaceView) {
        SurfaceView surfaceView = (SurfaceView)win;
        sur = surfaceView.getHolder().getSurface();
    } else if (win instanceof SurfaceHolder) {
        SurfaceHolder holder = (SurfaceHolder)win;
        sur = holder.getSurface();
    } else if (win instanceof Surface) {
        sur = (Surface) win;
    }
​
    EGLSurface surface;
    if (sur != null) {
        surface = _eglCreateWindowSurface(dpy, config, sur, attrib_list, offset);
    } else if (win instanceof SurfaceTexture) {
        // _eglCreateWindowSurfaceTexture为native层方法
        surface = _eglCreateWindowSurfaceTexture(dpy, config,
                                                 win, attrib_list, offset);
    } else {
        throw new java.lang.UnsupportedOperationException(
            "eglCreateWindowSurface() can only be called with an instance of " +
            "Surface, SurfaceView, SurfaceTexture or SurfaceHolder at the moment, " +
            "this will be fixed later.");
    }
​
    return surface;
}

在上面传入了SurfaceTexture实例,在eglCreateWindowSurface方法中会执行_eglCreateWindowSurfaceTexture方法,至此通过SurfaceTexture创建出了对应的Surface;

连接 EGLContext 和 EGLSurface

通过上述流程逻辑,已经将EGLDisplay、EGLContext以及EGLSurface初始化好了;

在createGlWithSurface方法中执行完createWindowSurface方法之后,紧接着执行EGL14.eglMakeCurrent方法:

kotlin 复制代码
if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
    LogManager.loge(TAG, "eglMakeCurrent failed : " + EGL14.eglGetError());
    return false;
}
arduino 复制代码
public static native boolean eglMakeCurrent(
    EGLDisplay dpy,
    EGLSurface draw,
    EGLSurface read,
    EGLContext ctx
);

通过eglMakeCurrent()方法绑定EGLDisplay和EGLContext;

使用 OpenGL ES API 绘制图形

当我们绑定完成之后,我们就可以进行RenderLoop循环了,使用OpenGL ES 绘制图像:gl_*();

在openGlRun()方法中紧接着执行循环遍历:

ini 复制代码
while (!mTextureProvider.frame() && mGlThreadFlag) {
    mInputSurfaceTexture.updateTexImage();
    mInputSurfaceTexture.getTransformMatrix(mVideoRenderer.getTextureMatrix());
​
    frameBufferUtil.bindFrameBuffer(sourceWidth, sourceHeight);
    GLES20.glViewport(0, 0, sourceWidth, sourceHeight);
    mVideoRenderer.draw(mInputSurfaceTextureId);
    frameBufferUtil.unBindFrameBuffer();
    renderBeanForVideo.textureId = frameBufferUtil.getCacheTextureId();
    renderBeanForVideo.timeStamp = mTextureProvider.getTimeStamp();
    renderBeanForVideo.textureTime = mInputSurfaceTexture.getTimestamp();
    observable.notify(renderBeanForVideo);
}

其中通过!mTextureProvider.frame() && mGlThreadFlag的组合条件来判断是否需要轮训:

mGlThreadFlag条件

这个变量在mglThread线程开启之前就设置为了true,即在start()方法调用之后,就会赋值为true:

csharp 复制代码
public void start() {
    synchronized (lock) {
        if (!mGlThreadFlag) {
            if (mTextureProvider == null) {
                return;
            }
            mGlThreadFlag = true;
            Thread mglThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    openGlRun();
                }
            });
            mglThread.start();
            try {
                lock.wait();
            } catch (InterruptedException exception) {
                exception.printStackTrace();
            }
        }
    }
}

赋值为false的情况有两种:

  • 调用stop()方法:
csharp 复制代码
public void stop() {
    synchronized (lock) {
        if (mGlThreadFlag) {
            mGlThreadFlag = false;
            mTextureProvider.close();
            try {
                lock.wait();
            } catch (InterruptedException exception) {
                exception.printStackTrace();
            }
        }
    }
}

stop()方法和start()方法是对应的,代表停止opengl线程;

  • 调用destroyEgl()方法:
ini 复制代码
private void destroyEgl(EglHelper egl) {
    mGlThreadFlag = false;
    EGL14.eglMakeCurrent(egl.getDisplay(), EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
    EGL14.eglDestroyContext(egl.getDisplay(), egl.getDefaultContext());
    EGL14.eglTerminate(egl.getDisplay());
}

在断开Display和Context以及销毁Context的时候,也需要将mGlThreadFlag变量赋值为false,表示while循环不需要再轮训了;

mTextureProvider.frame()

这个判断条件其实是在创建用于绑定到CameraCaptureSession的Surface的时候生成的一个条件;

因为opengl在预览下的工作其实就是将Surface中保存的camera数据进行二次渲染处理然后将处理后的数据填充到 preview Surface中,最终显示到窗口中;

那么就需要创建一个Surface绑定到Camera,用于获取camera原始数据。创建逻辑如下:

ini 复制代码
public Surface getVideoSurface() {
    if (videoSurface == null) {
        mInputSurfaceTextureId = createTextureId();
        mInputSurfaceTexture = new SurfaceTexture(mInputSurfaceTextureId);
        mInputSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        videoSurface = new Surface(mInputSurfaceTexture);
        mTextureProvider.setOnFrameAvailableListener(mInputSurfaceTexture);
    }
    return videoSurface;
}

在该方法中首先先调用createTextureId()方法,获取一个纹理:

ini 复制代码
protected int createTextureId() {
    int target = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
    int[] texture = new int[1];
    GLES20.glGenTextures(1, texture, 0);
    GLES20.glBindTexture(target, texture[0]);
    GLES20.glTexParameterf(target, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
    GLES20.glTexParameterf(target, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
    GLES20.glTexParameteri(target, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
    GLES20.glTexParameteri(target, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
    return texture[0];
}

纹理获取完成后,根据纹理创建一个SurfaceTexture对象,然后再根据SurfaceTexture对象创建一个Surface用于绑定CameraCaptureSession;

然后调用mTextureProvider.setOnFrameAvailableListener(mInputSurfaceTexture)方法将一个实现了SurfaceTexture.OnFrameAvailableListener接口的类实例设置到SurfaceTexture对象中:

java 复制代码
private SurfaceTexture.OnFrameAvailableListener frameListener = new SurfaceTexture.OnFrameAvailableListener() {
    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        mFrameSem.drainPermits();
        mFrameSem.release();
    }
};
​
/**
     * set  FrameAvailable Listener.
     *
     * @param surfaceTexture SurfaceTexture
     */
public void setOnFrameAvailableListener(final SurfaceTexture surfaceTexture) {
    surfaceTexture.setOnFrameAvailableListener(frameListener);
}

在SurfaceTexture.OnFrameAvailableListener的onFrameAvailable回调中可以监听到回调结果;

mTextureProvider.frame()方法:

scss 复制代码
private Semaphore mFrameSem;
​
TextureProvider() {
    mFrameSem = new Semaphore(0);
}
​
boolean frame() {
    try {
        mFrameSem.acquire();
    } catch (InterruptedException exception) {
        exception.printStackTrace();
    }
    return false;
}

在frame()方法中执行了mFrameSem.acquire()方法,acquire()和监听中的release()方法是对应的,acquire()方法用于获取一个令牌,而release()方法用于释放一个令牌;

综上,while循环通过上述两个判断条件的组合,确定是否继续轮训;

在while循环中,最后执行了observable.notify(renderBeanForVideo)方法:

ini 复制代码
while (!mTextureProvider.frame() && mGlThreadFlag) {
    mInputSurfaceTexture.updateTexImage();
    mInputSurfaceTexture.getTransformMatrix(mVideoRenderer.getTextureMatrix());
​
    frameBufferUtil.bindFrameBuffer(sourceWidth, sourceHeight);
    GLES20.glViewport(0, 0, sourceWidth, sourceHeight);
    mVideoRenderer.draw(mInputSurfaceTextureId);
    frameBufferUtil.unBindFrameBuffer();
    renderBeanForVideo.textureId = frameBufferUtil.getCacheTextureId();
    renderBeanForVideo.timeStamp = mTextureProvider.getTimeStamp();
    renderBeanForVideo.textureTime = mInputSurfaceTexture.getTimestamp();
    observable.notify(renderBeanForVideo);
}

在while循环中,首先先执行了mVideoRenderer.draw(mInputSurfaceTextureId),作用就是用于定义图像显示区域,可以理解为图元组装逻辑;

执行完成之后,最后执行observable.notify(renderBeanForVideo)方法:

ini 复制代码
@Override
public void onCall(RenderBean rb) {
    if (rb.endFlag && mShowSurface != null) {
        rb.eglHelper.destroySurface(mShowSurface);
        mShowSurface = null;
    } else if (isShow && mSurface != null) {
        replaceSurface(rb);
        if (mShowSurface == null) {
            mShowSurface = rb.eglHelper.createWindowSurface(mSurface);
            mFilter = new OriginalFilter();
            mFilter.create();
            int[] sideLength = new int[]{rb.sourceWidth, rb.sourceHeight, mWidth, mHeight};
            MatrixUtils.getMatrix(mFilter.getVertexMatrix(), MatrixUtils.TYPE_FIT_XY, sideLength);
            MatrixUtils.flip(mFilter.getVertexMatrix(), false, true);
        }
        rb.eglHelper.makeCurrent(mShowSurface);
​
        GLES20.glViewport(0, 0, mWidth, mHeight);
        mFilter.draw(rb.textureId);
        dispatch(rb);
        rb.eglHelper.swapBuffers(mShowSurface);
    }
}

onCall()方法中的逻辑其实和上面的逻辑类似,同样是执行了mFilter.draw(rb.textureId),将图像纹理绘制到Surface中;

front buffer 和 back buffer

在onCall()方法中,执行完mFilter.draw(rb.textureId)之后,紧接着执行rb.eglHelper.swapBuffers(mShowSurface)逻辑:

typescript 复制代码
public void swapBuffers(EGLSurface surface) {
    EGL14.eglSwapBuffers(mEglDisplay, surface);
}

swapBuffers主要用于刷新数据;

这里需要重点注意的是surface。如果这里的surface是一个像素缓冲(pixel buffer)Surface,那什么都不会发生,调用将正确的返回,不报任何错误;

但如果surface是一个双重缓冲surface(大多数情况),这个方法将会交换surface内部的前端缓冲(front-buffer)和后端缓冲(back-surface)。后端缓冲用于存储渲染结果,前端缓冲则用于底层窗口系统,底层窗口系统将缓冲中的颜色信息显示到设备上;

断开并释放与 EGLSurface 关联的 EGLContext 对象

与显示设备解绑,销毁eglSurface

ini 复制代码
EGL14.eglMakeCurrent(egl.getDisplay(), EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);

在该逻辑中,同样是执行了EGL14.eglMakeCurrent方法,和EGLSurface 关联 EGLContext所调用的方法一样,只不过是方法中传入的参数不同(二、三、四参数不同);

删除 EGLSurface 对象&删除 EGLContext 对象

arduino 复制代码
public void destroySurface(EGLDisplay display, EGLSurface surface) {
    EGL14.eglDestroySurface(display, surface);
}
​
public void destroyContext(EGLDisplay display, EGLContext context) {
    EGL14.eglDestroyContext(display, context);
}

终止与 EGLDisplay 之间的连接

ini 复制代码
EGL14.eglTerminate(egl.getDisplay());

销毁显示设备;

OpenGL ES 录像

其实预览和录像,在获取camera数据的逻辑上,其实是一致的,只是预览过程中camera数据渲染完成后是显示到窗口中,录像过程中camera数据经过渲染之后,使用MediaCodec进行编码,然后进行落盘生成视频文件;

其实差别就在于使用 OpenGL ES API 绘制图形这个过程中;

在预览过程中,通过preview Surface创建EGLSurface实例,这个preview Surface是从TextureView中获取的;同时会将纹理绘制到preview Surface中;

在录像过程中,通过MediaCodec创建的Surface创建的EGLSurface实例,然后MediaCodec通过Surface获取camera数据,然后进行编码、混合和落盘;

从使用什么类型的surface创建的EGLSurface可以区别预览录像

我们主要看一下录像的onCall()方法逻辑:

scss 复制代码
public void onCall(RenderBean rb) {
    if (isEncodeStarted) {
        ..................
        MediaEncoderManager.MediaEncoder currentMediaEncoder = mMediaEncoderManager.getCurrentMediaEncoder();
        if (currentMediaEncoder != null && !currentMediaEncoder.videoCodecStopSingle) {
            EGLSurface currentSurface = currentMediaEncoder.eglSurface;
            if (currentSurface != null) {
                rb.eglHelper.makeCurrent(currentSurface);
​
                GLES20.glViewport(0, 0, mConfig.width, mConfig.height);
                mFilter.draw(rb.textureId);
                if (mListener != null) {
                    mListener.onDrawEnd(currentSurface, rb);
                }
                rb.eglHelper.swapBuffers(currentSurface);
            }
        } else {
            if (currentMediaEncoder != null) {
                LogManager.loge(TAG, "onCall: The frame is lost because it has been marked as ended");
            }
        }
    }
}

在该方法中,通过EGLSurface currentSurface = currentMediaEncoder.eglSurface方式获取的EGLSurface实例,我们看一下currentMediaEncoder.eglSurface的定义:

ini 复制代码
static class MediaEncoder {
    MediaCodec videoCodec;
    MediaCodec audioCodec;
    MediaMp4Muxer mediaMuxer;
    EGLSurface eglSurface;
    Surface inputSurface;
    ..................
}

自定义了一个类,其中包含了MediaCodec videoCodec、EGLSurface eglSurface以及Surface inputSurface;

其中MediaCodec 是Android原生提供的编解码机制;

Surface inputSurface是录像使用的Surface,类同于预览使用的Surface;

主要看一下inputSurface是如何定义的:

ini 复制代码
if (mediaencoder.inputSurface == null) {
    mediaencoder.inputSurface = MediaCodec.createPersistentInputSurface();
}
mediaencoder.videoCodec.setInputSurface(mediaencoder.inputSurface);
​
if (mediaencoder.eglSurface == null) {
    mediaencoder.eglSurface = mEglHelper.createWindowSurface(mediaencoder.inputSurface);
}

inputSurface是通过MediaCodec.createPersistentInputSurface()获取的;

然后调用EGL14.eglCreateWindowSurface方法创建对应的EGLSurface;

在onCall()方法中将EGLSurface创建完成之后,会调用mListener.onDrawEnd(currentSurface, rb)方法,进行编码逻辑执行,这里就不展开叙述编码模块流程了;

相当于将预览逻辑中的mFilter.draw(rb.textureId)替换为mListener.onDrawEnd(currentSurface, rb),就可以从预览逻辑转变为录像逻辑;

相关推荐
咋吃都不胖lyh2 小时前
SQL-多对多关系
android·mysql·数据分析
cyy2982 小时前
android 屏幕适配
android
sorryhc2 小时前
如何设计一个架构良好的前端请求库?
前端·javascript·架构
Digitally4 小时前
如何通过 5 种有效方法同步 Android 和 Mac
android·macos
行墨5 小时前
Jetpack Compose 深入浅出(二)——基础组件Text
android
SandySY6 小时前
品三国谈人性
算法·架构
雨白7 小时前
深入理解协程的运作机制 —— 调度、挂起与性能
android·kotlin
沐怡旸7 小时前
【Android】Android系统体系结构
android
韩非7 小时前
if 语句对程序性能的影响
算法·架构
自由的疯7 小时前
java DWG文件转图片
java·后端·架构