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

webrtc代码走读(八)-QOS-FEC-flexfec rfc8627

WebRTC 实现媒体传输冗余的方式主要有三种,分别针对不同场景设计:

  • UlpFEC(RFC5109):早期视频冗余方案,仅支持部分视频编码格式
  • FlexFEC(RFC8627):当前主流冗余方案,兼容性更强、效率更高
  • InbandFEC:专为 Opus 音频设计的内置冗余机制

1 FEC 核心工作原理

UlpFEC 与 FlexFEC 的底层逻辑一致:将 M 个媒体报文 通过异或运算生成 N 个 FEC 冗余报文(N 即冗余度),形成一个保护包组。在网络传输中,即使该包组丢失不超过 N 个报文,接收端也能通过“剩余媒体包 + FEC 冗余包”反向计算,恢复出完整的媒体数据。

发送端打包示意图(冗余度=2)

img

以 4 个媒体包(D1-D4)为例,生成 2 个冗余包(R1-R2),形成“4+2”的包组结构。示意图中按行列排布媒体包与冗余包,明确冗余包对媒体包的保护范围。

好比发送 4 份重要文件时,额外生成 2 份“混合备份文件”(非简单复制,而是通过异或整合原文件信息),确保文件传输的安全性,冗余包与媒体包的对应关系在图中清晰可见。

网络丢包示意图(×表示丢包)

img

包组传输过程中,D2、D3 两个媒体包位置标记“×”表示丢失,仅 D1、D4 媒体包与 R1、R2 冗余包保留,直观展示丢包后的包组状态。
原本 6 个包(4 原 + 2 备)中,2 份原文件在网络中丢失,仅剩 2 份原文件和 2 份备份文件,图中丢失包的位置一目了然,便于理解后续恢复逻辑。

报文恢复示意图(⊕=异或运算)

img

标注“D1⊕x⊕y⊕”“D1⊕x⊕D4”等异或运算过程,展示接收端通过剩余媒体包(D1、D4)与冗余包(R1、R2)反向推导,逐步恢复丢失的 D2、D3 的过程。
利用备份文件的“混合信息”,结合剩余原文件反向推导,图中清晰标注运算步骤,最终找回所有丢失的原文件,确保接收内容完整。

2、FlexFEC VS UlpFEC:核心差异对比

对比维度UlpFECFlexFEC关键影响
媒体包打包格式RFC2198 + RFC5109RFC8627FlexFEC 是专为 WebRTC 优化的新协议,兼容性更强
FEC SSRC与媒体报文共享 SSRC独立 SSRCUlpFEC 无法区分媒体包与 FEC 包;FlexFEC 可快速识别,避免冗余处理
FEC Sequence与媒体报文共享 Sequence独立 SequenceUlpFEC 易导致序号混淆;FlexFEC 序号独立,逻辑更清晰
适用 Codec仅支持 VP8/VP9(需 PictureId)无 Codec 限制UlpFEC 无法用于 H264 等无帧边界标识的编码;FlexFEC 通用性更强
NACK 重传问题FEC 包会触发 NACK 重传FEC 包不触发 NACKUlpFEC 导致带宽浪费;FlexFEC 减少无效重传,节省带宽
单帧媒体包限制最大 48 个(超过不参与保护)理论无限制(源码未完全适配)UlpFEC 对大帧场景保护不足;FlexFEC 适配性更好
2.1 UlpFEC 的 Codec 支持

WebRTC 仅允许 VP8/VP9 等带帧边界信息的 Codec 使用 UlpFEC,核心原因是 H264 仅靠 Sequence 序号判断帧完整性,若 FEC 包插入帧中间会导致判断逻辑失效。相关逻辑通过 MaybeCreateFecGenerator->ShouldDisableRedAndUlpfec->PayloadTypeSupportsSkippingFecPackets 调用链实现,默认只有 VPX 系列编码支持 UlpFEC 冗余编码,具体源码如下:

