OpenGL.ES在Android上的简单实践:23-水印录制(FBO离屏录制,解决透明冲突)

1、水印签名罢工了?

不知道大家有没注意到,之前我们使用MediaCodec录制的视频,水印签名那部分区域还是黑黑的啊(笑哭.jpg)。道理还是之前说过的,原生的Surface默认格式是RGB565,不支持透明通道。我也在 20-水印录制 提供了解决方案。 第一种就是在shader层使用内置函数Mix进行混合(不推荐);第二种解决方案就是利用系统API,SurfaceHolder.setFormat(PixelFormat.RGBA_8888)(推荐);  但是我们这次的Surface是引用MediaCodec.createInputSurface。这下就没能setFormat了?(起码我是不知道Surface->SurfaceHolder,有知道方法的兄弟请指引一下)    难不成我们录制视频的水印就这样黑黑不处理?不要怂,我还有法宝!

2、离屏渲染->FrameBufferObject

今天我为大家介绍离屏渲染的概念。百度下来很多都是在说iOS的,其实这个概念不止是iOS独有,这个是所有渲染系统都应该具备的。在OpenGL中,GPU屏幕渲染有以下两种方式:

1.On-Screen Rendering

意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。(例如我们显示/录制的Surface)

2.Off-Screen Rendering
意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。(区别于Surface的另外一种渲染区)

你可能会说,我懂啊,录制视频就是离屏渲染啊!不不不,概念可不一样哦,请参照下图并说明。

我们用于预览/录制的Surface都是由Android系统提供给OpenGL渲染的帧缓冲区,OpenGL的所有渲染结果都是直接到达到帧缓冲区,这是on-screen的渲染方式;

除此方式以外,OpenGL还扩展提供了一种方式来创建额外的帧缓冲区对象(FBO)。使用帧缓冲区对象,OpenGL可以将原先绘制到窗口提供的帧缓冲区重定向到FBO之中。和窗口提供的帧缓冲区类似,FBO提供了一系列的缓冲区,包括颜色缓冲区、深度缓冲区和模板缓冲区。这些逻辑的缓冲区在FBO中被称为 framebuffer-attachable说明它们是可以绑定到FBO的对象数组上。

FBO中有两类绑定的对象:纹理图像(texture images)和渲染图像(renderbuffer images)。如果纹理对象绑定到FBO,那么OpenGL就会执行渲染到纹理(render to texture)的操作,如果渲染对象绑定到FBO,那么OpenGL会执行离屏渲染(offscreen rendering)

FBO可以理解为包含了许多挂接点的一个对象,它自身并不存储图像相关的数据,他提供了一种可以快速切换外部纹理对象和渲染对象挂接点的方式,在FBO中必然包含一个深度缓冲区挂接点和一个模板缓冲区挂接点,同时还包含许多颜色缓冲区挂节点(具体多少个受OpenGL实现的影响,可以通过GL_MAX_COLOR_ATTACHMENTS使用glGet查询),FBO的这些挂接点用来挂接纹理对象和渲染对象,这两类对象中才真正存储了需要被显示的数据。FBO提供了一种快速有效的方法挂接或者解绑这些外部的对象,对于纹理对象使用 glFramebufferTexture2D,对于渲染对象使用glFramebufferRenderbuffer 。具体描述参考下图:

上面是知识概念,而且已经画出了重点。白话文解释:FBO是一个挂接器,类似画家画画用的托架;其中FBO只能挂接两种对象,纹理图像 和 渲染图像,这个理解就是画家准备创作作品前,在托架上放的是油画纸还是水墨纸(纹理),或者根本不是放画纸,放的是木板雕刻(渲染模板)。最后我们等画家创作出他的艺术品后,直接搬到到展示区,呈现給大家。(鼓掌散花)

3、创建FBO

既然我们已经认识了什么是FBO,接下来我们就认识认识关于FBO的OpenGL.API

和其他VAO,VBO,IBO一样,通过调用Gen****,创建FBO,代码如下:

        final int frameBuffers[] = new int[1];GLES20.glGenFramebuffers(1, frameBuffers, 0);if (frameBuffers[0] == 0) {int i = GLES20.glGetError();throw new RuntimeException("Could not create a new frame buffer object, glErrorString : "+ GLES20.glGetString(i));}int frameBufferId = frameBuffers[0];

