webrtc代码走读(十二)Transport-cc协议与RTP扩展头
Transport-cc(Transport-wide Congestion Control)协议是WebRTC中用于全局拥塞控制的关键协议,通过RTCP反馈报文实现网络状况评估与带宽估算,确保实时音视频传输的服务质量(QoS)。其核心是通过记录RTP包的到达时间、丢包状态等信息,反向反馈给发送端以动态调整码率。
1、Transport-cc协议结构与参数说明
1.1 协议结构(RTCP反馈报文格式)
Transport-cc协议的RTCP反馈报文结构:
 0                   1                   2                   30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|FMT=15|PT=205|            length                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     SSRC of packet sender                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                   SSRC of media source                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|      base sequence number      |   packet status count        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     reference time                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| feedback packet count |          packet chunk                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         packet chunk (cont.)                  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        recv delta                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        recv delta (cont.)                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     zero padding (if needed)                  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
结构解释:
- 前4字节(V/P/FMT/PT/length):固定标识RTCP报文类型为Transport-cc反馈;
- SSRC字段:分别标识报文发送端和媒体源,确保信息归属正确;
- 核心控制字段(base sequence number、packet status count等):记录RTP包序列范围、状态数量及时间基准;
- packet chunk与recv delta:存储RTP包的到达状态(丢包/接收)和时间间隔,是拥塞评估的核心数据。
1.2 关键参数说明
各字段的具体含义和作用如下表所示,其中部分参数为Transport-cc协议特有,需严格遵循固定取值或格式:
| 参数名称 | 字段长度 | 取值/格式要求 | 核心作用 | 
|---|---|---|---|
| version (V) | 2 bits | 固定为2 | 标识RTCP协议版本,确保兼容性 | 
| padding § | 1 bit | 0(无填充)或1(有填充,用于对齐字节) | 调整报文长度至4字节倍数,满足底层传输要求 | 
| feedback message type (FMT) | 5 bits | 固定为15 | 唯一标识该RTCP报文为Transport-cc反馈类型 | 
| payload type (PT) | 8 bits | 固定为205 | 区分RTCP报文的负载类型,与FMT配合确认协议类型 | 
| SSRC of packet sender | 32 bits | 发送端设备的SSRC(唯一标识) | 标识反馈报文的发送方,便于接收端溯源 | 
| SSRC of media source | 32 bits | 媒体流(如视频/音频)的SSRC | 关联反馈信息到具体媒体流,避免多流混淆 | 
| base sequence number | 16 bits | RTP包的起始序列号(transport sequence number) | 确定当前反馈报文覆盖的RTP包序列范围起点,支持乱序反馈 | 
| packet status count | 16 bits | 正整数(0~65535) | 表示当前反馈报文记录的RTP包数量,即 base sequence number后续的包数量 | 
| reference time | 24 bits | 以64ms为单位的时间戳 | 作为RTP包到达时间的基准,所有 recv delta基于此计算实际到达时间 | 
| feedback packet count | 8 bits | 自增整数(0~255,循环计数) | 标识反馈报文自身的序列号,用于检测反馈包丢失 | 
| packet chunk | 16 bits/块 | 两种编码类型(Run length/Status vector) | 压缩存储RTP包的到达状态(丢包/接收),减少反馈报文体积 | 
| recv delta | 8/16 bits | 无符号1字节(小间隔)或有符号2字节(大/负间隔),单位250us | 记录RTP包到达时间间隔,结合 reference time计算每个包的实际到达时间 | 
| zero padding | 可变长度 | 0字节或多字节0值 | 确保报文总长度为4字节倍数,满足RTCP协议格式要求 | 
recv delta取值规则
recv delta根据RTP包到达时间间隔的大小选择不同存储长度,平衡精度与报文体积:
- 小间隔(Packet received, small delta):间隔≤63.75ms(255×0.25ms),用1字节存储,取值范围[0,255];
- 大/负间隔(Packet received, large or negative delta):间隔>63.75ms或为负(如乱序到达),用2字节存储,取值范围[-8192.0, 8191.75]ms;
- 超范围处理:若间隔超过2字节最大范围,直接创建新的Transport-cc反馈包,避免数据溢出。
2、packet chunk编码类型详解
packet chunk是Transport-cc协议中压缩存储RTP包状态的核心机制,通过两种编码类型适配不同场景下的状态存储需求,减少冗余数据。
2.1 Run length chunk(行程长度编码)
适用于连续多个RTP包状态相同的场景(如连续接收或连续丢包),用“状态+连续数量”的形式压缩存储。
结构格式
 0                   1                   2                   30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|T=0|    S    |                run length (L)                   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
