Qt实战--自定义播放控件类HVideoWidget

HVideoWidget

  • HVideoWidget用来显示、控制视频,包括打开、关闭媒体、开始、停止、暂停播放等功能

    public slots:
        void open(HMedia& media);
        void close();
    
        void start();
        void pause();
        void stop();
    
  • 由顶部标题栏、底部工具栏、中央播放窗口组成;

    HVideoWnd *videoWnd;
    HVideoTitlebar *titlebar;
    HVideoToolbar *toolbar;
    QPushButton *btnMedia;
    
  • 媒体类为HMedia、播放引擎类为HVideoPlayer

    HMedia media;
    HVideoPlayer* pImpl_player;

  • 状态:停止、暂停、播放

    enum Status{
        STOP,
        PAUSE,
        PLAY,
    };
    

initUI

void HVideoWidget::initUI(){
    setFocusPolicy(Qt::ClickFocus);

    videoWnd = new HVideoWnd(this);
    titlebar = new HVideoTitlebar(this);
    toolbar  = new HVideoToolbar(this);
    btnMedia = genPushButton(QPixmap(":/image/media_bk.png"), tr("Open media"));

    QVBoxLayout *vbox = genVBoxLayout();

    vbox->addWidget(titlebar, 0, Qt::AlignTop);
    vbox->addWidget(btnMedia, 0, Qt::AlignCenter);
    vbox->addWidget(toolbar, 0, Qt::AlignBottom);

    setLayout(vbox);

    titlebar->hide();
    toolbar->hide();
}

initConnect

void HVideoWidget::initConnect(){
    connect( btnMedia, SIGNAL(clicked(bool)), this, SLOT(onBtnMedia()) );
    connect( titlebar->btnClose, SIGNAL(clicked(bool)), this, SLOT(close()) );

    connect( toolbar, SIGNAL(sigStart()), this, SLOT(start()) );
    connect( toolbar, SIGNAL(sigPause()), this, SLOT(pause()) );
    connect( toolbar, SIGNAL(sigStop()), this, SLOT(stop()) );

    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(onTimerUpdate()));
}

updateUI

void HVideoWidget::updateUI(){
    titlebar->labTitle->setText(QString::asprintf("%02d ", playerid) + title);

    toolbar->btnStart->setVisible(status != PLAY);
    toolbar->btnPause->setVisible(status == PLAY);

    btnMedia->setVisible(status == STOP);
}

HVideoTitlebar、HVideoToolbar

这两个控件实际都是一个简单的QHBoxLayout布局,里面包含一些QLabelQPushButton等基础控件,源码很简单,就不多说了

HVideoWnd

我们的需求不仅仅是渲染RBG像素,还希望可以直接渲染YUV像素,为此就不能简单使用QLabel来显示RGB像素格式图片了,我们打算使用OpenGL,Qt中提供了QOpenGLWidget来简化OpenGL的使用。
为此,我们先封装一个通用的HGLWidget类,继承自QOpenGLWidget。

HGLWidget

  • 可以设置顶点坐标和纹理坐标;
  • 可以绘制视频帧、纹理、矩形、文字等;
  • 带YUV着色器;
#ifndef HGLWIDGET_H
#define HGLWIDGET_H

#include "hgl.h"
#include "hframe.h"
#include "hgui.h"
#include <qopenglwidget.h>

void bindTexture(GLTexture* tex, QImage* img);

class HGLWidget : public QOpenGLWidget
{
public:
    HGLWidget(QWidget* parent = NULL);

    void setVertices(double ratio);
    void setVertices(QRect rc);

    void drawFrame(HFrame *pFrame);
    void drawTexture(HRect rc, GLTexture *tex);
    void drawRect(HRect rc, HColor clr, int line_width = 1, bool bFill = false);
    void drawText(QPoint lb, const char* text, int fontsize, QColor clr);

protected:
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();

    static void loadYUVShader();
    void initVAO();
    void initYUV();

    void drawYUV(HFrame* pFrame);

protected:
    static bool s_bInitGLEW;
    static GLuint prog_yuv;
    static GLuint texUniformY;
    static GLuint texUniformU;
    static GLuint texUniformV;
    GLuint  tex_yuv[3];

    GLfloat vertices[8];
    GLfloat textures[8];

    enum VER_ATTR{
        VER_ATTR_VER,
        VER_ATTR_TEX,
        VER_ATTR_NUM
    };
};

#endif // HGLWIDGET_H

实现细节就直接上源码吧

#include "hglwidget.h"

void bindTexture(GLTexture* tex, QImage* img){
    if (img->format() != QImage::Format_ARGB32)
        return;

    glGenTextures(1, &tex->id);
    glBindTexture(GL_TEXTURE_2D, tex->id);
    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);

    tex->frame.w = img->width();
    tex->frame.h = img->height();
    tex->frame.type = GL_BGRA;
    tex->frame.bpp = img->bitPlaneCount();
    gluBuild2DMipmaps(GL_TEXTURE_2D, tex->frame.bpp/8, tex->frame.w, tex->frame.h, tex->frame.type, GL_UNSIGNED_BYTE, img->bits());
    //glTexImage2D(GL_TEXTURE_2D, 0, tex->bpp/8, tex->width, tex->height, 0, tex->type, GL_UNSIGNED_BYTE, img->bits());
}

bool HGLWidget::s_bInitGLEW = false;
GLuint HGLWidget::prog_yuv;
GLuint HGLWidget::texUniformY;
GLuint HGLWidget::texUniformU;
GLuint HGLWidget::texUniformV;

HGLWidget::HGLWidget(QWidget* parent)
    : QOpenGLWidget(parent)
{

}

void HGLWidget::setVertices(double ratio){
    double w = 1.0, h = 1.0;
    if (ratio < 1.0){
        w = ratio;
    }else{
        h = 1.0 / ratio;
    }

    GLfloat tmp[] = {
            -w, -h,
             w, -h,
            -w,  h,
             w,  h,
    };

    memcpy(vertices, tmp, sizeof(GLfloat)*8);
}

void HGLWidget::setVertices(QRect rc){
    int wnd_w = width();
    int wnd_h = height();
    if (wnd_w <= 0 || wnd_h <= 0)
        return;
    GLfloat left = (GLfloat)rc.left() * 2 / wnd_w - 1;
    GLfloat right = (GLfloat)(rc.right()+1) * 2 / wnd_w - 1;
    GLfloat top = 1 - (GLfloat)rc.top() * 2 / wnd_h;
    GLfloat bottom = 1 - (GLfloat)(rc.bottom()+1) * 2 / wnd_h;
    qDebug("l=%d, r=%d, t=%d, b=%d", rc.left(), rc.right(), rc.top(), rc.bottom());
    qDebug("l=%f, r=%f, t=%f, b=%f", left, right, top, bottom);
    GLfloat tmp[] = {
        left,  bottom,
        right, bottom,
        left,  top,
        right, top
    };

    memcpy(vertices, tmp, sizeof(GLfloat)*8);
}

void HGLWidget::loadYUVShader(){
    GLuint vs = glCreateShader(GL_VERTEX_SHADER);
    GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);

    char szVS[] = "             \
    attribute vec4 verIn;       \
    attribute vec2 texIn;       \
    varying vec2 texOut;        \
                                \
    void main(){                \
        gl_Position = verIn;    \
        texOut = texIn;         \
    }                           \
    ";
    const GLchar* pszVS = szVS;
    GLint len = strlen(szVS);
    glShaderSource(vs, 1, (const GLchar**)&pszVS, &len);

    char szFS[] = "             \
    varying vec2 texOut;        \
    uniform sampler2D tex_y;    \
    uniform sampler2D tex_u;    \
    uniform sampler2D tex_v;    \
                                \
    void main(){                \
        vec3 yuv;               \
        vec3 rgb;               \
        yuv.x = texture2D(tex_y, texOut).r;         \
        yuv.y = texture2D(tex_u, texOut).r - 0.5;   \
        yuv.z = texture2D(tex_v, texOut).r - 0.5;   \
        rgb = mat3( 1,       1,         1,          \
            0,       -0.39465,  2.03211,            \
            1.13983, -0.58060,  0) * yuv;           \
        gl_FragColor = vec4(rgb, 1);                \
    }                                               \
    ";
    const GLchar* pszFS = szFS;
    len = strlen(szFS);
    glShaderSource(fs, 1, (const GLchar**)&pszFS, &len);

    glCompileShader(vs);
    glCompileShader(fs);

//#ifdef _DEBUG
    GLint iRet = 0;
    glGetShaderiv(vs, GL_COMPILE_STATUS, &iRet);
    qDebug("vs::GL_COMPILE_STATUS=%d", iRet);
    glGetShaderiv(fs, GL_COMPILE_STATUS, &iRet);
    qDebug("fs::GL_COMPILE_STATUS=%d", iRet);
//#endif

    prog_yuv = glCreateProgram();

    glAttachShader(prog_yuv, vs);
    glAttachShader(prog_yuv, fs);

    glBindAttribLocation(prog_yuv, VER_ATTR_VER, "verIn");
    glBindAttribLocation(prog_yuv, VER_ATTR_TEX, "texIn");

    glLinkProgram(prog_yuv);

