当前位置: 首页 > news >正文

live555关于RTSP协议交互流程

RTP在和h264

RTP在和h265

RTP载荷AAC

live555关于RTSP协议交互流程

live555的核心数据结构值之闭环双向链表

live555 rtsp服务器实战之createNewStreamSource

概要

        rtsp在交互的过程中用到很多协议:tcp,udp,rtp,rtcp,sdp等协议;该篇文章主要分析在live555中这些协议是什么时候被创建的,什么时候被使用的等协议相关流程。

TCP:服务器与客户端进行协商(OPTION DESCRIBE SETUP PLAY);

UDP/TCP:协议是rtsp服务器用来想客户端推流;当然rtsp向客户端推流也可以使用tcp协议;那么就rtsp而言使用udp推流和使用tcp推流有什么区别呢?

UDP推流

        tcp连接进行rtsp信令交互;

        创建新的udp套接字来发送rtp包;

        创建新的udp套接字来发送rtcp包;

TCP推流

        tcp连接进行rtsp信令交互;

        复用rtsp的tcp连接发送rtp和rtcp包;

嵌入式开发一般使用udp推流,实时性相对较高;

RTP:对视频流(h264/h265)/音频流(AAC/MP3)裸流进行封装,用于网络传输;

RTCP:服务器和客户端用来管理流媒体协议;

TCP交互协商

在程序创建RTSPServer类对象时就会创建用于信令协商的TCP协议,见如下代码:

//创建RTSPServer类对象
RTSPServer* rtspServer = RTSPServer::createNew(*env, 8554, authDB);
//createNew实现
RTSPServer*
RTSPServer::createNew(UsageEnvironment& env, Port ourPort,UserAuthenticationDatabase* authDatabase,unsigned reclamationSeconds) {int ourSocketIPv4 = setUpOurSocket(env, ourPort, AF_INET);int ourSocketIPv6 = setUpOurSocket(env, ourPort, AF_INET6);if (ourSocketIPv4 < 0 && ourSocketIPv6 < 0) return NULL;return new RTSPServer(env, ourSocketIPv4, ourSocketIPv6, ourPort, authDatabase, reclamationSeconds);
}

        从源码可以看出创建RTSPServer类对象的时候会创建ipv4和ipv6两种套接字,因此理论上来说live555实现的rtsp服务器支持ipv4和ipv6两种网络传输。

//RTSPServer构造函数
RTSPServer::RTSPServer(UsageEnvironment& env,int ourSocketIPv4, int ourSocketIPv6, Port ourPort,UserAuthenticationDatabase* authDatabase,unsigned reclamationSeconds): GenericMediaServer(env, ourSocketIPv4, ourSocketIPv6, ourPort, reclamationSeconds),fHTTPServerSocketIPv4(-1), fHTTPServerSocketIPv6(-1), fHTTPServerPort(0),fClientConnectionsForHTTPTunneling(NULL), // will get created if neededfTCPStreamingDatabase(HashTable::create(ONE_WORD_HASH_KEYS)),fPendingRegisterOrDeregisterRequests(HashTable::create(ONE_WORD_HASH_KEYS)),fRegisterOrDeregisterRequestCounter(0), fAuthDB(authDatabase),fAllowStreamingRTPOverTCP(True),fOurConnectionsUseTLS(False), fWeServeSRTP(False) {
}
//GenericMediaServer构造函数
GenericMediaServer
::GenericMediaServer(UsageEnvironment& env, int ourSocketIPv4, int ourSocketIPv6, Port ourPort,unsigned reclamationSeconds): Medium(env),fServerSocketIPv4(ourSocketIPv4), fServerSocketIPv6(ourSocketIPv6),fServerPort(ourPort), fReclamationSeconds(reclamationSeconds),fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)),fClientConnections(HashTable::create(ONE_WORD_HASH_KEYS)),fClientSessions(HashTable::create(STRING_HASH_KEYS)),fPreviousClientSessionId(0),fTLSCertificateFileName(NULL), fTLSPrivateKeyFileName(NULL) {ignoreSigPipeOnSocket(fServerSocketIPv4); // so that clients on the same host that are killed don't also kill usignoreSigPipeOnSocket(fServerSocketIPv6); // ditto// Arrange to handle connections from others:env.taskScheduler().turnOnBackgroundReadHandling(fServerSocketIPv4, incomingConnectionHandlerIPv4, this);env.taskScheduler().turnOnBackgroundReadHandling(fServerSocketIPv6, incomingConnectionHandlerIPv6, this);
}

        在GenericMediaServer构造函数中会把创建的fServerSocketIPv4和fServerSocketIPv6这两个套接字插入到双向闭环链表中等待doEventLoop循环处理,对应的处理函数分别为:incomingConnectionHandlerIPv4, incomingConnectionHandlerIPv6;最终都会调用incomingConnectionHandlerOnSocket函数;

