#include #include #include #include "CPlayWidget.h" // 顶点着色器源码 const char *vsrcyuv = "attribute vec4 vertexIn; \ attribute vec2 textureIn; \ varying vec2 textureOut; \ void main(void) \ { \ gl_Position = vertexIn; \ textureOut = textureIn; \ }"; // 片段着色器源码 const char *fsrcyuv = "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); \ }"; // rgb片段着色器源码 // 注意MEDIASUBTYPE_RGB32 是bgr的,所以需要再进行一次转换 const char *fsrcrgb = "varying vec2 textureOut; \ uniform sampler2D rgbdata; \ void main() \ { \ gl_FragColor = texture(rgbdata, textureOut); \ }"; void CPlayWidget::OnUpdateFrame() { this->PlayOneFrame(); } void CPlayWidget::OnPaintData(const uint8_t *data, uint32_t len) { if(nullptr == m_pBufYuv420p) { m_pBufYuv420p = new unsigned char[len]; qDebug("CPlayWidget::PlayOneFrame new data memory. Len=%d width=%d height=%d\n", len, m_nVideoW, m_nVideoW); memcpy(m_pBufYuv420p, data,len); //刷新界面,触发paintGL接口 update(); } } CPlayWidget::CPlayWidget(QWidget *parent):QOpenGLWidget(parent) { textureUniformY = 0; textureUniformU = 0; textureUniformV = 0; id_y = 0; id_u = 0; id_v = 0; m_pTextureRGB = nullptr; m_pBufYuv420p = NULL; m_pVSHader = NULL; m_pFSHader = NULL; m_pShaderProgram = NULL; m_pTextureY = NULL; m_pTextureU = NULL; m_pTextureV = NULL; m_pYuvFile = NULL; m_nVideoH = 0; m_nVideoW = 0; mType = TYPE_YUV420P; connect(&this->tm,SIGNAL(timeout()),this,SLOT(OnUpdateFrame())); //tm.start(1000); } CPlayWidget::~CPlayWidget() { } void CPlayWidget::PlayOneFrame() {//函数功能读取一张yuv图像数据进行显示,每单击一次,就显示一张图片 if(NULL == m_pYuvFile) { //打开yuv视频文件 注意修改文件路径 // m_pYuvFile = fopen("F://OpenglYuvDemo//1920_1080.yuv", "rb"); m_pYuvFile = fopen("F://md_sample_sp420_1080p.yuv", "rb"); //根据yuv视频数据的分辨率设置宽高,demo当中是1080p,这个地方要注意跟实际数据分辨率对应上 // m_nVideoW = 1920; // m_nVideoH = 1080; } //申请内存存一帧yuv图像数据,其大小为分辨率的1.5倍 int nLen = m_nVideoW*m_nVideoH*3/2; if(nullptr == m_pBufYuv420p) { m_pBufYuv420p = new unsigned char[nLen]; qDebug("CPlayWidget::PlayOneFrame new data memory. Len=%d width=%d height=%d\n", nLen, m_nVideoW, m_nVideoW); } //将一帧yuv图像读到内存中 if(NULL == m_pYuvFile) { qFatal("read yuv file err.may be path is wrong!\n"); return; } fread(m_pBufYuv420p, 1, nLen, m_pYuvFile); //刷新界面,触发paintGL接口 update(); return; } int CPlayWidget::SetDataType(CPlayWidget::IMG_TYPE type){ this->mType = type; } int CPlayWidget::OnCameraData(uint8_t *dat, uint32_t size) { memcpy(this->m_pBufRgb32,dat,size); update(); } int CPlayWidget::SetImgSize(uint32_t width, uint32_t height) { m_nVideoH = height; m_nVideoW = width; if(mType == TYPE_RGB32){ m_pBufRgb32 = new uint8_t[width * height *4]; } if(mType == TYPE_YUV420P){ m_pBufYuv420p = new uint8_t[width * height *3/2]; } } /* * Y = 0.299 R + 0.587 G + 0.114 B U = - 0.1687 R - 0.3313 G + 0.5 B + 128 V = 0.5 R - 0.4187 G - 0.0813 B + 128 反过来,RGB 也可以直接从YUV (256级别) 计算: R = Y + 1.402 (Cr-128) G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128) B = Y + 1.772 (Cb-128) */ void CPlayWidget::initializeGL() { initializeOpenGLFunctions(); glEnable(GL_DEPTH_TEST); //现代opengl渲染管线依赖着色器来处理传入的数据 //着色器:就是使用openGL着色语言(OpenGL Shading Language, GLSL)编写的一个小函数, // GLSL是构成所有OpenGL着色器的语言,具体的GLSL语言的语法需要读者查找相关资料 //初始化顶点着色器 对象 m_pVSHader = new QOpenGLShader(QOpenGLShader::Vertex, this); //编译顶点着色器程序 bool bCompile = m_pVSHader->compileSourceCode(vsrcyuv); if(!bCompile) { // todo 设置错误状态 } //初始化片段着色器 功能gpu中yuv转换成rgb m_pFSHader = new QOpenGLShader(QOpenGLShader::Fragment, this); if(mType == TYPE_RGB32){ bCompile = m_pFSHader->compileSourceCode(fsrcrgb); } if(mType == TYPE_YUV420P){ bCompile = m_pFSHader->compileSourceCode(fsrcyuv); } if(!bCompile) { // todo 设置错误状态 } #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_TEXCOORD_ATTRIBUTE 1 //创建着色器程序容器 m_pShaderProgram = new QOpenGLShaderProgram; //将片段着色器添加到程序容器 m_pShaderProgram->addShader(m_pFSHader); //将顶点着色器添加到程序容器 m_pShaderProgram->addShader(m_pVSHader); //绑定属性vertexIn到指定位置ATTRIB_VERTEX,该属性在顶点着色源码其中有声明 m_pShaderProgram->bindAttributeLocation("vertexIn", ATTRIB_VERTEX); //绑定属性textureIn到指定位置ATTRIB_TEXTURE,该属性在顶点着色源码其中有声明 m_pShaderProgram->bindAttributeLocation("textureIn", ATTRIB_TEXTURE); //链接所有所有添入到的着色器程序 m_pShaderProgram->link(); //激活所有链接 m_pShaderProgram->bind(); if(this->mType == TYPE_YUV420P){ initShaderYuv(); } if(this->mType == TYPE_RGB32){ initShaderRgb(); } glClearColor(0.0,0.0,0.0,0.0);//设置背景色 } void CPlayWidget::resizeGL(int w, int h) { if(h == 0)// 防止被零除 { h = 1;// 将高设为1 } //设置视口 glViewport(0,0, w,h); } void CPlayWidget::paintGL() { if(mType == TYPE_YUV420P) loadYuvTexture(); if(mType == TYPE_RGB32){ loadRgbTexture(); } //使用顶点数组方式绘制图形 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); return; } void CPlayWidget::initShaderYuv() { //读取着色器中的数据变量tex_y, tex_u, tex_v的位置,这些变量的声明可以在 //片段着色器源码中可以看到 textureUniformY = m_pShaderProgram->uniformLocation("tex_y"); textureUniformU = m_pShaderProgram->uniformLocation("tex_u"); textureUniformV = m_pShaderProgram->uniformLocation("tex_v"); // 顶点矩阵 static const GLfloat vertexVertices[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, }; //纹理矩阵 static const GLfloat textureVertices[] = { 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, }; //设置属性ATTRIB_VERTEX的顶点矩阵值以及格式 glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices); //设置属性ATTRIB_TEXTURE的纹理矩阵值以及格式 glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices); //启用ATTRIB_VERTEX属性的数据,默认是关闭的 glEnableVertexAttribArray(ATTRIB_VERTEX); //启用ATTRIB_TEXTURE属性的数据,默认是关闭的 glEnableVertexAttribArray(ATTRIB_TEXTURE); //分别创建y,u,v纹理对象 m_pTextureY = new QOpenGLTexture(QOpenGLTexture::Target2D); m_pTextureU = new QOpenGLTexture(QOpenGLTexture::Target2D); m_pTextureV = new QOpenGLTexture(QOpenGLTexture::Target2D); m_pTextureY->create(); m_pTextureU->create(); m_pTextureV->create(); //获取返回y分量的纹理索引值 id_y = m_pTextureY->textureId(); //获取返回u分量的纹理索引值 id_u = m_pTextureU->textureId(); //获取返回v分量的纹理索引值 id_v = m_pTextureV->textureId(); } void CPlayWidget::initShaderRgb() { //读取着色器中的数据变量tex_y, tex_u, tex_v的位置,这些变量的声明可以在 //片段着色器源码中可以看到 textureUniformRGB = m_pShaderProgram->uniformLocation("rgbdata"); // 顶点矩阵 static const GLfloat vertexVertices[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, }; //纹理矩阵 static const GLfloat textureVertices[] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, }; //设置属性ATTRIB_VERTEX的顶点矩阵值以及格式 glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices); //设置属性ATTRIB_TEXTURE的纹理矩阵值以及格式 glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices); //启用ATTRIB_VERTEX属性的数据,默认是关闭的 glEnableVertexAttribArray(ATTRIB_VERTEX); //启用ATTRIB_TEXTURE属性的数据,默认是关闭的 glEnableVertexAttribArray(ATTRIB_TEXTURE); //分别创建y,u,v纹理对象 m_pTextureRGB = new QOpenGLTexture(QOpenGLTexture::Target2D); m_pTextureRGB->create(); //获取返回y分量的纹理索引值 id_rgb = m_pTextureRGB->textureId(); } int CPlayWidget::loadYuvTexture() { //加载y数据纹理 //激活纹理单元GL_TEXTURE0 glActiveTexture(GL_TEXTURE0); //使用来自y数据生成纹理 glBindTexture(GL_TEXTURE_2D, id_y); //使用内存中m_pBufYuv420p数据创建真正的y数据纹理 glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_nVideoW, m_nVideoH, 0, GL_RED, GL_UNSIGNED_BYTE, m_pBufYuv420p); 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); //加载u数据纹理 glActiveTexture(GL_TEXTURE1);//激活纹理单元GL_TEXTURE1 glBindTexture(GL_TEXTURE_2D, id_u); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_nVideoW/2, m_nVideoH/2, 0, GL_RED, GL_UNSIGNED_BYTE, (char*)m_pBufYuv420p+m_nVideoW*m_nVideoH); 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); //加载v数据纹理 glActiveTexture(GL_TEXTURE2);//激活纹理单元GL_TEXTURE2 glBindTexture(GL_TEXTURE_2D, id_v); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_nVideoW/2, m_nVideoH/2, 0, GL_RED, GL_UNSIGNED_BYTE, (char*)m_pBufYuv420p+m_nVideoW*m_nVideoH*5/4); 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纹理要使用新值 只能用0,1,2等表示纹理单元的索引,这是opengl不人性化的地方 //0对应纹理单元GL_TEXTURE0 1对应纹理单元GL_TEXTURE1 2对应纹理的单元 glUniform1i(textureUniformY, 0); //指定u纹理要使用新值 glUniform1i(textureUniformU, 1); //指定v纹理要使用新值 glUniform1i(textureUniformV, 2); return 0; } int CPlayWidget::loadRgbTexture() { //加载rgb数据纹理 //激活纹理单元GL_TEXTURE0 glActiveTexture(GL_TEXTURE0); //使用来自y数据生成纹理 glBindTexture(GL_TEXTURE_2D, id_rgb); //使用内存中m_pBufYuv420p数据创建真正的y数据纹理 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_nVideoW, m_nVideoH, 0, GL_BGRA, GL_UNSIGNED_BYTE, m_pBufRgb32); 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); glUniform1i(textureUniformRGB, 0); }