FFmpeg 4.3 音视频-多路H265监控录放C++开发十二:在屏幕上显示多路视频播放,可以有不同的分辨率,格式和帧率。

上图是在安防领域的要求,一般都是一个屏幕上有显示多个摄像头捕捉到的画面,这一节,我们是从文件中读取多个文件,显示在屏幕上。

一 改动UI文件

这里我们要添加两个label,为了区分我们设置一下背景色(这个是非必须的),并设置不同的objectname,分别为video1 和 video2(这个objectname是组件的唯一性标识)。

UI设计完成后长这样。

width1, width2, height1,heigth2 的值范围设定为 1-9999,表示视频的宽和高的范围

set_fpx1 和 set_fpx2的值范围为1-200,user可以设定 帧率

还有一个显示的fpx ,后续会在 video1 和 video2上显示

二 给 open1 和 open2 添加信号和槽事件

UI上的操作

使用编辑UI 和 信号与槽的按钮 切换。

这里顺便加了两个要显示的fps:

将UI的大小调整一下

代码中的实现

也就是说,我们通过UI的操作,

将 open1的clicked信号 绑定了 SlotOpen1槽函数

将 open2的clicked信号 绑定了 SlotOpen2槽函数

但是现在代码中还并没有 SlotOpen1槽函数 ,也没有 SlotOpen2槽函数

在代码中添加 槽函数的声明和实现

factorymodeforavframeshowsdl.h 声明

public slots: // 自定义槽函数void ViewSingleHandle();//槽函数需要声明,需要定义,需要在.cpp文件中写实现。void SlotOpen1();void SlotOpen2();///另外,为了扩展,因为后期我们可能要添加10个 button,打开10个视频,因此可以整理一个SlotOpen函数,参数为 int i ,表示打开哪一个file void SlotOpen(int i);

factorymodeforavframeshowsdl.cpp实现

为了代码整齐,我们将槽函数的实现写在之前的槽函数 ViewSingleHandle之后,方便代码看起来整齐。

我们先想一下slotopen1函数的功能应该是啥?打开一个yuv文件,或者RGBA文件,

1.使用 QFileDialog 让user 选择想要打开的文件

