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播放引擎的具体实现,敬请期待…

猜你喜欢

转载自blog.csdn.net/GG_SiMiDa/article/details/80925208