代码
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 = ▭
}
// 清空屏幕
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;
}
测试结果
可以成功的渲染。