代码
XVideoView.h
#ifndef XVIDEOVIEW_H
#define XVIDEOVIEW_H
#include <mutex>
struct AVFrame;
void MSleep(int ms);
/*
* 视频渲染接口类
* 隐藏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;
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:
int m_width; // 材质宽高
int m_height;
int m_scaleWid; // 显示大小
int m_scaleHgh;
PixelFormat m_fmt;
std::mutex m_mtx; // 保证线程安全
int m_renderFps; // 显示帧率
long long m_beginMs; // 起始时间
int m_count; // 一秒的帧数
};
#endif
XVideoView.cpp
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;
}
}
}
bool XVideoView::drawAVFrame(AVFrame* frame)
{
bool ret = (frame != nullptr);
if (ret)
{
m_count++;
if (m_beginMs == 0)
{
m_beginMs = clock(); // 开始计时
}
else if (((clock() - m_beginMs) / (CLOCKS_PER_SEC / 1000)) >= 1000) // 1s
{
m_renderFps = m_count;
m_count = 0;
m_beginMs = clock();
}
switch (frame->format)
{
case AV_PIX_FMT_ARGB:
case AV_PIX_FMT_RGBA:
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;
}
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);
~SdlQtRGB();
signals:
void viewSignal();
public slots:
void viewChanged();
private:
Ui::SdlQtRGBClass ui;
std::thread m_th;
bool 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>
extern "C"
{
#include "libavcodec/avcodec.h"
}
using namespace std;
static AVFrame* frame = nullptr;;
static int sdl_width = 0;
static int sdl_height = 0;
static int pixel_size = 2;
static ifstream yuv_file;
static long long file_size = 0;
static XVideoView* view = nullptr;
static QLabel* fps_lbl = nullptr; // 此控件用于显示播放帧率
static QSpinBox* fps_sbx = nullptr; // 此控件用于调整播放帧率
static int fps = 25; // 播放帧率
SdlQtRGB::SdlQtRGB(QWidget *parent)
: QWidget(parent), isExit(false)
{
int r = 0;
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;
}
yuv_file.seekg(0, ios::end);
file_size = yuv_file.tellg();
yuv_file.seekg(0, ios::beg);
ui.setupUi(this);
fps_lbl = new QLabel(this);
fps_lbl->setText("fps: ");
fps_sbx = new QSpinBox(this);
fps_sbx->move(200, 0);
fps_sbx->setValue(25);
fps_sbx->setRange(1, 300);
connect(this, &SdlQtRGB::viewSignal, this, &SdlQtRGB::viewChanged); // 绑定渲染信号槽
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());
}
frame = av_frame_alloc();
frame->width = 400;
frame->height = 300;
frame->format = AV_PIX_FMT_YUV420P;
// Y Y
// UV
// Y Y
frame->linesize[0] = sdl_width; // Y
frame->linesize[1] = sdl_width / 2; // U
frame->linesize[2] = sdl_width / 2; // V
r = av_frame_get_buffer(frame, 0);
if (r != 0)
{
char buf[1024] = {0};
av_strerror(r, buf, sizeof(buf));
cerr << buf << endl;
}
m_th = std::thread(&SdlQtRGB::thMain, this); // 子线程去渲染
}
void SdlQtRGB::thMain()
{
while (!isExit)
{
viewSignal(); // 触发信号去渲染
if (fps > 0)
{
MSleep(1000 / fps);
}
else
{
MSleep(10);
}
}
}
void SdlQtRGB::viewChanged()
{
stringstream ss;
/* AVFrame 中 YUV 是单独存放的,所以需要分开进行读取 */
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); // V
/* 循环播放 */
if (yuv_file.tellg() == file_size)
{
yuv_file.seekg(0, ios::beg);
}
if (view)
{
ss << "fps: " << view->renderFps();
fps_lbl->setText(ss.str().c_str()); // 显示帧率
fps = fps_sbx->value();
view->drawAVFrame(frame);
if (view->isExit())
{
isExit = true;
view->close();
delete view;
exit(0);
}
}
}
void SdlQtRGB::resizeEvent(QResizeEvent* evt)
{
ui.label->resize(size());
ui.label->move(0, 0);
}
SdlQtRGB::~SdlQtRGB()
{
m_th.join();
av_frame_free(&frame);
}
使用另外一个线程通过信号与槽的方式去渲染 AVFrame 的 YUV 数据,并且可以由用户指定视频播放帧率,并且把播放帧率显示出来。