继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Android下三种离屏渲染技术

2024-01-15 19:54:146310浏览

李超

7实战 · 30手记 · 13推荐
TA的实战

经常有同学问我如何将OpenGL渲染出的结果保存成mp4文件,今天我们就来聊聊这个话题。

离屏渲染

要想将OpenGL渲染出的结果保存成mp4文件,我们需要使用一种称为离屏渲染的技术。

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

使用离屏渲染有什么好处呢?比如我们可以让OpenGL将渲染的结果先输出到一个帧缓冲区中,然后再把帧缓冲区的内容送给编码器进行编码,最后写到mp4文件中,这样就达到我们的目标了。当然在将结果保存成mp4文件的同时,还能让它在屏幕上显示出来就更好了,那么如何做到这一点呢?

在Android系统下,有三种方法可以实现,下面我们就来一一介绍一下。

Android下离屏渲染的三种方式

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

首先我们来看看如何通过二次渲染法实现将OpenGL渲染结果送给屏幕和编码器。

二次渲染法

想通过二次渲染法实现OpenGL渲染结果送屏幕和编码器,我们必须使用SurfaceView,而不能使用GLSurfaceView。

之所以如此,是因为GLSurfaceView有自己的专有渲染线程,这虽然减少了开发者使用OpenGL的复杂度,但也同时降低了开发者对EGL的控制力,显得不够灵活,从而无法将OpenGL的渲染结果输出给多个目标。

为了解决这个问题,我们需要使用 SurfaceView+自己创建渲染线程 这个组合,在自己创建的渲染线程中使用EGL API,通过多次渲染并将结果输送给多个目标Surface来实现二次渲染,其架构如下图所示:

图片描述

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

其中SurfaceView用于展示OpenGL的渲染结果,而且它还实现了两个重要的事件处理方法,surfaceCreatedsurfaceChanged。surfaceCreated方法是在SurfaceView中的Surface创建好后被调用。在该方法中,首先创建一个渲染线程,并在渲染线程中创建EGL环境。而surfaceChanged方法是在Surface发生变化时被调用,在该方法中我们会创建FBO环境,关于这方面的内容我在讲解FBO时再向你做详细介绍。

MediaCodec用于编码,它也有一个Surface用于接收需要编码的数据。

Camera用于采集视频数据,当采集到视频数据后,它会通知渲染线程,渲染线程通过SurfaceTexture从BufferQueue中取走数据,并交由OpenGL处理。

OpenGL/EGL用于渲染,它收到视频帧后调用Shader程序进行渲染,之后将渲染后的结果输出给SurfaceView的Surface,让其在屏幕上显示;接下来,调用EGL的eglMakeCurrent方法,将默认Surface从SurfaceView的Surface改为MediaCodec的Surface,然后再次调用Shader程序进行渲染,并将渲染后的结果输出给MediaCodec的Surface进行编码。也就是说,二次渲染就是调用两次Shader程序进行渲染,每次渲染后的结果输送给不同的目标Surface,因此称为二次渲染法

当然,如果你需要将渲染结果输出给多个目标也是可以的,那就需要调用多次Shader程序,每次将渲染结果输送给不同的目标Surface就好了。

FBO法

上面的方法虽然能够将同一个源输送给不同的目标,但每次都要调用OpenGL进行重绘,效率上确实不高。尤其是当我们要渲染的模型特别复杂的时候,会严重影响效率,有没有更好的解决办法呢?

OpenGL为我们提供了一种高效的办法,即FBO(FrameBufferObject)。下面我们来看一下如何通过FBO高效的向多个目标输送渲染结果,其架构图如下所示:
图片描述

其架构图与我们上面展示的二次渲染架构图很类似,只不过通过OpenGL渲染的结果不再直接送给不同的Surface,而是将结果输出到FBO中,这里你可以先将FBO想像为一块特殊的空间(关于FBO的细节,我会单独写一篇文章进行讲解)。

然后通过另外一种Shader程序(这种Shader程序是专门用于处理FBO纹理的)再次进行渲染。当然,这种Shader程序由于处理的是纹理,所以会比第一次使用的Shader程序效率高很多。

渲染成功后将结果输出给屏幕,然后切换Surface再次调用Shader(第二个Shader),这次渲染的结果将会输送给MediaCodec的Surface进行编码。

因此,通过FBO方法我们只需要对原模型渲染一次,将结果保存到FBO。之后再对FBO中的内容进行多次渲染,通过这种方式来提高效率。

注意,之所以通过这种方式可以提高效率,原因就在于OpenGL对FBO纹理的渲染效率远远高于对原模型的渲染效率

BlitFramebuffer法

通过上面的描述,我们仍然觉得不够高效。有没有更好的方法,只渲染一次就可以将结果输送给多个目标呢?到了OpenGL3.0出现了一种更高效的方法,即BlitFramebuffer

它是如何工作的呢?其工作架构图所下所示:
图片描述

该方法不再使用FBO做缓存,而是像二次渲染法一样,先将渲染的内容输出到当前Surface中,但并不展示到屏幕上

相当于把当前的Surface当作一个缓冲区,然后切换Surface,此时MediaCodec的Surface变成了当前Surface,接下来利用OpenGL3.0提供的API BlitFramebuffer从原来的Surface拷贝数据到当前Surface中,再调用EGL的eglSwapBuffers将Surface中的内容送编码器编码。之后再将当前Surface切回原来的Surface,也就是SurfaceView的Surface,同样调用EGL的eglSwapBuffers方法,将其内容显示到屏幕上。

至此,实现了OpenGL仅渲染一次,却可以输出给多个目标,这种方法是最高效的。

小结

本文向你介绍了三种离屏渲染的方法,其中最高效的是BlitFramebuffer方法,但它是在OpenGL3.0才有的API,因此只有OpenGL3.0才可以使用该方法;其次是FBO方法,但其复杂度要比二次渲染法高很多,并且效率是有提高与原模型的复杂高有很大关系,如果原模型复杂度并不高,那么它与二次渲染法的效率并不多。

此外,本篇文章中还有很多细节并没有介绍到,比如如何使用FBO,如何在自己创建的渲染线程中构建EGL环境,如何使用BlitFramebuffer等,这些内容我将在后面的文章中再向你做详细讲解。

参考资料

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP