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

webrtc源码走读(一)-QOS-NACK-概述

与NACK对应的是ACK,ACK是到达通知技术。以TCP为例,他可靠因为接收方在收到数据后会给发送方返回一个“已收到数据”的消息(ACK),告诉发送方“我已经收到了”,确保消息的可靠。NACK也是一种通知技术,只是触发通知的条件刚好的ACK相反,在未收到消息时,通知发送方“我未收到消息”,即通知未达。

NACK是在接收端检测到数据丢包后,发送NACK报文到发送端;发送端根据NACK报文中的序列号,在发送缓冲区找到对应的数据包,重新发送到接收端。NACK需要发送端发送缓冲区的支持,RFC5104定义NACK数据包的格式。若在JB缓冲时间内接收端收到发送端重传的报文,就可以解决丢包问题。对应上图发送端的RTCP RTPFB

1.1 NACK实现

1.1.1、概念

与NACK对应的是ACK,ACK是到达通知技术。以TCP为例,他可靠因为接收方在收到数据后会给发送方返回一个“已收到数据”的消息(ACK),告诉发送方“我已经收到了”,确保消息的可靠。

NACK也是一种通知技术,只是触发通知的条件刚好的ACK相反,在未收到消息时,通知发送方“我未收到消息”,即通知未达。

在rfc4585协议中定义可重传未到达数据的类型有二种(RTPFB、PSFB)

(1)RTPFB(RTP 报文丢失重传)

  • 含义:针对 “RTP 包本身丢了” 的情况,直接要求重传某一个 RTP 包。
  • 场景:小明发的第 2 帧被封装在RTP 包 2里,这个包在网络中丢了 → 小红发送RTPFB 类型的 NACK,告诉小明 “RTP 包 2 丢了,重传它”。

(2)PSFB(指定净荷重传)

针对 “RTP 包没丢,但里面的视频数据丢了” 的情况,细分 3 种类型:

  • ① PLI(Picture Loss Indication,视频帧丢失重传)
    • 含义:一整个视频帧都丢了,要求重传整帧。
    • 场景:小明发的第 2 帧是一个完整的视频画面(如 “小明挥手” 的一帧),但小红没收到 → 小红发送PLI 类型的 NACK,告诉小明 “整帧丢了,重传这一帧”。
  • ② SLI(Slice Loss Indication,Slice 丢失重传)
    • 含义:视频帧的一部分(Slice)丢了,只重传丢失的部分。
    • 场景:小明发的第 2 帧被分成了 3 个 Slice(比如 “挥手的手”“身体”“背景”),其中 “手” 的 Slice 丢了 → 小红发送SLI 类型的 NACK,告诉小明 “只重传‘手’的 Slice”。
  • ③ RPSI(Reference Picture Selection Indication,参考帧丢失重传)
    • 含义:某一 “参考帧” 丢了,导致后续依赖它的帧无法解码,要求重传参考帧。
    • 场景:小明发的第 2 帧是 “参考帧”(后续帧都依赖它解码),但丢了 → 小红发送RPSI 类型的 NACK,告诉小明 “参考帧丢了,重传它”。

WebRTC 的 SDP 会通过SDP 协议协商,确定用哪种 NACK 重传:

  • 通常协商两种:
    • RTPFB 类型的 NACK(默认,对应 “RTP 包丢了就重传包”)。
    • PLI 类型的 NACK(对应 “整帧丢了就重传整帧”)。
1.1.2、触发场景

①包级重传(RTPFB 类型 NACK)的触发场景

核心逻辑:当单个 RTP 包丢失时触发,仅重传丢失的 RTP 包。

​ 典型场景:

  1. 网络偶发丢包: 例如,小明给小红发送视频帧时,某一帧被拆分为 3 个 RTP 包(包 1、包 2、包 3)。其中包 2 在网络中丢失,但包 1 和包 3 正常到达。 小红的设备检测到“包 2 未收到”,会发送 RTPFB 类型的 NACK,要求小明重传包 2
  2. 轻度网络波动: 网络短时拥塞导致少量 RTP 包丢失,但未影响整个视频帧的完整性。此时通过包级重传即可快速恢复数据,且开销小。

②帧级重传(PSFB 类型 PLI)的触发场景

