12.QT-通過QOpenGLWidget顯示YUV畫面,通過QOpenGLTexture紋理渲染YUV


在上章11.QT-ffmpeg+QAudioOutput實現音頻播放器,我們學習了如何播放音頻,接下來我們便來學習如何通過opengl來顯示YUV畫面
 
1.為什么使用QOpenGLWidget顯示YUV
如果軟件中通過公式來實現軟解碼的話,會耗掉很多CPU,所以使用opengl,我們只需要將YUV數據傳給opengl,然后opengl通過GPU硬件加速圖形繪制來實現硬解碼.
需要學習:
 
項目流程如下所示:
 
項目界面最終如下所示:

 

 

 
2.shader源碼分析
首先通過ffmpeg命令提取出yuv數據:
ffmpeg -i v1080.mp4 -t 10 -s 640x340 -pix_fmt yuv420p out640x340.yuv
然后將文件放置到G盤目錄下
2.1頂點shader源碼如下所示:
#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源碼如下所示:
#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源文件

#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; }

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM