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

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 bit0(无填充)或1(有填充,用于对齐字节)调整报文长度至4字节倍数,满足底层传输要求
feedback message type (FMT)5 bits固定为15唯一标识该RTCP报文为Transport-cc反馈类型
payload type (PT)8 bits固定为205区分RTCP报文的负载类型,与FMT配合确认协议类型
SSRC of packet sender32 bits发送端设备的SSRC(唯一标识)标识反馈报文的发送方,便于接收端溯源
SSRC of media source32 bits媒体流(如视频/音频)的SSRC关联反馈信息到具体媒体流,避免多流混淆
base sequence number16 bitsRTP包的起始序列号(transport sequence number)确定当前反馈报文覆盖的RTP包序列范围起点,支持乱序反馈
packet status count16 bits正整数(0~65535)表示当前反馈报文记录的RTP包数量,即base sequence number后续的包数量
reference time24 bits以64ms为单位的时间戳作为RTP包到达时间的基准,所有recv delta基于此计算实际到达时间
feedback packet count8 bits自增整数(0~255,循环计数)标识反馈报文自身的序列号,用于检测反馈包丢失
packet chunk16 bits/块两种编码类型(Run length/Status vector)压缩存储RTP包的到达状态(丢包/接收),减少反馈报文体积
recv delta8/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)说明
0157060(N)该包丢失
1157071(R)该包接收,需配合recv delta
2157080(N)该包丢失
3157091(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小间隔接收)说明
00175000(NR)该包丢失
01175101(SD)该包接收,小间隔recv delta
01175201(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 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, &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 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 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流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;
  • 音频/视频通道分别通过VoiceChannelVideoChannelSetRemoteContent_w触发扩展头注册;
  • 最终通过RtpHeaderExtensionMap::Register()将“扩展头类型-URI-ID”的映射关系存储,后续解析RTP包时,根据ID即可找到对应的扩展头类型(如kRtpExtensionTransportSequenceNumber),提取TransportSequenceNumber字段。
http://www.dtcms.com/a/548943.html

相关文章:

  • 前端多版本零404部署实践:为什么会404,以及怎么彻底解决
  • k8s的包管理工具helm3--流程控制语句(3)
  • Kubernetes 实战入门核心内容总结
  • F042 A星算法课程推荐(A*算法) | 课程知识图谱|课程推荐vue+flask+neo4j B/S架构前后端分离|课程知识图谱构造
  • STM32H743-ARM例程34-BootROM
  • Parasoft C/C++test如何在ARM DS-5环境中进行测试(上)
  • 网站建设批复意见证券投资网站建设
  • 激光测距望远镜的光学设计
  • Unity3D与Three.js构建3D可视化模型技术对比分析
  • 【开发者导航】开源轻量的 Linux 平台设计协作客户端:Figma Linux
  • 从 “不敢练” 到 “实战练”!XM-E01-100 桌面五轴重构院校实训课堂
  • Rust 开发环境管理:安装与切换 Rust 版本的深度实践
  • 网站建设费用模板正规网站建设推荐
  • 学习笔记前言
  • 专业软件网站建设班级网站建设维护
  • day03(10.30)——leetcode面试经典150
  • MySQL8.0全栈初始化脚本集
  • 算法20.0
  • golang程序对接prometheus
  • 服务器负载均衡架构部署:Keepalived+Nginx 实现双机热备与高可用负载均衡
  • 内容分享网站设计在阿里巴巴上做网站有效果吗
  • SAP PP BOM主数据维护接口分享
  • 合成孔径雷达(SAR)及其信号处理:一文读懂,从类比到原理
  • 深度学习神经网络入门-问答学习
  • 化工防爆气象站:化工安全的气象监测设备
  • 做货运网站找哪家好如何用云服务器搭建个人网站
  • RAG拓展、变体、增强版(三)
  • 【PDF】PDF文件体详解
  • C++ STL list 容器学习笔记:双向链表的 “小火车“ 操控指南
  • Visual Studio Code (VS Code) 官方下载渠道