【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


posted @ 2021-04-26 18:52  fengMisaka  阅读(1388)  评论(0编辑  收藏  举报