//#ifdef _DEBUG
    glGetProgramiv(prog_yuv, GL_LINK_STATUS, &iRet);
    qDebug("prog_yuv=%d GL_LINK_STATUS=%d", prog_yuv, iRet);
//#endif

    glValidateProgram(prog_yuv);

    texUniformY = glGetUniformLocation(prog_yuv, "tex_y");
    texUniformU = glGetUniformLocation(prog_yuv, "tex_u");
    texUniformV = glGetUniformLocation(prog_yuv, "tex_v");

    qDebug("loadYUVShader ok");
}

void HGLWidget::initVAO(){
    setVertices(1.0);

    GLfloat tmp[] = {
        0.0f, 1.0f,
        1.0f, 1.0f,
        0.0f, 0.0f,
        1.0f, 0.0f,
    };

    // reverse
//  GLfloat tmp[] = {
//        0.0f, 0.0f,
//        1.0f, 0.0f,
//        0.0f, 1.0f,
//        1.0f, 1.0f,
//    };
    memcpy(textures, tmp, sizeof(GLfloat)*8);

    glVertexAttribPointer(VER_ATTR_VER, 2, GL_FLOAT, GL_FALSE, 0, vertices);
    glEnableVertexAttribArray(VER_ATTR_VER);

    glVertexAttribPointer(VER_ATTR_TEX, 2, GL_FLOAT, GL_FALSE, 0, textures);
    glEnableVertexAttribArray(VER_ATTR_TEX);
}

void HGLWidget::initYUV(){
    glGenTextures(3, tex_yuv);
    for (int i = 0; i < 3; i++){
        glBindTexture(GL_TEXTURE_2D, tex_yuv[i]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    }
}

void HGLWidget::initializeGL(){
    if (!s_bInitGLEW){
        if (glewInit() != 0){
            qFatal("glewInit failed");
            return;
        }

        loadYUVShader();
        s_bInitGLEW = true;
    }

    initVAO();
    initYUV();
}

void HGLWidget::resizeGL(int w, int h){
    glViewport(0,0,w,h);
}

void HGLWidget::paintGL(){
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
}

void HGLWidget::drawYUV(HFrame* pFrame){
    glUseProgram(prog_yuv);

    int w = pFrame->w;
    int h = pFrame->h;
    int y_size = w*h;
    GLubyte* y = pFrame->buf.base;
    GLubyte* u = y + y_size;
    GLubyte* v = u + (y_size>>2);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex_yuv[0]);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, y);
    glUniform1i(texUniformY, 0);

    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, tex_yuv[1]);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w/2, h/2, 0, GL_RED, GL_UNSIGNED_BYTE, u);
    glUniform1i(texUniformU, 1);

    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, tex_yuv[2]);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w/2, h/2, 0, GL_RED, GL_UNSIGNED_BYTE, v);
    glUniform1i(texUniformV, 2);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glUseProgram(0);
}

void HGLWidget::drawFrame(HFrame *pFrame){
    if (pFrame->type == GL_I420){
        drawYUV(pFrame);
    }else{
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glRasterPos3f(-1.0f,1.0f,0);
        glPixelZoom(width()/(float)pFrame->w, -height()/(float)pFrame->h);
        glDrawPixels(pFrame->w, pFrame->h, pFrame->type, GL_UNSIGNED_BYTE, pFrame->buf.base);
    }
}

void HGLWidget::drawTexture(HRect rc, GLTexture *tex){
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0, width(), height(), 0.0, -1.0, 1.0);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex->id);

    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glBegin(GL_QUADS);
    glTexCoord2d(0,0);glVertex2i(rc.left(), rc.top());
    glTexCoord2d(1,0);glVertex2i(rc.right(), rc.top());
    glTexCoord2d(1,1);glVertex2i(rc.right(), rc.bottom());
    glTexCoord2d(0,1);glVertex2i(rc.left(), rc.bottom());
    glEnd();

    glDisable(GL_TEXTURE_2D);
    glDisable(GL_BLEND);
}

void HGLWidget::drawRect(HRect rc, HColor clr, int line_width, bool bFill){
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0, width(), height(), 0.0, -1.0, 1.0);

    if (bFill){
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }else{
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    }

    glLineWidth(line_width);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glColor4ub(CLR_R(clr), CLR_G(clr), CLR_B(clr), CLR_A(clr));
    glRecti(rc.left(), rc.top(), rc.right(), rc.bottom());
    glColor4ub(255,255,255,255);

    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glDisable(GL_BLEND);
}