接着,我们开始使用FBO的时候,需要通过绑定纹理对象来锁定挂接渲染区。

        int textureId = createFBOTexture(width, height, format); //创建指定format的纹理对象GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBufferId); //绑定fbo进行操作GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, //说明FBO挂接操作GLES20.GL_COLOR_ATTACHMENT0, //指定挂接区是单元0GLES20.GL_TEXTURE_2D, //说明挂接的是纹理对象textureId, //具体挂接的纹理对象0);

接着我们就可以渲染了(draw),当渲染结束后,我们就需要解绑FBO,告诉OpenGL我们已经不需要在FBO上创作了。

        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

最后,展览结束关门了,我们需要收拾东西。

        GLES20.glDeleteFramebuffers(1, new int[]{frameBufferId}, 0);GLES20.glDeleteTextures(1, new int[]{textureId}, 0);

以上,so easy。妈妈再也不用担心我们学不会啦。

4、使用FBO

可能有同学会吐槽,这么简单的API还要你教怎么使用哦。别紧张嘛,会用 和 用得native是两码事嘛。所以接下来我给自己的FBO封装,方便往后代码的编写调用。

public class FrameBuffer {private int mWidth;private int mHeight;private int frameBufferId;private int textureId;public FrameBuffer() {mWidth=0;mHeight=0;frameBufferId=0;textureId=0;}public int getTextureId() {return textureId;}public boolean isInstantiation() {return mWidth!=0||mHeight!=0;}public boolean setup(int width, int height){this.mWidth = width;this.mHeight = height;final int frameBuffers[] = new int[1];GLES20.glGenFramebuffers(1, frameBuffers, 0);if (frameBuffers[0] == 0) {int i = GLES20.glGetError();throw new RuntimeException("Could not create a new frame buffer object, glErrorString : "+ GLES20.glGetString(i));}frameBufferId = frameBuffers[0];GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);return  true;}public boolean begin(){if(textureId==0 ){textureId = createFBOTexture(mWidth, mHeight, GLES20.GL_RGBA);}GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBufferId);GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,GLES20.GL_TEXTURE_2D, textureId, 0);return true;}private int createFBOTexture(int width, int height, int format) {final int[] textureIds = new int[1];GLES20.glGenTextures(1, textureIds, 0);if(textureIds[0] == 0){int i = GLES20.glGetError();throw new RuntimeException("Could not create a new texture buffer object, glErrorString : "+ GLES20.glGetString(i));}GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[0]);GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, format, width, height,0, format, GLES20.GL_UNSIGNED_BYTE, null);return textureIds[0];}public void end(){GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);}public void release(){mWidth = 0;mHeight = 0;GLES20.glDeleteFramebuffers(1, new int[]{frameBufferId}, 0);GLES20.glDeleteTextures(1, new int[]{textureId}, 0);frameBufferId = 0;textureId = 0;}
}

然后我们回到测试页面ContinuousRecordActivity,开始使用我们的FBO修正录像签名的透明问题。概述如下图:

不知道大家有没看懂,这个就是利用离屏渲染 和 实时渲染的区别所在。离屏渲染充当一些幕后的准备操作,当我们向fbo挂载一个纹理对象的时候,在fbo所渲染的任何指令,都是输出到这个挂载的纹理对象当中。而且这个纹理的格式、大小、类型都是由我们自己定义,非常方便灵活。

利用这个方法,我们可以声明一个支持透明格式(RGBA四通道)的纹理对象,随后把这个纹理各自传到实时预览 和 录制器当中渲染,以到达效果。事不宜迟,show code!

    private FrameBuffer fbo;private FBOFrameRect mFBOFrameRect;@Overridepublic void surfaceCreated(SurfaceHolder surfaceHolder) {... ... ...mFrameRect.setShaderProgram(new FrameRectSProgram());mWaterSign.setShaderProgram(new WaterSignSProgram());mFBOFrameRect.setShaderProgram(new WaterSignSProgram());... ... ...fbo = new FrameBuffer();fbo.setup(VIDEO_HEIGHT, VIDEO_WIDTH);}

