Unity 实现利用 Andorid 能力进行视频渲染播放
在 Unity 中使用 Android 侧提供的视频渲染相关的能力,有两种方案可选:
第一种是将渲染播放页单独做一个页面,在 Unity事件交互的时候打开对应 Activity 页面,或者获取到 Unity 创建的 Acitivity 动态添加 View。
第二种是只借助 Android 的渲染能力,将数据渲染到 Unity 的控件上。
两种方案各有优劣,第一种大大地减少了播放器相关的开发工作量,整个页面逻辑可以实现复用,但是交互页面的话 iOS/Android 需要写两套。第二种实现成本相对较高,但是交互可以由 Unity 侧进行,只是播放器使用封装好的 plugin 进行,能达到交互相对较统一,本文也主要是讲述该方案的实现。
Android 平台基本播放逻辑
在正式开发改造之前,对 Android 侧的一个播放器渲染流程进行简单的介绍,以 MediaPlayer 为例,利用 MediaPlayer 进行视频解码渲染,并将视频最后输出到 SurfaceView 上,一次播放器视频渲染到View上的的主要代码流程为:
1 | public void initPlayer() { |
对于渲染 mediaPlayer.setSurface(surface)
设为播放器解码数据的接受器,Surface 来自于 SurfaceView。
播放器是将数据图形绘制在 Surface 对象上,Surface中会关联一个 BufferQueue 用于提供图像数据缓存,SurfaceFlinger 会把 Surface 对应的图像层混合在一起,将其输出到 FrameBuffer 中(Framebuffer就是一块内存区域,它通常是显示驱动的内部缓冲区在内存中的映射),最后在屏幕上看到合成的图像。
整个流程引入外部大佬的一张图所示:
Unity 中的一些改造
上面的流程最终是通过播放器解码渲染到 SurfaceView 上,当然,你可以通过获取到 UnityPlayer 对应的 Acitivity 将这个 SurfaceView 动态添加到当前界面,实现“在 Unity 中利用 Android 能力进行视频渲染”。
所以需要对其进行改造,我们的目的是实现 Android 播放器数据渲染到 Untiy 的组件中。实现这一过程需要借助 FBO(Frame Buffer Object) 的能力。
(一)FBO
在 OpenGL 渲染管线中几何数据和纹理经过变换和一些测试处理,最后以二维像素的形式显示在屏幕上。OpenGL管线的最终渲染目的地被称作帧缓存(framebuffer),OpenGL渲染管线的最终位置是在帧缓冲区中,默认情况下 OpenGL 使用的是窗口系统提供的帧缓冲区。
但有些场景是不想要直接渲染到窗口上的(例如加视频特效),于是 OpenGL 提供了一种方式来创建额外的帧缓冲区对象(FBO)。使用帧缓冲区对象,OpenGL 可以将原先绘制到窗口提供的帧缓冲区重定向到 FBO 之中。FBO本身不是一块内存,没有空间,真正存储东西,可实际读写的是依附于FBO的东西:纹理(texture)和渲染缓存(renderbuffer),依附的方式,是一个二维数组来管理,结构如图所示:
(二)具体实现
使用 FBO 我们可以将渲染目标渲染到其他的空间,我们目的是将播放器解码后的数据渲染到 Unity 控件的纹理空间中。
渲染播放器将输出到 FBO 中,FBO 指向 Unity 控件数据的输入,从而实现:Android 的播放器输出数据显示到 Unity 的控件中。
(三)从渲染输出数据到外部纹理
由于 mediaPlayer.setSurface(surface)
对应的 Surface 来源于 SurafaceView,会直接渲染到屏幕上,这里我们需要使用 构造一个新的 SurfaceTexture 以将图像流式传输到给定的 OpenGL 纹理;
要获取到播放器渲染得数据,需要借助 SurfaceTexture ,SurfaceTexture 是Surface 和 OpenGL ES 纹理的结合,其对图像流的处理并不直接显示,而是从图像流中捕获帧作为 OpenGL 的外部纹理,图像流来自相机预览和视频解码。
SurfaceTexture 创建的 Surface 是数据的生产者,而 SurfaceTexture 是对应的消费者,Surface 接收媒体数据并将数据发送到 SurfaceTexture,当调用 updateTexImage 的时候,创建SurfaceTexture 的纹理对象相应的内容将更新为最新图像帧,也就是会将图像帧转换为 GL 纹理,并将该纹理绑定到 GL_TEXTURE_EXTERNAL_OES 纹理对象上。具体实现逻辑参考:Android Opengl OES 纹理渲染到 GL_TEXTURE_2D
1 | SurfaceTexture surfaceTexture = new SurfaceTexture(videoTextureId); |
其中 videoTextureId 来源于创建的 OES 纹理:
1 | public static int createOESTextureID() { |
(四)FBO纹理数据到 Unity 的纹理数据
学习了解到Unity中可以使用 RawImage 或者 quad 等相关控件可以显示纹理,这里以 RawImage 为例。在 Unity 脚本编写初始化的逻辑,构造一个 Texture2D 对象,将句柄传递到 Android,并赋值给 RawImage,并将texture id 传递到 Android 平台,完成一次渲染的重定向。
1 | void InitPlayer() |
创建FBO
1 | public static int createFBO() { |
为SurfaceTexture
设置了 OnFrameAvailableListener
后,当有新的图形流数据生成之后,就可以通过 mSurfaceTexture.updateTexImage()
将当前图片流更新到纹理所关联的OpenGLES中纹理,并绘制 FBO.
1 | publc void draw() { |
这一步是最关键的,实现了将 FBO 的输出指向 Unity 里面创建的纹理,也就实现了 Android 渲染与 Unity 之间的数据打通。
这里的 unityTextureId 来源于在 Unity 中初始化的 (int)texture2D.GetNativeTexturePtr()
值。
整体的流程为:
效果图:
图中播放视频区域为 Unity 的 RawImage 控件,渲染的视频通过 Pag 等相关素材由渲染SDK合成。
如图所示,视频画面正常地进行渲染,图中有两个区域展示了视频画面,上面的使用的 Quad 组件,下面是用的 RawImage,流程都一直,只是在 Unity 使用 Texture2D 的时候通过 Quad.mainTexture = texture2D
赋值。
总结
本文主要讲了 Unity 利用 Android 提供的能力进行视频相关的特效渲染的方案,总体正常运行。还需要一些优化,例如对 Multithreaded Rendering
配置还未支持,以及一些逻辑可能受限于游戏侧的配置,例如图形渲染的配置使用的 OpenGL3.0,如果使用 OpenGL2.0 或者 Vulkan,还需要单独调整相关逻辑。