QOpenGLWidget渲染多路yuv,低cpu消耗,解决画面撕裂

QOpenGLWidget渲染多路yuv,低cpu消耗,解决画面撕裂

不是高手,有问题的欢迎指教

QOpenGLWidget渲染多路yuv, 低cpu消耗,不会渲染一会卡屏的几个重要因素如下:
1、update不要在子线程中调用,一般网上demo都是在RecvVideoStream中调用update,单个窗口渲染不会有影响,多个窗口渲染会导致渲染一会儿就会有窗口画面卡住,所以采用定时器中调用;
2、如果创建的GLYuvWidget 在渲染过程中有可能切换父窗口,那么就会导致崩溃,这个时候需要在main函数中调用 QGuiApplication::setAttribute(Qt::AA_UseOpenGLES);其他方法我没有成功,这个是根据源码找出来的办法,崩溃的原因是因为会重复调用initializeGL(),导致纹理资源没有释放而创建新的然后使用会崩溃

画面撕裂的解决办法: glPixelStorei(GL_UNPACK_ALIGNMENT, 1),设置对内存齐参数为1, 对于宽度不是4的倍数会出现渲染扭曲撕裂的情况,如果渲染的视频宽度一定是4的倍数,则此处不用设置,默认为4就好。

PlayVideoYUV.h

#pragma once
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QTimer>
#include <mutex>
#include <memory>

QT_FORWARD_DECLARE_CLASS(QOpenGLShaderProgram)
QT_FORWARD_DECLARE_CLASS(QOpenGLTexture)

class GLYuvWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
    Q_OBJECT
public:
    GLYuvWidget(QWidget *parent = 0);
    ~GLYuvWidget();

    void RecvVideoStream(uchar *ptr, uint width, uint height); //��ʾһ֡Yuvͼ��
    void initializeGL() Q_DECL_OVERRIDE;

public:
    void paintGL() Q_DECL_OVERRIDE;


private:
    QOpenGLShaderProgram *m_pShaderProgram{ nullptr };
    QOpenGLBuffer m_objGLBuffer;
    GLuint m_textureUniformY, m_textureUniformU, m_textureUniformV; 
    QOpenGLTexture *m_pTextureY{ nullptr }, *m_pTextureU{ nullptr }, *m_pTextureV{ nullptr };
    GLuint m_idY, m_idU, m_idV; 
    uint m_iVideoW, m_iVideoH;  
    uchar *m_pYuvBuffer{ nullptr };  
    int m_iBufferLength{ 0 };

    QTimer* timer;
    std::mutex m_mutex;
};


PlayVideoYUV.cpp

#include "PlayVideoYUV.h"
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QDebug>
#include <QSurface>
#include <QSurfaceFormat>

#define VERTEXIN 0
#define TEXTUREIN 1

GLYuvWidget::GLYuvWidget(QWidget *parent) :
    QOpenGLWidget(parent)
{
    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, [=] {
        // update不能在子线程中调用,否则多路视频流的渲染会因为qt内部优化导致渲染不在刷新
        update();
    });
    timer->start(40);
}

GLYuvWidget::~GLYuvWidget()
{
    makeCurrent();
    m_objGLBuffer.destroy();
    m_objGLBuffer.destroy();
    m_pTextureY->destroy();
    m_pTextureU->destroy();
    m_pTextureV->destroy();
    doneCurrent();

    if(nullptr != m_pYuvBuffer)
    {
        delete m_pYuvBuffer;
        m_pYuvBuffer = nullptr;
    }
}

void GLYuvWidget::RecvVideoStream(uchar *ptr, uint width, uint height)
{
    int ilength = width * height * 3 / 2;
    {
        std::lock_guard<std::mutex> lg(m_mutex);
        if (nullptr == m_pYuvBuffer)
        {
            m_pYuvBuffer = new uchar[ilength];
            m_iBufferLength = ilength;
        }
        if (m_iBufferLength < ilength)
        {
            delete m_pYuvBuffer;
            m_pYuvBuffer = new uchar[ilength];
            m_iBufferLength = ilength;
        }
        memcpy(m_pYuvBuffer, ptr, ilength);
        m_iVideoW = width;
        m_iVideoH = height;
    }
}