/*** @brief 判断当前 Payload 类型是否支持跳过 FEC 包(仅用于 UlpFEC 场景)* @param payload_name:Payload 名称(如 "VP8"、"H264")* @param trials:WebRTC 实验配置(用于开启特定功能)* @return true:支持;false:不支持* @说明:UlpFEC 依赖 Codec 的帧边界标识(如 VP8/VP9 的 PictureId),H264 无此信息故不支持*/
bool PayloadTypeSupportsSkippingFecPackets(const std::string& payload_name, const WebRtcKeyValueConfig& trials) {  // 将 Payload 名称转为对应的 Codec 类型const VideoCodecType codecType = PayloadStringToCodecType(payload_name);  // 1. 直接支持 VP8/VP9(自带 PictureId,可准确区分帧边界)if (codecType == kVideoCodecVP8 || codecType == kVideoCodecVP9) {    return true;  }  // 2. 通用 Codec(如 H264)需开启 "WebRTC-GenericPictureId" 实验配置才支持if (codecType == kVideoCodecGeneric &&      absl::StartsWith(trials.Lookup("WebRTC-GenericPictureId"), "Enabled")) {    return true;  }  // 3. 其他 Codec(如默认 H264)不支持 UlpFECreturn false;  
}
2.2 UlpFEC 的带宽浪费问题

由于 UlpFEC 的 SSRC 与媒体报文共享,且 Sequence 与媒体报文共用,接收端无法区分媒体包与 FEC 包。在开启 NACK(负确认)机制时,FEC 冗余包会被误判为媒体包,触发 NACK 重传,导致不必要的带宽消耗。而 FlexFEC 凭借独立 SSRC 和独立 Sequence,可精准区分包类型,避免该问题。因此,目前 WebRTC 场景中更优选 FlexFEC 冗余编码,UlpFEC 应用逐渐减少。

3 、FlexFEC 原理深度解析

3.1 冗余模式(RFC8627 定义)

FlexFEC 支持三种异或编码模式,可根据网络丢包类型(随机丢包/突发丢包)选择适配模式,但 WebRTC 源码仅实现了部分模式。

1D 行异或模式

img

模式逻辑
示意图以表格形式展示媒体包分组,如第一行包含 S₁、S₂、S₃、S_L,第二行包含 S_L+1、S_L+2、S_L+3,以此类推,每行媒体包通过异或运算生成 1 个行冗余包(R)。
通俗解释:将 15 个媒体包分成 3 行(每行 5 个),每行生成 1 个“行备份包”。若某行丢失 1 个媒体包,可通过该行剩余包 + 行备份包恢复。
适用场景:随机丢包(单一行内少量丢包易恢复)。

1D 列异或模式

img

模式逻辑
示意图中媒体包按列排布,如第一列包含 S₁、S_L+1、S_(D-1)×L+1,每列媒体包下方标注“XOR”运算符号,最终生成列冗余包(C₁、C₂、C₃)。
通俗解释:将 15 个媒体包分成 3 列(每列 5 个),每列生成 1 个“列备份包”。若某列丢失 1 个媒体包,可通过该列剩余包 + 列备份包恢复。
适用场景:突发丢包(同一列内连续丢包易恢复)。
WebRTC 实现:源码默认使用该模式,通过 MaskRandomMaskBursty 算法选择列分组方式,例如:

  • kMaskBursty12_4:对应的掩码值包括 0x8a80、0xc540、0x6220、0x3910 等
  • kMaskRandom12_4:对应的掩码值包括 0x8b20、0x14b0、0x22d0、0x4550 等
    系统根据初始配置选择 MaskRandomMaskBursty 冗余模式,当包组报文个数 > 12 时,自动启用 1D 列异或冗余模式,具体逻辑可参考 PacketMaskTable::LookUp 函数实现。

2D 数组异或模式

img

模式逻辑
示意图展示媒体包以二维数组形式排列,同时对“行”和“列”执行异或运算,生成“行冗余包(R)”和“列冗余包(C)”,形成二维保护网。例如数组中包含 xR、R2、xX12R3 等标识,标注行与列的运算关系。
通俗解释:15 个媒体包既按行生成备份,又按列生成备份。即使某区域同时丢失多个包(如 1 行 + 1 列),仍可通过双重冗余恢复。
WebRTC 现状:源码未实现该模式,仅支持 1D 模式,且标注“2-D Parity FEC Protection Fails Error Recovery”场景,说明该模式在特定丢包情况下也存在恢复失败的可能。

补充说明

Webrtc源码仅实现了MaskRandom、MaskBursty或1D列异或冗余模式。

img

3.2 RTP 包协议格式

完整 RTP 报文结构

img

FlexFEC 的 RTP 报文需在标准 RTP 结构基础上插入 FEC 头,完整结构如下,示意图中以分层形式展示各字段的包含关系:

IP Header(IP 层头)
├─ Transport Header(传输层头,如 UDP 头)
└─ RTP Header(标准 RTP 头,12 字节)├─ FEC Header(FlexFEC 自定义头,最小 20 字节)└─ Repair Payload(FEC 冗余数据载荷)

FEC Header 定义(WebRTC 自定义实现)

img

WebRTC 未完全遵循 RFC8627 的 FEC 头格式,而是在 flexfec_header_reader_writer.h 头文件中定义了自定义结构,示意图以二进制位和字节偏移标注字段位置,核心字段及偏移如下:

偏移(字节)字段名称长度(字节)说明
0-1标志位(R/F/P/X)2保留位(R)、FEC 类型位(F)、保护位(P)、扩展位(X)
2-3PT Recovery2对应的媒体包 PT 类型,用于关联 FEC 包与媒体包
4-7Length Recovery4保护的媒体包总长度,用于恢复媒体包数据长度
8-11TS Recovery4保护的媒体包时间戳,确保媒体包时序正确
12-13SSRC Count2保护的媒体流 SSRC 数量(通常为 1),支持多流保护场景
14-17SSRC_i4对应的媒体流 SSRC,用于绑定 FEC 包与特定媒体流
18-19SN Base_i2保护的媒体包起始序列号,作为计算保护包序列号的基准
20-21k + Mask [0-14]2保护的媒体包数量(k) + 掩码(前 15 位),掩码 bit=1 表示对应包被保护
22-27Mask [15-45]6(可选)掩码(中间 31 位),k>16 时启用,扩展保护包数量范围
28-41Mask [46-108]14(可选)掩码(后 63 位),k>48 时启用,进一步扩展保护范围

掩码(Mask)核心作用与计算

掩码用于标记“当前 FEC 包保护哪些媒体包”,1 个 bit 对应 1 个媒体包(bit=1 表示该媒体包被保护)。WebRTC 定义了掩码长度表,根据保护的媒体包数量(k)自动选择,源码中通过常量数组定义:

/*** @brief FlexFEC 掩码的字节长度表(根据保护的媒体包数量 k 确定)* @说明:1 个 bit 对应 1 个媒体包,故长度随 k 增加而扩展* - k ≤ 16:掩码占 2 字节(16 bit)* - 17 ≤ k ≤ 48:掩码占 6 字节(48 bit)* - 49 ≤ k ≤ 112:掩码占 14 字节(112 bit)* WebRTC 实际限制 k ≤ 48(即单 FEC 包最多保护 48 个媒体包)*/
constexpr size_t kFlexfecPacketMaskSizes[] = {2, 6, 14};

保护的媒体包序列号计算逻辑

img通过“起始序列号(SN Base_i) + 掩码 bit 置 1 的位置”确定被保护媒体包的序列号,核心源码如下(参考 ForwardErrorCorrection::InsertFecPacket 函数):

/*** @brief 解析 FEC 包的掩码,生成该 FEC 包保护的媒体包序列号列表* @param fec_packet:当前 FEC 包(含掩码信息)* @param protected_media_ssrc:对应的媒体流 SSRC(绑定 FEC 与媒体包)* @param fec_packet->protected_packets:输出参数,存储保护的媒体包信息*/
for (uint16_t byte_idx = 0; byte_idx < fec_packet->packet_mask_size; ++byte_idx) {  // 1. 逐字节读取掩码(1 字节 = 8 bit,对应 8 个媒体包)uint8_t packet_mask = fec_packet->pkt->data[fec_packet->packet_mask_offset + byte_idx];  // 2. 逐 bit 解析掩码(从高位到低位,bit=1 表示该媒体包被保护)for (uint16_t bit_idx = 0; bit_idx < 8; ++bit_idx) {  if (packet_mask & (1 << (7 - bit_idx))) {  // 3. 创建保护的媒体包对象,绑定 SSRCstd::unique_ptr<ProtectedPacket> protected_packet(new ProtectedPacket());  protected_packet->ssrc = protected_media_ssrc_;  // 4. 计算被保护媒体包的序列号:起始序列号 + 偏移(byte_idx*8 + bit_idx)// 示例:SN Base_i=100,byte_idx=0,bit_idx=2 → 序列号=100+0*8+2=102protected_packet->seq_num = static_cast<uint16_t>(  fec_packet->seq_num_base + (byte_idx << 3) + bit_idx);  // 5. 存储到保护列表(后续用于丢包恢复)protected_packet->pkt = nullptr;  // 暂不存储包数据,仅记录序列号fec_packet->protected_packets.push_back(std::move(protected_packet));  }  }  
}

