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

WebRTC服务质量(05)- 重传机制(02) NACK判断丢包

WebRTC服务质量(01)- Qos概述
WebRTC服务质量(02)- RTP协议
WebRTC服务质量(03)- RTCP协议
WebRTC服务质量(04)- 重传机制(01) RTX NACK概述
WebRTC服务质量(05)- 重传机制(02) NACK判断丢包
WebRTC服务质量(06)- 重传机制(03) NACK找到真正的丢包

一、前言:

上一篇介绍了NACK/RTX这种机制,注意,NACK是一种RTCP消息而已,本文结合代码看下WebRtc如何实现NACK机制的。

二、NACK格式:

2.1、RTPFB 消息头统一格式:

  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|V=2|P|   FMT   |       PT      |          length               |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|                  SSRC of packet sender                        |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|                  SSRC of media source                         |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+:            Feedback Control Information (FCI)                 :
  • V: 版本号,占2位。

  • P: 填充位,占1位。

  • FMT(Feedback message type): 反馈消息类型,这里设为15表示 NACK。

  • PT: Payload Type,占8位,指示 RTPFB 包类型,205 表示 RTPFB。

  • Length: 长度字段,指示反馈消息长度,以 32 位字为单位。

  • Sender SSRC: 发送者同步信源,4字节。

  • Media SSRC: 媒体同步信源,4字节。

  • FCI(Feedback Control Information,反馈控制信息): 是RTCP报文的核心部分,包含各种反馈信息,可以帮助发送端及时调整或重传数据。

    • 格式如下:

      在这里插入图片描述

    • 按照内容大致分为两大类:

      • RTPFB (RTP Feedback Messages): 针对RTP层的丢包检测和重传。

      • PSFB (Payload-Specific Feedback): 针对RTP净荷(Payload)层的增强反馈,主要用于处理更高层的问题,比如视频帧或切片的丢失。

2.2、RTPFB和PSFB:

  • 典型的RTPFB控制消息 —— NACK(Negative Acknowledgement):
    • 功能:接收端检测到有RTP数据包丢失后,通过NACK通知发送端重新发送丢失的RTP数据包。一般啥都不写就是这种传输机制。
    • 应用场景:当网络质量较差但延迟要求比较高的场景,比如视频通话、实时流媒体等。
    • 优点:粒度较小,可以精确地指出哪些RTP序列号丢失,有利于快速、精准地补偿丢包。
    • 处理流程:
      • 接收端发现一定范围的RTP序列号有丢失。
      • 接收端发送RTCP报文中的NACK消息,带有丢失的RTP序列号信息。
      • 发送端收到NACK后,针对性地重传丢失的RTP包。
  • PSFB 是RTCP中的一个用于反馈净荷内容的框架,主要针对编码和媒体数据层面的重传控制。相比RTPFB,PSFB通常涉及更高层的媒体内容,比如整个视频帧或某种编码参考信息。PSFB进一步细化为以下三种主要类型:
    • PLI (Picture Loss Indication) - 视频帧丢失重传
      • 功能:
        • 当接收端检测到关键帧(如I帧)丢失或破坏时,发送PLI消息给发送端,要求它重发一个完整的视频关键帧。
        • 用途尤其体现在视频传输中,避免多帧由于关键帧丢失而无法解码。
      • 处理流程:
        1. 接收端检测关键帧丢失或解码错误(比如画面突然坏块增多)。
        2. 接收端发送PLI信息给发送方。
        3. 发送端在收到PLI后,发送一个新的关键帧(通常是I帧)。
      • 特点:
        • 粒度较大,通常用于重要内容的恢复,比如视频关键帧丢失。
        • 可能消耗更多带宽,因为完整的关键帧通常较大。
    • SLI (Slice Loss Indication) - Slice丢失重传
      • 功能:
        • 反馈RTP流中某个视频切片(Slice)丢失的信息。
        • 通常用于视频传输中某些特定的片段(非整个帧)的丢失导致部分画面无法解码。
      • 处理流程:
        • 接收端判断某个Slice数据丢失或破坏,发送SLI给对端。
        • 发送端针对丢失Slice,通过数据包重传或替换的方式修复。
      • 特点:
        • 较之PLI,SLI的作用范围更小,仅针对部分切片,而不需要整帧重传。
        • 带宽开销较低,但可能延迟较大,因为重传的粒度较细。
    • RPSI (Reference Picture Selection Indication) - 参考帧丢失重传
      • 功能:
        • 当接收端检测到参考帧丢失(或者它依赖的解码参考无法使用时,如P帧无法解码),会反馈RPSI消息,建议发送端选择新的参考帧。
        • 发送端可以根据RPSI调整编码或重发相关参考信息。
      • 处理流程:
        1. 接收端通过解码检测或分析发现P帧等数据依赖的参考帧丢失或损毁。
        2. 接收端发送RPSI消息,建议使用新的参考帧。
        3. 发送端参考RPSI调整后续的编码策略,跳过丢失的参考帧并发送新的参考帧数据。
      • 特点:
        • 聚焦于“参考帧”的问题,对于影响范围有限的解码错误更加有效,避免过多的重传和带宽开销。
        • 在视频编码中(如H.264、H.265),参考帧是P帧和B帧的编码基础,丢失的影响可能尤为严重。

2.2.1、PLI和SLI和RPSI比较:

类型粒度应用场景带宽开销延迟影响
PLI整帧关键帧丢失,恢复整体画面中等,需整帧重传
SLI切片部分画面丢失,快速修复低到中较低,粒度更细
RPSI参考帧参考帧错误,影响解码链非重传型,调整编码策略

三、Call、Channel、Stream:

之前说过,Call、Channel、Stream这几个概念你是否还记得?

  • Session层:

    • 一个 Stream 对应的是一个完整的媒体流,可以包含多个 Track
    • 一个 Track 表示流中的单一媒体轨道,例如音频轨道或视频轨道(类似于 WebRTC API 中的MediaStreamTrack)。
  • MediaEngine层:

    • Channel 是进行音视频分类管理的基础单元。通常,音频和视频会分属于不同的 Channel(AudioChannel 和 VideoChannel)。
    • Stream是音视频数据在 Channel 层中的更细化管理单元。
      • 一个 Channel 通常会包含多个 Stream。
      • 每个 Stream 不仅负责具体的音频或视频数据处理,还可以进一步分为发送(send)和接收(recv)的数据流。
      • MediaEngine 层中的 Stream 是底层实现,不再对应 Session 层中的逻辑 Stream,而是为传输和解码服务的独立实体。

    关键点:

    • 一个 Channel 的核心目的是管理一种媒介类型(音频或视频)。例如,一个音频 Channel 可以包含多个音频 Stream;一个视频 Channel 可以包含多个视频 Stream。
    • 这些 Stream 分别表示 传输和接收方向的数据流
  • Call层:

    • 对于音频,引擎层的一个Stream就对应Call层的一个SendStream或者ReceiveStream
    • 对于音频,一个Stream中又有Channel,来连接编解码器;
    • 对于视频,只有Stream对应引擎层的Stream,并没有channel的概念;

三者的总结关系:

层次作用音频之间关系视频之间关系
Session管理逻辑 Stream 和其包含的 Track一个 Track(音轨)对应一个 Channel一个 Track 映射为一个 Stream
MediaEngine处理底层音视频流管理,区分发送与接收一个 Stream 对应 Call层的 SendStreamReceiveStream。每个 Channel 中有若干 Stream一个Stream直接传到Call层。
Call与用户操作逻辑一致,将 Stream 转化为最终发送/接收流Stream 对应 SendStreamReceiveStream,还有Channel连接编解码器。Stream 与用户的发送流或接收流一一对应。

视频 Channel 与 Stream图示:

Session 层:
+------------------+
| Stream           |--- 同一个对等会话上的视频流逻辑。
| - Track (Video)  |
+------------------+|
MediaEngine 层:
+------------------------+
| Channel (VideoChannel) |--- 管理多路视频数据
+------------------------+|+----> Stream 1 (Send方向)+----> Stream 2 (Recv).
Call 层:
+-------------------+
| SendStream        |
| ReceiveStream     |---- 视频输流的最外层封装接口
+-------------------+

四、NACK调用关系:

调用关系如下图:

在这里插入图片描述

  • 看下调用顺序基本是:Channel -> Call -> Stream;
  • 音频引擎那一节介绍过RtpDemuxer,就是数据分发器,总共两个地方用到:
    • 当收到RTP数据包的时候,通过RtpDemuxer分发给不同Channel(音频是Channel或者视频是Stream);
    • 就是当前这个地方,分发给不同的Stream(每个Stream又连接着解码器);
    • 分发给不同Stream时候,如果是正常包就分发给RtpVideoStreamReceiver,如果是RTX数据包,那么就分发给RtxReceiveStream,当然,处理完之后还得继续发给RtpVideoStreamReceiver
  • OnReceivedPayloadData收到数据包之后,就会判断数据包的间隔,如果间隔很大,那么就会调用NackModule::OnReceivedPacket方法请求重传这部分包。

4.1、ReceivePacket:

我们直接从上述的RtpVideoStreamReceiver模块看代码,在这个函数的时候,我们已经拿到的是视频的RTP包了。

  • 得到Payload:
void RtpVideoStreamReceiver::ReceivePacket(const RtpPacketReceived& packet) {// ...// 正常数据包走下面// 从 payload_type_map_ 中根据pt找出RTP包的解包器const auto type_it = payload_type_map_.find(packet.PayloadType());if (type_it == payload_type_map_.end()) {return;}// 调用解包器的Parse方法对RTP数据包进行解析absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed_payload =type_it->second->Parse(packet.PayloadBuffer());if (parsed_payload == absl::nullopt) {RTC_LOG(LS_WARNING) << "Failed parsing payload.";return;}// 这样就拿到了Rtp的Payload,对payload进行处理OnReceivedPayloadData(std::move(parsed_payload->video_payload), packet,parsed_payload->video_header);
}

根据PT找到解包器,然后解包得到Payload。

  • 处理Payload:
void RtpVideoStreamReceiver::OnReceivedPayloadData(rtc::CopyOnWriteBuffer codec_payload,const RtpPacketReceived& rtp_packet,const RTPVideoHeader& video) {// 根据入参构造一个Packet(后面会往里填其他项)auto packet = std::make_unique<video_coding::PacketBuffer::Packet>(rtp_packet, video, clock_->TimeInMilliseconds());// ...// 获取Video Header// 将视频的:角度、视频类型、是否为最后一个包,这几个参数设置到video_header当中RTPVideoHeader& video_header = packet->video_header;// ...// 如果是视频帧的最后一个包,获取并存储颜色空间(颜色空间信息只存在于最后一个包)if (video_header.is_last_packet_in_frame) {// ...}// 处理丢失找回的包if (loss_notification_controller_) {// ...}// 检测是否有丢包,将丢失的包记录下来(只是记录不会发送NACK,SendBufferedRtcpFeedback才会发送NACK)if (nack_module_) {// ...}// 处理H264的数据需要先更新pt以及pps和sps,可能还涉及"请求关键帧"、"丢包"、"正常拷贝数据"三个动作if (packet->codec() == kVideoCodecH264) {packet->video_payload = std::move(fixed.bitstream);} else {// 非H264的直接将payload拷贝到packet的payload即可packet->video_payload = std::move(codec_payload);}// 发送NACK给发送端rtcp_feedback_buffer_.SendBufferedRtcpFeedback();frame_counter_.Add(packet->timestamp);// 将payload data插入某一个帧当中(为组帧做好准备),packet_buffer_.InsertPacket会包含一帧的所有packetOnInsertedPacket(packet_buffer_.InsertPacket(std::move(packet)));
}

我删除了非常多的代码,否则,很难读明白,精简之后思路:

  1. 这个函数主要就是构造了一个packet,然后根据rtp_packet里面的信息来完善这个packet;
  2. 检测是否有丢包,将丢的包记录下来;
  3. 拷贝payload数据到packet里面;(注意,移动语义允许在不复制数据的情况下将资源所有权从一个对象转移到另一个对象,并非传统拷贝)
  4. 给发送端发送NACK;
  5. 最后将packet插入到packet_buffer_当中,凑齐了一帧所有packet,就可以给解码器去解码了;
  • 重点看下刚才的if (nack_module_)部分:
 // 检测是否有丢包,将丢失的包记录下来(只是记录不会发送NACK,SendBufferedRtcpFeedback才会发送NACK)if (nack_module_) {// 判断这个RTP包是否属于关键帧当中的一个包(是一个帧当中的第一个包,同时帧类型是视频关键帧)const bool is_keyframe =video_header.is_first_packet_in_frame &&video_header.frame_type == VideoFrameType::kVideoFrameKey;// 当知道了这个packet是否属于关键帧的包之后,在下面函数判断是否丢了包packet->times_nacked = nack_module_->OnReceivedPacket(rtp_packet.SequenceNumber(), is_keyframe, rtp_packet.recovered());} else {packet->times_nacked = -1;}

里面nack_module_->OnReceivedPacket会判断是否有丢包。接下来看看。

4.2、OnReceivedPacket:

这函数又很长,咱拆飞机,研究零件吧。

1. 初始化模块

/*** 里面会判断是否有丢包*/
int DEPRECATED_NackModule::OnReceivedPacket(uint16_t seq_num,bool is_keyframe) {return OnReceivedPacket(seq_num, is_keyframe, false);
}int DEPRECATED_NackModule::OnReceivedPacket(uint16_t seq_num,bool is_keyframe,bool is_recovered) {if (!initialized_) {newest_seq_num_ = seq_num;if (is_keyframe)keyframe_list_.insert(seq_num);initialized_ = true;return 0;}
}
  • 主要功能: 只在接收到的第一个包时执行。初始化 newest_seq_num_ 为当前包的序列号,同时保存第一个关键帧(如果该包是关键帧)。
  • 关键点:第一次初始化函数,只需要简化处理,本次包被记录后直接退出,不做其它操作。

2. 乱序包处理模块

if (seq_num == newest_seq_num_)return 0;if (AheadOf(newest_seq_num_, seq_num)) {auto nack_list_it = nack_list_.find(seq_num);if (nack_list_it != nack_list_.end()) {nacks_sent_for_packet = nack_list_it->second.retries;nack_list_.erase(nack_list_it);}if (!is_retransmitted)UpdateReorderingStatistics(seq_num);return nacks_sent_for_packet;
}
  • 主要功能:
    • 检查收到的包是否为重复包(seq_num == newest_seq_num_)或者乱序包(AheadOf 函数判断包是否比最新序列号旧)。
    • 乱序包的行动:如果乱序包在 nack_list_ 中,说明之前被判断为丢失,已请求重传,此时从 nack_list_ 中删除,因为该丢包实际上已经被恢复。
  • 关键点:
    • 当接收到乱序包时,通过清理对应的 NACK 请求可防止无意义的重传。

3. 新包到达模块

if (is_keyframe)keyframe_list_.insert(seq_num);auto it = keyframe_list_.lower_bound(seq_num - kMaxPacketAge);
if (it != keyframe_list_.begin())keyframe_list_.erase(keyframe_list_.begin(), it);
  • 主要功能:
    • 当新包到达时,如果是关键帧,则记录关键帧的序号。
    • 同时清理超出 kMaxPacketAge (值是10000)范围的历史关键帧序号,避免列表的无限增长。

4. 丢包列表管理

AddPacketsToNack(newest_seq_num_ + 1, seq_num);
newest_seq_num_ = seq_num;
  • 主要功能:
    • 检查当前包与上次接收的包之间是否存在包丢失(通过包序号差距判断)。调用 AddPacketsToNack 将中间的丢包插入到 nack_list_ 中。
    • 更新 newest_seq_num_,确保下次处理时以最新接收的包为基准。

5. NACK 批量发送

std::vector<uint16_t> nack_batch = GetNackBatch(kSeqNumOnly);
if (!nack_batch.empty()) {nack_sender_->SendNack(nack_batch, /*buffering_allowed=*/true);
}
  • 主要功能:
    • 构造一个丢失包序号的批量列表(nack_batch),并将这些序号通过 NACK 消息发送给远端。
    • GetNackBatch 函数会筛选出真正需要 NACK(仍未恢复)的丢失包。

小结:

有点复杂,小结一下:

  1. 初始化:
    • 在接收到的第一个 RTP 数据包时,初始化 newest_seq_num_(记录最近成功接收的 RTP 包序列号),同时判断该包是否为关键帧(Keyframe),如果是则记录在 keyframe_list_ 中。
    • 这是 NACK 模块的第一步,之后才能对后续到达的 RTP 数据包构建更加完整和正确的包状态跟踪。
  2. 重复包检查:
    • 对于重复收到的包(当前序列号等于 newest_seq_num_),可以直接忽略,因为它已经被记录为接收成功。
  3. 乱序包处理:
    • 如果接收到的包编号比上一次记录的最新序列号小,说明该包是一个迟到的乱序包。如果乱序的包在 nack_list_(NACK 缓存列表)中,说明它之前被认为是丢包并请求重传,此时需要从 nack_list_ 中删除,因为它已经补到了。
  4. 新包处理和 NACK 填充:
    • 对于序号比 newest_seq_num_ 更新的包,需要更新 newest_seq_num_,并检查中间遗漏的包(即从上一包到当前包之间的差值),将这些丢包插入到 nack_list_ 中。
    • 同时,只保留一定范围(kMaxPacketAge)内的 NACK 请求,过于久远的序列号被认为无法恢复,注意,虽然这个宏是10000,但是指的是RTP包的序列号差距,并不是差这么多视频帧,差这么多视频帧体验就很差了。
  5. 关键帧和恢复包:
    • 关键帧和恢复包(FEC 或 RTX 恢复的包)被单独处理和记录,不会请求 NACK,因为这些包有特殊的作用。
  6. 最终丢包确认(NACK 批量发送):
    • 分析确定哪些包是真正丢失没有恢复的,通过调用 GetNackBatch 构造 NACK 请求批量发送,通知发送端重传这些丢失的包。

4.3、AddPacketsToNack:

这个是根据包序号判断哪些包丢了,归纳功能如下:

  • 记录丢包: 当检测到某些数据包丢失时,将这些丢包的序号记录进 nack_list_,等待后续判断和处理。
  • 限制管理: 控制 nack_list_ 的尺寸,防止超出最大容量,同时根据策略清理不必要的条目。
  • 识别特殊包: 对于通过其他方式(例如 FEC/RTX)找回的包无需生成 NACK 条目。
// 拿到这个包到上一次的包newest_seq_num_中间所有丢失的包;
// 这些包有可能是乱序(假丢包),也有可能是真丢包,就可以用 GetNackBatch 判断
void DEPRECATED_NackModule::AddPacketsToNack(uint16_t seq_num_start,uint16_t seq_num_end) {// Remove old packets.auto it = nack_list_.lower_bound(seq_num_end - kMaxPacketAge);nack_list_.erase(nack_list_.begin(), it);// If the nack list is too large, remove packets from the nack list until// the latest first packet of a keyframe. If the list is still too large,// clear it and request a keyframe.uint16_t num_new_nacks = ForwardDiff(seq_num_start, seq_num_end);if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {while (RemovePacketsUntilKeyFrame() &&nack_list_.size() + num_new_nacks > kMaxNackPackets) {}if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {nack_list_.clear();RTC_LOG(LS_WARNING) << "NACK list full, clearing NACK"" list and requesting keyframe.";keyframe_request_sender_->RequestKeyFrame();return;}}// 接下来就是将可疑丢包找到for (uint16_t seq_num = seq_num_start; seq_num != seq_num_end; ++seq_num) {// Do not send nack for packets that are already recovered by FEC or RTXif (recovered_list_.find(seq_num) != recovered_list_.end())continue;NackInfo nack_info(seq_num, seq_num + WaitNumberOfPackets(0.5),clock_->TimeInMilliseconds());RTC_DCHECK(nack_list_.find(seq_num) == nack_list_.end());nack_list_[seq_num] = nack_info;}
}

老规矩,分段看下:

1. 清理老旧记录(防止 nack_list 过大):

auto it = nack_list_.lower_bound(seq_num_end - kMaxPacketAge);
nack_list_.erase(nack_list_.begin(), it);

使用 lower_bound 找到 seq_num_end - kMaxPacketAge 的边界,将过于久远的丢包条目从 nack_list_ 中删除。

2. 限制丢包列表大小:

uint16_t num_new_nacks = ForwardDiff(seq_num_start, seq_num_end);
if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {while (RemovePacketsUntilKeyFrame() && nack_list_.size() + num_new_nacks > kMaxNackPackets) { }if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {nack_list_.clear();keyframe_request_sender_->RequestKeyFrame();return;}
}
  • 检查 nack_list_ 的当前大小和即将添加的条目是否会超过最大容量。
  • 策略:
    • 优先通过 RemovePacketsUntilKeyFrame() 清理到最新关键帧之前的 NACK 条目。
    • 如果清理后仍超上限,则完全清空 nack_list_ 并请求关键帧。

3. 记录丢包(创建 NackInfo 条目):

for (uint16_t seq_num = seq_num_start; seq_num != seq_num_end; ++seq_num) {if (recovered_list_.find(seq_num) != recovered_list_.end()) continue;NackInfo nack_info(seq_num, seq_num + WaitNumberOfPackets(0.5),clock_->TimeInMilliseconds());nack_list_[seq_num] = nack_info;
}
  • 根据 seq_num 逐个检查这些包是否已通过 FEC 或 RTX 恢复。如果恢复过,则跳过。
  • 创建 NackInfo 记录丢包的序号和请求重传的时间等信息。

小结:

是 NACK 机制的核心,用于记录丢包并追踪其状态,并限制 NACK 列表的大小。

五、总结:

本文主要介绍了NACK的格式,以及NACK的调用栈,并且介绍了如何判断丢包,但请记住,这些包都是“可疑丢包”,真正的丢包下一节介绍。

相关文章:

WebRTC服务质量(05)- 重传机制(02) NACK判断丢包

WebRTC服务质量&#xff08;01&#xff09;- Qos概述 WebRTC服务质量&#xff08;02&#xff09;- RTP协议 WebRTC服务质量&#xff08;03&#xff09;- RTCP协议 WebRTC服务质量&#xff08;04&#xff09;- 重传机制&#xff08;01) RTX NACK概述 WebRTC服务质量&#xff08;…...

MySQL 存储过程与函数:增强数据库功能

一、MySQL 存储过程与函数概述 &#xff08;一&#xff09;存储过程的定义与特点 存储过程是一组预编译的 SQL 语句集合&#xff0c;它们被存储在数据库中&#xff0c;可根据需要被重复调用。例如&#xff0c;在一个电商系统中&#xff0c;经常需要查询某个时间段内的订单数据…...

丹摩|丹摩助力selenium实现大麦网抢票

丹摩&#xff5c;丹摩助力selenium实现大麦网抢票 声明&#xff1a;非广告&#xff0c;为用户体验 1.引言 在人工智能飞速发展的今天&#xff0c;丹摩智算平台&#xff08;DAMODEL&#xff09;以其卓越的AI算力服务脱颖而出&#xff0c;为开发者提供了一个简化AI开发流程的强…...

springcloud-gateway获取应用响应信息乱码

客户端通过springcloud gateway跳转访问tongweb上的应用&#xff0c;接口响应信息乱码。使用postman直接访问tongweb上的应用&#xff0c;响应信息显示正常。 用户gateway中自定义了实现GlobalFilter的Filter类&#xff0c;在该类中获取了上游应用接口的响应信息&#xff0c;直…...

Scala项目(一)

1&#xff0c;创建dao&#xff0c;models&#xff0c;service&#xff0c;ui等软件包 2&#xff0c;在各软件包下创建scala类 软件包dao里的代码 package org.app package daoimport models.BookModelimport scala.collection.mutable.ListBuffer//图书&#xff0c;数据操作…...

node(2) - npm run 原理

1. npm run 执行原理 npm run 命令的原理是执行 package.json 文件中定义的脚本。当你在命令行中运行 npm run 时,npm 会查找 package.json 文件中的 scripts 字段,然后执行对应的脚本命令。 2. 示例 2.1 以 dev:weapp 为例 运行 npm run dev:weapp 命令;npm 会查找 packa…...

概率论得学习和整理24:EXCEL的各种图形,统计图形

目录 0 EXCEL的各种图形&#xff0c;统计图形 1 统计图形 / 直方图 / 其实叫 频度图 hist最合适(用原始数据直接作图) 1.1 什么是频度图 1.2 如何创建频度图&#xff0c;一般是只选中1列数据&#xff08;1个数组&#xff09; 1.3 如何修改频度图的宽度 1.4 hist图的一个特…...

【zlm】 webrtc源码讲解三(总结)

目录 setsdp onwrite ​编辑 play 参考 setsdp onwrite play 参考 【zlm】 webrtc源码讲解_zlm webrtc-CSDN博客 【zlm】 webrtc源码讲解&#xff08;二&#xff09;_webrtc 源码-CSDN博客...

YashanDB共享集群产品能力观测:细节足见功底

本文基于前泽塔数科研发总监-王若楠2024年11月在“2024年国产数据库创新生态大会”-“根”技术专场的演讲整理形成&#xff0c;主要对崖山共享集群YAC的架构、功能、高可用性、性能四大方面进行全面测试&#xff0c;并分享了测试环境和测试结论。 年初&#xff0c;基于某些商业…...

游戏引擎学习第50天

仓库: https://gitee.com/mrxiao_com/2d_game Minkowski 这个算法有点懵逼 回顾 基本上&#xff0c;现在我们所处的阶段是&#xff0c;回顾最初的代码&#xff0c;我们正在讨论我们希望在引擎中实现的所有功能。我们正在做的版本是初步的、粗略的版本&#xff0c;涵盖我们认…...

