Opengl 渲染YUYV(YUV422)图像
//YUYV数据格式: 1个Y代表一个像素,YUYV代表2个像素,4字节,[所以2字节/每像素]
//w=width //图片宽度
//h=high //图片高度
//p=wh //图片像素总数
//c=p2 //数组总长度
//d=(w*h)/2 //一半像素
————————————————
版权声明:本文为CSDN博主「科技ing」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/WMX843230304WMX/article/details/119898913
h文件
#ifndef GLYUVWIDGET_H
#define GLYUVWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QTimer>
QT_FORWARD_DECLARE_CLASS(QOpenGLShaderProgram)
QT_FORWARD_DECLARE_CLASS(QOpenGLTexture)
class GLYuvWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
GLYuvWidget(QWidget *parent =0);
~GLYuvWidget();
public slots:
void slotShowYuv(uchar *ptr,uint width,uint height); //显示一帧Yuv图像
void onShowYUV(uchar *Yptr,uchar *Uptr,uchar *Vptr,uint width,uint height);
protected:
void initializeGL() Q_DECL_OVERRIDE;
void paintGL() Q_DECL_OVERRIDE;
private:
QOpenGLShaderProgram *program;
QOpenGLBuffer vbo;
//opengl中y、u、v分量位置
GLuint posUniformY,posUniformU,posUniformV;
//纹理
QOpenGLTexture *textureY = nullptr,*textureU = nullptr,*textureV = nullptr;
//纹理ID,创建错误返回0
GLuint m_idY,m_idU,m_idV;
//像素分辨率
uint videoW,videoH;
uchar *m_yuvPtr = nullptr;
//Y U V 分量
uchar *m_yPtr = nullptr;
uchar *m_uPtr = nullptr;
uchar *m_vPtr = nullptr;
};
#endif // GLYUVWIDGET_H
cpp
#include "glyuvwidget.h"
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QDebug>
#define VERTEXIN 0
#define TEXTUREIN 1
const char *vshadersrc ="attribute vec4 vertexIn; \
attribute vec2 textureIn; \
varying vec2 textureOut; \
void main(void) \
{ \
gl_Position = vertexIn; \
textureOut = textureIn; \
}";
const char *fshadersrc = "varying vec2 textureOut; \
uniform sampler2D tex_y; \
uniform sampler2D tex_u; \
uniform sampler2D tex_v; \
void main(void) \
{ \
vec3 yuv; \
vec3 rgb; \
yuv.x = texture2D(tex_y, textureOut).r; \
yuv.y = texture2D(tex_u, textureOut).r - 0.5; \
yuv.z = texture2D(tex_v, textureOut).r - 0.5; \
rgb = mat3( 1, 1, 1, \
0, -0.39465, 2.03211, \
1.13983, -0.58060, 0) * yuv; \
gl_FragColor = vec4(rgb, 1); \
}";
GLYuvWidget::GLYuvWidget(QWidget *parent):
QOpenGLWidget(parent)
{
}
GLYuvWidget::~GLYuvWidget()
{
makeCurrent();
vbo.destroy();
textureY->destroy();
textureU->destroy();
textureV->destroy();
doneCurrent();
}
void GLYuvWidget::slotShowYuv(uchar *ptr, uint width, uint height)
{
m_yuvPtr = ptr;
videoW = width;
videoH = height;
update();
}
void GLYuvWidget::onShowYUV(uchar *Yptr, uchar *Uptr, uchar *Vptr, uint width, uint height)
{
m_yPtr = Yptr;
m_uPtr = Uptr;
m_vPtr = Vptr;
videoW = width;
videoH = height;
update();
}
void GLYuvWidget::initializeGL()
{
initializeOpenGLFunctions();
glEnable(GL_DEPTH_TEST);
static const GLfloat vertices[]{
//顶点坐标
-1.0f,-1.0f,
-1.0f,+1.0f,
+1.0f,+1.0f,
+1.0f,-1.0f,
//纹理坐标
0.0f,1.0f,
0.0f,0.0f,
1.0f,0.0f,
1.0f,1.0f,
};
vbo.create();
vbo.bind();
vbo.allocate(vertices,sizeof(vertices));
program = new QOpenGLShaderProgram(this);
//Compile vertex shader
// if (!program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/vs.glsl"))
// close();
// // Compile fragment shader
// if (!program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/fs.glsl"))
// close();
program->addShaderFromSourceCode(QOpenGLShader::Vertex,vshadersrc);
program->addShaderFromSourceCode(QOpenGLShader::Fragment,fshadersrc);
//绑定输入的定点坐标和纹理坐标属性
program->bindAttributeLocation("vertexIn",VERTEXIN);
program->bindAttributeLocation("textureIn",TEXTUREIN);
// Link shader pipeline
if (!program->link())
{
qDebug()<<"program->link error";
close();
}
// Bind shader pipeline for use
if (!program->bind())
{
qDebug()<<"program->bind error";
close();
}
//启用并且设置定点位置和纹理坐标
program->enableAttributeArray(VERTEXIN);
program->enableAttributeArray(TEXTUREIN);
program->setAttributeBuffer(VERTEXIN,GL_FLOAT, 0, 2, 2*sizeof(GLfloat));
program->setAttributeBuffer(TEXTUREIN,GL_FLOAT,8*sizeof(GLfloat), 2 , 2*sizeof(GLfloat));
//定位shader中 uniform变量
posUniformY = program->uniformLocation("tex_y");
posUniformU = program->uniformLocation("tex_u");
posUniformV = program->uniformLocation("tex_v");
//创建纹理并且获取id
textureY = new QOpenGLTexture(QOpenGLTexture::Target2D);
textureU = new QOpenGLTexture(QOpenGLTexture::Target2D);
textureV = new QOpenGLTexture(QOpenGLTexture::Target2D);
textureY->create();
textureU->create();
textureV->create();
m_idY = textureY->textureId();
m_idU = textureU->textureId();
m_idV = textureV->textureId();
glClearColor(0.0,0.0,0.0,0.0);
}
void GLYuvWidget::paintGL()
{
//激活纹理单元GL_TEXTURE0,系统里面的
glActiveTexture(GL_TEXTURE0);
//绑定y分量纹理对象id到激活的纹理单元
glBindTexture(GL_TEXTURE_2D,m_idY);
///------------------------
/// glTexImage2D(
/// GLenum target,
/// GLint level,
/// GLint internalformat,
/// GLsizei width,
/// GLsizei height,
/// GLint border,
/// GLenum format,
/// GLenum type,
/// const GLvoid* pixels)
/* 函数很长,参数也不少,所以我们一个一个地讲解:
第一个参数指定了纹理目标(Target)。设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理
(任何绑定到GL_TEXTURE_1D和GL_TEXTURE_3D的纹理不会受到影响)。
第二个参数为纹理指定多级渐远纹理的级别,如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0,也就是基本级别。
第三个参数告诉OpenGL我们希望把纹理储存为何种格式。
第四个和第五个参数设置最终的纹理的宽度和高度。我们之前加载图像的时候储存了它们,所以我们使用对应的变量。
第六个参数应该总是被设为0(历史遗留的问题)。
第七个参数定义了源图的格式。
第八个参数定义了源图的数据类型。
最后一个参数是真正的图像数据。
当调用glTexImage2D时,当前绑定的纹理对象就会被附加上纹理图像。然而,目前只有基本级别(Base-level)的纹理图像被加载了,
如果要使用多级渐远纹理,我们必须手动设置所有不同的图像(不断递增第二个参数)。
或者,直接在生成纹理之后调用glGenerateMipmap。这会为当前绑定的纹理自动生成所有需要的多级渐远纹理。
*/
//使用内存中的数据创建真正的y分量纹理数据
glTexImage2D(GL_TEXTURE_2D,0,GL_RED,videoW,videoH,0,GL_RED,GL_UNSIGNED_BYTE,m_yPtr);//m_yPtr 大小是(videoW*videoH)
//https://blog.csdn.net/xipiaoyouzi/article/details/53584798 纹理参数解析
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
//激活纹理单元GL_TEXTURE1
glActiveTexture(GL_TEXTURE1);
//绑定u分量纹理对象id到激活的纹理单元
glBindTexture(GL_TEXTURE_2D,m_idU);
//使用内存中的数据创建真正的u分量纹理数据
glTexImage2D(GL_TEXTURE_2D,0,GL_RED,videoW>>1 , videoH,0,GL_RED,GL_UNSIGNED_BYTE,m_uPtr);//m_uPtr 大小是(videoW*videoH / 2)
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
//激活纹理单元GL_TEXTURE2
glActiveTexture(GL_TEXTURE2);
//绑定v分量纹理对象id到激活的纹理单元
glBindTexture(GL_TEXTURE_2D,m_idV);
//使用内存中的数据创建真正的v分量纹理数据
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, videoW>>1 , videoH, 0, GL_RED, GL_UNSIGNED_BYTE, m_vPtr);//m_vPtr 大小是(videoW*videoH / 2)
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
//指定y纹理要使用新值
glUniform1i(posUniformY, 0);
//指定u纹理要使用新值
glUniform1i(posUniformU, 1);
//指定v纹理要使用新值
glUniform1i(posUniformV, 2);
//使用顶点数组方式绘制图形
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
main.cpp
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
//相机
QSharedPointer<QCamera> cameraPtr(new QCamera(cameras.at(0)));
//取景器
QSharedPointer<MyVideoSurface> surface(new MyVideoSurface);
cameraPtr.get()->setViewfinder(qobject_cast<MyVideoSurface*>(surface.get()));
//opengl widget
GLYuvWidget openGLRenderWidget;
//QObject::connect(surface.get(),&MyVideoSurface::sendOpenGLYuv,&openGLRenderWidget,&GLYuvWidget::slotShowYuv);
QObject::connect(surface.get(),&MyVideoSurface::sendYUVChanged,&openGLRenderWidget,&GLYuvWidget::onShowYUV);
openGLRenderWidget.show();
cameraPtr->start();
return app.exec();
}
其中 MyVideoSurface 参考 《自定义 QAbstractVideoSurface 添加视频水印》
发出YUYV信号在
bool MyVideoSurface::present(const QVideoFrame &frame)
{
QVideoFrame frametodraw(frame);
if(!frametodraw.map(QAbstractVideoBuffer::ReadOnly))
{
setError(ResourceError);
return false;
}
unsigned char*bits = (unsigned char *)frametodraw.bits();
SpliterYUYV(frametodraw.width(),frametodraw.height(),
(unsigned char *)bits,
(unsigned char *)YBuf,
(unsigned char *)UBuf,
(unsigned char *)VBuf);
sendYUVChanged((uchar*)YBuf,(uchar*)UBuf,(uchar*)VBuf,frametodraw.width(),frametodraw.height());
}