• 【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();
    }
    

    五、效果展示

    执行后的效果图如下:

    FFmpeg_XPlayer_A.png

    六、代码下载

    下载链接:https://github.com/confidentFeng/FFmpeg/tree/master/XPlayer/XPlayer_3


  • 相关阅读:
    Git
    canvas画布
    Node.js
    node的consoidate的插件统一
    使用nodejs去做一个验证码
    node的cookie-parser和express-session
    node的router路由。
    node的经典事件监听
    使用node去爬虫
    node的读写流
  • 原文地址:https://www.cnblogs.com/linuxAndMcu/p/14706017.html
Copyright © 2020-2023  润新知