#include <QPainter>
void HGLWidget::drawText(QPoint lb, const char* text, int fontsize, QColor clr){
    QPainter painter(this);
    QFont font = painter.font();
    font.setPointSize(fontsize);
    painter.setFont(font);
    painter.setPen(clr);
    painter.drawText(lb, text);
}

说明下:加载纹理图片我们可以直接使用QImage类、绘制文本本来打算使用FTGL库的,但发现Qt的QPainter类可以直接drawText,就不用这么麻烦了。

回到HVideoWnd,继承HGLWidget,重载paintGL即可。

下节我们打算讲下HVideoPlayer播放引擎的具体实现,敬请期待…

发布了128 篇原创文章 · 获赞 142 · 访问量 28万+
展开阅读全文

Win7采集摄像头数据显示图像回抖

12-18

开发环境:VS2015+qt5.7+相机驱动 实现方案:通过驱动配置回调函数,获取相机数据,然后把数据放入到数据缓冲区; 然后开启一个线程读取缓冲队列的数据,拷贝到一帧图像的缓冲块,并使用opengl(QOpenglWidget) 进行显示(updata()-onFrameSwapped()中emit render消息,次线程SLot进行显示)。 显示代码如下: void GLThreadWidget::UpdateShowImage() { update(); } void GLThreadWidget::onFrameSwapped() { m_renderer->unlockRenderer(); if (IsShowUpdateflag() == true) { emit renderRequested();//f发送消息 } } //槽函数 void Renderer::render() { if (m_exiting) return; bool needrender = m_glwidget->IsShowUpdateflag(); if (needrender == false) return; QImage * pimage = m_glwidget->GetCurImage(); if (pimage == nullptr) return; QOpenGLContext *ctx = m_glwidget->context(); if (!ctx) // QOpenGLWidget not yet initialized { m_glwidget->SetShowUpdateFlagFalse(); return; } if (ctx->thread() == QThread::currentThread()) { //qDebug() << "ctx->thread()" << ctx->thread(); m_glwidget->SetShowUpdateFlagFalse(); return; } // Grab the context. m_grabMutex.lock(); emit contextWanted(); m_grabCond.wait(&m_grabMutex); QMutexLocker lock(&m_renderMutex); m_grabMutex.unlock(); if (m_exiting) return; Q_ASSERT(ctx->thread() == QThread::currentThread()); // Make the context (and an offscreen surface) current for this thread. The // QOpenGLWidget's fbo is bound in the context. m_glwidget->makeCurrent(); if (!m_inited) { m_inited = true; initializeOpenGLFunctions(); initShaders(); initTextures(); initBuffArray(); } //initializeOpenGLFunctions(); GLfloat *pViewRect = m_glwidget->GetViewPortForRender(); glViewport(pViewRect[0], pViewRect[1], pViewRect[2], pViewRect[3]); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); // pimage = m_glwidget->GetCurImage(); int width; int height; m_glwidget->GetImageSize(width, height); m_texture->destroy(); m_texture->create(); m_texture->setSize(width, height); m_texture->setData((*pimage).mirrored()); m_program->bind(); m_texture->bind(); QMatrix4x4 m; this->m_program->setUniformValue(m_projviewUniform, m); this->m_program->setUniformValue(m_textureUniform, 0); // Draw cube geometry using indices from VBO 1 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); m_texture->release(); m_program->release(); glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); // Make no context current on this thread and move the QOpenGLWidget's // context back to the gui thread. m_glwidget->doneCurrent(); ctx->moveToThread(qGuiApp->thread()); // Schedule composition. Note that this will use QueuedConnection, meaning // that update() will be invoked on the gui thread. QMetaObject::invokeMethod(m_glwidget, "update", Qt::QueuedConnection); m_glwidget->SetShowUpdateFlagFalse(); // qDebug() << "render finished"; } 方案结果:刚开始能够正常显示,当手在移动的时候,图像也能实时的进行移动。但是 过一段时间之后,图像出现回抖现象。 比如说,手在移动的时候,能看出来显示后面的一帧图像时,又显示了前一帧图像, 看到就是手往回移了一点然后又立马往前移动。 针对此问题怀疑是数据缓冲区对列乱了,但通过使用序号验证, 读取数据用于显示时,显示的序号都是从小到大的,没有出现变小的情况。 异常现象视频:http://v.youku.com/v_show/id_XMzI0MDg5NjYyNA==.html?spm=a2h3j.8428770.3416059.1 正常现象视频:http://v.youku.com/v_show/id_XMzI0MDg5Mzg3Ng==.html?spm=a2h3j.8428770.3416059.1 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 代码科技 设计师: Amelia_0503

分享到微信朋友圈

×

扫一扫,手机浏览