我们新增一个FBOFrameRect,这个类和FrameRect区别在于内部引用的ShaderProgram,因为之前我的摄像头帧纹理是samplerExternalOES的类型;现在我们使用离屏渲染的方式,使用普通的纹理方式(纹理y坐标上下颠倒),所以我们直接引用水印签名的WaterSignSProgram就可以了;之后我们创建FBO,注意屏幕是竖屏的方式,和摄像头默认横屏是宽高互换。

    private void drawFrame() {mDisplaySurface.makeCurrent();fbo.begin();GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);GLES20.glEnable(GLES20.GL_BLEND);GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);mCameraTexture.updateTexImage();mCameraTexture.getTransformMatrix(mTmpMatrix);int viewWidth = sv.getWidth();int viewHeight = sv.getHeight();GLES20.glViewport(0, 0, viewWidth, viewHeight);mFrameRect.drawFrame(mTextureId, mTmpMatrix);GLES20.glViewport(0, 0, 288, 144);mWaterSign.drawFrame(mSignTexId);fbo.end();GLES20.glViewport(0, 0, viewWidth, viewHeight);mFBOFrameRect.drawFrame(fbo.getTextureId());mDisplaySurface.swapBuffers();// 水印录制 状态设置if(mRequestRecord) {if(!recording) {mRecordEncoder.startRecording(new CameraRecordEncoder.EncoderConfig(outputFile, VIDEO_HEIGHT, VIDEO_WIDTH, 1000000,EGL14.eglGetCurrentContext(), ContinuousRecordActivity.this));mRecordEncoder.setTextureId(fbo.getTextureId());recording = mRecordEncoder.isRecording();}mRecordEncoder.frameAvailable(mCameraTexture);} else {if(recording) {mRecordEncoder.stopRecording();recording = false;}}}

之后我们再看看drawFrame方法,我们把原来之前直接渲染的指令(mFrameRect.drawFrame 和 mWaterSign.drawFrame)都包裹在fbo(begin~end)的操作当中,这样渲染指令的结果就流入到fbo挂载的纹理对象。

正常的渲染流程结束后,我们就可以取出fbo.texture一整张的纹理对象,渲染到显示区域(mFBOFrameRect.drawFrame)。接着我们还要进行录制,把fbo.texture设置到编码录制器,编码录制器的渲染操作修改思路也是一样的。

    private void handleFrameAvailable(float[] transform, long timestampNanos) {//先推动一次编码器工作,把编码后的数据写入MuxermRecordEncoder.drainEncoder(false);mRecorderInputSurface.makeCurrent();GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);GLES20.glEnable(GLES20.GL_BLEND);GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);//GLES20.glViewport(0, 0, mConfig.mWidth, mConfig.mHeight );//mFrameRect.drawFrame(mFrameTextureId, transform);//GLES20.glViewport(0, 0, 288, 144);//mWaterSign.drawFrame(mSignTexId);GLES20.glViewport(0, 0, mConfig.mWidth, mConfig.mHeight );mFBOFrameRect.drawFrame(mFrameTextureId);// mRecorderInputSurface是 获取编码器的输入Surface 创建的EGLSurface,// 以上的draw直接渲染到mRecorderInputSurface,喂养数据到编码器当中,非常方便。mRecorderInputSurface.setPresentationTime(timestampNanos);mRecorderInputSurface.swapBuffers();}

我们可以看到,修改后的编码录制器不在需要独自的重新绘制FrameRect/WaterSign,我们只需要绘制显示FBO.texture就大功告成了,so easy。

而且因为FBO.texture是我们自己独立创建的,格式GLES20.GL_RGBA支持四通道的颜色区,水印签名不再变成黑色的了!

5、延伸思考。

这章我们学习了FBO,解决了MediaCodec.inputSurface不支持透明通道的问题。 其实FBO用途还有很多,比如微信视频通话中的显示对方的画中画效果,我们就可以利用FBO离屏渲染显示出来了;更有一些视频图像应用双击放大,右下角还保留显示着原图;再譬如一些短视频应用的 镜像 九宫格(不同色调) 等等效果。 相信同学们一定能自己扩展延伸。

至此,OpenGL.ES在Android上的简单实践:水印录制  结束。

The End .

OpenGL.ES在Android上的简单实践:23-水印录制(FBO离屏渲染,解决透明冲突,画中画)相关推荐

  1. OpenGL.ES在Android上的简单实践:11-全景(索引-深度测试)

    OpenGL.ES在Android上的简单实践:11-全景(正方体-索引-深度测试) 0.全景图要怎么看? What is 全景?可能很多人单看这名字不太清楚.但看到下面的图的时候就噢的一声~瞬间廓然 ...

  2. OpenGL.ES在Android上的简单实践:10-曲棍球(拖动物体、碰撞测试)

    OpenGL.ES在Android上的简单实践:10-曲棍球(拖动物体.碰撞测试) 1.让木槌跟随手指移动 继续上一篇文章9的内容.既然可以测试木槌是否被触碰了,我们将继续努力下去:当我们来回拖动木槌 ...

  3. OpenGL.ES在Android上的简单实践:21-水印录制(MediaCodec输出h264+MediaMuxer合成mp4 上)

    OpenGL.ES在Android上的简单实践:21-水印录制(MediaCodec输出h264+MediaMuxer合成mp4 上) 1.录制视频需要什么? 在上篇文章,我们已经成功的满足了需求,在 ...

  4. OpenGL.ES在Android上的简单实践:20-水印录制(预览+透明水印 表情 弹幕 gl_blend)

    OpenGL.ES在Android上的简单实践:20-水印录制(预览 gl_blend) 1.继续画出预览帧 紧接着上篇文章,既然是要画出预览帧,按照之前其他项目的架构组成.我们是通过模型FrameR ...

  5. SDL2源码分析之OpenGL ES在windows上的渲染过程

    SDL2源码分析之OpenGL ES在windows上的渲染过程 更新于2018年11月4日. 更新于2018年11月21日. ffmpeg + SDL2实现的简易播放器 ffmpeg和SDL非常强大 ...

  6. 在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping)

    在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping) 视差贴图 最近一直在研究如何在我的 iPad 2(只支持 OpenGL ES 2.0, 不支持 3.0) 上实现 视 ...

  7. OpenGL ES for Android 绘制旋转的地球

    No 图 No Code,我们先来欣赏下旋转的地球: 是不是很酷炫,要想绘制出上面酷炫的效果需要3个步骤: 计算球体顶点数据 地球纹理贴图 通过MVP矩阵旋转地球 计算球体顶点数据 我们知道OpenG ...

  8. android opengl流程,【Android OpenGL ES】Android Opengl ES创建流程

    在android 1.0rc2 sdk中,提供了以下包支持Opengl ES 编程: 一.openglES包 android.opengl Class: GLDebugHelper:用于调试OpenG ...

  9. OpenGL ES for Android 绘制立方体

    立方体有6个面,8个顶点,因此绘制立方体其实就是绘制6个面. 顶点shader attribute vec4 a_Position; attribute vec4 a_color; varying v ...

