流媒体基础-RTCP

news2024/11/26 15:50:07

1、RTCP的封装

RTP需要RTCP为其服务器质量提供保证,周期性发送

RTCP的主要功能是:服务质量的监视反馈(QoS)媒体间的同步(Sync),以及多播组中成员的标识。在RTP会话期间,各参与者周期性地传送RTCP包。RTCP包中含有已发送的数据包的梳理、丢失的数据包数量等统计资料。

因此,各参与者可以利用这些信息动态地修改传输速率,甚至改变有效载荷类型。RTP和RTCP配合使用,它们能以有效的反馈和最小的开销使传输效率最佳化,因而特别适合传送网上的实时数据。

RTCP由如下五种分组类型:

200

SR(Sender Report)

发送端报告

201

RR(Receiver Report)

接收端报告

202

SDES(Source Description Items)

源点描述

203

BYE

结束传输

204

APP

特定应用

上述五种分组的封装大同小异,下面只讲诉SR类型,其他类型请参考RFC5330。

1.1、RTCP头部

版本(V)

2B

RTCP版本号

填充标志(P)

1B

填充标志,如果P=1,则该RTCP包的尾部就包含附加的填充字节

接收报告计数器(RC)

5B

接收报告计数器,表示SR包中接收报告块的数目,可以为零

包类型(PT)

8B

SR包是200

长度域(length)

16B

RTCP包的长度,包括填充的内容

/**
 * RTCP固定头
 */ 
typedef struct _rtcp_hdr_t {
	uint32_t v:2;		/* version */
	uint32_t p:1;		/* padding */
	uint32_t rc:5;		/* reception report count */
	uint32_t pt:8;		/* packet type */
	uint32_t length:16; /* pkt len in words, w/o this word */
} rtcp_hdr_t;

2.2、RTCP报告

/**
 * RTCP报告类型
 */
enum {
    RTCP_FIR    = 192, // 关键帧请求, RFC2032
    RTCP_NACK   = 193, // 丢包重传, RFC2032
    RTCP_SMPTETC= 194, // RFC5484
    RTCP_IJ     = 195, // RFC5450
    
	RTCP_SR		= 200, // 发送者报告
	RTCP_RR		= 201, // 接受者报告
	RTCP_SDES	= 202, // 源点描述
	RTCP_BYE	= 203, // 结束传输
	RTCP_APP	= 204, // 特定应用
    
    RTCP_RTPFB  = 205, // RTP Feedback, RFC4585
    RTCP_PSFB   = 206, // PS Feedback, RFC4585
    RTCP_XR     = 207, // RFC3611
    RTCP_AVB    = 208,
    RTCP_RSI    = 209, // RFC5760
    RTCP_TOKEN  = 210, // RFC6284
	RTCP_IDMS	= 211, // RFC7272
	RTCP_RGRS	= 212, // RFC8861

	RTCP_LIMIT  = 223,
};

2.2.1、发送端报告(SR)

发送端报告分组SR(Sender Report)用来使发送端以多播方式向所有接收端报告发送情况。

发送者报告SR的PT值:

以下是发送者报告详细内容:

同步源(SSRC of sender)

32B

SR包发送者的同步源标识符,与对应RTP包中的SSRC一样

NTP时间戳(NTP timestamp)

64B

表示发送此报告时以挂钟时间测量的时间点。 结合来自各个接收器的接收报告中返回的时间戳,它可用于估计往返于接收器的往返传播时间

RTP时间戳(RTP timestamp)

32B

与NTP时间戳对应,与RTP数据包中的RTP时间戳具有相同的单位和随机初始值

发送包总数(packet count)

32B

从开始发送包到产生这个SR包这段时间里,发送者发送的RTP数据包的总数,SSRC改变时,这个域清零

发送总字节数(octet count)

32B

从开始发送包到产生这个SR包这段时间里,发送者发送的净荷数据的总字节数(不包括头部和填充)发送者改变其SSRC时,这个域要清零

同步源N的SSRC标识符(SSRC_n)

32B

该报告块中包含的是从该源接收到的包的统计信息

丢包率(Fraction Lost)

8B

从开始接收到SSRC_n的包到发送SR,从SSRC_n传过来的RTP数据包的丢失总数

累计的包丢失数目(cumulative number of packets lost)

24B

从开始接收到SSRC_n的包到发送SR,从SSRC_n传过来的RTP数据包的丢失总数

收到的扩展最大序列号(extended highest sequence number received)

32B

从SSRC_n收到的RTP数据包中最大的序列号

接收抖动(interarrival jitter)

32B

RTP数据包接受时间的统计方差估计

上次SR时间戳(LSR)

32B

取最近从SSRC_n收到的SR包中的NTP时间戳的中间32比特。如果目前还没收到SR包,则该域清零

上次SR以来的延时(DLSR)

32B

上次从SSRC_n收到SR包到发送本报告的延时

抓包截取的SR报文:

2.2.2、接收端报告(RR)

除包类型代码外,SR与RR间唯一的差别是源报告包含有一个20字节发送者信息段。活动源在发出最后一个数据包之后或前一个数据包与下一个数据包间隔期间发送SR;否则,就发送RR。

SR和RR包都可没有接收报告块也可以包括多个接收报告块,其发布报告表示的源不一定是在CSRC列表上的起作用的源,每个接收报告块提供从特殊源接收数据的统计。最大可有31个接收报告块嵌入在SR 或 RR包中。

接收端报告没有“SenderInfo”,剩余部分都是“Report Block”:

抓包截取的RR报文:

2.2.3、源点描述(SDES)

SDES源描述包提供了直观的文本信息来描述会话的参加者,包括CNAME、NAME、EMAIL、PHONE、LOC等源描述项,这些为接收方获取发送方的有关信息提供了方便。SDES 包由包头与数据块组成,数据块可以没有,也可有多个。

/**
 * SDES报告类型
 */
enum
{
	RTCP_SDES_END		= 0, 
	RTCP_SDES_CNAME		= 1, // 源唯一标识
	RTCP_SDES_NAME		= 2, // 描述源的名称
	RTCP_SDES_EMAIL		= 3, // email
	RTCP_SDES_PHONE		= 4, // 电话号码
	RTCP_SDES_LOC		= 5, // 位置
	RTCP_SDES_TOOL		= 6,
	RTCP_SDES_NOTE		= 7, // 备注信息
	RTCP_SDES_PRIVATE	= 8, // 私有扩展
};

例如:

抓包截取的SDES报文:

2.2.4、结束传输(BYE)

参与者发送 BYE 数据包以指示一个或多个源不再活动,可选择给出离开的理由。

作为可选项,BYE包可包括一个8位八进制计数,后跟文本信息,表示离开原因,如:"cameramalfunction"或"RTPloop detected"。字符串的编码与在SDES 项中所描述的相同。如字符串信息至BYE包下32位边界结束处,字符串就不以空结尾;否则,BYE包以空八进制填充。

抓包截取到的BYE报文:

2.2.5、特定应用(APP)

这里仅是RTCP报文的环路延时和丢包率的计算,实际的QOS用的不是这个机制。

2、SR, RR

2.1、环路时延(rtt)

// ArriveTime是发送者接收到RR的时间
// LSR为发送者发出SR的时间,DLSR为接收端处理SR报的耗时
RTT = ArriveTime - LSR - DLSR
// 上面等式等同于
RTT = (ST2 - ST1) - (RT2 - RT1)
// 真实的计算公式
RTT = (A - LSR - DLSR) >> 16 // 单位是秒

要知道,发送端和接收端的时钟是不同的,所以在计算NTP时,必须在同一个时钟上进行计算,而计算差值时可以跨时钟计算。

其中,这几个值的详细说明:

  • NTP时间戳

NTP把当前时间(自1970.1.1以来的秒数)分为整数部分N和小数部分X

NTP高位 = 整数部分N + 2208988800UL(其中2208988800UL表示自1900.1.1到1970.1.1的秒数)

NTP低位 = 小数部分X * (2^32)(其中4294967296为2^32)

  • LSR

取自上一个接收到的SR包中的NTP值,取ntp_msw的低16位和ntp_lsw的高16位。

ntp_msw&0xffff + ntp_lsw>>16

  • DLSR

从接收到上一个SR包到发送此接收报告块之间的延时,以1/65536秒为单位。

在WebRTC里面函数实现如下:

// 处理接受者报告
void RTCPReceiver::HandleReportBlock(const ReportBlock& report_block,
                                     PacketInformation* packet_information,
                                     uint32_t remote_ssrc) {
  // This will be called once per report block in the RTCP packet.
  // We filter out all report blocks that are not for us.
  // Each packet has max 31 RR blocks.
  //
  // We can calc RTT if we send a send report and get a report block back.

  // `report_block.source_ssrc()` is the SSRC identifier of the source to
  // which the information in this reception report block pertains.

  // Filter out all report blocks that are not for us.
  if (!registered_ssrcs_.contains(report_block.source_ssrc()))
    return;

  last_received_rb_ = clock_->CurrentTime();

  ReportBlockData* report_block_data =
      &received_report_blocks_[report_block.source_ssrc()];
  RTCPReportBlock rtcp_report_block;
  rtcp_report_block.sender_ssrc = remote_ssrc;
  rtcp_report_block.source_ssrc = report_block.source_ssrc();
  rtcp_report_block.fraction_lost = report_block.fraction_lost();
  rtcp_report_block.packets_lost = report_block.cumulative_lost_signed();
  if (report_block.extended_high_seq_num() >
      report_block_data->report_block().extended_highest_sequence_number) {
    // We have successfully delivered new RTP packets to the remote side after
    // the last RR was sent from the remote side.
    last_increased_sequence_number_ = last_received_rb_;
  }
  rtcp_report_block.extended_highest_sequence_number =
      report_block.extended_high_seq_num();
  rtcp_report_block.jitter = report_block.jitter();
  rtcp_report_block.delay_since_last_sender_report =
      report_block.delay_since_last_sr();
  rtcp_report_block.last_sender_report_timestamp = report_block.last_sr();
  // Number of seconds since 1900 January 1 00:00 GMT (see
  // https://tools.ietf.org/html/rfc868).
  report_block_data->SetReportBlock(
      rtcp_report_block,
      (clock_->CurrentNtpInMilliseconds() - rtc::kNtpJan1970Millisecs) *
          rtc::kNumMicrosecsPerMillisec);

  uint32_t send_time_ntp = report_block.last_sr();
  // RFC3550, section 6.4.1, LSR field discription states:
  // If no SR has been received yet, the field is set to zero.
  // Receiver rtp_rtcp module is not expected to calculate rtt using
  // Sender Reports even if it accidentally can.
  if (send_time_ntp != 0) {
    uint32_t delay_ntp = report_block.delay_since_last_sr();
    // Local NTP time.
    uint32_t receive_time_ntp =
        CompactNtp(clock_->ConvertTimestampToNtpTime(last_received_rb_));

    // RTT in 1/(2^16) seconds.
    uint32_t rtt_ntp = receive_time_ntp - delay_ntp - send_time_ntp;
    // Convert to 1/1000 seconds (milliseconds).
    TimeDelta rtt = CompactNtpRttToTimeDelta(rtt_ntp);
    report_block_data->AddRoundTripTimeSample(rtt.ms());
    if (report_block.source_ssrc() == local_media_ssrc()) {
      rtts_[remote_ssrc].AddRtt(rtt);
    }

    packet_information->rtt_ms = rtt.ms();
  }

  packet_information->report_blocks.push_back(
      report_block_data->report_block());
  packet_information->report_block_datas.push_back(*report_block_data);
}

2.2、丢包率(fraction lost)

以PeerA端为例,PeerA想要知道:

  • PeerA发送给PeerB的RTP报文的丢包率和环路延时;
  • PeerB发送给PeerA的RTP报文的丢包率;

接收端维护两个计数器,每收到一个RTP包都更新:

  • transmitted,接收到的RTP包的总数;
  • retransmitted,接收到重传RTP包的数量;

某时刻收到的有序包的数量count = transmitted-retransmitte,当前时刻为count2,上一时刻为count1;

接收端以一定的频率发送RTCP包(RR、REMB、NACK等)时,会统计两次发送间隔之间(fraction)的接收包信息