前端部署实战:从人工发布到全自动化流程

"又发错环境了&#xff01;"周四下午,测试同学小李急匆匆地找到我。原来是开发人员手动部署时,不小心把测试代码发布到了生产环境。这已经是本月第二次类似的事故了。 回想起每次发布时的场景&#xff1a;手动打包、手动上传、手动替换文件...每一步都战战兢兢,生怕…...

JVM系列之内存区域

每日禅语 有一位年轻和尚&#xff0c;一心求道&#xff0c;多年苦修参禅&#xff0c;但一直没有开悟。有一天&#xff0c;他打听到深山中有一古寺&#xff0c;住持和尚修炼圆通&#xff0c;是得道高僧。于是&#xff0c;年轻和尚打点行装&#xff0c;跋山涉水&#xff0c;千辛万…...

如何用3个月零基础入门网络安全?_网络安全零基础怎么学习

&#x1f91f; 基于入门网络安全/黑客打造的&#xff1a;&#x1f449;黑客&网络安全入门&进阶学习资源包 前 言 写这篇教程的初衷是很多朋友都想了解如何入门/转行网络安全&#xff0c;实现自己的“黑客梦”。文章的宗旨是&#xff1a; 1.指出一些自学的误区 2.提供…...

易语言OCR证件照文字识别

一.引言 文字识别&#xff0c;也称为光学字符识别&#xff08;Optical Character Recognition, OCR&#xff09;&#xff0c;是一种将不同形式的文档&#xff08;如扫描的纸质文档、PDF文件或数字相机拍摄的图片&#xff09;中的文字转换成可编辑和可搜索的数据的技术。随着技…...

【人工智能】基于Python的自然语言处理:深入实现文本相似度计算

解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 文本相似度计算是自然语言处理(NLP)中的核心任务,广泛应用于搜索引擎、推荐系统、问答系统等领域。本文全面解析文本相似度计算的核心技术,使用Python中的spaCy和sentence-transformers库实现多种方法,包括基…...

基于wifi的火焰报警系统设计(论文+源码)

1 总体方案设计 在本次基于wifi模板的火焰报警系统中&#xff0c;整个系统架构如图2.1所示&#xff0c;其采用STM32F103单片机作为控制器&#xff0c;并结合DS18B20温度传感器,火焰传感器&#xff0c;ESP8266 WiFi通信模块&#xff0c;蜂鸣器,OLED液晶构成整个系统&#xff0c;…...

【第三节】Git 基本操作指南

目录 前言 一、获取与创建项目 1.1 git init 1.2 git clone 二、基本快照操作 2.1 git add 2.2 git status 2.3 git diff 2.4 git commit 2.5 git reset HEAD 三、 文件管理 3.1 git rm 3.2 git mv 四、Git 文件状态 5.1 工作目录 5.2 暂存区 5.3 本地仓库 5…...

GaLore和Q-GaLore:一种记忆高效的预训练和微调策略,用于大型语言模型(LLMs)

GaLore和Q-GaLore&#xff1a;一种记忆高效的预训练和微调策略&#xff0c;用于大型语言模型&#xff08;LLMs&#xff09; GaLore和Q-GaLore的设计背景、工作原理及其优势 设计背景 随着大型语言模型&#xff08;LLMs&#xff09;的发展&#xff0c;模型的规模和复杂性不断…...

免费开源了一个图床工具 github-spring-boot-starter

文章目录 第一步&#xff0c;新建一个SpringBoot项目第二步&#xff0c;在pom文件里面引入jar包第三步&#xff0c;配置你的github信息github.authorization1、进入github官网&#xff0c;登录账号&#xff0c;点击头像&#xff0c;选择setting2、选择[Developer Settings](htt…...

Android显示系统(13)- 向SurfaceFlinger提交Buffer

Android显示系统&#xff08;01&#xff09;- 架构分析 Android显示系统&#xff08;02&#xff09;- OpenGL ES - 概述 Android显示系统&#xff08;03&#xff09;- OpenGL ES - GLSurfaceView的使用 Android显示系统&#xff08;04&#xff09;- OpenGL ES - Shader绘制三角…...

python小课堂(一)

基础语法 1 常量和表达式2 变量和类型2.1 变量是什么2.2 变量语法 3 变量的类型3.1 动态类型特性 4 注释4.1注释是什么 5 输入输出5.1 print的介绍5.2 input 6 运算符6.1 算术运算符在这里插入图片描述6.2 关系运算符6.3 逻辑运算符6.4赋值运算符 1 常量和表达式 在print()中可…...

【原创教程】西门子1500TCP_UDP通信说明大全(下篇)

2.3.3 TRCV故障说明 通讯无法正常连接时,ERROR引脚和STATUS引脚得状态有助于我们判断错误得原因,根据下表得提示,快速排除问题。 2.3.4 TRCV使用 点击TRCV指令得右上角蓝色图标,打开开始组态画面,按照控制要求填写 EN_R:用于激活接收的控制参数,及何时使用TRCV的接收功…...

【报错记录】Ubuntu22.04解决开机卡在 /dev/sda5 : clean , *files , *blocks

