一套rk3588 rtsp服务器推流的 github 方案及记录 -01

我不生产代码,我只是代码的搬运工,相信我,看完这个文章你的图片一定能变成流媒体推出去。
诉求:使用opencv拉流,转成bgr数据,需要把处理后的数据(BGR)编码成264,然后推流推出去,相当于直播(实时编码)

播放器

超低延迟的RTSP播放器
https://github.com/tsingsee/EasyPlayer-RTSP-Win

青犀的一个播放器,直接下他的EasyPlayer-RTSP-Win用来测试就行。划重点,超低延时,我整体方案的延时大概是600-700ms,使用海康相机,rtsp拉流,做了yolo处理,再推出去,有编码,有解码,vlc的延时设置低了就回卡帧,Gop已经改成5了还是卡帧,没有测试Gop改成1的情况,但是vlc的延时和流畅,整体看是不太兼容的。ffmpeg使用nobuffer也会卡帧。直观感受卡的就是Gop的P帧。

服务器

live555 方案

如果你不着急的话。。。 可以试试这个方案,这方面的参考文献给列下面了,因为确实正经研究了几天还看了不少代码,认真想了应该怎么处理,但是确实不太想写,而且对我的需求来讲live555冗余了很多功能,再加上网上确实没有写好的,我又很着急要结果,确定方案能用,所以也没有用这个方案。

官方demo

live555自己的测试文件是有推流demo的,主要是根据实时需求推264文件,以及无脑做一个推264文件的服务器,当时看代码的时候一头雾水加上着急,也没太认真看,主要在live555\testProgs下面,testOnDemandRTSPServer,testH264VideoStreamer ,第二个是无脑推,第一个是你来一个请求,我从头开始给你播放一次视频文件。
这个东西的底层是向一个fTo指针里面拷贝264码流。
如果你Cpp、coding能力强的话,应该是能看懂直接改的,也就不用往后看了。

参考demo 零声 usb相机推流

网上基本上和我需求最接近的live555方案下的代码是国内的做音视频开发教学的一个零声出的视频还有他们传的这个代码。

主要功能是 v4l2相机读取mjpeg,然后ffmpeg的avcodec相关库编码,然后送live555,然后推实时流,像是改的testOnDemandRTSPServer,结构很清晰,除了不能用我也找不到原因外都挺好的。另外他的课是5K的,有点贵。

视频的话去B站带关键词,基本都能搜到,这个代码我加了那个联系QQ要到的。但是在我本地没有推成功,我也不确定是哪里的问题,编译过了,放在这里
https://gitee.com/qingfuliao/v4l2_ipc_live555?_from=gitee_search

在我这是下面这个demo 实现了一个相似的功能,编译实测是可以读取usb相机然后推流成功的。但是代码结构没有上面那个清晰。

参考demo

https://github.com/mpromonet/v4l2rtspserver

这个功能是基于linux的v4l2,使用264的方式读取相机视频流(如果你的usb相机不支持264输出,会驱动失败),然后直接拆帧把流发出去
需要自己下一个libv4l2cpp的代码放进来就能编译了

这个现在看稍微改一改就能用了,不过当时对整体没有概念,改了一阵子不知道怎么下手把我自己的码流变成demo的输入,码流送进去了但是没有推成功,定位了一会儿很难定位问题,也就搁置了。有兴趣的可以基于这个改一改。

https://blog.csdn.net/qq_43418269/article/details/122488866

这个方案我是成功了的,不过延时不太能满足我的需求,这个复现很快。他是用一个管道文件做的,我把编码之后264文件直接写到live555的testOnDemandRTSPServer.cpp这边的读取文件里面,然后逻辑是live555这边接收到请求,创建管道,相机程序初始化后阻塞住,管道被创建后往管道里写,然后另一边就开始播。。

这个就相当于是运行在一个demo里的两个程序。流倒是推出来了,只不过,我这样实现,延时很大,他说的百毫秒量级我做不到,我是2s左右。感觉也可能是我操作不当。

https://blog.csdn.net/lifexx/article/details/52823777

live555读文件改为内存读取实现,确实C++不太行,这个文章对我理解Live555,还有改成内存中的数据方向给了很大启发,但是没有按照他的做,而且他的参考代码无法运行。对我理解另一个推相机的demo有帮助

其他开源服务器框架

这个也是一个很容易就编译成功的服务器,可以使用这个做服务器,然后调用ffmpeg推流,在RK3588上也推成功了,基本没改make相关的配置,需要按照他给的快速开始流程使用git下附加库,功能很强大,但是对我的需求来讲,这个功能我进行二次开发比较慢。不排除我太菜。
https://github.com/ZLMediaKit/ZLMediaKit/tree/master

这里写这个的主要原因是他的一些文章对我的启发和快速上手有很大的参考意义,比如下面这个。

https://github.com/ZLMediaKit/ZLMediaKit/wiki/%E6%80%8E%E4%B9%88%E6%B5%8B%E8%AF%95ZLMediaKit%E7%9A%84%E5%BB%B6%E6%97%B6%EF%BC%9F

RK3399 参考

网上有瑞芯微其他方案的rtsp推流,我只能帮忙排除错误选项
https://t.rock-chips.com/forum.php?mod=viewthread&tid=749&extra=page%3D1
如果你是在看这个帖子,可以不用看了,这个猫头的代码虽然推出去了,但是他的Rtsp是调库,这个库是闭源的,3588没有,这个网站注册要两三天才能通过,不必等这个,用不了。

rtsp推流

在github上搜索rtsp,排名最高的那个结果,就是那个小乌龟,

https://github.com/PHZ76/RtspServer
这个功能比较单一,但是足够满足我的需求了,他还使用他自己的库做了一个windows下的应用,windows上编译成功了,但是不太好用,不过对我理解他的Demo运行有一定帮助。因为用的是一套库。另外他主页还有一个rtmp ,我没进去看也没试能不能用。

https://github.com/PHZ76/DesktopSharing

在这里插入图片描述

下载安装

下载下来直接在3588上面make就可以,编译的是RtspServer-master/example里面的main 文件,这里的rtsp_h264_file.cpp是可以直接推运行推264文件的,一般不需要修改就能直接用,如果不能用,有可能是554端口被占用,改一个大点的就好了

    std::string suffix = "live";std::string ip = "127.0.0.1";std::string port = "5543";// 改这里 不要改那个"0.0.0.0" 那个是对的不用改std::string rtsp_url = "rtsp://" + ip + ":" + port + "/" + suffix;

然后rtsp_pusher.cpp还有rtsp_server.cpp都把发送文件的部分注释掉了,需要结合h264那个文件来对比把264码流写进去。

运行测试

编译出来之后运行 ./rtsp_h264_file ./test.264 就能推出来了
工程已经被我魔改过了,重新生成一个sample 来演示下结果
这是RK3588 服务器
在这里插入图片描述

这是VLC的界面IP
在这里插入图片描述

下面的是显示的结果
在这里插入图片描述

改cmake 支持opencv 、rknn,mpp

因为我使用的RKNN之间是在Qt里面编译的,工程使用的都是Cmake的cmakelists,他的makefile也不难改,主要的问题是我自己写的解码器,在使用makefile指定mpp库之后编译出来的mpp库运行不正常,具体报错找不到了,然后我qt上用是没问题的,定位到是makefile没写好,改成cmakelist就可以正常编译了
-g是为了支持gdb调试,配合我的vscode 调试配置文件,可以单步调试,全程在板上编译,没配置交叉编译环境

