想了挺久, 我决定还是记录一些东西;
讲述一下 rk_mmp_demo 编码的一些背景, 出于何种目的, 又解决了什么问题.
背景
大概在一年半年前由于工作上的原因接触到了 OpenGL
, 为此我还写了一篇笔记 OpenGL 简介.
在这个示例里, 演示了如何使用 OpenGL
实现转场特效这一实现, 比如百叶窗的 frag.glsl
就如下:
varying vec2 oUV;
uniform sampler2D TexA;
uniform sampler2D TexB;
uniform sampler2D TexL;
uniform float Progress;
void main(void)
{
float softness = 0.03f;
float luma = texture(TexL, oUV).x;
float time = mix(0.0f, 1.0f + softness, Progress);
vec4 acolor = texture(TexA, oUV);
vec4 bcolor = texture(TexB, oUV);
if (luma <= time - softness)
{
gl_FragColor = bcolor;
}
else if (luma >= time)
{
gl_FragColor = acolor;
}
else
{
float alpha = (time - luma) / softness;
gl_FragColor = mix(acolor, bcolor, alpha);
}
}
如果对 OpenGL
稍微熟悉的朋友应该会觉得这是一个简单的实现吧;
的确, 基于 glsl
的语法上来看这是一个简单的功能.
转场特效这个功能已经相当成熟了, 还不清楚的小伙伴也可以下载 OBS 尝试体验一下.
但是如果将它放到实际应用场景,随着而来就会带来几个不得不面对的问题.在实际的应用场景中, 转场特效一般用于场景切换, 更具体一步说则是视频源切换;
由此带来了以下几种特性:
- 纹理需要实时全局更新, 根据视频源的帧率决定
- 渲染方式为离屏渲染, 渲染完之后的结果一般需要进行再编码, 可能也会同步显示输出
- 解码数据和编码数据一般是
YUV
格式, 而 GPU 处理数据通常由以RGB
数据为主
如果是做 3D
引擎的同学可能已经看到了一些 Bad Smell
.
冲突
这, 会带来什么问题? 如果将转场特效的流程用图描述, 应该像下图这样:
按照流程, 把每一步都简单地说明一下:
-
(1) 上传纹理; 将编码器输出的
YUV
数据分别上传至Y
和NV
纹理, 这里主要使用glTexSubImage2D
接口 -
(2) 色彩空间转换, 写一个简单的
glsl
实现YUV
转RGB
-
(3) 转场合成, 其中的一个例子如上
-
(4) 渲染, 将转场合成结果渲染到
FBO
(FrameBuffer Object) 上
将 YUV 数据渲染到
FBO
这一操作并不常见, 这里简单描述一下操作过程; 具体地的操作实现可以使用MRT
多重渲染技术将一张
R8
纹理绑定至GL_COLOR_ATTACHMENT0
, 作为Y
分量输出将一张
R8G8
纹理绑定至GL_COLOR_ATTACHMENT1
, 作为NV
分量输出
- (5) 读取渲染结果至内存, 这里可以采用类似于
glReadPixels
的方式实现.
仔细看一下上图, 可以发现我特定将不同流程用不同的颜色标注了一下;
在不需要显示的情况下, 整条链路实际上还是以 CPU
作为 VPU
和 GPU
的纽带,负责为二者相互传输数据.
到这里发现什么问题了吗? VPU
-> CPU
-> GPU
这个过程传输的并不是什么小数据, 而是 YUV
裸数据!!!
如果按照 1080P30
的规格计算, 每秒的带宽占用最少也在 746,496,000 bits/s
, 这还仅仅是一路数据!
除此之外, CPU
和 GPU
对于像素的理解也是不同的; 在 CPU
侧, RGBA
是 uint32_t(0~255)
; 而在 GPU
侧, RGBA
则是 float(0.0f~1.0f)[4]
;
是的,当我们将纹理从 CPU
传输到 GPU
时; 我们需要将 uint8_t
除上 255
得到一个归一化的 float
给到 GPU
; 起码在 ARM
上, 这一步实际是由 CPU
实现.
所以当你实际跑起来时, 会很奇怪会什么 CPU
跑得老高了并且帧率还提不上.
探索
在发现这个问题之后,我辗转研究了不少项目,尝试从中寻找解决方法;
其中比较重要的包含
在加上各位大佬的帮助,找了一种比较合理的解决方案; 但是其并没有被完整实现在某一个仓库里,故我打算花一点时间整理一下,写一下这个系列的文章.
对了, 补充一下结论; 这个流程是可以优化的, 正如这个 rk_mmp_demo 给出的示例代码; 不过如何实现在后面的文章再进行描述了.