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 ,format
    int w = 0;
    int h = 0;
    QString pix = 0;  //YUV420P RGBA

    X_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中的各个参数都是一样的,我们就没有必要free
	if (_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]读取大小也是 宽度*高度/4
		if (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);
        
        //显示fps
        stringstream 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());
        }
    }

}

八 。释放资源

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据集】【YOLO】【目标检测】道路垃圾识别数据集 8805 张,垃圾堆放识别数据集,YOLO垃圾识别算法实战训练教程!

数据集介绍 【数据集】道路垃圾识别、垃圾堆放识别数据集 8805 张&#xff0c;目标检测&#xff0c;包含YOLO/VOC格式标注。 数据集中包含2种分类&#xff1a;{0: garbage, 1: garbage_bag}&#xff0c;分别是普通垃圾和垃圾袋。 数据集来自国内外图片网站和视频截图&#x…

《TCP/IP网络编程》学习笔记 | Chapter 4:基于TCP的服务器端/客户端(2)

《TCP/IP网络编程》学习笔记 | Chapter 4&#xff1a;基于TCP的服务器端/客户端&#xff08;2&#xff09; 《TCP/IP网络编程》学习笔记 | Chapter 4&#xff1a;基于TCP的服务器端/客户端&#xff08;2&#xff09;回声客户端的完美实现回声客户端的问题回声客户端问题的解决方…

类加载过程详解

类的生命周期 类从被加载到虚拟机内存中开始到卸载出内存为止&#xff0c;它的整个生命周期可以简单概括为 7 个阶段&#xff1a;加载&#xff08;Loading&#xff09;、验证&#xff08;Verification&#xff09;、准备&#xff08;Preparation&#xff09;、解析&#xff08…

03-构建数据中台的三要素:方法论、组织和技术

03-构建数据中台的三要素&#xff1a;方法论、组织和技术 知道要转型&#xff0c;要建设数据中台&#xff0c;却不知咋做&#xff0c;咋办&#xff1f; 现在有很多讲“如何建设数据中台”文章&#xff0c;观点各不相同&#xff1a; 数据中台是数据建设方法论&#xff0c;按照数…

华为Mate70前瞻,鸿蒙NEXT正式版蓄势待发,国产系统迎来关键一战

Mate 70系列要来了 上个月&#xff0c;vivo、小米、OPPO、荣耀等众多智能手机制造商纷纷发布了他们的年度旗舰产品&#xff0c;手机行业内竞争异常激烈。 同时&#xff0c;华为首席执行官余承东在其个人微博上透露&#xff0c;Mate 70系列将标志着华为Mate系列手机达到前所未有…

源代码防泄密管理分享

随着信息技术的快速发展&#xff0c;软件已成为现代企业不可或缺的核心资产之一。然而&#xff0c;源代码作为软件的心脏&#xff0c;其安全性直接关系到企业的核心竞争力。为了有效防止源代码泄露&#xff0c;构建一套全面且高效的源代码安全管理体系显得尤为重要。以下是六个…

从神经元到神经网络:深度学习的进化之旅

神经元、神经网络 神经元 Neuron )&#xff0c;又名感知机( Perceptron )&#xff0c;在模型结构上与 逻辑回归 一致&#xff0c;这里以一个二维输入量的例子对其进行进一步 的解释&#xff1a; 假设模型的输 入向 量是一 维特征向 (x1,x2). 则单神 经元的模型结构 如下…

[C语言]strstr函数的使用和模拟实现

1.strstr函数的使用 char * strstr ( const char *str1, const char * str2); 返回一个指向str1中str2第一次出现的指针&#xff0c;如果str2中没有str1则返回 NULL。。 实例&#xff1a; #include <stdio.h> #include <string.h> int main() {char str[] "…

【论文速读】| RePD:通过基于检索的提示分解过程防御越狱攻击

基本信息 原文标题&#xff1a;RePD: Defending Jailbreak Attack through a Retrieval-based Prompt Decomposition Process 原文作者&#xff1a;Peiran Wang, Xiaogeng Liu, Chaowei Xiao 作者单位&#xff1a;University of Wisconsin–Madison 关键词&#xff1a;越狱…

React 前端通过组件实现 “下载 Excel模板” 和 “上传 Excel 文件读取内容生成对象数组”

文章目录 一、Excel 模板下载01、代码示例 二、Excel 文件上传01、文件展示02、示例代码03、前端样式展示04、数据结果展示 三、完整代码 本文的业务需求是建立在批量导入数据的情况下&#xff0c;普通组件只能少量导入&#xff0c;数据较多的情况都会选择 Excel 数据导入&…

