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: sampling

示意图2

sampling_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格式转换

  1. PNG -> YUV
     ffmpeg -i in.png -s 512x512 -pix_fmt yuv420p out.yuv
    
  2. 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);

results matching ""

    No results matching ""