WEBRTC浅析(八)GCC:: Transport-wide Congestion Control的协议简析
Transport-wide Congestion Control
参考文档:https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
一:SDP协商
a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions
二:RTP扩展头::TransportSequenceNumber
发送端在
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0xBE | 0xDE | length=1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ID | L=1 |transport-wide sequence number | zero padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
An RTP header extension with a 16 bits sequence number attached to
all packets sent. This sequence number is incremented by 1 for each
packet being sent over the same socket.
-
在webrt中的TWCC报文实现:
// 0 1 2 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID | L=1 |transport wide sequence number | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ constexpr RTPExtensionType TransportSequenceNumber::kId; constexpr uint8_t TransportSequenceNumber::kValueSizeBytes; constexpr const char* TransportSequenceNumber::kUri; bool TransportSequenceNumber::Parse(rtc::ArrayView<const uint8_t> data, uint16_t* value) { if (data.size() != 2) return false; *value = ByteReader<uint16_t>::ReadBigEndian(data.data()); return true; } bool TransportSequenceNumber::Write(uint8_t* data, uint16_t value) { ByteWriter<uint16_t>::WriteBigEndian(data, value); return true; }
-
发送端:TransportSequenceNumber RTP扩展头的发送
bool RTPSender::UpdateTransportSequenceNumber(RtpPacketToSend* packet, int* packet_id) const { ...... *packet_id = transport_sequence_number_allocator_->AllocateSequenceNumber(); if (!packet->SetExtension<TransportSequenceNumber>(*packet_id)) { return false; } return true; }
-
接收端:TransportSequenceNumber RTP扩展头的解析
void Packet::GetHeader(RTPHeader* header) const { header->markerBit = Marker(); header->payloadType = PayloadType(); header->sequenceNumber = SequenceNumber(); header->timestamp = Timestamp(); header->ssrc = Ssrc(); ...... header->extension.hasAbsoluteSendTime = GetExtension<AbsoluteSendTime>(&header->extension.absoluteSendTime); header->extension.hasTransportSequenceNumber = GetExtension<TransportSequenceNumber>( &header->extension.transportSequenceNumber); ...... }
三:TWCC 反馈报文(Transport-wide RTCP Feedback Message)
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 | fb pkt. count |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| packet chunk | packet chunk |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. .
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| packet chunk | recv delta | recv delta |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. .
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| recv delta | recv delta | zero padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
包状态块解析(packet chunk)
包状态块,有两种:-
Run length chunk:当我们连续收到多个数据包,他们都有相同的到达状态,就可以用这种编码方式
0 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |T| S | Run Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
chunk type (T): 为0,表示为Run length chunk
-
packet status symbol (S): 包状态符号,有四种:
00 Packet not received 01 Packet received, small delta (所谓small detal是指能用一个字节表示的数值) 10 Packet received, large ornegative delta (large即是能用两个字节表示的数值) 11 [Reserved]
-
-
Status vector chunk:这种表示方式用于每个数据包都需要自己的状态表示码。但是这里的S就不是上面的意思。
0 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |T|S| symbol list | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
chunk type (T): 为1,表示为Status vector chunk
-
symbol size (S):
s = 0 时,表示symbollist的每一个bit能表示一个数据包的到达状态0 Packet not received 1 Packet received, small delta s = 1 时,表示symbollist每两个bit表示一个数据包的状态 00 Packet not received 01 Packet received, small delta 10 Packet received, large ornegative delta 11 [Reserved]
-
-
-
接收时间差(recv delta)
最后,对于每一个状态为Packet received 的数据包的延迟依次填入|recv delta|字段 到达状态为1的,recv delta占用一个字节 到达状态为2的,recv delta占用两个字节 可以看出以上编码的目的是为了尽量减少该数据包的大小。
-
webrtc中TWCC的打包代码:
// Serialize packet. bool TransportFeedback::Create(uint8_t* packet, size_t* position, size_t max_length, PacketReadyCallback* callback) const { } static std::unique_ptr<TransportFeedback> ParseFrom(const uint8_t* buffer, size_t length);
-
webrtc中发送端:打包TWCC报文
// 在收到RTP包的时候先同步数据 PacketReceiver::DeliveryStatus Call::DeliverRtp(MediaType media_type, const uint8_t* packet, size_t length, const PacketTime& packet_time, bool just_for_bwe) { parsed_packet->IdentifyExtensions(it->second.extensions); NotifyBweOfReceivedPacket(*parsed_packet, media_type); } // 传入arrive time和sequence number void RemoteEstimatorProxy::IncomingPacket(int64_t arrival_time_ms, size_t payload_size, const RTPHeader& header) { if (!header.extension.hasTransportSequenceNumber) { OnPacketArrival(header.extension.transportSequenceNumber, arrival_time_ms); } //在收到包后,组包并发送 void RemoteEstimatorProxy::OnPacketArrival(uint16_t sequence_number, int64_t arrival_time) { 。。。。。。 rtcp::TransportFeedback feedback_packet; if (BuildFeedbackPacket(&feedback_packet)) { RTC_DCHECK(packet_router_ != nullptr); } 。。。。。。 }
-
Webrtc中接收端:接收解析TWCC报文
//1. 在rtcp callback的回调中,分析拆解 bool RTCPReceiver::ParseCompoundPacket(const uint8_t* packet_begin, const uint8_t* packet_end, PacketInformation* packet_information) { switch (rtcp_block.type()) { case rtcp::SenderReport::kPacketType: HandleSenderReport(rtcp_block, packet_information); break; case rtcp::ReceiverReport::kPacketType: HandleReceiverReport(rtcp_block, packet_information); break; case rtcp::Sdes::kPacketType: HandleSdes(rtcp_block, packet_information); break; case rtcp::ExtendedReports::kPacketType: HandleXr(rtcp_block, packet_information); break; case rtcp::Bye::kPacketType: HandleBye(rtcp_block); break; case rtcp::Rtpfb::kPacketType: switch (rtcp_block.fmt()) { case rtcp::Nack::kFeedbackMessageType: HandleNack(rtcp_block, packet_information); break; case rtcp::Tmmbr::kFeedbackMessageType: HandleTmmbr(rtcp_block, packet_information); break; case rtcp::Tmmbn::kFeedbackMessageType: HandleTmmbn(rtcp_block, packet_information); break; case rtcp::RapidResyncRequest::kFeedbackMessageType: HandleSrReq(rtcp_block, packet_information); break; case rtcp::TransportFeedback::kFeedbackMessageType: HandleTransportFeedback(rtcp_block, packet_information); break; default: ++num_skipped_packets_; break; } //2.计算twcc feedback 中,最后一个包的 sendtime和当前时间的差,就可以用来计算实时的rtt DelayBasedBwe::Result DelayBasedBwe::IncomingPacketFeedbackVector( std::vector<PacketFeedback>& packet_feedback_vector, rtc::Optional<uint32_t> acked_bitrate_bps, rtc::Optional<uint32_t> max_acked_bitrate_bps, float loss_rate, float total_loss_rate, float exceed_sent_ratio, bool is_end2end_feedback) { //Calculate instant rtt frome per feedback std::vector<PacketFeedback>::iterator end_feedback = packet_feedback_vector.end(); if(packet_feedback_vector.begin() != packet_feedback_vector.end()) { end_feedback--; double instent_rtt = now_ms - end_feedback->send_time_ms; rate_control_.SetRtt(instent_rtt); twcc_rtt_ = instent_rtt; LOG(LS_VERBOSE) << "#BWE instant_rtt_" << instent_rtt << " now " << now_ms; }