在上章11.QT-ffmpeg+QAudioOutput实现音频播放器,我们学习了如何播放音频,接下来我们便来学习如何通过opengl来显示YUV画面
1.为什么使用QOpenGLWidget显示YUV
如果软件中通过公式来实现软解码的话,会耗掉很多CPU,所以使用opengl,我们只需要将YUV数据传给opengl,然后opengl通过GPU硬件加速图形绘制来实现硬解码.
需要学习:
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;
}
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);
}
);
以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;
}