核心逻辑:当一整个视频帧的关键数据丢失,导致该帧无法解码时触发,要求重传整个视频帧

典型场景:

  1. 关键帧(I 帧)丢失: 视频编码中,I 帧是“完整画面帧”,后续 P 帧/B 帧依赖它解码。如果某一 I 帧的多个 RTP 包丢失,导致整帧无法解码,小红的设备会发送 PLI 类型的 NACK,要求小明重传整个 I 帧
  2. 整帧 RTP 包大面积丢失: 例如,某一视频帧被拆分为 10 个 RTP 包,但超过一半的包丢失,即使重传个别包也无法恢复整帧,此时直接触发 PLI 要求重传整帧更高效。
  3. 解码链断裂: 若丢失的包属于“参考帧”(后续帧依赖它解码),且无法通过包级重传修复,会触发 PLI 重传整帧以恢复解码链。
1.1.3、WebRTC 如何决策

​ WebRTC 会根据丢包的严重程度和对解码的影响自动选择: - 优先尝试包级重传:如果丢失的是“非关键 RTP 包”,且重传后能恢复整帧,就用 RTPFB 类型 NACK。 - 升级为帧级重传:当包级重传无法解决问题(如关键帧丢失、整帧大量丢包),则触发 PLI 类型 NACK 重传整帧。 这种分层策略既保证了“小丢包快速修复”,又能在“严重丢包”时及时恢复画面,平衡了重传开销和画面流畅性。

1.1.4、报文示例:
# 包级重传协商 
a=rtcp-fb:96 nack\r\n          // 包级重传(RTPFB) 
#PSFB 类型-帧级重传的 SDP 协商示例
a=rtcp-fb:96 nack pli\r\n      // 帧级重传(PLI) 
a=rtcp-fb:96 nack sli\r\n      //SLI(Slice 丢失重传)
a=rtcp-fb:96 nack rpsi\r\n     //RPSI(参考帧丢失重传)
# 音频媒体(98)的重传协商(音频一般少用 NACK 重传,此处为示例) 
a=rtcp-fb:98 nack\r\n 
a=rtcp-fb:98 nack pli\r\n

1.2、 RTCP 反馈消息定义

img

1.2.1 包头

这是 RTCP 反馈消息的“通用包头”,所有 NACK、PLI、SLI 等反馈都基于此结构:

字段长度含义说明
V(版本)2bit固定为 2,标识 RTP/RTCP 协议版本。
P(填充)1bit若为 1,表示报文末尾有填充字节(用于对齐,不影响数据);0 则无。
FMT5bit反馈消息类型(区分是 NACK、PLI 还是其他类型)。
PT(负载类型)8bit标识这是“反馈报文”,WebRTC 中通常为 205(RTPFB)或 206(PSFB)。
length16bit报文总长度(以 32 位字为单位,包头本身占 2 个 32 位字,因此实际数据长度需计算)。
SSRC of packet sender32bit发送反馈报文的设备标识(如接收端的 SSRC)。
SSRC of media source32bit媒体源的标识(如发送端的视频 SSRC)。
FCI(Feedback Control Information)变长具体的反馈内容(如丢失的 RTP 序列号、帧丢失信息等)。
1.2.2 FMT

(5bit Feedback message type。可以通过 5bit 的 FMT 值,区分 “RTP 包级反馈”(RTPFB)和 “视频帧级反馈”(PSFB)的不同类型)

RTPFB 和 PSFB 两者是 RTCP 反馈的两大 “模式”,由报文里的 PT 字段(8bit)区分

  • 当 PT=205 时,是 RTPFB 模式(针对 RTP 数据包本身的反馈,比如包丢了);
  • 当 PT=206 时,是 PSFB 模式(针对视频 “净荷数据” 的反馈,比如帧、Slice 丢了);

img

  • 而 FMT 字段的取值,会 “跟着模式走”——RTPFB 有一套 FMT 定义,PSFB 有另一套。

RTPFB 模式下的 FMT 定义(处理“RTP 包级”问题)

RTPFB 聚焦于“RTP 数据包本身的丢失或异常”,目前仅定义了 1 个可用的 FMT 值,其他值暂未分配或预留:

