/* * 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 #include #include #include #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 ssrc; // If absent, the sequence number defaults to the previous sequence number // plus one. std::optional sequence_number; bool received = true; }; rtcp::CongestionControlFeedback GenerateFeedback( std::vector packets) { std::vector 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 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