FFmpeg 4.3 音视频-多路H265监控录放C++开发十. 多线程控制帧率。循环播放,QT connect 细节,

在前面,我们总结一下前面的代码。

在 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来决定是调用子类的那个Drawbool 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_OBJECTpublic: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() constbool _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());//准备 avframeavframe = 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;//AVPixelFormatavframe->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);//Vif (_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);//Vif(_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();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/466552.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

企业文件加密要怎么做?好用的10款企业文件加密软件排行榜!

在现代信息化的工作环境中&#xff0c;企业数据安全面临着越来越多的威胁。尤其是当涉及到敏感文件和商业机密时&#xff0c;如何保护这些数据不被泄露或遭受恶意攻击显得尤为重要。企业文件加密成为了保护企业信息安全的关键手段。本文将探讨如何进行企业文件加密&#xff0c;…

20241107给野火LubanCat1-BTB刷Ubuntu的预编译固件并点亮USB接口的热像仪AT600

20241107给野火LubanCat1-BTB刷Ubuntu的预编译固件并点亮USB接口的热像仪AT600 2024/11/7 20:08 缘起&#xff1a;需要使用RK3566的linux/Buildroot系统。 将 鲁班猫的 云盘资料下载之后&#xff0c;发现里面没有Buildroot的预编译固件。 火速联系 淘宝客服&#xff01;转技术支…

VMware没有卸载干净,安装后ping不通

目录 1.问题 2.问题分析 3. 解决办法 &#x1f353; STEP1&#xff1a;卸载VMware &#x1f348; STEP2&#xff1a;services.msc设置 &#x1f352;STEP3&#xff1a;安装everything删除所有与vmware相关的文件 &#x1f351;STEP4&#xff1a;使用CCleaner清理修复注册…

【科普】简述机器学习和深度学习及其相关的算法

文章目录 机器学习1. 基本概念2. 机器学习的分类3. 机器学习的常用方法4. 应用领域5. 挑战与未来6. 未来趋势 机器学习算法 深度学习1.深度学习的基本概念2.深度学习的主要架构3.深度学习的应用4.深度学习的挑战 深度学习算法 机器学习 机器学习是人工智能的一个重要分支&…

HCIP--3实验- 链路聚合,VLAN间通讯,Super VLAN,MSTP,VRRPip配置,静态路由,环回,缺省,空接口,NAT

学习目标&#xff1a; 链路聚合VLAN间通讯Super VLANMSTPVRRPip配置,静态路由,环回&#xff0c;缺省&#xff0c;空接口NAT 学习内容&#xff1a; 实验拓扑实验需求实验需求分析实验配置内容 &#xff08;每一个设备的每一步操作&#xff09;实验结果验证 1.实验拓扑 搭建 …

Zabbix监控架构

目录 1. Zabbix监控架构-CS架构 2. Zabbix极速上手指南 主机规划 2.1 部署ngxphp环境并测试 检查安装结果 2.2 部署数据库 2.3 编译安装zabbix-server服务端及后续配置 2.4 部署前端代码代码进行访问 前端的配置文件(连接数据库与主机名等信息) 2.5 欢迎来到zabbix 2…

【CentOS】中的Firewalld:全面介绍与实战应用(上)

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Linux &#xff1a;从菜鸟到飞鸟的逆袭》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、iptables 时代 2、firewalld 时代 3、 从 ipt…

人工智能未来前景好不好?

人工智能&#xff08;AI&#xff09;正以前所未有的速度改变着我们的世界。随着技术的不断进步&#xff0c;AI不仅在各行各业中扮演着越来越重要的角色&#xff0c;也为求职者和职业发展带来了广阔的机会。那么&#xff0c;人工智能未来的前景如何&#xff1f; 1 高增长行业 人…

湘潭大学软件工程专业选修 SOA 期末考试复习(二)

文章目录 回顾序言第一章课后题填空选择简答 第二章课后题填空选择编程 计划第三章课后题填空选择简答编程 第四章课后题填空选择简答编程 第五章课后题填空选择简答编程 第六章课后题说明 第七章课后题填空选择简答编程 第八章课后题填空选择简答编程 第九章课后题填空选择简答…