// 两次发送间隔之间理论上应该收到的包数量 = 当前接收到的最大包序号 - 上个时刻最大有序包序号
uint16_t exp_since_last = (received_seq_max_ - last_report_seq_max_);
// 两次发送间隔之间实际接收到有序包的数量 = 当前时刻收到的有序包的数量 - 上一个时刻收到的有序包的数量
uint32_t rec_since_last = count2 - count1
// 丢包数 = 理论上应收的包数 - 实际收到的包数
int32_t missing = exp_since_last - rec_since_last

missing即为两次发送间隔之间的丢包数量,会累加并通过RR包通知发送端。

// 更新统计数
void StreamStatisticianImpl::UpdateCounters(const RtpPacketReceived& packet) {
  RTC_DCHECK_EQ(ssrc_, packet.Ssrc());
  // 当前时钟
  int64_t now_ms = clock_->TimeInMilliseconds();

  incoming_bitrate_.Update(packet.size(), now_ms);
  receive_counters_.last_packet_received_timestamp_ms = now_ms;
  receive_counters_.transmitted.AddPacket(packet);
  --cumulative_loss_;

  // 包序号
  int64_t sequence_number =
      seq_unwrapper_.UnwrapWithoutUpdate(packet.SequenceNumber());
  // RR包
  if (!ReceivedRtpPacket()) {
    received_seq_first_ = sequence_number;
    last_report_seq_max_ = sequence_number - 1;
    received_seq_max_ = sequence_number - 1;
    receive_counters_.first_packet_time_ms = now_ms;
  } else if (UpdateOutOfOrder(packet, sequence_number, now_ms)) {
    return;
  }

  cumulative_loss_ += sequence_number - received_seq_max_;
  received_seq_max_ = sequence_number;
  seq_unwrapper_.UpdateLast(sequence_number);

  // 如果接收到新的时间戳和多个有序数据包,则计算新的抖动统计。
  if (packet.Timestamp() != last_received_timestamp_ &&
      (receive_counters_.transmitted.packets -
       receive_counters_.retransmitted.packets) > 1) {
    UpdateJitter(packet, now_ms);
  }
  last_received_timestamp_ = packet.Timestamp();
  last_receive_time_ms_ = now_ms;
}

接收端发送的RR包中包含两个丢包,一个是fraction_lost,是两次统计间隔间的丢包率(以256为基数换算成8bit),一个是cumulative_lost,是总的累积丢包。

// RTCP包统计
RtcpStatistics StreamStatisticianImpl::CalculateRtcpStatistics() {
  RtcpStatistics stats;

  if (last_report_inorder_packets_ == 0) {
    // First time we send a report.
    last_report_seq_max_ = received_seq_first_ - 1;
  }

  // last_report_seq_max_ 上一次发送rr时的seq_max
  // received_seq_max_ 当前的seq_max
  // exp_since_last 期望能收到多少包
  uint16_t exp_since_last = (received_seq_max_ - last_report_seq_max_);

  // ......
  
  // 当前收到的有序包的数量 = transmitted.packets - retransmitted.packets
  // 上一次发送RR时收到包的数量: last_report_old_packets_
  // 实际收到包的数量: rec_since_last
  uint32_t rec_since_last =
      (receive_counters_.transmitted.packets -
       receive_counters_.retransmitted.packets) - last_report_inorder_packets_;
  
  // With NACK we don't know the expected retransmissions during the last
  // second. We know how many "old" packets we have received. We just count
  // the number of old received to estimate the loss, but it still does not
  // guarantee an exact number since we run this based on time triggered by
  // sending of an RTP packet. This should have a minimum effect.

  // With NACK we don't count old packets as received since they are
  // re-transmitted. We use RTT to decide if a packet is re-ordered or
  // re-transmitted.
  // 计算从上一次rr到当前这段时间内,收到的重传包总数
  uint32_t retransmitted_packets =
      receive_counters_.retransmitted.packets - last_report_old_packets_;
  // 实际丢包数加上重传丢包数
  rec_since_last += retransmitted_packets;

  // 计算丢包数:期望收到的包总数exp_since_last - 实际收到的包总数rec_since_last
  int32_t missing = 0;
  if (exp_since_last > rec_since_last) {
    missing = (exp_since_last - rec_since_last);
  }
  
  // 丢包率 = 255 * 丢包数 / 预期收到的包总数
  uint8_t local_fraction_lost = 0;
  if (exp_since_last) {
    // Scale 0 to 255, where 255 is 100% loss.
    local_fraction_lost =
        static_cast<uint8_t>(255 * missing / exp_since_last);
  }
  stats.fraction_lost = local_fraction_lost;

  // We need a counter for cumulative loss too.
  // TODO(danilchap): Ensure cumulative loss is below maximum value of 2^24.
  // 累加丢包总数
  cumulative_loss_ += missing;
  stats.cumulative_lost = cumulative_loss_;    
  // ......
}