cmake_minimum_required(VERSION 3.5)
project(rtspserver)
set(CMAKE_INCLUDE_CURRENT_DIR ON)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSOCKLEN_T=socklen_t -g ")find_package(OpenCV 4.5.5 REQUIRED)
find_package(OpenSSL REQUIRED)# MPP
set(MPP_PATH /home/orangepi/code/mpp-develop/inc)
set(MPP_LIBS /home/orangepi/code/mpp-develop/mpp/librockchip_mpp.so)
include_directories(${MPP_PATH})# OSAL
set(OSAL_PATH /home/orangepi/code/mpp-develop/osal/inc/ /home/orangepi/code/mpp-develop/utils)
set(OSAL_LIBS /home/orangepi/code/mpp-develop/osal/libosal.a /home/orangepi/code/mpp-develop/utils/libutils.a)
include_directories(${OSAL_PATH})# RKNN lib
set(RKNN_API_PATH ${CMAKE_SOURCE_DIR}/lib)
set(RKNN_RT_LIB ${RKNN_API_PATH}/aarch64/librknnrt.so)
include_directories(${RKNN_API_PATH}/include)
include_directories(${CMAKE_SOURCE_DIR}/3rdparty)# RGA
set(RGA_PATH ${CMAKE_SOURCE_DIR}/3rdparty/rga/RK3588)
set(RGA_LIB ${RGA_PATH}/lib/Linux/aarch64/librga.so)
include_directories(${RGA_PATH}/include)aux_source_directory(src/xop SOURCE1)
aux_source_directory(src/net SOURCE2)include_directories(    src    src/xopsrc/netsrc/3rdpart)# add_executable(rtsp
# example/rtsp_server.cpp
# ${SOURCE1}
# ${SOURCE2}
# src/3rdpart/md5/md5.hpp
# )
# target_link_libraries(rtsp ${MPP_LIBS} ${OSAL_LIBS}  ${OpenCV_LIBS} ${RKNN_RT_LIB} ${RGA_LIB} OpenSSL::SSL OpenSSL::Crypto )
# add_executable(rh264
# example/rtsp_h264_file.cpp
# ${SOURCE1}
# ${SOURCE2}
# src/3rdpart/md5/md5.hpp
# )
# target_link_libraries(rh264 ${MPP_LIBS} ${OSAL_LIBS}  ${OpenCV_LIBS} OpenSSL::SSL OpenSSL::Crypto )
add_executable(sample
example/sample.cpp
${SOURCE1}
${SOURCE2}
src/3rdpart/md5/md5.hpp
)
target_link_libraries(sample ${MPP_LIBS} ${OSAL_LIBS}  ${OpenCV_LIBS}  ${RKNN_RT_LIB} ${RGA_LIB} OpenSSL::SSL OpenSSL::Crypto )
代码修改

代码是基于他的rtsp_server来修改的,主要修改的内容是sendFrameThread,大概思路是这样的,还差一个问题,怎么把你的原始mat图像转成264码流

