QOpenGLWidget显示视频流数据

使用QOpenGLWidget显示

QT中的QOpenGLWidget底层是使用opengl库,是对opengl的再封装。使用QOpenGLWidget类时,我们仅需继承QOpenGLWidget,仅需重写void initializeGL()、void resizeGL(int w, int h)和void paintGL()三个函数即可。

目的与理论方法

我们的目的是为了在GPU端进行格式转换处理,然后显示视频流(图片),减轻CPU的占用率。
我们将上诉的目的进行分解可知有以下几个工作:

  1. 定时(可选);
  2. 格式转换,I420->RGB、NV12->RGB等;
  3. 显示图片(视频流)。

方法实现

将目的进行分解之后,我们对功能也就更明确了。接下来我们细说上述的三个待做工作:

定时功能实现

QT提供了多种定时器,我们选择一种自己常用的即可。例如如下代码:

#include <QTimer>
QTimer *t = new QTimer(this);
t->setInterval(50);
connect(t, &QTimer::timeout, [=](){
	qDebug() << "timeout!";
};
t->start();

格式转换

能在显示器上显示的图像都为rgb图像。所以对于一些非rgb的图像格式,我们需要对其做格式转换,转为rgb格式。
对于一幅列(宽)为width、行(高)为height的图像数据来说:
rgba8888格式: 总大小为width * height * 4(bytes)。图像格式为rgbargbargba…rgba;
rgb888格式: 总大小为width * height * 3(bytes)。图像格式为rgbrgbrgb…rgb;
bgr888格式(OpenCV Mat格式): 总大小为width * height * 3(bytes)。图像格式为bgrbgr…bgr;
I420格式: 总大小为width * height * 1.5(bytes)。图像格式为y…yu…uv…v;
NV12格式:总大小为width * height * 1.5(bytes)。图像格式为y…yuvuvuv…uvuv;
NV21格式: 总大小为width * height * 1.5(bytes)。图像格式为y…yvuvu…vuvu;
Grayscale8格式: 总大小为width * height(bytes)。rgb三通道的值都一样。

了解了上述了几种常见的格式通道排布格式。我们就可以在cpu端进行运算操作然后将其转为rgb/rgba格式,然后进行显示;在QOpenGLWidget中,我们使用纹理映射来做相应的格式转换操作。

QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
f->glActiveTexture(GL_TEXTURE0);
f->glBindTexture(GL_TEXTURE_2D, this->m->texture_id_y);
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, this->m->m_video_w, this->m->m_video_h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, this->m->m_video_ptr);

f->glActiveTexture(GL_TEXTURE1);
f->glBindTexture(GL_TEXTURE_2D, this->m->texture_id_u);
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, this->m->m_video_w/2, this->m->m_video_h/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, this->m->m_video_ptr+this->m->m_video_w*this->m->m_video_h);

f->glActiveTexture(GL_TEXTURE2);
f->glBindTexture(GL_TEXTURE_2D, this->m->texture_id_v);
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, this->m->m_video_w/2, this->m->m_video_h/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, this->m->m_video_ptr+this->m->m_video_w*this->m->m_video_h*5/4);

上述代码是I420格式的纹理读取操作,是在CPU端进行的,读取之后将数据送至GPU端。

"      yuv.x = texture(texture_y, out_texture).r;\n"
"      yuv.y = texture(texture_u, out_texture).r-0.5;\n"
"      yuv.z = texture(texture_v, out_texture).r-0.5;\n"
"      rgb = mat3(1,       1,          1,  \
                  0,       -0.34414,   1.77216,\
                  1.40168, -0.71417,   0) * yuv;\n"
"  fragColor = vec4(rgb, alpha);\n"

上述代码是在片段着色器中进行运算的,该部分的操作是使用GPU运算,固可以极大的减少CPU的使用率。

显示视频流

在void initializeGL()函数中使用vao、vbo创建一个四边形及纹理坐标,然后再void paintGL()函数中更绑定纹理、更新纹理、绘制四边形进行纹理贴图即可。

完整代码

完整代码如下:

#ifndef VIDEOOPENGL_H
#define VIDEOOPENGL_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>


QT_BEGIN_NAMESPACE
class QOpenGLTexture;
class QOpenGLShaderProgram;
class QOpenGLBuffer;
class QOpenGLVertexArrayObject;
QT_END_NAMESPACE