2.3、抖动计算(jitter)

Jitter是一个统计变量,它用来表征RTP数据包与包的传输时间之间的差异程度。具体的推算公式如下:

J(i) = J(i-1) + (传输预估差 - J(i-1)) / 16

J(i) = J(i-1) + (| D(i-1,i) | - J(i-1))/16

i时刻的jitter是由i-1时刻的jitter推出的。其中D(i-1,i)表达式如下:

D(i,j) = 接收时间差 - 发送时间差

D(i,j) = (Rj - Ri) - (Sj - Si) = (Rj - Sj) - (Ri - Si)

Si表示i包里的RTP timestamp,Ri表示i包到达目的端时的timestamp。

jitter的单位是RTP中timestamp的单位。RTP timestamp单位和RTP Data的采样频率有关,比如采用PCMU编码的audio包采样频率是8000Hz,那timestamp单位就是1/8000s;采用OPUS编码的audio包采样频率是48000Hz,那timestamp单位就是1/48000s。

whireshark抓包测试:

1)PCMU:

如图相隔1s的两个包,timestamp相减1441520544 - 1441512544结果等于8000,即PCMU采样频率;

2)OPUS:

相隔1s的两个包,timestamp相减879484868 - 879436868结果等于48000,即OPUS采样频率。

RTP接收端根据上面的公式推算出jitter值后,会通过RTCP发送给对端。以Receiver Report为例,jitter在RTCP中的存放位置如下:

Jitter计算实例

让我们以一个jitter的计算实例来加深对jitter的理解。我们假设发送端每隔20ms发送一个RTP包,并且理想的传输时间是10ms。为了便于理解,我们使用ms为jitter的计算单位(timestamp单位和时间单位可以换算)。RTCP规定timestamp的起始值是一个随机值,同样为了便于理解,我们起始值设为0。具体计算如下表:

根据公式:D(i,j) = (Rj - Ri) - (Sj - Si) = (Rj - Sj) - (Ri - Si)

  1. D(i,j) = (30 - 10) - (20 - 0) = 0
  2. D(i,j) = (49 - 30) - (40 - 20) = -1
  3. D(i,j) = (74 - 49) - (60 - 40) = 5
  4. D(i,j) = (90 - 74) - (80 - 60) = -4

从表中可以看出,当包与包的传输时间之间的差异变大时,jitter值也开始增长。同时,jitter并没有随着差异的急速变大而剧烈变化,这是由于有去噪增益1/16。当差异逐渐缩小趋于稳定时,jitter的推算值也趋近于一个预估的均值。

Jitter Buffer

因网络状况总是千变万化的,那么包在网络上传输的时间也就不断变化。对于RTC audio的应用场景,为了得到良好的音频效果,我们需要以一个固定的时间间隔来播放音频包。因此,在接收端,无论网络延时怎么变化,我们都需要将变化的延时转化成固定的延时。而这,可以通过jitter buffer来实现

jitter buffer的实现很简单:我们可以创建一个buffer来存储100ms时长的audio包。假设audio的采样频率是8000Hz,那么100ms就对应800个sample。我们可以设定如果buffer的sample数达到总的一半了,就可以去取出来播放了

当然,这样做并不能确保播放质量。因为有可能网络实在是太差了,我们想要去取下一个audio包,但是buffer已经空了;也可能buffer已经满了,但是又接收到了audio包(只能丢弃)。为了减少这种风险,我们可以增加buffer的大小,但这样同时也增加了音频的滞后:因为如果buffer里满了50ms的audio包才取出来播放,那audio音频其实滞后了50ms。能不能再改进呢?

能的,我们可以使用根据jitter值自适应调整大小的buffer。这样既能适应网络状况变换,又能兼顾音频滞后的负面影响。

Jitter的起因

除了因为网络状况导致的传输时间变化,jitter还可能是由发送端自身导致的。当发送端的audio包不是从系统声卡采集的,而是从其他应用软件或某个音频文件采集的,那么也可能造成较大的jitter。

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

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

相关文章

Lichee_RV学习系列---认识Lichee_RV、环境搭建和编译第一个程序

系列文章目录 文章目录系列文章目录前言一、认识Lichee RV1、D1-H 芯片2、Lichee RV开发板3、系统镜像二、Lichee RV 固件烧录1、要求基本硬件2、基本资料下载3、固件烧录在这里插入图片描述三、连接上开发板1、ADB方式连接a&#xff1a;ADB下载b&#xff1a;ADB连接c&#xff…

孙溟㠭篆刻《无有中无尽藏》

《无有中无尽藏》孙溟㠭篆刻 无一物中无尽藏&#xff0c;是说当“我执”袪除&#xff0c;仅余“真如”时&#xff0c;便可以理解“无尽藏”。虽然身上没有东西&#xff0c;但是其实世人身上藏了所有的东西。“无心”亦是有心&#xff0c;心中富足。所以当人祛除心中的偏执&…

自动语音识别(ASR)研究综述

自动语音识别ASR研究综述 一、语言识别基础知识 从语音系统识别构成来讲&#xff0c;一套完整的语音识别系统包括&#xff1a;预处理、特征提取、声学模型、语言模型、以及搜索算法等模块&#xff0c;具体结构示意图如下所示: 特征提取&#xff08;MFCC声学特征&#xff09…

Error handling response: TypeError: self.processResponse is not a function

问题背景 &#xff1a; 自己在搭建 Vue 初始模板架子的时候 &#xff0c; 解决完 router 路由的报错问题后 &#xff0c; 控制台还剩下一个显眼的 Error 红色 Bug &#xff0c; 不解决的话看着难受 &#xff0c; 盘它 &#xff01; 点击报错内容后进入 &#xff1a; Error h…

redis应用笔记

1.登录服务 在登陆服务中,如果将数据全部存储到tomcat中,当存在多个tomcat的时候,数据是无法同步的,这就导致了数据的共享问题: 1、每台服务器中都有完整的一份session数据&#xff0c;服务器压力过大。 2、session拷贝数据时&#xff0c;可能会出现延迟 解决办法就是采用redi…

SpringBoot整合Redis实现优惠券秒杀服务(笔记+优化思路版)

本文属于看黑马的redis的学习笔记&#xff0c;记录了思路和优化流程&#xff0c;精简版最终版请点击这里查看。 文章目录一、全局ID生成器1.1 理论1.1.1 全局唯一ID生成策略1.2 代码(Redis自增)二、实现优惠券秒杀下单2.1 SQL2.2 SQL对应实体类2.2.1 普通券实体类2.2.2 秒杀券实…

声纹识别之说话人验证speaker verification

目录 一、speaker verification简介 二、主流方案和模型 1、Ecapa_TDNN模型 2、WavLm 三、代码实践 1、Ecapa_TDNN方案 a、模型结构 b、loss c、数据处理 d、模型训练和评估 e、说话人验证推理 2、WavLm预训练方案 a、模型结构和loss b、数据处理 c、模型训练 …

html5支持的几种音频格式介绍

关于音频的格式 ogg音频 Ogg全称应该是OGGVobis(oggVorbis)是一种新的音频压缩格式&#xff0c;类似于MP3等的音乐格式。Ogg是完全免费、开放和没 有专利限制的。OggVorbis文件的扩展名是.OGG。Ogg文件格式可以不断地进行大小和音质的改良&#xff0c;而不影响旧有的编码器或…

合合信息扫描全能王“照片高清修复”功能上线,3秒还原老照片

穿越时光的“美颜”!合合信息智能图像处理技术让老照片“焕新”“春运”已经开始&#xff0c;团聚时刻即将到来。和亲人们一起围炉话家常&#xff0c;翻开旧日的相册&#xff0c;品读一张张泛黄的照片背后最牵动人心的情感&#xff0c;也是“年味”所在。时光会在照片上留下斑驳…

巨量引擎·2023教育Future大会:扎根内容生态,做好经营提效