bool IsKeyFrame(const char* data, uint32_t size)
{if (size > 4) {//0x67:sps ,0x65:IDR, 0x6: SEIif (data[4] == 0x67 || data[4] == 0x65 || data[4] == 0x6 || data[4] == 0x27) {return true;}}return false;
}void SendFrameThread(xop::RtspServer* rtsp_server, xop::MediaSessionId session_id, int& clients)
{       encoder e;//  encoder相关 内存拷贝int size = 0;char* buffer ;// 编码标志位int i = 0;// 生成图像int width = 1920;int height = 1080;cv::Mat colorBar= cv::Mat::zeros(height, width, CV_8UC3);// 设置彩条的宽度int barWidth = width / 8; // 8个彩条,你可以根据需要调整// 生成彩条for (int i = 0; i < 8; ++i) {// 计算彩条的起始和结束位置int startX = i * barWidth;int endX = (i + 1) * barWidth;// 设置彩条颜色(BGR格式)cv::Vec3b color;if (i % 2 == 0) {color = cv::Vec3b(255, 0, 155); // 蓝色} else {color = cv::Vec3b(0, 255, 0); // 绿色}// 在colorBar上画出彩条colorBar(cv::Rect(startX, 0, barWidth, height)) = color;}while(1){if(clients > 0) /* 会话有客户端在线, 发送音视频数据 */{{     xop::AVFrame videoFrame = {0};// printf("width is %d, height is %d",colorBar.rows,colorBar.cols);// 编码 发包if(0==i){// 第一帧有sps信息 给他两帧拼一起char *buffer1;int size1;e.init(buffer1,size1);videoFrame.size = size1;e.postAframe(colorBar,buffer,size);videoFrame.size += size;					videoFrame.buffer.reset(new uint8_t[videoFrame.size]);memcpy(videoFrame.buffer.get(), buffer1, size1);memcpy(videoFrame.buffer.get()+size1, buffer, size);i++;}		else{e.postAframe(colorBar,buffer,size);videoFrame.size = size;  // 视频帧大小 videoFrame.buffer.reset(new uint8_t[videoFrame.size]);memcpy(videoFrame.buffer.get(), buffer, videoFrame.size);}videoFrame.type = IsKeyFrame(buffer, size) ? xop::VIDEO_FRAME_I : xop::VIDEO_FRAME_P;// videoFrame.type = 0; // 建议确定帧类型。I帧(xop::VIDEO_FRAME_I) P帧(xop::VIDEO_FRAME_P)videoFrame.timestamp = xop::H264Source::GetTimestamp(); // 时间戳, 建议使用编码器提供的时间戳// writeCharPointerToFile((char *)videoFrame.buffer.get(), videoFrame.size,  "filename.txt");		rtsp_server->PushFrame(session_id, xop::channel_0, videoFrame); //送到服务器进行转发, 接口线程安全/*//获取一帧 H264, 打包xop::AVFrame videoFrame = {0};videoFrame.type = 0; // 建议确定帧类型。I帧(xop::VIDEO_FRAME_I) P帧(xop::VIDEO_FRAME_P)videoFrame.size = video frame size;  // 视频帧大小 videoFrame.timestamp = xop::H264Source::GetTimestamp(); // 时间戳, 建议使用编码器提供的时间戳videoFrame.buffer.reset(new uint8_t[videoFrame.size]);                    memcpy(videoFrame.buffer.get(), video frame data, videoFrame.size);					rtsp_server->PushFrame(session_id, xop::channel_0, videoFrame); //送到服务器进行转发, 接口线程安全*/}{				/*//获取一帧 AAC, 打包xop::AVFrame audioFrame = {0};audioFrame.type = xop::AUDIO_FRAME;audioFrame.size = audio frame size;  /* 音频帧大小 audioFrame.timestamp = xop::AACSource::GetTimestamp(44100); // 时间戳audioFrame.buffer.reset(new uint8_t[audioFrame.size]);                    memcpy(audioFrame.buffer.get(), audio frame data, audioFrame.size);rtsp_server->PushFrame(session_id, xop::channel_1, audioFrame); // 送到服务器进行转发, 接口线程安全*/}		}// xop::Timer::Sleep(20);  /* 实际使用需要根据帧率计算延时!  我这里处理延时很大,就不人工延迟了*/}videocapture->release();e.deinit((MPP_RET)0);yolo5.deinit();}

rk3588编码

这方面网上的文章不太多,但是官方给了demo,都是中文,认真看看,功能都是能用的。
主要参考他的mpp-develop/test/mpi_enc_test 以及Rk3588-linux-v002\linux\docs\Linux\Multimedia\Rockchip_Developer_Guide_MPP_CN.pdf进行配置和使用。这相关的东西我之后再另开一个文章单独说,总之参考这部分可以做一个BGR888转成264存储的demo。
这里重点说一个概念,I、P、B帧
这个东西是264流编码的一个概念,正常你每一帧的图像都很大,比如一个1920*1080,每个像素点存一个BGR888的话,就是1920*1080*3 Byte =6220800 Byte ≈ 6M ,然后一秒30帧的话,一秒就要传180M,局域网或许勉强可以,但是对带宽压力也很大。所以这里就涉及到了压缩,264、265就是压缩标准,压缩中需要做两种压缩 帧内压缩帧间压缩

  • 帧内压缩:使用一定方法使用尽量小的空间存一帧数据。

  • 帧间压缩:利用帧和前后帧的关联来进一步的压缩视频。

这里我们重点关注帧间压缩。给一个参考文献
https://zhuanlan.zhihu.com/p/409527359
说的挺透彻的,这里我粗略说一下,看下面这个图
在这里插入图片描述

I帧 ,是帧间压缩里面的一帧完整图像,P帧是前向预测帧,B帧是双向预测帧
IDR帧是特殊的I帧,在编解码时候P、B帧可以参考I帧前面的帧进行复原,但是不能参考IDR帧前面的帧进行复原

而一个上面这样的循环,被编码成一组,我们指定了h264的gop大小,就确定了多少帧中有一个I帧
而我们在做直播,就导致为了低延时,不要B帧,gop 也要尽量的不要太大,如果要解码P帧,前面就一定要缓存I帧,

监测工具

wireshark, 这个感觉还是蛮必要的,至少你能看见客户端和服务器之间说没说话。如果你有耐心开RTSP 或者网络协议握手说明的话,你甚至能看到他们之间的握手流程。

杂记

中间遇到了一个问题,因为我这个地方没有公网,所以只能自己给开一个热点给电脑,PC和RK3588之间通过路由器连接,然后使用前面的某个demo的时候遇到的,主机向服务器(RK3588)发出视频请求时,服务器并没有直接给客户端发rtcp包,而是给一个200多的地址发包,而这个包在客户端收不到,但是他能收到rtp的协议包,所以vlc这边也不提示打不开,就是没有图像显示。
后来使用的方法是,和板子通过路由器网线连接,然后电脑PC wifi 连接路由器,然后路由器没有公网,再使用一个手机usb给电脑共享网络,让我的调试环境稳定可以接受到3588的流,和路由器网线连接的时候接受不到包,感觉是因为路由器没有网,使用网线连接时候被屏蔽了服务器功能,所以交给路由器的包转发请求没有被PC识别到,但是wifi连接的话,就算他没有网,也是不能忽略的。 目前是这样理解的。

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

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

相关文章

字符串函数strtok

1.调用格式&#xff1a; 2.调用形式&#xff1a;char*strtok(char*p1,const char*p2),其中第二个是由分隔符组成的字符串&#xff0c;第一个为需要分隔的字符串 3.调用目的&#xff1a;将分隔符之间的字符串取出 4.调用时一般将源字符串拷贝后调用&#xff0c;因为此函数会将…

基于Unity3D 低多边形地形模型纹理贴图

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 当谈到游戏角色的3D模型风格时&#xff0c;有几种不同的风格&#xf…

【工程实践】使用modelscope下载大模型文件

前言 Modelscope&#xff08;魔搭社区&#xff09;是阿里达摩院的一款开源模型平台&#xff0c;里面提供了很多的热门模型供使用体验&#xff0c;其中的模型文件可以通过git clone 快速下载。并且为模型提供了Notebook的快速开发体验&#xff0c;使用阿里云服务&#xff0c;不需…

【优选算法系列】【专题二滑动窗口】第三节.904. 水果成篮和438. 找到字符串中所有字母异位词

文章目录 前言一、水果成篮 1.1 题目描述 1.2 题目解析 1.2.1 算法原理 1.2.2 代码编写 1.2.3 题目总结二、找到字符串中所有字母异位词 2.1 题目描述 2.2 题目解析 2.2.1 算法原理 2.2.2 代码编写 …

SAP UI5 walkthrough step9 Component Configuration

在之前的章节中&#xff0c;我们已经介绍完了MVC的架构和实现&#xff0c;现在我们来讲一下&#xff0c;SAPUI5的结构 这一步&#xff0c;我们将所有的UI资产从index.html里面独立封装在一个组件里面 这样组件就变得独立&#xff0c;可复用了。这样&#xff0c;无所什么时候我…

队列的实现

学习就像一段长跑&#xff0c;比的不是谁跑得快&#xff0c;而是谁更能坚持&#xff01;&#xff01; 1 队列的概念及结构 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c;队列具有先进先出 FIFO(First In First O…

外网访问内网服务器使用教程

如何在任何地方都能访问自己家里的笔记本上的应用&#xff1f;如何让局域网的服务器可以被任何地方访问到&#xff1f;有很多类似的需求&#xff0c;我们可以统一用一个解决方案&#xff1a;内网穿透。内网穿透的工具及方式有很多&#xff0c;如Ngrok、Ssh、autossh、Natapp、F…

特殊进程之守护进程

文章目录 1、守护进程的概念2、如何查看守护进程3、编写守护进程的步骤3.1 创建子进程&#xff0c;父进程退出3.2 在子进程中创建新会话3.3 改变当前工作目录3.4 重设文件权限掩码3.5 关闭不需要的文件描述符3.6 某些特殊的守护进程打开/dev/null 4、守护进程代码示例 1、守护进…

[UNILM]论文实现:Unified Language Model Pre-training for Natural Language.........

文章目录 一、完整代码二、论文解读2.1 介绍2.2 架构2.3 输入端2.4 结果 三、过程实现四、整体总结 论文&#xff1a;Unified Language Model Pre-training for Natural Language Understanding and Generation 作者&#xff1a;Li Dong, Nan Yang, Wenhui Wang, Furu Wei, Xia…

MyBatis--07--启动过程分析、SqlSession安全问题、拦截器

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 谈谈MyBatis的启动过程具体的操作过程如下&#xff1a;实现测试类,并测试SqlSessionFactorySqlSession SqlSession有数据安全问题?在MyBatis中&#xff0c;SqlSess…

vuex如何存储数据、获取数据、以及数据的持久化

前提必须已经在vue中安装了vuex插件不然无法使用&#xff0c;不知道怎么创建vue和安装vuex的可以看这个视频&#xff0c;node.js版本最好16以上不然可能会安装失败&#xff1a;30分钟学会Vue之VueRouter&Vuex 趁着暑假掌握一门技能 大学生前端实习毕业设计必备技能_哔哩哔哩…

好代码资源网整站打包代码(包含了最新数据),集成了深度二开的ripro主题,非常适合做资源网站创业用

好代码资源网是基于wordpress开发的一个资源分享类网站&#xff0c;在开发者圈子里还算小有名气&#xff0c;这里分享婴整站打包代码&#xff08;包含了最新数据&#xff09;。网站本身集成了深度二开的ripro主题&#xff0c;非常适合做资源网站创业用。 资源下载类网站目前还…

Button背景颜色改不了,一直是默认的紫色

使用android.widget.Button <android.widget.Buttonandroid:layout_width"wrap_content"android:layout_height"wrap_content"android:onClick"doClick"android:text"这是一个按钮"android:textColor"color/black"androi…

kubesphere安装后启用DevOps

官方文档&#xff1a;KubeSphere DevOps 系统 1、集群管理---定制资源定义 进入目录&#xff1a;集群管理---定制资源定义搜索&#xff1a;clusterconfiguration 点击 ks-installer 右侧的 &#xff0c;选择编辑 YAML 在该 YAML 文件中&#xff0c;搜索 devops&#xff0c;…

No CUDA GPUs are available

文章目录 前言尝试方法一、尝试方法一二、尝试方法二 总结 前言 之前用服务器跑的时候&#xff0c;发现是可以跑的。但当有其他人一同使用的时候&#xff0c;就会抛出&#xff1a;No CUDA GPUs are available&#xff0c;这个时候我尝试了以下两种方式解决&#xff0c;后面终于…

一到冬天,助听器出现声音小、无声、时有时无……

冬天是一个寒冷干燥的季节&#xff0c;对于助听器的使用者来说&#xff0c;也是一个需要特别注意保养的季节。助听器是高精密的电子产品&#xff0c;如果不注意保养&#xff0c;可能会出现声音小、无声、时有时无等故障&#xff0c;影响听力康复的效果。那么&#xff0c;冬天我…

C++中string类的使用

目录 一.string类 1.1为什么学习string类&#xff1f; 1.2.标准库中的string类 二.string对象的元素访问 2.1.1使用operator[]与at实现访问 2.1.2正向迭代器访问 2.1.3反向迭代器访问 2.1.4const正向迭代器&#xff08;不能修改&#xff09; 2.1.5const反向迭代器&#…

垃圾收集算法和各种垃圾收集器的实现

深入理解Jvm虚拟机第三章 二、对象已死&#xff1f;3.2.1 引用计数算法3.2.2 可达性分析算法3.2.3 再谈引用3.2.4 生存还是死亡3.2.5 回收方法区 三、垃圾收集算法3.3.1 分代收集理论3.3.2 标记-清除算法3.3.3 标记-复制算法3.3.4 标记-整理算法 四、HotSpot的算法细节实现3.4.…

C# WPF上位机开发(串口界面设计)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 如果只是把上位机看成是纯软件开发&#xff0c;本身不和硬件打交道的话&#xff0c;那么这就把上位机的操作范围给限定死了。事实上&#xff0c;上…

数据库系统概论期末经典大题讲解(范式提升、求闭包、求主码)

上一次我们介绍了数据库中关系代数查询&#xff0c;从选择、投影到连接等操作符&#xff0c;探索了数据库查询 大家可以移步我的文章&#xff1a;数据库系统概论期末经典大题讲解&#xff08;用关系代数进行查询&#xff09;-CSDN博客 今天&#xff0c;我们将继续沿着数据库系统…