namespace common {

namespace qt {

class VideoOpenGL : public QOpenGLWidget, protected QOpenGLFunctions
{
    Q_OBJECT
public:
    enum ImageFormat{
        IMAGE_FORMAT_Unknown,
        IMAGE_FORMAT_NV12,      // y... uvuv...
        IMAGE_FORMAT_NV21,      // y... vuvu...
        IMAGE_FORMAT_I420,      // y... u... v...
        IMAGE_FORMAT_RGB32,     // b g r a
        IMAGE_FORMAT_ARGB32,    // b g r a
        IMAGE_FORMAT_RGB888,    // r g b
        IMAGE_FORMAT_Grayscale8,// grayscale8
        IMAGE_FORMAT_BGR888,    // b g r(OpenCV Mat Format)
    };
    VideoOpenGL(QWidget* parent = nullptr);
    ~VideoOpenGL();
    void set_image_size(const int& width, const int& height, const ImageFormat& farmat);
    void update_image(char* data_ptr);
protected:
    void initializeGL() override;
    void paintGL() override;
    void resizeGL(int w, int h) override;
    void paintEvent(QPaintEvent *e) override;

private:
    struct impl;
    struct impl* m;
};

}

}

#endif // VIDEOOPENGL_H

#include "videoopengl.h"
#include <QOpenGLTexture>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLShaderProgram>
#include <iostream>
#include <QPainter>
using namespace common::qt;

QByteArray version_shader_code(const char* src);
struct VideoOpenGL::impl
{
    impl() = default;
    ~impl(){
        if(this->m_video_ptr != nullptr){
            delete this->m_video_ptr;
            this->m_video_ptr = nullptr;
        }
        f->glDeleteTextures(1, &texture_id_y);
        f->glDeleteTextures(1, &texture_id_u);
        f->glDeleteTextures(1, &texture_id_v);
        m_vbo->release();
        m_program->release();
        delete m_vbo;
        delete m_program;
    }
    QOpenGLShaderProgram *m_program ;
    QOpenGLBuffer *m_vbo = nullptr;
    QOpenGLVertexArrayObject *m_vao = nullptr;
    GLuint texture_id_y, texture_id_u, texture_id_v;
    int m_video_w = 0, m_video_h = 0;
    int m_image_size = 0;
    char* m_video_ptr = nullptr;
    ImageFormat format = IMAGE_FORMAT_Unknown;
    QOpenGLFunctions *f;
};

VideoOpenGL::VideoOpenGL(QWidget* parent)
    : QOpenGLWidget(parent)
{
    m = new struct impl;
}


VideoOpenGL::~VideoOpenGL()
{
    makeCurrent();
    delete m;
}

QByteArray version_shader_code(const char *src)
{
    QByteArray versionedSrc;

    if (QOpenGLContext::currentContext()->isOpenGLES())
        versionedSrc.append(QByteArrayLiteral("#version 300 es\n"));
    else
        versionedSrc.append(QByteArrayLiteral("#version 330\n"));

    versionedSrc.append(src);
    return versionedSrc;
}

void VideoOpenGL::initializeGL()
{
    //显示视频流,一定不要开启深度测试!!!
    this->m->f = QOpenGLContext::currentContext()->functions();
    QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
    const char* vertex_shader_source = "layout(location = 0) in vec4 in_position;\n"
                                       "layout(location = 1) in vec2 in_texture;\n"
                                       "out vec2 out_texture;\n"
                                       "void main(void){\n"
                                       "    gl_Position = in_position;\n"
                                       "    out_texture = in_texture;\n"
                                       "}\n";

    // OpenGLES 内置矩阵mat3是一列一列的构建
    const char* fragment_shader_source = "in highp vec2 out_texture;\n"
                                         "uniform sampler2D texture_y;\n"
                                         "uniform sampler2D texture_u;\n"
                                         "uniform sampler2D texture_v;\n"
                                         "uniform int video_format;\n"
                                         "out highp vec4 fragColor;\n"
                                         "void main(){\n"
                                         "  highp vec3 yuv, rgb;\n"
                                         "  highp float alpha = 1.0;\n"
                                         "  if(video_format == 1){\n"
                                         "      yuv.x = texture(texture_y, out_texture).r;\n"
                                         "      yuv.y = texture(texture_u, out_texture).r-0.5;\n"
                                         "      yuv.z = texture(texture_u, out_texture).a-0.5;\n"
                                         "      rgb = mat3(1,       1,          1,  \
                                                           0,       -0.34414,   1.77216,\
                                                           1.40168, -0.71417,   0) * yuv;\n"
                                         "  }else if(video_format == 2){\n"
                                         "      yuv.x = texture(texture_y, out_texture).r;\n"
                                         "      yuv.y = texture(texture_u, out_texture).a-0.5;\n"
                                         "      yuv.z = texture(texture_u, out_texture).r-0.5;\n"
                                         "      rgb = mat3(1,       1,          1,  \
                                                             0,       -0.34414,   1.77216,\
                                                             1.40168, -0.71417,   0) * yuv;\n"
                                         "  }else if(video_format == 3){\n"
                                         "      yuv.x = texture(texture_y, out_texture).r;\n"
                                         "      yuv.y = texture(texture_u, out_texture).r-0.5;\n"
                                         "      yuv.z = texture(texture_v, out_texture).r-0.5;\n"
                                         "      rgb = mat3(1,       1,          1,  \
                                                           0,       -0.34414,   1.77216,\
                                                           1.40168, -0.71417,   0) * yuv;\n"
                                         "  }else if(video_format == 4){\n"
                                         "      highp vec4 tmp = texture(texture_y, out_texture);\n"
                                         "      rgb = vec3(tmp.b, tmp.g, tmp.r);\n"
                                         "  }else if(video_format == 5){\n"
                                         "      highp vec4 tmp = texture(texture_y, out_texture);\n"
                                         "      rgb = vec3(tmp.b, tmp.g, tmp.r);\n"
                                         "      alpha = tmp.a;\n"
                                         "  }else if(video_format == 6){\n"
                                         "      highp vec4 tmp = texture(texture_y, out_texture);\n"
                                         "      rgb = vec3(tmp.r, tmp.g, tmp.b);\n"
                                         "  }else if(video_format == 7){\n"
                                         "      highp vec4 tmp = texture(texture_y, out_texture);\n"
                                         "      rgb = tmp.rgb;\n"
                                         "  }else if(video_format == 8){\n"
                                         "      highp vec4 tmp = texture(texture_y, out_texture);\n"
                                         "      rgb = vec3(tmp.b, tmp.g, tmp.r);\n"
                                         "  }\n"
                                         "  fragColor = vec4(rgb, alpha);\n"
                                         "}\n";

    this->m->m_program = new QOpenGLShaderProgram;
    this->m->m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, version_shader_code(vertex_shader_source));
    this->m->m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, version_shader_code(fragment_shader_source));
    this->m->m_program->link();
    this->m->m_program->bind();
    this->m->m_program->setUniformValue("texture_y", 0);
    this->m->m_program->setUniformValue("texture_u", 1);
    this->m->m_program->setUniformValue("texture_v", 2);