void  FactoryModeForAVFrameShowSDL::SlotOpen(int i) {//1.使用 QFileDialog 让user 选择想要打开的文件QFileDialog qfd;QString filename = qfd.getOpenFileName();if (filename.isEmpty()) {cout << "SlotOpen can not open filename because filename.isEmpty()" << endl;return;}cout << "SlotOpen filename = " <<filename.toStdString() <<  endl;
2.打开文件

到现在我们已经选择一个文件了,到这里文件名字就有了,应该使用 C++的打开文件函数了 fstream open。或者 Qfile的open函数
    //这里考虑到 文件的打开应该是个常规操作,我们将文件的打开函数 放在 x_video_view中 定义,对于最终调用这来说,只需要传递一个文件名字就OK了
    // 也就是 需要 调用 x_video_view.open(filename.toStdString()); 打开文件

x_video_view.h

    //打开文件,C++的文件打开是使用 ifstream.open,因此还需要一个文件句柄--类似ifstream ifs//那么我们就需要定义一个成员变量 ifstream ifs了,又因为我们不想让这个文件句柄给最终使用的user拿到,因此要写成private或者protected的bool Open(std::string filename);

x_video_view.cpp

bool X_Video_View::Open(std::string filename)
{//容错处理if (_ifs.is_open()) {_ifs.close();}//使用二进制的方式打开,C++的file open没有返回值,文件打开或者没打开需要看is_open()方法 _ifs.open(filename, std::ios_base::binary);//返回值告诉user 该文件是否打开。return _ifs.is_open();
}

接口已经有了,下来就要调用 x_video_view.open(filename.toStdString()); 打开文件。

那么这个 x_video_view什么时候得到的呢?

我们知道,之前我们是一个画面,在FactoryModeForAVFrameShowSDL的构造函数中,CreateVideoAudio出来一个 x_video_view
    // 现在有多个显示视频的窗口,那么就应该createviewaudio多次,且后面要使用 createviuewaudio 出来的X_Video_View*,因此应该还需要保存 多个 X_Video_View*,这里使用vector<X_Video_View*> 保存。
    //由于这里 明显要在构造方法中,调用 createviewaudio。并保存多个 X_Video_View* 到vector 中。因此这里先要跳到 构造方法中,完成 createviewaudio 并保存到vector 的操作

三.在构造方法中 创建 vector<X_Video_View*>  

第一个问题就是要创建几个显示视频的窗口。

如下是自己的想法:根据有几个 QLabel 来创建 几个 X_Video_View,我们在UI中有4个qlabel,两个显示视频,2个显示 fps,但是这样设计会有要求,就是UI中的和代码中要有 协商。

    //这里我们想从widget中找到,有几个显示视频的窗口,就应该要CreateVideoAudio几次。当前UI设定是有4个label的,2个显示video,2个显示fps
    //QList<QLabel*> allPButtons = ui.centralWidget->findChildren<QLabel*>(); ///4
    //QList<QLabel*> allPButtons = ui.centralWidget->findChildren<QLabel*>("video1"); ///1,video1是第一个显示视频的QLabel。

我们这里直接写2个,如果后续要添加,看有没有比较合理的动态的方法。

    _vec.push_back(X_Video_View::CreateVideoAudio());
    _vec.push_back(X_Video_View::CreateVideoAudio());

第二个问题,我们之前在init的时候,会绑定一个ui.label->winId


        //类似这样的代码_view->Init(_sdl_width, _sdl_height, X_Video_View::YUV420P, (void*)ui.label->winId());
    //那么现在有多个winid,我们应该怎么绑定呢?这里我们想到的方法是,在 x_video_view.h 中 声明 _winid,并提供 setwindid的方法,记录这个值。那么在init 调用的时候,就不需要winid的传递了,直接从x_video_view中获得 winid,绑定SDL 创建的window 到winid上
    _vec[0]->setWinId((void *) ui.video1->winId());
    _vec[1]->setWinId((void *) ui.video2->winId());

代码

x_video_view.h 添加 变量 void * _winid = nullptr; 是protected的,提供public的访问方法 setWinid

public:void setWinId(void *winid);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;              //统计显示次数void* _winid = nullptr;

x_video_view.cpp

void X_Video_View::setWinId(void* winid) {this->_winid = winid;
}


 

 这还意味着,我们在 init 的时候,不需要传递 winid了,,还需要改动一下 之前的 init 接口。这里需要代码改动 init 接口
    // 那么之前在构造方法中调用的 init 方法,还在这里调用吗?
    //我们可以想象一下只有当user 选择了要播放的文件的时候,我们再去init 是不是更加合理一些呢,因此我们在改造了init方法后,调用的地方应该是 button的click信号发送后的SlotOpen槽函数中更加合理
        //_view->Init(_sdl_width, _sdl_height,X_Video_View::YUV420P, (void*)ui.label->winId());
    cout << "" << endl;

x_video_view.h

    //在多路播放中,winid会被记录,因此需要调整上面的接口virtual bool Init(int w,int h,Format fmt = RGBA) = 0;

xsdlview.h

    bool Init(int w,int h,Format fmt = RGBA) override;

xsdlview.cpp

注意 更换的核心行       

if (this->_winid) {

                _sdlwindow = SDL_CreateWindowFrom(this->_winid);

bool XSDLView::Init(int w,int h,Format fmt) {//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 (this->_winid) {_sdlwindow = SDL_CreateWindowFrom(this->_winid);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;
}

四。我们再回到 SlotOpen方法中 去init

void  FactoryModeForAVFrameShowSDL::SlotOpen(int i) {//1.使用 QFileDialog 让user 选择想要打开的文件QFileDialog qfd;QString filename = qfd.getOpenFileName();if (filename.isEmpty()) {cout << "SlotOpen can not open filename because filename.isEmpty()" << endl;return;}cout << "SlotOpen filename = " <<filename.toStdString() <<  endl;//2.到这里文件名字就有了,应该使用 C++的打开文件函数了 fstream open。或者 Qfile的open函数//这里考虑到 文件的打开应该是个常规操作,我们将文件的打开函数 放在 x_video_view中 定义,对于最终调用这来说,只需要传递一个文件名字就OK了// 也就是 需要 调用 x_video_view.open(filename.toStdString()); 打开文件‘// 那么理论上就要先弄出来一个 x_video_view了,我们知道,之前我们是一个画面,在FactoryModeForAVFrameShowSDL的构造函数中,CreateVideoAudio出来一个 x_video_view// 现在有多个显示视频的窗口,那么就应该createviewaudio多次,且后面要使用 createviuewaudio 出来的X_Video_View*,因此应该还需要保存 多个 X_Video_View*,这里使用vector<X_Video_View*> 保存。//由于这里 明显要在构造方法中,调用 createviewaudio。并保存多个 X_Video_View* 到vector 中/// <summary>/// 在Qt中,toLocal8Bit()是一个QString类的函数,/// 用于将QString对象转换为本地8位字符集编码的QByteArray对象。/// 这个函数会根据当前系统的本地编码将QString对象转换为对应的8位字符集编码,/// 比如在中文Windows系统中,toLocal8Bit()会将QString对象转换为GB2312编码的QByteArray对象。/// 这个函数通常用于将QString对象转换为可以在底层API中使用的8位字符集编码。/// <param name="i"></param>this->_vec[i]->Open(filename.toLocal8Bit().toStdString());//3.回到SlotOpen方法中,来init,init 需要的参数已经变成 了  Init(int w,int h,Format fmt = RGBA);//那么对于每一个 x_video_view, 都要知道 w,h ,formatint w = 0;int h = 0;QString pix = 0;  //YUV420P RGBAX_Video_View::Format format = X_Video_View::YUV420P;if (i == 0 ) {w = ui.width1->value();h = ui.height1->value();pix = ui.pix1->currentText();}else if (i == 1) {w = ui.width2->value();h = ui.height2->value();pix = ui.pix2->currentText();}if (pix == "YUV420P"){format = X_Video_View::YUV420P;cout << "444" << endl;}else if (pix == "RGBA"){format = X_Video_View::RGBA;}else if (pix == "ARGB"){format = X_Video_View::ARGB;}else if (pix == "BGRA"){format = X_Video_View::BGRA;}bool aa = this->_vec[i]->Init(w, h, format);cout << "init = " << aa << endl;}

至此,为了每一个 视频界面都create 了 xvideoview,且都init 了。

五。准备要处理的数据

那么我们下来就要准备要显示的YUV文件,RGB文件,显示了,先准备一下这些数据吧。


ffmpeg -i v1080.mp4 -s 800x400 -pix_fmt rgba 1.rgb

ffmpeg -i v1080.mp4 -s 600x300 -pix_fmt yuv420p 2.yuv

六。 抽取读取AVFrame的接口到 x_video_view

我们现在 使用 ifs 打开了一个文件,那么下来就是要从文件中读取数据到AVFrame,然后将AVFrame再显示到画面上。

目前的做法是:

我们当前在 构造函数中, 创建AVFrame的结构体和给AVFrame分配内存,然后再 每隔10ms发送一次信号,在信号槽函数中,给 AVFrame中读取 一张图片的大小,紧接着画出来。也就是说,当前的做法是在业务逻辑中分配了AVFrame,并在业务逻辑中 给AVFrame分配空间和赋值。

改动这部分,我们将  读取文件的这部分操作放在xvideoview的接口中,让 业务层在调用 这个方法的时候,就可以读取一张图片到AVFrame。

x_video_view.h

    //
/// 读取一帧数据,并维护AVFrame空间
/// 每次调用会覆盖上一次数据AVFrame* ReadAVFrame();

x_video_view.cpp

//在读取数据之前,我们先要做判断,如果读取数据之前,要确定 init 已经完成了。
//也就是,读取文件的ifs已经打开了,width和height都有了值,this->_fmt有了值
AVFrame* X_Video_View::ReadAVFrame() {//但是这里error 判断 的条件if (this->_width <= 0 || this->_height <=0 ||  !_ifs) {return nullptr;}//如果_avframe已经存在,因为每次都要读取数据,因此要释放///但是不是每次都释放,假设_avframe中的各个参数都是一样的,我们就没有必要freeif (_avframe!=nullptr) {if (_avframe->width != _width|| _avframe->height != _height|| _avframe->format != _fmt){//释放AVFrame对象空间,和buf引用计数减一av_frame_free(&_avframe);}}//if (_avframe == nullptr) {//分配 avframe,并且初始化,并且通过 av_frame_get_buffer,给avframe中赋值。_avframe = av_frame_alloc();if (_avframe == nullptr) {cout << "ReadAVFrame error because av_frame_alloc == nullptr" << endl;}_avframe->width = this->_width;_avframe->height = this->_height;_avframe->format = this->_fmt;//根据不同的 format,设置  linesize 的值if (_avframe->format == AV_PIX_FMT_YUV420P){_avframe->linesize[0] = _width; // Y_avframe->linesize[1] = _width / 2;//U_avframe->linesize[2] = _width / 2;//V}else if (_avframe->format == AV_PIX_FMT_ARGB) {_avframe->linesize[0] = _width * 4;}else if (_avframe->format == AV_PIX_FMT_RGBA) {_avframe->linesize[0] = _width * 4;}else if (_avframe->format == AV_PIX_FMT_ABGR) {_avframe->linesize[0] = _width * 4;}else if (_avframe->format == AV_PIX_FMT_BGRA) {_avframe->linesize[0] = _width * 4;}int ret = 0;ret = av_frame_get_buffer(_avframe, 0);if (ret < 0) {char errbuf[1024] = { 0 };//这里给 sizeof(errbuf) - 1, 是为了留下一个 填写 字符\0,方便打印log观察av_strerror(ret, errbuf, sizeof(errbuf) - 1);av_frame_free(&_avframe);cout << "av_frame_get_buffer error _avframe->width  = " << _avframe->width<< endl;return nullptr;}}//这里再次判断 avframe == nullptr,实际上是冗余的。只是习惯而已if (_avframe == nullptr) {return nullptr;}//到这里 _avframe就真的可以使用了,那么就要给这里填充数据拉,在这之前,我们的 _ifs中已经有了要读取文件的流//如果是YUV420P,_avframe->data[0]读取的大小就是 宽度*宽度,_avframe->data[1]读取的大小为 宽度*高度/4,_avframe->data[2]读取大小也是 宽度*高度/4if (this->_avframe->format == AV_PIX_FMT_YUV420P) {this->_ifs.read((char*)this->_avframe->data[0], this->_avframe->width * this->_avframe->height);this->_ifs.read((char*)this->_avframe->data[1], this->_avframe->width * this->_avframe->height /4);this->_ifs.read((char*)this->_avframe->data[2], this->_avframe->width * this->_avframe->height /4);}else if (this->_avframe->format == AV_PIX_FMT_ARGB) {this->_ifs.read((char*)this->_avframe->data[0], this->_avframe->width * this->_avframe->height * 4 );}else if (this->_avframe->format == AV_PIX_FMT_RGBA) {this->_ifs.read((char*)this->_avframe->data[0], this->_avframe->width * this->_avframe->height * 4);}else if (this->_avframe->format == AV_PIX_FMT_ABGR) {this->_ifs.read((char*)this->_avframe->data[0], this->_avframe->width * this->_avframe->height * 4);}else if (this->_avframe->format == AV_PIX_FMT_BGRA) {this->_ifs.read((char*)this->_avframe->data[0], this->_avframe->width * this->_avframe->height * 4);}//gcount()返回已读字符数,也就是最后一次读取到的,实际上这里写成==0 是不严谨的,//如果我们yuv数据最后一些数据丢失了,那么合理的判断应该是 this->_ifs.gcount()<要实际应该读取的大小//if (this->_avframe->format == AV_PIX_FMT_YUV420P) {//	if (this->_ifs.gcount() < this->_avframe->width * this->_avframe->height *1.5) {//		return nullptr;//	}//}//else if (this->_avframe->format == AV_PIX_FMT_ARGB) {//	if (this->_ifs.gcount() < this->_avframe->width * this->_avframe->height * 4) {//		return nullptr;//	}//}//else if (this->_avframe->format == AV_PIX_FMT_RGBA) {//	if (this->_ifs.gcount() < this->_avframe->width * this->_avframe->height * 4) {//		return nullptr;//	}//}//else if (this->_avframe->format == AV_PIX_FMT_ABGR) {//	if (this->_ifs.gcount() < this->_avframe->width * this->_avframe->height * 4) {//		return nullptr;//	}//}//else if (this->_avframe->format == AV_PIX_FMT_BGRA) {//	if (this->_ifs.gcount() < this->_avframe->width * this->_avframe->height * 4) {//		return nullptr;//	}//}if (this->_ifs.gcount()==0) {this->_ifs.clear();this->_ifs.seekg(0, std::ios::beg);//return nullptr;}//那么我们在什么时候调用ReadAVFrame 函数 呢?应该是在 业务逻辑 的 收到信号槽函数 return _avframe;
}

七。读取数据,显示数据

void FactoryModeForAVFrameShowSDL::ViewSingleHandle() {//槽函数的实现,cout << "ViewSingleHandle thread::get_id = " << std::this_thread::get_id() << endl;// 新代码改动到这里,我们还是要在 子线程 信号的槽函数这里 进行文件的读取,///在读取之前,需要向获得 user在界面上设置的 fps是多少,这样的话,int setfpx1 = ui.set_fpx1->value();   //int setfpx2 = ui.set_fpx1_2->value();_set_fps_arr[0] = setfpx1;_set_fps_arr[1] = setfpx2;//循环,对每个视频画面进行显示,显示前如果 user 设置的 fpx <=0,则循环到下一个屏幕for (int i = 0; i < _vec.size(); ++i) {if (_set_fps_arr[i] < 0) {continue;}//如果user有设置,其实由于我们设置了 set_fps 的默认值是25,因此一定会走到这里///计算出每隔多少毫秒 渲染一次int ms = 1000 / _set_fps_arr[i];//计算当前时间 和 上一次渲染时间 的 差值,如果差值 < ms,就轮换到下一个if ((NowMs() - _last_avframe_play_time_arr[i]) < ms) {continue;}//如果到这里就说明,真的要读取数据了,那么这时候要记录 当前帧 播放的时间。_last_avframe_play_time_arr[i] = NowMs();//读取数据auto avframedata = _vec[i]->ReadAVFrame();if (avframedata == nullptr) {continue;}//显示画面_vec[i]->DrawAVFrame(avframedata);//显示fpsstringstream ss;ss << "fps:" << _vec[i]->render_fps();if (i == 0) {ui.show_fps1->setText(ss.str().c_str());}else {ui.show_fps2->setText(ss.str().c_str());}}}

八 。释放资源

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

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

相关文章

前言2、VS(Visual Studio)-2022使用

早前用VS-2010编译平台&#xff0c;进行C语言编程学习。 现如今&#xff0c;为了适应未来发展趋势以及日新月异的新功能&#xff0c;就此转到VS-2022编译平台&#xff1b; 由于都是VS编译平台&#xff0c;大多数基础功能都类似&#xff0c;关于一些基础操作可参考前言1&#…

深入了解逻辑回归:机器学习中的经典算法

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

[High Speed Serial ] Xilinx

Xilinx 高速串行数据接口 收发器产品涵盖了当今高速协议的方方面面。GTH 和 GTY 收发器提供要求苛刻的光互连所需的低抖动&#xff0c;并具有世界一流的自适应均衡功能&#xff0c;具有困难的背板操作所需的 PCS 功能。 Versal™ GTY &#xff08;32.75Gb/s&#xff09;&…

基于CNN-RNN的影像报告生成

项目源码获取方式见文章末尾&#xff01; 600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【PaddleNLP的FAQ问答机器人】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模型实现…

java list使用基本操作

import java.util.ArrayList; import java.util.Collection; import java.util.Iterator;public class Main {public static void main(String[] args) {ArrayList list new ArrayList();list.add("张三");list.add("李四");list.add("王五");l…

高级 <HarmonyOS主题课>借助AR引擎帮助应用实现虚拟与现实交互的能力的课后习题

持而盈之&#xff0c;不如其已&#xff1b; 揣而锐之&#xff0c;不可长保。 金玉满堂&#xff0c;莫之能守&#xff1b; 富贵而骄&#xff0c;自遗其咎。 功成身退&#xff0c;天之道也。 VR (Virtual Reality): 虚拟现实技术 AR (Augmented Reality): 增强现实) XR.(Extend…

高校实验室安全巡检系统设计与实现(源码+定制+开发)高校实验室巡检系统、实验室安全管理平台、实验室安全监控系统、智能实验室巡查系统、高校实验室风险管理

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

102、Python并发编程:Queue与生产者消费者模型实现解耦、协作

引言 在实际业务场景中&#xff0c;很多时候在处理复杂任务的时候&#xff0c;会拆分上下游各个环节&#xff0c;形成一个类似于流水线的处理方式。上游类似于生产者&#xff0c;下游要依赖上游的输出进行工作&#xff0c;类似于消费者。但是&#xff0c;很多时候&#xff0c;…

【梯度提升专题】XGBoost、Adaboost、CatBoost预测合集:抗乳腺癌药物优化、信贷风控、比特币应用|附数据代码...

全文链接&#xff1a;https://tecdat.cn/?p38115 分析师:Yang Yang&#xff0c;Kechen Zhao 在当今科技日新月异的时代&#xff0c;数据的有效利用成为各领域突破发展的关键。于医疗领域&#xff0c;乳腺癌的高发性与严重性不容忽视&#xff0c;优化抗乳腺癌候选药物的筛选与特…

机器学习与AI|如何利用数据科学优化库存周转率?

对于所有零售商来说&#xff0c;良好的库存管理都是非常重要的。众所周知&#xff0c;商品如果不放在货架上就无法出售&#xff0c;而如果库存过多则意味着严重的财务负担。 但是做好库存管理绝非易事&#xff0c;它依赖于对未来需求的准确预测和确保始终有合适库存的敏捷供应链…

安卓智能对讲终端|北斗有源终端|三防对讲机|单兵终端|单北斗

在当今快速发展的通信技术时代&#xff0c;智能对讲手持机已成为众多行业领域中不可或缺的通讯工具。QM240T安卓智能对讲手持机&#xff0c;作为一款集先进技术与实用功能于一身的高端设备&#xff0c;凭借其卓越的性能和多样化的应用特性&#xff0c;正逐步引领对讲机市场的革…

【数据集】【YOLO】【目标检测】抽烟识别数据集 6953 张,YOLO/VOC格式标注,吸烟检测!

数据集介绍 【数据集】抽烟识别数据集 6953 张&#xff0c;目标检测&#xff0c;包含YOLO/VOC格式标注。数据集中包含1种分类&#xff1a;“smoking”。数据集来自国内外图片网站和视频截图。检测范围园区吸烟检测、禁烟区吸烟检测、监控吸烟检测、无人机吸烟检测等。 主页私…

软件设计师-上午题-15 计算机网络(5分)

计算机网络题号一般为66-70题&#xff0c;分值一般为5分。 目录 1 网络设备 1.1 真题 2 协议簇 2.1 真题 3 TCP和UDP 3.1 真题 4 SMTP和POP3 4.1 真题 5 ARP 5.1 真题 6 DHCP 6.1 真题 7 URL 7.1 真题 8 浏览器 8.1 真题 9 IP地址和子网掩码 9.1 真题 10 I…

视频制作与剪辑怎么学,零基础入门视频剪辑和制作

视频制作与剪辑是一门充满创意与挑战的艺术形式&#xff0c;对于零基础的学习者来说&#xff0c;没选对软件不了解剪辑步骤&#xff0c;入门可能会显得有些棘手。接下来&#xff0c;我们将一同探讨如何开启视频剪辑与制作之旅&#xff0c;让新手从零基础入门&#xff0c;逐步迈…

[Element] el-table修改滚动条上部分的背景色

[Element] el-table修改滚动条上部分的背景色 ::v-deep .el-table__cell .gutter {background: red;}

SAP ABAP开发学习——WDA 七 使用文本与消息

目录 从数据字典读取文本 使用OTR文本 从程序中调用OTR文本 消息分类 定义消息显示位置 text类消息的使用 T100 消息的使用 OTR消息实例 消息内容修改 从数据字典读取文本 使用OTR文本 可以自己创建OTR文本 从程序中调用OTR文本 消息分类 定义消息显示位置 text类消息的…

基于物联网设计的地下煤矿安全监测与预警

文章目录 一、前言1.1 项目介绍【1】项目开发背景【2】设计实现的功能【3】项目硬件模块组成 1.2 设计思路1.3 系统功能总结1.4 开发工具的选择【1】设备端开发【2】上位机开发 1.5 模块的技术详情介绍【1】NBIOT-BC26模块【2】MQ5传感器【4】DHT11传感器【5】红外热释电人体检…

golang分布式缓存项目 Day 1

注&#xff1a;该项目原作者&#xff1a;https://geektutu.com/post/geecache-day1.html。本文旨在记录本人做该项目时的一些疑惑解答以及部分的测试样例以便于本人复习。 LRU缓存淘汰策略 三种缓存淘汰策略 FIFO&#xff08;First In, First Out&#xff09;先进先出 原理&…

Pr 视频过渡:沉浸式视频 - VR 默比乌斯缩放

效果面板/视频过渡/沉浸式视频/VR 默比乌斯缩放 Video Transitions/Immersive Video/VR Mobius Zoom VR 默比乌斯缩放 VR Mobius Zoom用于 VR 视频中的缩放式场景切换&#xff0c;通过缩小或放大的渐变效果在两个场景之间平滑过渡。 自动 VR 属性 Auto VR Properties 默认勾选…

华为实时视频使用FLV播放RTSP流

import flvjs from ‘flv.js’; 安装flv <video style"width:100%;height:100%;" ref"videoHWRef" ></video>// src 华为rtsp流 rtsp://admin:Huaweivideo10.10.8.151:554/xxx/trackID1// url 需要后端提供视频源地址playVideo() {if (fl…