SDL工厂模式渲染yuv视频及其窗口缩放和抗锯齿(代码超全,复制直接运行)

4 篇文章 0 订阅

0.前要

项目简介

这个项目实现了在 Qt 框架下播放 YUV 视频文件的功能。它结合了 Qt 和 SDL 技术。具体而言,项目中包含了以下功能和技术:

  1. 播放 YUV 视频文件:通过读取 YUV 格式的视频文件,在 Qt 界面中播放该视频。

  2. Qt 框架:项目使用了 Qt 框架来构建用户界面和窗口,实现了图形界面的显示和交互。

  3. SDL 技术:项目中使用了 SDL(Simple DirectMedia Layer)库来进行视频渲染。SDL 提供了跨平台的多媒体处理功能,包括图形、音频和输入设备处理。

  4. 工厂模式:使用了工厂模式。在头文件 "xvideo_view.h" 中定义了一个抽象基类 XVideoView,它具有一个静态方法 Create,用于创建具体的 XVideoView 子类对象。而在源文件 "xsdl.cpp" 中实现了这个 Create 方法,根据传入的参数 type,决定创建哪个具体的子类对象(这里是 XSDL 对象)。这种方式可以让用户在不了解具体实现细节的情况下,通过抽象基类来创建对象,提供了一种方便的对象创建方式,属于工厂模式的一种实现方式。

总体而言,该项目提供了一个基于 Qt 框架的简单 YUV 视频播放器,利用 SDL 实现视频渲染,通过工厂模式管理不同的渲染器对象,实现了跨平台的视频播放功能。

整个代码流程:

  1. 主函数入口 (main):

    • 创建了一个 QApplication 对象 a
    • 创建了一个 SdlQtRGB 对象 w
    • 调用 w.show() 方法显示窗口。
    • 进入 Qt 的事件循环,等待用户交互和系统事件。
  2. SdlQtRGB 类:

    • 构造函数:
      • 打开一个 YUV 视频文件。
      • 初始化窗口大小和界面。(Init函数)
      • 创建一个 XVideoView 对象 view,用于视频渲染。
      • 启动定时器,定时读取视频数据并渲染。
    • 定时器事件处理函数 (timerEvent):
      • 从 YUV 文件中读取数据。
      • 调用 view->Draw(yuv) 方法将读取的视频数据渲染到窗口上。
    • 窗口大小变化事件处理函数 (resizeEvent):
      • 调整视频渲染区域的大小,确保与窗口大小相匹配。
  3. 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 的枚举类型,包含了三个枚举值:RGBAARGBYUV420P,分别表示不同的像素格式。

  • 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_firstfalse,说明已经执行过初始化,直接返回 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();
}

2.运行结果

  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值