使用工厂模式封装SDL接口

代码 

XVideoView.h

#ifndef XVIDEOVIEW_H
#define XVIDEOVIEW_H

#include <mutex>

/*
 * 视频渲染接口类
 * 隐藏SDL实现
 * 渲染方案可替代
 * 线程安全
 */
class XVideoView
{
public:
	enum PixelFormat
	{
		RGBA = 0,
		ARGB,
		YUV420P
	};

	enum ViewType
	{
		VIEW_SDL = 0
	};

	XVideoView();
	static XVideoView* create(ViewType type = VIEW_SDL);

	/*
	 * 初始化渲染窗口 线程安全 支持重复初始化
	 * @param w 窗口宽度
	 * @param h 窗口高度
	 * @param fmt 渲染图像所使用的像素格式
	 * @param winId 窗口的句柄,为空则创建新一个窗口
	 * @return 是否创建成功
	 */
	virtual bool init(int w, int h, PixelFormat fmt = RGBA, void* winId = nullptr) = 0;

	// 清理所有清理的资源,包括关闭窗口
	virtual void close() = 0;

	// 处理窗口退出事件
	virtual bool isExit() = 0;

	/*
	 * 渲染图像 线程安全
	 * @param data 要渲染的二进制数据
	 * @param lineSize 一行数据的字节数,对于 YUV420P 就是Y一行字节数
	 * linesize <= 0 就根据宽度和像素格式自动算出大小
	 * return 0为成功
	 */
	virtual bool draw(const unsigned char* data, int lineSize = 0) = 0;
	void scale(int w, int h);

protected:
	int m_width;				// 材质宽高
	int m_height;
	int m_scaleWid;				// 显示大小
	int m_scaleHgh;
	PixelFormat m_fmt;
	std::mutex m_mtx;			// 保证线程安全
};

#endif

XVideoView.cpp

#include "XVideoView.h"
#include "XSDL.h"

XVideoView::XVideoView()
{
	m_width = 0;
	m_height = 0;
	m_fmt = ARGB;
	m_scaleWid = 0;
	m_scaleHgh = 0;
}

XVideoView* XVideoView::create(ViewType type)
{
	XVideoView* ret = nullptr;

	switch (type)
	{
		case VIEW_SDL:
			ret = new XSDL();  // 使用 SDL 方式进行渲染
			break;
		default:
			break;
	}

	return ret;
}

void XVideoView::scale(int w, int h)
{
	m_scaleWid = w;
	m_scaleHgh = h;
}

XSDL.h

#ifndef XSDL_H
#define XSDL_H

#include "XVideoView.h"

class SDL_Window;
class SDL_Renderer;
class SDL_Texture;

class XSDL : public XVideoView
{
public:
	XSDL();

	/*
	 * 初始化渲染窗口 线程安全
	 * @param w 窗口宽度
	 * @param h 窗口高度
	 * @param fmt 渲染图像所使用的像素格式
	 * @param winId 窗口的句柄,为空则创建新一个窗口
	 * @return 是否创建成功
	 */
	bool init(int w, int h, PixelFormat fmt = RGBA, void* winId = nullptr) override;

	// 清理所有清理的资源,包括关闭窗口
	void close() override;

	// 处理窗口退出事件
	bool isExit() override;

	/*
	 * 渲染图像 线程安全
	 * @param data 要渲染的二进制数据
	 * @param lineSize 一行数据的字节数,对于 YUV420P 就是Y一行字节数
	 * linesize <= 0 就根据宽度和像素格式自动算出大小
	 * return 0为成功
	 */
	bool draw(const unsigned char* data, int lineSize = 0) override;

private:
	bool initVideo();

	SDL_Window* m_win;
	SDL_Renderer* m_render;
	SDL_Texture* m_texture;
};

#endif

XSDL.cpp

#include <iostream>
#include "XSDL.h"
#include "sdl/SDL.h"

#pragma comment(lib, "SDL2.lib")

using namespace std;

XSDL::XSDL()
{
	m_win = nullptr;
	m_render = nullptr;
	m_texture = nullptr;
}

bool XSDL::initVideo()
{
	static bool first_init = false;
	static mutex mtx;

	unique_lock<mutex> sdl_lock(mtx);  // sdl_lock 在创建时自动加锁,生命周期结束时自动解锁,为了解决锁还没释放函数就返回可能导致的死锁问题

	if (!first_init)
	{
		first_init = (SDL_Init(SDL_INIT_VIDEO) == 0);

		if (first_init)
		{
			//设定缩放算法,解决锯齿问题,线性插值算法
			SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
		}
		else
		{
			cerr << SDL_GetError() << endl;
		}
	}

	return first_init;
}

bool XSDL::init(int w, int h, PixelFormat fmt, void* winId)
{
	bool ret = ((w > 0) && (h > 0) && initVideo());

	if (ret)
	{
		close();  // 可能已经初始化,先进行清理操作

		// 确保线程安全
		unique_lock<mutex> sdl_lock(m_mtx);  

		m_width = w;
		m_height = h;
		m_fmt = fmt;

		/* 创建窗口 */
		if (winId)
		{
			// 渲染到控件窗口
			m_win = SDL_CreateWindowFrom(winId);  
		}
		else
		{
			// 新建窗口
			m_win = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
		}

		if (!m_win)
		{
			cerr << SDL_GetError() << endl;
			ret = false;
		}

		/* 创建渲染器 */
		if (ret)
		{
			m_render = SDL_CreateRenderer(m_win, -1, SDL_RENDERER_ACCELERATED);

			if (!m_render)
			{
				cerr << SDL_GetError() << endl;
				ret = false;
			}
		}

		/* 创建材质 (显存) */
		if (ret)
		{
			unsigned int pixFmt = SDL_PIXELFORMAT_ARGB8888;

			switch (fmt)
			{
				case XVideoView::ARGB:
					break;
				case XVideoView::RGBA:
					pixFmt = SDL_PIXELFORMAT_RGBA8888;
					break;
				case XVideoView::YUV420P:
					pixFmt = SDL_PIXELFORMAT_IYUV;
					break;
			}

			m_texture = SDL_CreateTexture(m_render, pixFmt, SDL_TEXTUREACCESS_STREAMING, w, h);  // 这里的 w, h 为分辨率

			if (!m_texture)
			{
				cerr << SDL_GetError() << endl;
				ret = false;
			}
		}	
	}

	return ret;
}