JVM垃圾回收详解

前言 当需要排查各种内存溢出问题、当垃圾收集成为系统达到更高并发的瓶颈时&#xff0c;我们就需要对这些“自动化”的技术实施必要的监控和调节。 堆空间的基本结构 Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时&#xff0c;Java 自动内存管理最核…

Hive 操作基础(进阶篇✌️)

Hive 进阶操作 分区表 创建分区表 create table score_part(字段名 字段类型,字段名 字段类型 )partitioned by (分区字段 分区类型) row format delimited fields terminated by \t; 创建单极分区表 注意: 分区的列名不能和数据列名相同.分区列会当做虚拟列出现在数据列…

【Kafka】Windows+KRaft部署指南

【Kafka】WindowsKRaft部署指南 摘要本地环境说明官网快速开始修改config/kraft/server.properties初始化数据存储目录启动 测试创建topic创建生产者创建消费者 FAQ输入行太长。命令语法不正确。问题描述解决方案 参考资料 摘要 Kafka是一种高吞吐量的分布式发布订阅消息系统&…

Docker-软件容器平台

一、容器 1、什么是容器 容器就是将软件打包成标准化单元&#xff0c;以用于开发、交付和部署 容器镜像是轻量的、可执行的独立软件包 &#xff0c;包含软件运行所需的所有内容&#xff1a;代码、运行时环境、系统工具、系统库和设置。容器化软件适用于基于 Linux 和 Windows…

OSS和FastDFS的区别

FastDFS&#xff1a; FastDFS 是一种开源的轻量级分布式文件系统&#xff0c;基于HTTP协议实现。具有高扩展性、高可用性和高稳定性。它解决了大容量文件存储和高效访问的问题&#xff0c;适合作为大容量文件的存储服务器。FastDFS 通过文件系统集群&#xff0c;使得用户可以将…

分离编译(介绍,解决“类模板定义和声明不在同一文件导致链接错误“的问题),类模板实例化原理,

目录 分离编译 介绍 问题代码示例 代码 说明 预处理 编译 链接 类模板实例化原理 总结 解决方法 显式实例化 模板的声明和定义放在一个头文件 分离编译 介绍 分离编译是一种编程技术 允许将程序代码分割成多个文件&#xff0c;每个文件可以独立地编译成目标文件…

云计算答案

情境一习题练习 一、选择题 1、在虚拟机VMware软件中实现联网过程&#xff0c;图中箭头所指的网络连接方式与下列哪个相关&#xff08; C &#xff09;。 A.仅主机模式 B.桥接 C.NAT D.嫁接 2、请问下图这个虚拟化架构属于什么类型&#xff08; A …

如何做好多项目进度管理

在同时管理多个项目时&#xff0c;重要的是要确保每个项目都能按时、按质完成。有效的时间管理、资源优化配置、持续的沟通和使用专业工具是关键要素。这些元素有助于维护项目的整体质量和效率&#xff0c;确保所有项目成员的责任和期望都明确无误。本文将深入探讨如何通过实践…

如何在vscode中安装git详细新手教程

一、安装git后点击vscode中的设置 今天教大家如何在VScode中编写代码后提交到git仓库&#xff0c;如果我们不想切换到git的命令行窗口&#xff0c;可以在VScode中配置git&#xff0c;然后就可以很方便快捷的把代码提交到仓库中。 二、在输入框中输入 git.path &#xff0c;再点…

使用Docker Compose构建多容器应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用Docker Compose构建多容器应用 引言 Docker Compose 简介 安装 Docker Compose 创建基本配置 运行多容器应用 查看服务状态 …

Python-利用tkinter库编写一个exe伪恶意程序文件(下)

前言 接着上篇所讲的&#xff0c;我们已经完成了源代码的准备&#xff0c;并将其储存在了function_1.py文件中。接下来我们将把function_1.py文件编写为相对应的exe文件。那么好&#xff0c;废话不多说&#xff0c;我们直接开始。&#xff08;温馨提示&#xff1a;由于整蛊的需…