void GLYuvWidget::initializeGL()
{
    qDebug() << "initializeGL";
    initializeOpenGLFunctions();
    glEnable(GL_DEPTH_TEST);
    // 设置对内存齐参数为1, 对于宽度不是4的倍数会出现渲染扭曲的情况,如果渲染的视频宽度一定是4的倍数,则此处不用设置,默认为4就好
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

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


    m_objGLBuffer.create();
    m_objGLBuffer.bind();
    m_objGLBuffer.allocate(vertices, sizeof(vertices));

    QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);
    const char *vsrc =
        "attribute vec4 vertexIn; \
		attribute vec2 textureIn; \
		varying vec2 textureOut;  \
		void main(void)           \
		{                         \
        gl_Position = vertexIn; \
        textureOut = textureIn; \
		}";
    vshader->compileSourceCode(vsrc);

    QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);
    const char *fsrc = 
        "#ifdef GL_ES\n"
        "#ifdef GL_FRAGMENT_PRECISION_HIGH\n"
        "precision highp float;\n"
        "#else\n"
        "precision mediump float;\n"
        "#endif\n"
        "#endif\n"
        "varying vec2 textureOut;\n"
        "uniform sampler2D tex_y;\n"
        "uniform sampler2D tex_u;\n"
        "uniform sampler2D tex_v;\n"
        "void main(void)\n"
        "{ \n"
        "    vec3 yuv; \n"
        "    vec3 rgb; \n"
        "    yuv.x = texture2D(tex_y, textureOut).r;\n"
        "    yuv.y = texture2D(tex_u, textureOut).r - 0.5; \n"
        "    yuv.z = texture2D(tex_v, textureOut).r - 0.5; \n"
        "    rgb = mat3( 1,       1,         1,\n"
        "                0,       -0.39465,  2.03211, \n"
        "                1.13983, -0.58060,  0) * yuv; \n"
        "    gl_FragColor = vec4(rgb, 1);\n "
        "}";
        
    fshader->compileSourceCode(fsrc);

    m_pShaderProgram = new QOpenGLShaderProgram(this);
    m_pShaderProgram->addShader(vshader);
    m_pShaderProgram->addShader(fshader);
    m_pShaderProgram->bindAttributeLocation("vertexIn", VERTEXIN);
    m_pShaderProgram->bindAttributeLocation("textureIn", TEXTUREIN);
    m_pShaderProgram->link();
    m_pShaderProgram->bind();
    m_pShaderProgram->enableAttributeArray(VERTEXIN);
    m_pShaderProgram->enableAttributeArray(TEXTUREIN);
    m_pShaderProgram->setAttributeBuffer(VERTEXIN, GL_FLOAT, 0, 2, 2 * sizeof(GLfloat));
    m_pShaderProgram->setAttributeBuffer(TEXTUREIN, GL_FLOAT, 8 * sizeof(GLfloat), 2, 2 * sizeof(GLfloat));

    m_textureUniformY = m_pShaderProgram->uniformLocation("tex_y");
    m_textureUniformU = m_pShaderProgram->uniformLocation("tex_u");
    m_textureUniformV = m_pShaderProgram->uniformLocation("tex_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();
    m_idY = m_pTextureY->textureId();
    m_idU = m_pTextureU->textureId();
    m_idV = m_pTextureV->textureId();
    glClearColor(0.0, 0.0, 0.0, 0.0);
}

void GLYuvWidget::paintGL()
{
    std::lock_guard<std::mutex> lg(m_mutex);
    if (nullptr == m_pYuvBuffer)
    {
        return;
    }

    // 计算窗口的宽高比和码流的宽高比,设置显示比例
    long yuv_w_view_h = m_iVideoW * height();
    long yuv_h_view_w = m_iVideoH * width();
    if (yuv_w_view_h > yuv_h_view_w)
    { // 横向铺满,纵向显示不全,需要计算位置以及大小
        long lRealViewH = yuv_h_view_w / m_iVideoW;
        int y = (height() - lRealViewH) / 2;
        glViewport(0, y, width(), lRealViewH);
    }
    else
    {
        long lRealViewW = yuv_w_view_h / m_iVideoH;
        int x = (width() - lRealViewW) / 2;
        glViewport(x, 0, lRealViewW, height());
    }
    //qDebug() << "paintGL";
    //    QMatrix4x4 m;
    //    m.perspective(60.0f, 4.0f/3.0f, 0.1f, 100.0f );//透视矩阵随距离的变化,图形跟着变化。屏幕平面中心就是视点(摄像头),需要将图形移向屏幕里面一定距离。
    //    m.ortho(-2,+2,-2,+2,-10,10);//近裁剪平面是一个矩形,矩形左下角点三维空间坐标是(left,bottom,-near),右上角点是(right,top,-near)所以此处为负,表示z轴最大为10;
    //远裁剪平面也是一个矩形,左下角点空间坐标是(left,bottom,-far),右上角点是(right,top,-far)所以此处为正,表示z轴最小为-10;
    //此时坐标中心还是在屏幕水平面中间,只是前后左右的距离已限制。
    glActiveTexture(GL_TEXTURE0);  //激活纹理单元GL_TEXTURE0,系统里面的
    glBindTexture(GL_TEXTURE_2D, m_idY); //绑定y分量纹理对象id到激活的纹理单元
                                       //使用内存中的数据创建真正的y分量纹理数据
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_iVideoW, m_iVideoH, 0, GL_RED, GL_UNSIGNED_BYTE, m_pYuvBuffer);
    // 纹理参数解析
    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);

    glActiveTexture(GL_TEXTURE1); //激活纹理单元GL_TEXTURE1
    glBindTexture(GL_TEXTURE_2D, m_idU);
    //使用内存中的数据创建真正的u分量纹理数据
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_iVideoW >> 1, m_iVideoH >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, m_pYuvBuffer + m_iVideoW * m_iVideoH);
    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);

    glActiveTexture(GL_TEXTURE2); //激活纹理单元GL_TEXTURE2
    glBindTexture(GL_TEXTURE_2D, m_idV);
    //使用内存中的数据创建真正的v分量纹理数据
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_iVideoW >> 1, m_iVideoH >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, m_pYuvBuffer + m_iVideoW*m_iVideoH * 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纹理要使用新值
    glUniform1i(m_textureUniformY, 0);
    //指定u纹理要使用新值
    glUniform1i(m_textureUniformU, 1);
    //指定v纹理要使用新值
    glUniform1i(m_textureUniformV, 2);

    //使用顶点数组方式绘制图形
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}

  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