最新文章

  1. 无监督学习:大数据带我们洞察现在,但小数据将带我们抵达未来
  2. iOS使用shell脚本批量修改属性
  3. [CTSC2017]吉夫特
  4. UIImage指定区域自由拉伸
  5. echarts中triggeron与trigger不能同时出现吗_好物|痛风、血糖高、虚不受补能吃它吗?你想知道的阿胶十问十答一锅出!...
  6. android红米3调用相机,红米3有什么接口?红米3有HDMI接口吗?
  7. Madagascar编程的Makefile文件配置
  8. 教你用R画列线图,形象展示预测模型的结果
  9. 视频直播方案(加强版
  10. Kali渗透测试:散列密码破解
  11. DEM生成等高线及提取等高线3+1种方法
  12. 会议OA(会议排座送审)
  13. SMARTBI权限管理
  14. 2021年起重机司机(限桥式起重机)考试及起重机司机(限桥式起重机)免费试题
  15. N沟道增强型MOS管原理
  16. Mapbox Android学习笔记(8)离线地图
  17. 第二季 明文封包教程
  18. 《财报就像一本故事书》刘顺仁(一) 山西出版集团山西人民出版社
  19. leecode做题笔记17————电话号码的字母组合
  20. AI全自动车辆外观视觉检测

热门文章

  1. CANOpen,关于 DS402 电机驱动器的状态切换
  2. 天梯赛 L2-008 最长对称子串(区间DP)
  3. Typora导出word操作指导
  4. 本地加密maven仓库密码
  5. IP-GUARD试用版下载地址
  6. 计算机行业现状概论,计算机教学专业概论论文
  7. LLVM pre-build binaries 配置路径
  8. 研究:武功与性欲的关系
  9. 常见蛋白质种类_常见的蛋白质种类归纳
  10. html5中footer元素的作用,html5 footer标签怎么用?footer标签的用法实例