一个愿意伫立在巨人肩膀上的农民...... 一、错误现象 本人的电脑安装Windows10和Ubuntu22.04双系统&#xff0c;一次训练中电脑死机无法开机&#xff0c;重启之后便出现如下错误&#xff0c;在网上寻找过很多方法均无效&#xff0c;在root下禁用了samba服务&#xff0c;也无济…...

JumpServer开源堡垒机搭建及使用

目录 一,产品介绍 二,功能介绍 三,系统架构 3.1 应用架构 3.2 组件说明 3.3 逻辑架构 3.3 逻辑架构 四,linux单机部署及方式选择 4.1 操作系统要求(JumpServer-v3系列版本) 4.1.1 数据库 4.1.3创建数据库参考 4.2 在线安装 4.2.1 环境访问 4.3 基于docker容…...

libilibi项目总结(17)Elasticsearch 的使用

这段代码定义了一个 EsSearchComponent 类&#xff0c;主要用于与 Elasticsearch 进行交互&#xff0c;执行一些基本的操作&#xff0c;如创建索引、保存、更新和删除文档&#xff0c;及搜索操作。以下是对每部分代码的详细解释&#xff1a; 1. 类的依赖注入 Resource privat…...

C++ 模版函数 函数模版 区别

C中&#xff0c;函数模板&#xff08;Function Template&#xff09;和模板函数&#xff08;Template Function&#xff09;是同一个概念&#xff0c;通常没有区分&#xff0c;但为了避免混淆&#xff0c;有时我们可以从不同的角度来看待它们。 1. 函数模板 (Function Templat…...

SpringBoot 3.4.x踩坑记录及解决方案(持续更新)

废话 最近使用JDK17Spring Boot3.4.0 做新项目遇到的一些坑&#xff0c;记录并且给出一些实际的解决方案 一、集成Mybatis Plus 3.5.9的问题 第一&#xff1a;不能只引入mybatis-plus-spring-boot3-starter依赖了&#xff0c;需要配合mybatis-plus-jsqlparser <dependenc…...

Linux文件属性 --- 七种文件类型---文件.目录、软硬链接、字符设备文件

目录 七种文件类型 1、普通文件和目录 2、链接文件 2.1硬链接 2.2软链接 3、字符设备文件 一、七种文件类型 Linux的文件属性中一共有以下七种类型 &#xff1a; 符号类型含义解释-普通文件纯文本文件&#xff08;ASCII&#xff09;和二进制文件&#xff08;binary&#xff…...

C# 读取EXCEL的数据批量插入单个PDF里的多个位置

C# 读取EXCEL的数据批量插入单个PDF里的多个位置 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Reflection; usin…...

ARM Linux 虚拟环境搭建

一、目标 在没有arm硬件的情况下&#xff0c;使用QEMU模拟器&#xff0c;在PC上模拟一块ARM开发板&#xff0c;对ARM Linux进行学习。 二、搭建步骤 首先先有一个Linux 开发环境&#xff0c;我目前使用的是Ubuntu20. 首先安装qemu&#xff0c;qemu的官网&#xff1a;https:…...

【功能安全】安全确认

目录 01 功能安全确认介绍 02 安全确认用例 03 安全确认模板 01 功能安全确认介绍 定义: 来源...

LruCache(本地cache)生产环境中遇到的问题及改进

问题&#xff1a;单机qps增加时请求摘要后端&#xff0c;耗时也会增加&#xff0c;因为超过了后端处理能力&#xff08;最大qps&#xff0c;存在任务堆积&#xff09;。 版本一 引入LruCache。为了避免数据失效&#xff0c;cache数据的时效性要小于摘要后端物料的更新时间&…...

【21天学习AI底层概念】day8 什么是类意识?

类意识&#xff08;Quasi-Consciousness&#xff09; 是一个用来描述人工智能或复杂系统表现出的类似意识的行为或特性的概念。虽然这种系统不具备真正的意识&#xff08;即主观体验、情感和自我觉知&#xff09;&#xff0c;但在外部表现上&#xff0c;它们可能表现出与有意识…...

PostgreSQL JSON/JSONB 查询与操作指南

PostgreSQL 提供了强大的 JSON 和 JSONB 数据类型及相关操作&#xff0c;适用于存储和查询半结构化数据。本文将详细介绍其常用操作。 1. 基础操作 1.1 JSON 属性访问 ->: 返回 JSON 对象中的值&#xff0c;结果为 JSON 格式。 SELECT {"a": {"b": 1…...

SamOutV2 0.18B模型发布

