0.前要
项目简介
这个项目实现了在 Qt 框架下播放 YUV 视频文件的功能。它结合了 Qt 和 SDL 技术。具体而言,项目中包含了以下功能和技术:
-
播放 YUV 视频文件:通过读取 YUV 格式的视频文件,在 Qt 界面中播放该视频。
-
Qt 框架:项目使用了 Qt 框架来构建用户界面和窗口,实现了图形界面的显示和交互。
-
SDL 技术:项目中使用了 SDL(Simple DirectMedia Layer)库来进行视频渲染。SDL 提供了跨平台的多媒体处理功能,包括图形、音频和输入设备处理。
-
工厂模式:使用了工厂模式。在头文件 "xvideo_view.h" 中定义了一个抽象基类 XVideoView,它具有一个静态方法 Create,用于创建具体的 XVideoView 子类对象。而在源文件 "xsdl.cpp" 中实现了这个 Create 方法,根据传入的参数 type,决定创建哪个具体的子类对象(这里是 XSDL 对象)。这种方式可以让用户在不了解具体实现细节的情况下,通过抽象基类来创建对象,提供了一种方便的对象创建方式,属于工厂模式的一种实现方式。
总体而言,该项目提供了一个基于 Qt 框架的简单 YUV 视频播放器,利用 SDL 实现视频渲染,通过工厂模式管理不同的渲染器对象,实现了跨平台的视频播放功能。
整个代码流程:
-
主函数入口 (
main
):- 创建了一个
QApplication
对象a
。 - 创建了一个
SdlQtRGB
对象w
。 - 调用
w.show()
方法显示窗口。 - 进入 Qt 的事件循环,等待用户交互和系统事件。
- 创建了一个
-
SdlQtRGB 类:
- 构造函数:
- 打开一个 YUV 视频文件。
- 初始化窗口大小和界面。(Init函数)
- 创建一个
XVideoView
对象view
,用于视频渲染。 - 启动定时器,定时读取视频数据并渲染。
- 定时器事件处理函数 (
timerEvent
):- 从 YUV 文件中读取数据。
- 调用
view->Draw(yuv)
方法将读取的视频数据渲染到窗口上。
- 窗口大小变化事件处理函数 (
resizeEvent
):- 调整视频渲染区域的大小,确保与窗口大小相匹配。
- 构造函数:
-
XVideoView 类及其派生类:
XVideoView
类是一个视频渲染接口类,定义了视频渲染的基本操作,如初始化窗口、渲染图像等。XSDL
类是XVideoView
的一个具体实现,使用 SDL 库进行视频渲染。XVideoView::Create(RenderType type)
方法根据指定的渲染类型创建相应的XVideoView
子类对象。XSDL::Init
方法用于初始化 SDL 窗口和渲染器,并创建渲染材质。XSDL::Draw
方法用于将视频数据渲染到 SDL 窗口上。
整个项目的执行流程是:主函数创建了一个 Qt 窗口,该窗口由 SdlQtRGB
类处理。SdlQtRGB
类中创建了一个 XVideoView
对象,用于视频渲染。定时器定期从 YUV 文件中读取视频数据,并调用 view->Draw()
方法进行渲染。 XVideoView
类及其派生类负责视频渲染的具体实现,其中 XSDL
类使用 SDL 库进行视频渲染。
1.代码
1.1 头文件
sdlqtrgb.h
#pragma once
#include <QtWidgets/QWidget>
#include "ui_sdlqtrgb.h"
class SdlQtRGB : public QWidget
{
Q_OBJECT
public:
SdlQtRGB(QWidget *parent = Q_NULLPTR);
void timerEvent(QTimerEvent* ev) override;
void resizeEvent(QResizeEvent* ev) override;//重新改写这个接口函数 再cpp中实现就行
private:
Ui::SdlQtRGBClass ui;
};
解析:
-
void timerEvent(QTimerEvent* ev) override;
:这是一个重写了基类QWidget
的虚函数timerEvent
,用于处理定时器事件。override
关键字表示该函数是对基类虚函数的重写。 -
void resizeEvent(QResizeEvent* ev) override;
:这是一个重写了基类QWidget
的虚函数resizeEvent
,用于处理窗口大小改变事件。override
关键字表示该函数是对基类虚函数的重写。
xvideo_view.h
#ifndef XVIDEO_VIEW_H
#define XVIDEO_VIEW_H
#include <mutex>
/// 视频渲染接口类
/// 隐藏SDL实现
/// 渲染方案可替代
// 线程安全
class XVideoView
{
public:
enum Format
{
RGBA = 0,
ARGB,
YUV420P
};
enum RenderType
{
SDL = 0
};
static XVideoView* Create(RenderType type=SDL);
/// 初始化渲染窗口 线程安全
/// @para w 窗口宽度
/// @para h 窗口高度
/// @para fmt 绘制的像素格式
/// @para win_id 窗口句柄,如果为空,创建新窗口
/// @return 是否创建成功
virtual bool Init(int w, int h,
Format fmt = RGBA,
void* win_id = nullptr) = 0;
/// 渲染图像 线程安全
///@para data 渲染的二进制数据
///@para linesize 一行数据的字节数,对于YUV420P就是Y一行字节数
/// linesize<=0 就根据宽度和像素格式自动算出大小
/// @return 渲染是否成功
virtual bool Draw(const unsigned char* data, int linesize = 0) = 0;
//窗口显示缩放
void Scale(int w, int h)
{
scale_w_ = w;
scale_h_ = h;
}
protected:
int width_ = 0; //窗口和材质宽高
int height_ = 0;
Format fmt_ = RGBA; //像素格式
std::mutex mtx_; //确保线程安全
int scale_w_ = 0;//实际显示大小
int scale_h_ = 0;
};
#endif
解析:
-
enum Format
:这行代码定义了一个名为Format
的枚举类型,包含了三个枚举值:RGBA
、ARGB
和YUV420P
,分别表示不同的像素格式。 -
enum RenderType
:这行代码定义了一个名为RenderType
的枚举类型,包含了一个枚举值SDL
,表示渲染器的类型为 SDL,还有OpenGL等,暂时不用
xsdl.h
#pragma once
#include "xvideo_view.h"
struct SDL_Window;
struct SDL_Renderer;
struct SDL_Texture;
class XSDL :public XVideoView
{
public:
/// 初始化渲染窗口 线程安全
/// @para w 窗口宽度
/// @para h 窗口高度
/// @para fmt 绘制的像素格式
/// @para win_id 窗口句柄,如果为空,创建新窗口
/// @return 是否创建成功
bool Init(int w, int h,
Format fmt = RGBA,
void* win_id = nullptr) override;
//
/// 渲染图像 线程安全
///@para data 渲染的二进制数据
///@para linesize 一行数据的字节数,对于YUV420P就是Y一行字节数
/// linesize<=0 就根据宽度和像素格式自动算出大小
/// @return 渲染是否成功
bool Draw(const unsigned char* data,
int linesize = 0) override;
private:
SDL_Window* win_ = nullptr;
SDL_Renderer* render_ = nullptr;
SDL_Texture* texture_ = nullptr;
};
解析:
-
bool Init(int w, int h, Format fmt = RGBA, void* win_id = nullptr) override;
:这行代码声明了一个成员函数Init
,用于初始化渲染窗口。它接受窗口的宽度w
、高度h
、像素格式fmt
和窗口句柄win_id
作为参数,返回一个布尔值,表示是否初始化成功。override
关键字表示这个函数是对基类的虚函数进行重写。 -
bool Draw(const unsigned char* data, int linesize = 0) override;
:这行代码声明了一个成员函数Draw
,用于渲染图像。它接受渲染的二进制数据data
和一行数据的字节数linesize
作为参数,返回一个布尔值,表示渲染是否成功。override
关键字表示这个函数是对基类的虚函数进行重写。
1.2 源文件
sdlqtrgb.cpp
#include "sdlqtrgb.h"
#include <fstream>
#include <QtWidgets/QMessageBox>
#include "xvideo_view.h"
using namespace std;
//static SDL_Window* sdl_win = NULL;
//static SDL_Renderer* sdl_render = NULL;
//static SDL_Texture* sdl_texture = NULL;
static int sdl_width = 0;
static int sdl_height = 0;
static unsigned char* yuv = NULL;
static int pix_size = 2;
static ifstream yuv_file;
static XVideoView* view = nullptr;
void SdlQtRGB::timerEvent(QTimerEvent* ev)
{
yuv_file.read((char*)yuv, sdl_width * sdl_height * 1.5);
view->Draw(yuv);
}
SdlQtRGB::SdlQtRGB(QWidget *parent)
: QWidget(parent)
{
//打开yuv文件
yuv_file.open("D:\\lesson\\code\\bin\\x86\\400_300_25.yuv", ios::binary);
if (!yuv_file)
{
QMessageBox::information(this, "", "open yuv failed!");
return;
}
ui.setupUi(this);
sdl_width = 400;
sdl_height = 300;
ui.label->resize(sdl_width, sdl_height);
view = XVideoView::Create();
view->Init(sdl_width, sdl_height,
XVideoView::YUV420P,(void*)ui.label->winId());//指定到QT的控件label窗口
yuv = new unsigned char[sdl_width * sdl_height * pix_size];
startTimer(10);
}
void SdlQtRGB::resizeEvent(QResizeEvent* ev) {//调用的时候 设置label的大小随窗口大小一起变
ui.label->resize(size());
ui.label->move(0, 0);
view->Scale(width(), height());
}
解析:
ui.label->resize(sdl_width, sdl_height);
:调整ui.label
控件的大小为指定的宽度和高度。view = XVideoView::Create();
:调用静态函数XVideoView::Create()
创建一个视频渲染器对象,并将其赋值给view
指针。view->Init(sdl_width, sdl_height, XVideoView::YUV420P, (void*)ui.label->winId());
:调用视频渲染器对象的Init
方法,对其进行初始化。yuv = new unsigned char[sdl_width * sdl_height * pix_size];
:动态分配内存,将分配的内存地址赋值给yuv
指针。void SdlQtRGB::resizeEvent(QResizeEvent* ev)
:定义了SdlQtRGB
类的成员函数resizeEvent
,用于处理窗口大小改变事件。ui.label->resize(size());
:将ui.label
控件的大小调整为窗口大小。ui.label->move(0, 0);
:将ui.label
控件移动到窗口的左上角。view->Scale(width(), height())
的作用是将窗口的大小信息传递给视频渲染器,用于调整视频渲染的显示大小或者其他相关操作。
xvideo_view.cpp
#include "xsdl.h"
XVideoView* XVideoView::Create(RenderType type)
{
switch (type)
{
case XVideoView::SDL:
return new XSDL();
break;
default:
break;
}
return nullptr;
}
解析:
当调用 new XSDL()
时,会在堆内存中分配一块内存空间,用来存储 XSDL
类的对象,并返回这个对象的指针。这样,就创建了一个 XSDL
类的对象。
在 C++ 中,new
操作符用于动态分配内存,创建对象,并返回对象的指针。XSDL()
调用了 XSDL
类的构造函数来初始化这个对象。因此,return new XSDL();
表示返回一个指向新创建的 XSDL
类对象的指针。
xsdl.cpp
#include "xsdl.h"
#include <sdl/SDL.h>
#include <iostream>
using namespace std;
#pragma comment(lib,"SDL2.lib")
static bool InitVideo()
{
static bool is_first = true;
static mutex mux;
unique_lock<mutex> sdl_lock(mux);
if (!is_first)return true;
is_first = false;
if (SDL_Init(SDL_INIT_VIDEO))
{
cout << SDL_GetError() << endl;
return false;
}
//抗锯齿
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
return true;
}
bool XSDL::Init(int w, int h,Format fmt,void* win_id)
{
if (w <= 0 || h <= 0)return false;
//初始化SDL 视频库
InitVideo();
//确保线程安全
unique_lock<mutex> sdl_lock(mtx_);
width_ = w;
height_ = h;
fmt_ = fmt;
///1 创建窗口
if (!win_)
{
if (!win_id)
{
//新建窗口
win_ = SDL_CreateWindow("",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
w, h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE
);
}
else
{
//渲染到控件窗口
win_ = SDL_CreateWindowFrom(win_id);
}
}
if (!win_)
{
cerr << SDL_GetError() << endl;
return false;
}
/// 2 创建渲染器
render_ = SDL_CreateRenderer(win_, -1, SDL_RENDERER_ACCELERATED);
if (!render_)
{
cerr << SDL_GetError() << endl;
return false;
}
//创建材质 (显存)
unsigned int sdl_fmt = SDL_PIXELFORMAT_RGBA8888;
switch (fmt)
{
case XVideoView::RGBA:
break;
case XVideoView::ARGB:
sdl_fmt = SDL_PIXELFORMAT_ARGB32;
break;
case XVideoView::YUV420P:
sdl_fmt = SDL_PIXELFORMAT_IYUV;
break;
default:
break;
}
texture_ = SDL_CreateTexture(render_,
sdl_fmt, //像素格式
SDL_TEXTUREACCESS_STREAMING, //频繁修改的渲染(带锁)
w, h //材质大小
);
if (!texture_)
{
cerr << SDL_GetError() << endl;
return false;
}
return true;
}
bool XSDL::Draw(const unsigned char* data,int linesize)//渲染函数 将纹理到材质上的数据放到窗口上渲染 播放视频
{
if (!data) return false;
unique_lock<mutex> sdl_lock(mtx_);
if (!texture_ || !render_ || !win_ || width_ <= 0 || height_ <= 0)
return false;
if (linesize <= 0) {
switch (fmt_)
{
case XVideoView::ARGB:
case XVideoView::RGBA:
linesize = width_ * 4;
break;
case XVideoView::YUV420P:
linesize = width_;
break;
default:
break;
}
}
if (linesize <= 0)
return false;
//复制内存到显存 把材质复制过去
auto re = SDL_UpdateTexture(texture_, NULL, data, linesize);//返回0表示成功
if (re != 0) {
cout << SDL_GetError() << endl;
return false;
}
//清空屏幕
SDL_RenderClear(render_);
//材质复制到渲染器
if (scale_w_ <= 0) scale_w_ = width_;
if (scale_h_ <= 0) scale_h_ = height_;
SDL_Rect rect;
rect.x = 0; rect.y = 0;
rect.w = scale_w_;//渲染的宽高 可缩放
rect.h = scale_h_;
re = SDL_RenderCopy(render_, texture_, NULL, &rect);
if (re != 0) {
cout << SDL_GetError() << endl;
return false;
}
SDL_RenderPresent(render_);
return true;
}
解析:
static bool InitVideo()
:定义了一个静态函数InitVideo()
,返回类型为bool
。static bool is_first = true;
:定义了一个静态变量is_first
,并初始化为true
。这个变量用于确保初始化只执行一次。static mutex mux;
:定义了一个静态的互斥量对象mux
,用于确保线程安全。unique_lock<mutex> sdl_lock(mux);
:创建了一个unique_lock
对象sdl_lock
,并将mux
互斥量锁住。这个锁在函数作用域内自动释放,用于保护SDL_Init()
函数调用,确保只有一个线程执行初始化。if (!is_first)return true;
:如果is_first
为false
,说明已经执行过初始化,直接返回true
。is_first = false;
:将is_first
设置为false
,表示已经执行过初始化。if (SDL_Init(SDL_INIT_VIDEO))
:调用SDL_Init()
函数初始化 SDL 视频库,如果初始化失败,则执行下面的代码。返回0表示成功初始化InitVideo();
:调用了前面定义的静态函数InitVideo()
,用于初始化 SDL 视频库。- Init()主要实现了
XSDL
类的初始化函数Init()
,其中包括初始化 SDL 库、创建窗口、创建渲染器和创建纹理等操作。
draw函数
auto re = SDL_UpdateTexture(texture_, NULL, data, linesize);
:使用SDL_UpdateTexture()
函数将数据更新到纹理上,返回值re
表示更新操作的结果。SDL_RenderClear(render_);
:清空渲染器。if (scale_w_ <= 0) scale_w_ = width_;
和if (scale_h_ <= 0) scale_h_ = height_;
:如果未指定渲染的宽度和高度,则使用窗口的宽度和高度。SDL_Rect rect; rect.x = 0; rect.y = 0; rect.w = scale_w_; rect.h = scale_h_;
:创建一个SDL_Rect
结构体变量rect
,用于指定渲染的矩形区域。re = SDL_RenderCopy(render_, texture_, NULL, &rect);
:将纹理复制到渲染器中,指定渲染区域为rect
所指定的矩形。if (re != 0)
:如果复制操作失败,则输出错误信息,并返回false
。SDL_RenderPresent(render_);
:更新渲染器的显示。
1.3 主函数
main.cpp
#include "sdlqtrgb.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
SdlQtRGB w;
w.show();
return a.exec();
}