int main(int argc, char *argv[])
{
testtread tt;
tt.init();
QApplication a(argc, argv);
Xplay2 w;
w.show();
w.ui.openGLWidget->Init(tt.demux.width, tt.demux.height);
tt.video = w.ui.openGLWidget;
tt.start();
return a.exec();
}
在实战14中,是直接读取的yuv文件然后用OpenGL进行绘制:
fp = fopen(“out240x128.yuv”, “rb”);
这里我们是利用解码获得的frame来进行绘制
打开.ui文件,添加OpenGL控件:
提升为类xvideowidget:
在ui_Xplay2文件中可以看到如下代码:
class Ui_Xplay2Class
{
public:
xvideowidget *openGLWidget;
void setupUi(QWidget *Xplay2Class)
{
if (Xplay2Class->objectName().isEmpty())
Xplay2Class->setObjectName(QString::fromUtf8("Xplay2Class"));
Xplay2Class->resize(1072, 790);
openGLWidget = new xvideowidget(Xplay2Class);
openGLWidget->setObjectName(QString::fromUtf8("openGLWidget"));
openGLWidget->setGeometry(QRect(149, 129, 800, 600));
retranslateUi(Xplay2Class);
QMetaObject::connectSlotsByName(Xplay2Class);
} // setupUi
void retranslateUi(QWidget *Xplay2Class)
{
Xplay2Class->setWindowTitle(QCoreApplication::translate("Xplay2Class", "Xplay2", nullptr));
} // retranslateUi
};
可以看到xvideowidget *openGLWidget;在这个界面的大类里放了个指向xvideowidget的指针OpenGLwidget,并用该指针调用resize等方法
在代码中添加xvideowidget.h:
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QGLShaderProgram>
#include <mutex>
struct AVFrame;
class xvideowidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
void Init(int width, int height);
//不管成功与否都释放frame空间
virtual void Repaint(AVFrame* frame);
xvideowidget(QWidget* parent);
~xvideowidget();
protected:
//刷新显示
void paintGL();
//初始化gl
void initializeGL();
// 窗口尺寸变化
void resizeGL(int width, int height);
private:
std::mutex mux;
//shader程序
QGLShaderProgram program;
//shader中yuv变量地址
GLuint unis[3] = { 0 };
//openg的 texture地址
GLuint texs[3] = { 0 };
//材质内存空间
unsigned char* datas[3] = { 0 };
int width = 0;
int height = 0;
};
xvideowidget.cpp与实战14中不同的是实战14中将宽与高是提前定死的,而这里根据传进来的数据来设定宽高,并提供了一个新接口,目的是设置宽高,分配材质内存空间,设置放大缩小方式:
void xvideowidget::Init(int width, int height)
{
mux.lock();
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);
mux.unlock();
}
因为创建材质也用到高和宽,所以也放到这个接口中
创建材质显卡空间要用到tex,删除也会删tex,为了防止用到一半被删掉导致程序宕掉,加个锁,其他函数用到data与tex的也要加锁,对于repaint()这种频繁出现的,要注意加锁的代码量,会影响性能,对于init这种只出现一次的无所谓
读与绘制会阻塞,因此使用QThread:
class testtread :public QThread
{
public:
void init()
{
const char* path = "E:\\ffmpeg\\test.mp4";
const char* url = "rtsp://27.22.78.122:8554/1";
cout << "demux.Open = " << demux.Open(path) << endl;
cout << "vdecode.open()" << vdecode.open(demux.CopyVPara()) << endl;
cout << "adecode.open()" << adecode.open(demux.CopyAPara()) << endl;
}
void run()
{
for (;;)
{
AVPacket* pkt = demux.readfz();
if (demux.isvideo(pkt) == true)
{
vdecode.send(pkt);
AVFrame* frame = vdecode.receive();
//cout << "视频解码" <<frame<< endl;
video->Repaint(frame);
Sleep(40);
}
else
{
//adecode.send(pkt);
//AVFrame* frame = adecode.receive();
//cout << "音频解码" <<frame<< endl;
}
if (!pkt)break;
}
}
xdemux demux;
xvideowidget* video;
protected:
xdecode vdecode;
xdecode adecode;
};
因为主线程退出后video被删了,然而该线程还在,所以会出点小问题,后面再解决
main函数:
int main(int argc, char *argv[])
{
testtread tt;
tt.init();
QApplication a(argc, argv);
Xplay2 w;
w.show();
w.ui.openGLWidget->Init(tt.demux.width, tt.demux.height);
tt.video = w.ui.openGLWidget;
tt.start();
return a.exec();
}
1.实例化一个线程,
实例化线程的同时会实例化demux,vdecode,adecode,xvideowidget*,并将解封装后的视频信息传给vdecode,音频信息传给adecode
2.打开一个QT的窗口
3.将解封装得到的宽高信息传给ui窗口里添加的控件的初始化函数,让QT窗口上可以显示一个与我们的媒体文件同样宽高的视频窗口,只不过不能显示画面
tt的demux与video,w的ui都为类外访问,所以要把这些成员开放出去设为public
4.让线程中的指向xvideowidget的指针指向我们添加的控件,调用start即调用线程的run,在run里将解码得到的packet发给openg进行绘制并显示在控件上
void xvideowidget::Repaint(AVFrame* frame)
{
if (!frame)
{
qDebug() << "show failed1" ;
return;
}
mux.lock();
//容错,保证尺寸正确
if (!datas[0] || width * height == 0 || frame->width != this->width || frame->height != this->height)
{
av_frame_free(&frame);
mux.unlock();
qDebug() << "show failed2";
return;
}
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);
//行对齐问题
mux.unlock();
//刷新显示
update();
qDebug()<< "repaint" ;
}
运行情况如下: