webrtc代码走读(二十)_QOS系列12_Transport-cc协议与RTP扩展头
Transport-cc协议是WebRTC中基于RTCP反馈报文的拥塞控制机制,通过动态调整码率保障实时音视频传输质量。其核心结构包括:1)报头标识字段;2)SSRC源标识;3)控制参数(序列号、包状态计数等);4)packet chunk状态编码(行程编码Run length或状态向量Status vector);5)到达时间差recv delta(1/2字节存储)。该协议通过压缩反馈信息实现高
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 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 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, ×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 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 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流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; - 音频/视频通道分别通过
VoiceChannel和VideoChannel的SetRemoteContent_w触发扩展头注册; - 最终通过
RtpHeaderExtensionMap::Register()将“扩展头类型-URI-ID”的映射关系存储,后续解析RTP包时,根据ID即可找到对应的扩展头类型(如kRtpExtensionTransportSequenceNumber),提取TransportSequenceNumber字段。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)