渲染接口设计说明
界面端说明
代码
XVideoView.h
#ifndef XVIDEOVIEW_H
#define XVIDEOVIEW_H
#include <mutex>
#include <fstream>
struct AVFrame;
void MSleep(int ms);
long long NowMs();
/*
* 视频渲染接口类
* 隐藏SDL实现
* 渲染方案可替代
* 线程安全
*/
class XVideoView
{
public:
// 和 ffmpeg 中的像素格式的值保持一致
enum PixelFormat
{
YUV420P = 0,
ARGB = 25,
RGBA = 26,
BGRA = 28,
};
enum ViewType
{
VIEW_SDL = 0
};
XVideoView();
static XVideoView* create(ViewType type = VIEW_SDL);
/*
* 初始化渲染窗口 线程安全 支持重复初始化
* @param w 窗口宽度
* @param h 窗口高度
* @param fmt 渲染图像所使用的像素格式
* @return 是否创建成功
*/
virtual bool init(int w, int h, PixelFormat fmt = RGBA) = 0;
void setWinId(void* winId = nullptr);
// 清理所有需要清理的资源,包括关闭窗口
virtual void close() = 0;
// 处理窗口退出事件
virtual bool isExit() = 0;
// 打开文件
bool open(std::string filePath);
// 读取一帧数据
AVFrame* read();
bool drawAVFrame(AVFrame* frame);
/*
* 渲染图像 线程安全
* @param data 要渲染的二进制数据
* @param lineSize 一行数据的字节数,对于 YUV420P 就是Y一行字节数
* linesize <= 0 就根据宽度和像素格式自动算出大小
* return 0为成功
*/
virtual bool draw(const unsigned char* data, int lineSize = 0) = 0;
virtual bool draw(const unsigned char* Yplane, int Ypitch,
const unsigned char* Uplane, int Upitch,
const unsigned char* Vplane, int Vpitch) = 0;
void scale(int w, int h);
int renderFps();
protected:
void* m_winId; // 窗口句柄
int m_width; // 材质宽高
int m_height;
int m_scaleWid; // 显示大小
int m_scaleHgh;
PixelFormat m_fmt;
std::mutex m_mtx; // 保证线程安全
long long m_beginMs = 0; // 起始时间
int m_count = 0; // 一秒的帧数
int m_renderFps; // 显示帧率
private:
std::ifstream m_ifs;
AVFrame* m_frame;
};
#endif
XVideoView.cpp
#include <thread>
#include <iostream>
#include "XVideoView.h"
#include "XSDL.h"
extern "C"
{
#include "libavcodec/avcodec.h"
}
using namespace std;
#pragma comment (lib, "avutil.lib")
void MSleep(int ms)
{
auto beg = clock();
for (int i = 0; i < ms; i++)
{
this_thread::sleep_for(1ms);
if ((clock() - beg) / (CLOCKS_PER_SEC / 1000) >= ms)
{
break;
}
}
}
long long NowMs()
{
return (clock() / (CLOCKS_PER_SEC / 1000));
}
XVideoView::XVideoView()
{
m_winId = nullptr;
m_width = 0;
m_height = 0;
m_fmt = ARGB;
m_scaleWid = 0;
m_scaleHgh = 0;
m_beginMs = 0;
m_count = 0;
m_renderFps = 0;
m_frame = nullptr;
}
void XVideoView::setWinId(void* winId)
{
m_winId = winId;
}
XVideoView* XVideoView::create(ViewType type)
{
XVideoView* ret = nullptr;
switch (type)
{
case VIEW_SDL:
ret = new XSDL(); // 使用 SDL 方式进行渲染
break;
default:
break;
}
return ret;
}
bool XVideoView::open(std::string filePath)
{
bool ret = true;
if (m_ifs.is_open())
{
m_ifs.close();
}
m_ifs.open(filePath, ios::in | ios::binary);
ret = m_ifs.is_open();
return ret;
}
AVFrame* XVideoView::read()
{
AVFrame* ret = nullptr;
int r = 0;
if (m_ifs.is_open() && (m_width > 0) && (m_height > 0))
{
if (m_frame != nullptr)
{
/* 需要重新创建 frame */
if ((m_frame->width != m_width) || (m_frame->height != m_height) || (m_frame->format != m_fmt))
{
av_frame_free(&m_frame);
}
else
{
ret = m_frame;
}
}
if (m_frame == nullptr)
{
m_frame = av_frame_alloc();
m_frame->width = m_width;
m_frame->height = m_height;
m_frame->format = m_fmt;
if (m_fmt == PixelFormat::YUV420P)
{
m_frame->linesize[0] = m_width;
m_frame->linesize[1] = m_width / 2;
m_frame->linesize[2] = m_width / 2;
}
else
{
m_frame->linesize[0] = m_width * 4;
}
r = av_frame_get_buffer(m_frame, 0);
if (r == 0)
{
ret = m_frame;
}
else
{
char buf[1024] = { 0 };
av_strerror(r, buf, sizeof(buf) - 1);
cerr << buf << endl;
}
}
if (ret != nullptr)
{
if (m_fmt == PixelFormat::YUV420P)
{
m_ifs.read((char*)m_frame->data[0], m_frame->width * m_frame->height);
m_ifs.read((char*)m_frame->data[1], m_frame->width * m_frame->height / 4);
m_ifs.read((char*)m_frame->data[2], m_frame->width * m_frame->height / 4);
}
else
{
m_ifs.read((char*)m_frame->data[0], m_width * m_height * 4);
}
if(m_ifs.gcount() == 0)
{
ret = nullptr;
}
}
}
return ret;
}
bool XVideoView::drawAVFrame(AVFrame* frame)
{
bool ret = (frame != nullptr);
if (ret)
{
m_count++;
if (m_beginMs == 0)
{
m_beginMs = NowMs(); // 开始计时
}
else if ((NowMs() - m_beginMs >= 1000)) // 1s 刷新了多少帧
{
m_renderFps = m_count;
m_count = 0;
m_beginMs = NowMs();
}
switch (frame->format)
{
case AV_PIX_FMT_ARGB:
case AV_PIX_FMT_RGBA:
case AV_PIX_FMT_BGRA:
ret = draw(frame->data[0], frame->linesize[0]);
break;
case AV_PIX_FMT_YUV420P:
ret = draw(frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2]);
break;
default:
ret = false;
break;
}
}
return ret;
}
void XVideoView::scale(int w, int h)
{
m_scaleWid = w;
m_scaleHgh = h;
}
int XVideoView::renderFps()
{
return m_renderFps;
}
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) 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;
bool draw(const unsigned char* Yplane, int Ypitch,
const unsigned char* Uplane, int Upitch,
const unsigned char* Vplane, int Vpitch) 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)
{
bool ret = ((w > 0) && (h > 0) && initVideo());
if (ret)
{
// 确保线程安全
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;
}
m_width = w;
m_height = h;
m_fmt = fmt;
if (m_win == nullptr)
{
/* 创建窗口 */
if (m_winId)
{
// 渲染到控件窗口
m_win = SDL_CreateWindowFrom(m_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_ARGB32;
switch (fmt)
{
case XVideoView::ARGB:
break;
case XVideoView::RGBA:
pixFmt = SDL_PIXELFORMAT_RGBA32;
break;
case XVideoView::BGRA:
pixFmt = SDL_PIXELFORMAT_BGRA32;
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:
case XVideoView::BGRA:
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;
}
bool XSDL::draw(const unsigned char* Yplane, int Ypitch,
const unsigned char* Uplane, int Upitch,
const unsigned char* Vplane, int Vpitch)
{
bool ret = (Yplane && (Ypitch > 0) && Uplane && (Upitch > 0) && Vplane && (Vpitch > 0) && (m_width > 0) && (m_height > 0) && m_win && m_render && m_texture);
if (ret)
{
// 复制内存到显存中
ret = (SDL_UpdateYUVTexture(m_texture, nullptr, Yplane, Ypitch, Uplane, Upitch, Vplane, Vpitch) == 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 <thread>
#include "ui_SdlQtRGB.h"
class SdlQtRGB : public QWidget
{
Q_OBJECT
public:
SdlQtRGB(QWidget *parent = Q_NULLPTR);
void open(int i);
~SdlQtRGB();
signals:
void viewSignal();
public slots:
void open1();
void open2();
void viewChanged();
private:
Ui::SdlQtRGBClass ui;
std::thread m_th;
bool m_isExit;
void resizeEvent(QResizeEvent* evt) override;
void thMain();
};
SdlQtRGB.cpp
#include "SdlQtRGB.h"
#include "XVideoView.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <QMessageBox>
#include <QSpinBox>
#include <QFileDialog>
#include <vector>
extern "C"
{
#include "libavcodec/avcodec.h"
}
using namespace std;
static vector<XVideoView*> views;
SdlQtRGB::SdlQtRGB(QWidget *parent)
: QWidget(parent), m_isExit(false)
{
int r = 0;
ui.setupUi(this);
connect(this, &SdlQtRGB::viewSignal, this, &SdlQtRGB::viewChanged); // 绑定渲染信号槽
// ui.video1->resize(ui.width1->value(), ui.height1->value());
// ui.video2->resize(ui.width2->value(), ui.height2->value());
/* 2路渲染 */
views.push_back(XVideoView::create());
views.push_back(XVideoView::create());
views[0]->setWinId((void*)ui.video1->winId());
views[1]->setWinId((void*)ui.video2->winId());
m_th = std::thread(&SdlQtRGB::thMain, this); // 子线程去渲染
}
void SdlQtRGB::open(int i)
{
QString file_path = QFileDialog::getOpenFileName();
int w = 0;
int h = 0;
QString pix = "";
XVideoView::PixelFormat fmt = XVideoView::YUV420P;
if (!file_path.isEmpty())
{
/* 打开文件 */
if (views[i]->open(file_path.toStdString()))
{
if (i == 0)
{
w = ui.width1->value();
h = ui.height1->value();
pix = ui.pixfmt1->currentText();
}
else if (i == 1)
{
w = ui.width2->value();
h = ui.height2->value();
pix = ui.pixfmt2->currentText();
}
if (pix == "RGBA")
{
fmt = XVideoView::RGBA;
}
else if (pix == "ARGB")
{
fmt = XVideoView::ARGB;
}
else if (pix == "BGRA")
{
fmt = XVideoView::BGRA;
}
/* 创建材质和窗口 */
views[i]->init(w, h, fmt);
}
else
{
QMessageBox::critical(this, "open file error", "Can not open file!");
}
}
}
void SdlQtRGB::open1()
{
open(0);
}
void SdlQtRGB::open2()
{
open(1);
}
void SdlQtRGB::thMain()
{
while (!m_isExit)
{
viewSignal(); // 触发信号去渲染
MSleep(1);
}
}
void SdlQtRGB::viewChanged()
{
static int fps[32] = { 0 }; // 实际的帧率
static int last_pst[32] = { 0 }; // 上一次渲染的时间,单位为 ms
for (int i = 0; i < views.size(); i++)
{
AVFrame* frame = nullptr;
stringstream ss;
ss << "fps: " << views[i]->renderFps();
/* 显示实际的帧率并且获取用户设置的帧率 */
if (i == 0)
{
ui.show_fps1->setText(ss.str().c_str());
fps[0] = ui.set_fps1->value();
}
else if (i == 1)
{
ui.show_fps2->setText(ss.str().c_str());
fps[1] = ui.set_fps2->value();
}
if ((NowMs() - last_pst[i]) >= (1000 / fps[i])) // 到了需要渲染的时间
{
last_pst[i] = NowMs();
frame = views[i]->read();
if (frame != nullptr)
{
views[i]->drawAVFrame(frame);
}
}
}
}
void SdlQtRGB::resizeEvent(QResizeEvent* evt)
{
}
SdlQtRGB::~SdlQtRGB()
{
m_isExit = true;
m_th.join();
}