前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >12.QT-通过QOpenGLWidget显示YUV画面,通过QOpenGLTexture纹理渲染YUV

12.QT-通过QOpenGLWidget显示YUV画面,通过QOpenGLTexture纹理渲染YUV

作者头像
诺谦
发布2020-11-04 14:34:38
3.2K0
发布2020-11-04 14:34:38
举报
文章被收录于专栏:Linux驱动Linux驱动

在上章11.QT-ffmpeg+QAudioOutput实现音频播放器,我们学习了如何播放音频,接下来我们便来学习如何通过opengl来显示YUV画面

1.为什么使用QOpenGLWidget显示YUV

如果软件中通过公式来实现软解码的话,会耗掉很多CPU,所以使用opengl,我们只需要将YUV数据传给opengl,然后opengl通过GPU硬件加速图形绘制来实现硬解码.

需要学习:

2.通过QOpenGLWidget绘制三角形

3.QOpenGLWidget-通过着色器来渲染渐变三角形

4.QOpenGLWidget-对三角形进行纹理贴图、纹理叠加

项目流程如下所示:

项目界面最终如下所示:

2.shader源码分析

首先通过ffmpeg命令提取出yuv数据:

代码语言:javascript
复制
ffmpeg -i v1080.mp4 -t 10 -s 640x340 -pix_fmt yuv420p out640x340.yuv

然后将文件放置到G盘目录下

2.1顶点shader源码如下所示:

代码语言:javascript
复制
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
 
out vec2 TexCoord;
 
void main()
{
gl_Position = vec4(aPos, 1.0);
TexCoord = aTexCoord;
}
  • #version 330 core : 定义版本号,需要注意的是,使用版本3.0以上后、则不能用attribute、varying变量修饰变量了,只能用in和out来代替
  • layout (location = 0) in vec3 aPos : 使用in关键字来声明顶点属性输入,这里创建一个输入变量aPos(3分量),通过layout (location = 0)设定了输入变量的顶点属性的位置值(Location)为0,后面将会通过 setAttributeBuffer()函数来设置它.
  • gl_Position : 设置顶点着色器的输出,这里gl_Position之所以为vec4类型,是因为3d图形演算要用到 4x4的矩阵(4行4列),而矩阵乘法要求n行m列 和 m行p列才能相乘,所以是vec4而不是vec3,由于position 是位置所以应该是 (x,y,z,1.0f),如果是方向向量,则就是 (x,y,z,0.0f).

2.2片元shader源码如下所示:

代码语言:javascript
复制
#version 330 core
const char *fsrc =GET_GLSTR(
 
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D texY;
uniform sampler2D texU;
uniform sampler2D texV;
 
void main()
{
vec3 yuv;
vec3 rgb;
 
yuv.x = texture2D(texY, TexCoord).r;
yuv.y = texture2D(texU, TexCoord).r-0.5;
yuv.z = texture2D(texV, TexCoord).r-0.5;
 
rgb = mat3(1.0, 1.0, 1.0,
0.0, -0.3455, 1.779,
1.4075, -0.7169, 0.0) * yuv;
 
FragColor = vec4(rgb, 1.0);
}
);
  • sampler2D: 纹理采样器,存的是一个画面的颜色值,对应的还有sampler3D等
  • texture2D(texY, TexCoord): 其实等价于texture()函数,第一个参数为纹理采样器,第二个参数是对应的纹理坐标,该函数就会根据当前所在纹理坐标去获取对应的颜色,然后输出到FragColor来显示颜色.
  • FragColor : 控制输出的颜色(rgba),(在3.3版本后需要通过out的方式来声明)
  • texture2D(texU, TexCoord).r-0.5: 由于opengl接受的颜色值为(0.0~1.0)浮点数,而不是0~255方式,所以这里减去0.5其实是减去128
  • mat3()函数 : mat3表示的是3x3全矩阵,由于yuv是个1x3矩阵,所以计算出来的rgb也是1x3矩阵.

以R为例:

由于R=yuv的第1行(y,u,v)和mat3()内的第1列(1.0,0.0,1.4075)的相乘和、

所以R=1.0Y+0*(U-128)+1.4075(V-128)

3.myglwidget源文件

代码语言:javascript
复制
#include "myglwidget.h"
#include <QtDebug>
#include <QTimer>

////GLSL3.0版本后,废弃了attribute关键字(以及varying关键字),属性变量统一用in/out作为前置关键字
#define GL_VERSION  "#version 330 core\n"
#define GET_GLSTR(x) GL_VERSION#x

static int VideoWidth=640;
static int VideoHeight=340;


const char *vsrc = GET_GLSTR(

    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec2 aTexCoord;

    out vec2 TexCoord;

    void main()
    {
        gl_Position = vec4(aPos, 1.0);
        TexCoord = aTexCoord;
    }

);

const char *fsrc =GET_GLSTR(

    out vec4 FragColor;
    in vec2 TexCoord;
    uniform sampler2D texY;
    uniform sampler2D texU;
    uniform sampler2D texV;

    void main()
    {
        vec3 yuv;
        vec3 rgb;

        yuv.x = texture(texY, TexCoord).r;
        yuv.y = texture(texU, TexCoord).r-0.5;
        yuv.z = texture(texV, TexCoord).r-0.5;

        rgb = mat3(1.0, 1.0, 1.0,
            0.0, -0.3455, 1.779,
            1.4075, -0.7169, 0.0) * yuv;

        FragColor = vec4(rgb, 1.0);
    }
);

myGlWidget::myGlWidget(QWidget *parent):QOpenGLWidget(parent)
{

}


void myGlWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT);


    // 渲染Shader
    vao.bind();
     if (file->atEnd())
     {
        qDebug()<<"repaly!!!!!!!!";
        file->seek(0);
     }
      program->setUniformValue("texY", 0);
      program->setUniformValue("texU", 1);
      program->setUniformValue("texV", 2);

      for(int i=0;i<3;i++)
      {
          if(i==0)
          {

                  yuvArr[i] = file->read(VideoWidth*VideoHeight);
          }
          else
          {
                  yuvArr[i] = file->read(VideoWidth*VideoHeight/4);
          }



          m_textureYUV[i]->setData(QOpenGLTexture::Red,QOpenGLTexture::UInt8,yuvArr[i]);

          m_textureYUV[i]->bind(i);
      }

     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

     m_textureYUV[0]->release();
     m_textureYUV[1]->release();
     m_textureYUV[2]->release();

    vao.release();       //解绑
}

void myGlWidget::initializeGL()
{
    //为当前环境初始化OpenGL函数
    initializeOpenGLFunctions();

    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);    //设置背景色为白色


    file =new QFile("G:\\out640x340.yuv");
    if (!file->open(QIODevice::ReadOnly ))
    {
        qDebug()<<"out640x340.yuv file open failed!";
    }


     //初始化纹理对象
     for(int i=0;i<3;i++)
     {

         m_textureYUV[i]  = new QOpenGLTexture(QOpenGLTexture::Target2D);

         if(i==0)
         {
                 m_textureYUV[i]->setSize(VideoWidth,VideoHeight);
         }
         else
         {
                 m_textureYUV[i]->setSize(VideoWidth/2,VideoHeight/2);
         }

         m_textureYUV[i]->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear,QOpenGLTexture::Linear);
         m_textureYUV[i]->create();
         m_textureYUV[i]->setFormat(QOpenGLTexture::R8_UNorm);
         m_textureYUV[i]->allocateStorage();        //存储配置(放大缩小过滤、格式、size)
         m_textureYUV[i]->setData(QOpenGLTexture::Red,QOpenGLTexture::UInt8,yuvArr[i]);

     }


    program = new  QOpenGLShaderProgram(this);

    program->addShaderFromSourceCode(QOpenGLShader::Fragment,fsrc);

    program->addShaderFromSourceCode(QOpenGLShader::Vertex,vsrc);

    program->link();
    program->bind();


    //初始化VBO,将顶点数据存储到buffer中,等待VAO激活后才能释放

    float vertices[] = {
         //顶点坐标               //纹理坐标的Y方向需要是反的,因为opengl中的坐标系是Y原点位于下方
        -1.0f, -1.0f, 0.0f,  0.0f, 1.0f,        //左下
        1.0f , -1.0f, 0.0f,  1.0f, 1.0f,        //右下
        -1.0f, 1.0f,  0.0f,  0.0f, 0.0f,        //左上
        1.0f,  1.0f,  0.0f,  1.0f, 0.0f         //右上

    };


    vbo.create();
    vbo.bind();
    vbo.bind();              //绑定到当前的OpenGL上下文,
    vbo.allocate(vertices, sizeof(vertices));
    vbo.setUsagePattern(QOpenGLBuffer::StaticDraw);  //设置为一次修改,多次使用(坐标不变,变得只是像素点)

    //初始化VAO,设置顶点数据状态(顶点,法线,纹理坐标等)
    vao.create();
    vao.bind();

    // void setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0);
    program->setAttributeBuffer(0, GL_FLOAT, 0,                  3, 5 * sizeof(float));   //设置aPos顶点属性
    program->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(float),  2, 5 * sizeof(float));   //设置aColor顶点颜色

    program->enableAttributeArray(0); //使能
    program->enableAttributeArray(1);



    //解绑所有对象
    vao.release();
    vbo.release();

    //启动定时器
    QTimer *ti = new QTimer(this);
    connect(ti, SIGNAL(timeout()), this, SLOT(update()));
    ti->start(40);


}



// 窗口尺寸变化
void myGlWidget::resizeGL(int width, int height)
{
    qDebug() << "resizeGL "<<width<<":"<<height;
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-11-02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据保险箱
数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档