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,
};
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();
}
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()));
}
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);
}
这两个控件实际都是一个简单的QHBoxLayout
布局,里面包含一些QLabel
、QPushButton
等基础控件,源码很简单,就不多说了
我们的需求不仅仅是渲染RBG像素,还希望可以直接渲染YUV像素,为此就不能简单使用QLabel来显示RGB像素格式图片了,我们打算使用OpenGL,Qt中提供了QOpenGLWidget来简化OpenGL的使用。
为此,我们先封装一个通用的HGLWidget类,继承自QOpenGLWidget。
#ifndef HGLWIDGET_H
#define HGLWIDGET_H
#include "hgl.h"
#include "hframe.h"
#include "hgui.h"
#include
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
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
播放引擎的具体实现,敬请期待…