字段解释:
- T(1 bit):固定为0,标识该chunk为Run length类型;
- S(2 bits):RTP包状态,取值规则:
- 00:Packet not received(丢包);
- 01:Packet received, small delta(接收,小时间间隔);
- 10:Packet received, large or negative delta(接收,大/负时间间隔);
- 11:Reserved(保留,暂未使用);
 
- L(13 bits):连续相同状态的RTP包数量,取值范围[0,8191]。
示例解析
以实际抓包数据为例,展示Run length chunk的解码过程:
- 抓包数据:Packet Chunk: 8193 (0x2001) [Run Length Chunk] Small Delta. Length :1
- 十六进制转二进制:0x2001 = 0010 0000 0000 0001
- 字段拆分:
- T=0(第1 bit):确认Run length类型;
- S=01(第2-3 bits):状态为“接收,小时间间隔”;
- L=0000000000001(后13 bits):连续1个包;
 
- 结论:该chunk表示从base sequence number开始,1个RTP包被成功接收,且到达时间间隔为小间隔(需结合recv delta计算具体值)。
2.2 Status vector chunk(状态矢量编码)
适用于RTP包状态频繁变化的场景(如丢包与接收交替出现),用位图形式逐包记录状态,灵活性更高。
结构格式
 0                   1                   2                   30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|T=1|  S  |                symbol list                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
字段解释:
- T(1 bit):固定为1,标识该chunk为Status vector类型;
- S(1 bit):状态标识长度,取值规则:
- 0:每个状态用1 bit表示(仅区分“丢包0”和“接收1”);
- 1:每个状态用2 bits表示(支持完整4种状态,同Run length的S字段);
 
- symbol list(14 bits):存储具体状态序列,根据S的取值分为两种模式:
- S=0:14 bits对应14个RTP包的状态(每bit对应1个包);
- S=1:14 bits对应7个RTP包的状态(每2 bits对应1个包)。
 
示例解析
以两种常见场景为例,展示Status vector chunk的解码过程:
示例1:S=0(1 bit/状态)
- 抓包数据:Packet Chunk: 38822 (0x97a6) [1 bit Status Vector Chunk]: |N|R|R|R|R|R|N|R|N|N|R|R|N|N|
- 十六进制转二进制:0x97a6 = 1001 0111 1010 0110
- 字段拆分:
- T=1(第1 bit):确认Status vector类型;
- S=0(第2 bit):1 bit/状态,共14个包;
- symbol list=01011110100110(后14 bits);
 
- 状态映射(结合base sequence number=15706):
| symbol list位序 | 对应RTP序列号 | 状态(0=丢包N,1=接收R) | 说明 | 
|---|---|---|---|
| 0 | 15706 | 0(N) | 该包丢失 | 
| 1 | 15707 | 1(R) | 该包接收,需配合recv delta | 
| 2 | 15708 | 0(N) | 该包丢失 | 
| 3 | 15709 | 1(R) | 该包接收,需配合recv delta | 
| …(后续位) | … | … | … | 
示例2:S=1(2 bits/状态)
- 抓包数据:Packet Chunk: 50500 (0xc544) [2 bits Status Vector Chunk]: |NR|SD|SD|SD|NR|SD|NR|
- 十六进制转二进制:0xc544 = 1100 0101 0100 0100
- 字段拆分:
- T=1(第1 bit):确认Status vector类型;
- S=1(第2 bit):2 bits/状态,共7个包;
- symbol list=00010101000100(后14 bits);
 