基于YOLOv8 Web的安全帽佩戴识别检测系统的研究和设计,数据集+训练结果+Web源码

摘要 在工地&#xff0c;制造工厂&#xff0c;发电厂等地方&#xff0c;施工人佩戴安全帽能有效降低事故发生概率&#xff0c;在工业制造、发电等领域需要进行施工人员安全帽监测。目前大多数的 YOLO 模型还拘泥于公司、企业开发生产的具体产品中&#xff0c;大多数无编程基础…

内部知识库:优化企业培训流程的关键驱动力

在当今快速变化的商业环境中&#xff0c;企业培训的重要性日益凸显。内部知识库作为整合、管理和分享企业内部学习资源的关键工具&#xff0c;正逐步成为优化企业培训流程的核心。以下将探讨内部知识库如何通过多种功能&#xff0c;助力企业提升培训效率、质量和员工满意度。 …

TapData 发布官方性能测试报告,针对各流行数据源,在多项指标中表现拔群

近日&#xff0c;TapData 官方发布了最新的性能测试报告&#xff0c;该报告详细展示了 TapData v3.5.13 在各种数据源下的性能表现&#xff0c;包括全量同步、增量同步、读写延迟等关键性能指标。 随着企业对实时数据集成和处理能力需求的提升&#xff0c;TapData 凭借其高效、…

JDK1.5 java代码打包jar HmacSha256

文章目录 demo地址背景实现编写代码编译class文件打包 JAR 文件执行生成的 JAR 文件辅助验证方式 常见问题和解决方法常规生成jar方案maven插件idea工具 demo地址 https://github.com/xiangge-zx/HmacSha256 背景 最近接到一个需求,做一个可以用来HmacSha256加密的小工具&am…

【Python TensorFlow】进阶指南

在前文中&#xff0c;我们介绍了TensorFlow的基础知识及其在实际应用中的初步使用。现在&#xff0c;我们将进一步探讨TensorFlow的高级特性&#xff0c;包括模型优化、评估、选择、高级架构设计、模型部署、性能优化等方面的技术细节&#xff0c;帮助读者达到对TensorFlow的精…

Vue实现登录功能

一、Vue登录逻辑梳理&#xff1a; 1、登录流程&#xff1a; 用户在前端输入用户名和密码&#xff0c;点击登录按钮。 登录成功后的逻辑&#xff1a; 主要功能和流程&#xff1a; 异步函数 signInSuccess&#xff1a;这是一个异步函数&#xff0c;使用了 async 关键字&#xff…

「Mac畅玩鸿蒙与硬件26」UI互动应用篇3 - 倒计时和提醒功能实现

本篇将带领你实现一个倒计时和提醒功能的应用&#xff0c;用户可以设置倒计时时间并开始计时。当倒计时结束时&#xff0c;应用会显示提醒。该项目涉及时间控制、状态管理和用户交互&#xff0c;是学习鸿蒙应用开发的绝佳实践项目。 关键词 UI互动应用倒计时器状态管理用户交互…

(62)使用RLS自适应滤波器进行系统辨识的MATLAB仿真

文章目录 前言一、基本概念二、RLS算法原理三、RLS算法的典型应用场景四、MATLAB仿真代码五、仿真结果1.滤波器的输入信号、参考信号、输出信号、误差信号2.对未知系统进行辨识得到的系数 总结与后续 前言 RLS&#xff08;递归最小二乘&#xff09;自适应滤波器是一种用于系统…

Oracle 12C安装教程

Oracle 12c&#xff0c;全称Oracle Database 12c&#xff0c;是Oracle 11g的升级版&#xff0c;新增了很多新的特性。 Oracle 12c下载 打开Oracle的官方中文网站&#xff0c;选择相应的版本即可。 下载地址&#xff1a;http://www.oracle.com/technetwork/cn/database/enterp…

探索空间计算与 VR 设备的未来:4K4DGen 高分辨率全景 4D 内容生成系统

在当今科技飞速发展的时代,空间计算和 VR 设备正逐渐成为人们体验沉浸式场景的重要工具。而今天,我们要为大家介绍一款具有创新性的技术 ——4K4DGen 高分辨率全景 4D 内容生成系统,它为 VR/AR 沉浸式体验带来了全新的可能性。 一、项目概述 4K4DGen 项目的核心目标是实现 …