FMT 值含义说明核心用途
0unassigned(未分配)目前没有对应的反馈类型,暂时不用
1Generic NACK(通用 NACK)最常用,用于通知“某个/某些 RTP 包丢了”,比如之前例子中“RTP 序列号 176 丢了”,就用 FMT=1
2-30unassigned(未分配)协议预留,未来可能新增其他 RTP 包级反馈类型
31reserved(预留)用于未来扩展更多标识,当前不用

简单说:在 RTPFB 模式下,只要是“RTP 包丢了”,反馈报文的 FMT 就固定填 1,发送端看到 FMT=1 就知道“要重传指定的 RTP 包”。

PSFB 模式下的 FMT 定义(处理“视频帧级”问题)

PSFB 聚焦于“视频编码后的数据(净荷)丢失”,比如整帧、图像分片丢了,定义了 4 个关键 FMT 值,覆盖不同帧级问题:

FMT 值含义说明核心用途
0unassigned(未分配)暂时无对应反馈类型
1Picture Loss Indication (PLI)整帧丢失,比如“整个 I 帧没收到”,发送 FMT=1 的反馈,要求重传完整视频帧
2Slice Loss Indication (SLI)Slice 丢失,比如视频帧被分成 3 个 Slice(图像分片),其中 1 个丢了,用 FMT=2 反馈“要重传这个 Slice”
3Reference Picture Selection Indication (RPSI)参考帧丢失,比如“依赖的 I 帧丢了,后续 P 帧解不了”,用 FMT=3 反馈“要重传这个参考帧”
4-14unassigned(未分配)预留未来扩展
15Application layer FB (AFB) message应用层自定义反馈,比如 WebRTC 里的“带宽估计(REMB)”就用这个类型,非重传用途
16-30unassigned(未分配)预留未来扩展
31reserved(预留)用于未来扩展更多标识

简单说:在 PSFB 模式下,不同 FMT 值对应不同“帧级问题”——FMT=1 是整帧、FMT=2 是 Slice、FMT=3 是参考帧,发送端看到对应的 FMT 就知道“要重传哪类帧数据”。

WebRTC 中接收端要发起重传请求时,会先判断“丢的是 RTP 包还是视频帧”,再组合 PT 和 FMT 字段,如:

  1. 丢 RTP 包 → PT=205(RTPFB)+ FMT=1(Generic NACK);

  2. 丢整帧 → PT=206(PSFB)+ FMT=1(PLI);

  3. 丢 Slice → PT=206(PSFB)+ FMT=2(SLI);

  4. 丢参考帧 → PT=206(PSFB)+ FMT=3(RPSI);

发送端收到报文后,通过“PT 确定模式,FMT 确定具体问题”,就能精准执行对应的重传操作,这也是 WebRTC 实现“UDP 可靠传输”的关键逻辑之一。

1.2.3 FCI

变长 Feedback Control Information。
1、RTPFB

img

报文结构(FCI 部分)

  • Packet Identifier (PID):丢失 RTP 包的序列号(标识具体丢了哪个包)。
  • Bitmap of Lost Packets (BLP):16 位位图,每一位表示“从 PID 开始的下一个包是否丢失”(1=丢失,0=未丢失)。

Packet identifier(PID)即为丢失RTP数据包的序列号,Bitmap of Lost Packets(BLP)指示从PID开始接下来16个RTP数据包的丢失情况。一个NACK报文可以携带多个RTP序列号,NACK接收端对这些序列号逐个处理。如下示例:

img

示例解析

  • PID = 176 → 表示“RTP 序列号为 176 的包丢失”。
  • BLP = 0x6ae1(二进制需按小端解析为 1000 0111 0101 0110)→ 每一位对应“177、178…191”包的丢失情况:
    • 第 1 位(对应 177)= 1 → 177 包丢失;

    • 第 6 位(对应 182)= 1 → 182 包丢失;

    • 以此类推,最终丢失的包序列号为 177、182、183、184、186、188、190、191。

        0x6ae1对应二进制:110101011100001倒过来看1000 0111 0101 0110。按照1bit是丢包,0bit是没有丢包解析,丢失报文序列号分别是:177 182 183 184 186 188 190 191与wireshark解析一致。
      

1.3、实现

以下是基于WebRTC源码对RTPFB(NACK)和PLI FB两种重传机制的实现详解:

1.3.1、 RTPFB(NACK)

