深入浅出mediasoup—通信框架
libuv 是一个跨平台的异步事件驱动库,用于构建高性能和可扩展的网络应用程序。mediasoup 基于 libuv 构建了包括管道、信号和 socket 在内的一整套通信框架,具有单线程、事件驱动和异步的典型特征,是构建高性能 WebRTC 流媒体服务器的重要基础,本文主要分析 mediasoup 对 libuv 的封装。
1. Pipe 通信
Node.js 进程与 worker 进程之间使用管道通信,而且是双向通信。node.js 进程通过管道向 worker 进程发送请求,并接收响应。worker 进程也可以主动向 node.js 进程发送通知消息。

1.1. 文件描述符
管道通信需要使用两个文件描述符,node.js 进程的文件描述符定义如下:
this.#channel = new Channel({producerSocket: this.#child.stdio[3],consumerSocket: this.#child.stdio[4],pid: this.#pid,
});
worker 进程的文件描述符定义如下:
static constexpr int ConsumerChannelFd{ 3 };
static constexpr int ProducerChannelFd{ 4 };
1.2. 静态结构
worker 进程对管道通信的封装看起来比较复杂,涉及到多个类,如下图所示。由于这里面糅合了几个逻辑,拆解以后会更好理解:
1)UnixStreamSocketHandle 封装了基于 libuv 的 pipe 通信能力,内部包含 libuv 句柄。
2)ChannelSocket 内部包含的 ConsumerSocket 和 ProducerSocket 对应管道通信的读和写两个方向。ChannelSocket 继承了 ConsumerSocket::Listener,从 ConsumerSocket 收到的管道消息,都会回调到 ChannelSocket。
3)全局只有一个 ChannelSocket 对象,被 Worker 持有。Worker 继承了 ChannelSocekt::Listener,ChannelSocket 收到的所有管道消息都会回调 Worker。
4)Worker 包含了一个 Shared 对象,从名字上能看出,这是一个“共享对象”,通过传参的方式共享给各个对象,本质上就是一个全局对象。
5)Shared 内部包含两个对象:ChannelMessageRegistor 和 ChannelNotifier。ChannelMessageRegistor 用来管理管道消息处理器,因为全局就一个 ChannelSocket 对象,所有需要处理管道消息的对象都要把自己注册到 ChannelMessageRegistor,Worker 根据注册信息把管道消息分发给各个处理器。ChannelNotifier 用来发送管道消息,其内部也是使用 ChannelSocket 来发送消息,所有对象需要向 Node.js 进程发送管道消息调用 ChannelNotifier 接口即可。

1.3. 数据流
管道通信的数据流如下图所示。接收到的管道消息会一层层回调到 Worker 对象,Worker 先对消息进行过滤,如果是 Worker 自己关注的消息,自己先处理,其他消息则根据“注册表”进行路由。发送管道消息,调用 ChannelNotifier::Emit 接口,最终通过 libuv 发送出去。

2. Socket 通信
Socket 通信主要用来处理 mediasoup worker 与 WebRTC 客户端之间的媒体通信,支持 TCP 和 UDP。
2.1. 静态结构
2.1.1. UDP
1)UdpSocketHandle 封装了基于 libuv 的 UDP 通信能力,内部包含 libuv 句柄。
2)UdpSocket 继承自 UdpSocketHandle,内部包含了一个数据监听对象,用来接收 UDP 消息。
2)PipeTransport、PlainTransport、WebRtcTransport 和 WebRtcServer 都支持 UDP 通信,它们内部都包含一个指向 UdpSocket 的指针,用来发送 UDP 消息。
【注】这里的 PipeTransport 并不是使用管道通信的 transport。

2.1.2. TCP
1)TcpServerHandle 封装了基于 libuv 的 TCP 监听能力,内部包含 libuv 句柄。
2)TcpConnectionHandle 封装了基于 libuv 的 TCP 通信能力,内部包含 libuv 句柄。TCP 连接中断会通过 OnTcpConnectionClosed 通知 TcpServerHandle。
3)TcpConnection 继承自 TcpConnectionHandle,收到 TCP 报文会回调连接监听者。
4)当前只有 WebRtcServer 和 WebRtcTransport 支持 TCP 通信。
【注】WebRtcServer 用来实现端口聚合,其上可以承载多个 WebRtcTransport。

2.2. Socket 创建
2.2.1. WebRtcServer
WebRtcServer 用来实现 WebRTC 连接的端口聚合,WebRtcTransport 可以运行在 WebRtcServer 之上,共享 WebRtcServer 的端口。
WebRtcServer 根据传入的参数,决定创建 UdpSocket 还是 TcpServer,支持指定端口或端口范围。
WebRtcServer::WebRtcServer(RTC::Shared* shared, const std::string& id,const flatbuffers::Vector<flatbuffers::Offset<Transport::ListenInfo>>* listenInfos): id(id), shared(shared)
{...// 遍历所有地址for (const auto* listenInfo : *listenInfos){auto ip = listenInfo->ip()->str();...// UDP 协议if (listenInfo->protocol() == FBS::Transport::Protocol::UDP){RTC::UdpSocket* udpSocket;// 指定端口范围,从中选择一个if (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0){uint64_t portRangeHash{ 0u };udpSocket = new RTC::UdpSocket(this,ip,listenInfo->portRange()->min(),listenInfo->portRange()->max(),flags,portRangeHash);}// 指定端口else if (listenInfo->port() != 0){udpSocket = new RTC::UdpSocket(this, ip, listenInfo->port(), flags);}// 未指定端口,使用配置中的端口else{uint64_t portRangeHash{ 0u };udpSocket = new RTC::UdpSocket(this,ip,Settings::configuration.rtcMinPort,Settings::configuration.rtcMaxPort,flags,portRangeHash);}...}// TCP 协议else if (listenInfo->protocol() == FBS::Transport::Protocol::TCP){RTC::TcpServer* tcpServer;// 指定端口范围if (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0){uint64_t portRangeHash{ 0u };tcpServer = new RTC::TcpServer(this,this,ip,listenInfo->portRange()->min(),listenInfo->portRange()->max(),flags,portRangeHash);}// 指定端口else if (listenInfo->port() != 0){tcpServer = new RTC::TcpServer(this, this, ip, listenInfo->port(), flags);}// 未指定端口,使用配置中的端口else{uint64_t portRangeHash{ 0u };tcpServer = new RTC::TcpServer(this,this,ip,Settings::configuration.rtcMinPort,Settings::configuration.rtcMaxPort,flags,portRangeHash);}...}}...
}
2.2.2. WebRtcTransport
如果 WebRtcTransport 运行在 WebRtcServer 之上,则 WebRtcTransport 不会再创建 Socket。
WebRtcTransport::WebRtcTransport(...)
{...// 将 WebRtcTransport 加入到 WebRtcServer 的转发列表this->webRtcTransportListener->OnWebRtcTransportCreated(this);...
}
否则,还需自食其力,WebRtcTransport 创建 Socket 的逻辑与 WebRtcServer 类似,不再赘述。
2.2.3. PlainTransport
PlainTransport 用来对接像 FFMPEG 这种第三方编码器和工具的推拉流, 只支持 UDP 协议,创建逻辑类似,也支持指定端口或端口范围。
PipeTransport::PipeTransport(RTC::Shared* shared,const std::string& id,RTC::Transport::Listener* listener,const FBS::PipeTransport::PipeTransportOptions* options): RTC::Transport::Transport(shared, id, listener, options->base())
{...// 指定端口范围if (this->listenInfo.portRange.min != 0 && this->listenInfo.portRange.max != 0){uint64_t portRangeHash{ 0u };this->udpSocket = new RTC::UdpSocket(this,this->listenInfo.ip,this->listenInfo.portRange.min,this->listenInfo.portRange.max,this->listenInfo.flags,portRangeHash);}// 指定端口else if (this->listenInfo.port != 0){this->udpSocket = new RTC::UdpSocket(this, this->listenInfo.ip, this->listenInfo.port, this->listenInfo.flags);}// 未指定端口,使用配置else{uint64_t portRangeHash{ 0u };this->udpSocket = new RTC::UdpSocket(this,this->listenInfo.ip,Settings::configuration.rtcMinPort,Settings::configuration.rtcMaxPort,this->listenInfo.flags,portRangeHash);}...
}
2.2.4. PipeTransport
PipeTransport 的设计目的是为了使位于同一主机上或不同主机上的两个Router实例之间进行通信,只支持 UDP 协议,创建逻辑类似,也支持指定端口或端口范围。
PipeTransport::PipeTransport(RTC::Shared* shared,const std::string& id,RTC::Transport::Listener* listener,const FBS::PipeTransport::PipeTransportOptions* options): RTC::Transport::Transport(shared, id, listener, options->base())
{...if (this->listenInfo.portRange.min != 0 && this->listenInfo.portRange.max != 0){uint64_t portRangeHash{ 0u };this->udpSocket = new RTC::UdpSocket(this,this->listenInfo.ip,this->listenInfo.portRange.min,this->listenInfo.portRange.max,this->listenInfo.flags,portRangeHash);}else if (this->listenInfo.port != 0){this->udpSocket = new RTC::UdpSocket(this, this->listenInfo.ip, this->listenInfo.port, this->listenInfo.flags);}else{uint64_t portRangeHash{ 0u };this->udpSocket = new RTC::UdpSocket(this,this->listenInfo.ip,Settings::configuration.rtcMinPort,Settings::configuration.rtcMaxPort,this->listenInfo.flags,portRangeHash);}...
}
2.3. 数据流
2.3.1. UDP
2.3.1.1. 接收数据
以 WebRtcServer 为例,libuv 收到 UDP 消息会回调 UdpSocketHandle::OnUvRecv,UdpSocketHandle 再回调 UdpSocket::UserOnUdpDatagramReceived,最终将消息回调给数据监听者 WebRtcServer。

2.3.1.2. 发送数据
需要发送 UDP 消息的模块持有 TransportTuple 对象,调用 TransportTuple:: Send 方法,内部调用 UdpSocketHandle::Send,最终通过 libuv 接口将数据发送到网络。

需要注意,UDP 报文发送有一个特殊机制,mediaoup 会先调用 libuv 同步发送接口,如果同步发送接口出错,mediasoup 不是立即返回,而是拷贝发送数据,继续调用 libuv 的异步发送接口。这在某些极端场景下,可能会大量消耗服务器内存。
void UdpSocketHandle::Send(const uint8_t* data, size_t len, const struct sockaddr* addr, UdpSocketHandle::onSendCallback* cb)
{...// 使用待发送送数据初始化一块uv缓冲区uv_buf_t buffer = uv_buf_init(reinterpret_cast<char*>(const_cast<uint8_t*>(data)), len);// 调用同步接口发送const int sent = uv_udp_try_send(this->uvHandle, &buffer, 1, addr);// 所有数据都发送完成if (sent == static_cast<int>(len)){// Update sent bytes.this->sentBytes += sent;if (cb){(*cb)(true); // 回调返回成功delete cb;}return;}// 发送了部分数据else if (sent >= 0){this->sentBytes += sent;if (cb){(*cb)(false); // 回调返回失败delete cb;}return;}// 出错了,可能是网络繁忙,使用异步接口uv_udp_send发送else if (sent != UV_EAGAIN){MS_WARN_DEV("uv_udp_try_send() failed, trying uv_udp_send(): %s", uv_strerror(sent));}// 创建一个异步处理数据结构auto* sendData = new UvSendData(len);// 作为自定义数据挂载到uv数据结构中sendData->req.data = static_cast<void*>(sendData);// 拷贝待发送数据std::memcpy(sendData->store, data, len);// 保存回调函数指针sendData->cb = cb;// 使用待发送数据的拷贝初始化uv缓冲区buffer = uv_buf_init(reinterpret_cast<char*>(sendData->store), len);// 调用异步接口发送,设置回调接口onSendconst int err = uv_udp_send(&sendData->req, this->uvHandle, &buffer, 1, addr, static_cast<uv_udp_send_cb>(onSend));if (err != 0){if (cb){(*cb)(false);}delete sendData;}else{this->sentBytes += len;}
}
UvSendData 定义如下:
struct UvSendData
{uv_udp_send_t req{};uint8_t* store{ nullptr };UdpSocketHandle::onSendCallback* cb{ nullptr };
};
libuv 发送完成后会回调 onSend,在 onSend 函数中处理善后事宜。
inline static void onSend(uv_udp_send_t* req, int status)
{auto* sendData = static_cast<UdpSocketHandle::UvSendData*>(req->data);auto* handle = req->handle;auto* socket = static_cast<UdpSocketHandle*>(handle->data);const auto* cb = sendData->cb;if (socket){socket->OnUvSend(status, cb);}// Delete the UvSendData struct (it will delete the store and cb too).delete sendData;
}
2.3.2. TCP
2.3.2.1. 监听连接
1)TcpServer 调用 libuv 接口建立监听。
2)客户端与服务器完成三次握手后,libuv 会回调 TcpServerHandle::OnUvConnection。
3)TcpServerHandle 回调 TcpServer::UserOnTcpConnectionAlloc。
4)TcpServer 创建 TcpConnection 并调用 TcpServerHandle::AcceptTcpConnection 告知要接受这个连接。
5)TcpServerHandle 对 TcpConnection 进行初始化,调用 libuv 的 uv_accpet 方法完成新连接的创建。
6)调用 TcpConnectionHandle::Start 开始接收数据。

