所有随风而逝的都是属于昨天的,所有历经风雨留下来的才是面向未来的
本系列主要讲述Android开发中Camera的相关操作、预览方式、视频录制等。项目结构简单、代码耦合性低,旨在帮助大家能从中有所收获(方便copy :) ),对于个人来说也是一个总结的好机会。
上一篇中我们采用了共享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来实现二次渲染,架构图如下:
上图我们看到有SurfaceView,MediaCodec,Camera,OpenGL/EGL等组件。
通过上面的流程我们知道,二次渲染就是调用了两次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程序。
上面的代码流程我们知道,CameraFilter
程序会调用两次draw
方法,将同一个纹理绘制到不同的Surface上,看起来貌似没有问题。如果CameraFilter中要对图像进行变换,如美颜、高斯模糊等操作,那么我们就要对同一个纹理进行两次计算,这无疑是对GPU的浪费,且效率低下。那么我们如何只计算一次,把结果输出给不同的Surface呢?
OpenGL ES为我们提供了一种高效的办法,即FBO(FrameBufferObject)。接下里我们看看FBO是如何将渲染结果输送给多个目标的
FBO法中我们操作步骤如下:
FBO法中,我们不直接将OpenGL ES的渲染结果输送给不同的Surface,而是将结果输出到FBO中,FBO可以理解为一块显存区域,用于存放OpenGL ES的渲染结果。
我们知道CameraFilter着色器程序是将SurfaceTexture纹理渲染到EGLSurface中的,如何将纹理渲染到FBO帧缓冲区中呢,我们需要一个渲染到FBO中的着色器程序。
渲染结果输出到FBO后,我们可以将FBO结果分别输出给不同的目标,FBO->屏幕,FBO->MediaCodec。而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()