3.3 SDP 协商(FlexFEC 启用流程)

FlexFEC 需通过 SDP 协商确定媒体流与 FEC 流的绑定关系,核心参数包括 SSRC 分组、PT 类型、恢复窗口等。以下是典型的 SDP 示例,展示单媒体流(SSRC:1234)与单 FEC 流(SSRC:2345)的协商配置:

img

v=0
o=ali 1122334455 1122334466 IN IP4 fec.example.com
s=2-D Parity FEC with no in band signaling Example
t=0 0
m=video 30000 RTP/AVP 100 110  # 媒体流 PT=100,FlexFEC 流 PT=110
c=IN IP4 192.0.2.0/24
a=rtpmap:100 MP2T/90000        # 媒体流编码格式:MP2T,时钟频率 90000Hz
a=rtpmap:110 flexfec/90000     # FlexFEC 编码格式,时钟频率与媒体流一致(90000Hz)
a=fmtp:110; repair-window:200000  # FlexFEC 恢复窗口:200ms(单位:微秒)
a=ssrc:1234                     # 媒体流 SSRC 标识:1234
a=ssrc:2345                     # FlexFEC 流 SSRC 标识:2345
a=ssrc-group:FEC-FR 1234 2345   # 绑定媒体流与 FEC 流,FEC-FR 表示前向错误恢复

PT 类型与 SSRC 绑定(源码实现)

  1. PT 类型确定
    WebRTC 通过 GetPayloadTypesAndDefaultCodecs 函数分配 FlexFEC 的 PT 类型,优先使用 [35,63] 低区间(适配新 Codec),若低区间用尽则使用 [96,127] 高区间。恢复窗口默认设为 10 秒(10000000 微秒),该参数必须在 SDP 中声明,源码逻辑如下:
// 定义 FlexFEC 相关常量
static const int kFirstDynamicPayloadTypeLowerRange = 35;  // 低区间 PT 起始值
static const int kLastDynamicPayloadTypeLowerRange = 63;   // 低区间 PT 结束值
static const int kFirstDynamicPayloadTypeUpperRange = 96;  // 高区间 PT 起始值
static const int kLastDynamicPayloadTypeUpperRange = 127;  // 高区间 PT 结束值bool GetPayloadTypesAndDefaultCodecs(const std::vector<webrtc::SdpVideoFormat>& supported_formats,const WebRtcKeyValueConfig& trials,std::vector<VideoCodec>* output_codecs,bool is_decoder_factory) {std::vector<webrtc::SdpVideoFormat> formats = supported_formats;int payload_type_lower = kFirstDynamicPayloadTypeLowerRange;int payload_type_upper = kFirstDynamicPayloadTypeUpperRange;// 检查是否启用 FlexFEC,若启用则添加 FlexFEC 格式到支持列表if ((!is_decoder_factory && IsEnabled(trials, "WebRTC-FlexFEC-03-Advertised")) ||(!IsDisabled(trials, "WebRTC-FlexFEC-03-Advertised"))) {webrtc::SdpVideoFormat flexfec_format(kFlexfecCodecName);// 设置恢复窗口为 10 秒(单位:微秒),该参数必须在 SDP 中存在flexfec_format.parameters.insert({kFlexfecFmtpRepairWindow, "10000000"});formats.push_back(flexfec_format);}// 为每个支持的格式分配 PT 类型for (const webrtc::SdpVideoFormat& format : formats) {VideoCodec codec(format);bool isFecCodec = absl::EqualsIgnoreCase(codec.name, kULpfecCodecName) ||absl::EqualsIgnoreCase(codec.name, kFlexfecCodecName);bool isAv1Codec = absl::EqualsIgnoreCase(codec.name, kAv1CodecName);bool isCodecValidForLowerRange = isFecCodec || isAv1Codec;// 检查 PT 类型是否用尽if (payload_type_lower > kLastDynamicPayloadTypeLowerRange) {RTC_LOG(LS_ERROR) << "Out of dynamic payload types [35, 63] after fallback from [96, 127], skipping the rest.";break;}// 低区间用于新 Codec(如 FlexFEC、AV1)或高区间用尽时if (isCodecValidForLowerRange || payload_type_upper >= kLastDynamicPayloadTypeUpperRange) {codec.id = payload_type_lower++;} else {codec.id = payload_type_upper++;}// 添加默认反馈参数(如 NACK、TWCC 等)AddDefaultFeedbackParams(&codec, trials);output_codecs->push_back(codec);}return true;
}
  1. SSRC 绑定与查询
    WebRTC 通过 AddFecFrSsrc 函数将 FEC 流 SSRC 与媒体流 SSRC 绑定为“FEC-FR”组,通过 GetFecFrSsrc 函数查询媒体流对应的 FEC 流 SSRC,源码如下:
/*** @brief 为已添加的主 SSRC(媒体流)添加 FEC-FR 类型的从 SSRC(FEC 流)* @param primary_ssrc:主 SSRC(媒体流 SSRC)* @param fecfr_ssrc:从 SSRC(FEC 流 SSRC)* @return true:添加成功;false:添加失败*/
bool RtpParameters::AddFecFrSsrc(uint32_t primary_ssrc, uint32_t fecfr_ssrc) {return AddSecondarySsrc(kFecFrSsrcGroupSemantics, primary_ssrc, fecfr_ssrc);
}/*** @brief 根据主 SSRC(媒体流)查询对应的 FEC-FR 从 SSRC(FEC 流)* @param primary_ssrc:主 SSRC(媒体流 SSRC)* @param fecfr_ssrc:输出参数,存储查询到的 FEC 流 SSRC* @return true:查询成功;false:查询失败(主 SSRC 不存在或无对应 FEC 流)*/
bool RtpParameters::GetFecFrSsrc(uint32_t primary_ssrc, uint32_t* fecfr_ssrc) const {return GetSecondarySsrc(kFecFrSsrcGroupSemantics, primary_ssrc, fecfr_ssrc);
}

4、FlexFEC 编码与解码

4.1 FlexFEC 编码实现

编码调用栈

FlexFEC 编码的核心流程是“媒体包入队 → 生成 FEC 包 → 加入 pacing 队列 → 调度发送”,调用栈如下表所示,清晰展示各模块的调用关系:

流程阶段涉及函数/模块功能说明
媒体包入队FlexfecSender::AddPacketAndGenerateFec、UlpfecGenerator::AddPacketAndGenerateFec接收媒体包,满足条件(如包数达标、帧结束)时触发 FEC 生成
FEC 包生成ForwardErrorCorrection::EncodeFec、ForwardErrorCorrection::GenerateFecPayloads对媒体包进行异或运算,生成 FEC 冗余数据,封装 FEC 头
FEC 包入队FlexfecSender::GetFecPackets、PacingController::EnqueuePacket获取生成的 FEC 包,加入 pacing 队列(控制发送速率,避免网络拥塞)
FEC 包发送PacedSender::Process、PacketRouter::SendPacket调度 pacing 队列中的 FEC 包,封装 TWCC 头(用于带宽估计)后发送

编码核心流程

  1. 初始化 FEC 编码句柄
    通过 FlexfecSender 构造函数初始化 FEC 编码参数,如冗余度、掩码类型等,依赖 UlpfecGeneratorForwardErrorCorrection 模块实现编码逻辑。

  2. 媒体包入队与 FEC 触发
    UlpfecGenerator::AddPacketAndGenerateFec 函数负责接收媒体包,当媒体包数量达到 kUlpfecMaxMediaPackets(48 个)或当前帧结束时,触发 FEC 生成。需注意,WebRTC 限制单帧媒体包数不超过 48,超过部分不加入 media_packets_ 队列,不参与 FEC 保护,源码如下:

/*** @brief 向 UlpFEC 生成器添加媒体包,满足条件时生成 FEC 包* @param packet:待添加的媒体包(RTP 包)* @param complete_frame:是否为当前帧的最后一个媒体包* @说明:单帧媒体包数超过 48 时,后续包不参与 FEC 保护;不支持跨帧冗余*/
bool UlpfecGenerator::AddPacketAndGenerateFec(const RtpPacket& packet, bool complete_frame) {// 限制:单帧媒体包数不超过 48,超过部分不加入队列if (media_packets_.size() < kUlpfecMaxMediaPackets) {// 创建 FEC 包对象,复制媒体包数据auto fec_packet = std::make_unique<ForwardErrorCorrection::Packet>();fec_packet->data = packet.Buffer();media_packets_.push_back(std::move(fec_packet));// 保存最后一个媒体包的 RTP 头,用于生成 FEC 包时复用头信息RTC_DCHECK_GE(packet.headers_size(), kRtpHeaderSize);last_media_packet_ = packet;}// 标记当前帧是否结束,统计已保护的帧数if (complete_frame) {++num_protected_frames_;}// 满足以下条件时生成 FEC 包:// 1. 当前帧结束;// 2. 已保护帧数达到最大限制(params.max_fec_frames);// 3. 媒体包数达到最小限制,且带宽开销低于阈值auto params = CurrentParams();if (complete_frame &&(num_protected_frames_ >= params.max_fec_frames ||(media_packets_.size() >= params.min_media_packets &&OverheadBelowMax(params.fec_rate)))) {// 不使用不等保护机制,无重要包优先级区分constexpr int kNumImportantPackets = 0;constexpr bool kUseUnequalProtection = false;// 调用 FEC 编码接口生成 FEC 包fec_->EncodeFec(media_packets_, params.fec_rate, kNumImportantPackets,kUseUnequalProtection, params.fec_mask_type, &generated_fec_packets_);// 若生成的 FEC 包为空,重置状态(准备下一组编码)if (generated_fec_packets_.empty()) {ResetState();}}return true;
}
  1. FEC 包生成关键步骤
  • 确定冗余包数量:通过 ForwardErrorCorrection::NumFecPackets 函数,根据冗余度(fec_rate)和媒体包数量计算需生成的 FEC 包数量。
  • 生成掩码数据:调用 internal::GeneratePacketMasks 函数,根据掩码类型(如 kMaskBurstykMaskRandom)生成掩码,确定 FEC 包保护的媒体包列表。
  • 异或运算生成载荷ForwardErrorCorrection::GenerateFecPayloads 函数对媒体包的 RTP 头(仅前 12 字节,不含扩展头)和载荷进行异或运算,生成 FEC 载荷。
  • 封装 FEC 头ForwardErrorCorrection::FinalizeFecHeaders 函数根据自定义格式封装 FEC 头,填充 SSRC、SN Base、掩码等信息。
4.2 FlexFEC 解码实现

解码调用栈

FlexFEC 解码的核心流程是“区分包类型 → 存储包数据 → 恢复丢失包 → 回调上层”,调用栈如下表所示:

流程阶段涉及函数/模块功能说明
接收包入口FlexfecReceiver::OnRtpPacket、FlexfecReceiver::ProcessReceivedPacket接收 RTP 包,触发包处理逻辑
包类型区分FlexfecReceiver::AddReceivedPacket根据 SSRC 区分媒体包与 FEC 包:媒体包保存完整 RTP 数据,FEC 包仅保存载荷
FEC 包解析ForwardErrorCorrection::InsertFecPacket解析 FEC 包的掩码和保护列表,加入 received_fec_packets 队列
媒体包存储ForwardErrorCorrection::InsertMediaPacket解析媒体包序列号,加入 recovered_packets 队列
丢包恢复ForwardErrorCorrection::DecodeFec、ForwardErrorCorrection::AttemptRecovery检查丢失包,用 FEC 包恢复,仅支持“N+1”模式(1 个 FEC 包恢复 1 个丢失包)
恢复包回调RtpVideoStreamReceiver::OnRecoveredPacket将恢复的媒体包交给上层模块(如解码器)处理

解码核心流程(源码解析)

  1. 包类型区分与存储
    FlexfecReceiver::AddReceivedPacket 函数根据 SSRC 判断包类型:
  • 媒体包:完整保存 RTP 头和载荷,调用 ForwardErrorCorrection::InsertMediaPacket 加入媒体包队列,用于后续恢复验证。
  • FEC 包:去除 RTP 头,仅保存 FEC 载荷和 FEC 头,调用 ForwardErrorCorrection::InsertFecPacket 解析掩码和保护列表,加入 FEC 包队列。
  1. 丢包恢复核心逻辑
    WebRTC 仅支持“N+1”恢复模式(1 个 FEC 包最多恢复 1 个丢失的媒体包),核心通过 ForwardErrorCorrection::AttemptRecovery 函数实现,步骤如下:
    • 遍历 FEC 包队列,获取每个 FEC 包的保护列表;
    • 统计保护列表中“已接收媒体包数”和“丢失媒体包数”;
    • 若仅丢失 1 个媒体包,且已接收包数 = 保护总数 - 1,调用 ForwardErrorCorrection::RecoverPacket 函数通过异或运算恢复丢失包;
    • 将恢复的媒体包加入 recovered_packets 队列,通过 RtpVideoStreamReceiver::OnRecoveredPacket 回调给上层。
