YUV编码
1.简介
YUV数据由Y、U、V三个分量组成,现在通常说的YUV指的是YCbCr。
Y:表示亮度(Luminance、Luma),占8bit(1字节)
Cb、Cr:表示色度(Chrominance、Chroma)
Cb(U):蓝色色度分量,占8bit(1字节)
Cr(V):红色色度分量,占8bit(1字节)
2.采样方式(444, 422, 420的区别)
把Y、U、V数据转变为R、G、B时用到
2.1 采样方式
采样方式通常用A:B:C的形式来表示,比如4:4:4、4:2:2、4:2:0等
-
A:假定在一块A*2个像素的概念区域,一般都是4.
-
B:第1行的色度(UV)采样数目。
-
C:第2行的色度(UV)采样数目
所以这里的B,C指的分别是在第一行,第二行UV采样的数目。 C的值一般要么等于B,要么等于0
示意图1:
示意图2
上图中,不管是哪种采样格式,Y分量都是全水平、全垂直分辨率采样的,每一个像素都有自己独立的Y分量
2.2 占用字节数
如果Y,U,V分别用一个字节存储,由上可以推算出不同采样方式下每个像素需要的平均字节数。 (现在也有Y,U,V分别用10bit储存的格式, 见#7)
-
4:4:4
一个像素YUV各占一个字节,总共3个字节 24bit
-
4:2:2
8个像素 : 8个Y + 2个U +2个V +2个U +2个V = 16字节
每个像素:16字节/8 = 2个字节 16bit
-
4:2:0
8个像素 : 8个Y + 2个U +2个V = 12字节
每个像素:12字节/8 = 1.5个字节 12bit
3.存储方式(Planar, Semi-Planar和Packed的区别)
存储格式,表示的是Y、U、V数据是如何排列和存储的。 读取或写入Y、U、V数据时用到.
3.1 分类
YUV的存储格式可以分为3大类:
名称 | 特点 |
---|---|
Planar(平面) | Y、U、V分量分开单独存储,名称通常以字母p结尾, 3个planar |
Semi-Planar(半平面) | Y分量单独存储,U、V分量交错存储, 名称通常以字母sp结尾, 1个planar |
Packed(紧凑) | 或者叫Interleaved(交错), Y、U、V分量交错存储, 1个planar |
3.2 444
I444 和YV24 主要是UV次序的不同
semi-planar NV24 和NV42 主要是UV交替次序的不同
3.3 422
Planar I422 YV16 区别:VU 次序
Semi-Planar NV16 NV61 区别:VU 次序
Packed UYVY YUYV YVYU 区别:VU 次序
3.4 420
Planar I420 YV12
采样方式420 I420,像素示意图
Semi-Planar NV12 NV21
采样方式420,各种存储方式, 像素示意图
4.借助ffmpeg格式转换
- PNG -> YUV
ffmpeg -i in.png -s 512x512 -pix_fmt yuv420p out.yuv
- YUV -> PNG
ffmpeg -s 512x512 -pix_fmt yuv420p -i in.yuv out.jpg
YUV 文件只是存储数据的文件,没有大小信息,所以转换时一定要给出它的尺寸
PNG 文件含有尺寸信息,所以转YUV时可以不指定大小,默认原大小
5.借助ffplay显示YUV
可以通过ffplay显示YUV数据。
-
YUV中直接存储的是所有像素的颜色信息(可以理解为是图像的一种原始数据)
-
必须得设置YUV的尺寸(-s)、像素格式(-pix_fmt)才能正常显示
ffplay -s 512x512 -pix_fmt yuv420p in.yuv
# 在ffplay中
# -s已经过期,建议改为:-video_size
# -pix_fmt已经过期,建议改为:-pixel_format
ffplay -video_size 512x512 -pixel_format yuv420p in.yuv
6.GLSL实现YUV转RGBA
6.1 基本计算公式
根据的标准不同,有不同的计算公式。下面是一个可以在shader中使用的计算方法。 只要先得到Y,U,V, 就可以按下面方法转换RGB
" yuv.y = yuv.y - 0.5; \n"
" yuv.z = yuv.z - 0.5; \n"
" \n"
" rgb.r = yuv.x + 1.402 * yuv.z; \n"
" rgb.g = yuv.x - 0.34413 * yuv.y - 0.71414 * yuv.z; \n"
" rgb.b = yuv.x + 1.772 * yuv.y; \n"
YUV到RGBA的转换其实就两个要点
- 构建合适的纹理
- 在shader中提前YUV
6.2 YUV444P->ARGB
6.2.1 构建纹理
static GLuint build_texture_4_yuv444p(int width, int height, void *data){
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
6.2.2 在shader中提取YUV
static const char* yuv444_2_rgba_frag_src =
"uniform sampler2D Sampler; \n"
"varying highp vec2 TexCoord; \n"
"void main (void) \n"
"{ \n"
" highp vec3 yuv; \n"
" highp vec3 rgb; \n"
" yuv.x = texture2D(Sampler, TexCoord).r; \n"
" yuv.y = texture2D(Sampler, vec2(TexCoord.x, TexCoord.y+0.3333333)).r;\n"
" yuv.z = texture2D(Sampler, vec2(TexCoord.x, TexCoord.y+0.6666667)).r;\n"
" \n"
" yuv.y = yuv.y - 0.5; \n"
" yuv.z = yuv.z - 0.5; \n"
// 矩阵计算方法
" rgb = mat3( \n"
"1, 1, 1, \n"
"0, -.34413, 1.772, \n"
"1.402, -.71414, 0 \n"
" ) * yuv; \n"
" \n"
" gl_FragColor = vec4(rgb, 1.0); \n"
"} \n";
6.3 NV24->ARGB
6.3.1 构建纹理
这里需要构建两个纹理,一个是Y的纹理, 一个是UV的纹理
static GLuint build_texture_4_nv24_y(int width, int height, void *y_data){
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, y_data);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
static GLuint build_texture_4_nv24_uv(int width, int height, void *uv_data){
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, width, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, uv_data);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
6.3.2 在shader中提取YUV
static const char* nv24_2_rgba_frag_src =
"uniform sampler2D Sampler_y; \n"
"uniform sampler2D Sampler_uv; \n"
"varying highp vec2 TexCoord; \n"
"void main (void) \n"
"{ \n"
" highp vec3 yuv; \n"
" highp vec3 rgb; \n"
" \n"
" yuv.x = texture2D(Sampler_y, TexCoord).r; \n"
" yuv.y = texture2D(Sampler_uv, TexCoord).r; \n"
" yuv.z = texture2D(Sampler_uv, TexCoord).a; \n"
" \n"
" yuv.y = yuv.y - 0.5; \n"
" yuv.z = yuv.z - 0.5; \n"
" \n"
" rgb.r = yuv.x + 1.402 * yuv.z; \n"
" rgb.g = yuv.x - 0.34413 * yuv.y - 0.71414 * yuv.z; \n"
" rgb.b = yuv.x + 1.772 * yuv.y; \n"
" \n"
" gl_FragColor = vec4(rgb, 1.0); \n"
"} \n";
6.4 NV16->ARGB
6.4.1 构建纹理
这里需要构建两个纹理,一个是Y的纹理, 一个是UV的纹理
static GLuint build_texture_4_nv16_y(int width, int height, void *data){
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
注意glTexImage2D()的第四参数值发生了变化
static GLuint build_texture_4_nv16_uv(int width, int height, void *data){
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, width/2, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
6.4.2 在shader中提取YUV
与NV24方法相同
6.5 NV12->ARGB
6.5.1 构建纹理
这里需要构建两个纹理,一个是Y的纹理, 一个是UV的纹理
static GLuint build_texture_4_nv12_y(int width, int height, void *data){
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
注意glTexImage2D()的第四,五参数值发生了变化
static GLuint build_texture_4_nv12_uv(int width, int height, void *data){
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
CHK_GL_ERR();
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, width/2, height/2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, data);
CHK_GL_ERR();
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
6.5.2 在shader中提取YUV
与NV24方法相同
6.6 直接提取YUV数据
static const char* yuv420_frag_src =
"#version 300 es \n"
"#extension GL_OES_EGL_image_external_essl3 : enable \n"
"#extension GL_EXT_YUV_target : enable \n"
"precision mediump float; \n"
"uniform __samplerExternal2DY2YEXT uTexSampler; \n"
"in vec2 varTexCoord; \n"
"out vec4 rgb; \n"
"void main() \n"
"{ \n"
" vec4 yuv = texture(uTexSampler, varTexCoord); \n"
" yuv.y = yuv.y - 0.5; \n"
" yuv.z = yuv.z - 0.5; \n"
" \n"
" rgb.r = yuv.x + 1.402 * yuv.z; \n"
" rgb.g = yuv.x - 0.34413 * yuv.y - 0.71414 * yuv.z; \n"
" rgb.b = yuv.x + 1.772 * yuv.y; \n"
" rgb.a = 1.0; \n"
"} \n";
7 10bit 存储
10bit YUV就是每个 Y、U、V 分量分别占用10个bit ,由于实际处理中我们是以字节为单位进行存储和处理的,所以最终处理的数据是以2个字节来存储 10bit 的有效数据。这样 在10bit YUV中 ,每个像素(以Y 分量为例)将占用 16bit 两个字节,但是其中 6 个 bit 是 padding ,补 0 。
最近发现越来越多的视频解码出来是 10bit YUV 的图像,毫无疑问 10bit YUV 会有更大的取值范围,能表现出更丰富的颜色信息。
10bit纹理数据的加载
可以利用2个通道 8bit 格式如 GL_LUMINANCE_ALPHA 或者 GL_RG8 完成加载 16bit 图像数据到纹理,然后采样的时候再将 2 个 8bit 数据转换成 16bit.
在 shader 采样之后,每一个通道的值都会被归一化,8bit 归一化之前的范围是 0~255,16bit 归一化之前的范围是 0~65535, 2 个 8bit 值进行移位求和转成 16bit 值,最后再进行归一化。
shader脚本
#version 300 es
precision highp float;
in vec2 v_texCoord;
uniform sampler2D u_texture;
out vec4 outColor;
void main() {
vec4 col = texture(u_texture, v_texCoord);
float val = 255.0 * col.r + col.a * 255.0 * pow(2.0, 8.0);
outColor = vec4(vec3(val / 65535.0), 1.0);
}
纹理加载
glBindTexture(GL_TEXTURE_2D, m_uTextureId);
glTexImage2D ( GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, m_RenderImage.width, m_RenderImage.height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, m_RenderImage.ppPlane[0]);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, GL_NONE);