Android Camera系列(八):MediaCodec视频编码下-OpenGL ES离屏渲染

所有随风而逝的都是属于昨天的,所有历经风雨留下来的才是面向未来的

  • Android Camera系列(一):SurfaceView+Camera

  • Android Camera系列(二):TextureView+Camera

  • Android Camera系列(三):GLSurfaceView+Camera

  • Android Camera系列(四):TextureView+OpenGL ES+Camera

  • Android Camera系列(五):Camera2

  • Android Camera系列(六):MediaCodec视频编码上-编码YUV

  • Android Camera系列(七):MediaCodec视频编码中-OpenGL ES多线程渲染

  • Android Camera系列(八):MediaCodec视频编码下-OpenGL ES离屏渲染

本系列主要讲述Android开发中Camera的相关操作、预览方式、视频录制等。项目结构简单、代码耦合性低,旨在帮助大家能从中有所收获(方便copy :) ),对于个人来说也是一个总结的好机会。

Alt

引言

上一篇中我们采用了共享EGLContext的方式,实现了将SurfaceTexture纹理绘制到不同线程的Surface中。这种方式需要采用多线程,实现起来不够线性,能不能在单一线程中将纹理绘制到多个Surface上呢?

还记得我们Camera2章节吗,Camera2是如何实现一份数据绘制到不同的Surface中的呢?查看源码我们可知,他是通过创建N个EGLSurface,每个EGLSurface绑定不同的Surface,具体要绘制到哪个Surface就通过makeCurrent方法进行切换控制。

离屏渲染

要将渲染的结果输出到不同的目标,我们需要使用一种称为离屏渲染的技术。

什么是离屏渲染呢?顾名思意,就是让OpenGL不将渲染的结果直接输出到屏幕上,而是输出到一个中间缓冲区(一块GPU空间),然后再将中间缓冲区的内容输出到屏幕或编码器等目标上,这就称为离屏渲染。

在Android系统下可以使用三种方法实现同时将OpenGL ES的内容输出给多个目标(屏幕和编码器)。第一种方法是二次渲染法;第二种方法是FBO;第三种是使用BlitFramebuffer

一. 二次渲染

想通过二次渲染实现OpenGL ES将渲染结果送给屏幕和编码器,我们必须自定义EGL环境,创建屏幕预览EGLSurface和编码器EGLSurface。Android Camera系列(四):TextureView+OpenGL ES+Camera不熟悉自定义OpenGL ES环境,请查看该章节。我们在自己创建的OpenGL ES线程中使用EGL API,通过多次渲染将结果输出给多个目标Surface来实现二次渲染,架构图如下:
Android Camera系列(八):MediaCodec视频编码下-OpenGL ES离屏渲染_第1张图片

上图我们看到有SurfaceView,MediaCodec,Camera,OpenGL/EGL等组件。

  • SurfaceView用于展示OpenGL的渲染结果
  • MediaCodec用于编码,关联的Surface用于接收需要编码的数据
  • Camera用于采集视频数据,采集到数据后通知渲染线程,渲染线程通过SurfaceTexture从BufferQueue中取走数据交由OpenGL处理
  • OpenGL/EGL用于渲染,它收到数据后调用Shader程序进行渲染;将渲染结果输出到SurfaceView中显示到屏幕;然后我们需要切换当前渲染的EGLSurface,通过调用EGL的eglMakeCurrent方法,将默认SurfaceView的EGLSurface切换到MediaCodec的EGLSurface上,然后再次调用Shader程序进行渲染,并将渲染结果输出给MediaCodec的Surface进行编码。

通过上面的流程我们知道,二次渲染就是调用了两次Shader程序进行渲染,每次渲染后的结果输送给不同的Surface,因此称为二次渲染法

渲染核心代码实现如下:

/**
 * 二次渲染方式
 *
 * @param recordWindowSurface
 * @return
 */
private boolean drawTwice(WindowSurface recordWindowSurface) {
   
    boolean swapResult;
    // 先绘制到屏幕上
    mPreviewTexture.getTransformMatrix(mDisplayProjectionMatrix);
    mCameraFilter.draw(mTextureId, mDisplayProjectionMatrix);
    swapResult = mWindowSurface.swapBuffers();

    // 再绘制到视频Surface中
    mVideoEncoder.frameAvailable();
    recordWindowSurface.makeCurrent();
    GLES20.glViewport(0, 0,
            mVideoEncoder.getVideoWidth(), mVideoEncoder.getVideoHeight());
    mCameraFilter.draw(mTextureId, mDisplayProjectionMatrix);
    recordWindowSurface.setPresentationTime(mPreviewTexture.getTimestamp());
    recordWindowSurface.swapBuffers();

    // Restore
    GLES20.glViewport(0, 0, mWindowSurface.getWidth(), mWindowSurface.getHeight());
    mWindowSurface.makeCurrent();
    return swapResult;
}

代码中我们有两个WindowSurface,一个是预览的WindowSurface,一个是编码WindowSurface,代码实现和上面的流程完全一样。

WindowSurface是我们在 Android Camera系列(四)中对EGL进行了封装,CameraFilter我们在Android OpenGLES2.0开发(八)中定义的Shader程序。

二. FBO

上面的代码流程我们知道,CameraFilter程序会调用两次draw方法,将同一个纹理绘制到不同的Surface上,看起来貌似没有问题。如果CameraFilter中要对图像进行变换,如美颜、高斯模糊等操作,那么我们就要对同一个纹理进行两次计算,这无疑是对GPU的浪费,且效率低下。那么我们如何只计算一次,把结果输出给不同的Surface呢?

OpenGL ES为我们提供了一种高效的办法,即FBO(FrameBufferObject)。接下里我们看看FBO是如何将渲染结果输送给多个目标的
Android Camera系列(八):MediaCodec视频编码下-OpenGL ES离屏渲染_第2张图片

FBO法中我们操作步骤如下:

  1. 将渲染结果绘制到FBO中
  2. 将FBO数据输送到屏幕中
  3. 将FBO数据输送到编码器

FBO法中,我们不直接将OpenGL ES的渲染结果输送给不同的Surface,而是将结果输出到FBO中,FBO可以理解为一块显存区域,用于存放OpenGL ES的渲染结果

我们知道CameraFilter着色器程序是将SurfaceTexture纹理渲染到EGLSurface中的,如何将纹理渲染到FBO帧缓冲区中呢,我们需要一个渲染到FBO中的着色器程序。

渲染结果输出到FBO后,我们可以将FBO结果分别输出给不同的目标,FBO->屏幕,FBO->MediaCodec。而FBO输出到不同的目标也需要一个新的着色器去绘制。

1. 创建FBO着色器

由上面的概念我们知道FBO就是一块缓冲区,那么我们就需要创建出这块缓冲区出来,我们需要对CameraFilter进行改造

/**
 * 渲染Camera数据,可离屏渲染到FBO中
 */
public class CameraFilter implements AFilter {
   
    //FBO id
    protected int[] mFrameBuffers;
    //fbo 纹理id
    protected int[] mFrameBufferTextures;
    // 是否使用离屏渲染
    protected boolean isFBO;
    
    public void setFBO(boolean FBO) {
   
        isFBO = FBO;
    }
    
    /**
     * 创建帧缓冲区(FBO)
     *
     * @param width
     * @param height
     */
    public void createFrameBuffers(int width, int height) {
   
        if (mFrameBuffers != null) {
   
            destroyFrameBuffers()

你可能感兴趣的:(Android,Camera,Android,OpenGL,ES,Android音视频,音视频,OpenGL,ES,MediaCodec,android)