blob: 3e07614c36f79d3a1e3d76af935f0eda0c4e566b [file] [log] [blame]
/*
* Copyright (c) 2025 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 "call/rtp_transport_controller_send.h"
#include <cstdint>
#include <optional>
#include <utility>
#include <vector>
#include "api/rtp_parameters.h"
#include "api/transport/ecn_marking.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "modules/congestion_controller/rtp/congestion_controller_feedback_stats.h"
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
#include "modules/rtp_rtcp/source/rtcp_packet/congestion_control_feedback.h"
#include "rtc_base/containers/flat_map.h"
#include "rtc_base/thread.h"
#include "test/create_test_environment.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
using ::testing::AllOf;
using ::testing::Eq;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::UnorderedElementsAre;
constexpr uint32_t kSsrc = 0x554c;
class PacketSender {
public:
explicit PacketSender(RtpTransportControllerSend& transport)
: transport_(transport) {}
struct SentPacketsOptions {
uint32_t ssrc = kSsrc;
uint16_t first_sequence_number = 1;
int num_packets = 1;
bool send_as_ect1 = true;
};
void SimulateSentPackets(SentPacketsOptions options) {
uint16_t sequence_number = options.first_sequence_number;
for (int i = 0; i < options.num_packets; ++i, ++sequence_number) {
RtpPacketToSend rtp_packet(nullptr);
rtp_packet.SetSsrc(options.ssrc);
rtp_packet.SetSequenceNumber(sequence_number);
rtp_packet.set_transport_sequence_number(++transport_sequence_number_);
rtp_packet.set_packet_type(RtpPacketMediaType::kVideo);
if (options.send_as_ect1) {
rtp_packet.set_send_as_ect1();
}
rtp_packet.SetPayloadSize(100);
transport_.NotifyBweOfSentPacketForTesting(rtp_packet);
}
}
private:
int64_t transport_sequence_number_ = 0;
RtpTransportControllerSend& transport_;
};
struct FeedbakPacketTemplate {
EcnMarking ecn = EcnMarking::kNotEct;
// If absent, the SSRC defaults to the previous SSRC.
std::optional<uint32_t> ssrc;
// If absent, the sequence number defaults to the previous sequence number
// plus one.
std::optional<uint16_t> sequence_number;
bool received = true;
};
rtcp::CongestionControlFeedback GenerateFeedback(
std::vector<FeedbakPacketTemplate> packets) {
std::vector<rtcp::CongestionControlFeedback::PacketInfo> packet_infos;
packet_infos.reserve(packets.size());
uint32_t ssrc = kSsrc;
uint16_t sequence_number = 1;
for (const FeedbakPacketTemplate& p : packets) {
ssrc = p.ssrc.value_or(ssrc);
sequence_number = p.sequence_number.value_or(sequence_number + 1);
packet_infos.push_back(
{.ssrc = ssrc,
.sequence_number = sequence_number,
.arrival_time_offset =
p.received ? TimeDelta::Millis(10) : TimeDelta::MinusInfinity(),
.ecn = p.ecn});
}
return rtcp::CongestionControlFeedback(std::move(packet_infos),
/*report_timestamp_compact_ntp=*/0);
}
TEST(RtpTransportControllerSendTest,
IgnoresFeedbackForReportedReceivedPacketThatWereNotSent) {
AutoThread main_thread;
RtpTransportControllerSend transport({.env = CreateTestEnvironment()});
transport.SetPreferredRtcpCcAckType(RtcpFeedbackType::CCFB);
PacketSender sender(transport);
sender.SimulateSentPackets({.ssrc = 123,
.first_sequence_number = 111,
.num_packets = 10,
.send_as_ect1 = true});
sender.SimulateSentPackets({.ssrc = 321,
.first_sequence_number = 10'111,
.num_packets = 10,
.send_as_ect1 = true});
// Generate feedback for packets that weren't sent: reuse sequence number
// range from 1st batch, but SSRC from the 2nd batch to double check sequence
// numbers are checked per SSRC.
transport.OnCongestionControlFeedback(
/*receive_time=*/Timestamp::Seconds(123),
GenerateFeedback(
{{.ecn = EcnMarking::kEct1, .ssrc = 321, .sequence_number = 111},
{.ecn = EcnMarking::kEct1},
{.ecn = EcnMarking::kCe}}));
EXPECT_THAT(transport.GetCongestionControlFeedbackStatsPerSsrc(), IsEmpty());
}
TEST(RtpTransportControllerSendTest,
AccumulatesNumberOfReportedReceivedPacketsPerSsrcPerEcnMarkingType) {
constexpr uint32_t kSsrc1 = 1'000;
constexpr uint32_t kSsrc2 = 2'000;
AutoThread main_thread;
RtpTransportControllerSend transport({.env = CreateTestEnvironment()});
transport.SetPreferredRtcpCcAckType(RtcpFeedbackType::CCFB);
PacketSender sender(transport);
sender.SimulateSentPackets(
{.ssrc = kSsrc1, .first_sequence_number = 1, .num_packets = 10});
sender.SimulateSentPackets(
{.ssrc = kSsrc2, .first_sequence_number = 101, .num_packets = 10});
transport.OnCongestionControlFeedback(
/*receive_time=*/Timestamp::Seconds(123),
GenerateFeedback(
{{.ecn = EcnMarking::kEct1, .ssrc = kSsrc1, .sequence_number = 1},
{.ecn = EcnMarking::kEct1},
{.ecn = EcnMarking::kCe},
{.ecn = EcnMarking::kEct1, .ssrc = kSsrc2, .sequence_number = 101},
{.ecn = EcnMarking::kEct1},
{.ecn = EcnMarking::kEct1},
{.ecn = EcnMarking::kCe},
{.ecn = EcnMarking::kCe},
{.ecn = EcnMarking::kCe},
{.ecn = EcnMarking::kCe}}));
flat_map<uint32_t, ReceivedCongestionControlFeedbackStats> stats =
transport.GetCongestionControlFeedbackStatsPerSsrc();
EXPECT_EQ(stats[kSsrc1].num_packets_received_with_ect1, 2);
EXPECT_EQ(stats[kSsrc1].num_packets_received_with_ce, 1);
EXPECT_EQ(stats[kSsrc2].num_packets_received_with_ect1, 3);
EXPECT_EQ(stats[kSsrc2].num_packets_received_with_ce, 4);
transport.OnCongestionControlFeedback(
/*receive_time=*/Timestamp::Seconds(123),
GenerateFeedback(
{{.ecn = EcnMarking::kEct1, .ssrc = kSsrc1, .sequence_number = 5},
{.ecn = EcnMarking::kEct1},
{.ecn = EcnMarking::kEct1},
{.ecn = EcnMarking::kCe}}));
stats = transport.GetCongestionControlFeedbackStatsPerSsrc();
EXPECT_EQ(stats[kSsrc1].num_packets_received_with_ect1, 2 + 3);
EXPECT_EQ(stats[kSsrc1].num_packets_received_with_ce, 1 + 1);
}
TEST(RtpTransportControllerSendTest, CalculatesNumberOfBleachedPackets) {
AutoThread main_thread;
RtpTransportControllerSend transport({.env = CreateTestEnvironment()});
transport.SetPreferredRtcpCcAckType(RtcpFeedbackType::CCFB);
PacketSender sender(transport);
// Packets send as ect1 and received without ect1 are the bleached packets.
sender.SimulateSentPackets(
{.first_sequence_number = 1, .num_packets = 10, .send_as_ect1 = true});
transport.OnCongestionControlFeedback(
/*receive_time=*/Timestamp::Seconds(123),
GenerateFeedback({{.ecn = EcnMarking::kNotEct, .sequence_number = 1},
{.ecn = EcnMarking::kNotEct},
{.ecn = EcnMarking::kEct1},
{.ecn = EcnMarking::kEct1},
{.ecn = EcnMarking::kEct1},
{.ecn = EcnMarking::kCe}}));
EXPECT_EQ(transport.GetCongestionControlFeedbackStatsPerSsrc()[kSsrc]
.num_packets_with_bleached_ect1_marking,
2);
// Packets not send as ect1 do not add to number of bleached packets.
sender.SimulateSentPackets(
{.first_sequence_number = 11, .num_packets = 10, .send_as_ect1 = false});
transport.OnCongestionControlFeedback(
/*receive_time=*/Timestamp::Seconds(123),
GenerateFeedback({{.ecn = EcnMarking::kNotEct, .sequence_number = 11},
{.ecn = EcnMarking::kNotEct},
{.ecn = EcnMarking::kNotEct}}));
EXPECT_EQ(transport.GetCongestionControlFeedbackStatsPerSsrc()[kSsrc]
.num_packets_with_bleached_ect1_marking,
2 + 0);
}
TEST(RtpTransportControllerSendTest,
AccumulatesNumberOfReportedLostAndRecoveredPackets) {
AutoThread main_thread;
RtpTransportControllerSend transport({.env = CreateTestEnvironment()});
transport.SetPreferredRtcpCcAckType(RtcpFeedbackType::CCFB);
PacketSender sender(transport);
sender.SimulateSentPackets({.first_sequence_number = 1, .num_packets = 30});
// Produce 1st report with 2 received and 3 lost packets.
transport.OnCongestionControlFeedback(
/*receive_time=*/Timestamp::Seconds(123),
GenerateFeedback({{.sequence_number = 1, .received = true},
{.sequence_number = 2, .received = false},
{.sequence_number = 3, .received = false},
{.sequence_number = 4, .received = false},
{.sequence_number = 5, .received = true}}));
ReceivedCongestionControlFeedbackStats stats =
transport.GetCongestionControlFeedbackStatsPerSsrc()[kSsrc];
EXPECT_EQ(stats.num_packets_reported_as_lost, 3);
EXPECT_EQ(stats.num_packets_reported_as_lost_but_recovered, 0);
// Produce 2nd report with 1 packet recovered, 1 old packet reported still
// lost, and 2 new packets lost.
transport.OnCongestionControlFeedback(
/*receive_time=*/Timestamp::Seconds(123),
GenerateFeedback({{.sequence_number = 3, .received = true},
{.sequence_number = 4, .received = false},
{.sequence_number = 5, .received = true},
{.sequence_number = 6, .received = false},
{.sequence_number = 7, .received = false},
{.sequence_number = 8, .received = true}}));
stats = transport.GetCongestionControlFeedbackStatsPerSsrc()[kSsrc];
EXPECT_EQ(stats.num_packets_reported_as_lost, 3 + 2);
EXPECT_EQ(stats.num_packets_reported_as_lost_but_recovered, 1);
// Produce 3rd report with 2 more packets recovered.
transport.OnCongestionControlFeedback(
/*receive_time=*/Timestamp::Seconds(123),
GenerateFeedback({{.sequence_number = 6, .received = true},
{.sequence_number = 7, .received = true},
{.sequence_number = 8, .received = true}}));
stats = transport.GetCongestionControlFeedbackStatsPerSsrc()[kSsrc];
EXPECT_EQ(stats.num_packets_reported_as_lost_but_recovered, 1 + 2);
}
TEST(RtpTransportControllerSendTest,
DoesNotCountGapsInSequenceNumberBetweenReportsAsLoss) {
AutoThread main_thread;
RtpTransportControllerSend transport({.env = CreateTestEnvironment()});
transport.SetPreferredRtcpCcAckType(RtcpFeedbackType::CCFB);
PacketSender sender(transport);
sender.SimulateSentPackets({.first_sequence_number = 1, .num_packets = 30});
// Produce two report with a sequence number gap between them.
transport.OnCongestionControlFeedback(
/*receive_time=*/Timestamp::Seconds(123),
GenerateFeedback({{.sequence_number = 1}}));
transport.OnCongestionControlFeedback(
/*receive_time=*/Timestamp::Seconds(123),
GenerateFeedback({{.sequence_number = 5}}));
// It is unclear if packets 2-4 weren't received and thus were excluded from
// the feedback report, or report about these packets was itself lost.
// Such packets are not counted as loss.
EXPECT_EQ(transport.GetCongestionControlFeedbackStatsPerSsrc()[kSsrc]
.num_packets_reported_as_lost,
0);
// Only count losses explicitly marked as such in a report to align with
// the metric definition "report has been sent with a zero R bit"
transport.OnCongestionControlFeedback(
/*receive_time=*/Timestamp::Seconds(123),
GenerateFeedback({{.sequence_number = 3, .received = true},
{.sequence_number = 4, .received = false},
{.sequence_number = 5, .received = true}}));
EXPECT_EQ(transport.GetCongestionControlFeedbackStatsPerSsrc()[kSsrc]
.num_packets_reported_as_lost,
1);
}
} // namespace
} // namespace webrtc