网站建设是干什么,公司网站用什么语言开发,wordpress开源程序,移动网站开发教材在前面#xff0c;我们总结一下前面的代码。
在 FactoryModeForAVFrameShowSDL 构造函数中 init SDL。
通过 QT timerevent机制#xff0c;通过startTimer(10);每隔10ms#xff0c;就会调用timerEvent事件。
在timerEvent事件中#xff0c;真正的去 读取数据#xff0c…在前面我们总结一下前面的代码。
在 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一行字节数
/// linesize0 就根据宽度和像素格式自动算出大小
/// 纯虚函数并不需要在 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 放在一起
/// /summaryvirtual 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.hX_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_lockmutex 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_lockmutex 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_lockmutex 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_lockmutex 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_lockmutex 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 QSpinBoxusing 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.hFactoryModeForAVFrameShowSDL::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个Y2个U2个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/QApplicationint main(int argc, char *argv[])
{QApplication a(argc, argv);FactoryModeForAVFrameShowSDL w;w.show();return a.exec();
}