2.3.2.2. 接收数据
接收数据逻辑非常简单,以 WebRtcServer 为例,libuv 收到 TCP 数据后会层层回调到 WebRtcServer。

2.3.2.3. 发送数据
发送 TCP 数据的逻辑也很简单,调用 TransportTuple 接口,内部最终调用 libuv 将数据发送到网络。

3. 定时器
定时器在很多地方都会被用到,mediasoup 使用 TimerHanlde 封装 libuv 的定时器能力。需要使用定时器的类需要继承 TimerHandle::Listener,实现 OnTimer 虚拟方法。然后创建一个 TimerHandle 对象,传入 this 指针,调用 TimerHandle::Start 方法启动定时器即可。

4. 信号处理
信号是进程间通信的一种机制,也是操作系统用来通知进程有关系统事件或异常状况的重要手段。信号可以由系统内核发送给进程,也可以由一个进程发送给另一个进程。在 Worker 进程中,Worker 类是唯一处理 signal 的类,它继承 SignalHandle::Listener,实现 OnSignal 虚拟方法,进程接收的所有信号都会回调给 Worker 处理。

mediasoup 当前只处理了 SIGINT 和 SIGTERM 两个信号,用来优雅的关闭 mediasoup 进程。
void Worker::OnSignal(SignalHandle* /*signalHandle*/, int signum)
{if (this->closed){return;}switch (signum){case SIGINT:{if (this->closed){return;}Close();break;}case SIGTERM:{if (this->closed){return;}Close();break;}default:{MS_WARN_DEV("received a non handled signal [signum:%d]", signum);}}
}
5. 总结
熟悉 mediasoup 的底层通信机制,是深入阅读 mediasoup 源码的基础。本文详细描述了 mediasoup 对 libuv 的封装,覆盖了 pipe、socket、signal 等几种通信方式,重点分析了 Socket 通信的静态结构和数据流,补充分析了 UDP 报文的异步发送机制。mediasoup 对 libuv 的封装简洁清晰,是一个优秀的设计方案,值得大家借鉴。
相关文章:
深入浅出mediasoup—通信框架
libuv 是一个跨平台的异步事件驱动库,用于构建高性能和可扩展的网络应用程序。mediasoup 基于 libuv 构建了包括管道、信号和 socket 在内的一整套通信框架,具有单线程、事件驱动和异步的典型特征,是构建高性能 WebRTC 流媒体服务器的重要基础…...
每日一题 LeetCode03 无重复字符的最长字串
1.题目描述 给定一个字符串 s ,请你找出其中不含有重复字符的最长字串的长度。 2 思路 可以用两个指针, 滑动窗口的思想来做这道题,即定义两个指针.一个left和一个right 并且用一个set容器,一个length , 一个maxlength来记录, 让right往右走,并且用一个set容器来…...
栈和队列(C语言)
栈的定义 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。 压栈:…...
swagger-ui.html报错404
问题1:权限受限无法访问 由于采用的Shiro安全框架,需要在配置类ShiroConfig下的Shiro 的过滤器链放行该页面:【添加:filterChainDefinitionMap.put("/swagger-ui.html", "anon");】 public ShiroFilterFact…...
Milvus 核心组件(3)--- MinIO详解
目录 背景 MinIO 安装 docker desktop 安装 Ubuntu UI 在 docker 中的安装 Minio 下载及安装 启动minio docker image 保存 启动 minio web 网页 下一次启动 MinIO基本概念 基本概述 主要特性 应用场景 MinIO 使用 连接server 创建bucket 查询bucket 上传文件…...
[数据集][目标检测]婴儿车检测数据集VOC+YOLO格式1073张5类别
数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):1073 标注数量(xml文件个数):1073 标注数量(txt文件个数):1073 标注…...
JAVASE进阶day14(网络编程续TCP,日志)
TCP 三次握手 四次挥手 package com.lu.day14.tcp;import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket;public class Client {public static void main(String[] args) {try(Socket socket new Socket("192.…...
机器学习(五) -- 无监督学习(1) --聚类1
系列文章目录及链接 上篇:机器学习(五) -- 监督学习(7) --SVM2 下篇:机器学习(五) -- 无监督学习(1) --聚类2 前言 tips:标题前有“***”的内容…...
leetcode 116. 填充每个节点的下一个右侧节点指针
leetcode 116. 填充每个节点的下一个右侧节点指针 题目 给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下: struct Node { int val; Node *left; Node *right; Node *next; } 填充它的每个 next …...
[C++]优先级队列
1 .了解优先级队列 优先级队列是一种容器适配器,根据一些严格的弱排序标准,专门设计使其第一个元素始终是它所包含的元素中最大的元素。 此上下文类似于堆,其中可以随时插入元素,并且只能检索最大堆元素(优先级队列中顶…...
学习大数据DAY22 Linux 基 本 指 令 3与 在 Linux 系 统 中 配 置MySQL 和 Oracle
目录 网络配置类 ps 显示系统执行的进程 kill systemctl 服务管理 配置静态 ip 常见错误---虚拟机重启网卡失败或者网卡丢失 mysql 操作 上机练习 6---安装 mysql---参考《mysql 安装》文档 解锁 scott 重启后的步骤 上机练习 7---安装 oracle---参考《oracle 安装》…...
scp 服务器复制命令
步骤如下: 终端执行如下命令 #ssh-keygen -t rsa 2. 密钥生成后会在 /root/.ssh/ 文件夹下产生两个文件 id_rsa id_rsa.pub 将 id_rsa.pub 文件复制到 152.136.121.24 执行如下命令 scp /root/.ssh/id_rsa.pub root152.136.121.24:/root/.ssh/authorized_keys…...
PyQt5学习路线
后续会根据该文章的路线逐步发布对应的教程,订阅专栏不迷路🥰 本专栏纯干货🤩 学习Python的PyQt5库,可以遵循以下的学习路线: 1. Python基础 掌握Python语法:确保你熟悉Python的基本语法,包括…...
2024论文精读:利用大语言模型(GPT)增强上下文学习去做关系抽取任务
文章目录 1. 前置知识2. 文章通过什么来引出他要解决的问题3. 作者通过什么提出RE任务存在上面所提出的那几个问题3.1 问题一:ICL检索到的**示范**中实体个关系的相关性很低。3.2 问题二:示范中缺乏解释输入-标签映射导致ICL效果不佳。 4. 作者为了解决上…...
WEB 手柄 http通信,mcu端解析代码 2024/7/23 日志
WEB 手柄 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>WEB遥控器</title> </head> &l…...
cmake中的正则表达式
以下字符或者字符组合在cmake的正则表达式中的特殊含义: ^ 匹配输入的开始 $ 匹配输入的结束 . 匹配任意一个字符 \<char> 匹配一个字符,如.匹配字符.,\匹配字符\,\a匹配字符a [ ] 匹配在括号里面的任意字符࿰…...
05. Java 三大范式
1. 前言 在面向对象语言中涉及到诸多的设计模式,例如单例模式、适配器模式,设计模式的存在是为了让系统中的代码逻辑更加清晰,帮助开发者建立更加健壮的系统,同时满足易修改特性和易扩展特性。数据库设计时也存在类似设计模式的通…...
opencv 按键开启连续截图,并加载提示图片
背景图小图 键盘监听使用的是pynput 库 保存图片时使用了年月日时分秒命名 原图: from pynput import keyboard import cv2 import time# 键盘监听 def on_press(key):global jieglobal guanif key.char a:jie Trueelif key.char d:jie Falseelif key.char…...
Android-- 集成谷歌地图
引言 项目需求需要在谷歌地图: 地图展示,设备点聚合,设备站点,绘制点和区域等功能。 我只针对我涉及到的技术做一下总结,希望能帮到开始接触谷歌地图的伙伴们。 集成步骤 1、在项目的modle的build.gradle中添加依赖如…...
Jvm是如何处理异常的
异常抛出 当Java程序运行时遇到无法处理的情况时,会抛出一个异常(比如在一个方法中如果发生异常),这时会创建一个异常对象,并转交给JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给JVM的过程称为抛出异常。 异常捕捉 当JVM检测…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...
Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...