#if 0
    static const GLfloat vertex_vertices[] = {
        -1.0f,  -1.0f,
         1.0f,  -1.0f,
        -1.0f,   1.0f,
         1.0f,   1.0f,
    };

    static const GLfloat texture_vertices[] = {
        0.0f,   1.0f,
        1.0f,   1.0f,
        0.0f,   0.0f,
        1.0f,   0.0f,
    };

    f->glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, vertex_vertices);
    f->glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, texture_vertices);
    f->glEnableVertexAttribArray(0);
    f->glEnableVertexAttribArray(1);
#else
    static const GLfloat vertices[] = {
        -1.0f,  -1.0f,  0.0f,   1.0f,
         1.0f,  -1.0f,  1.0f,   1.0f,
        -1.0f,   1.0f,  0.0f,   0.0f,
         1.0f,   1.0f,  1.0f,   0.0f,
    };

    this->m->m_vao = new QOpenGLVertexArrayObject(this);
    this->m->m_vao->create();
    this->m->m_vao->bind();

    this->m->m_vbo = new QOpenGLBuffer;
    this->m->m_vbo->create();
    this->m->m_vbo->bind();
    this->m->m_vbo->allocate(vertices, sizeof(vertices));
    f->glEnableVertexAttribArray(0);
    f->glEnableVertexAttribArray(1);
    f->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), 0);
    f->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float),
                             reinterpret_cast<void *>(2*sizeof(float)));
#endif

#if 1
    f->glGenTextures(1, &this->m->texture_id_y);
    f->glGenTextures(1, &this->m->texture_id_u);
    f->glGenTextures(1, &this->m->texture_id_v);

    f->glActiveTexture(GL_TEXTURE0);
    f->glBindTexture(GL_TEXTURE_2D, this->m->texture_id_y);
    f->glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    f->glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    f->glActiveTexture(GL_TEXTURE1);
    f->glBindTexture(GL_TEXTURE_2D, this->m->texture_id_u);
    f->glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    f->glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    f->glActiveTexture(GL_TEXTURE2);
    f->glBindTexture(GL_TEXTURE_2D, this->m->texture_id_v);
    f->glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    f->glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
#endif
    f->glBindTexture(GL_TEXTURE_2D, 0);
    this->m->m_vao->release();
    this->m->m_vbo->release();
    this->m->m_program->release();

    f->glEnable(GL_BLEND);
    f->glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA_SATURATE);//GL_DST_ALPHA
}

void VideoOpenGL::resizeGL(int w, int h)
{
    QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
    f->glViewport(0,0,w,h==0?1:h);
}

