播放器实战17 xvideowidget

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" ;
}

运行情况如下:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值