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

webrtc源码走读(二)-QOS-RTT

1、什么是RTT?

RTT的全称是Round-Trip Time,翻译过来就是“环路延时”,简单说就是数据从发送端发出去,到接收端收到后返回确认信息,这一整个过程花费的时间,单位通常是毫秒(ms)。

它为什么重要?因为WebRTC要根据RTT判断网络状况:

  • RTT小(比如20ms):说明网络通畅,数据传得又快又稳。
  • RTT大(比如500ms):说明网络有延迟,数据传输慢,可能需要调整策略来保证通信质量。

2、RTT是怎么来的?------WebRTC中的获取流程

WebRTC通过解析RTCP协议中的RR报告(Receiver Report,接收者报告)计算RTT,整个流程需多个模块协作,以下结合核心类与函数逻辑拆解:

2.1 RTT获取的核心流程
  1. 接收端生成RR报告:接收端收到RTP数据(音视频帧)后,生成RTCP的RR报告,包含“接收时间戳”“延迟since最后一个SR”等计算RTT必需的字段。

  2. RR报告回传与模块处理:RR报告通过网络回传给发送端,发送端各模块按如下路径处理,最终计算RTT:

    BaseChannel::ProcessPacket(接收RR报文)
    → WebRtcVideoChannel::OnRtcpReceived(转发RTCP事件)
    → Call::DeliverRtcp(Call模块分发RTCP任务)
    → VideoSendStreamImpl::DeliverRtcp(视频发送流接收RTCP)
    → ModuleRtpRtcpImpl::IncomingRtcpPacket(RTP/RTCP模块解析报文)
    → RTCPReceiver::ParseCompoundPacket(解析复合RTCP包,识别RR报告)
    → RTCPReceiver::HandleReceiverReport(处理RR报告,调用计算逻辑)
    → RTCPReceiver::HandleReportBlock(提取报告块中的延迟字段)
    → 计算RTT并更新到SendSideBandwidthEstimation::last_round_trip_time_ms(核心参数存储)
    
2.2 核心类RTCPReceiver的RTT计算逻辑

RTCPReceiver是计算RTT的核心类,其HandleReportBlock函数会提取RR报告中的字段,结合发送端的SR(Sender Report)时间戳,计算环路延时,以下是关键逻辑:

// RTCPReceiver.cpp 中处理RR报告块的函数
void RTCPReceiver::HandleReportBlock(const ReportBlock& report_block,const RtcpPacket& rtcp_packet) {// 1. 获取接收端记录的“最后一次收到发送端SR的时间戳”(LSR,Last Sender Report)uint32_t lsr = report_block.last_sender_report_timestamp();// 2. 获取接收端“从收到SR到发送当前RR的延迟”(DLSR,Delay Since Last Sender Report)uint32_t dlsr = report_block.delay_since_last_sender_report();// 3. 从发送端的SR缓存中,获取LSR对应的“发送端实际发送SR的时间”(sender_send_time_ms)auto it = sender_report_map_.find(lsr);if (it == sender_report_map_.end()) {// 若未找到对应的SR,无法计算RTT,直接返回return;}int64_t sender_send_time_ms = it->second;// 4. 计算RTT:公式为“当前时间 - 发送端发SR时间 - 接收端DLSR延迟”// 当前时间(接收端收到RR的时间)- 发送端发SR的时间 - 接收端处理延迟 = 环路延时int64_t now_ms = clock_->TimeInMilliseconds();int64_t rtt_ms = now_ms - sender_send_time_ms - (dlsr * 1000) / 65536;// 注:dlsr单位是1/65536秒,需转换为毫秒(×1000/65536)// 5. 将计算出的RTT更新到带宽估计模块(供NACK/FEC使用)bandwidth_observer_->OnReceivedRtcpReceiverReport(rtt_ms);// 最终更新到SendSideBandwidthEstimation::last_round_trip_time_ms
}

img

图片解释:

阶段1:在RTCP RR报告中计算RTT延时(“RTT是怎么算出来的”)

这是RTT的“诞生环节”——接收端通过解析RTCP协议的RR报告(接收者报告),从底层协议一路解析到最终计算出RTT:

  • BaseChannel::ProcessPacket 开始接收RTCP报文,经过 WebRtcVideoChannelCallVideoSendStream 等模块层层转发,最终由 RTCPReceiver 负责解析报文。
  • 核心逻辑在 RTCPReceiver::HandleReportBlock 函数中:它提取RR报告里的 LSR(最后一次收到发送端“SR报告”的时间戳)和 DLSR(接收端处理延迟),通过公式算出RTT,然后更新到 last_round_trip_time_ms 这个关键参数中。

阶段2:获取更新到的RTT信息(“RTT怎么被上层模块拿到的”)

计算好的RTT需要被上层模块感知,才能用来调整网络策略:

  • SendSideCongestionController::MaybeTriggerOnNetworkChanged 开始(这是一个定时触发的网络状态更新逻辑),通过 BitrateControllerImplSendSideBandwidthEstimation 等模块,最终拿到最新的 last_round_trip_time_ms(也就是当前的RTT值)。

阶段3:线程定时调用,更新保护机制(“RTT怎么指导抗丢包策略的”)

RTT最终用来决定是用**NACK(重传)还是FEC(前向纠错)**来抗丢包,这部分是“策略执行环节”:

  • 定时线程由 PlatformThread::StartThread 启动,触发 ProcessThreadImpl 的处理逻辑。
  • 最终调用 VCMNackFecMethod::ProtectionFactor 函数——根据RTT的大小,决定是只开NACK、只开FEC,还是混合使用,从而动态调整音视频传输的抗丢包策略。

3、RTT的核心作用:决定NACK和FEC怎么工作

WebRTC的NACK(否定确认)和FEC(前向纠错)是抗丢包的核心机制,RTT通过VCMNackFecMethod等类直接决定两种机制的选择、参数配置,以下结合关键源码拆解。

3.1 影响NACK和FEC的“选择”:低/中/高RTT对应不同策略

VCMNackFecMethod::ProtectionFactor是决定NACK/FEC选择的核心函数,它根据RTT大小切换“NACK-only”“FEC-only”“混合模式”,以下是带注释的源码片段:

// VCMNackFecMethod.cpp 中决定保护机制的函数
bool VCMNackFecMethod::ProtectionFactor(const VCMProtectionParameters* parameters) {// parameters->rtt:当前最新的RTT值(从last_round_trip_time_ms获取)// _lowRttNackMs:低RTT阈值(默认~100ms,可配置)// _highRttNackMs:高RTT阈值(默认~300ms,可配置)// 1. 低RTT场景:仅用NACK,关闭FEC(FEC冗余因子设为0)if (_lowRttNackMs == -1 || parameters->rtt < _lowRttNackMs) {// 调用父类FEC方法,将FEC的delta帧保护因子设为0(不发FEC冗余)VCMFecMethod::UpdateProtectionFactorD(_protectionFactorD);_protectionFactorD = 0; // FEC冗余量为0,仅用NACK}// 2. 中RTT场景:混合模式(FEC+NACK),调整FEC冗余量else if (_highRttNackMs == -1 || parameters->rtt < _highRttNackMs) {// adjustRtt:RTT调整因子(范围0~1,RTT越大,因子越小,FEC冗余越少,依赖NACK补充)float adjustRtt = 1.0f; // 注:原代码暂禁用动态调整,实际场景会根据RTT查表计算// 按RTT调整FEC的delta帧冗余因子(RTT越大,FEC冗余越少,减少带宽浪费)_protectionFactorD = static_cast<uint8_t>(adjustRtt * static_cast<float>(_protectionFactorD));// 更新调整后的FEC参数VCMFecMethod::UpdateProtectionFactorD(_protectionFactorD);}// 3. 高RTT场景:仅用FEC(NACK重发会迟到,无效)else {// 不调整FEC冗余因子,保持默认值(靠FEC独立抗丢包)VCMFecMethod::UpdateProtectionFactorD(_protectionFactorD);}return true;
}
3.2 影响FEC的“冗余量”:RTT决定最大保护帧数

前情提要(术语解释):

①时间分层:

概念:把视频拆成“基础层+增强层”,分层传

时间分层是WebRTC为了适配不同网络带宽的一种技术:

  • 它把原本连续的视频帧,拆成多个“层”。最底层叫“基础层”,画面最核心(比如能保证基本流畅);上面的“增强层”则是补充细节(让画面更清晰、更流畅)。
  • 比如,把30帧/秒的视频拆成2层:基础层可能只有15帧/秒(保证基本流畅),增强层再补充15帧/秒(让画面更丝滑)。

作用:网络差就只传基础层,网络好就传多层

  • 当用户网络很差时,只发基础层,保证“能看”;
  • 当用户网络变好时,再发增强层,让画面“更清晰/更流畅”。
    这样既避免了“网络差时视频直接卡住”,又能在网络好时提供更好的体验。

实现逻辑:分层后,各层帧间隔发送

还是以“30帧/秒拆成2层”为例:

  • 基础层的帧:第1、3、5…帧(间隔1帧发);
  • 增强层的帧:第2、4、6…帧(补全基础层的间隔)。
    接收端收到后,把两层的帧合起来,就能还原30帧/秒的流畅画面;如果只收到基础层,也能以15帧/秒的帧率播放,不至于完全看不了。

简单来说,时间分层就是**“视频版的‘低配/高配切换’”**——网络差就用低配(基础层),网络好就用高配(基础层+增强层),从而在各种网络环境下都能保证视频能看、好看。

FEC的“最大保护帧数”指一次为多少帧数据准备冗余,RTT越大,需保护的帧数越多(避免多帧在传输中同时丢包),核心逻辑在VCMNackFecMethod::ComputeMaxFramesFec

// VCMNackFecMethod.cpp 中计算FEC最大保护帧数的函数
int VCMNackFecMethod::ComputeMaxFramesFec(const VCMProtectionParameters* parameters) {// parameters->rtt:当前RTT(ms)// parameters->frameRate:视频帧率(fps)// parameters->numLayers:时间分层数(用于适配不同网络)// 1. 特殊场景:时间分层数>2时,仅保护1帧(多层数据间隔大,多帧保护意义小)if (parameters->numLayers > 2) {return 1; }// 2. 计算基础层帧率:时间分层会降低基础层帧率(分层数越多,基础层帧率越低)// 例:2层分层时,基础层帧率=原帧率/(2^(2-1))=原帧率/2float base_layer_framerate = parameters->frameRate / static_cast<float>(1 << (parameters->numLayers - 1));// 3. 核心公式:计算最大保护帧数(RTT越大,帧数越多)// 逻辑:2 × 基础层帧率 × (RTT/1000) → 确保1个RTT内传输的帧都被FEC覆盖int max_frames_fec = std::max(static_cast<int>(2.0f * base_layer_framerate * parameters->rtt / 1000.0f + 0.5f), // +0.5f用于四舍五入1 // 最少保护1帧,避免帧数为0);// 4. 限制最大帧数(避免FEC冗余过多,浪费带宽)// kUpperLimitFramesFec:默认是10帧(WebRTC内置常量)if (max_frames_fec > kUpperLimitFramesFec) {max_frames_fec = kUpperLimitFramesFec;}return max_frames_fec; // 返回最终的FEC最大保护帧数
}

VCMNackFecMethod::ComputeMaxFramesFec 代码运算示例表

场景编号输入参数(代码中parameters值)代码执行关键步骤最终FEC最大保护帧数
1- 时间分层数(numLayers):1
- 视频帧率(frameRate):30fps
- RTT:100ms
1. 分层数=1≤2,不触发特殊场景;
2. 基础层帧率=30/(2^(1-1))=30/1=30fps;
3. 核心公式计算:2×30×(100/1000)+0.5=6.5→取整6,且6≥1;
4. 6≤10(默认上限),不触发限制。
6帧
2- 时间分层数(numLayers):2
- 视频帧率(frameRate):60fps
- RTT:200ms
1. 分层数=2≤2,不触发特殊场景;
2. 基础层帧率=60/(2^(2-1))=60/2=30fps;
3. 核心公式计算:2×30×(200/1000)+0.5=12.5→取整12,且12≥1;
4. 12>10(默认上限),触发限制,取10。
10帧
3- 时间分层数(numLayers):3
- 视频帧率(frameRate):24fps
- RTT:500ms
1. 分层数=3>2,触发特殊场景,直接返回1;
2-4步不执行(因第一步已返回结果)。
1帧
4- 时间分层数(numLayers):1
- 视频帧率(frameRate):15fps
- RTT:50ms
1. 分层数=1≤2,不触发特殊场景;
2. 基础层帧率=15/(2^(1-1))=15/1=15fps;
3. 核心公式计算:2×15×(50/1000)+0.5=1.5→取整1,且1≥1;
4. 1≤10,不触发限制。
1帧
5- 时间分层数(numLayers):2
- 视频帧率(frameRate):45fps
- RTT:150ms
1. 分层数=2≤2,不触发特殊场景;
2. 基础层帧率=45/(2^(2-1))=45/2=22.5fps;
3. 核心公式计算:2×22.5×(150/1000)+0.5=7.25→取整7,且7≥1;
4. 7≤10,不触发限制。
7帧

4、RTT的另一个作用:配置NACK的“缓存时间”

img

NACK会将“待重发的数据”存在NackModule的“NACK列表”中,RTT决定“等多久判断数据丢包、发起重发”,核心逻辑在NackModule::GetNackBatch,源码注释如下:

// NackModule.cpp 中获取NACK重发列表的函数
std::vector<uint16_t> NackModule::GetNackBatch(int64_t now_ms) {std::vector<uint16_t> nack_batch; // 存储待重发的序列号auto it = nack_list_.begin();while (it != nack_list_.end()) {// it->second:NACK列表中的条目(含sent_at_time:上次发送时间,retries:重发次数)// delay_timed_out:是否超过“等待RTT”的时间(判断是否丢包)bool delay_timed_out = now_ms - it->second.sent_at_time >= rtt_ms_;// consider_timestamp:是否满足时间戳判断条件(辅助丢包检测)bool consider_timestamp = it->second.sent_at_time == 0 || delay_timed_out;// 核心逻辑:若超过1个RTT未收到确认,判定为丢包,加入重发列表if (delay_timed_out && consider_timestamp) {nack_batch.emplace_back(it->second.seq_num); // 加入待重发列表it->second.retries++; // 重发次数+1it->second.sent_at_time = now_ms; // 更新本次重发时间// 若重发次数超过上限(kMaxNackRetries:默认3次),从列表删除(避免无限重发)if (it->second.retries >= kMaxNackRetries) {LOG(LS_WARNING) << "Sequence number " << it->second.seq_num << " removed from NACK list due to max retries.";it = nack_list_.erase(it); // 删除超次数条目continue;}}++it;}return nack_batch; // 返回待重发列表,供发送端重发
}

关键逻辑解读:

  • now_ms - it->second.sent_at_time >= rtt_ms_:若数据发送后,超过1个RTT未收到确认,判定为丢包,发起重发。
  • kMaxNackRetries:默认3次重发上限,超过后删除条目,避免因网络极差导致的无限重发,浪费带宽。

5、总结:RTT是WebRTC QoS的“核心指挥棒”

整个WebRTC的QoS(服务质量)保障里,RTT通过RTCPReceiver计算、VCMNackFecMethodNackModule应用,扮演着“眼睛”和“指挥棒”的角色:

  • 作为“眼睛”:通过RR报告反映网络延迟状况,让WebRTC实时感知网络快慢。
  • 作为“指挥棒”:
    1. 决定NACK/FEC的选择(低RTT用NACK,高RTT用FEC,中RTT混合用);
    2. 配置FEC的最大保护帧数(RTT越大,保护帧数越多);
    3. 设定NACK的缓存重发时间(超过1个RTT判定丢包,发起重发)。
http://www.dtcms.com/a/520902.html

相关文章:

  • 【Java】线程安全问题
  • 数据结构算法学习:LeetCode热题100-链表篇(下)(随机链表的复制、排序链表、合并 K 个升序链表、LRU 缓存)
  • 网络营销网站 功能南京微信小程序开发制作
  • 域名打不开原来的网站手机免费网站制作
  • 中药饮片网购是什么?主要的市场特点及未来发展潜力如何?
  • 自己做的网站数据库360优化大师app
  • Python-__init__函数
  • 沈阳网站维护公司网站建设财务怎么入账
  • JavaEE:知识总结(一)
  • 各家高性能MCU的内置Flash逐渐走向MRAM之路,关于嵌入式 MRAM 的性能和能效
  • Leetcode 35
  • GPIO口输出
  • 专教做美食的网站胶州哪里有做网站的
  • 企业网站制作 徐州网站设计原则的历史
  • 隐私保护与数据安全合规(十三)
  • 2025年高真空共晶炉排名
  • 网站做转链接违反版权吗wordpress页面不显示子类
  • 5.3类的构造方法
  • 视频监控系统原理与计量
  • 蓝桥杯高校新生编程赛第一场题解——Java
  • JavaScript 的优势和劣势是什么?
  • 鸿蒙Next的Camera Kit:开启全场景智慧影像开发新纪元
  • 软件开发包含网站开发吗搭建网站成本
  • asp.net 微网站开发教程比较大的建站公司
  • h5游戏免费下载:小猪飞飞
  • 基于单片机的档案库房漏水检测报警labview上位机系统设计
  • 网站开发图标汕头网站建设seo外包
  • DeepSeek-OCR:光学Token:长上下文建模的范式转变
  • Windows 11 24H2内核堆栈保护:系统安全新盾牌
  • 自定义组件(移动端下拉多选)中使用 v-model