xvideo_view.h
#pragma once
#include <mutex>
/// <summary>
/// 视频渲染接口
/// 1、隐藏SDL实现
/// 2、渲染方式可替代
/// 3、线程安全
/// </summary>
struct AVFrame;
void MSleep(unsigned int ms);
class XVideoView {
public:
enum Format {
RGBA = 0,
ARGB,
YUV420P
};
// 产品类型
enum RenderType {
SDL = 0
};
// 工厂类
static XVideoView* Creat(RenderType type = SDL);
bool DrawFrame(AVFrame* frame);
// 抽象产品类
virtual bool Init(int w, int h, Format fmt = RGBA, void* win_id = nullptr) = 0;
virtual bool Close() = 0;
virtual bool IsExit() = 0;
virtual bool Draw(const unsigned char* data, int linesize = 0) = 0;
virtual bool Draw(const unsigned char* y, int yPitch, const unsigned char* u, int uPitch, const unsigned char* v, int vPitch) = 0;
//void Scale(int w, int h)
//{
// scale_w_ = w;
// scale_h_ = h;
//}
int render_fps() {
return render_fps_;
}
protected:
int width_ = 0;
int height_ = 0;
Format fmt_ = RGBA;
std::mutex mtx_; //确保线程安全(不在头文件中引入命名空间)
int scale_w_ = 0; //显示大小
int scale_h_ = 0;
int render_fps_ = 0;
long beg_ms_ = 0;
int count_ = 0;
};
xvideo_view.cpp
#include "xvideo_view.h"
#include "xsdl.h"
#include <thread>
using namespace std;
extern "C"
{
#include <libavcodec/avcodec.h>
}
#pragma comment(lib, "avutil.lib")
void MSleep(unsigned int ms)
{
auto beg = clock();
for (int i = 0; i < ms; i++) {
this_thread::sleep_for(1ms);
if ((clock() - beg) >= ms) {
break;
}
}
}
int CountFPS()
{
static int fps = 0;
static int lastTime = clock(); // ms
static int frameCount = 0;
++frameCount;
int currTime = clock();
if (currTime - lastTime > 1000) { // 取固定时间间隔为1秒
fps = frameCount;
frameCount = 0;
lastTime = currTime;
}
return fps;
}
XVideoView* XVideoView::Creat(RenderType type)
{
switch (type) {
case XVideoView::SDL:
return new XSDL();
break;
default:
break;
}
return nullptr;
}
bool XVideoView::DrawFrame(AVFrame* frame)
{
if (!frame || !frame->data[0]) {
return false;
}
render_fps_ = CountFPS(); // 计算帧率
switch (frame->format)
{
case AV_PIX_FMT_YUV420P:
Draw(frame->data[0], frame->linesize[0],// Y
frame->data[1], frame->linesize[1], // U
frame->data[2], frame->linesize[2] // V
);
break;
case AV_PIX_FMT_BGRA:
Draw(frame->data[0], frame->linesize[0]);
break;
default:
break;
}
return true;
}
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;
bool Close() override;
bool IsExit() override;
//
/// 渲染图像 线程安全
///@para data 渲染的二进制数据
///@para linesize 一行数据的字节数,对于YUV420P就是Y一行字节数
/// linesize<=0 就根据宽度和像素格式自动算出大小
/// @return 渲染是否成功
bool Draw(const unsigned char* data, int linesize = 0) override;
bool Draw(const unsigned char* y, int yPitch,
const unsigned char* u, int uPitch,
const unsigned char* v, int vPitch) override;
private:
SDL_Window* win_ = nullptr;
SDL_Renderer* render_ = nullptr;
SDL_Texture* texture_ = nullptr;
};
xsdl.cpp
#include "xsdl.h"
#include <sdl/SDL.h>
#include <iostream>
using namespace std;
#pragma comment(lib,"SDL2.lib")
static bool InitVideo(string& strError)
{
static bool bOK = false;
static mutex mux;
unique_lock<mutex> sdl_lock(mux);
if (!bOK) {
if (SDL_Init(SDL_INIT_VIDEO)) {
strError = SDL_GetError();
return false;
}
}
// 设定缩放算法(线性插值)
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
bOK = true;
return true;
}
bool XSDL::IsExit()
{
SDL_Event ev;
SDL_WaitEventTimeout(&ev, 1);
if (ev.type == SDL_QUIT) {
return true;
}
return false;
}
bool XSDL::Close()
{
//确保线程安全
unique_lock<mutex> sdl_lock(mtx_);
// 依次清理
if (texture_) {
SDL_DestroyTexture(texture_);
texture_ = nullptr;
}
if (render_) {
SDL_DestroyRenderer(render_);
render_ = nullptr;
}
if (win_) {
SDL_DestroyWindow(win_);
win_ = nullptr;
}
return true;
}
bool XSDL::Init(int w, int h, Format fmt, void* win_id)
{
string strError = "";
// 1、初始化视频库
if (!InitVideo(strError)) {
return false;
}
//确保线程安全
unique_lock<mutex> sdl_lock(mtx_);
width_ = w;
height_ = h;
fmt_ = fmt;
if (texture_) {
SDL_DestroyTexture(texture_);
}
if (render_) {
SDL_DestroyRenderer(render_);
}
// 2、创建窗口
if (!win_) {
if (win_id) {
win_ = SDL_CreateWindowFrom(win_id);
if (!win_) {
cout << SDL_GetError() << endl;
return false;
}
}
else {
win_ = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
w, h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
if (!win_) {
cout << SDL_GetError() << endl;
return false;
}
}
}
// 3、创建渲染器
render_ = SDL_CreateRenderer(win_, -1, SDL_RENDERER_ACCELERATED);
if (!render_) {
cout << SDL_GetError() << endl;
return false;
}
// 4、创建材质
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_) {
cout << SDL_GetError() << endl;
return false;
}
return true;
}
bool XSDL::Draw(const unsigned char* y, int yPitch, const unsigned char* u, int uPitch, const unsigned char* v, int vPitch)
{
if (!y || !u || !v) {
cout << "Data Is Empty!" << endl;
return false;
}
unique_lock<mutex> sdl_lock(mtx_);
if (!win_ || !render_ || !texture_ || width_ <= 0 || height_ <= 0) {
cout << "Init Is Fail!" << endl;
return false;
}
// 5、渲染
if (SDL_UpdateYUVTexture(texture_, NULL,
y, yPitch, u, uPitch, v, vPitch)) { // 复制内存数据到显存
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 sdl_rect; // 目标尺寸
sdl_rect.x = 0;
sdl_rect.y = 0;
sdl_rect.w = width_;
sdl_rect.h = height_;
if (SDL_RenderCopy(render_, texture_, NULL, &sdl_rect)) {
cout << SDL_GetError() << endl;
return false;
}
SDL_RenderPresent(render_); // 渲染
return true;
}
bool XSDL::Draw(const unsigned char* data, int linesize)
{
if (!data) {
cout << "Data Is Empty!" << endl;
return false;
}
unique_lock<mutex> sdl_lock(mtx_);
if (!win_ || !render_ || !texture_ || width_ <= 0 || height_ <= 0) {
cout << "Init Is Fail!" << endl;
return false;
}
if (linesize <= 0) {
switch (fmt_)
{
case XVideoView::RGBA:
case XVideoView::ARGB:
linesize = width_ * 4;
break;
case XVideoView::YUV420P:
linesize = width_ * 1;
break;
default:
break;
}
}
// 5、渲染
if (SDL_UpdateTexture(texture_, NULL, data, linesize)) { // 复制到显存
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 sdl_rect; // 目标尺寸
sdl_rect.x = 0;
sdl_rect.y = 0;
sdl_rect.w = width_;
sdl_rect.h = height_;
if (SDL_RenderCopy(render_, texture_, NULL, &sdl_rect)) {
cout << SDL_GetError() << endl;
return false;
}
SDL_RenderPresent(render_); // 渲染
return true;
}
sdlqtrgb.h
#pragma once
#include <QtWidgets/QWidget>
#include "ui_sdlqtrgb.h"
#include <thread>
class SdlQtRgb : public QWidget
{
Q_OBJECT
public:
SdlQtRgb(QWidget* parent = Q_NULLPTR);
~SdlQtRgb()
{
is_exit_ = true;
//等待渲染线程退出
th_.join();
}
void timerEvent(QTimerEvent* ev) override; // 重载定时器
//void resizeEvent(QResizeEvent* ev) override;
void threadMain(); // 线程函数,用于刷新视频
private:
Ui::SdlQtRgbClass ui;
bool is_exit_ = false;
std::thread th_;
signals:
void ViewS();
public slots:
void View();
};
sdlqtrgb.cpp
#include "sdlqtrgb.h"
#include <fstream>
#include <iostream>
#include <QMessageBox>
#include <thread>
#include <sstream>
#include "xvideo_view.h"
extern "C"
{
#include <libavcodec/avcodec.h>
}
using namespace std;
static int sdl_width = 0;
static int sdl_height = 0;
static int pix_size = 2;
static ifstream yuv_file;
static XVideoView* view = nullptr;
static AVFrame* frame = nullptr;
static long long int fileSize = 0;
static QLabel* view_fps = nullptr;
void SdlQtRgb::timerEvent(QTimerEvent* ev)
{
//yuv_file.read((char*)yuv, sdl_width * sdl_height * 1.5);
yuv_file.read((char*)frame->data[0], sdl_width * sdl_height); // Y
yuv_file.read((char*)frame->data[1], sdl_width * sdl_height / 4); // U
yuv_file.read((char*)frame->data[2], sdl_width * sdl_height / 4); // U
if (view->IsExit()) {
view->Close();
exit(0);
}
view->DrawFrame(frame);
}
void SdlQtRgb::View()
{
//yuv_file.read((char*)yuv, sdl_width * sdl_height * 1.5);
yuv_file.read((char*)frame->data[0], sdl_width * sdl_height); // Y
yuv_file.read((char*)frame->data[1], sdl_width * sdl_height / 4); // U
yuv_file.read((char*)frame->data[2], sdl_width * sdl_height / 4); // U
if (fileSize == yuv_file.tellg()) {
yuv_file.seekg(0, ios::beg);
}
if (view->IsExit()) {
view->Close();
exit(0);
}
view->DrawFrame(frame);
stringstream s;
s << "fps:" << view->render_fps();
view_fps->setText(s.str().c_str());
}
void SdlQtRgb::threadMain()
{
while(!is_exit_) {
ViewS();
MSleep(10);
}
}
//void SdlQtRgb::resizeEvent(QResizeEvent* ev)
//{
// ui.label->resize(ui.label->size());
// ui.label->move(0, 0);
// view->Scale(ui.label->width(), ui.label->height());
//}
SdlQtRgb::SdlQtRgb(QWidget* parent)
: QWidget(parent)
{
// 打开yuv文件
string file = "400_300_25.yuv";
yuv_file.open(file.c_str(), ios::binary);
if (!yuv_file) {
QMessageBox::information(this, "", "Open yuv failed!");
return;
}
yuv_file.seekg(0, ios::end); // 移动到文件末尾
fileSize = yuv_file.tellg();
yuv_file.seekg(0, ios::beg);
ui.setupUi(this);
connect(this, SIGNAL(ViewS()), this, SLOT(View()));
view_fps = new QLabel(this);
view_fps->setText("fps:100");
sdl_width = ui.label->width();
sdl_height = ui.label->height();
ui.label->resize(sdl_width, sdl_height);
view = XVideoView::Creat();
view->Init(sdl_width, sdl_height, XVideoView::YUV420P, (void*)ui.label->winId());
// 创建YUV空间
frame = av_frame_alloc();
frame->width = sdl_width;
frame->height = sdl_height;
frame->format = AV_PIX_FMT_YUV420P;
frame->linesize[0] = sdl_width; //Y
frame->linesize[1] = sdl_width / 2; //U
frame->linesize[2] = sdl_width / 2; //V
int err = av_frame_get_buffer(frame, 0);
if (err) {
char* buf = { 0 };
av_strerror(err, buf, sizeof(buf));
cerr << buf << endl;
}
//startTimer(20);
th_ = std::thread(&SdlQtRgb::threadMain, this);
}