void GenericMediaServer::incomingConnectionHandlerOnSocket(int serverSocket) {struct sockaddr_storage clientAddr;SOCKLEN_T clientAddrLen = sizeof clientAddr;int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);if (clientSocket < 0) {int err = envir().getErrno();if (err != EWOULDBLOCK) {envir().setResultErrMsg("accept() failed: ");}return;}ignoreSigPipeOnSocket(clientSocket); // so that clients on the same host that are killed don't also kill usmakeSocketNonBlocking(clientSocket);increaseSendBufferTo(envir(), clientSocket, 50*1024);#ifdef DEBUGenvir() << "accept()ed connection from " << AddressString(clientAddr).val() << "\n";
#endif// Create a new object for handling this connection:(void)createNewClientConnection(clientSocket, clientAddr);
}
//createNewClientConnection函数实现
GenericMediaServer::ClientConnection*
RTSPServer::createNewClientConnection(int clientSocket, struct sockaddr_storage const& clientAddr) {return new RTSPClientConnection(*this, clientSocket, clientAddr, fOurConnectionsUseTLS);
}

        在doEventLoop循环中会议中accept监视tcp连接,如果有客户端连接就会创建客户端连接类RTSPClientConnection;最终会把客户端套接字clientSocket传递给ClientConnection构造函数;

