基于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),就可以从预览逻辑转变为录像逻辑;

相关推荐
- 羊羊不超越 -6 分钟前
App渠道来源追踪方案全面分析(iOS/Android/鸿蒙)
android·ios·harmonyos
阿伟*rui12 分钟前
认识微服务,微服务的拆分,服务治理(nacos注册中心,远程调用)
微服务·架构·firefox
ZHOU西口44 分钟前
微服务实战系列之玩转Docker(十八)
分布式·docker·云原生·架构·数据安全·etcd·rbac
wk灬丨1 小时前
Android Kotlin Flow 冷流 热流
android·kotlin·flow
千雅爸爸1 小时前
Android MVVM demo(使用DataBinding,LiveData,Fresco,RecyclerView,Room,ViewModel 完成)
android
晨曦_子画1 小时前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
孤客网络科技工作室2 小时前
AJAX 全面教程:从基础到高级
android·ajax·okhttp
deephub3 小时前
Tokenformer:基于参数标记化的高效可扩展Transformer架构
人工智能·python·深度学习·架构·transformer
Mr Lee_3 小时前
android 配置鼠标右键快捷对apk进行反编译
android
顾北川_野4 小时前
Android CALL关于电话音频和紧急电话设置和获取
android·音视频