12.QT-通過QOpenGLWidget顯示YUV畫面,通過QOpenGLTexture紋理渲染YUV
阿新 • • 發佈:2020-11-02
在上章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資料:
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).
#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矩陣.
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;
}