在前面,我们总结一下前面的代码。
在 FactoryModeForAVFrameShowSDL 构造函数中 init SDL。
通过 QT timerevent机制,通过startTimer(10);每隔10ms,就会调用timerEvent事件。
在timerEvent事件中,真正的去 读取数据,刷新UI 界面。
上述行为都是在 主线程完成的。
下面我们改变机制:
在 FactoryModeForAVFrameShowSDL 构造函数中 init SDL。
然后开启一个线程,在线程中每隔10ms发送一次信号,那么这个信号就在子线程。信号只需要定义,不需要声明
在这个子线程中,我们通过 发送 这个信号,启动这个信号对应的槽函数,当然槽函数我们要先定义和声明,然后再 FactoryModeForAVFrameShowSDL 的构造函数 中 绑定 信号与槽。
实现细节:
1. 线程的相关细节
在构造方法中完成
//我们这里使用类的成员函数 threadfor 作为 线程函数入口。,注意成员函数做为线程函数作为入口的写法
// thread 这块如果忘记了,可以参考 https://blog.csdn.net/hunandede/article/details/135535979
_threadfor = std::thread(&FactoryModeForAVFrameShowSDL::threadformethod,this);
在析构函数中,要调用 线程 的join方法,避免主线程结束后,子线程还在运行
FactoryModeForAVFrameShowSDL::~FactoryModeForAVFrameShowSDL()
{
_is_exit = true;
//主线程结束前,一定要等待 子线程 join,然后会有问题。
if (_threadfor.joinable()) {
_threadfor.join();
}
if (_view_fps == nullptr) {
delete _view_fps;
_view_fps = nullptr;
}
if (spin_Box == nullptr) {
delete spin_Box;
spin_Box = nullptr;
}
}
我们这代码中打印了线程id,来看信号 和 槽函数分别是在哪里线程中运行的
在线程中打印 id
void FactoryModeForAVFrameShowSDL::threadformethod() {
//每隔10ms 发送一次信号,发送信号是在子线程,我们发送了ViewSingle的信号后,会调用 ViewSingleHandle的槽函数处理,ViewSingleHandle函数是在主线程做的
cout << "threadformethod single thread::get_id = " << std::this_thread::get_id() << endl;
在槽函数中打印id
void FactoryModeForAVFrameShowSDL::ViewSingleHandle() {
//槽函数的实现,
cout << "ViewSingleHandle thread::get_id = " << std::this_thread::get_id() << endl;
在构造函数中打印线程id
cout << "FactoryModeForAVFrameShowSDL constructor getpid() = " << std::this_thread::get_id() << endl;
引出QT 的 信号与槽 链接方法 connect方法的最后一个参数的学习
先来看connect 的方法
static QMetaObject::Connection connect(const QObject *sender,
const char *signal,
const QObject *receiver,
const char *member,
Qt::ConnectionType = Qt::AutoConnection);
static QMetaObject::Connection connect(const QObject *sender,
const QMetaMethod &signal,
const QObject *receiver,
const QMetaMethod &method,
Qt::ConnectionType type = Qt::AutoConnection);
inline QMetaObject::Connection connect(const QObject *sender,
const char *signal,
const char *member,
Qt::ConnectionType type = Qt::AutoConnection) const;
ConnectionType可以是以下几种类型:
Qt::AutoConnection(默认): Qt自动决定连接类型。如果信号发射和接收在同一个线程,它就选择 Qt::DirectConnection;如果在不同线程,它就选择 Qt::QueuedConnection。
Qt::DirectConnection: 槽函数将直接、立即在信号发射的环境中被调用,这通常在同一个线程中。
Qt::QueuedConnection: 信号发射后,槽函数将被放入事件队列,并在控制权返回事件循环时被调用,常用于跨线程通信。
Qt::BlockingQueuedConnection: 类似于 Qt::QueuedConnection,但是发射信号的线程会阻塞直到槽函数返回。这种方式在处理跨线程通信时要小心使用,以防止死锁。
Qt::UniqueConnection: 连接的信号和槽之间如果已经存在则不会再次连接,确保了同一个信号和槽之间只有一个连接。
2.关于循环播放的问题
C++ 如果读取到了文件的最后,那么如果要seekg 到文件的开头,需要 先使用clear方法
if (_yuv_file.eof()) //读取到文件结尾
{
cout << "last yuv_file" << endl;
//实验测试如果当文件走到 eof()的时候,如果想要seek 到文件开头,需要先clear 一下,
//在调用ifstream的定位操作如seekg()之前,必须先调用clear()来清除流的错误标志。这是因为如果流处于错误状态,seekg()等操作将无法正常执行
_yuv_file.clear();
_yuv_file.seekg(0, ios::beg);
}
3.如何计算帧率
帧率就是1s中,我们播放了多少张画面
我们通过槽函数 最终往上画,也就是,里面上槽函数在1秒中 调用了多少次,就是fps。
实际上更加确切的说,需要在 真正 画的那个函数里面计数 会比较合理
bool X_Video_View::DrawAVFrame(AVFrame* frame) {
if (frame == nullptr || frame->data[0] == nullptr) {
cout << "X_Video_View::DrawAVFrame error because (frame == nullptr || frame->data[0] == nullptr)" << endl;
return false;
}
_count++; // 只要画一次 count 就++;
//第一次的时候 _beg_ms =0的,因此要赋初值,后面就走不到了
if (_beg_ms <= 0)
{
_beg_ms = clock();
}
//计算显示帧率,当前时间减去 上一次 记录的时间, 超过 1000 ms 后,将count的值 赋值出去,然后count还原0,重新记录 _beg_ms的值
else if ((clock() - _beg_ms) / (CLOCKS_PER_SEC / 1000) >= 1000) //一秒计算一次fps
{
_render_fps = _count;
_count = 0;
_beg_ms = clock();
}
全部代码
x_video_view.h
#pragma once
#include <iostream>
#include <mutex>
extern "C" {
#include <libavutil/frame.h>
}
using namespace std;
void MSleep(unsigned int ms);
class X_Video_View
{
public:
enum RenderType{
SDL_TYPE = 0
};
enum Format
{
RGBA = 0,
ARGB,
YUV420P
};
static X_Video_View* CreateVideoAudio(RenderType type = SDL_TYPE);
//初始化渲染窗口 线程安全
//@para w 窗口宽度
//@para h 窗口高度
//@para fmt 绘制的像素格式
//@para win_id 窗口句柄,如果为空,创建新窗口
//@return 是否创建成功
virtual bool Init(int w,
int h,
Format fmt = RGBA,
void* win_id = nullptr) = 0;
//
/// 渲染图像 线程安全
///@para data 渲染的二进制数据
///@para linesize 一行数据的字节数,对于YUV420P就是Y一行字节数
/// linesize<=0 就根据宽度和像素格式自动算出大小
/// 纯虚函数,并不需要在 x_video_view.cpp 中实现,需要在子类中实现
/// @return 渲染是否成功
virtual bool Draw(const unsigned char* data, int linesize = 0) = 0;
/// <summary>
/// 前面提供了 CreateVideoAudio 方法,在子类内部实现是 new XSDL 或者 new XOpenGL
/// 那么应该提供一种方法让user 可以delete XSDL的方法才合理。
/// 那么这个 DestoryVideoAudio 方法具体应该干些啥才合理呢?
/// 也就是说,要不要把 SDLQuit 和 delete SDL 放在一起
/// </summary>
virtual void DestoryVideoAudio() = 0;
void changedWindow(int changed_w, int changed_h);
//如果传递的是 AVFrame,那么在cpp 中根据 AVFrame的format来决定是调用子类的那个Draw
bool DrawAVFrame(AVFrame* frame);
//
/// 渲染图像 for avframe 线程安全
///@para y y数据的指针
///@para y_pitch y数据的大小
///@para u u数据的指针
///@para u_pitch u数据的大小
///@para v v数据的指针
///@para v_pitch v数据的大小
/// @return 渲染是否成功
/// 此函数需要在子类中实现,因此是纯虚函数
virtual bool Draw(
const unsigned char* y, int y_pitch,
const unsigned char* u, int u_pitch,
const unsigned char* v, int v_pitch
) = 0;
//基类的 析构函数 需要写成 虚函数
virtual ~X_Video_View();
int render_fps();
protected:
int _width = 0; //材质宽高
int _height = 0;
Format _fmt = RGBA; //像素格式
mutex _mtx; //确保线程安全
int _changed_w = 0; //显示大小
int _changed_h = 0;
int _render_fps = 0; //显示帧率
long long _beg_ms = 0; //计时开始时间
int _count = 0; //统计显示次数
};
x_video_view.cpp
#include "x_video_view.h"
#include "xsdlview.h"
X_Video_View* X_Video_View::CreateVideoAudio(RenderType type)
{
switch (type)
{
case X_Video_View::SDL_TYPE:
return new XSDLView();
break;
default:
break;
}
return nullptr;
}
void X_Video_View::changedWindow(int changed_w, int changed_h) {
_changed_w = changed_w;
_changed_h = changed_h;
}
bool X_Video_View::DrawAVFrame(AVFrame* frame) {
if (frame == nullptr || frame->data[0] == nullptr) {
cout << "X_Video_View::DrawAVFrame error because (frame == nullptr || frame->data[0] == nullptr)" << endl;
return false;
}
_count++; // 只要画一次 count 就++;
//第一次的时候 _beg_ms =0的,因此要赋初值,后面就走不到了
if (_beg_ms <= 0)
{
_beg_ms = clock();
}
//计算显示帧率,当前时间减去 上一次 记录的时间, 超过 1000 ms 后,将count的值 赋值出去,然后count还原0,重新记录 _beg_ms的值
else if ((clock() - _beg_ms) / (CLOCKS_PER_SEC / 1000) >= 1000) //一秒计算一次fps
{
_render_fps = _count;
_count = 0;
_beg_ms = clock();
}
switch (frame->format)
{
case AV_PIX_FMT_YUV420P:
//如果是YUV420p, 则要调用显示 YUV420P的接口
return Draw(frame->data[0],
frame->linesize[0],
frame->data[1],
frame->linesize[1],
frame->data[2],
frame->linesize[2]);
case AV_PIX_FMT_BGRA: //如果不是YUV的数据,则使用最开始的Draw接口就可以。
return Draw(frame->data[0],frame->linesize[0]);
default:
break;
}
}
void MSleep(unsigned 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;
}
}
X_Video_View::~X_Video_View() {
}
int X_Video_View::render_fps()
{
return _render_fps;
}
xsdlview.h
#pragma once
#include "x_video_view.h";
struct SDL_Window;
struct SDL_Renderer;
struct SDL_Texture;
class XSDLView : public X_Video_View
{
protected:
bool Init(int w,
int h,
Format fmt = RGBA,
void* win_id = nullptr) override;
void endSDL();//没有线程锁的,用于在 init 或者 show的时候,有错误发生后,调用。这是由于init 或者 draw 方法的开头,都是线程 lock 的,再次使用线程 lock 就会有运行时异常,用的是同一个锁子
bool Draw(const unsigned char* data, int linesize = 0) override;
void DestoryVideoAudio() override; //线程相关的,线程安全的。,用于客户销毁sdl相关数据
bool Draw(
const unsigned char* y, int y_pitch,
const unsigned char* u, int u_pitch,
const unsigned char* v, int v_pitch
) override;
~XSDLView();
private:
SDL_Window *_sdlwindow = nullptr;
SDL_Renderer * _sdlrenderer = nullptr;
SDL_Texture * _sdltexture = nullptr;
char* _sdltitles = (char *)"sdlshow";
};
xsdlview.cpp
#include "xsdlview.h"
#include "sdl/SDL.h"
#include <iostream>
#include <mutex>
using namespace std;
#pragma comment(lib,"SDL2.lib")
void XSDLView::endSDL() {
if (_sdltexture) {
SDL_DestroyTexture(_sdltexture);
_sdltexture = nullptr;
}
if (_sdlrenderer) {
SDL_DestroyRenderer(_sdlrenderer);
_sdlrenderer = nullptr;
}
if (_sdlwindow) {
SDL_DestroyWindow(_sdlwindow);
_sdlwindow = nullptr;
}
//SDL_Quit();
}
static bool InitVideo()
{
static bool is_first = true;
static mutex mux;
unique_lock<mutex> sdl_lock(mux);
if (!is_first)return true;
is_first = false;
if (SDL_Init(SDL_INIT_VIDEO))
{
cout << "SDL_INIT ERROR" << SDL_GetError() << endl;
return false;
}
//设定缩放算法,解决锯齿问题,线性插值算法
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
return true;
}
bool XSDLView::Init(int w,
int h,
Format fmt,
void* win_id) {
//0.错误检查
if (w <= 0 || h <= 0) {
cout << "SDL Init error because w <= 0, h <= 0 w = " << w
<<" h = " << h
<< SDL_GetError() << endl;
return false;
}
//1.SDLinit(初始化SDL 视频库),由于 SDL init 只需要一次,因此最好做成 static 的
InitVideo();
//2.确保线程安全后,将user 传递的宽 和高 都赋值了
unique_lock<mutex> sdl_lock(_mtx);
_width = w;
_height = h;
_fmt = fmt;
//3. 创建窗口,user创建windows的时候如果没有传递 win_id
//4. 我们这里还要考虑user 多次调用 Init 函数的情况,假设多次调用了init 函数,那么需要考虑_sdlwindow,sdlrenderer,sdltexture,是否需要多次 create出来
//对于sdlwindows,是没有必要create多次的。
if (_sdlwindow == nullptr) {
if (win_id) {
_sdlwindow = SDL_CreateWindowFrom(win_id);
if (_sdlwindow == nullptr) {
cout << "SDL_CreateWindowFrom win_id error " << SDL_GetError() << endl;
endSDL();
return false;
}
}
else {
_sdlwindow = SDL_CreateWindow(_sdltitles,
0, 0,
_width, _height,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
if (_sdlwindow == nullptr) {
cout << "SDL_CreateWindow error "
<< " _sdltitles = " << _sdltitles
<< " _width = " << _width
<< " _height = " << _height
<< " SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE "
<< SDL_GetError()
<< endl;
endSDL();
return false;
}
}
}
//4. 创建renderer 渲染器
//对于 renderer如果多次调用init函数,则可能有内存泄漏,因此我们最开始的想法是,和 sdlwindows的处理方法一样
//参考sdlwindow 的处理方法,就是如果sdlwindows存在了就不需要创建了。
// 如下的写法也是可以的,如果 renderer 和texture存在,就直接先destory了
if (_sdltexture) {
SDL_DestroyTexture(_sdltexture);
}
if (_sdlrenderer) {
SDL_DestroyRenderer(_sdlrenderer);
}
_sdlrenderer = SDL_CreateRenderer(_sdlwindow, -1, SDL_RENDERER_ACCELERATED);
if (_sdlrenderer == nullptr) {
cout << "SDL_CreateRenderer SDL_RENDERER_ACCELERATED error " << SDL_GetError() << endl;
_sdlrenderer = SDL_CreateRenderer(_sdlwindow, -1, SDL_RENDERER_SOFTWARE);
if (_sdlrenderer == nullptr) {
cout << "SDL_CreateRenderer SDL_RENDERER_SOFTWARE error " << SDL_GetError() << endl;
endSDL();
return false;
}
}
//5 创建 texture 材质
//转化 fmt 和 sdlfmt
unsigned int sdl_fmt = SDL_PIXELFORMAT_RGBA8888;
switch (fmt)
{
case X_Video_View::RGBA:
break;
case X_Video_View::ARGB:
sdl_fmt = SDL_PIXELFORMAT_ARGB32;
break;
case X_Video_View::YUV420P:
sdl_fmt = SDL_PIXELFORMAT_IYUV;
break;
default:
break;
}
_sdltexture = SDL_CreateTexture(_sdlrenderer,
sdl_fmt,
SDL_TEXTUREACCESS_STREAMING,
_width,
_height);
if (_sdltexture == nullptr) {
cout << "SDL_CreateTexture SDL_RENDERER_SOFTWARE error " << SDL_GetError() << endl;
endSDL();
return false;
}
return true;
}
bool XSDLView::Draw(const unsigned char* data, int linesize) {
//1.先做判断
if (data == nullptr) {
cout << "xsdlview Draw error becase data = nullptr " << endl;
return false;
}
unique_lock<mutex> sdl_lock(_mtx);
if (!_sdltexture || !_sdlrenderer || !_sdlwindow || _width <= 0 || _height <= 0) {
cout << "xsdlview Draw error _sdltexture = " << _sdltexture
<<" _sdlrenderer = " << _sdlrenderer
<<" _sdlwindow = " << _sdlwindow
<< " _width = " << _width
<<" _height = "<< _height << endl;
return false;
}
//如果user 没有指定 linesize,则需要通过fmt计算一下
if (linesize <= 0)
{
switch (_fmt)
{
case X_Video_View::RGBA:
case X_Video_View::ARGB:
linesize = _width * 4;
break;
case X_Video_View::YUV420P:
linesize = _width;
break;
default:
break;
}
}
if (linesize <= 0) {
cout << "xsdlview Draw error becase linesize <= 0 _fmt = " << _fmt << endl;
return false;
}
int ret = 0;
// 将数据copy 到 材质
auto re = SDL_UpdateTexture(_sdltexture, NULL, data, linesize);
if (re != 0)
{
cout << "xsdlview Draw error becase SDL_UpdateTexture " << SDL_GetError() << endl;
return false;
}
//清空屏幕
SDL_RenderClear(_sdlrenderer);
//将材质copy 到 渲染器
//第三个参数,const SDL_Rect * srcrect,意思是,你要将 texture的哪些部分拿出来显示,传递NULL,表示整个texture
//第四个参数,const SDL_Rect * dstrect,意思是:要显示的数据,应该放置在window的什么位置
SDL_Rect rect;
rect.x = 0;
rect.y = 0;
if (_changed_w <= 0) {
_changed_w = _width;
}
if (_changed_h <= 0) {
_changed_h = _height;
}
rect.w = _changed_w;
rect.h = _changed_h;
re = SDL_RenderCopy(_sdlrenderer, _sdltexture, NULL, &rect);
if (re != 0)
{
cout << "xsdlview Draw error becase SDL_RenderCopy error " << SDL_GetError() << endl;
return false;
}
SDL_RenderPresent(_sdlrenderer);
return true;
}
bool XSDLView::Draw(
const unsigned char* y, int y_pitch,
const unsigned char* u, int u_pitch,
const unsigned char* v, int v_pitch
) {
//1.先做判断
if (y == nullptr || u == nullptr || v == nullptr) {
cout << "xsdlview Draw error becase y == nullptr || u == nullptr || v == nullptr " << endl;
return false;
}
unique_lock<mutex> sdl_lock(_mtx);
if (!_sdltexture || !_sdlrenderer || !_sdlwindow || _width <= 0 || _height <= 0) {
cout << "xsdlview Draw error _sdltexture = " << _sdltexture
<< " _sdlrenderer = " << _sdlrenderer
<< " _sdlwindow = " << _sdlwindow
<< " _width = " << _width
<< " _height = " << _height << endl;
return false;
}
int ret = 0;
// 将数据copy 到 材质
ret = SDL_UpdateYUVTexture(_sdltexture, nullptr,
y, y_pitch, u, u_pitch, v, v_pitch);
if (ret != 0)
{
cout << "xsdlview Draw error becase SDL_UpdateTexture " << SDL_GetError() << endl;
return false;
}
//清空屏幕
SDL_RenderClear(_sdlrenderer);
//将材质copy 到 渲染器
//第三个参数,const SDL_Rect * srcrect,意思是,你要将 texture的哪些部分拿出来显示,传递NULL,表示整个texture
//第四个参数,const SDL_Rect * dstrect,意思是:要显示的数据,应该放置在window的什么位置
SDL_Rect rect;
rect.x = 0;
rect.y = 0;
if (_changed_w <= 0) {
_changed_w = _width;
}
if (_changed_h <= 0) {
_changed_h = _height;
}
rect.w = _changed_w;
rect.h = _changed_h;
ret = SDL_RenderCopy(_sdlrenderer, _sdltexture, NULL, &rect);
if (ret != 0)
{
cout << "xsdlview Draw error becase SDL_RenderCopy error " << SDL_GetError() << endl;
return false;
}
SDL_RenderPresent(_sdlrenderer);
return true;
}
void XSDLView::DestoryVideoAudio() {
unique_lock<mutex> sdl_lock(_mtx);
endSDL();
}
XSDLView::~XSDLView()
{
SDL_Quit();
}
factorymodeforavframeshowsdl.h
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_factorymodeforavframeshowsdl.h"
#include <iostream>
#include <fstream>
#include <QMessageBox>
#include "x_video_view.h"
#include <thread>
#include <ctime>
#include <sstream>
#include <QSpinBox>
using namespace std;
class FactoryModeForAVFrameShowSDL : public QMainWindow
{
Q_OBJECT
public:
FactoryModeForAVFrameShowSDL(QWidget *parent = nullptr);
~FactoryModeForAVFrameShowSDL();
void timerEvent(QTimerEvent* ev) override;
void resizeEvent(QResizeEvent* ev) override;
//线程函数,用于刷新视频
void threadformethod();
signals: //自定义信号
void ViewSingle(); //信号只需要声明,不需要定义,也就是说不需要 在 .cpp文件写东西
public slots: // 自定义槽函数
void ViewSingleHandle();//槽函数需要声明,需要定义,需要在.cpp文件中写实现。
private:
Ui::FactoryModeForAVFrameShowSDLClass ui;
ifstream _yuv_file;
int _sdl_width = 400;
int _sdl_height = 300;
X_Video_View* _view = nullptr;
unsigned char* _yuvdata = NULL;
int _pix_size = 2;//这个_pix_size是为了分盘内存用的, 这里我们用的YUV420p,在不考虑字节对齐的case下,是乘以 1.5的,这里写成2,是内存只要够用就可以了
AVFrame* avframe = nullptr;
std::thread _threadfor; // thread 和QT 的thread冲突,因此需要加上std 。参考QThread *QObject::thread() const
bool _is_exit = false;//处理线程退出
QLabel* _view_fps = nullptr;
QSpinBox* spin_Box = nullptr;
int _spin_box_value = 0;
};
factorymodeforavframeshowsdl.cpp
#include "factorymodeforavframeshowsdl.h"
#include "x_video_view.h"
FactoryModeForAVFrameShowSDL::FactoryModeForAVFrameShowSDL(QWidget *parent)
: QMainWindow(parent)
{
//在这里都是初始化的行为。
ui.setupUi(this);
//打开yuv文件
_yuv_file.open("400_300_25.yuv", ios::binary);
if (!_yuv_file)
{
QMessageBox::information(this, "", "open yuv failed!");
return;
}
//绑定信号与槽
connect(this, SIGNAL(ViewSingle()), this, SLOT(ViewSingleHandle()));
// new 一个组件,目的是显示 fps,那么这个fps 的值应该怎么计算呢?应该是1s中播放了多少次
//那么我们可以在 真正显示的方法 DrawAVFrame()方法中用一个计数器,显示一次就加1,当超过1s的时候,显示这个count,当然超过1s后还需要将count再变成0
//因此我们需要提供一个 render_fps 方法
_view_fps = new QLabel(this);
_view_fps->setText("正在计算fps");
spin_Box = new QSpinBox(this);
spin_Box->move(200, 0);
spin_Box->setValue(25);
spin_Box->setRange(1, 200);
_spin_box_value = spin_Box->value();
_sdl_width = 400;
_sdl_height = 300;
//将windows 的宽高 设置成和 yuv的宽和高一样
resize(_sdl_width, _sdl_height);
//将label 的宽高 设置成和 yuv的宽和高一样
ui.label->resize(_sdl_width, _sdl_height);
//将label 的x y 坐标设置为window的0,0 ,这样label就能完全显示在window中
ui.label->move(0, 0);
_view = X_Video_View::CreateVideoAudio();
_view->Init(_sdl_width, _sdl_height,
X_Video_View::YUV420P, (void*)ui.label->winId());
//准备 avframe
avframe = av_frame_alloc();
if (avframe == nullptr) {
cout << "av_frame_alloc error " << endl;
_view->DestoryVideoAudio();
delete _view;
return;
}
avframe->width = _sdl_width;
avframe->height = _sdl_height;
//AVPixelFormat
avframe->format = AV_PIX_FMT_YUV420P;
avframe->linesize[0] = _sdl_width; //我们这里虽然是在后面使用0作为第二个参数,但是也可以自己设定 linesize的大小,av_frame_get_buffer(avframe, 0)
avframe->linesize[1] = _sdl_width/2; //这里为啥是 width/2 呢?参考前面007的项目的测试
avframe->linesize[2] = _sdl_width/2; // YUV420P不是 4个Y对应一个U+一个V 吗?为什么是除以2呢?实际上 存储的时候假设有8个Y,2个U,2个V ,那么存储的时候,是 YYYY 一行,然后YYYY一行,UU一行,VV一行,因此是除以2的
int ret = 0;
ret = av_frame_get_buffer(avframe, 0);
if (ret < 0 ) {
char buf[1024] = { 0 };
av_strerror(ret, buf, sizeof(buf));
cout << "av_frame_get_buffer error " << endl;
_view->DestoryVideoAudio();
delete _view;
av_frame_free(&avframe);
return;
}
cout << "FactoryModeForAVFrameShowSDL constructor getpid() = " << std::this_thread::get_id() << endl;
//cout << "FactoryModeForAVFrameShowSDL constructor thread::get_id = " << thread::get_id << endl;
//我们之前的做法是通过startTimer来开启一个定时器,
// 让定时器不断地 读取一张一张的图片 到avframe中,读取一张,显示一张
// 我们这里不在使用 startTimer(10)来完成显示行为,而是通过 thread 来做
//startTimer(10);
//现在我们的想法是要通过 线程 来读取数据,那么就先要弄一个线程出来
//我们这里使用类的成员函数 threadfor 作为 线程函数入口。,注意成员函数做为线程函数作为入口的写法
// thread 这块如果忘记了,可以参考 https://blog.csdn.net/hunandede/article/details/135535979
_threadfor = std::thread(&FactoryModeForAVFrameShowSDL::threadformethod,this);
//那么这个线程用来干什么呢?用来发送信号,对应信号的槽函数收到信号之后,会自动调用
//因此 我们这里要有信号和槽的概念。
//也就是说,我们在子线程_threadfor对应的 threadformethod 函数中,每隔 10ms 发送一个信号,
// 我们假设这个信号叫做 ViewSingle,对应的槽函数叫做 ViewSingleHandler 函数
//那么这个ViewSingle信号的发送是在 子线程做的。这里可以通过打印 threadid来证明
//问题是 对应的槽函数 ViewSingleHandler,是在哪个线程进行的呢?实验测试一下。
//在这之前,首先我们需要绑定 信号和槽函数。
}
void FactoryModeForAVFrameShowSDL::threadformethod() {
//每隔10ms 发送一次信号,发送信号是在子线程,我们发送了ViewSingle的信号后,会调用 ViewSingleHandle的槽函数处理,ViewSingleHandle函数是在主线程做的
cout << "threadformethod single thread::get_id = " << std::this_thread::get_id() << endl;
while (!_is_exit)
{
ViewSingle();
//this_thread::sleep_for(10ms);
_spin_box_value = spin_Box->value();
if (_spin_box_value > 0) {
MSleep(1000 / _spin_box_value);
}
else {
MSleep(10);
}
}
}
void FactoryModeForAVFrameShowSDL::ViewSingleHandle() {
//槽函数的实现,
cout << "ViewSingleHandle thread::get_id = " << std::this_thread::get_id() << endl;
cout << "avframe event" << endl;
_yuv_file.read((char*)avframe->data[0], _sdl_width * _sdl_height);//Y
_yuv_file.read((char*)avframe->data[1], _sdl_width * _sdl_height / 4);//U
_yuv_file.read((char*)avframe->data[2], _sdl_width * _sdl_height / 4);//V
if (_yuv_file.eof()) //读取到文件结尾
{
cout << "last yuv_file" << endl;
//实验测试如果当文件走到 eof()的时候,如果想要seek 到文件开头,需要先clear 一下,
//在调用ifstream的定位操作如seekg()之前,必须先调用clear()来清除流的错误标志。这是因为如果流处于错误状态,seekg()等操作将无法正常执行
_yuv_file.clear();
_yuv_file.seekg(0, ios::beg);
}
_view->DrawAVFrame(avframe);
stringstream ss;
ss << "fps:" << _view->render_fps();
//只能在槽函数中调用
_view_fps->setText(ss.str().c_str());
}
void FactoryModeForAVFrameShowSDL::timerEvent(QTimerEvent* ev) {
// yuv420p
// 4*2
// yyyy yyyy
// u u
// v v 这里是从yuv_file 中 读取一张图片的大小到 Y,U,V 中
cout << "avframe event" << endl;
_yuv_file.read((char*)avframe->data[0], _sdl_width * _sdl_height);//Y
_yuv_file.read((char*)avframe->data[1], _sdl_width * _sdl_height / 4);//U
_yuv_file.read((char*)avframe->data[2], _sdl_width * _sdl_height / 4);//V
if(_yuv_file.bad()) {
//debug 测试,读取到文件末尾 不会 走到这一行
cout << "bad" << endl;
}
if (_yuv_file.good()) {
cout << "good" << endl;
}
if (_yuv_file.eof()) //读取到文件结尾
{
cout << "last yuv_file" << endl;
//实验测试如果当文件走到 eof()的时候,如果想要seek 到文件开头,需要先clear 一下,
//在调用ifstream的定位操作如seekg()之前,必须先调用clear()来清除流的错误标志。这是因为如果流处于错误状态,seekg()等操作将无法正常执行
_yuv_file.clear();
_yuv_file.seekg(0, ios::beg);
}
_view->DrawAVFrame(avframe);
}
//当窗口的大小变化的时候,会调用到这个函数,该函数由QT 驱动
//测试发现,在第一次的时候窗口显示的时候就会显示
void FactoryModeForAVFrameShowSDL::resizeEvent(QResizeEvent* ev) {
cout << "resizeEvent" << endl;
//在窗口变化后,先得到窗口的大小
QSize aa = size();
//将label 的大小也变成和窗口一样大
ui.label->resize(aa);
//然后是将视频的显示大小变得和这个一样大。
//这里要注意的是:视频的大小是不变的,变化的是 视频显示 的大小
//视频显示 的大小 在SDL里面是在 SDL_RenderCopy(_sdlrenderer, _sdltexture, NULL, &rect);
//那么意味着 我们要记录这个 窗口变化的值,想办法传递给 SDL_RenderCopy 函数
cout << "aa.width() = " << aa.width() << " aa.height() = " << aa.height() << endl;
_view->changedWindow(aa.width(), aa.height());
}
FactoryModeForAVFrameShowSDL::~FactoryModeForAVFrameShowSDL()
{
_is_exit = true;
//主线程结束前,一定要等待 子线程 join,然后会有问题。
if (_threadfor.joinable()) {
_threadfor.join();
}
if (_view_fps == nullptr) {
delete _view_fps;
_view_fps = nullptr;
}
if (spin_Box == nullptr) {
delete spin_Box;
spin_Box = nullptr;
}
}
main.cpp
#include "factorymodeforavframeshowsdl.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
FactoryModeForAVFrameShowSDL w;
w.show();
return a.exec();
}