Transport-cc(Transport-wide Congestion Control)协议是WebRTC中用于全局拥塞控制的关键协议,通过RTCP反馈报文实现网络状况评估与带宽估算,确保实时音视频传输的服务质量(QoS)。其核心是通过记录RTP包的到达时间、丢包状态等信息,反向反馈给发送端以动态调整码率。

1、Transport-cc协议结构与参数说明

1.1 协议结构(RTCP反馈报文格式)

Transport-cc协议的RTCP反馈报文结构:

 0                   1                   2                   3
 0 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                   3
 0 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                   3
 0 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 chunkrecv 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 number
       rtc::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, &timestamp);
   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 number
   if (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 chunkrecv 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流ID
   kRtpExtensionRepairedRtpStreamId,  // 修复流ID
   kRtpExtensionMid,  // 媒体流ID(Mid)
   kRtpExtensionGenericFrameDescriptor00,  // 通用帧描述符(版本00)
   kRtpExtensionGenericFrameDescriptor = kRtpExtensionGenericFrameDescriptor00,  // 别名
   kRtpExtensionGenericFrameDescriptor02,  // 通用帧描述符(版本02)
   kRtpExtensionColorSpace,  // 颜色空间
   kRtpExtensionVideoFrameTrackingId,  // 视频帧跟踪ID
   kRtpExtensionNumberOfExtensions  // 总数(必须在最后)
};
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和ID
   for (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,  // Mid
       webrtc::RtpExtension::kRidUri,  // Rid
       webrtc::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. 视频帧跟踪ID
   result.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;
  • 音频/视频通道分别通过VoiceChannelVideoChannelSetRemoteContent_w触发扩展头注册;
  • 最终通过RtpHeaderExtensionMap::Register()将“扩展头类型-URI-ID”的映射关系存储,后续解析RTP包时,根据ID即可找到对应的扩展头类型(如kRtpExtensionTransportSequenceNumber),提取TransportSequenceNumber字段。
Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