RTPFB用于单个RTP包级别的丢包重传,核心依赖NackTracker类和抖动缓冲(JitterBuffer)的丢包检测逻辑。

1.3.1.1发送端重传流程

当接收端发送NACK请求后,发送端通过RtpPacketHistory维护已发送的RTP包缓存,触发重传的调用链如下:

PlatformThread::StartThread  // 启动处理线程
-> PlatformThread::Run        // 线程执行入口
-> ProcessThreadImpl::Run     // 处理线程循环
-> ProcessThreadImpl::Process // 处理待执行任务
-> PacedSender::Process       // 速率控制器处理待发送包
-> PacedSender::SendPacket    // 发送包到网络
-> PacketRouter::TimeToSendPacket // 路由包到对应模块
-> ModuleRtpRtcpImpl::TimeToSendPacket // RTP/RTCP模块处理
-> RTPSender::TimeToSendPacket // RTP发送器处理
-> RtpPacketHistory::GetPacketAndSetSendTime // 从历史缓存中获取待重传包
-> RtpPacketHistory::GetPacket // 最终获取RTP包并标记发送时间
  • 关键类:RtpPacketHistory负责缓存已发送的RTP包,支持按序列号检索重传;PacedSender确保重传包的速率控制,避免网络拥塞。

1.3.1.2 接收端NACK触发机制

接收端通过收包驱动定时驱动两种方式检测丢包并发送NACK:

  • 收包驱动(实时检测):

    DeliverPacket  // 1. 网络层接收UDP数据包(从系统内核获取原始字节流)-> DeliverRtp  // 2. 将原始数据包转换为RTP格式(解析UDP载荷为RTP包结构,提取版本、序列号等头部信息)-> RtpStreamReceiverController::OnRtpPacket  // 3. 路由RTP包到对应的流控制器(根据SSRC区分不同媒体流,如视频流/音频流)-> RtpDemuxer::OnRtpPacket  // 4. 媒体流解复用(根据RTP包的SSRC和负载类型,分发到对应的视频接收器)-> RtpVideoStreamReceiver::OnRtpPacket  // 5. 视频流接收器接收RTP包(确认该RTP包属于当前视频流,进入视频处理链路)-> RtpVideoStreamReceiver::ReceivePacket  // 6. 预处理RTP包(检查包完整性,过滤无效包,记录接收时间)-> RtpReceiverImpl::IncomingRtpPacket  // 7. RTP接收器实现类处理包(更新接收统计,如丢包率、抖动值)-> RTPReceiverVideo::ParseRtpPacket  // 8. 视频RTP包解析(提取视频载荷数据,验证时间戳、序列号连续性)-> RtpVideoStreamReceiver::OnReceivedPayloadData  // 9. 处理视频载荷(将解析后的载荷数据传递给后续模块,如抖动缓冲)-> NackModule::OnReceivedPacket  // 10. NACK模块检测丢包(对比已接收序列号与期望序列号,标记丢失的RTP包)-> VideoReceiveStream::SendNack  // 11. 触发NACK发送(收集丢失的序列号,准备生成NACK请求)-> RtpVideoStreamReceiver::RequestPacketRetransmit  // 12. 请求重传丢失包(将需要重传的序列号列表传递给RTCP模块) -> oduleRtpRtcpImpl::SendNack  // 13. 发送NACK报文(构造RTCP NACK反馈报文,通过网络发送给发送端)
    
    • NackModule::OnReceivedPacket:核心丢包检测逻辑,维护一个 “期望序列号窗口”,每收到一个 RTP 包就检查其序列号是否连续。例如,若当前收到序列号 100 的包,而上次收到 98,则判定 99 号包丢失,将其加入 NACK 列表。
    • ModuleRtpRtcpImpl::SendNack:最终构造符合 RFC4585 标准的 RTCP NACK 报文(RTPFB 类型,FMT=1),包含丢失包的 PID(首个丢失序列号)和 BLP(后续 16 个包的丢失位图),确保发送端能精准重传。
  • 定时驱动(周期性检测):

    NackModule::Process
    
    • 核心逻辑:NackModule定期(如每20ms)检查未确认的RTP包,若超时则触发NACK重传,避免收包驱动的遗漏。
1.3.2、PLI FB的实现逻辑