GenericMediaServer::ClientConnection
::ClientConnection(GenericMediaServer& ourServer,int clientSocket, struct sockaddr_storage const& clientAddr,Boolean useTLS): fOurServer(ourServer), fOurSocket(clientSocket), fClientAddr(clientAddr), fTLS(envir()) {fInputTLS = fOutputTLS = &fTLS;// Add ourself to our 'client connections' table:fOurServer.fClientConnections->Add((char const*)this, this);if (useTLS) {// Perform extra processing to handle a TLS connection:fTLS.setCertificateAndPrivateKeyFileNames(ourServer.fTLSCertificateFileName,ourServer.fTLSPrivateKeyFileName);fTLS.isNeeded = True;fTLS.tlsAcceptIsNeeded = True; // call fTLS.accept() the next time the socket is readable}// Arrange to handle incoming requests:resetRequestBuffer();envir().taskScheduler().setBackgroundHandling(fOurSocket, SOCKET_READABLE|SOCKET_EXCEPTION, incomingRequestHandler, this);
}
//incomingRequestHandler函数最终调用
void GenericMediaServer::ClientConnection::incomingRequestHandler() {if (fInputTLS->tlsAcceptIsNeeded) { // we need to successfully call fInputTLS->accept() first:if (fInputTLS->accept(fOurSocket) <= 0) return; // either an error, or we need to try again laterfInputTLS->tlsAcceptIsNeeded = False;// We can now read data, as usual:}int bytesRead;if (fInputTLS->isNeeded) {bytesRead = fInputTLS->read(&fRequestBuffer[fRequestBytesAlreadySeen], fRequestBufferBytesLeft);} else {struct sockaddr_storage dummy; // 'from' address, meaningless in this casebytesRead = readSocket(envir(), fOurSocket, &fRequestBuffer[fRequestBytesAlreadySeen], fRequestBufferBytesLeft, dummy);}handleRequestBytes(bytesRead);//该函数实现了对 OPTION DESCRIBE SETUP等各种信令的处理逻辑
}

        在构造函数中setBackgroundHandling会把客户端套接字fOurSocket和对应的处理函数incomingRequestHandler添加到闭环双链表中,在doEventLoop中循环遍历,客户端有信令交互就调用相关的处理函数;至此用于协商的TCP协议处理流程就结束了。

关于live555的闭环双向链表参考我的另一篇文章:live555的核心数据结构值之闭环双向链表-CSDN博客

UDP流媒体传输

        UDP流媒体传输服务器需要创建两个四个UDP套接字,用于传输音频RTP,音频RTCP,视频RTP,视频RTCP;该文档是以H264的传输为例所以只介绍视频RTP端口,视频RTCP端口的创建过程,音频类似;

        RTP,RTCP端口是在SETUP信令处理函数handleCmd_SETUP中被创建,该函数最终调用了getStreamParameters函数:

subsession->getStreamParameters(fOurSessionId, fOurClientConnection->fClientAddr,clientRTPPort, clientRTCPPort,fStreamStates[trackNum].tcpSocketNum, rtpChannelId, rtcpChannelId,&fOurClientConnection->fTLS,destinationAddress, destinationTTL, fIsMulticast,serverRTPPort, serverRTCPPort,fStreamStates[trackNum].streamToken);

        该函数将客户端的RTP端口:clientRTPPort和RTCP端口:clientRTCPPort都进行了处理;这两个端口是客户端发送SETUP信令时携带的消息;告诉服务器RTP RTCP包改往哪里发;getStreamParameters也创建了服务器的RTP RTCP端口:serverRTPPort, serverRTCPPort;

getStreamParameters内部调用了createGroupsock函数:

void OnDemandServerMediaSubsession ::getStreamParameters(...)
{...if (clientRTPPort.num() != 0 || tcpSocketNum >= 0){ // Normal case: Create destinationsportNumBits serverPortNum;if (clientRTCPPort.num() == 0){// We're streaming raw UDP (not RTP). Create a single groupsock:NoReuse dummy(envir()); // ensures that we skip over ports that are already in usefor (serverPortNum = fInitialPortNum;; ++serverPortNum){serverRTPPort = serverPortNum;rtpGroupsock = createGroupsock(nullAddress(destinationAddress.ss_family), serverRTPPort);if (rtpGroupsock->socketNum() >= 0)break; // success}udpSink = BasicUDPSink::createNew(envir(), rtpGroupsock);}else{// Normal case: We're streaming RTP (over UDP or TCP).  Create a pair of// groupsocks (RTP and RTCP), with adjacent port numbers (RTP port number even).// (If we're multiplexing RTCP and RTP over the same port number, it can be odd or even.)NoReuse dummy(envir()); // ensures that we skip over ports that are already in usefor (portNumBits serverPortNum = fInitialPortNum;; ++serverPortNum){serverRTPPort = serverPortNum;//创建RTP端口(rtp的UDP套接字)rtpGroupsock = createGroupsock(nullAddress(destinationAddress.ss_family), serverRTPPort);if (rtpGroupsock->socketNum() < 0){delete rtpGroupsock;continue; // try again}if (fMultiplexRTCPWithRTP){// Use the RTP 'groupsock' object for RTCP as well:serverRTCPPort = serverRTPPort;rtcpGroupsock = rtpGroupsock;}else{// Create a separate 'groupsock' object (with the next (odd) port number) for RTCP://RTCP端口号在RTP端口号的基础上加1serverRTCPPort = ++serverPortNum;//创建RTCP端口(rtcp的UDP套接字)rtcpGroupsock = createGroupsock(nullAddress(destinationAddress.ss_family), serverRTCPPort);if (rtcpGroupsock->socketNum() < 0){delete rtpGroupsock;delete rtcpGroupsock;continue; // try again}}break; // success}unsigned char rtpPayloadType = 96 + trackNumber() - 1; // if dynamicrtpSink = mediaSource == NULL ? NULL: createNewRTPSink(rtpGroupsock, rtpPayloadType, mediaSource);if (rtpSink != NULL){if (fParentSession->streamingUsesSRTP){rtpSink->setupForSRTP(fMIKEYStateMessage, fMIKEYStateMessageSize);}if (rtpSink->estimatedBitrate() > 0)streamBitrate = rtpSink->estimatedBitrate();}}...
}

        由代码可以看出serverRTPPort的初始值是fInitialPortNum;而fInitialPortNum在创建OnDemandServerMediaSubsession对象时有个默认值6970;如果没有设置端口号则使用默认端口号;

        上面代码可以看出而RTCP端口号是在RTP的端口号的基础上加1

OnDemandServerMediaSubsession(UsageEnvironment& env, Boolean reuseFirstSource,portNumBits initialPortNum = 6970,Boolean multiplexRTCPWithRTP = False);

        当第二个客户端连接时,依然是从6970开始创建所需的RTP RTCP端口号,但是createGroupsock会发现6970 6971端口号被占用,于是返回-1;继续for循环将端口号累加;

 for (portNumBits serverPortNum = fInitialPortNum;; ++serverPortNum){serverRTPPort = serverPortNum;rtpGroupsock = createGroupsock(nullAddress(destinationAddress.ss_family), serverRTPPort);if (rtpGroupsock->socketNum() < 0){delete rtpGroupsock;continue; // try again}...}

//fInitialPortNum为基数6970;

第一个客户端:rtp:6970 rtcp:6971

第二个客户端:6970 6971 被占用createGroupsock返回-1;因此for循环continue继续累加++serverPortNum; rtp:6972 rtcp:6973

......

那么怎么自定义端口号呢?

        我们在做rtsp服务器的时候都会创建一个类用于实现createNewStreamSource虚函数该类继承于OnDemandServerMediaSubsession;而类的构造函数里会执行OnDemandServerMediaSubsession的构造函数;所以如果你想要自己定义服务器的RTP端口号只需在执行OnDemandServerMediaSubsession构造函数是传入参数即可:

H264LiveVideoServerMediaSubssion::H264LiveVideoServerMediaSubssion(UsageEnvironment &env, Boolean reuseFirstSource): OnDemandServerMediaSubsession(env, reuseFirstSource, 1234) {}

        TCP流媒体传输使用的时信令交互的套接字,这里不做解释;关于流媒体裸流怎么打包成RTP的参考上面的文章;

该文章在持续更新,望持续关注;

相关文章:

live555关于RTSP协议交互流程

RTP在和h264 RTP在和h265 RTP载荷AAC live555关于RTSP协议交互流程 live555的核心数据结构值之闭环双向链表 live555 rtsp服务器实战之createNewStreamSource 概要 rtsp在交互的过程中用到很多协议:tcp,udp,rtp,rtcp,sdp等协议&#xff1b;该篇文章主要分析在live555中这些…...

Centos7 安装私有 Gitlab

在 CentOS 7上&#xff0c;下面的命令也会在系统防火墙中打开 HTTP、HTTPS 和 SSH 访问。这是一个可选步骤&#xff0c;如果您打算仅从本地网络访问极狐GitLab&#xff0c;则可以跳过它。 sudo yum install -y curl policycoreutils-python openssh-server perl sudo systemct…...

浅谈数学模型在UGC/AIGC游戏数值配置调参中的应用(AI智能体)

浅谈数学模型在UGC/AIGC游戏数值配置调参中的应用 ygluu 卢益贵 关键词&#xff1a;UGC、AIGC、AI智能体、大模型、数学模型、游戏数值调参、游戏策划 一、前言 在策划大大群提出《游戏工厂&#xff1a;AI&#xff08;AIGC/ChatGPT&#xff09;与流程式游戏开发》讨论之后就…...

第T5周:使用TensorFlow实现运动鞋品牌识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 文章目录 一、前期工作1.设置GPU&#xff08;如果使用的是CPU可以忽略这步&#xff09;2. 导入数据3. 查看数据 二、数据预处理1、加载数据2、数据可视化3、再…...

网络编程学习之tcp

按下*&#xff08;星号&#xff09;可以搜索当前光标下的单词。 Tcp编程的过程 打开网络设备 Bind&#xff1a;给服务地址把ip号和端口号连接进去 Tcp是有状态的 Listen是进入监听状态&#xff0c;看有没有客户端来连接服务器 Tcp比udp消耗过多资源 Upd类似于半双工&#…...

前端XMLHttpRequest、Fetch API、Axios实现文件上传、下载方法及后端Spring文件服务器处理方法

前言 本文总结Web应用开发中文件上传、下载的方法&#xff0c;即从前端表单输入文件并封装表单数据&#xff0c;然后请求后端服务器的处理过程&#xff1b;从基础的JavaScript中XmlHttpRequest对象、Fetch API实现上传、下载进行说明&#xff0c;并给出了前端常用的axios库的请…...

STM32智能交通监测系统教程

目录 引言环境准备智能交通监测系统基础代码实现&#xff1a;实现智能交通监测系统 4.1 数据采集模块 4.2 数据处理与控制模块 4.3 通信与网络系统实现 4.4 用户界面与数据可视化应用场景&#xff1a;交通监测与管理问题解决方案与优化收尾与总结 1. 引言 智能交通监测系统通…...

【利用Selenium+autoIt实现文件上传】

利用Selenium+autoIt实现文件上传 利用Selenium+autoIT实现文件上传autoIt脚本制作转换成exe文件java代码运行部分利用Selenium+autoIT实现文件上传 当你看到这篇文章时,证明你遇到了和我一样的难题。正常情况下我们利用selenium完全可以实现表单的提交和文件上传等操作。但当…...

python join

1、join函数 *.join(seq) 以*作为分隔符&#xff0c;将seq所有的元素合并为一个新的字符串 seq ABDWDPO new_seq list(.joint(seq)) # ABDWDPO #[A, B, D, W, D, P, O]...

cython加速python代码

python这个语言在使用的层面上看几乎没有缺点&#xff0c;简单易学&#xff0c;语法简单&#xff0c;唯一的弱点就是慢&#xff0c; 当然了万能的python社区是给了解决方法的&#xff0c;那就是cython 使用Cython可以显著提升Python代码的执行效率&#xff0c;特别是在涉及到数…...

React@16.x(60)Redux@4.x(9)- 实现 applyMiddleware

目录 1&#xff0c;applyMiddleware 原理2&#xff0c;实现2.1&#xff0c;applyMiddleware2.1.1&#xff0c;compose 方法2.1.2&#xff0c;applyMiddleware 2.2&#xff0c;修改 createStore 接上篇文章&#xff1a;Redux中间件介绍。 1&#xff0c;applyMiddleware 原理 R…...

level 6 day1 Linux网络编程之网络基础

v1 网络的历史和分层 TCP 是可靠传输&#xff0c;IP协议是不可靠传输 网络的体系结构 网络分层的思想&#xff1a; OSI体系结构 两层交换机是指数据链路层的交换 三层交换是指网络层这边的交换 四层模型 蓝色的字 是由手机发给PC机&#xff0c;由传输层来决定应该交给哪一…...

PostgreSQL UPDATE 命令

PostgreSQL UPDATE 命令 PostgreSQL 是一种功能强大的开源对象关系型数据库管理系统&#xff08;ORDBMS&#xff09;&#xff0c;它使用并扩展了SQL语言。在处理数据库时&#xff0c;我们经常需要更新现有的记录。在PostgreSQL中&#xff0c;UPDATE命令用于修改表中的现有记录…...

什么? CSS 将支持 if() 函数了?

CSS Working Group 简称 CSSWG, 在近期的会议中决定将 if() 添加到 CSS Values Module Level 5 中。 详情可见&#xff1a;css-meeting-bot 、[css-values] if() function 当我看到这个消息的时候&#xff0c;心中直呼这很逆天了&#xff0c;我们知道像 less 这些 css 这些预…...

function calling实现调用理杏仁api获取数据

LLM是不存在真正逻辑的且并不是知晓万事万物的&#xff08;至少目前是这样&#xff09;在很多更垂直的环境下LLM并不能很好的赋能。 function calling的实现使LLM可以对接真正的世界以及真正有逻辑的系统&#xff0c;这将很大程度上改变LLM的可用范围&#xff08;当然安全问题依…...

Excel中用VBA实现Outlook发送当前工作簿

Excel中用VBA实现Outlook发送当前工作簿&#xff0c;首先按AltF11打开VBA编辑器&#xff0c;插入模块&#xff0c;并在工具-引用中勾选 Microseft Outlook .0 Object Library(其中为你Microseft Outlook的版本号。 Sub 发送邮件() 保存当前excel ThisWorkbook.Save让excel连接…...

从 ArcMap 迁移到 ArcGIS Pro

许多 ArcMap 用户正在因 ArcGIS Pro 所具有的现代 GIS 桌面工作流优势而向其迁移。 ArcGIS Pro 与其余 ArcGIS 平台紧密集成&#xff0c;使您可以更有效地共享和使用内容。 它还将 2D 和 3D 组合到一个应用程序中&#xff0c;使您可以在同一工程中使用多个地图和多个布局。 Arc…...

WSL2 的安装与运行 Linux 系统

前言 适用于 Linux 的 Windows 子系统 (WSL) 是 Windows 的一项功能&#xff0c;允许开发人员在 Windows 系统上直接安装并使用 Linux 发行版。不用进行任何修改&#xff0c;也无需承担传统虚拟机或双启动设置的开销。 可以将 WSL 看作也是一个虚拟机&#xff0c;但是它更为便…...

业务终端动态分配IP-DHCP技术、DHCP中继技术

一、为什么需要DHCP? 1、许多设备(主机、无线WiFi终端等)需要动态地址的分配; 2、人工手工配置任务繁琐、容易出错,比如:IP地址冲突; 3、网络规模扩大、复杂度提高,网络配置越来越复杂,计算机的位置变化和数量超过可分配IP地址的数量,造成IP地址变法频繁以及IP地址…...

新一代大语言模型 GPT-5 对工作与生活的影响及应对策略

文章目录 &#x1f4d2;一、引言 &#x1f4d2;二、GPT-5 的发展背景 &#x1f680;&#xff08;一&#xff09;GPT-4 的表现与特点 &#x1f680;&#xff08;二&#xff09;GPT-5 的预期进步 &#x1f4d2;三、GPT-5 对工作的影响 &#x1f680;&#xff08;一&#xf…...

椭圆曲线密码学(ECC)

一、ECC算法概述 椭圆曲线密码学&#xff08;Elliptic Curve Cryptography&#xff09;是基于椭圆曲线数学理论的公钥密码系统&#xff0c;由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA&#xff0c;ECC在相同安全强度下密钥更短&#xff08;256位ECC ≈ 3072位RSA…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

《基于Apache Flink的流处理》笔记

思维导图 1-3 章 4-7章 8-11 章 参考资料 源码&#xff1a; https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

Axure 下拉框联动

实现选省、选完省之后选对应省份下的市区...

《信号与系统》第 6 章 信号与系统的时域和频域特性

目录 6.0 引言 6.1 傅里叶变换的模和相位表示 6.2 线性时不变系统频率响应的模和相位表示 6.2.1 线性与非线性相位 6.2.2 群时延 6.2.3 对数模和相位图 6.3 理想频率选择性滤波器的时域特性 6.4 非理想滤波器的时域和频域特性讨论 6.5 一阶与二阶连续时间系统 6.5.1 …...

GraphRAG优化新思路-开源的ROGRAG框架

目前的如微软开源的GraphRAG的工作流程都较为复杂&#xff0c;难以孤立地评估各个组件的贡献&#xff0c;传统的检索方法在处理复杂推理任务时可能不够有效&#xff0c;特别是在需要理解实体间关系或多跳知识的情况下。先说结论&#xff0c;看完后感觉这个框架性能上不会比Grap…...

CSS 工具对比:UnoCSS vs Tailwind CSS,谁是你的菜?

在现代前端开发中&#xff0c;Utility-First (功能优先) CSS 框架已经成为主流。其中&#xff0c;Tailwind CSS 无疑是市场的领导者和标杆。然而&#xff0c;一个名为 UnoCSS 的新星正以其惊人的性能和极致的灵活性迅速崛起。 这篇文章将深入探讨这两款工具的核心理念、技术差…...