| /* |
| * Copyright (c) 2024 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "modules/rtp_rtcp/source/rtcp_packet/congestion_control_feedback.h" |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <utility> |
| #include <vector> |
| |
| #include "api/array_view.h" |
| #include "api/units/time_delta.h" |
| #include "api/units/timestamp.h" |
| #include "modules/rtp_rtcp/source/byte_io.h" |
| #include "rtc_base/network/ecn_marking.h" |
| |
| namespace webrtc { |
| namespace rtcp { |
| |
| /* |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |V=2|P| FMT=11 | PT = 205 | length | |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | SSRC of RTCP packet sender | |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | SSRC of 1st RTP Stream | |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | begin_seq | num_reports | |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |R|ECN| Arrival time offset | ... . |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| . . |
| . . |
| . . |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | SSRC of nth RTP Stream | |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | begin_seq | num_reports | |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |R|ECN| Arrival time offset | ... | |
| . . |
| . . |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | Report Timestamp (32 bits) | |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| */ |
| |
| namespace { |
| |
| constexpr size_t kSenderSsrcLength = 4; |
| constexpr size_t kHeaderPerMediaSssrcLength = 8; |
| constexpr size_t kTimestampLength = 4; |
| |
| // RFC-3168, Section 5 |
| constexpr uint16_t kEcnEct1 = 0x01; |
| constexpr uint16_t kEcnEct0 = 0x02; |
| constexpr uint16_t kEcnCe = 0x03; |
| |
| // Arrival time offset (ATO, 13 bits): |
| // The arrival time of the RTP packet at the receiver, as an offset before the |
| // time represented by the Report Timestamp (RTS) field of this RTCP congestion |
| // control feedback report. The ATO field is in units of 1/1024 seconds (this |
| // unit is chosen to give exact offsets from the RTS field) so, for example, an |
| // ATO value of 512 indicates that the corresponding RTP packet arrived exactly |
| // half a second before the time instant represented by the RTS field. If the |
| // measured value is greater than 8189/1024 seconds (the value that would be |
| // coded as 0x1FFD), the value 0x1FFE MUST be reported to indicate an over-range |
| // measurement. If the measurement is unavailable or if the arrival time of the |
| // RTP packet is after the time represented by the RTS field, then an ATO value |
| // of 0x1FFF MUST be reported for the packet. |
| uint16_t To13bitAto(TimeDelta arrival_time_offset) { |
| if (arrival_time_offset < TimeDelta::Zero()) { |
| return 0x1FFF; |
| } |
| return std::min( |
| static_cast<int64_t>(1024 * arrival_time_offset.seconds<float>()), |
| int64_t{0x1FFE}); |
| } |
| |
| TimeDelta AtoToTimeDelta(uint16_t receive_info) { |
| // receive_info |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // |R|ECN| Arrival time offset | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| const uint16_t ato = receive_info & 0x1FFF; |
| if (ato == 0x1FFE) { |
| return TimeDelta::PlusInfinity(); |
| } |
| if (ato == 0x1FFF) { |
| return TimeDelta::MinusInfinity(); |
| } |
| return TimeDelta::Seconds(ato) / 1024; |
| } |
| |
| uint16_t To2BitEcn(rtc::EcnMarking ecn_marking) { |
| switch (ecn_marking) { |
| case rtc::EcnMarking::kNotEct: |
| return 0; |
| case rtc::EcnMarking::kEct1: |
| return kEcnEct1 << 13; |
| case rtc::EcnMarking::kEct0: |
| return kEcnEct0 << 13; |
| case rtc::EcnMarking::kCe: |
| return kEcnCe << 13; |
| } |
| } |
| |
| rtc::EcnMarking ToEcnMarking(uint16_t receive_info) { |
| const uint16_t ecn = (receive_info >> 13) & 0b11; |
| if (ecn == kEcnEct1) { |
| return rtc::EcnMarking::kEct1; |
| } |
| if (ecn == kEcnEct0) { |
| return rtc::EcnMarking::kEct0; |
| } |
| if (ecn == kEcnCe) { |
| return rtc::EcnMarking::kCe; |
| } |
| return rtc::EcnMarking::kNotEct; |
| } |
| |
| } // namespace |
| |
| CongestionControlFeedback ::CongestionControlFeedback( |
| std::vector<PacketInfo> packets, |
| uint32_t compact_ntp_timestamp) |
| : packets_(std::move(packets)), |
| report_timestamp_compact_ntp_(compact_ntp_timestamp) {} |
| |
| bool CongestionControlFeedback::Create(uint8_t* buffer, |
| size_t* position, |
| size_t max_length, |
| PacketReadyCallback callback) const { |
| // Ensure there is enough room for this packet. |
| while (*position + BlockLength() > max_length) { |
| if (!OnBufferFull(buffer, position, callback)) |
| return false; |
| } |
| const size_t position_end = *position + BlockLength(); |
| |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // |V=2|P| FMT=11 | PT = 205 | length | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | SSRC of RTCP packet sender | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), buffer, |
| position); |
| ByteWriter<uint32_t>::WriteBigEndian(&buffer[*position], sender_ssrc()); |
| *position += 4; |
| |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | SSRC of nth RTP Stream | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | begin_seq | num_reports | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // |R|ECN| Arrival time offset | ... . |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // . . |
| auto write_report_for_ssrc = [&](rtc::ArrayView<const PacketInfo> packets) { |
| // SSRC of nth RTP stream. |
| ByteWriter<uint32_t>::WriteBigEndian(&buffer[*position], packets[0].ssrc); |
| *position += 4; |
| |
| // begin_seq |
| ByteWriter<uint16_t>::WriteBigEndian(&buffer[*position], |
| packets[0].sequence_number); |
| *position += 2; |
| // num_reports |
| uint16_t num_reports = packets[packets.size() - 1].sequence_number - |
| packets[0].sequence_number + 1; |
| |
| // Each report block MUST NOT include more than 16384 packet metric blocks |
| // (i.e., it MUST NOT report on more than one quarter of the sequence number |
| // space in a single report). |
| if (num_reports > 16384) { |
| RTC_DCHECK_NOTREACHED() << "Unexpected number of reports:" << num_reports; |
| return; |
| } |
| ByteWriter<uint16_t>::WriteBigEndian(&buffer[*position], num_reports); |
| *position += 2; |
| |
| int packet_index = 0; |
| for (int i = 0; i < num_reports; ++i) { |
| uint16_t sequence_number = packets[0].sequence_number + i; |
| |
| bool received = sequence_number == packets[packet_index].sequence_number; |
| |
| uint16_t packet_info = 0; |
| if (received) { |
| packet_info = 0x8000 | To2BitEcn(packets[packet_index].ecn) | |
| To13bitAto(packets[packet_index].arrival_time_offset); |
| |
| ++packet_index; |
| } |
| ByteWriter<uint16_t>::WriteBigEndian(&buffer[*position], packet_info); |
| *position += 2; |
| } |
| // 32bit align per SSRC block. |
| if (num_reports % 2 != 0) { |
| ByteWriter<uint16_t>::WriteBigEndian(&buffer[*position], 0); |
| *position += 2; |
| } |
| }; |
| |
| rtc::ArrayView<const PacketInfo> remaining(packets_); |
| while (!remaining.empty()) { |
| int number_of_packets_for_ssrc = 0; |
| uint32_t ssrc = remaining[0].ssrc; |
| for (const PacketInfo& packet_info : remaining) { |
| if (packet_info.ssrc != ssrc) { |
| break; |
| } |
| ++number_of_packets_for_ssrc; |
| } |
| write_report_for_ssrc(remaining.subview(0, number_of_packets_for_ssrc)); |
| remaining = remaining.subview(number_of_packets_for_ssrc); |
| } |
| |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | Report Timestamp (32 bits) | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| ByteWriter<uint32_t>::WriteBigEndian(&buffer[*position], |
| report_timestamp_compact_ntp_); |
| *position += 4; |
| |
| RTC_DCHECK_EQ(*position, position_end); |
| return true; |
| } |
| |
| size_t CongestionControlFeedback::BlockLength() const { |
| // Total size of this packet |
| size_t total_size = kSenderSsrcLength + kHeaderLength + kTimestampLength; |
| if (packets_.empty()) { |
| return total_size; |
| } |
| |
| auto increase_size_per_ssrc = [](int number_of_packets_for_ssrc) { |
| // Each packet report needs two bytes. |
| size_t packet_block_size = number_of_packets_for_ssrc * 2; |
| // 32 bit aligned. |
| return kHeaderPerMediaSssrcLength + packet_block_size + |
| ((number_of_packets_for_ssrc % 2) != 0 ? 2 : 0); |
| }; |
| |
| uint32_t ssrc = packets_.front().ssrc; |
| uint16_t first_sequence_number = packets_.front().sequence_number; |
| for (size_t i = 0; i < packets_.size(); ++i) { |
| if (packets_[i].ssrc != ssrc) { |
| uint16_t number_of_packets = |
| packets_[i - 1].sequence_number - first_sequence_number + 1; |
| total_size += increase_size_per_ssrc(number_of_packets); |
| ssrc = packets_[i].ssrc; |
| first_sequence_number = packets_[i].sequence_number; |
| } |
| } |
| uint16_t number_of_packets = |
| packets_.back().sequence_number - first_sequence_number + 1; |
| total_size += increase_size_per_ssrc(number_of_packets); |
| |
| return total_size; |
| } |
| |
| bool CongestionControlFeedback::Parse(const rtcp::CommonHeader& packet) { |
| const uint8_t* payload = packet.payload(); |
| const uint8_t* payload_end = packet.payload() + packet.payload_size_bytes(); |
| |
| if (packet.payload_size_bytes() % 4 != 0 || |
| packet.payload_size_bytes() < kSenderSsrcLength + kTimestampLength) { |
| return false; |
| } |
| |
| SetSenderSsrc(ByteReader<uint32_t>::ReadBigEndian(payload)); |
| payload += 4; |
| |
| report_timestamp_compact_ntp_ = |
| ByteReader<uint32_t>::ReadBigEndian(payload_end - 4); |
| payload_end -= 4; |
| |
| while (payload + kHeaderPerMediaSssrcLength < payload_end) { |
| uint32_t ssrc = ByteReader<uint32_t>::ReadBigEndian(payload); |
| payload += 4; |
| |
| uint16_t base_seqno = ByteReader<uint16_t>::ReadBigEndian(payload); |
| payload += 2; |
| uint16_t num_reports = ByteReader<uint16_t>::ReadBigEndian(payload); |
| payload += 2; |
| |
| constexpr size_t kPerPacketLength = 2; |
| if (payload + kPerPacketLength * num_reports > payload_end) { |
| return false; |
| } |
| |
| for (int i = 0; i < num_reports; ++i) { |
| uint16_t packet_info = ByteReader<uint16_t>::ReadBigEndian(payload); |
| payload += 2; |
| |
| uint16_t seq_no = base_seqno + i; |
| bool received = (packet_info & 0x8000); |
| if (received) { |
| packets_.push_back({.ssrc = ssrc, |
| .sequence_number = seq_no, |
| .arrival_time_offset = AtoToTimeDelta(packet_info), |
| .ecn = ToEcnMarking(packet_info)}); |
| } |
| } |
| if (num_reports % 2) { |
| // 2 bytes padding |
| payload += 2; |
| } |
| } |
| return payload == payload_end; |
| } |
| } // namespace rtcp |
| } // namespace webrtc |