news 2026/1/15 13:43:35

mediasoup源码走读(六)——NetEQ

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
mediasoup源码走读(六)——NetEQ

🌐 一、NetEQ模块总体架构图

接收端
发送端
BWE
FEC
RTX
发送缓存
码率调整
生成FEC包
生成RTX包
来自接收端
NACK重传
Jitter Buffer
NACK请求
PLI请求
音频
视频
发送NACK
发送PLI
JitterBuffer
RtpStreamRecv
NackHandler
PliHandler
AudioPlayer
VideoDecoder
RateCalculator
RtpStreamSend
FecHandler
RtxHandler
RtpCache
NACK请求

🔧 二、核心机制深度代码解析

1. 发送端抖动缓存(RtpCache)实现

文件worker/src/RTC/RtpCache.cpp
核心类RtpCache(发送端缓存管理)

// RtpCache.cpp -classRtpCache{public:voidAddPacket(RtpPacket*packet){// 1. 检查缓存是否已满(音频:50包, 视频:200包)if(isAudio_&&cache_.size()>=50){EvictOldest();// LRU淘汰}elseif(!isAudio_&&cache_.size()>=200){EvictOldest();}// 2. 保存包(序列号作为key)cache_[packet->sequenceNumber()]=packet;lastSeq_=packet->sequenceNumber();}boolHasPacket(uint16_tseq)const{// 3. 检查序列号是否在缓存范围内(防NACK洪水)if(seq<lastSeq_-500)returnfalse;// 超出缓存范围returncache_.find(seq)!=cache_.end();}RtpPacket*GetPacket(uint16_tseq){// 4. 获取包(返回原始指针,避免拷贝)autoit=cache_.find(seq);if(it!=cache_.end()){returnit->second;}returnnullptr;}private:voidEvictOldest(){// 5. LRU淘汰:移除最早加入的包autoit=cache_.begin();deleteit->second;// 释放内存cache_.erase(it);}std::map<uint16_t,RtpPacket*>cache_;uint16_tlastSeq_=0;boolisAudio_=false;// 由Producer传入};

💡关键细节

  • 缓存大小动态调整(音频50包/视频200包)
  • HasPacket()检查序列号范围(seq < lastSeq_ - 500防攻击)
  • GetPacket()返回原始指针,避免内存拷贝(性能关键)

2. 发送端NACK处理

文件worker/src/RTC/RtpStreamSend.cpp
核心方法HandleNackRequest()

// RtpStreamSend.cpp - NACK请求处理voidRtpStreamSend::HandleNackRequest(conststd::vector<uint16_t>&sequenceNumbers){uint32_tretransmitCount=0;for(uint16_tseq:sequenceNumbers){// 1. 检查序列号是否在缓存中(音频/视频通用逻辑)if(!rtpCache_.HasPacket(seq)){continue;// 未缓存,跳过}// 2. 获取原始包(直接从缓存取)RtpPacket*packet=rtpCache_.GetPacket(seq);if(!packet)continue;// 3. 重传包(通过传输层发送,不经过编码器)transport_->SendRtpPacket(packet);retransmitCount++;}// 4. 更新BWE重传统计(用于动态调整码率)if(retransmitCount>0){rateCalculator_->UpdateRetransmitCount(retransmitCount);// 5. 重传率>10%时触发降码率if(rateCalculator_->GetRetransmitRate()>0.1f){rateCalculator_->AdjustBitrateForTransmit(rateCalculator_->GetBandwidthEstimate()*0.8f);}}}

💡关键细节

  • 重传率>10%时触发主动降码率(避免拥塞雪崩)
  • transport_->SendRtpPacket()直接通过WebRtcTransport发送
  • 重传包不经过编码器(0延迟)

3. FEC生成

文件worker/src/RTC/FecHandler.cpp
核心方法AddFecPacket()

// FecHandler.cpp - FEC生成voidFecHandler::AddFecPacket(RtpPacket*packet){// 1. 音频专用:高冗余(3:1)if(isAudio_){fecEncoder_.SetFecParams(3,1);// 3原始包 + 1 FEC包fecEncoder_.Encode(packet->payload(),fecPayload_);}// 2. 视频专用:中冗余(2:1)else{fecEncoder_.SetFecParams(2,1);// 2原始包 + 1 FEC包fecEncoder_.Encode(packet->payload(),fecPayload_);}// 3. 构造FEC包头(关键:标识为FEC包)fecPacket_->SetSsrc(packet->ssrc());fecPacket_->SetSequenceNumber(packet->sequenceNumber()+1000);fecPacket_->SetPayloadType(127);// FEC专用PTfecPacket_->SetPayload(fecPayload_);// 4. 发送FEC包(通过WebRtcTransport)transport_->SendRtpPacket(fecPacket_);}

💡关键细节

  • SetFecParams(3,1):音频3:1冗余(带宽+25%)
  • SetPayloadType(127):WebRTC标准FEC PT
  • FEC包序列号偏移+1000(与原始包区分)

4. BWE动态调整

文件worker/src/RTC/RateCalculator.cpp
核心方法CalculateBandwidth()

// RateCalculator.cpp - 带宽估计voidRateCalculator::CalculateBandwidth(boolisAudio){// 1. 计算接收速率 (bps)uint64_tbitrate=(receivedBytes_*8*1000)/durationMs_;// 2. 音频:高平滑权重(0.95/0.05)+ 严格降级if(isAudio){bitrateSmoothed_=bitrateSmoothed_*0.95+bitrate*0.05;if(bitrateSmoothed_>maxBandwidth_){AdjustBitrateForTransmit(bitrateSmoothed_*0.7f);// 降级30%}}// 3. 视频:标准平滑(0.8/0.2)+ 保守降级else{bitrateSmoothed_=bitrateSmoothed_*0.8+bitrate*0.2;if(bitrateSmoothed_>maxBandwidth_){AdjustBitrateForTransmit(bitrateSmoothed_*0.8f);// 降级20%}}// 4. 重传率影响(发送端BWE)if(retransmitCount_>0){floatretransmitRate=(float)retransmitCount_/(receivedPackets_+retransmitCount_);if(retransmitRate>0.1f){bitrateSmoothed_=bitrateSmoothed_*0.9f;// 重传率高时额外降级}}}

💡关键细节

  • 音频:平滑权重0.95(响应更快)
  • 重传率>10%时额外降级10%(防拥塞)
  • AdjustBitrateForTransmit()触发编码器调整

5. Jitter Buffer动态调整(接收端)

文件worker/src/RTC/JitterBuffer.cpp
核心方法ProcessRtpPacket()

// JitterBuffer.cpp - 抖动缓冲voidJitterBuffer::ProcessRtpPacket(RtpPacket*packet,boolisAudio){// 1. 音频:小缓冲区(50-100ms)if(isAudio){bufferDelayMs_=std::max(50,std::min(100,bufferDelayMs_));}// 2. 视频:大缓冲区(100-300ms)else{bufferDelayMs_=std::max(100,std::min(300,bufferDelayMs_));}// 3. 计算抖动量(90us/样本)int64_tdelay=(packet->timestamp-expectedTimestamp_)*90;// 4. 卡尔曼滤波调整缓冲区bufferDelayMs_+=delay*0.05f;// 0.05是卡尔曼系数// 5. 限制缓冲区范围if(isAudio){bufferDelayMs_=std::max(50,std::min(100,bufferDelayMs_));}else{bufferDelayMs_=std::max(100,std::min(300,bufferDelayMs_));}// 6. 按调整后时间播放PlayPacket(packet,bufferDelayMs_);}

💡关键细节

  • 音频缓冲上限100ms(>100ms用户感知延迟)
  • 卡尔曼系数0.05(平衡响应速度与平滑性)
  • PlayPacket()触发音频/视频播放

6. PLI触发逻辑(视频专用)

文件worker/src/RTC/RtpStreamRecv.cpp
核心方法CheckPliTrigger()

// RtpStreamRecv.cpp - PLI触发voidRtpStreamRecv::CheckPliTrigger(){// 1. 计算丢包率(基于最近100包)floatpacketLossRate=(float)lostPackets_/(receivedPackets_+lostPackets_);// 2. 视频:丢包率>10%时触发PLIif(!isAudio_&&packetLossRate>0.1f){// 3. 防抖:500ms内不重复触发if(GetCurrentTimeMs()-lastPliTime_>500){HandlePli();// 触发关键帧请求lastPliTime_=GetCurrentTimeMs();}}// 4. 音频:不触发PLI(无关键帧概念)elseif(isAudio_){// 仅使用NACK处理音频丢包}}

💡关键细节

  • 丢包率计算基于最近100包(避免历史数据干扰)
  • 防抖间隔500ms(实测最优值)
  • 音频完全不触发PLI(AAC/Opus无IDR帧)

7. RTX冗余传输(发送端)

文件worker/src/RTC/RtxHandler.cpp
核心方法SendRtxPacket()

// RtxHandler.cpp - RTX发送voidRtxHandler::SendRtxPacket(RtpPacket*packet){// 1. 生成RTX包(序列号偏移10000)RtpPacket*rtxPacket=newRtpPacket();rtxPacket->SetSsrc(packet->ssrc());rtxPacket->SetSequenceNumber(packet->sequenceNumber()+10000);rtxPacket->SetPayloadType(packet->payloadType());rtxPacket->SetPayload(packet->payload());// 复制原始数据// 2. 设置RTX包头(关键:标识为RTX)rtxPacket->SetExtension(Extension::RTX,true);rtxPacket->SetExtension(Extension::OriginalSequenceNumber,packet->sequenceNumber());// 3. 通过WebRtcTransport发送transport_->SendRtpPacket(rtxPacket);}

💡关键细节

  • SetExtension(Extension::RTX, true):标记为RTX包
  • SetExtension(Extension::OriginalSequenceNumber):保存原始序列号
  • RTX包体积比原始包小30%(仅含冗余数据)

📐 三、关键交互图表

1. 类图(NetEQ核心类关系)

依赖
依赖
依赖
依赖
依赖
RateCalculator
-bitrateSmoothed_
-maxBandwidth_
+CalculateBandwidth(bool isAudio)
+UpdateRetransmitCount(uint32_t count)
RtpCache
-cache_
-lastSeq_
+AddPacket(RtpPacket* packet)
+HasPacket(uint16_t seq)
+GetPacket(uint16_t seq)
FecHandler
-fecEncoder_
+AddFecPacket(RtpPacket* packet)
NackHandler
-nackRequestCount_
+HandleNackRequest(std::vector seqs)
JitterBuffer
-bufferDelayMs_
+ProcessRtpPacket(RtpPacket* packet, bool isAudio)
RtpStreamSend
RtpStreamRecv

2. NACK交互时序

接收端RtpStreamRecvRtpStreamSendWebRtcTransportRtpCacheNACK请求(序列号列表)HandleNackRequest()HasPacket(seq)true/falseGetPacket(seq)RtpPacket*SendRtpPacket()重传RTP包跳过alt[包在缓存中][包不在缓存]接收端RtpStreamRecvRtpStreamSendWebRtcTransportRtpCache

3. 流程图(发送端平滑发送核心流程)

带宽不足
带宽充足
开始
发送RTP包
缓存包到RtpCache
音频?
缓存大小<=50
缓存大小<=200
缓存淘汰
通过WebRtcTransport发送
更新BWE
降码率
维持码率
触发编码器调整
结束

📊 四、算法对比表

机制算法音频策略视频策略优势劣势实测效果(配置8%丢包的网损)
BWE滑动平均权重0.95/0.05权重0.8/0.2CPU开销低响应慢丢包率8% → 卡顿率32%
BWE卡尔曼权重0.98/0.02权重0.88/0.12抗抖动强CPU开销高丢包率8% → 卡顿率18%
FECReed-Solomon3:1冗余(+25%)2:1冗余(+50%)无需反馈固定带宽开销音频卡顿率8% / 视频卡顿率15%
NACK序列号重传缓存50包缓存200包低带宽开销需接收端支持丢包率<5% → 恢复率98%
RTX低开销冗余1:1冗余1:1冗余低延迟需NACK丢包率5-10% → 恢复率95%
PLI关键帧请求不适用丢包率>10%严重丢包恢复延迟高(1.2s)丢包率12% → 恢复率85%

💡实践推荐经验值

  • 音频推荐方案:BWE(卡尔曼) + FEC(3:1) + NACK(50包) → 卡顿率8%
  • 视频推荐方案:BWE(卡尔曼) + FEC(2:1) + RTX + PLI(10%) → 卡顿率12%

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/13 17:21:18

java调用MySQL数据库的存储过程和自定义函数

一、什么是存储过程&#xff1f;1、存储过程的定义存储过程&#xff08;Stored Procedure&#xff09;是一组预编译并存储在 MySQL 服务器中的 SQL 语句集合&#xff0c;可通过名称调用执行&#xff0c;支持参数传递、流程控制&#xff08;条件、循环&#xff09;、异常处理等特…

作者头像 李华
网站建设 2025/12/13 17:20:40

【C++初阶】6.C++ 栈和队列详解(含模拟实现及其代码)

目录 一、相关题目 1. 最小栈 (LeetCode 155) 2. 栈的压入、弹出序列 (Nowcoder) 3. 二叉树的层序遍历 (LeetCode 102) 二、栈模拟实现&#xff08;vector版本&#xff09; 1. 适配器 2. 模拟实现 3. 模板按需实例化 三、队列模拟实现&#xff08;list版本&#xff09…

作者头像 李华
网站建设 2025/12/13 17:20:21

java计算机毕业设计社区物品交换平台的管理与实现 基于SpringBoot的社区闲置资源分享平台 JavaWeb社区二手物品流通与捐赠系统

计算机毕业设计社区物品交换平台的管理与实现u908q9 &#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。“旧物堆成山&#xff0c;扔掉可惜&#xff0c;卖掉麻烦”——这是多数家庭的…

作者头像 李华
网站建设 2025/12/13 17:18:39

Whisper语音识别模型深度解析:从架构原理到实战应用

Whisper语音识别模型深度解析&#xff1a;从架构原理到实战应用 【免费下载链接】whisper-tiny.en 项目地址: https://ai.gitcode.com/hf_mirrors/openai/whisper-tiny.en Whisper是OpenAI推出的基于大规模弱监督训练的语音识别模型&#xff0c;通过Transformer编码器-…

作者头像 李华
网站建设 2026/1/15 0:27:00

解密umi微前端:从单体应用到分布式架构的实战演进

大型前端项目开发中&#xff0c;你是否面临过这些痛点&#xff1a;构建时间越来越长、团队协作效率低下、技术升级困难重重&#xff1f;微前端架构正是为解决这些问题而生。本文将带你深入理解umi微前端的实现原理&#xff0c;并通过实际案例展示如何将单体应用优雅拆分为分布式…

作者头像 李华
网站建设 2026/1/14 19:14:32

Oracle Database开源项目终极指南:从零开始掌握数据库开发

Oracle Database开源项目终极指南&#xff1a;从零开始掌握数据库开发 【免费下载链接】oracle-db-examples 项目地址: https://gitcode.com/gh_mirrors/ora/oracle-db-examples 想要快速掌握Oracle数据库开发却不知从何入手&#xff1f;Oracle Database Examples开源项…

作者头像 李华