【FFmpeg视频播放器开发】加入Qt和OpenGl只显示视频(四)
一、前言
这里我们加入 Qt 来设计播放器界面,解码出的 RGB 数据使用 OpenGl 来进行渲染绘制,这样比直接转换成 QImage 在 QLabel 等控件上显示效率更高。
二、XVideoWidget类的实现(渲染绘制RGB)
新创建个工程。然后我们先看下 XVideoWidget 的头文件:
#pragma once
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QGLShaderProgram>
#include <mutex>
extern "C" {
#include <libavutil/frame.h>
}
struct AVFrame;
class XVideoWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
XVideoWidget();
XVideoWidget(QWidget* parent);
~XVideoWidget();
void init(int width, int height); // 初始化
void myRepaint(AVFrame *frame); // 重绘AVFrame(不能与Qt的repaint函数名冲突)
protected:
void initializeGL(); // 初始化gl
void paintGL(); // 刷新显示
void resizeGL(int width, int height); // 窗口尺寸变化
private:
QGLShaderProgram program; // shader程序
GLuint unis[3] = { 0 }; // shader中yuv变量地址
GLuint texs[3] = { 0 }; // openg的 texture地址
// 材质内存空间
unsigned char *datas[3] = { 0 };
int width = 240;
int height = 128;
std::mutex mux;
};
2.1 init():初始化
// 初始化
void XVideoWidget::init(int width, int height)
{
std::unique_lock<std::mutex> guard(m_mutex);
this->width = width;
this->height = height;
delete datas[0];
delete datas[1];
delete datas[2];
// 分配材质内存空间
datas[0] = new unsigned char[width * height]; // Y
datas[1] = new unsigned char[width * height / 4]; // U
datas[2] = new unsigned char[width * height / 4]; // V
if (texs[0])
{
glDeleteTextures(3, texs);
}
// 创建材质
glGenTextures(3, texs);
// Y
glBindTexture(GL_TEXTURE_2D, texs[0]);
// 放大过滤,线性插值 GL_NEAREST(效率高,但马赛克严重)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// 创建材质显卡空间
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
// U
glBindTexture(GL_TEXTURE_2D, texs[1]);
// 放大过滤,线性插值
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// 创建材质显卡空间
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width / 2, height / 2, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
// V
glBindTexture(GL_TEXTURE_2D, texs[2]);
// 放大过滤,线性插值
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// 创建材质显卡空间
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width / 2, height / 2, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
}
2.2 myRepaint():重绘AVFrame
// 重绘AVFrame(不能与Qt的repaint函数名冲突)
void XVideoWidget::myRepaint(AVFrame *frame)
{
if (!frame)return;
std::unique_lock<std::mutex> guard(m_mutex);
// 容错,保证尺寸正确
if (!datas[0] || width*height == 0 || frame->width != this->width || frame->height != this->height)
{
av_frame_free(&frame);
return;
}
if (width == frame->linesize[0]) // 无需对齐
{
memcpy(datas[0], frame->data[0], width*height);
memcpy(datas[1], frame->data[1], width*height / 4);
memcpy(datas[2], frame->data[2], width*height / 4);
}
else // 行对齐问题
{
for(int i = 0; i < height; i++) // Y
memcpy(datas[0] + width*i, frame->data[0] + frame->linesize[0]*i, width);
for (int i = 0; i < height/2; i++) // U
memcpy(datas[1] + width/2*i, frame->data[1] + frame->linesize[1] * i, width);
for (int i = 0; i < height/2; i++) // V
memcpy(datas[2] + width/2*i, frame->data[2] + frame->linesize[2] * i, width);
}
av_frame_free(&frame);
//qDebug() << "刷新显示" << endl;
// 刷新显示
update();
}
2.3 initializeGL():初始化opengl
//初始化opengl
void XVideoWidget::initializeGL()
{
qDebug() << "initializeGL";
std::unique_lock<std::mutex> guard(m_mutex);
// 初始化opengl (QOpenGLFunctions继承)函数
initializeOpenGLFunctions();
// program加载shader(顶点和片元)脚本
// 片元(像素)
qDebug() << program.addShaderFromSourceCode(QGLShader::Fragment, tString);
// 顶点shader
qDebug() << program.addShaderFromSourceCode(QGLShader::Vertex, vString);
// 设置顶点坐标的变量
program.bindAttributeLocation("vertexIn", A_VER);
// 设置材质坐标
program.bindAttributeLocation("textureIn", T_VER);
// 编译shader
qDebug() << "program.link() = " << program.link();
qDebug() << "program.bind() = " << program.bind();
// 传递顶点和材质坐标
// 顶点
static const GLfloat ver[] = {
-1.0f,-1.0f,
1.0f,-1.0f,
-1.0f, 1.0f,
1.0f,1.0f
};
// 材质
static const GLfloat tex[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f
};
// 顶点
glVertexAttribPointer(A_VER, 2, GL_FLOAT, 0, 0, ver);
glEnableVertexAttribArray(A_VER);
// 材质
glVertexAttribPointer(T_VER, 2, GL_FLOAT, 0, 0, tex);
glEnableVertexAttribArray(T_VER);
// 从shader获取材质
unis[0] = program.uniformLocation("tex_y");
unis[1] = program.uniformLocation("tex_u");
unis[2] = program.uniformLocation("tex_v");
}
2.4 paintGL():刷新显示
// 刷新显示
void XVideoWidget::paintGL()
{
std::unique_lock<std::mutex> guard(m_mutex);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texs[0]); // 0层绑定到Y材质
// 修改材质内容(复制内存内容)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, datas[0]);
// 与shader uni遍历关联
glUniform1i(unis[0], 0);
glActiveTexture(GL_TEXTURE0 + 1);
glBindTexture(GL_TEXTURE_2D, texs[1]); // 1层绑定到U材质
// 修改材质内容(复制内存内容)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width / 2, height / 2, GL_RED, GL_UNSIGNED_BYTE, datas[1]);
// 与shader uni遍历关联
glUniform1i(unis[1], 1);
glActiveTexture(GL_TEXTURE0 + 2);
glBindTexture(GL_TEXTURE_2D, texs[2]); // 2层绑定到V材质
// 修改材质内容(复制内存内容)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width / 2, height / 2, GL_RED, GL_UNSIGNED_BYTE, datas[2]);
// 与shader uni遍历关联
glUniform1i(unis[2], 2);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// qDebug() << "paintGL";
}
2.5 resizeGL():窗口尺寸变化
// 窗口尺寸变化
void XVideoWidget::resizeGL(int width, int height)
{
std::unique_lock<std::mutex> guard(m_mutex);
qDebug() << "resizeGL " << width << ":" << height;
}
三、XPlayer():播放器类的实现
这里暂时只实现了一个简单的播放界面,按钮和播放进度条的槽函数都留到后面实现。
XPlayer::XPlayer(QWidget *parent)
: QWidget(parent)
{
// 初始化窗口大小
this->setFixedSize(600, 400);
// 初始化窗口部件
video = new XVideoWidget;
m_pBtnOpen = new QPushButton;
m_pBtnPlay = new QPushButton;
m_pSliderVideo = new QSlider;
m_pLabCurTime = new QLabel;
m_pLabTotal = new QLabel;
m_pBtnOpen->setFixedSize(53, 23);
m_pBtnPlay->setFixedSize(53, 23);
m_pBtnOpen->setText(QStringLiteral("打开"));
m_pBtnPlay->setText(QStringLiteral("播放"));
m_pLabCurTime->setText("00:00:00");
m_pLabTotal->setText("00:00:00");
m_pLabCurTime->setStyleSheet("QLabel{color:white;font-size:16px;}");
m_pLabTotal->setStyleSheet("QLabel{color:white;font-size:16px;}");
m_pLabCurTime->hide();
m_pLabTotal->hide();
// 打开视频、播放按钮-水平布局
QHBoxLayout* pHLayoutBtn = new QHBoxLayout;
pHLayoutBtn->addStretch();
pHLayoutBtn->addWidget(m_pBtnOpen);
pHLayoutBtn->addSpacing(12);
pHLayoutBtn->addWidget(m_pBtnPlay);
pHLayoutBtn->addStretch();
// 当前播放时间标签相关-水平布局
QHBoxLayout* pHLayoutLab = new QHBoxLayout;
pHLayoutLab->addSpacing(16);
pHLayoutLab->addWidget(m_pLabCurTime);
pHLayoutLab->addStretch();
pHLayoutLab->addWidget(m_pLabTotal);
pHLayoutLab->addSpacing(16);
// 主布局
QVBoxLayout* pVLayout = new QVBoxLayout(this);
pVLayout->addWidget(video);
pVLayout->addWidget(m_pSliderVideo);
pVLayout->addLayout(pHLayoutLab);
pVLayout->addLayout(pHLayoutBtn);
pVLayout->addSpacing(12);
// 初始化视频滑动条
m_pSliderVideo->setOrientation(Qt::Horizontal);
m_pSliderVideo->setMinimum(0);
m_pSliderVideo->setMaximum(100);
m_pSliderVideo->setValue(0);
m_pSliderVideo->setFixedHeight(16);
}
四、客户端实现
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
// 播放界面
XPlayer w;
w.show();
//=================1、解封装测试====================
XDemux demux;
const char* url = "dove_640x360.mp4";
cout << "demux.Open = " << demux.open(url);
demux.read();
cout << "demux.Open = " << demux.open(url); // open一次的话,很久之后才开始播放
cout << "CopyVPara = " << demux.copyVPara() << endl;
cout << "CopyAPara = " << demux.copyAPara() << endl;
// 初始化openGl窗口
w.video->init(demux.getVideoInfo().width, demux.getVideoInfo().height);
//=================2、解码测试====================
XDecode vdecode;
XDecode adecode;
cout << "vdecode.Open() = " << vdecode.Open(demux.copyVPara()) << endl;
cout << "adecode.Open() = " << adecode.Open(demux.copyAPara()) << endl;
// 开辟异步线程进行解码播放,避免阻塞GUI
auto futureLambda = std::async([&]() {
for (;;)
{
AVPacket* pkt = demux.read();
if (!pkt)
{
// 异步线程退出后,才清空销毁
demux.close();
vdecode.Close();
break;
}
if (demux.isAudio(pkt))
{
//adecode.Send(pkt);
//AVFrame *frame = adecode.Recv();
//cout << "Audio:" << frame << endl;
}
else
{
vdecode.Send(pkt);
AVFrame* frame = vdecode.Recv();
// OpenGl子界面重绘
w.video->myRepaint(frame);
QThread::msleep(40); // 25帧播放
}
}
});
return a.exec();
}
五、效果展示
执行后的效果图如下:
六、代码下载
下载链接:https://github.com/confidentFeng/FFmpeg/tree/master/XPlayer/XPlayer_3