blob: 0b10f8332d25200c158c5466a71a91d1761b97a3 [file]
/*
* Copyright (c) 2015 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/congestion_controller/rtp/transport_feedback_adapter.h"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <optional>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "api/transport/ecn_marking.h"
#include "api/transport/network_types.h"
#include "api/units/data_size.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
#include "modules/rtp_rtcp/source/ntp_time_util.h"
#include "modules/rtp_rtcp/source/rtcp_packet/congestion_control_feedback.h"
#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h"
#include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/network/sent_packet.h"
#include "rtc_base/network_route.h"
namespace webrtc {
namespace {
constexpr TimeDelta kSendTimeHistoryWindow = TimeDelta::Seconds(60);
class MinMax {
public:
explicit MinMax(int64_t value) : minimum_(value), maximum_(value) {}
void Update(int64_t value) {
minimum_ = std::min(minimum_, value);
maximum_ = std::max(maximum_, value);
}
int64_t minimum() const { return minimum_; }
int64_t maximum() const { return maximum_; }
private:
int64_t minimum_;
int64_t maximum_;
};
} // namespace
void InFlightBytesTracker::AddInFlightPacketBytes(
const PacketFeedback& packet) {
RTC_DCHECK(packet.sent.send_time.IsFinite());
auto it = in_flight_data_.find(packet.network_route);
if (it != in_flight_data_.end()) {
it->second += packet.sent.size;
} else {
in_flight_data_.insert({packet.network_route, packet.sent.size});
}
}
void InFlightBytesTracker::RemoveInFlightPacketBytes(
const PacketFeedback& packet) {
if (packet.sent.send_time.IsInfinite())
return;
auto it = in_flight_data_.find(packet.network_route);
if (it != in_flight_data_.end()) {
RTC_DCHECK_GE(it->second, packet.sent.size);
it->second -= packet.sent.size;
if (it->second.IsZero())
in_flight_data_.erase(it);
}
}
DataSize InFlightBytesTracker::GetOutstandingData(
const NetworkRoute& network_route) const {
auto it = in_flight_data_.find(network_route);
if (it != in_flight_data_.end()) {
return it->second;
} else {
return DataSize::Zero();
}
}
// Comparator for consistent map with NetworkRoute as key.
bool InFlightBytesTracker::NetworkRouteComparator::operator()(
const NetworkRoute& a,
const NetworkRoute& b) const {
if (a.local.network_id() != b.local.network_id())
return a.local.network_id() < b.local.network_id();
if (a.remote.network_id() != b.remote.network_id())
return a.remote.network_id() < b.remote.network_id();
if (a.local.adapter_id() != b.local.adapter_id())
return a.local.adapter_id() < b.local.adapter_id();
if (a.remote.adapter_id() != b.remote.adapter_id())
return a.remote.adapter_id() < b.remote.adapter_id();
if (a.local.uses_turn() != b.local.uses_turn())
return a.local.uses_turn() < b.local.uses_turn();
if (a.remote.uses_turn() != b.remote.uses_turn())
return a.remote.uses_turn() < b.remote.uses_turn();
return a.connected < b.connected;
}
TransportFeedbackAdapter::TransportFeedbackAdapter() = default;
void TransportFeedbackAdapter::AddPacket(const RtpPacketToSend& packet_to_send,
const PacedPacketInfo& pacing_info,
size_t overhead_bytes,
Timestamp creation_time) {
RTC_DCHECK(packet_to_send.transport_sequence_number());
PacketFeedback feedback;
feedback.creation_time = creation_time;
// Note, if transport sequence number header extension is used, transport
// sequence numbers are wrapped to 16 bit. See
// RtpSenderEgress::CompleteSendPacket.
feedback.sent.sequence_number = seq_num_unwrapper_.Unwrap(
packet_to_send.transport_sequence_number().value_or(0));
feedback.sent.size = DataSize::Bytes(packet_to_send.size() + overhead_bytes);
feedback.sent.audio =
packet_to_send.packet_type() == RtpPacketMediaType::kAudio;
feedback.network_route = network_route_;
feedback.sent.pacing_info = pacing_info;
feedback.ssrc = packet_to_send.Ssrc();
feedback.rtp_sequence_number = packet_to_send.SequenceNumber();
feedback.is_retransmission =
packet_to_send.packet_type() == RtpPacketMediaType::kRetransmission;
feedback.sent_with_ect1 = packet_to_send.send_as_ect1();
while (!history_.empty() &&
creation_time - history_.begin()->second.creation_time >
kSendTimeHistoryWindow) {
// TODO(sprang): Warn if erasing (too many) old items?
if (history_.begin()->second.sent.sequence_number > last_ack_seq_num_)
in_flight_.RemoveInFlightPacketBytes(history_.begin()->second);
const PacketFeedback& packet = history_.begin()->second;
rtp_to_transport_sequence_number_.erase(
{.ssrc = packet.ssrc,
.rtp_sequence_number = packet.rtp_sequence_number});
history_.erase(history_.begin());
}
// Note that it can happen that the same SSRC and sequence number is sent
// again, e.g., audio retransmission. Delay metrics from such packets are
// ambiguous, since it is not specified which of the two packets was
// received.
auto [it, inserted] = rtp_to_transport_sequence_number_.emplace(
SsrcAndRtpSequencenumber(
{.ssrc = feedback.ssrc,
.rtp_sequence_number = feedback.rtp_sequence_number}),
feedback.sent.sequence_number);
if (!inserted) {
feedback.ambiguous_receive_time = true;
auto prev_it = history_.find(it->second);
if (prev_it != history_.end()) {
prev_it->second.ambiguous_receive_time = true;
}
} else if (feedback.is_retransmission &&
(!packet_to_send.original_ssrc().has_value() ||
packet_to_send.original_ssrc() == packet_to_send.Ssrc())) {
// A retransmission without RTX (same SSRC and sequence number) whose
// original packet is no longer in `rtp_to_transport_sequence_number_`
// (e.g., the original packet was already acknowledged and removed from
// history).
feedback.ambiguous_receive_time = true;
}
history_.emplace(feedback.sent.sequence_number, feedback);
}
std::optional<SentPacket> TransportFeedbackAdapter::ProcessSentPacket(
const SentPacketInfo& sent_packet) {
auto send_time = Timestamp::Millis(sent_packet.send_time_ms);
// TODO(srte): Only use one way to indicate that packet feedback is used.
if (sent_packet.info.included_in_feedback || sent_packet.packet_id != -1) {
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 = send_time;
last_send_time_ = std::max(last_send_time_, send_time);
// TODO(srte): Don't do this on retransmit.
if (!pending_untracked_size_.IsZero()) {
if (send_time < last_untracked_send_time_)
RTC_LOG(LS_WARNING)
<< "appending acknowledged data for out of order packet. (Diff: "
<< ToString(last_untracked_send_time_ - send_time) << " ms.)";
it->second.sent.prior_unacked_data += pending_untracked_size_;
pending_untracked_size_ = DataSize::Zero();
}
if (!packet_retransmit) {
if (it->second.sent.sequence_number > last_ack_seq_num_)
in_flight_.AddInFlightPacketBytes(it->second);
it->second.sent.data_in_flight = GetOutstandingData();
return it->second.sent;
}
}
} else if (sent_packet.info.included_in_allocation) {
if (send_time < last_send_time_) {
RTC_LOG(LS_WARNING) << "ignoring untracked data for out of order packet.";
}
pending_untracked_size_ +=
DataSize::Bytes(sent_packet.info.packet_size_bytes);
last_untracked_send_time_ = std::max(last_untracked_send_time_, send_time);
}
return std::nullopt;
}
std::optional<TransportPacketsFeedback>
TransportFeedbackAdapter::ProcessTransportFeedback(
const rtcp::TransportFeedback& feedback,
Timestamp feedback_receive_time) {
if (feedback.GetPacketStatusCount() == 0) {
RTC_LOG(LS_INFO) << "Empty transport feedback packet received.";
return std::nullopt;
}
// Add timestamp deltas to a local time base selected on first packet arrival.
// This won't be the true time base, but makes it easier to manually inspect
// time stamps.
if (last_transport_feedback_base_time_.IsInfinite()) {
current_offset_ = feedback_receive_time;
} else {
// TODO(srte): We shouldn't need to do rounding here.
const TimeDelta delta =
feedback.GetBaseDelta(last_transport_feedback_base_time_)
.RoundDownTo(TimeDelta::Millis(1));
// Protect against assigning current_offset_ negative value.
if (delta < Timestamp::Zero() - current_offset_) {
RTC_LOG(LS_WARNING) << "Unexpected feedback timestamp received.";
current_offset_ = feedback_receive_time;
} else {
current_offset_ += delta;
}
}
last_transport_feedback_base_time_ = feedback.BaseTime();
std::vector<PacketResult> packet_result_vector;
packet_result_vector.reserve(feedback.GetPacketStatusCount());
size_t failed_lookups = 0;
size_t ignored = 0;
feedback.ForAllPackets([&](uint16_t sequence_number,
TimeDelta delta_since_base) {
int64_t seq_num = seq_num_unwrapper_.Unwrap(sequence_number);
std::optional<PacketFeedback> packet_feedback = RetrievePacketFeedback(
seq_num, /*received=*/delta_since_base.IsFinite());
if (!packet_feedback) {
++failed_lookups;
return;
}
if (packet_feedback->network_route == network_route_) {
PacketResult result;
result.sent_packet = packet_feedback->sent;
if (delta_since_base.IsFinite()) {
result.receive_time = current_offset_ + delta_since_base.RoundDownTo(
TimeDelta::Millis(1));
}
result.rtp_packet_info = {
.ssrc = packet_feedback->ssrc,
.rtp_sequence_number = packet_feedback->rtp_sequence_number,
.is_retransmission = packet_feedback->is_retransmission};
result.ambiguous_receive_time = packet_feedback->ambiguous_receive_time;
packet_result_vector.push_back(result);
} else {
++ignored;
}
});
if (failed_lookups > 0) {
RTC_LOG(LS_WARNING)
<< "Failed to lookup send time for " << failed_lookups << " packet"
<< (failed_lookups > 1 ? "s" : "")
<< ". Packets reordered or send time history too small?";
}
if (ignored > 0) {
RTC_LOG(LS_INFO) << "Ignoring " << ignored
<< " packets because they were sent on a different route.";
}
return ToTransportFeedback(std::move(packet_result_vector),
feedback_receive_time, /*supports_ecn=*/false);
}
std::optional<TransportPacketsFeedback>
TransportFeedbackAdapter::ProcessCongestionControlFeedback(
const rtcp::CongestionControlFeedback& feedback,
Timestamp feedback_receive_time) {
if (feedback.packets().empty()) {
RTC_LOG(LS_INFO) << "Empty congestion control feedback packet received.";
return std::nullopt;
}
if (current_offset_.IsInfinite()) {
current_offset_ = feedback_receive_time;
}
TimeDelta feedback_delta = last_feedback_compact_ntp_time_
? CompactNtpIntervalToTimeDelta(
feedback.report_timestamp_compact_ntp() -
*last_feedback_compact_ntp_time_)
: TimeDelta::Zero();
last_feedback_compact_ntp_time_ = feedback.report_timestamp_compact_ntp();
if (feedback_delta < TimeDelta::Seconds(-5)) {
RTC_LOG(LS_WARNING) << "Unexpected feedback ntp time delta "
<< feedback_delta << ".";
current_offset_ = feedback_receive_time;
} else {
current_offset_ += feedback_delta;
}
int ignored_packets = 0;
int failed_lookups = 0;
bool unexpected_arrival_time_offset = false;
bool supports_ecn = true;
std::vector<PacketResult> packet_result_vector;
std::optional<MinMax> sequence_number_in_report;
for (const rtcp::CongestionControlFeedback::PacketInfo& packet_info :
feedback.packets()) {
std::optional<PacketFeedback> packet_feedback = RetrievePacketFeedback(
{.ssrc = packet_info.ssrc,
.rtp_sequence_number = packet_info.sequence_number},
/*received=*/packet_info.arrival_time_offset.IsFinite());
if (!packet_feedback) {
++failed_lookups;
continue;
}
if (packet_feedback->network_route != network_route_) {
++ignored_packets;
continue;
}
if (!sequence_number_in_report.has_value()) {
sequence_number_in_report.emplace(packet_feedback->sent.sequence_number);
} else {
sequence_number_in_report->Update(packet_feedback->sent.sequence_number);
}
PacketResult result;
result.sent_packet = packet_feedback->sent;
if (packet_info.arrival_time_offset.IsFinite()) {
// Arrival time offset (ATO) is expected to be bounded by:
// 1. The interval between this feedback and the last received feedback
// (using the receiver's NTP report timestamps). Under RFC 8888,
// received packets must be reported. Since previously acknowledged
// packets are erased from `history_`, any packet newly acknowledged
// here must have arrived after the previous received feedback was
// generated.
// 2. The round-trip elapsed time between packet send and feedback
// receive.
// A packet cannot arrive before it was sent, so the time the receiver
// held the packet (ATO) cannot exceed the total round-trip duration.
TimeDelta time_between_two_last_feedback =
feedback_delta > TimeDelta::Zero()
? feedback_delta + TimeDelta::Millis(1)
: TimeDelta::PlusInfinity();
TimeDelta feedback_send_time_delta =
std::max(feedback_receive_time - packet_feedback->sent.send_time,
TimeDelta::Millis(0));
TimeDelta max_expected_ato =
std::min(time_between_two_last_feedback, feedback_send_time_delta);
TimeDelta arrival_time_offset = packet_info.arrival_time_offset;
if (arrival_time_offset > max_expected_ato) {
RTC_LOG_IF(LS_WARNING, !unexpected_arrival_time_offset)
<< "Arrival time offset " << packet_info.arrival_time_offset
<< " exceeds max expected ATO " << max_expected_ato
<< " (feedback_send_time_delta: " << feedback_send_time_delta
<< ", time_between_two_last_feedback: "
<< time_between_two_last_feedback << ")";
unexpected_arrival_time_offset = true;
arrival_time_offset = max_expected_ato;
}
result.receive_time = current_offset_ - arrival_time_offset;
// Note that this deliberately does not look at how the packet was sent,
// in order to keep the deprecated `transport_supports_ecn` bit for bit
// compatible. Use `HasPacketWithBleachedEct1()` instead.
supports_ecn &= packet_info.ecn != EcnMarking::kNotEct;
result.arrival_time_offset = arrival_time_offset;
}
result.ambiguous_receive_time = packet_feedback->ambiguous_receive_time;
result.ecn = packet_info.ecn;
result.sent_with_ect1 = packet_feedback->sent_with_ect1;
result.reported_recovered_for_the_first_time =
packet_info.received() && packet_feedback->previously_reported_lost;
result.reported_lost_for_the_first_time =
!packet_info.received() && !packet_feedback->previously_reported_lost;
result.rtp_packet_info = {
.ssrc = packet_feedback->ssrc,
.rtp_sequence_number = packet_feedback->rtp_sequence_number,
.is_retransmission = packet_feedback->is_retransmission};
packet_result_vector.push_back(result);
}
// Report packets as lost that are in the same transport sequence number range
// as reported packets, but that are not mentioned in the report.
// Code below relies on the fact that received packets are already erased from
// the `history_`.
if (sequence_number_in_report.has_value()) {
auto begin = history_.lower_bound(sequence_number_in_report->minimum());
auto end = history_.upper_bound(sequence_number_in_report->maximum());
for (auto it = begin; it != end; ++it) {
PacketFeedback& packet_feedback = it->second;
if (it->second.previously_reported_lost) {
continue;
}
it->second.previously_reported_lost = true;
PacketResult result;
result.sent_packet = packet_feedback.sent;
result.sent_with_ect1 = packet_feedback.sent_with_ect1;
result.reported_lost_for_the_first_time = true;
result.rtp_packet_info = {
.ssrc = packet_feedback.ssrc,
.rtp_sequence_number = packet_feedback.rtp_sequence_number,
.is_retransmission = packet_feedback.is_retransmission};
packet_result_vector.push_back(result);
}
}
if (failed_lookups > 0) {
RTC_LOG(LS_INFO) << "Failed to lookup send time for " << failed_lookups
<< " packet" << (failed_lookups > 1 ? "s" : "")
<< ". Packets reordered or send time history too "
"small, or packet reported received more than once.";
}
if (ignored_packets > 0) {
RTC_LOG(LS_INFO) << "Ignoring " << ignored_packets
<< " packets because they were sent on a different route.";
}
// Feedback is expected to be sorted in send order.
absl::c_sort(packet_result_vector, [](const PacketResult& lhs,
const PacketResult& rhs) {
return lhs.sent_packet.sequence_number < rhs.sent_packet.sequence_number;
});
return ToTransportFeedback(std::move(packet_result_vector),
feedback_receive_time, supports_ecn);
}
std::optional<TransportPacketsFeedback>
TransportFeedbackAdapter::ToTransportFeedback(
std::vector<PacketResult> packet_results,
Timestamp feedback_receive_time,
bool supports_ecn) {
TransportPacketsFeedback msg;
msg.feedback_time = feedback_receive_time;
if (packet_results.empty()) {
return std::nullopt;
}
msg.packet_feedbacks = std::move(packet_results);
msg.data_in_flight = in_flight_.GetOutstandingData(network_route_);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
msg.transport_supports_ecn = supports_ecn;
#pragma clang diagnostic pop
return msg;
}
void TransportFeedbackAdapter::SetNetworkRoute(
const NetworkRoute& network_route) {
network_route_ = network_route;
}
DataSize TransportFeedbackAdapter::GetOutstandingData() const {
return in_flight_.GetOutstandingData(network_route_);
}
std::optional<PacketFeedback> TransportFeedbackAdapter::RetrievePacketFeedback(
const SsrcAndRtpSequencenumber& key,
bool received) {
auto it = rtp_to_transport_sequence_number_.find(key);
if (it == rtp_to_transport_sequence_number_.end()) {
return std::nullopt;
}
return RetrievePacketFeedback(it->second, received);
}
std::optional<PacketFeedback> TransportFeedbackAdapter::RetrievePacketFeedback(
int64_t transport_seq_num,
bool received) {
if (transport_seq_num > last_ack_seq_num_) {
// Starts at history_.begin() if last_ack_seq_num_ < 0, since any
// valid sequence number is >= 0.
for (auto it = history_.upper_bound(last_ack_seq_num_);
it != history_.upper_bound(transport_seq_num); ++it) {
in_flight_.RemoveInFlightPacketBytes(it->second);
}
last_ack_seq_num_ = transport_seq_num;
}
auto it = history_.find(transport_seq_num);
if (it == history_.end()) {
RTC_LOG(LS_INFO) << "Failed to lookup send time for packet with "
<< transport_seq_num
<< ". Send time history too small or packet was "
"reported as received twice.";
return std::nullopt;
}
if (it->second.sent.send_time.IsInfinite()) {
// TODO(srte): Fix the tests that makes this happen and make this a
// DCHECK.
RTC_DLOG(LS_ERROR)
<< "Received feedback before packet was indicated as sent";
return std::nullopt;
}
PacketFeedback packet_feedback = it->second;
if (received) {
// Note: Lost packets are not removed from history because they might
// be reported as received by a later feedback.
rtp_to_transport_sequence_number_.erase(
{.ssrc = packet_feedback.ssrc,
.rtp_sequence_number = packet_feedback.rtp_sequence_number});
history_.erase(it);
} else {
// Update `previously_reported_lost` after taking copy of the
// `packet_feedback'. Thus the new value would be returned next time caller
// retrieves the feedback. This way caller can distinguish when packet was
// reported lost for the first time and thus account such loss only once.
it->second.previously_reported_lost = true;
}
return packet_feedback;
}
} // namespace webrtc