求知方寸间&#xff0c;如风过千川。当知识创作成为新的潮流&#xff0c;当教育数字化迈入直播与短视频新时代&#xff0c;当图书电商红红火火&#xff0c;如何做好教育全产业链升级与创新&#xff1f;新年伊始&#xff0c;巨量引擎举办“行知.行为.行万里 2023教育Future大会”…

嵌入式实时操作系统的设计与开发(六)

中断系统结构 在RTOS中&#xff0c;中断是与具体硬件平台关联度最大的部分&#xff0c;为了实现高可移植性、可配置性&#xff0c;中断子系统依照aCoral的整体结构来设计&#xff0c;划分为HAL&#xff08;硬件抽象层&#xff09;和内核层。 在HAL层先将各种中断汇拢&#xff…

第三周周二1.10

-A 添加规则 -I 插入 -F 清空 -L 查看 -p 调整默认规则 -D 删除规则 dport : -j ACCEPT DROP REJECT LOG /var/log/messages -n 以数字的形式显示结果 -v 详细信息 -x 精确的 -line-number 行号 删除&#xff1a;指…

2023 年你应该知道的 10 个开源项目

精心策划的 2023 年 GitHub 上最有趣的开发工具和项目列表。1.NetBeansNetBeans 是一个开源的集成开发环境&#xff0c;因其支持多种编程语言和平台而受到开发人员的欢迎。动图2.OpenCVOpenCV 是一个用于图像和视频处理的开源计算机视觉库。它广泛用于对象检测、面部识别和机器…

嬴图 | 走进 Ultipa Manager 之 高可视化

Ultipa Manager是嬴图数据库管理系统基于网页端的应用。自2019年至今&#xff0c;已迭代最新至v4.2版本。本系列&#xff0c;笔者将分三篇文章&#xff0c;引导大家走进嬴图之Ultipa Manager&#xff0c;详细了解其高可视化、数据科学家工具箱、便捷的数据迁移3大亮点功能&…

分享91个NET源码,总有一款适合您

NET源码 分享91个NET源码&#xff0c;总有一款适合您 91个NET源码下载链接&#xff1a;https://pan.baidu.com/s/1dqb9XgiiVfsVkq-wqKt3Kg?pwd275d 提取码&#xff1a;275d 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&…

mybatis插入mysql数据库PersistenceException 数据库连接超时

mybatis插入mysql部分数据失败mybatis插入mysql数据库PersistenceException1、异常堆栈信息&#xff1a;2、问题原因&#xff1a;3、问题排查3.1 查看数据库连接信息3.2 问题解决3.2.1 Spring项目可以在配置文件里面设置3.2.2 修改conn改成局部变量mybatis插入mysql数据库Persi…

python 语法(空行、变量、if条件控制、循环语句)编码示例

文章目录前言python 语法(空行、变量、if条件控制、循环语句)编码示例1 空行&#xff0c;在python中空行也是代码2 单个变量赋值、多个变量赋值3 if条件控制4 循环语句4.1 while循环4.2 for 循环前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff…

C++程序的编译与运行

C和C语言类似&#xff0c;也要经过编译和链接后才能运行。 下图是 C/C 代码生成可执行文件的过程&#xff1a; C源文件的后缀 C语言源文件的后缀非常统一&#xff0c;在不同的编译器下都是.c。C 源文件的后缀则有些混乱&#xff0c;不同的编译器支持不同的后缀&#xff0c;下…

新应用——设备巡检管理,系统化更具稳定性

对于生产制造型企业来说&#xff0c;随着企业逐步发展&#xff0c;产量增大&#xff0c;生产设备的稳定性和安全性就成为企业经济效益的关键。设备巡检是有效保证设备安全和稳定的重要环节。通过设备巡检&#xff0c;定期掌握各台设备的运行情况及周围环境的变化&#xff0c;尽…

算法竞赛百日——快速排序 - 分治

本文已收录于专栏 &#x1f332;《百日算法竞赛》&#x1f332; 目录 本文已收录于专栏 &#x1f332;《百日算法竞赛》&#x1f332; 快速排序 解题思路 ​ 思路分析&#xff1a; 模拟&#xff1a; AC_Code 二分查找 用二分法求 平方根 二分模板 快速排序 给定你…