void VideoOpenGL::paintGL()
{
    QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
    f->glClear(GL_COLOR_BUFFER_BIT);
    this->m->m_program->bind();
    this->m->m_vao->bind();
#if 1
    this->m->m_program->setUniformValue("video_format", this->m->format);
    switch (this->m->format)
    {
    case IMAGE_FORMAT_Unknown:
        std::cout << "[W] Unknow video farmat." << std::endl;
        return ;
    case IMAGE_FORMAT_NV12:
    case IMAGE_FORMAT_NV21:
        f->glActiveTexture(GL_TEXTURE0);
        f->glBindTexture(GL_TEXTURE_2D, this->m->texture_id_y);
        f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, this->m->m_video_w, this->m->m_video_h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, this->m->m_video_ptr);

        f->glActiveTexture(GL_TEXTURE1);
        f->glBindTexture(GL_TEXTURE_2D, this->m->texture_id_u);
        f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, this->m->m_video_w/2, this->m->m_video_h/2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, this->m->m_video_ptr+this->m->m_video_w*this->m->m_video_h);
        break;
    case IMAGE_FORMAT_I420:
        f->glActiveTexture(GL_TEXTURE0);
        f->glBindTexture(GL_TEXTURE_2D, this->m->texture_id_y);
        f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, this->m->m_video_w, this->m->m_video_h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, this->m->m_video_ptr);

        f->glActiveTexture(GL_TEXTURE1);
        f->glBindTexture(GL_TEXTURE_2D, this->m->texture_id_u);
        f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, this->m->m_video_w/2, this->m->m_video_h/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, this->m->m_video_ptr+this->m->m_video_w*this->m->m_video_h);

        f->glActiveTexture(GL_TEXTURE2);
        f->glBindTexture(GL_TEXTURE_2D, this->m->texture_id_v);
        f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, this->m->m_video_w/2, this->m->m_video_h/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, this->m->m_video_ptr+this->m->m_video_w*this->m->m_video_h*5/4);
        break;
    case IMAGE_FORMAT_ARGB32:
    case IMAGE_FORMAT_RGB32:
        f->glActiveTexture(GL_TEXTURE0);
        f->glBindTexture(GL_TEXTURE_2D, this->m->texture_id_y);
        f->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, this->m->m_video_w, this->m->m_video_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, this->m->m_video_ptr);
        break;
    case IMAGE_FORMAT_RGB888:
        f->glActiveTexture(GL_TEXTURE0);
        f->glBindTexture(GL_TEXTURE_2D, this->m->texture_id_y);
        f->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, this->m->m_video_w, this->m->m_video_h, 0, GL_RGB, GL_UNSIGNED_BYTE, this->m->m_video_ptr);
        break;
    case IMAGE_FORMAT_Grayscale8:
        f->glActiveTexture(GL_TEXTURE0);
        f->glBindTexture(GL_TEXTURE_2D, this->m->texture_id_y);
        f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, this->m->m_video_w, this->m->m_video_h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, this->m->m_video_ptr);
        break;
    case IMAGE_FORMAT_BGR888:
        f->glActiveTexture(GL_TEXTURE0);
        f->glBindTexture(GL_TEXTURE_2D, this->m->texture_id_y);
        f->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, this->m->m_video_w, this->m->m_video_h, 0, GL_RGB, GL_UNSIGNED_BYTE, this->m->m_video_ptr);
        break;
    default:
        break;
    }
#endif
    f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    f->glBindTexture(GL_TEXTURE_2D, 0);
    this->m->m_vao->release();
    this->m->m_program->release();
    f->glFinish();
}

void VideoOpenGL::paintEvent(QPaintEvent *e)
{
    QOpenGLWidget::paintEvent(e);
    QPainter painter(this);
    painter.fillRect(0, 0, 100, 100, Qt::green);
}

void VideoOpenGL::set_image_size(const int &width, const int &height, const ImageFormat& format)
{
    this->m->m_video_w = width;
    this->m->m_video_h = height;
    this->m->format = format;

    float multiple = 1.0;
    switch (format)
    {
    case IMAGE_FORMAT_NV12:
    case IMAGE_FORMAT_NV21:
    case IMAGE_FORMAT_I420:
        multiple = 1.5;
        break;
    case IMAGE_FORMAT_ARGB32:
    case IMAGE_FORMAT_RGB32:
        multiple = 4;
        break;
    case IMAGE_FORMAT_RGB888:
    case IMAGE_FORMAT_BGR888:
        multiple = 3;
        break;
    case IMAGE_FORMAT_Grayscale8:
        multiple = 1;
        break;
    default:
        break;
    }
    this->m->m_image_size = multiple*this->m->m_video_w*this->m->m_video_h;

    if(this->m->m_video_ptr != nullptr){
        delete this->m->m_video_ptr;
        this->m->m_video_ptr = nullptr;
    }

    this->m->m_video_ptr = new char[this->m->m_image_size];
}

void VideoOpenGL::update_image(char *data_ptr)
{
    memcpy(this->m->m_video_ptr, data_ptr, this->m->m_image_size);
    update();
}


  • 5
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值