/*** @brief 尝试用 FEC 包恢复丢失的媒体包(核心解码函数)* @param media_packets:已接收的媒体包队列* @param fec_packets:已接收的 FEC 包队列* @param recovered_packets:输出参数,存储恢复成功的媒体包* @return true:至少恢复 1 个包;false:未恢复任何包* @说明:仅支持“N+1”模式,即 1 个 FEC 包最多恢复 1 个丢失的媒体包*/
bool ForwardErrorCorrection::AttemptRecovery(const std::vector<Packet*>& media_packets,const std::vector<Packet*>& fec_packets,std::vector<std::unique_ptr<Packet>>* recovered_packets) {RTC_DCHECK(recovered_packets);bool recovery_succeeded = false;// 遍历所有 FEC 包,检查是否可用于恢复for (const auto* fec_pkt : fec_packets) {const auto& protected_list = fec_pkt->protected_packets;if (protected_list.empty()) {continue;  // 无保护的媒体包,跳过该 FEC 包}// 统计已接收媒体包数和丢失媒体包信息size_t received_count = 0;Packet* lost_pkt = nullptr;uint16_t lost_seq = 0;for (const auto& protected_pkt : protected_list) {// 查找该保护包是否已在媒体包队列中(通过序列号匹配)auto it = std::find_if(media_packets.begin(), media_packets.end(),[&](const Packet* p) {return p->seq_num == protected_pkt->seq_num &&p->ssrc == protected_pkt->ssrc;});if (it != media_packets.end()) {received_count++;  // 已接收,计数加 1} else {lost_pkt = const_cast<Packet*>(protected_pkt.get());  // 标记丢失的包lost_seq = protected_pkt->seq_num;}}// 仅当“丢失 1 个包”且“已接收包数 = 保护总数 - 1”时,尝试恢复if (lost_pkt != nullptr && received_count == protected_list.size() - 1) {// 调用 RecoverPacket 函数恢复丢失的媒体包auto recovered_pkt = RecoverPacket(media_packets, fec_pkt, lost_seq);if (recovered_pkt != nullptr) {recovered_packets->push_back(std::move(recovered_pkt));recovery_succeeded = true;// 一次仅恢复 1 个包,跳出循环(可优化为批量恢复)break;}}}return recovery_succeeded;
}
http://www.dtcms.com/a/532765.html

相关文章:

  • Linux内核RDMA连接管理(CMA)驱动深度解析:高性能网络的基石
  • 网站建设网站自助建设wordpress迁移安装
  • [sam2图像分割] 提示编码器 | PositionEmbeddingRandom
  • 晋江网站建设联系电话迁安做网站中的cms润强
  • 【图像处理基石】图像匹配技术:从原理到实践,OpenCV实现与进阶方向
  • JavaScript性能优化:reduce方法的巧妙运用
  • Go语言数组和切片
  • 静安做网站公司wordpress 做社区
  • SVD分解在MIMO系统中的应用:从信道建模到信号恢复
  • chp03【组队学习】Post-training-of-LLMs
  • 网站建设大神级公司北京网站建设价位
  • HCIE云计算题超长解析
  • MobaXterm 全面使用指南:从入门到高效运维
  • 律师网站模板wordpress建站侵权
  • 昆明网站设计制作公司如何注册微信公众平台账号
  • 深入浅出 SSE:实现服务器向客户端的单向实时通信
  • Datawhale AI夏令营--构建一个面向应急管理领域的智能问答系统task2
  • 论gRPC:基于 TCP/IP 的通用网络模式,以及基于 Unix Domain Sockets (UDS) 的同机进程间通信 (IPC) 模式
  • C语言习题~day27
  • AI之智能体agent与dify的搭建
  • 哪个网站可以接图纸做哪个行业建设网站的需求多
  • 做微信的网站叫什么米WordPress多重筛选功能
  • C++与Python:内存管理与指针的对比
  • 辽宁专业模板网站建设公司网站开发顶岗周记
  • X红书AI发布助手 - 自动化内容创作与发布工具
  • html5网站建设中模板网站改版 seo
  • 第十章:生态篇 - 构建您的第一个插件生态
  • 大模型MCP原理及实践
  • Keil 5 找不到编译器 Missing:Compiler Version 5
  • 24级移动ui