yuv格式介绍与opengl 显示 yuv数据

时间:2024-05-21 17:52:12

目录

 

yuv数据介绍:

yuv444

yuv420

yuv422

Opengl显示yuv数据

提取数据

创建纹理

shader显示


yuv数据介绍:

yuv数据存储主要分3大种:yuv420、yuv444、yuv422

 

yuv格式介绍与opengl 显示 yuv数据

图中:空心圆表示 一个uv, 实心圆表示一个y

下面对三种格式进行一个解释:cb代表u,cr代表v。大部分数据格式再 ffmpeg的枚举类型 AVPixelFormat中都有介绍,可以拿来参考。

yuv444

yuv就是一个y对应一个uv,大多是yuv444p

  • yuv444p p表示plane模式,先存储y,然后存储u、再存储v 。

yuv420

yuv420 只是表示 4个y 共一个uv,存储方式多种(下图中:16个y 和 4个 u  4个 v,图像是 y :4x4 , uv : 2x2, 每一行可能有像素补充):

  • yuv420p: p表示plane模式,先存储y,然后存储u、再存储v  如:yyyyyyyyyyyyyyyy uuuu vvvv
  • yu12: 也是一种plane模式,如:yyyyyyyyyyyyyyyy uuuu vvvv
  • yv12:也是一种plane模式,如:yyyyyyyyyyyyyyyy vvvv uuuuuu
  • nv12:y是单独存储,uv混合存储,但第一个为u:如:yyyyyyyyyyyyyyyy uvuvuvuv
  • nv12:y是单独存储,uv混合存储,但第一个为v:如:yyyyyyyyyyyyyyyy uvuvuvuv

yuv422

yuv422 一行中连续2个y共用一个uv,存储方式多种:

  • YUV422P:p表示plane模式,存储如:yyyyyyyyyyyyyyyy uuuuuuuu vvvvvvvv(图像是 y :4x4 , uv : 2x4, 每一行可能有像素补充)
  • YUYV422 :名称代表了存储的顺序,2个y 之间夹杂一个u后面添加一个v。如:Y0 Cb Y1 Cr
  • UYVY422:名称代表了存储的顺序,如: Cb Y0 Cr Y1

Opengl显示yuv数据

提取数据

上一篇FFMpeg opengl显示解码avframe以 yuv420p 为例,讲述了从avframe中提取像素数据,其实这些数据都是根据上面格式的存储方式来提取的。这里给出nv12的提取方式(nv12和nv21的提取方式一样,只是uv的顺序不同)

void getNV12Data(AVFrame *avFrame)
{
    int w = avFrame->width;
    int h = avFrame->height;

    if(y == nullptr){
        y = new unsigned char[w*h];
    }
    if(u == nullptr){
        u = new unsigned char[w*h/2];
    }
    LOGE("avFrame->linesize[0] = %d" ,avFrame->linesize[0]);
    LOGE("avFrame->linesize[1] = %d" ,avFrame->linesize[1]);

    int l1 = avFrame->linesize[0];
    int l2 = avFrame->linesize[1];
    for(int i= 0 ; i < h ; i++)
    {
        memcpy(y + w*i,avFrame->data[0] + l1* i, sizeof( unsigned char)*w);
    }
    for(int i= 0 ; i < h/2 ; i++)
    {
        memcpy(u + w*i,avFrame->data[1] +  l2 * i, sizeof( unsigned char)*w);
    }
}

创建纹理

熟悉opengl都知道,通过上面提取的yuv数据 创建纹理,然后在shader中对yuv的数据转化为rgb 然后渲染显示。问题就在于纹理如何创建,下面给出创建函数和绘制函数:


void GLProgram::GetTexture(unsigned int index,int width,int height, unsigned char *buf, bool isa)
{

    unsigned int format =GL_LUMINANCE; // 这里是 灰度图,单通道
    if(isa)
        format = GL_LUMINANCE_ALPHA; // 这表示的是 带 alpha通道的单通道图,即 2通道,r 和 a

    if(yuvTexture[index] == 0)
    {
        //材质初始化
        glGenTextures(1,&yuvTexture[index]);
         LOGE("创建纹理yuvTexture[index] = %d ", yuvTexture[index]);

        //设置纹理属性
        glBindTexture(GL_TEXTURE_2D,yuvTexture[index]);
        //缩小的过滤器
        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,           //细节基本 0默认
                     format,//gpu内部格式 亮度,灰度图
                     width,height, //拉升到全屏
                     0,             //边框
                     format,//数据的像素格式 亮度,灰度图 要与上面一致
                     GL_UNSIGNED_BYTE, //像素的数据类型
                     NULL                    //纹理的数据
        );
    }


    //**第1层纹理,绑定到创建的opengl纹理
    glActiveTexture(GL_TEXTURE0+index);
    glBindTexture(GL_TEXTURE_2D,yuvTexture[index]);
    //替换纹理内容
    glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width,height,format,GL_UNSIGNED_BYTE,buf);

}