- 状态映射(结合base sequence number=1708):
| symbol list位段 | 对应RTP序列号 | 状态(00=NR丢包,01=SD小间隔接收) | 说明 | 
|---|---|---|---|
| 00 | 1750 | 00(NR) | 该包丢失 | 
| 01 | 1751 | 01(SD) | 该包接收,小间隔recv delta | 
| 01 | 1752 | 01(SD) | 该包接收,小间隔recv delta | 
| …(后续位段) | … | … | … | 
3、Transport-cc在WebRTC中的核心流程
3.1 码率估计流程(基于延时的BWE)
Transport-cc的核心作用是为WebRTC的带宽估计(BWE)提供数据支持,通过接收端反馈的RTP包到达时间和状态,动态调整发送端码率。完整流程的函数调用栈如下,关键步骤已添加注释:
// 1. 接收端收到RTCP Transport-cc反馈报文,触发处理流程
Call::DeliverRtcp()
-> ModuleRtpRtcpImpl::IncomingRtcpPacket()  // 解析RTCP包,识别Transport-cc类型
-> RTCPReceiver::IncomingPacket(const uint8_t*, size_t)  // 接收端RTCP处理入口
-> RTCPReceiver::TriggerCallbacksFromRtcpPacket()  // 触发拥塞控制回调
-> RtpTransportControllerSend::OnTransportFeedback()  // 传输控制器接收反馈
-> GoogCcNetworkController::OnTransportPacketsFeedback()  // Google拥塞控制核心入口// 2. 基于反馈数据进行延时分析与码率计算-> DelayBasedBwe::IncomingPacketFeedbackVector()  // 延时-based BWE核心函数-> InterArrival::ComputeDeltas()  // 计算RTP包组的到达延迟差-> TrendlineEstimator::Update()  // 趋势线滤波,判断网络拥塞状态(过载/正常)-> AimdRateControl::Update()  // AIMD算法调整码率(增/减速率控制)-> SendSideBandwidthEstimation::UpdateDelayBasedEstimate()  // 更新最终码率估计值// 3. 触发网络状态变化,应用新码率-> MaybeTriggerOnNetworkChanged()  // 通知发送端调整码率-> bandwidth_estimation_->target_rate()  // 获取最终目标码率,用于后续发送
流程说明:
- 接收端通过RTCPReceiver解析Transport-cc报文,提取packet chunk和recv delta数据;
- DelayBasedBwe模块是核心,通过- InterArrival计算包组延迟差,结合- TrendlineEstimator判断网络是否过载(如延迟持续增加则判定为拥塞);
- AimdRateControl采用“加法增、乘法减”(AIMD)算法调整码率:网络正常时缓慢增加码率以利用带宽,拥塞时快速降低码率以缓解压力;
- 最终通过MaybeTriggerOnNetworkChanged通知发送端应用新码率,实现动态拥塞控制。
3.2 Transport-cc反馈报文发送流程
接收端需周期性发送Transport-cc反馈报文,确保发送端能及时获取网络状态。发送流程的函数调用栈如下:
// 1. 线程周期性调度,触发反馈发送
RunPlatformThread()
-> ProcessThreadImpl::Process()  // WebRTC核心线程处理函数
-> RemoteEstimatorProxy::Process()  // 远程估计代理,管理反馈发送
-> RemoteEstimatorProxy::SendPeriodicFeedbacks()  // 周期性发送反馈(核心触发点)// 2. 构造Transport-cc RTCP报文-> rtcp::TransportFeedback::Create()  // 创建Transport-cc报文实例,填充参数-> 设置base sequence number、reference time等核心字段-> 编码packet chunk(根据RTP状态选择Run length/Status vector)-> 计算并填充recv delta列表// 3. 发送报文到网络-> PacketRouter::SendCombinedRtcpPacket()  // 路由RTCP包,合并多类型反馈-> ModuleRtpRtcpImpl2::SendCombinedRtcpPacket()  // RTP/RTCP模块发送入口-> RTCPSender::SendCombinedRtcpPacket()  // RTCP发送器处理-> RTCPSender::PacketSender::AppendPacket()  // 将报文添加到发送队列
关键细节:
- SendPeriodicFeedbacks()是周期性触发点,默认间隔约100ms,确保反馈及时性;
- 报文构造时,rtcp::TransportFeedback::Create()会根据RTP包状态的连续性选择最优packet chunk编码(连续状态用Run length,零散状态用Status vector),减少报文体积;
- PacketRouter负责合并多种RTCP反馈(如Transport-cc、丢包反馈等),提高传输效率。
3.3 RTP包发送与时间记录流程
发送端在发送RTP包时,需记录关键时间信息(启动发送时间、实际发送时间),用于后续与接收端反馈的到达时间对比,计算延迟。完整流程如下:
启动发送时间记录(发送队列调度)
// 1. 发送队列调度RTP包发送
ProcessThreadImpl::Process()
-> PacedSender::Process()  //  pacing发送器,控制发送速率
-> PacingController::ProcessPackets()  // 调度待发送包
-> PacketRouter::SendPacket()  // 路由RTP包到对应通道
-> ModuleRtpRtcpImpl2::TrySendPacket()  // RTP/RTCP模块尝试发送
-> RtpSenderEgress::SendPacket()  // 发送端出口处理// 2. 记录启动发送时间(进入发送队列的时间)-> RtpSenderEgress::AddPacketToTransportFeedback()-> RtpTransportControllerSend::OnAddPacket(const RtpPacketSendInfo& packet_info) {// 记录包创建时间(毫秒级)Timestamp creation_time = Timestamp::Millis(clock_->TimeInMilliseconds());// 提交任务到队列,添加到TransportFeedback适配器task_queue_.PostTask([this, packet_info, creation_time]() {RTC_DCHECK_RUN_ON(&task_queue_);  // 确保线程安全transport_feedback_adapter_.AddPacket(packet_info,// 计算传输开销(如SRTP加密、IP/UDP头部)send_side_bwe_with_overhead_ ? transport_overhead_bytes_per_packet_ : 0,creation_time);  // 传入启动发送时间});}
实际发送时间记录(Socket发送)
// 1. 从发送队列到网络Socket的发送流程
RtpSenderEgress::SendPacketToNetwork()
-> WebRtcVoiceMediaChannel::SendRtp()  // 音视频通道发送入口
-> MediaChannel::SendPacket()
-> BaseChannel::SendPacket()
-> SrtpTransport::SendRtpPacket()  // SRTP加密(如需)
-> RtpTransport::SendPacket()
-> DtlsTransport::SendPacket()  // DTLS加密(如需)
-> P2PTransportChannel::SendPacket()  // P2P传输通道(ICE候选)
-> ProxyConnection::Send()
-> UDPPort::SendTo()  // UDP端口发送
-> AsyncUDPSocket::SendTo(const void* pv, size_t cb, const SocketAddress& addr, const rtc::PacketOptions& options) {// 2. 记录实际发送时间(调用Socket发送时的时间)rtc::SentPacket sent_packet(options.packet_id,  // RTP包的transport sequence numberrtc::TimeMillis(),  // 实际发送时间(毫秒级)options.info_signaled_after_sent);// 3. 发送信号量,通知后续处理(如更新发送历史)SignalSentPacket(this, sent_packet);// 4. 调用底层Socket发送数据int ret = socket_->SendTo(pv, cb, addr);return ret;
}// 3. 接收发送信号量,更新TransportFeedback历史
TransportFeedbackAdapter::ProcessSentPacket(const rtc::SentPacket& sent_packet) {// 解析RTP包的序列号(解缠绕,处理溢出)int64_t unwrapped_seq_num = seq_num_unwrapper_.Unwrap(sent_packet.packet_id);// 查找该包在历史记录中的条目auto it = history_.find(unwrapped_seq_num);if (it != history_.end()) {// 标记是否为重传包(已记录过发送时间则为重传)bool packet_retransmit = it->second.sent.send_time.IsFinite();// 更新实际发送时间it->second.sent.send_time = Timestamp::Millis(sent_packet.send_time_ms);// 更新最后发送时间,用于后续超时判断last_send_time_ = std::max(last_send_time_, it->second.sent.send_time);// 处理未跟踪的数据量(如重传包的额外开销)if (!pending_untracked_size_.IsZero()) {if (it->second.sent.send_time < last_untracked_send_time_) {// 处理乱序发送的包,追加未跟踪数据量it->second.sent.prior_unacked_data += pending_untracked_size_;}pending_untracked_size_ = DataSize::Zero();}}return absl::optional<SentPacket>(sent_packet);
}
时间记录说明:
- 启动发送时间(creation_time):RTP包进入发送队列的时间,用于判断发送端内部延迟;
- 实际发送时间(sent_packet.send_time_ms):调用UDP Socket发送的时间,是计算网络延迟的基准(网络延迟=接收端到达时间-实际发送时间);
- ProcessSentPacket通过- seq_num_unwrapper_处理RTP序列号溢出问题(16位序列号循环),确保包的唯一性跟踪。
3.4 接收端RTP包到达时间记录
接收端需准确记录RTP包的到达时间,用于计算recv delta和后续反馈。关键流程如下:
// 1. UDP Socket接收RTP包,触发读事件
void AsyncUDPSocket::OnReadEvent(AsyncSocket* socket) {RTC_DCHECK(socket_.get() == socket);SocketAddress remote_addr;int64_t timestamp;  // 接收端系统时间(微秒级)// 调用底层Socket接收数据,获取到达时间int len = socket_->RecvFrom(buf_, size_, &remote_addr, ×tamp);if (len < 0) {// 处理接收错误(如ICMP不可达)SocketAddress local_addr = socket_->GetLocalAddress();RTC_LOG(LS_INFO) << "AsyncUDPSocket[" << local_addr.ToSensitiveString() << "] receive failed with error " << socket_->GetError();return;}// 2. 发送读包信号,携带到达时间SignalReadPacket(this, buf_, static_cast<size_t>(len),  // 包数据和长度remote_addr, (timestamp > -1 ? timestamp : TimeMicros())  // 到达时间(微秒级,兜底当前时间));
}// 3. 远程估计代理处理到达时间,关联RTP包
void RemoteEstimatorProxy::IncomingPacket(int64_t arrival_time_ms,  // 到达时间(毫秒级,从SignalReadPacket转换)size_t payload_size, const RTPHeader& header) {// 校验到达时间有效性if (arrival_time_ms < 0 || arrival_time_ms > kMaxTimeMs) {RTC_LOG(LS_WARNING) << "Arrival time out of bounds: " << arrival_time_ms;return;}// 记录媒体流SSRC,关联反馈media_ssrc_ = header.ssrc;int64_t seq = 0;MutexLock lock(&lock_);  // 线程安全锁RTC_LOG(LS_INFO) << "IncomingPacket Arrival time: " << arrival_time_ms;// 4. 从RTP扩展头中提取transport sequence numberif (header.extension.hasTransportSequenceNumber) {// 解缠绕序列号,处理溢出seq = unwrapper_.Unwrap(header.extension.transportSequenceNumber);// 清理过期的包记录(避免内存泄漏)if (send_periodic_feedback_) {MaybeCullOldPackets(seq, arrival_time_ms);}// 5. 存储到达时间与序列号的关联,用于后续构造Transport-cc反馈packet_arrival_times_[seq] = arrival_time_ms;}
}
到达时间记录关键细节:
- AsyncUDPSocket::OnReadEvent通过底层Socket的- RecvFrom获取RTP包的到达时间(微秒级),确保精度;
- RemoteEstimatorProxy::IncomingPacket从RTP扩展头(- TransportSequenceNumber)中提取序列号,与到达时间关联存储,为后续构造- packet chunk和- recv delta提供数据;
- MaybeCullOldPackets定期清理过期的包记录(如已反馈过的包),避免内存占用过大。
4、WebRTC RTP扩展头协商与初始化
Transport-cc依赖RTP扩展头中的TransportSequenceNumber字段标识RTP包,而该扩展头的ID需通过SDP协商动态确定。以下是完整的协商与初始化流程。
4.1 RTP扩展头类型枚举
WebRTC支持多种RTP扩展头,用于传递不同的附加信息。RTPExtensionType枚举定义了所有支持的扩展类型,其中kRtpExtensionTransportSequenceNumber是Transport-cc的核心依赖:
// WebRTC中所有RTP扩展头的类型枚举(无间隙,连续取值)
enum RTPExtensionType : int {kRtpExtensionNone,  // 无效类型kRtpExtensionTransmissionTimeOffset,  // 传输时间偏移kRtpExtensionAudioLevel,  // 音频音量kRtpExtensionCsrcAudioLevel,  // CSRC音频音量kRtpExtensionInbandComfortNoise,  // 带内舒适噪声kRtpExtensionAbsoluteSendTime,  // 绝对发送时间kRtpExtensionAbsoluteCaptureTime,  // 绝对采集时间kRtpExtensionVideoRotation,  // 视频旋转角度kRtpExtensionTransportSequenceNumber,  // Transport-cc依赖的序列号扩展(核心)kRtpExtensionTransportSequenceNumber02,  // 备用序列号扩展kRtpExtensionPlayoutDelay,  // 播放延迟kRtpExtensionVideoContentType,  // 视频内容类型kRtpExtensionVideoLayersAllocation,  // 视频层分配kRtpExtensionVideoTiming,  // 视频时序kRtpExtensionRtpStreamId,  // RTP流IDkRtpExtensionRepairedRtpStreamId,  // 修复流IDkRtpExtensionMid,  // 媒体流ID(Mid)kRtpExtensionGenericFrameDescriptor00,  // 通用帧描述符(版本00)kRtpExtensionGenericFrameDescriptor = kRtpExtensionGenericFrameDescriptor00,  // 别名kRtpExtensionGenericFrameDescriptor02,  // 通用帧描述符(版本02)kRtpExtensionColorSpace,  // 颜色空间kRtpExtensionVideoFrameTrackingId,  // 视频帧跟踪IDkRtpExtensionNumberOfExtensions  // 总数(必须在最后)
};
4.2 SDP协商流程(扩展头ID动态分配)
RTP扩展头的ID无标准值,需通过SDP的a=extmap字段协商确定。以下是发送端创建Offer和接收端解析Answer的完整流程。
发送端创建Offer(封装扩展头到SDP)
发送端在创建SDP Offer时,会将支持的RTP扩展头封装为a=extmap字段,示例SDP片段如下:
a=mid:video\r\n
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\n  // 传输时间偏移(ID=2)
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n  // 绝对发送时间(ID=3)
a=extmap:4 urn:3gpp:video-orientation\r\n  // 视频旋转(ID=4)
a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n  // Transport-cc序列号(ID=5,核心)
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\n  // 播放延迟(ID=6)
a=sendrecv\r\n
a=rtcp-mux\r\n
a=rtpmap:96 H264/90000\r\n  // 视频编码格式(H264)
封装流程的函数调用栈:
// 1. 用户触发连接,初始化PeerConnection
wWinMain()
-> MainWnd::PreTranslateMessage()  // 窗口消息处理
-> MainWnd::OnDefaultAction()  // 用户默认操作(如“连接”按钮)
-> Conductor::ConnectToPeer()  // 导体类,PeerConnection控制入口-> Conductor::InitializePeerConnection()  // 初始化PeerConnection-> Conductor::AddTracks()  // 添加音视频轨道-> PeerConnection::AddTrack()  // PeerConnection添加轨道-> RtpTransmissionManager::AddTrack()  // RTP传输管理器处理-> RtpTransmissionManager::AddTrackUnifiedPlan()  // 统一计划模式处理-> RtpTransmissionManager::CreateAndAddTransceiver()  // 创建Transceiver// 2. 获取音视频默认支持的扩展头列表-> ChannelManager::GetSupportedAudioRtpHeaderExtensions()  // 音频扩展头-> WebRtcVoiceEngine::GetRtpHeaderExtensions()  // 具体音频扩展实现-> ChannelManager::GetSupportedVideoRtpHeaderExtensions()  // 视频扩展头-> WebRtcVideoEngine::GetRtpHeaderExtensions()  // 具体视频扩展实现
// 3. 创建SDP Offer,封装扩展头到a=extmap字段
-> SdpOfferAnswerHandler::CreateOffer()  // SDP Offer创建入口
-> SdpOfferAnswerHandler::GetOptionsForOffer()  // 获取Offer配置
-> SdpOfferAnswerHandler::GetOptionsForUnifiedPlanOffer()  // 统一计划Offer配置
-> MediaDescriptionOptions::GetMediaDescriptionOptionsForTransceiver()  // 媒体描述配置
-> RtpTransceiver::HeaderExtensionsToOffer()  // 将扩展头转为SDP的a=extmap字段
音频扩展头获取实现(WebRtcVoiceEngine)
// 获取音频默认支持的RTP扩展头列表,分配初始ID(从1开始)
std::vector<webrtc::RtpHeaderExtensionCapability> 
WebRtcVoiceEngine::GetRtpHeaderExtensions() const {RTC_DCHECK(signal_thread_checker_.IsCurrent());  // 确保在信号线程执行std::vector<webrtc::RtpHeaderExtensionCapability> result;int id = 1;  // 扩展头ID从1开始分配(0为保留)// 按顺序添加音频支持的扩展头,每个扩展头对应唯一URI和IDfor (const auto& uri : {webrtc::RtpExtension::kAudioLevelUri,  // 音频音量:"urn:ietf:params:rtp-hdrext:audio-level"webrtc::RtpExtension::kAbsSendTimeUri,  // 绝对发送时间:"http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time"webrtc::RtpExtension::kTransportSequenceNumberUri,  // Transport-cc序列号:"http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"webrtc::RtpExtension::kMidUri,  // Mid:"urn:ietf:params:rtp-hdrext:ssrc-audio-level"webrtc::RtpExtension::kRidUri,  // Rid:"urn:ietf:params:rtp-hdrext:rtp-stream-id"webrtc::RtpExtension::kRepairedRidUri  // Repaired Rid:"urn:ietf:params:rtp-hdrext:repaired-rtp-stream-id"}) {// 添加扩展头能力,方向为发送接收(kSendRecv)result.emplace_back(uri, id++, webrtc::RtpTransceiverDirection::kSendRecv);}return result;
}
视频扩展头获取实现(WebRtcVideoEngine)
// 获取视频默认支持的RTP扩展头列表,分配初始ID(从1开始)
std::vector<webrtc::RtpHeaderExtensionCapability> 
WebRtcVideoEngine::GetRtpHeaderExtensions() const {std::vector<webrtc::RtpHeaderExtensionCapability> result;int id = 1;  // 扩展头ID从1开始分配// 按顺序添加视频支持的扩展头(基础扩展)for (const auto& uri : {webrtc::RtpExtension::kTimestampOffsetUri,  // 时间戳偏移:"urn:ietf:params:rtp-hdrext:toffset"webrtc::RtpExtension::kAbsSendTimeUri,  // 绝对发送时间webrtc::RtpExtension::kVideoRotationUri,  // 视频旋转:"urn:3gpp:video-orientation"webrtc::RtpExtension::kTransportSequenceNumberUri,  // Transport-cc序列号(核心)webrtc::RtpExtension::kPlayoutDelayUri,  // 播放延迟:"http://www.webrtc.org/experiments/rtp-hdrext/playout-delay"webrtc::RtpExtension::kVideoContentTypeUri,  // 视频内容类型:"http://www.webrtc.org/experiments/rtp-hdrext/video-content-type"webrtc::RtpExtension::kVideoTimingUri,  // 视频时序:"http://www.webrtc.org/experiments/rtp-hdrext/video-timing"webrtc::RtpExtension::kColorSpaceUri,  // 颜色空间:"http://www.webrtc.org/experiments/rtp-hdrext/color-space"webrtc::RtpExtension::kMidUri,  // Midwebrtc::RtpExtension::kRidUri,  // Ridwebrtc::RtpExtension::kRepairedRidUri  // Repaired Rid}) {result.emplace_back(uri, id++, webrtc::RtpTransceiverDirection::kSendRecv);}// 添加条件性扩展头(根据实验开关启用)// 1. 通用帧描述符(版本00)result.emplace_back(webrtc::RtpExtension::kGenericFrameDescriptorUri00, id++,IsEnabled(trials_, "WebRTC-GenericDescriptorAdvertised")  // 实验开关? webrtc::RtpTransceiverDirection::kSendRecv: webrtc::RtpTransceiverDirection::kStopped);  // 禁用时为停止状态// 2. 依赖描述符result.emplace_back(webrtc::RtpExtension::kDependencyDescriptorUri, id++,IsEnabled(trials_, "WebRTC-DependencyDescriptorAdvertised")? webrtc::RtpTransceiverDirection::kSendRecv: webrtc::RtpTransceiverDirection::kStopped);// 3. 视频层分配result.emplace_back(webrtc::RtpExtension::kVideolayersAllocationUri, id++,IsEnabled(trials_, "WebRTC-VideolayersAllocationAdvertised")? webrtc::RtpTransceiverDirection::kSendRecv: webrtc::RtpTransceiverDirection::kStopped);// 4. 视频帧跟踪IDresult.emplace_back(webrtc::RtpExtension::kVideoframeTrackingIdUri, id++,IsEnabled(trials_, "WebRTC-VideoFrameTrackingIdAdvertised")? webrtc::RtpTransceiverDirection::kSendRecv: webrtc::RtpTransceiverDirection::kStopped);return result;
}
4.3 接收端解析SDP Answer并注册ExtensionMap
接收端收到对端的SDP Answer后,需解析其中的a=extmap字段,获取扩展头的实际ID,并注册到RtpHeaderExtensionMap中,确保后续能正确解析RTP包中的扩展头。完整流程如下:
// 1. 接收端收到SDP Answer,触发处理
wWinMain()
-> Win32Window::WndProc()  // 窗口消息处理
-> Win32SocketServer::MessageWindow::OnMessage()  // Socket消息
-> Win32SocketServer::Pump()  // 消息循环
-> Thread::Dispatch()  // 线程消息分发
-> WebRtcSessionDescriptionFactory::OnMessage()  // SDP工厂消息处理
-> Conductor::OnSuccess()  // 导体类处理成功回调(如收到Answer)// 2. 设置本地SDP,触发解析-> PeerConnectionProxyWithInternal<webrtc::PeerConnectionInterface>::SetLocalDescription()-> SdpOfferAnswerHandler::SetLocalDescription()  // SDP处理入口-> SdpOfferAnswerHandler::DoSetLocalDescription()  // 执行SDP设置-> SdpOfferAnswerHandler::ApplyLocalDescription()  // 应用SDP配置-> SdpOfferAnswerHandler::UpdateSessionState()  // 更新会话状态-> SdpOfferAnswerHandler::PushdownMediaDescription()  // 向下传递媒体描述// 3. 分音频/视频通道处理扩展头注册// 3.1 音频通道分支-> BaseChannel::SetRemoteContent()-> VoiceChannel::SetRemoteContent_w()  // 音频通道远程内容设置-> WebRtcVoiceMediaChannel::SetSendParameters()  // 设置音频发送参数-> AudioSendStream::ConfigureStream()  // 配置音频发送流-> ModuleRtpRtcpImpl2::RegisterRtpHeaderExtension()  // 注册扩展头-> RTPSender::RegisterRtpHeaderExtension()  // RTP发送器注册-> RtpHeaderExtensionMap::Register()  // 核心:添加到扩展头映射表// 3.2 视频通道分支-> BaseChannel::SetRemoteContent()-> VideoChannel::SetRemoteContent_w()  // 视频通道远程内容设置-> WebRtcVideoChannel::SetSendParameters()  // 设置视频发送参数-> WebRtcVideoChannel::ApplyChangedParams()  // 应用参数变更-> WebRtcVideoChannel::WebRtcVideoSendStream::SetCodec()  // 设置视频编码-> WebRtcVideoChannel::WebRtcVideoSendStream::RecreateWebRtcStream()  // 重建视频流-> internal::Call::CreateVideoSendStream()  // 创建视频发送流-> VideoSendStream::VideoSendStream()  // 视频流构造-> RtpTransportControllerSend::CreateRtpVideoSender()  // 创建视频RTP发送器-> RtpVideoSender::RtpVideoSender()  // 视频发送器构造-> ModuleRtpRtcpImpl2::RegisterRtpHeaderExtension()  // 注册扩展头-> RTPSender::RegisterRtpHeaderExtension()-> RtpHeaderExtensionMap::Register()  // 核心:添加到扩展头映射表
注册流程说明:
- 接收端通过SdpOfferAnswerHandler解析SDP Answer中的a=extmap字段,提取扩展头的URI和对应的ID;
- 音频/视频通道分别通过VoiceChannel和VideoChannel的SetRemoteContent_w触发扩展头注册;
- 最终通过RtpHeaderExtensionMap::Register()将“扩展头类型-URI-ID”的映射关系存储,后续解析RTP包时,根据ID即可找到对应的扩展头类型(如kRtpExtensionTransportSequenceNumber),提取TransportSequenceNumber字段。
