基于OpenGl的yuv视频渲染程序开发实例
前言
这是我音视频专栏的第一篇实例解析,也算是入门篇,重点讲下如何使用OpenGl去渲染一个yuv视频。
本篇博文涉及的知识点主要有三个:1.yuv的概念 2.基于ndk进行C++程序的基本编写 3.OpenGl纹理的绘制
本文将重点讲知识点1和3,ndk开发部分就不细谈,由于OpenGl知识体系庞大,本文也是根据重点来分析,所以如果没有ndk开发基础和OpenGl基础的读者看本文可能会比较困难。
谈谈YUV
YUV,是一种颜色编码方法。常使用在各个影像处理组件中。Y”表示明亮度(Luminance、Luma),“U”和“V”则是色度、浓度(Chrominance、Chroma)相对我们都比较熟悉的编码格式RGB,RGB诉求于人眼对色彩的感应,YUV则着重于视觉对于亮度的敏感程度。 **YUV在对照片或影片编码时,考虑到人类的感知能力,允许降低色度的带宽。**换句话说,也就是编码的时候允许Y的量比UV要多,允许对图片的UV分量进行下采样(关于下采样,简单来说就是以比原来更低的采样率进行采样。详细可以看下维基百科:Downsampling (signal processing)也可以看下知乎这篇文章:oversampling,undersampling,downsampling,upsampling 四个概念的区别和联系是什么?)。
图像中的Y’, U,和V组成:
这样说有点抽象,可以看看微软这篇有名的文章进行理解:Video Rendering with 8-Bit YUV Formats
这里主要讲yuv的两个方面,分别是采样格式和存储格式。采样格式简单可以理解一张原图,每个像素怎么采样yuv各个分量,比如每隔几个像素采一个y分量(或者u、v)。存储格式简单来说就是采样之后,按照什么方式存储,比如哪个字节存储y,第几个字节存储u。
yuv采样格式:
文章里面“YUV Sampling”一节详细说明了各种不同格式的yuv是如何采样的。
以下是对该章节的节选翻译:
YUV的优点之一是,感知质量不会显著下降的前提下,色度通道的采样率与Y通道的采样率相比更低。一般用一个叫做A:B:C(即y:u:v)的符号用来描述U和V相对于Y的采样频率,为了方便理解,使用图来描述,图中y分量使用x表示,uv使用o表示:
4:4:4意味着色度通道没有向下采样,也就是说yuv三个通道都是全采样:
4:2:2表示2:1水平下采样,没有垂直下采样。每条扫描线包含四个Y样本对应两个U或V样本。也就是水平方向按照y:uv使用2:1进行采样,垂直方向全采样的方式:
4:2:0表示2:1水平下采样,2:1垂直下采样。也就是水平方向按照y:uv使用2:1进行采样,垂直方向按照y:uv使用2:1的方式:
4:1:1表示4:1水平下采样,没有垂直下采样。每条扫描线包含四个Y样本对应于每一个U或V样本。4:1:1抽样比其他格式更少见,本文不详细讨论。
yuv存储格式:
YUV存储格式有两大类:planar 和 packed:
packed:Y、U和V组件存储在一个数组中。每个像素点的Y,U,V是连续交错存储的。和RGB的存储格式类似。
planar :Y、U和V组件存储为三个独立的数组中。
y、u、v每个采样点使用8bit存储。
接下来详细讲下集中常见的yuv格式存储方式:
4:2:2格式:
使用16bit数据表示一个像素点,主要有两种具体格式:
YUY2:
属于packed类型,YUY2格式,数据可视为unsigned char数组。第一个字节包含第一个Y样本,第二个字节包含第一U (Cb)样本,第三字节包含第二Y样本,第四个字节包含第V (Cr)样本,以此类推,如图:
可以看到,Y0 和 Y1 公用 U0 V0 分量,Y2 和 Y3 公用 U1 V1 分量,以此类推。
UYVY:
也是属于属于packed类型的,和YUY2和类似,只是存储方向是相反的:
4:2:0格式
使用12bit数据表示一个像素点,该格式又包含多种存储方式,这里重点将以下几种:
YUV 420P 和 YUV 420SP 都是基于 Planar 平面模式 进行存储的,先存储所有的 Y 分量后, YUV420P 类型就会先存储所有的 U 分量或者 V 分量,而 YUV420SP 则是按照 UV 或者 VU 的交替顺序进行存储了,具体查看看下图(图来源于:音视频基础知识—像素格式YUV):
YUV420P:
(这里需要敲黑板,因为本文播放的yuv就是YUV420P格式,熟悉它的存储格式才可以理解代码中读取视频帧数据的逻辑)
YUV420SP
4:2:0格式还有YV12、YU12、NV12 、NV21等存储格式,这里因为篇幅关系就不做细谈。
yuv就先介绍到这里,熟悉yuv对于后面yuv视频播放至关重要。
谈谈OpenGl
OpenGL是一个跨平台的软件接口语言,用于调用硬件的2D、3D图形处理器。由于是只软件接口,所以具体底层实现依赖硬件设备制造商。
关于OpenGl的知识,可能写20篇博文也介绍不完,这里只介绍和当前播放yuv相关的,不会很详细,详细教程可以看这个网站:欢迎来到OpenGL的世界
首先要解释的是OpenGl的图形渲染管线:指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程。分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。
图形渲染管线接受一组3D坐标,然后把它们转变为你屏幕上的有色2D像素输出。图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。
当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的相互独立的并行处理小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。
图形渲染管线的每个阶段的展示:
图形渲染管线的第一个部分是顶点着色器(Vertex Shader),它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标(坐标系统的转化),同时顶点着色器允许我们对顶点属性进行一些基本处理。
图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状。如图的例子就是将三个顶点装配为三角形。
几何着色器的输出会被传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment,OpenGL渲染一个像素所需的所有数据)。
片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。
而我们要处理的,主要就是顶点着色器和片段着色器的代码逻辑,着色器是用叫GLSL的类C语言写成的,它包含一些针对向量和矩阵操作的有用特性。详细语法见着色器
纹理
待续
参考文献:
learnopengl
Video Rendering with 8-Bit YUV Formats
音视频基础知识—像素格式YUV