void XSDL::close()
{
	unique_lock<mutex> sdl_lock(m_mtx);

	if (m_texture)
	{
		SDL_DestroyTexture(m_texture);
		m_texture = nullptr;
	}

	if (m_render)
	{
		SDL_DestroyRenderer(m_render);
		m_render = nullptr;
	}

	if (m_win)
	{
		SDL_DestroyWindow(m_win);
		m_win = nullptr;
	}
}

bool XSDL::isExit()
{
	bool ret = false;
	SDL_Event evt;
	SDL_WaitEventTimeout(&evt, 1);

	if (evt.type == SDL_QUIT)
	{
		ret = true;
	}

	return ret;
}

bool XSDL::draw(const unsigned char* data, int lineSize)
{
	unique_lock<mutex> sdl_lock(m_mtx);
	bool ret = (data && (m_width > 0) && (m_height > 0) &&  m_win && m_render && m_texture);

	if (ret)
	{
		if (lineSize <= 0)
		{
			switch (m_fmt)
			{
				case XVideoView::RGBA:
				case XVideoView::ARGB:
					lineSize = m_width * 4;
					break;
				case XVideoView::YUV420P:
					lineSize = m_width;
					break;
				default:
					break;
			}

			if (lineSize <= 0)
			{
				cerr << "Param lineSize is invalid ...\n" << endl;
				ret = false;
			}
		}

		if (ret)
		{
			// 复制内存到显存中
			ret = (SDL_UpdateTexture(m_texture, nullptr, data, lineSize) == 0);

			if (ret)
			{
				SDL_Rect rect;
				SDL_Rect* pr = nullptr;

				rect.x = 0;
				rect.y = 0;
				rect.w = m_scaleWid;  // rect.w, rect.h 为显示的尺寸大小
				rect.h = m_scaleHgh;

				/* 默认渲染尺寸为窗口大小,若用户设置了缩放尺寸,则使用用户设定的尺寸来渲染 */
				if ((m_scaleWid > 0) && (m_scaleHgh > 0))
				{
					pr = &rect;
				}

				// 清空屏幕
				SDL_RenderClear(m_render);

				// 将材质复制到渲染器
				ret = (SDL_RenderCopy(m_render, m_texture, nullptr, pr) == 0);

				if (ret)
				{
					SDL_RenderPresent(m_render);
				}
				else
				{
					cerr << SDL_GetError() << endl;
				}
			}
			else
			{
				cerr << SDL_GetError() << endl;
			}
		}
		
	}

	return ret;
}

测试代码

SdlQtRGB.h

#pragma once

#include <QtWidgets/QWidget>
#include "ui_SdlQtRGB.h"

class SdlQtRGB : public QWidget
{
    Q_OBJECT

public:
    SdlQtRGB(QWidget *parent = Q_NULLPTR);
    ~SdlQtRGB();

private:
    Ui::SdlQtRGBClass ui;

    void timerEvent(QTimerEvent* evt) override;
    void resizeEvent(QResizeEvent* evt) override;
};

SdlQtRGB.cpp

#include "SdlQtRGB.h"
#include "XVideoView.h"
#include <iostream>
#include <fstream>
#include <QMessageBox>

using namespace std;

static unsigned char* yuv = nullptr;
static int sdl_width = 0;
static int sdl_height = 0;
static int pixel_size = 2;
static ifstream yuv_file;
static XVideoView* view = nullptr;

SdlQtRGB::SdlQtRGB(QWidget *parent)
    : QWidget(parent)
{
    yuv_file.open("400_300_25.yuv", ios::in | ios::binary);

    if (!yuv_file)
    {
        QMessageBox::information(this, "information", "open 400_300_25.yuv failed!");

        return;
    }

    ui.setupUi(this);
 
    sdl_width = 400;
    sdl_height = 300;

    ui.label->resize(400, 300);

    view = XVideoView::create();

    if (view)
    {    
        view->init(sdl_width, sdl_height, XVideoView::YUV420P);
        view->init(sdl_width, sdl_height, XVideoView::YUV420P, (void*)ui.label->winId());
    }
    
    yuv = new unsigned char[sdl_width * sdl_height * pixel_size];

    startTimer(10);  // 每过10ms就会调用一次 timerEvent,1s大约有100帧的图像
}

void SdlQtRGB::timerEvent(QTimerEvent* evt)
{
    yuv_file.read((char*)yuv, sdl_width * sdl_height * 1.5);  // 读取一帧数据

    if (view)
    {
        view->draw(yuv);

        if (view->isExit())
        {
            view->close();
            delete view;

            exit(0);
        }
    }
}

void SdlQtRGB::resizeEvent(QResizeEvent* evt)
{
    ui.label->resize(size());
    ui.label->move(0, 0);
    // view->scale(width(), height());
}

SdlQtRGB::~SdlQtRGB()
{
    delete[] yuv;
}

测试结果

 可以成功的渲染。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值