blob: c03cefd779ee65759da981654e0a4a90c4c62e1b [file] [edit]
/*
* Copyright 2026 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/scream/scream_feedback.h"
#include <algorithm>
#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/congestion_controller/scream/scream_v2_parameters.h"
#include "rtc_base/checks.h"
namespace webrtc {
ScreamFeedback ParseScreamFeedback(const TransportPacketsFeedback& msg) {
return ParseScreamFeedback(msg, ScreamV2Parameters());
}
ScreamFeedback ParseScreamFeedback(const TransportPacketsFeedback& msg,
const ScreamV2Parameters& params) {
RTC_DCHECK_LE(params.burst_window_min.Get(), params.burst_window_max.Get());
ScreamFeedback parsed;
parsed.feedback_time = msg.feedback_time;
parsed.data_in_flight = msg.data_in_flight;
const PacketResult* first_packet = nullptr;
const PacketResult* last_packet = nullptr;
PacketResult::ReceiveTimeOrder order;
for (const PacketResult& packet : msg.packet_feedbacks) {
// Sum size of all packets in feedback that are not CE marked (includes
// lost).
if (packet.ecn != EcnMarking::kCe) {
parsed.acked_not_marked_size += packet.sent_packet.size;
}
// Record loss and recovery events.
bool is_lost =
!packet.IsReceived() && packet.reported_lost_for_the_first_time;
bool is_recovered = packet.reported_recovered_for_the_first_time;
if (is_lost) {
parsed.num_lost_packets++;
} else if (is_recovered) {
parsed.num_recovered_packets++;
}
// Update received-packet metrics.
if (packet.IsReceived()) {
parsed.received += packet.sent_packet.size;
parsed.num_received_packets++;
if (packet.ecn == EcnMarking::kCe) {
parsed.num_ce_marked_packets++;
}
// Replicate exact ReceiveTimeOrder tie-breaking logic to find first &
// last packets among packets with unambiguous receive times.
if (!packet.ambiguous_receive_time) {
if (!first_packet || order(packet, *first_packet)) {
first_packet = &packet;
}
if (!last_packet || order(*last_packet, packet)) {
last_packet = &packet;
}
}
}
}
// Calculate min and max delay for packets sent within a window of the
// latest received packet. Feedback is typically received every ~25ms, but
// can span significantly longer (e.g. 100-250ms) at low packet rates,
// such as audio-only or application-limited periods. Earlier packets in
// such sparse feedback are excluded to prevent natural route jitter over
// long intervals from being falsely interpreted as burst queueing delay,
// and to avoid lag in queue delay estimation.
Timestamp latest_send_time = Timestamp::MinusInfinity();
Timestamp newer_send_time = Timestamp::MinusInfinity();
TimeDelta min_one_way_delay = TimeDelta::PlusInfinity();
TimeDelta max_one_way_delay = TimeDelta::MinusInfinity();
for (auto it = msg.packet_feedbacks.rbegin();
it != msg.packet_feedbacks.rend(); ++it) {
const PacketResult& packet = *it;
if (!packet.IsReceived() || packet.ambiguous_receive_time) {
continue;
}
if (!latest_send_time.IsFinite()) {
latest_send_time = packet.sent_packet.send_time;
newer_send_time = packet.sent_packet.send_time;
}
TimeDelta delta_from_latest =
latest_send_time - packet.sent_packet.send_time;
TimeDelta gap = newer_send_time - packet.sent_packet.send_time;
if (delta_from_latest <= params.burst_window_min.Get() ||
(delta_from_latest <= params.burst_window_max.Get() &&
gap <= params.burst_window_max_gap.Get())) {
TimeDelta one_way_delay =
packet.receive_time - packet.sent_packet.send_time;
min_one_way_delay = std::min(min_one_way_delay, one_way_delay);
max_one_way_delay = std::max(max_one_way_delay, one_way_delay);
newer_send_time = packet.sent_packet.send_time;
} else {
break;
}
}
if (last_packet != nullptr) {
RTC_DCHECK(first_packet != nullptr);
RTC_DCHECK(min_one_way_delay.IsFinite());
RTC_DCHECK(max_one_way_delay.IsFinite());
TimeDelta feedback_hold_time =
last_packet->receive_time +
last_packet->arrival_time_offset.value_or(TimeDelta::Zero()) -
first_packet->receive_time;
TimeDelta rtt_sample = std::max(
msg.feedback_time - last_packet->sent_packet.send_time -
last_packet->arrival_time_offset.value_or(TimeDelta::Zero()),
TimeDelta::Zero());
parsed.delay_metrics = ScreamFeedback::DelayMetrics{
.min_one_way_delay = min_one_way_delay,
.max_one_way_delay = max_one_way_delay,
.feedback_hold_time = feedback_hold_time,
.rtt_sample = rtt_sample,
.last_packet_receive_time = last_packet->receive_time,
};
}
return parsed;
}
} // namespace webrtc