void GLProgram::Draw(int width , int height,unsigned char * y, unsigned char *u, unsigned char *v,AVPixelFormat avPixelFormat, int* showSize)
{

    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |GL_STENCIL_BUFFER_BIT);
    glViewport(showSize[0],showSize[1], showSize[2], showSize[3]);
    glUseProgram(program);
    setUniform();

    glUniform1i(formatTypeLocation, avPixelFormat);

    if(avPixelFormat == AV_PIX_FMT_YUV420P)
    {
        GetTexture(0,width,height,y, false);  // Y
        glUniform1i(yTextureLocation,0);
        GetTexture(1,width/2,height/2,u,false);  // U
        glUniform1i(uTextureLocation,1);
        GetTexture(2,width/2,height/2,v,false);  // V
        glUniform1i(vTextureLocation,2);
    }
    else if(avPixelFormat == AV_PIX_FMT_NV12 || avPixelFormat == AV_PIX_FMT_NV21)
    {
        GetTexture(0,width,height,y,false);  // Y
        glUniform1i(yTextureLocation,0);
        GetTexture(1,width/2,height/2,u,true);  // Uv
        glUniform1i(uTextureLocation,1);
    }


    //三维绘制
    glDrawArrays(GL_TRIANGLE_STRIP,0,4);

    glDisableVertexAttribArray(aPositionLocation);
    glDisableVertexAttribArray(aTexCoordLocation);

}

1、fomat代表创建纹理的类型,通常有RGB、RGBA,当然我们传入的是yuv数据,uv的数据与y不同,且数据得一行一行取出来,因为可能有补充像素。因此,这里format的选择就很重要。

  • yuv420p的时候,yuv的数据分开的,uv的纹理大小与y不同,因此 创建三个纹理: y、u、v,并且都是单通道GL_LUMINANCE表示  灰度图,单通道
  • nv12 或者 nv21 的时候,因为uv的数据是柔和在一起的,因此创建2个纹理:y, u,创建y的时候format选择GL_LUMINANCE,而 创建uv的format为GL_LUMINANCE_ALPHA :表示的是带 alpha通道的灰度图,即有2个通道,包含r和 a

shader显示

yuv转rgb的方法

yuv格式介绍与opengl 显示 yuv数据

shader

关于顶点shader 和 纹理坐标的对应关系,之前已经讲过,这里只说下yuv数据转化为rgb数据的方法,这里先给出片元着色器shader:

#define GET_STR(x) #x
//片元着色器,软解码和部分x86硬解码
static const char *fragYUV420P = GET_STR(
        precision mediump float;    //精度
        varying vec2 vTexCoord;     //顶点着色器传递的坐标
        uniform sampler2D yTexture; //输入的材质(不透明灰度,单像素)
        uniform sampler2D uTexture;
        uniform sampler2D vTexture;
        uniform int formatType;
        void main(){
            vec3 yuv;
            vec3 rgb;
            if(formatType==1)//yuv420p
            {
                yuv.r = texture2D(yTexture,vTexCoord).r;
                yuv.g = texture2D(uTexture,vTexCoord).r - 0.5;//
                yuv.b = texture2D(vTexture,vTexCoord).r - 0.5;
            }else if(formatType==25)//nv12
            {
                yuv.r = texture2D(yTexture,vTexCoord).r;
                yuv.g = texture2D(uTexture,vTexCoord).r - 0.5 ;
                // shader 会将数据归一化,而 uv 的取值区间本身存在-128到正128 然后归一化到0-1 为了正确计算成rgb,
                // 则需要归一化到 -0.5 - 0.5的区间
                yuv.b = texture2D(uTexture,vTexCoord).a - 0.5;
            }else if(formatType==26)//nv21
            {
                yuv.r = texture2D(yTexture,vTexCoord).r;
                yuv.g = texture2D(uTexture,vTexCoord).a - 0.5 ;
                // shader 会将数据归一化,而 uv 的取值区间本身存在-128到正128 然后归一化到0-1 为了正确计算成rgb,
                // 则需要归一化到 -0.5 - 0.5的区间
                yuv.b = texture2D(uTexture,vTexCoord).r - 0.5;
            }
            rgb = mat3(1.0,     1.0,    1.0,
                       0.0,-0.39465,2.03211,
                       1.13983,-0.58060,0.0)*yuv;
            //输出像素颜色
            gl_FragColor = vec4(rgb,1.0);
        }
);

shader是根据传入format的不同,转化方式不同。注意下面几点:

  • y的纹理大小和视频帧的宽高一致,但uv的大小是y的1/2,但纹理会插值缩放到与y一样的大小
  • 在获取u、v的数据时候,需 减去0.5 , 因为 uv的数据取值返回是 从 -100多到正100多,最后传入shader中会 归一化到0-1,因此,为了正确转化为rgb,需要将uv归一化到 -0.5 到 0.5 与原来的 rgb对应一样。

到这里就可以显示 yuv的数据了,当然大部分的相机的数据是nv21 或者nv12 ,相机的预览或者滤镜最后的显示都可以用这个方式。