可以使用OpenGL来播放本地yuv文件,具体步骤如下: 1. 创建一个继承自QOpenGLWidget的类,并在其构造函数中初始化OpenGL相关设置: ```cpp class YUVPlayer : public QOpenGLWidget, protected QOpenGLFunctions { public: YUVPlayer(QWidget *parent = nullptr); ~YUVPlayer(); protected: void initializeGL() override; void paintGL() override; void resizeGL(int w, int h) override; void cleanupGL() override; private: GLuint m_textureY; GLuint m_textureU; GLuint m_textureV; GLsizei m_videoWidth; GLsizei m_videoHeight; }; ``` ```cpp YUVPlayer::YUVPlayer(QWidget *parent) : QOpenGLWidget(parent) { // 初始化OpenGL函数 initializeOpenGLFunctions(); // 设置背景色 glClearColor(0, 0, 0, 1); // 创建纹理 glGenTextures(1, &m_textureY); glGenTextures(1, &m_textureU); glGenTextures(1, &m_textureV); } ``` 2. 在initializeGL()函数中加载yuv文件,创建纹理并绑定到对应的纹理单元上: ```cpp void YUVPlayer::initializeGL() { // 加载yuv文件 QFile file("test.yuv"); if (!file.open(QIODevice::ReadOnly)) return; QByteArray data = file.readAll(); file.close(); m_videoWidth = 640; m_videoHeight = 480; // 创建纹理 glBindTexture(GL_TEXTURE_2D, m_textureY); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_videoWidth, m_videoHeight, 0, GL_RED, GL_UNSIGNED_BYTE, data.data()); glBindTexture(GL_TEXTURE_2D, m_textureU); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_videoWidth / 2, m_videoHeight / 2, 0, GL_RED, GL_UNSIGNED_BYTE, data.data() + m_videoWidth * m_videoHeight); glBindTexture(GL_TEXTURE_2D, m_textureV); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_videoWidth / 2, m_videoHeight / 2, 0, GL_RED, GL_UNSIGNED_BYTE, data.data() + m_videoWidth * m_videoHeight * 5 / 4); // 设置纹理过滤方式 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 绑定纹理单元 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, m_textureY); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, m_textureU); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, m_textureV); } ``` 3. 在paintGL()函数中绘制纹理: ```cpp void YUVPlayer::paintGL() { glClear(GL_COLOR_BUFFER_BIT); // 绑定纹理并绘制 glBindTexture(GL_TEXTURE_2D, m_textureY); glActiveTexture(GL_TEXTURE0); glBegin(GL_QUADS); glTexCoord2i(0, 0); glVertex3f(-1, -1, 0); glTexCoord2i(1, 0); glVertex3f(1, -1, 0); glTexCoord2i(1, 1); glVertex3f(1, 1, 0); glTexCoord2i(0, 1); glVertex3f(-1, 1, 0); glEnd(); glBindTexture(GL_TEXTURE_2D, m_textureU); glActiveTexture(GL_TEXTURE1); glBegin(GL_QUADS); glTexCoord2i(0, 0); glVertex3f(-1, -1, 0); glTexCoord2i(1, 0); glVertex3f(1, -1, 0); glTexCoord2i(1, 1); glVertex3f(1, 1, 0); glTexCoord2i(0, 1); glVertex3f(-1, 1, 0); glEnd(); glBindTexture(GL_TEXTURE_2D, m_textureV); glActiveTexture(GL_TEXTURE2); glBegin(GL_QUADS); glTexCoord2i(0, 0); glVertex3f(-1, -1, 0); glTexCoord2i(1, 0); glVertex3f(1, -1, 0); glTexCoord2i(1, 1); glVertex3f(1, 1, 0); glTexCoord2i(0, 1); glVertex3f(-1, 1, 0); glEnd(); } ``` 4. 在resizeGL()函数中设置视口: ```cpp void YUVPlayer::resizeGL(int w, int h) { glViewport(0, 0, w, h); } ``` 5. 在程序中创建YUVPlayer类的对象,并显示: ```cpp int main(int argc, char *argv[]) { QApplication a(argc, argv); YUVPlayer player; player.show(); return a.exec(); } ``` 这样就可以使用OpenGL来播放本地yuv文件了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

echo<£>

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值