| /* | |
| * Copyright (c) 2012 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/rtp_sender.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/strings/string_view.h" | |
| #include "api/environment/environment.h" | |
| #include "api/environment/environment_factory.h" | |
| #include "api/rtp_packet_sender.h" | |
| #include "api/rtp_parameters.h" | |
| #include "api/units/frequency.h" | |
| #include "api/units/time_delta.h" | |
| #include "api/units/timestamp.h" | |
| #include "api/video/video_codec_type.h" | |
| #include "api/video/video_frame_type.h" | |
| #include "modules/rtp_rtcp/include/flexfec_sender.h" | |
| #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" | |
| #include "modules/rtp_rtcp/source/packet_sequencer.h" | |
| #include "modules/rtp_rtcp/source/rtp_format_video_generic.h" | |
| #include "modules/rtp_rtcp/source/rtp_header_extension_size.h" | |
| #include "modules/rtp_rtcp/source/rtp_header_extensions.h" | |
| #include "modules/rtp_rtcp/source/rtp_packet_history.h" | |
| #include "modules/rtp_rtcp/source/rtp_packet_to_send.h" | |
| #include "modules/rtp_rtcp/source/rtp_rtcp_interface.h" | |
| #include "modules/rtp_rtcp/source/rtp_sender_video.h" | |
| #include "modules/rtp_rtcp/source/video_fec_generator.h" | |
| #include "rtc_base/rate_limiter.h" | |
| #include "test/gmock.h" | |
| #include "test/gtest.h" | |
| #include "test/time_controller/simulated_time_controller.h" | |
| namespace webrtc { | |
| namespace { | |
| enum : int { // The first valid value is 1. | |
| kAbsoluteSendTimeExtensionId = 1, | |
| kAudioLevelExtensionId, | |
| kGenericDescriptorId, | |
| kMidExtensionId, | |
| kRepairedRidExtensionId, | |
| kRidExtensionId, | |
| kTransmissionTimeOffsetExtensionId, | |
| kTransportSequenceNumberExtensionId, | |
| kVideoRotationExtensionId, | |
| kVideoTimingExtensionId, | |
| }; | |
| constexpr int kPayload = 100; | |
| constexpr int kRtxPayload = 98; | |
| constexpr uint32_t kTimestamp = 10; | |
| constexpr uint16_t kSeqNum = 33; | |
| constexpr uint32_t kSsrc = 725242; | |
| constexpr uint32_t kRtxSsrc = 12345; | |
| constexpr uint32_t kFlexFecSsrc = 45678; | |
| constexpr uint64_t kStartTime = 123456789; | |
| constexpr uint8_t kPayloadData[] = {47, 11, 32, 93, 89}; | |
| constexpr TimeDelta kDefaultExpectedRetransmissionTime = TimeDelta::Millis(125); | |
| constexpr Frequency kRtpClockRate = Frequency::Hertz(90'000); | |
| constexpr absl::string_view kMid = "mid"; | |
| constexpr absl::string_view kRid = "f"; | |
| constexpr bool kMarkerBit = true; | |
| using ::testing::AllOf; | |
| using ::testing::AtLeast; | |
| using ::testing::Each; | |
| using ::testing::ElementsAre; | |
| using ::testing::ElementsAreArray; | |
| using ::testing::Eq; | |
| using ::testing::Gt; | |
| using ::testing::IsEmpty; | |
| using ::testing::Not; | |
| using ::testing::Pointee; | |
| using ::testing::Property; | |
| using ::testing::Return; | |
| using ::testing::SizeIs; | |
| class MockRtpPacketPacer : public RtpPacketSender { | |
| public: | |
| MockRtpPacketPacer() {} | |
| ~MockRtpPacketPacer() override {} | |
| MOCK_METHOD(void, | |
| EnqueuePackets, | |
| (std::vector<std::unique_ptr<RtpPacketToSend>>), | |
| (override)); | |
| MOCK_METHOD(void, RemovePacketsForSsrc, (uint32_t), (override)); | |
| }; | |
| uint32_t ToRtpTimestamp(Timestamp time) { | |
| return static_cast<int64_t>((time - Timestamp::Zero()) * kRtpClockRate) & | |
| 0xFFFF'FFFF; | |
| } | |
| } // namespace | |
| class RtpSenderTest : public ::testing::Test { | |
| protected: | |
| RtpSenderTest() | |
| : time_controller_(Timestamp::Millis(kStartTime)), | |
| env_(CreateEnvironment(time_controller_.GetClock())), | |
| retransmission_rate_limiter_(&env_.clock(), 1000), | |
| flexfec_sender_(env_, | |
| 0, | |
| kFlexFecSsrc, | |
| kSsrc, | |
| "", | |
| std::vector<RtpExtension>(), | |
| std::vector<RtpExtensionSize>(), | |
| nullptr) {} | |
| void SetUp() override { SetUpRtpSender(true, false, nullptr); } | |
| void SetUpRtpSender(bool populate_network2, | |
| bool always_send_mid_and_rid, | |
| VideoFecGenerator* fec_generator) { | |
| RtpRtcpInterface::Configuration config = GetDefaultConfig(); | |
| config.fec_generator = fec_generator; | |
| config.populate_network2_timestamp = populate_network2; | |
| config.always_send_mid_and_rid = always_send_mid_and_rid; | |
| CreateSender(config); | |
| } | |
| RtpRtcpInterface::Configuration GetDefaultConfig() { | |
| RtpRtcpInterface::Configuration config; | |
| config.local_media_ssrc = kSsrc; | |
| config.rtx_send_ssrc = kRtxSsrc; | |
| config.retransmission_rate_limiter = &retransmission_rate_limiter_; | |
| config.paced_sender = &mock_paced_sender_; | |
| // Configure rid unconditionally, it has effect only if | |
| // corresponding header extension is enabled. | |
| config.rid = std::string(kRid); | |
| return config; | |
| } | |
| void CreateSender(const RtpRtcpInterface::Configuration& config) { | |
| packet_history_ = std::make_unique<RtpPacketHistory>( | |
| env_, RtpPacketHistory::PaddingMode::kRecentLargePacket); | |
| sequencer_.emplace(kSsrc, kRtxSsrc, | |
| /*require_marker_before_media_padding=*/!config.audio, | |
| &env_.clock()); | |
| rtp_sender_ = std::make_unique<RTPSender>( | |
| env_, config, packet_history_.get(), config.paced_sender); | |
| sequencer_->set_media_sequence_number(kSeqNum); | |
| rtp_sender_->SetTimestampOffset(0); | |
| } | |
| GlobalSimulatedTimeController time_controller_; | |
| const Environment env_; | |
| MockRtpPacketPacer mock_paced_sender_; | |
| RateLimiter retransmission_rate_limiter_; | |
| FlexfecSender flexfec_sender_; | |
| std::optional<PacketSequencer> sequencer_; | |
| std::unique_ptr<RtpPacketHistory> packet_history_; | |
| std::unique_ptr<RTPSender> rtp_sender_; | |
| std::unique_ptr<RtpPacketToSend> BuildRtpPacket(int payload_type, | |
| bool marker_bit, | |
| uint32_t rtp_timestamp, | |
| Timestamp capture_time) { | |
| auto packet = rtp_sender_->AllocatePacket(); | |
| packet->SetPayloadType(payload_type); | |
| packet->set_packet_type(RtpPacketMediaType::kVideo); | |
| packet->SetMarker(marker_bit); | |
| packet->SetTimestamp(rtp_timestamp); | |
| packet->set_capture_time(capture_time); | |
| return packet; | |
| } | |
| std::unique_ptr<RtpPacketToSend> SendPacket(Timestamp capture_time, | |
| int payload_length) { | |
| uint32_t rtp_timestamp = ToRtpTimestamp(capture_time); | |
| auto packet = | |
| BuildRtpPacket(kPayload, kMarkerBit, rtp_timestamp, capture_time); | |
| packet->AllocatePayload(payload_length); | |
| packet->set_allow_retransmission(true); | |
| // Packet should be stored in a send bucket. | |
| std::vector<std::unique_ptr<RtpPacketToSend>> packets(1); | |
| packets[0] = std::make_unique<RtpPacketToSend>(*packet); | |
| rtp_sender_->EnqueuePackets(std::move(packets)); | |
| return packet; | |
| } | |
| std::unique_ptr<RtpPacketToSend> SendGenericPacket() { | |
| // Use maximum allowed size to catch corner cases when packet is dropped | |
| // because of lack of capacity for the media packet, or for an rtx packet | |
| // containing the media packet. | |
| return SendPacket(/*capture_time=*/env_.clock().CurrentTime(), | |
| /*payload_length=*/rtp_sender_->MaxRtpPacketSize() - | |
| rtp_sender_->ExpectedPerPacketOverhead()); | |
| } | |
| std::vector<std::unique_ptr<RtpPacketToSend>> GeneratePadding( | |
| size_t target_size_bytes) { | |
| return rtp_sender_->GeneratePadding( | |
| target_size_bytes, /*media_has_been_sent=*/true, | |
| sequencer_->CanSendPaddingOnMediaSsrc()); | |
| } | |
| std::vector<std::unique_ptr<RtpPacketToSend>> Sequence( | |
| std::vector<std::unique_ptr<RtpPacketToSend>> packets) { | |
| for (auto& packet : packets) { | |
| sequencer_->Sequence(*packet); | |
| } | |
| return packets; | |
| } | |
| size_t GenerateAndSendPadding(size_t target_size_bytes) { | |
| size_t generated_bytes = 0; | |
| std::vector<std::unique_ptr<RtpPacketToSend>> packets; | |
| for (auto& packet : GeneratePadding(target_size_bytes)) { | |
| generated_bytes += packet->payload_size() + packet->padding_size(); | |
| packets.push_back(std::move(packet)); | |
| } | |
| rtp_sender_->EnqueuePackets(std::move(packets)); | |
| return generated_bytes; | |
| } | |
| // The following are helpers for configuring the RTPSender. They must be | |
| // called before sending any packets. | |
| // Enable the retransmission stream with sizable packet storage. | |
| void EnableRtx() { | |
| // RTX needs to be able to read the source packets from the packet store. | |
| // Pick a number of packets to store big enough for any unit test. | |
| constexpr uint16_t kNumberOfPacketsToStore = 100; | |
| packet_history_->SetStorePacketsStatus( | |
| RtpPacketHistory::StorageMode::kStoreAndCull, kNumberOfPacketsToStore); | |
| rtp_sender_->SetRtxPayloadType(kRtxPayload, kPayload); | |
| rtp_sender_->SetRtxStatus(kRtxRetransmitted | kRtxRedundantPayloads); | |
| } | |
| // Enable sending of the MID header extension for both the primary SSRC and | |
| // the RTX SSRC. | |
| void EnableMidSending(absl::string_view mid) { | |
| rtp_sender_->RegisterRtpHeaderExtension(RtpMid::Uri(), kMidExtensionId); | |
| rtp_sender_->SetMid(mid); | |
| } | |
| // Enable sending of the RSID header extension for the primary SSRC and the | |
| // RRSID header extension for the RTX SSRC. | |
| void EnableRidSending() { | |
| rtp_sender_->RegisterRtpHeaderExtension(RtpStreamId::Uri(), | |
| kRidExtensionId); | |
| rtp_sender_->RegisterRtpHeaderExtension(RepairedRtpStreamId::Uri(), | |
| kRepairedRidExtensionId); | |
| } | |
| }; | |
| TEST_F(RtpSenderTest, AllocatePacketSetCsrcs) { | |
| // Configure rtp_sender with csrc. | |
| uint32_t csrcs[] = {0x23456789}; | |
| auto packet = rtp_sender_->AllocatePacket(csrcs); | |
| ASSERT_TRUE(packet); | |
| EXPECT_EQ(rtp_sender_->SSRC(), packet->Ssrc()); | |
| EXPECT_THAT(packet->Csrcs(), ElementsAreArray(csrcs)); | |
| } | |
| TEST_F(RtpSenderTest, AllocatePacketReserveExtensions) { | |
| // Configure rtp_sender with extensions. | |
| ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( | |
| TransmissionOffset::Uri(), kTransmissionTimeOffsetExtensionId)); | |
| ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( | |
| AbsoluteSendTime::Uri(), kAbsoluteSendTimeExtensionId)); | |
| ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( | |
| AudioLevelExtension::Uri(), kAudioLevelExtensionId)); | |
| ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( | |
| TransportSequenceNumber::Uri(), kTransportSequenceNumberExtensionId)); | |
| ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( | |
| VideoOrientation::Uri(), kVideoRotationExtensionId)); | |
| auto packet = rtp_sender_->AllocatePacket(); | |
| ASSERT_TRUE(packet); | |
| // Preallocate BWE extensions RtpSender set itself. | |
| EXPECT_TRUE(packet->HasExtension<TransmissionOffset>()); | |
| EXPECT_TRUE(packet->HasExtension<AbsoluteSendTime>()); | |
| EXPECT_TRUE(packet->HasExtension<TransportSequenceNumber>()); | |
| // Do not allocate media specific extensions. | |
| EXPECT_FALSE(packet->HasExtension<AudioLevelExtension>()); | |
| EXPECT_FALSE(packet->HasExtension<VideoOrientation>()); | |
| } | |
| TEST_F(RtpSenderTest, PaddingAlwaysAllowedOnAudio) { | |
| RtpRtcpInterface::Configuration config = GetDefaultConfig(); | |
| config.audio = true; | |
| CreateSender(config); | |
| std::unique_ptr<RtpPacketToSend> audio_packet = rtp_sender_->AllocatePacket(); | |
| // Padding on audio stream allowed regardless of marker in the last packet. | |
| audio_packet->SetMarker(false); | |
| audio_packet->SetPayloadType(kPayload); | |
| sequencer_->Sequence(*audio_packet); | |
| const size_t kPaddingSize = 59; | |
| EXPECT_CALL( | |
| mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(AllOf( | |
| Pointee(Property(&RtpPacketToSend::packet_type, | |
| RtpPacketMediaType::kPadding)), | |
| Pointee(Property(&RtpPacketToSend::padding_size, kPaddingSize)))))); | |
| EXPECT_EQ(kPaddingSize, GenerateAndSendPadding(kPaddingSize)); | |
| // Requested padding size is too small, will send a larger one. | |
| const size_t kMinPaddingSize = 50; | |
| EXPECT_CALL(mock_paced_sender_, | |
| EnqueuePackets(ElementsAre( | |
| AllOf(Pointee(Property(&RtpPacketToSend::packet_type, | |
| RtpPacketMediaType::kPadding)), | |
| Pointee(Property(&RtpPacketToSend::padding_size, | |
| kMinPaddingSize)))))); | |
| EXPECT_EQ(kMinPaddingSize, GenerateAndSendPadding(kMinPaddingSize - 5)); | |
| } | |
| TEST_F(RtpSenderTest, SendToNetworkForwardsPacketsToPacer) { | |
| std::vector<std::unique_ptr<RtpPacketToSend>> packets(1); | |
| packets[0] = | |
| BuildRtpPacket(kPayload, kMarkerBit, kTimestamp, Timestamp::Zero()); | |
| Timestamp now = env_.clock().CurrentTime(); | |
| EXPECT_CALL(mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(AllOf( | |
| Pointee(Property(&RtpPacketToSend::Ssrc, kSsrc)), | |
| Pointee(Property(&RtpPacketToSend::capture_time, now)))))); | |
| rtp_sender_->EnqueuePackets(std::move(packets)); | |
| } | |
| TEST_F(RtpSenderTest, ReSendPacketForwardsPacketsToPacer) { | |
| packet_history_->SetStorePacketsStatus( | |
| RtpPacketHistory::StorageMode::kStoreAndCull, 10); | |
| Timestamp now = env_.clock().CurrentTime(); | |
| auto packet = BuildRtpPacket(kPayload, kMarkerBit, kTimestamp, now); | |
| packet->SetSequenceNumber(kSeqNum); | |
| packet->set_allow_retransmission(true); | |
| packet_history_->PutRtpPacket(std::move(packet), now); | |
| EXPECT_CALL(mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(AllOf( | |
| Pointee(Property(&RtpPacketToSend::Ssrc, kSsrc)), | |
| Pointee(Property(&RtpPacketToSend::SequenceNumber, kSeqNum)), | |
| Pointee(Property(&RtpPacketToSend::capture_time, now)), | |
| Pointee(Property(&RtpPacketToSend::packet_type, | |
| RtpPacketMediaType::kRetransmission)))))); | |
| EXPECT_TRUE(rtp_sender_->ReSendPacket(kSeqNum)); | |
| } | |
| // This test sends 1 regular video packet, then 4 padding packets, and then | |
| // 1 more regular packet. | |
| TEST_F(RtpSenderTest, SendPadding) { | |
| constexpr int kNumPaddingPackets = 4; | |
| EXPECT_CALL(mock_paced_sender_, EnqueuePackets); | |
| std::unique_ptr<RtpPacketToSend> media_packet = | |
| SendPacket(/*capture_time=*/env_.clock().CurrentTime(), | |
| /*payload_length=*/100); | |
| sequencer_->Sequence(*media_packet); | |
| // Wait 50 ms before generating each padding packet. | |
| for (int i = 0; i < kNumPaddingPackets; ++i) { | |
| time_controller_.AdvanceTime(TimeDelta::Millis(50)); | |
| const size_t kPaddingTargetBytes = 100; // Request 100 bytes of padding. | |
| // Padding should be sent on the media ssrc, with a continous sequence | |
| // number range. Size will be forced to full pack size and the timestamp | |
| // shall be that of the last media packet. | |
| EXPECT_CALL(mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee(AllOf( | |
| Property(&RtpPacketToSend::Ssrc, kSsrc), | |
| Property(&RtpPacketToSend::padding_size, kMaxPaddingLength), | |
| Property(&RtpPacketToSend::SequenceNumber, | |
| media_packet->SequenceNumber() + i + 1), | |
| Property(&RtpPacketToSend::Timestamp, | |
| media_packet->Timestamp())))))); | |
| std::vector<std::unique_ptr<RtpPacketToSend>> padding_packets = | |
| Sequence(GeneratePadding(kPaddingTargetBytes)); | |
| ASSERT_THAT(padding_packets, SizeIs(1)); | |
| rtp_sender_->EnqueuePackets(std::move(padding_packets)); | |
| } | |
| // Send a regular video packet again. | |
| EXPECT_CALL( | |
| mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee(Property( | |
| &RtpPacketToSend::Timestamp, Gt(media_packet->Timestamp())))))); | |
| std::unique_ptr<RtpPacketToSend> next_media_packet = | |
| SendPacket(/*capture_time=*/env_.clock().CurrentTime(), | |
| /*payload_length=*/100); | |
| } | |
| TEST_F(RtpSenderTest, NoPaddingAsFirstPacketWithoutBweExtensions) { | |
| EXPECT_THAT(rtp_sender_->GeneratePadding( | |
| /*target_size_bytes=*/100, | |
| /*media_has_been_sent=*/false, | |
| /*can_send_padding_on_media_ssrc=*/false), | |
| IsEmpty()); | |
| // Don't send padding before media even with RTX. | |
| EnableRtx(); | |
| EXPECT_THAT(rtp_sender_->GeneratePadding( | |
| /*target_size_bytes=*/100, | |
| /*media_has_been_sent=*/false, | |
| /*can_send_padding_on_media_ssrc=*/false), | |
| IsEmpty()); | |
| } | |
| TEST_F(RtpSenderTest, RequiresRtxSsrcToEnableRtx) { | |
| RtpRtcpInterface::Configuration config = GetDefaultConfig(); | |
| config.rtx_send_ssrc = std::nullopt; | |
| RTPSender rtp_sender(env_, config, packet_history_.get(), | |
| config.paced_sender); | |
| rtp_sender.SetRtxPayloadType(kRtxPayload, kPayload); | |
| rtp_sender.SetRtxStatus(kRtxRetransmitted); | |
| EXPECT_EQ(rtp_sender.RtxStatus(), kRtxOff); | |
| } | |
| TEST_F(RtpSenderTest, RequiresRtxPayloadTypesToEnableRtx) { | |
| RtpRtcpInterface::Configuration config = GetDefaultConfig(); | |
| config.rtx_send_ssrc = kRtxSsrc; | |
| RTPSender rtp_sender(env_, config, packet_history_.get(), | |
| config.paced_sender); | |
| rtp_sender.SetRtxStatus(kRtxRetransmitted); | |
| EXPECT_EQ(rtp_sender.RtxStatus(), kRtxOff); | |
| } | |
| TEST_F(RtpSenderTest, CanEnableRtxWhenRtxSsrcAndPayloadTypeAreConfigured) { | |
| RtpRtcpInterface::Configuration config = GetDefaultConfig(); | |
| config.rtx_send_ssrc = kRtxSsrc; | |
| RTPSender rtp_sender(env_, config, packet_history_.get(), | |
| config.paced_sender); | |
| rtp_sender.SetRtxPayloadType(kRtxPayload, kPayload); | |
| ASSERT_EQ(rtp_sender.RtxStatus(), kRtxOff); | |
| rtp_sender.SetRtxStatus(kRtxRetransmitted); | |
| EXPECT_EQ(rtp_sender.RtxStatus(), kRtxRetransmitted); | |
| } | |
| TEST_F(RtpSenderTest, AllowPaddingAsFirstPacketOnRtxWithTransportCc) { | |
| ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( | |
| TransportSequenceNumber::Uri(), kTransportSequenceNumberExtensionId)); | |
| // Padding can't be sent as first packet on media SSRC since we don't know | |
| // what payload type to assign. | |
| EXPECT_THAT(rtp_sender_->GeneratePadding( | |
| /*target_size_bytes=*/100, | |
| /*media_has_been_sent=*/false, | |
| /*can_send_padding_on_media_ssrc=*/false), | |
| IsEmpty()); | |
| // With transportcc padding can be sent as first packet on the RTX SSRC. | |
| EnableRtx(); | |
| EXPECT_THAT(rtp_sender_->GeneratePadding( | |
| /*target_size_bytes=*/100, | |
| /*media_has_been_sent=*/false, | |
| /*can_send_padding_on_media_ssrc=*/false), | |
| Not(IsEmpty())); | |
| } | |
| TEST_F(RtpSenderTest, AllowPaddingAsFirstPacketOnRtxWithAbsSendTime) { | |
| ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( | |
| AbsoluteSendTime::Uri(), kAbsoluteSendTimeExtensionId)); | |
| // Padding can't be sent as first packet on media SSRC since we don't know | |
| // what payload type to assign. | |
| EXPECT_THAT(rtp_sender_->GeneratePadding( | |
| /*target_size_bytes=*/100, | |
| /*media_has_been_sent=*/false, | |
| /*can_send_padding_on_media_ssrc=*/false), | |
| IsEmpty()); | |
| // With abs send time, padding can be sent as first packet on the RTX SSRC. | |
| EnableRtx(); | |
| EXPECT_THAT(rtp_sender_->GeneratePadding( | |
| /*target_size_bytes=*/100, | |
| /*media_has_been_sent=*/false, | |
| /*can_send_padding_on_media_ssrc=*/false), | |
| Not(IsEmpty())); | |
| } | |
| TEST_F(RtpSenderTest, UpdatesTimestampsOnPlainRtxPadding) { | |
| EnableRtx(); | |
| // Timestamps as set based on capture time in RtpSenderTest. | |
| const Timestamp start_time = env_.clock().CurrentTime(); | |
| const uint32_t start_timestamp = ToRtpTimestamp(start_time); | |
| // Start by sending one media packet. | |
| EXPECT_CALL( | |
| mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(AllOf( | |
| Pointee(Property(&RtpPacketToSend::padding_size, 0u)), | |
| Pointee(Property(&RtpPacketToSend::Timestamp, start_timestamp)), | |
| Pointee(Property(&RtpPacketToSend::capture_time, start_time)))))); | |
| std::unique_ptr<RtpPacketToSend> media_packet = | |
| SendPacket(start_time, /*payload_length=*/600); | |
| sequencer_->Sequence(*media_packet); | |
| // Advance time before sending padding. | |
| const TimeDelta kTimeDiff = TimeDelta::Millis(17); | |
| time_controller_.AdvanceTime(kTimeDiff); | |
| // Timestamps on padding should be offset from the sent media. | |
| EXPECT_THAT( | |
| Sequence(GeneratePadding(/*target_size_bytes=*/100)), | |
| Each(Pointee(AllOf( | |
| Property(&RtpPacketToSend::padding_size, kMaxPaddingLength), | |
| Property(&RtpPacketToSend::Timestamp, | |
| start_timestamp + kRtpClockRate * kTimeDiff), | |
| Property(&RtpPacketToSend::capture_time, start_time + kTimeDiff))))); | |
| } | |
| TEST_F(RtpSenderTest, KeepsTimestampsOnPayloadPadding) { | |
| ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( | |
| TransportSequenceNumber::Uri(), kTransportSequenceNumberExtensionId)); | |
| EnableRtx(); | |
| // Timestamps as set based on capture time in RtpSenderTest. | |
| const Timestamp start_time = env_.clock().CurrentTime(); | |
| const uint32_t start_timestamp = ToRtpTimestamp(start_time); | |
| const size_t kPayloadSize = 200; | |
| // Start by sending one media packet and putting in the packet history. | |
| EXPECT_CALL( | |
| mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(AllOf( | |
| Pointee(Property(&RtpPacketToSend::padding_size, 0u)), | |
| Pointee(Property(&RtpPacketToSend::Timestamp, start_timestamp)), | |
| Pointee(Property(&RtpPacketToSend::capture_time, start_time)))))); | |
| std::unique_ptr<RtpPacketToSend> media_packet = | |
| SendPacket(start_time, kPayloadSize); | |
| packet_history_->PutRtpPacket(std::move(media_packet), start_time); | |
| // Advance time before sending padding. | |
| const TimeDelta kTimeDiff = TimeDelta::Millis(17); | |
| time_controller_.AdvanceTime(kTimeDiff); | |
| // Timestamps on payload padding should be set to original. | |
| EXPECT_THAT( | |
| GeneratePadding(/*target_size_bytes=*/100), | |
| Each(AllOf( | |
| Pointee(Property(&RtpPacketToSend::padding_size, 0u)), | |
| Pointee(Property(&RtpPacketToSend::payload_size, | |
| kPayloadSize + kRtxHeaderSize)), | |
| Pointee(Property(&RtpPacketToSend::Timestamp, start_timestamp)), | |
| Pointee(Property(&RtpPacketToSend::capture_time, start_time))))); | |
| } | |
| // Test that the MID header extension is included on sent packets when | |
| // configured. | |
| TEST_F(RtpSenderTest, MidIncludedOnSentPackets) { | |
| EnableMidSending(kMid); | |
| // Send a couple packets, expect both packets to have the MID set. | |
| EXPECT_CALL(mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee( | |
| Property(&RtpPacketToSend::GetExtension<RtpMid>, kMid))))) | |
| .Times(2); | |
| SendGenericPacket(); | |
| SendGenericPacket(); | |
| } | |
| TEST_F(RtpSenderTest, RidIncludedOnSentPackets) { | |
| EnableRidSending(); | |
| EXPECT_CALL(mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee(Property( | |
| &RtpPacketToSend::GetExtension<RtpStreamId>, kRid))))); | |
| SendGenericPacket(); | |
| } | |
| TEST_F(RtpSenderTest, RidIncludedOnRtxSentPackets) { | |
| EnableRtx(); | |
| EnableRidSending(); | |
| EXPECT_CALL(mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee(AllOf( | |
| Property(&RtpPacketToSend::GetExtension<RtpStreamId>, kRid), | |
| Property(&RtpPacketToSend::HasExtension<RepairedRtpStreamId>, | |
| false)))))) | |
| .WillOnce([&](std::vector<std::unique_ptr<RtpPacketToSend>> packets) { | |
| sequencer_->Sequence(*packets[0]); | |
| packet_history_->PutRtpPacket(std::move(packets[0]), | |
| env_.clock().CurrentTime()); | |
| }); | |
| SendGenericPacket(); | |
| EXPECT_CALL( | |
| mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee(AllOf( | |
| Property(&RtpPacketToSend::GetExtension<RepairedRtpStreamId>, kRid), | |
| Property(&RtpPacketToSend::HasExtension<RtpStreamId>, false)))))); | |
| rtp_sender_->ReSendPacket(kSeqNum); | |
| } | |
| TEST_F(RtpSenderTest, MidAndRidNotIncludedOnSentPacketsAfterAck) { | |
| EnableMidSending(kMid); | |
| EnableRidSending(); | |
| // This first packet should include both MID and RID. | |
| EXPECT_CALL( | |
| mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee(AllOf( | |
| Property(&RtpPacketToSend::GetExtension<RtpMid>, kMid), | |
| Property(&RtpPacketToSend::GetExtension<RtpStreamId>, kRid)))))); | |
| auto first_built_packet = SendGenericPacket(); | |
| rtp_sender_->OnReceivedAckOnSsrc(first_built_packet->SequenceNumber()); | |
| // The second packet should include neither since an ack was received. | |
| EXPECT_CALL( | |
| mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee(AllOf( | |
| Property(&RtpPacketToSend::HasExtension<RtpMid>, false), | |
| Property(&RtpPacketToSend::HasExtension<RtpStreamId>, false)))))); | |
| SendGenericPacket(); | |
| } | |
| TEST_F(RtpSenderTest, MidAndRidAlwaysIncludedOnSentPacketsWhenConfigured) { | |
| SetUpRtpSender(false, /*always_send_mid_and_rid=*/true, nullptr); | |
| EnableMidSending(kMid); | |
| EnableRidSending(); | |
| // Send two media packets: one before and one after the ack. | |
| // Due to the configuration, both sent packets should contain MID and RID. | |
| EXPECT_CALL( | |
| mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee( | |
| AllOf(Property(&RtpPacketToSend::GetExtension<RtpMid>, kMid), | |
| Property(&RtpPacketToSend::GetExtension<RtpStreamId>, kRid)))))) | |
| .Times(2); | |
| auto first_built_packet = SendGenericPacket(); | |
| rtp_sender_->OnReceivedAckOnSsrc(first_built_packet->SequenceNumber()); | |
| SendGenericPacket(); | |
| } | |
| // Test that the first RTX packet includes both MID and RRID even if the packet | |
| // being retransmitted did not have MID or RID. The MID and RID are needed on | |
| // the first packets for a given SSRC, and RTX packets are sent on a separate | |
| // SSRC. | |
| TEST_F(RtpSenderTest, MidAndRidIncludedOnFirstRtxPacket) { | |
| EnableRtx(); | |
| EnableMidSending(kMid); | |
| EnableRidSending(); | |
| // This first packet will include both MID and RID. | |
| EXPECT_CALL(mock_paced_sender_, EnqueuePackets); | |
| auto first_built_packet = SendGenericPacket(); | |
| rtp_sender_->OnReceivedAckOnSsrc(first_built_packet->SequenceNumber()); | |
| // The second packet will include neither since an ack was received, put | |
| // it in the packet history for retransmission. | |
| EXPECT_CALL(mock_paced_sender_, EnqueuePackets(SizeIs(1))) | |
| .WillOnce([&](std::vector<std::unique_ptr<RtpPacketToSend>> packets) { | |
| packet_history_->PutRtpPacket(std::move(packets[0]), | |
| env_.clock().CurrentTime()); | |
| }); | |
| auto second_built_packet = SendGenericPacket(); | |
| // The first RTX packet should include MID and RRID. | |
| EXPECT_CALL(mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee(AllOf( | |
| Property(&RtpPacketToSend::GetExtension<RtpMid>, kMid), | |
| Property(&RtpPacketToSend::GetExtension<RepairedRtpStreamId>, | |
| kRid)))))); | |
| rtp_sender_->ReSendPacket(second_built_packet->SequenceNumber()); | |
| } | |
| // Test that the RTX packets sent after receving an ACK on the RTX SSRC does | |
| // not include either MID or RRID even if the packet being retransmitted did | |
| // had a MID or RID. | |
| TEST_F(RtpSenderTest, MidAndRidNotIncludedOnRtxPacketsAfterAck) { | |
| EnableRtx(); | |
| EnableMidSending(kMid); | |
| EnableRidSending(); | |
| // This first packet will include both MID and RID. | |
| auto first_built_packet = SendGenericPacket(); | |
| sequencer_->Sequence(*first_built_packet); | |
| packet_history_->PutRtpPacket( | |
| std::make_unique<RtpPacketToSend>(*first_built_packet), | |
| /*send_time=*/env_.clock().CurrentTime()); | |
| rtp_sender_->OnReceivedAckOnSsrc(first_built_packet->SequenceNumber()); | |
| // The second packet will include neither since an ack was received. | |
| auto second_built_packet = SendGenericPacket(); | |
| sequencer_->Sequence(*second_built_packet); | |
| packet_history_->PutRtpPacket( | |
| std::make_unique<RtpPacketToSend>(*second_built_packet), | |
| /*send_time=*/env_.clock().CurrentTime()); | |
| // The first RTX packet will include MID and RRID. | |
| EXPECT_CALL(mock_paced_sender_, EnqueuePackets(SizeIs(1))) | |
| .WillOnce([&](std::vector<std::unique_ptr<RtpPacketToSend>> packets) { | |
| rtp_sender_->OnReceivedAckOnRtxSsrc(packets[0]->SequenceNumber()); | |
| packet_history_->MarkPacketAsSent( | |
| *packets[0]->retransmitted_sequence_number()); | |
| }); | |
| rtp_sender_->ReSendPacket(second_built_packet->SequenceNumber()); | |
| // The second and third RTX packets should not include MID nor RRID. | |
| EXPECT_CALL(mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee(AllOf( | |
| Property(&RtpPacketToSend::HasExtension<RtpMid>, false), | |
| Property(&RtpPacketToSend::HasExtension<RepairedRtpStreamId>, | |
| false)))))) | |
| .Times(2); | |
| rtp_sender_->ReSendPacket(first_built_packet->SequenceNumber()); | |
| rtp_sender_->ReSendPacket(second_built_packet->SequenceNumber()); | |
| } | |
| TEST_F(RtpSenderTest, MidAndRidAlwaysIncludedOnRtxPacketsWhenConfigured) { | |
| SetUpRtpSender(false, /*always_send_mid_and_rid=*/true, nullptr); | |
| EnableRtx(); | |
| EnableMidSending(kMid); | |
| EnableRidSending(); | |
| // Send two media packets: one before and one after the ack. | |
| EXPECT_CALL( | |
| mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee( | |
| AllOf(Property(&RtpPacketToSend::GetExtension<RtpMid>, kMid), | |
| Property(&RtpPacketToSend::GetExtension<RtpStreamId>, kRid)))))) | |
| .Times(2) | |
| .WillRepeatedly( | |
| [&](std::vector<std::unique_ptr<RtpPacketToSend>> packets) { | |
| packet_history_->PutRtpPacket(std::move(packets[0]), | |
| env_.clock().CurrentTime()); | |
| }); | |
| auto media_packet1 = SendGenericPacket(); | |
| rtp_sender_->OnReceivedAckOnSsrc(media_packet1->SequenceNumber()); | |
| auto media_packet2 = SendGenericPacket(); | |
| // Send three RTX packets with different combinations of orders w.r.t. the | |
| // media and RTX acks. | |
| // Due to the configuration, all sent packets should contain MID | |
| // and either RID (media) or RRID (RTX). | |
| EXPECT_CALL(mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee(AllOf( | |
| Property(&RtpPacketToSend::GetExtension<RtpMid>, kMid), | |
| Property(&RtpPacketToSend::GetExtension<RepairedRtpStreamId>, | |
| kRid)))))) | |
| .Times(3) | |
| .WillRepeatedly( | |
| [&](std::vector<std::unique_ptr<RtpPacketToSend>> packets) { | |
| rtp_sender_->OnReceivedAckOnRtxSsrc(packets[0]->SequenceNumber()); | |
| packet_history_->MarkPacketAsSent( | |
| *packets[0]->retransmitted_sequence_number()); | |
| }); | |
| rtp_sender_->ReSendPacket(media_packet2->SequenceNumber()); | |
| rtp_sender_->ReSendPacket(media_packet1->SequenceNumber()); | |
| rtp_sender_->ReSendPacket(media_packet2->SequenceNumber()); | |
| } | |
| // Test that if the RtpState indicates an ACK has been received on that SSRC | |
| // then neither the MID nor RID header extensions will be sent. | |
| TEST_F(RtpSenderTest, MidAndRidNotIncludedOnSentPacketsAfterRtpStateRestored) { | |
| EnableMidSending(kMid); | |
| EnableRidSending(); | |
| RtpState state = rtp_sender_->GetRtpState(); | |
| EXPECT_FALSE(state.ssrc_has_acked); | |
| state.ssrc_has_acked = true; | |
| rtp_sender_->SetRtpState(state); | |
| EXPECT_CALL( | |
| mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee(AllOf( | |
| Property(&RtpPacketToSend::HasExtension<RtpMid>, false), | |
| Property(&RtpPacketToSend::HasExtension<RtpStreamId>, false)))))); | |
| SendGenericPacket(); | |
| } | |
| // Test that if the RTX RtpState indicates an ACK has been received on that | |
| // RTX SSRC then neither the MID nor RRID header extensions will be sent on | |
| // RTX packets. | |
| TEST_F(RtpSenderTest, MidAndRridNotIncludedOnRtxPacketsAfterRtpStateRestored) { | |
| EnableRtx(); | |
| EnableMidSending(kMid); | |
| EnableRidSending(); | |
| RtpState rtx_state = rtp_sender_->GetRtxRtpState(); | |
| EXPECT_FALSE(rtx_state.ssrc_has_acked); | |
| rtx_state.ssrc_has_acked = true; | |
| rtp_sender_->SetRtxRtpState(rtx_state); | |
| EXPECT_CALL(mock_paced_sender_, EnqueuePackets(SizeIs(1))) | |
| .WillOnce([&](std::vector<std::unique_ptr<RtpPacketToSend>> packets) { | |
| packet_history_->PutRtpPacket(std::move(packets[0]), | |
| env_.clock().CurrentTime()); | |
| }); | |
| auto built_packet = SendGenericPacket(); | |
| EXPECT_CALL( | |
| mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee(AllOf( | |
| Property(&RtpPacketToSend::HasExtension<RtpMid>, false), | |
| Property(&RtpPacketToSend::HasExtension<RtpStreamId>, false)))))); | |
| ASSERT_LT(0, rtp_sender_->ReSendPacket(built_packet->SequenceNumber())); | |
| } | |
| TEST_F(RtpSenderTest, RespectsNackBitrateLimit) { | |
| const int32_t kPacketSize = 1400; | |
| const int32_t kNumPackets = 30; | |
| retransmission_rate_limiter_.SetMaxRate(kPacketSize * kNumPackets * 8); | |
| EnableRtx(); | |
| std::vector<uint16_t> sequence_numbers; | |
| for (int32_t i = 0; i < kNumPackets; ++i) { | |
| std::unique_ptr<RtpPacketToSend> packet = | |
| BuildRtpPacket(kPayload, /*marker_bit=*/true, /*rtp_timestamp=*/0, | |
| /*capture_time=*/env_.clock().CurrentTime()); | |
| packet->set_allow_retransmission(true); | |
| sequencer_->Sequence(*packet); | |
| sequence_numbers.push_back(packet->SequenceNumber()); | |
| packet_history_->PutRtpPacket(std::move(packet), | |
| /*send_time=*/env_.clock().CurrentTime()); | |
| time_controller_.AdvanceTime(TimeDelta::Millis(1)); | |
| } | |
| time_controller_.AdvanceTime(TimeDelta::Millis(1000 - kNumPackets)); | |
| // Resending should work - brings the bandwidth up to the limit. | |
| // NACK bitrate is capped to the same bitrate as the encoder, since the max | |
| // protection overhead is 50% (see MediaOptimization::SetTargetRates). | |
| EXPECT_CALL(mock_paced_sender_, EnqueuePackets(ElementsAre(Pointee(Property( | |
| &RtpPacketToSend::packet_type, | |
| RtpPacketMediaType::kRetransmission))))) | |
| .Times(kNumPackets) | |
| .WillRepeatedly( | |
| [&](std::vector<std::unique_ptr<RtpPacketToSend>> packets) { | |
| for (const auto& packet : packets) { | |
| packet_history_->MarkPacketAsSent( | |
| *packet->retransmitted_sequence_number()); | |
| } | |
| }); | |
| rtp_sender_->OnReceivedNack(sequence_numbers, 0); | |
| // Must be at least 5ms in between retransmission attempts. | |
| time_controller_.AdvanceTime(TimeDelta::Millis(5)); | |
| // Resending should not work, bandwidth exceeded. | |
| EXPECT_CALL(mock_paced_sender_, EnqueuePackets).Times(0); | |
| rtp_sender_->OnReceivedNack(sequence_numbers, 0); | |
| } | |
| TEST_F(RtpSenderTest, UpdatingCsrcsUpdatedOverhead) { | |
| RtpRtcpInterface::Configuration config = GetDefaultConfig(); | |
| config.rtx_send_ssrc = {}; | |
| CreateSender(config); | |
| // Base RTP overhead is 12B. | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 12u); | |
| // Using packet with two csrcs adds 2*4 bytes to the header. | |
| uint32_t csrcs[] = {1, 2}; | |
| rtp_sender_->AllocatePacket(csrcs); | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 20u); | |
| } | |
| TEST_F(RtpSenderTest, OnOverheadChanged) { | |
| RtpRtcpInterface::Configuration config = GetDefaultConfig(); | |
| config.rtx_send_ssrc = {}; | |
| CreateSender(config); | |
| // Base RTP overhead is 12B. | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 12u); | |
| rtp_sender_->RegisterRtpHeaderExtension(TransmissionOffset::Uri(), | |
| kTransmissionTimeOffsetExtensionId); | |
| // TransmissionTimeOffset extension has a size of 3B, but with the addition | |
| // of header index and rounding to 4 byte boundary we end up with 20B total. | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 20u); | |
| } | |
| TEST_F(RtpSenderTest, CountMidOnlyUntilAcked) { | |
| RtpRtcpInterface::Configuration config = GetDefaultConfig(); | |
| config.rtx_send_ssrc = {}; | |
| CreateSender(config); | |
| // Base RTP overhead is 12B. | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 12u); | |
| rtp_sender_->RegisterRtpHeaderExtension(RtpMid::Uri(), kMidExtensionId); | |
| // Counted only if set. | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 12u); | |
| rtp_sender_->SetMid("foo"); | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 36u); | |
| rtp_sender_->RegisterRtpHeaderExtension(RtpStreamId::Uri(), kRidExtensionId); | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 52u); | |
| // Ack received, mid/rid no longer sent. | |
| rtp_sender_->OnReceivedAckOnSsrc(0); | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 12u); | |
| } | |
| TEST_F(RtpSenderTest, CountMidRidRridUntilAcked) { | |
| RtpRtcpInterface::Configuration config = GetDefaultConfig(); | |
| CreateSender(config); | |
| // Base RTP overhead is 12B and we use RTX which has an additional 2 bytes | |
| // overhead. | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 14u); | |
| rtp_sender_->RegisterRtpHeaderExtension(RtpMid::Uri(), kMidExtensionId); | |
| // Counted only if set. | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 14u); | |
| rtp_sender_->SetMid("foo"); | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 38u); | |
| rtp_sender_->RegisterRtpHeaderExtension(RtpStreamId::Uri(), kRidExtensionId); | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 54u); | |
| // mid/rrid may be shared with mid/rid when both are active. | |
| rtp_sender_->RegisterRtpHeaderExtension(RepairedRtpStreamId::Uri(), | |
| kRepairedRidExtensionId); | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 54u); | |
| // Ack received, mid/rid no longer sent but we still need space for | |
| // mid/rrid which can no longer be shared with mid/rid. | |
| rtp_sender_->OnReceivedAckOnSsrc(0); | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 54u); | |
| // Ack received for RTX, no need to send RRID anymore. | |
| rtp_sender_->OnReceivedAckOnRtxSsrc(0); | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 14u); | |
| } | |
| TEST_F(RtpSenderTest, DontCountVolatileExtensionsIntoOverhead) { | |
| RtpRtcpInterface::Configuration config = GetDefaultConfig(); | |
| config.rtx_send_ssrc = {}; | |
| CreateSender(config); | |
| // Base RTP overhead is 12B. | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 12u); | |
| rtp_sender_->RegisterRtpHeaderExtension(InbandComfortNoiseExtension::Uri(), | |
| 1); | |
| rtp_sender_->RegisterRtpHeaderExtension(AbsoluteCaptureTimeExtension::Uri(), | |
| 2); | |
| rtp_sender_->RegisterRtpHeaderExtension(VideoOrientation::Uri(), 3); | |
| rtp_sender_->RegisterRtpHeaderExtension(PlayoutDelayLimits::Uri(), 4); | |
| rtp_sender_->RegisterRtpHeaderExtension(VideoContentTypeExtension::Uri(), 5); | |
| rtp_sender_->RegisterRtpHeaderExtension(VideoTimingExtension::Uri(), 6); | |
| rtp_sender_->RegisterRtpHeaderExtension(RepairedRtpStreamId::Uri(), 7); | |
| rtp_sender_->RegisterRtpHeaderExtension(ColorSpaceExtension::Uri(), 8); | |
| // Still only 12B counted since can't count on above being sent. | |
| EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 12u); | |
| } | |
| TEST_F(RtpSenderTest, SendPacketHandlesRetransmissionHistory) { | |
| packet_history_->SetStorePacketsStatus( | |
| RtpPacketHistory::StorageMode::kStoreAndCull, 10); | |
| // Ignore calls to EnqueuePackets() for this test. | |
| EXPECT_CALL(mock_paced_sender_, EnqueuePackets).WillRepeatedly(Return()); | |
| // Build a media packet and put in the packet history. | |
| std::unique_ptr<RtpPacketToSend> packet = | |
| BuildRtpPacket(kPayload, true, 0, env_.clock().CurrentTime()); | |
| const uint16_t media_sequence_number = packet->SequenceNumber(); | |
| packet->set_allow_retransmission(true); | |
| packet_history_->PutRtpPacket(std::move(packet), env_.clock().CurrentTime()); | |
| // Simulate successful retransmission request. | |
| time_controller_.AdvanceTime(TimeDelta::Millis(30)); | |
| EXPECT_THAT(rtp_sender_->ReSendPacket(media_sequence_number), Gt(0)); | |
| // Packet already pending, retransmission not allowed. | |
| time_controller_.AdvanceTime(TimeDelta::Millis(30)); | |
| EXPECT_THAT(rtp_sender_->ReSendPacket(media_sequence_number), Eq(0)); | |
| // Simulate packet exiting pacer, mark as not longer pending. | |
| packet_history_->MarkPacketAsSent(media_sequence_number); | |
| // Retransmissions allowed again. | |
| time_controller_.AdvanceTime(TimeDelta::Millis(30)); | |
| EXPECT_THAT(rtp_sender_->ReSendPacket(media_sequence_number), Gt(0)); | |
| } | |
| TEST_F(RtpSenderTest, MarksRetransmittedPackets) { | |
| packet_history_->SetStorePacketsStatus( | |
| RtpPacketHistory::StorageMode::kStoreAndCull, 10); | |
| // Build a media packet and put in the packet history. | |
| std::unique_ptr<RtpPacketToSend> packet = | |
| BuildRtpPacket(kPayload, true, 0, env_.clock().CurrentTime()); | |
| const uint32_t kMediaSsrc = 567; | |
| const uint16_t kMediaSequenceNumber = 123; | |
| packet->SetSsrc(kMediaSsrc); | |
| packet->SetSequenceNumber(kMediaSequenceNumber); | |
| packet->set_allow_retransmission(true); | |
| packet_history_->PutRtpPacket(std::move(packet), env_.clock().CurrentTime()); | |
| // Expect a retransmission packet marked with which packet it is a | |
| // retransmit of. | |
| EXPECT_CALL( | |
| mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(AllOf( | |
| Pointee(Property(&RtpPacketToSend::packet_type, | |
| RtpPacketMediaType::kRetransmission)), | |
| Pointee(Property(&RtpPacketToSend::original_ssrc, kMediaSsrc)), | |
| Pointee(Property(&RtpPacketToSend::retransmitted_sequence_number, | |
| Eq(kMediaSequenceNumber))))))); | |
| EXPECT_THAT(rtp_sender_->ReSendPacket(kMediaSequenceNumber), Gt(0)); | |
| } | |
| TEST_F(RtpSenderTest, GeneratedPaddingHasBweExtensions) { | |
| // Min requested size in order to use RTX payload. | |
| const size_t kMinPaddingSize = 50; | |
| EnableRtx(); | |
| ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( | |
| TransmissionOffset::Uri(), kTransmissionTimeOffsetExtensionId)); | |
| ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( | |
| AbsoluteSendTime::Uri(), kAbsoluteSendTimeExtensionId)); | |
| ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( | |
| TransportSequenceNumber::Uri(), kTransportSequenceNumberExtensionId)); | |
| // Put a packet in the history, in order to facilitate payload padding. | |
| std::unique_ptr<RtpPacketToSend> packet = | |
| BuildRtpPacket(kPayload, true, 0, env_.clock().CurrentTime()); | |
| packet->set_allow_retransmission(true); | |
| packet->SetPayloadSize(kMinPaddingSize); | |
| packet->set_packet_type(RtpPacketMediaType::kVideo); | |
| packet_history_->PutRtpPacket(std::move(packet), env_.clock().CurrentTime()); | |
| // Generate a plain padding packet, check that extensions are registered. | |
| std::vector<std::unique_ptr<RtpPacketToSend>> generated_packets = | |
| GeneratePadding(/*target_size_bytes=*/1); | |
| ASSERT_THAT(generated_packets, SizeIs(1)); | |
| auto& plain_padding = generated_packets.front(); | |
| EXPECT_GT(plain_padding->padding_size(), 0u); | |
| EXPECT_TRUE(plain_padding->HasExtension<TransportSequenceNumber>()); | |
| EXPECT_TRUE(plain_padding->HasExtension<AbsoluteSendTime>()); | |
| EXPECT_TRUE(plain_padding->HasExtension<TransmissionOffset>()); | |
| EXPECT_GT(plain_padding->padding_size(), 0u); | |
| // Generate a payload padding packets, check that extensions are registered. | |
| generated_packets = GeneratePadding(kMinPaddingSize); | |
| ASSERT_EQ(generated_packets.size(), 1u); | |
| auto& payload_padding = generated_packets.front(); | |
| EXPECT_EQ(payload_padding->padding_size(), 0u); | |
| EXPECT_TRUE(payload_padding->HasExtension<TransportSequenceNumber>()); | |
| EXPECT_TRUE(payload_padding->HasExtension<AbsoluteSendTime>()); | |
| EXPECT_TRUE(payload_padding->HasExtension<TransmissionOffset>()); | |
| EXPECT_GT(payload_padding->payload_size(), 0u); | |
| } | |
| TEST_F(RtpSenderTest, GeneratedPaddingHasMidRidExtensions) { | |
| EnableMidSending("mid"); | |
| EnableRidSending(); | |
| // Send a dummy video packet so it ends up in the packet history. Since we | |
| // are not using RTX, it should never be used as padding. | |
| packet_history_->SetStorePacketsStatus( | |
| RtpPacketHistory::StorageMode::kStoreAndCull, 1); | |
| std::unique_ptr<RtpPacketToSend> packet = | |
| BuildRtpPacket(kPayload, true, 0, env_.clock().CurrentTime()); | |
| packet->set_allow_retransmission(true); | |
| packet->SetPayloadSize(1234); | |
| packet->set_packet_type(RtpPacketMediaType::kVideo); | |
| sequencer_->Sequence(*packet); | |
| packet_history_->PutRtpPacket(std::move(packet), env_.clock().CurrentTime()); | |
| std::vector<std::unique_ptr<RtpPacketToSend>> padding_packets = | |
| GeneratePadding(/*target_size_bytes=*/1); | |
| ASSERT_THAT(padding_packets, SizeIs(1)); | |
| EXPECT_TRUE(padding_packets[0]->HasExtension<RtpMid>()); | |
| EXPECT_TRUE(padding_packets[0]->HasExtension<RtpStreamId>()); | |
| } | |
| TEST_F(RtpSenderTest, GeneratedPaddingOnRtxHasMidRidExtensions) { | |
| EnableRtx(); | |
| EnableMidSending("mid"); | |
| EnableRidSending(); | |
| std::vector<std::unique_ptr<RtpPacketToSend>> padding_packets = | |
| GeneratePadding(/*target_size_bytes=*/1); | |
| ASSERT_THAT(padding_packets, SizeIs(1)); | |
| EXPECT_TRUE(padding_packets[0]->HasExtension<RtpMid>()); | |
| EXPECT_TRUE(padding_packets[0]->HasExtension<RepairedRtpStreamId>()); | |
| } | |
| TEST_F(RtpSenderTest, GeneratePaddingResendsOldPacketsWithRtx) { | |
| // Min requested size in order to use RTX payload. | |
| const size_t kMinPaddingSize = 50; | |
| rtp_sender_->SetRtxPayloadType(kRtxPayload, kPayload); | |
| rtp_sender_->SetRtxStatus(kRtxRetransmitted | kRtxRedundantPayloads); | |
| packet_history_->SetStorePacketsStatus( | |
| RtpPacketHistory::StorageMode::kStoreAndCull, 1); | |
| ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( | |
| TransportSequenceNumber::Uri(), kTransportSequenceNumberExtensionId)); | |
| const size_t kPayloadPacketSize = kMinPaddingSize; | |
| std::unique_ptr<RtpPacketToSend> packet = | |
| BuildRtpPacket(kPayload, true, 0, env_.clock().CurrentTime()); | |
| packet->set_allow_retransmission(true); | |
| packet->SetPayloadSize(kPayloadPacketSize); | |
| packet->set_packet_type(RtpPacketMediaType::kVideo); | |
| packet_history_->PutRtpPacket(std::move(packet), env_.clock().CurrentTime()); | |
| // Generated padding has large enough budget that the video packet should be | |
| // retransmitted as padding. | |
| std::vector<std::unique_ptr<RtpPacketToSend>> generated_packets = | |
| GeneratePadding(kMinPaddingSize); | |
| ASSERT_EQ(generated_packets.size(), 1u); | |
| auto& padding_packet = generated_packets.front(); | |
| EXPECT_EQ(padding_packet->packet_type(), RtpPacketMediaType::kPadding); | |
| EXPECT_EQ(padding_packet->Ssrc(), kRtxSsrc); | |
| EXPECT_EQ(padding_packet->payload_size(), | |
| kPayloadPacketSize + kRtxHeaderSize); | |
| // Not enough budged for payload padding, use plain padding instead. | |
| const size_t kPaddingBytesRequested = kMinPaddingSize - 1; | |
| size_t padding_bytes_generated = 0; | |
| generated_packets = GeneratePadding(kPaddingBytesRequested); | |
| EXPECT_EQ(generated_packets.size(), 1u); | |
| for (auto& generated_packet : generated_packets) { | |
| EXPECT_EQ(generated_packet->packet_type(), RtpPacketMediaType::kPadding); | |
| EXPECT_EQ(generated_packet->Ssrc(), kRtxSsrc); | |
| EXPECT_EQ(generated_packet->payload_size(), 0u); | |
| EXPECT_GT(generated_packet->padding_size(), 0u); | |
| padding_bytes_generated += generated_packet->padding_size(); | |
| } | |
| EXPECT_EQ(padding_bytes_generated, kMaxPaddingLength); | |
| } | |
| TEST_F(RtpSenderTest, LimitsPayloadPaddingSize) { | |
| // RTX payload padding is limited to 3x target size. | |
| const double kFactor = 3.0; | |
| SetUpRtpSender(false, false, nullptr); | |
| rtp_sender_->SetRtxPayloadType(kRtxPayload, kPayload); | |
| rtp_sender_->SetRtxStatus(kRtxRetransmitted | kRtxRedundantPayloads); | |
| packet_history_->SetStorePacketsStatus( | |
| RtpPacketHistory::StorageMode::kStoreAndCull, 1); | |
| ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( | |
| TransportSequenceNumber::Uri(), kTransportSequenceNumberExtensionId)); | |
| // Send a dummy video packet so it ends up in the packet history. | |
| const size_t kPayloadPacketSize = 1234u; | |
| std::unique_ptr<RtpPacketToSend> packet = | |
| BuildRtpPacket(kPayload, true, 0, env_.clock().CurrentTime()); | |
| packet->set_allow_retransmission(true); | |
| packet->SetPayloadSize(kPayloadPacketSize); | |
| packet->set_packet_type(RtpPacketMediaType::kVideo); | |
| packet_history_->PutRtpPacket(std::move(packet), env_.clock().CurrentTime()); | |
| // Smallest target size that will result in the sent packet being returned as | |
| // padding. | |
| const size_t kMinTargerSizeForPayload = | |
| (kPayloadPacketSize + kRtxHeaderSize) / kFactor; | |
| // Generated padding has large enough budget that the video packet should be | |
| // retransmitted as padding. | |
| EXPECT_THAT( | |
| GeneratePadding(kMinTargerSizeForPayload), | |
| AllOf(Not(IsEmpty()), | |
| Each(Pointee(Property(&RtpPacketToSend::padding_size, Eq(0u)))))); | |
| // If payload padding is > 2x requested size, plain padding is returned | |
| // instead. | |
| EXPECT_THAT( | |
| GeneratePadding(kMinTargerSizeForPayload - 1), | |
| AllOf(Not(IsEmpty()), | |
| Each(Pointee(Property(&RtpPacketToSend::padding_size, Gt(0u)))))); | |
| } | |
| TEST_F(RtpSenderTest, GeneratePaddingCreatesPurePaddingWithoutRtx) { | |
| packet_history_->SetStorePacketsStatus( | |
| RtpPacketHistory::StorageMode::kStoreAndCull, 1); | |
| ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( | |
| TransmissionOffset::Uri(), kTransmissionTimeOffsetExtensionId)); | |
| ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( | |
| AbsoluteSendTime::Uri(), kAbsoluteSendTimeExtensionId)); | |
| ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( | |
| TransportSequenceNumber::Uri(), kTransportSequenceNumberExtensionId)); | |
| const size_t kPayloadPacketSize = 1234; | |
| // Send a dummy video packet so it ends up in the packet history. Since we | |
| // are not using RTX, it should never be used as padding. | |
| std::unique_ptr<RtpPacketToSend> packet = | |
| BuildRtpPacket(kPayload, true, 0, env_.clock().CurrentTime()); | |
| packet->set_allow_retransmission(true); | |
| packet->SetPayloadSize(kPayloadPacketSize); | |
| packet->set_packet_type(RtpPacketMediaType::kVideo); | |
| sequencer_->Sequence(*packet); | |
| packet_history_->PutRtpPacket(std::move(packet), env_.clock().CurrentTime()); | |
| // Payload padding not available without RTX, only generate plain padding on | |
| // the media SSRC. | |
| // Number of padding packets is the requested padding size divided by max | |
| // padding packet size, rounded up. Pure padding packets are always of the | |
| // maximum size. | |
| const size_t kPaddingBytesRequested = kPayloadPacketSize + kRtxHeaderSize; | |
| const size_t kExpectedNumPaddingPackets = | |
| (kPaddingBytesRequested + kMaxPaddingLength - 1) / kMaxPaddingLength; | |
| size_t padding_bytes_generated = 0; | |
| std::vector<std::unique_ptr<RtpPacketToSend>> padding_packets = | |
| GeneratePadding(kPaddingBytesRequested); | |
| EXPECT_EQ(padding_packets.size(), kExpectedNumPaddingPackets); | |
| for (auto& generated_packet : padding_packets) { | |
| EXPECT_EQ(generated_packet->packet_type(), RtpPacketMediaType::kPadding); | |
| EXPECT_EQ(generated_packet->Ssrc(), kSsrc); | |
| EXPECT_EQ(generated_packet->payload_size(), 0u); | |
| EXPECT_GT(generated_packet->padding_size(), 0u); | |
| padding_bytes_generated += generated_packet->padding_size(); | |
| EXPECT_TRUE(generated_packet->HasExtension<TransportSequenceNumber>()); | |
| EXPECT_TRUE(generated_packet->HasExtension<AbsoluteSendTime>()); | |
| EXPECT_TRUE(generated_packet->HasExtension<TransmissionOffset>()); | |
| } | |
| EXPECT_EQ(padding_bytes_generated, | |
| kExpectedNumPaddingPackets * kMaxPaddingLength); | |
| } | |
| TEST_F(RtpSenderTest, SupportsPadding) { | |
| bool kSendingMediaStats[] = {true, false}; | |
| bool kEnableRedundantPayloads[] = {true, false}; | |
| absl::string_view kBweExtensionUris[] = { | |
| TransportSequenceNumber::Uri(), TransportSequenceNumberV2::Uri(), | |
| AbsoluteSendTime::Uri(), TransmissionOffset::Uri()}; | |
| const int kExtensionsId = 7; | |
| for (bool sending_media : kSendingMediaStats) { | |
| rtp_sender_->SetSendingMediaStatus(sending_media); | |
| for (bool redundant_payloads : kEnableRedundantPayloads) { | |
| int rtx_mode = kRtxRetransmitted; | |
| if (redundant_payloads) { | |
| rtx_mode |= kRtxRedundantPayloads; | |
| } | |
| rtp_sender_->SetRtxPayloadType(kRtxPayload, kPayload); | |
| rtp_sender_->SetRtxStatus(rtx_mode); | |
| for (auto extension_uri : kBweExtensionUris) { | |
| EXPECT_FALSE(rtp_sender_->SupportsPadding()); | |
| rtp_sender_->RegisterRtpHeaderExtension(extension_uri, kExtensionsId); | |
| if (!sending_media) { | |
| EXPECT_FALSE(rtp_sender_->SupportsPadding()); | |
| } else { | |
| EXPECT_TRUE(rtp_sender_->SupportsPadding()); | |
| if (redundant_payloads) { | |
| EXPECT_TRUE(rtp_sender_->SupportsRtxPayloadPadding()); | |
| } else { | |
| EXPECT_FALSE(rtp_sender_->SupportsRtxPayloadPadding()); | |
| } | |
| } | |
| rtp_sender_->DeregisterRtpHeaderExtension(extension_uri); | |
| EXPECT_FALSE(rtp_sender_->SupportsPadding()); | |
| } | |
| } | |
| } | |
| } | |
| TEST_F(RtpSenderTest, SetsCaptureTimeOnRtxRetransmissions) { | |
| EnableRtx(); | |
| // Put a packet in the packet history, with current time as capture time. | |
| const Timestamp start_time = env_.clock().CurrentTime(); | |
| std::unique_ptr<RtpPacketToSend> packet = | |
| BuildRtpPacket(kPayload, kMarkerBit, /*rtp_timestamp=*/0, | |
| /*capture_time=*/start_time); | |
| packet->set_allow_retransmission(true); | |
| sequencer_->Sequence(*packet); | |
| packet_history_->PutRtpPacket(std::move(packet), start_time); | |
| // Advance time, request an RTX retransmission. Capture timestamp should be | |
| // preserved. | |
| time_controller_.AdvanceTime(TimeDelta::Millis(10)); | |
| EXPECT_CALL(mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee( | |
| Property(&RtpPacketToSend::capture_time, start_time))))); | |
| EXPECT_GT(rtp_sender_->ReSendPacket(kSeqNum), 0); | |
| } | |
| TEST_F(RtpSenderTest, IgnoresNackAfterDisablingMedia) { | |
| const TimeDelta kRtt = TimeDelta::Millis(10); | |
| EnableRtx(); | |
| packet_history_->SetRtt(kRtt); | |
| // Put a packet in the history. | |
| const Timestamp start_time = env_.clock().CurrentTime(); | |
| std::unique_ptr<RtpPacketToSend> packet = | |
| BuildRtpPacket(kPayload, kMarkerBit, 0, /*capture_time=*/start_time); | |
| packet->set_allow_retransmission(true); | |
| sequencer_->Sequence(*packet); | |
| packet_history_->PutRtpPacket(std::move(packet), start_time); | |
| // Disable media sending and try to retransmit the packet, it should fail. | |
| rtp_sender_->SetSendingMediaStatus(false); | |
| time_controller_.AdvanceTime(kRtt); | |
| EXPECT_LT(rtp_sender_->ReSendPacket(kSeqNum), 0); | |
| } | |
| TEST_F(RtpSenderTest, DoesntFecProtectRetransmissions) { | |
| // Set up retranmission without RTX, so that a plain copy of the old packet is | |
| // re-sent instead. | |
| const TimeDelta kRtt = TimeDelta::Millis(10); | |
| rtp_sender_->SetSendingMediaStatus(true); | |
| rtp_sender_->SetRtxStatus(kRtxOff); | |
| packet_history_->SetStorePacketsStatus( | |
| RtpPacketHistory::StorageMode::kStoreAndCull, 10); | |
| packet_history_->SetRtt(kRtt); | |
| // Put a fec protected packet in the history. | |
| const Timestamp start_time = env_.clock().CurrentTime(); | |
| std::unique_ptr<RtpPacketToSend> packet = | |
| BuildRtpPacket(kPayload, kMarkerBit, 0, start_time); | |
| packet->set_allow_retransmission(true); | |
| packet->set_fec_protect_packet(true); | |
| sequencer_->Sequence(*packet); | |
| packet_history_->PutRtpPacket(std::move(packet), start_time); | |
| // Re-send packet, the retransmitted packet should not have the FEC protection | |
| // flag set. | |
| EXPECT_CALL(mock_paced_sender_, | |
| EnqueuePackets(ElementsAre(Pointee( | |
| Property(&RtpPacketToSend::fec_protect_packet, false))))); | |
| time_controller_.AdvanceTime(kRtt); | |
| EXPECT_GT(rtp_sender_->ReSendPacket(kSeqNum), 0); | |
| } | |
| TEST_F(RtpSenderTest, MarksPacketsWithKeyframeStatus) { | |
| RTPSenderVideo::Config video_config; | |
| video_config.clock = &env_.clock(); | |
| video_config.rtp_sender = rtp_sender_.get(); | |
| video_config.field_trials = &env_.field_trials(); | |
| RTPSenderVideo rtp_sender_video(video_config); | |
| const uint8_t kPayloadType = 127; | |
| const VideoCodecType kCodecType = VideoCodecType::kVideoCodecGeneric; | |
| const uint32_t kCaptureTimeMsToRtpTimestamp = 90; // 90 kHz clock | |
| { | |
| EXPECT_CALL(mock_paced_sender_, | |
| EnqueuePackets(Each( | |
| Pointee(Property(&RtpPacketToSend::is_key_frame, true))))) | |
| .Times(AtLeast(1)); | |
| RTPVideoHeader video_header; | |
| video_header.frame_type = VideoFrameType::kVideoFrameKey; | |
| Timestamp capture_time = env_.clock().CurrentTime(); | |
| EXPECT_TRUE(rtp_sender_video.SendVideo( | |
| kPayloadType, kCodecType, | |
| capture_time.ms() * kCaptureTimeMsToRtpTimestamp, capture_time, | |
| kPayloadData, sizeof(kPayloadData), video_header, | |
| kDefaultExpectedRetransmissionTime, {})); | |
| time_controller_.AdvanceTime(TimeDelta::Millis(33)); | |
| } | |
| { | |
| EXPECT_CALL(mock_paced_sender_, | |
| EnqueuePackets(Each( | |
| Pointee(Property(&RtpPacketToSend::is_key_frame, false))))) | |
| .Times(AtLeast(1)); | |
| RTPVideoHeader video_header; | |
| video_header.frame_type = VideoFrameType::kVideoFrameDelta; | |
| Timestamp capture_time = env_.clock().CurrentTime(); | |
| EXPECT_TRUE(rtp_sender_video.SendVideo( | |
| kPayloadType, kCodecType, | |
| capture_time.ms() * kCaptureTimeMsToRtpTimestamp, capture_time, | |
| kPayloadData, sizeof(kPayloadData), video_header, | |
| kDefaultExpectedRetransmissionTime, {})); | |
| time_controller_.AdvanceTime(TimeDelta::Millis(33)); | |
| } | |
| } | |
| } // namespace webrtc |