PLI用于视频帧级别的丢包重传,当整帧(如I帧)丢失导致解码中断时,触发“请求关键帧”的RTCP反馈。

3.2.1 触发条件

  • 连续解码失败:解码器多次尝试解码失败,判断为整帧丢失。
  • 长期无解码输入:抖动缓冲长期无有效帧输入,推测关键帧丢失。

3.2.2 源码调用链

VideoReceiveStream::Decode  // 解码过程中检测到帧丢失
-> VideoReceiveStream::RequestKeyFrame  // 触发关键帧请求
-> RTCRtpSenderVideo::SendPictureLossIndication  // 构造PLI RTCP报文
-> RTCRtpSenderVideo::SendFeedback  // 发送RTCP反馈
  • 关键类:VideoReceiveStream是视频接收流的核心控制器,RTCRtpSenderVideo负责生成并发送PLI的RTCP报文。
1.3.3、SDP协商与模块关联

AssignPayloadTypesAndAddAssociatedRtxCodecs函数中,通过AddDefaultFeedbackParams将RTPFB和PLI的支持写入SDP:

// 简化示例逻辑
void AddDefaultFeedbackParams(SdpMediaSection* media_section) {// 添加RTPFB类型的NACK支持media_section->AddFeedbackParam(FeedbackParam("nack"));// 添加PLI类型的NACK支持media_section->AddFeedbackParam(FeedbackParam("nack", "pli"));// 可同时添加其他反馈类型(如goog-remb等)
}
  • 该逻辑确保两端在SDP协商阶段就确认支持“包级NACK”和“帧级PLI重传”,为后续传输的重传机制铺路。
1.3.4、核心类与模块职责总结
模块/类职责
NackTracker跟踪RTP包的接收状态,检测丢包并生成NACK请求。
RtpPacketHistory缓存发送端的RTP包,支持重传时的包检索。
VideoReceiveStream管理视频接收流的解码、丢帧检测及PLI请求触发。
RTCRtpSenderVideo生成并发送PLI等RTCP反馈报文。
AssignPayloadTypesAndAddAssociatedRtxCodecs处理SDP中Payload类型和反馈参数的协商,确保重传机制的协议级支持。

通过以上逻辑,WebRTC实现了“包级细粒度重传”和“帧级兜底重传”的双层保障,既保证了丢包恢复的效率,又在极端丢包场景下通过PLI请求关键帧维持解码连续性。

http://www.dtcms.com/a/519618.html

相关文章:

  • wordpress 企业网站 免费如何注册网站免费的
  • 斗地主游戏源码,自适应手机版,带有管理后端
  • Linux桌面X11服务-XRecord方案捕获鼠标点击的应用窗口
  • 021数据结构之并查集——算法备赛
  • 网站制作售后免费在线代理网站
  • Vue组件的一些底层细节
  • 2. =>的用法 C#例子 WPF例子
  • 在C#中出现WinForm原控件Chart卡顿问题
  • Spring Boot 3零基础教程,WEB 开发 内嵌服务器底层源码分析 笔记48
  • 网站开发案例分析成都制作网页
  • 导入的 Google(Chrome)书签默认不会自动显示在「书签栏」,而是放在一个文件夹里。下面是详细步骤,帮你把 导入的全部书签添加到书签栏
  • 一小时内使用NVIDIA Nemotron创建你自己的Bash计算机使用智能体
  • Chrome开发者工具
  • 虚拟机 Ubuntu 中安装 Google Chrome 浏览器
  • Docker/K8s部署MySQL的创新实践与优化技巧大纲
  • 网站建设管理流程避免网站侵权
  • 如何在Visual Studio中配置C++环境?
  • 珠海翻译公司高效翻译服务 2025年10月
  • 网站后台管理系统怎么登陆鄂州网站建设与设计
  • 建设系统网站企业密信下载app下载官网
  • 算法面经常考题整理(1)机器学习
  • 使用java如何进行接口测试
  • 机器学习-方差与偏差
  • 甘肃省网站建设咨询seo最好的网站源码
  • 3.序列式容器-heap
  • Module JDK is not defined 警告解决
  • 柞水县住房和城乡建设局网站网站建设客户分析调查表文档
  • html`contenteditable`
  • 【语音识别】语音识别的发展历程
  • 【C++ 类与对象 (下)】:进阶特性与编译器优化的深度实战