项目地址 SamOutV2 0.18B模型 采取 em参数共享在参数量减半的情况下将维度从1024 拉升到了1536sft 单论对话 loss 保持1.8如果未来匹配state 推理代码性能不变的同时推理任意长度使用资源空间保持不变 模型代码 import torchclass MaxState(torch.nn.Module):def __init__(…...

〔 MySQL 〕事务管理

事务代码目录 1. 设置事务隔离级别 2. 开启事务 3. CRUD操作 3.1 创建&#xff08;Create&#xff09; 3.2 读取&#xff08;Read&#xff09; 3.3 更新&#xff08;Update&#xff09; 3.4 删除&#xff08;Delete&#xff09; 4. 提交或回滚事务 5. 示例&#xff1a…...

centOS定时任务-cron服务

最近在训练模型的过程中&#xff0c;经常会因为内存爆炸而停止模型训练过程&#xff0c;而且因为内存占满停止的训练进程甚至都没有任何的报错提示。 1、需要减少num_worker的数量&#xff0c;降低需要占用内存的数据数量 2、可以通过free -h监控内存的占用情况 3、可以通过lin…...

ubuntu22.04.5本地apt源部署

很多情况下&#xff0c;内网服务器无法连接互联网&#xff0c;这样如果原始系统只是最基本的下载安装包&#xff0c;因为存在依赖包不全的情况&#xff0c;难以对其进行更新及通过apt安装包 所以为解决不能联网的问题&#xff0c;首先先通过可以联网的机器制造好源&#xff0c;…...

CSS 实现带tooltip的slider

现代 CSS 强大的令人难以置信 这次我们来用 CSS 实现一个全功能的滑动输入器&#xff0c;也就是各大组件库都有的slider&#xff0c;效果如下 还可以改变一下样式&#xff0c;像这样 特别是在拖动时&#xff0c;tooltip还能跟随拖动的方向和速度呈现不同的倾斜角度&#xff0c…...

【LeetCode每日一题】——220.存在重复元素 III

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时空频度】九【代码实现】十【提交结果】 一【题目类别】 数组 二【题目难度】 困难 三【题目编号】 220.存在重复元素 III 四【题目描述】 给你一个…...

Git命令

目录 一、创建版本库 二、pwd 命令是用于显示当前的目录 三、通过命令 git init 把这个目录变成git可以管理的仓库 四、ll 五、添加文件和修改提交文件 1.创建文件test.txt --- 此刻文件在工作区&#xff08;WorkSpace&#xff09; 2.使用命令 git add test.txt添加到暂…...

2024第十六届蓝桥杯模拟赛(第二期)-Python

# 2024第十六届蓝桥杯模拟赛&#xff08;第二期&#xff09;-Python题解 # 自己改注释# -----------------------1------------------------ # def prime(x): # if x < 2: # return 0 # for i in range(2, int(x ** 0.5) 1): # if x % i 0: # …...

数据结构:Win32 API详解

目录 一.Win32 API的介绍 二.控制台程序(Console)与COORD 1..控制台程序(Console): 2.控制台窗口坐标COORD&#xff1a; 3.GetStdHandle函数&#xff1a; &#xff08;1&#xff09;语法&#xff1a; &#xff08;2&#xff09;参数&#xff1a; 4.GetConsoleCursorInf…...

Hive-4.0.1数据库搭建(可选配置用户名密码远程连接,涵盖切换为tez引擎)

一、hive搭建&#xff08;所依赖的Hadoop集群参照文章&#xff1a;最新版hadoop-3.4.0集群安装和配置&#xff08;目前论坛的都是老古董了&#xff0c;看我的准没错&#xff01;&#xff01;&#xff01;&#xff09;这里以三台服务器为例_hadoop 3.4安装-CSDN博客&#xff09;…...

【从零开始入门unity游戏开发之——C#篇13】命名规范——驼峰命名法和帕斯卡命名法,函数(方法)的使用介绍

文章目录 一、命名规范1、**驼峰命名法&#xff08;Camel Case&#xff09;**用途&#xff1a; 2、**帕斯卡命名法&#xff08;Pascal Case&#xff09;**用途&#xff1a; 3、**C# 中命名约定的最佳实践**3.1 **类、结构体、接口、枚举、委托**3.2 **方法、属性、事件**3.3 **…...

Android 写排行榜,顶部前三

activity_step_rank.xml <?xml version"1.0" encoding"UTF-8"?> <FrameLayout android:layout_height"match_parent" android:layout_width"match_parent" android:id"id/fragment_parent" android:orientation…...

sql server一些冷知识

1. Sql Server冷知识 &#xff08;1&#xff09; 删除表内容的方法 truncate table 表名 &#xff08;清除表记录&#xff0c;这个快&#xff09; &#xff08;2&#xff09; 列出所有数据库 sp_redatabases &#xff08;3&#xff09; 存储过程的参数命名 参数一定要以开头&am…...

【功能安全】随机硬件失效导致违背安全目标的评估(FMEDA)

目录 01 随机硬件失效介绍 02 FMEDA介绍 03 FMEDA模板 01 随机硬件失效介绍 GBT 34590 part5...

【Qt】信号、槽

目录 一、信号和槽的基本概念 二、connect函数&#xff1a;关联信号和槽 例子&#xff1a; 三、自定义信号和槽 1.自定义槽函数 2.自定义信号函数 例子&#xff1a; 四、带参的信号和槽 例子&#xff1a; 五、Q_OBJECT宏 六、断开信号和槽的连接 例子&#xff1a; …...

二叉树、平衡二叉树、红黑树、BTree、B+Tree的区别

一、二叉查找树 二叉树具有以下性质&#xff1a;左子树的键值小于根的键值&#xff0c;右子树的键值大于根的键值。 如下图所示就是一棵二叉查找树&#xff0c; 对该二叉树的节点进行查找发现深度为1的节点的查找次数为1&#xff0c;深度为2的查找次数为2&#xff0c;深度为n…...