diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/video/end_to_end_tests/transport_feedback_tests.cc | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/video/end_to_end_tests/transport_feedback_tests.cc')
-rw-r--r-- | third_party/libwebrtc/video/end_to_end_tests/transport_feedback_tests.cc | 484 |
1 files changed, 484 insertions, 0 deletions
diff --git a/third_party/libwebrtc/video/end_to_end_tests/transport_feedback_tests.cc b/third_party/libwebrtc/video/end_to_end_tests/transport_feedback_tests.cc new file mode 100644 index 0000000000..36be6d9015 --- /dev/null +++ b/third_party/libwebrtc/video/end_to_end_tests/transport_feedback_tests.cc @@ -0,0 +1,484 @@ +/* + * Copyright 2018 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 <memory> +#include <vector> + +#include "api/rtp_parameters.h" +#include "api/task_queue/task_queue_base.h" +#include "api/units/time_delta.h" +#include "call/call.h" +#include "call/fake_network_pipe.h" +#include "call/simulated_network.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtp_header_extensions.h" +#include "modules/rtp_rtcp/source/rtp_packet.h" +#include "rtc_base/numerics/sequence_number_unwrapper.h" +#include "rtc_base/synchronization/mutex.h" +#include "test/call_test.h" +#include "test/field_trial.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" +#include "test/video_test_constants.h" +#include "video/end_to_end_tests/multi_stream_tester.h" + +namespace webrtc { +namespace { +enum : int { // The first valid value is 1. + kTransportSequenceNumberExtensionId = 1, +}; +} // namespace + +TEST(TransportFeedbackMultiStreamTest, AssignsTransportSequenceNumbers) { + static constexpr int kSendRtxPayloadType = 98; + static constexpr TimeDelta kDefaultTimeout = TimeDelta::Seconds(30); + static constexpr int kNackRtpHistoryMs = 1000; + static constexpr uint32_t kSendRtxSsrcs[MultiStreamTester::kNumStreams] = { + 0xBADCAFD, 0xBADCAFE, 0xBADCAFF}; + + class RtpExtensionHeaderObserver : public test::DirectTransport { + public: + RtpExtensionHeaderObserver( + TaskQueueBase* task_queue, + Call* sender_call, + const std::map<uint32_t, uint32_t>& ssrc_map, + const std::map<uint8_t, MediaType>& payload_type_map, + rtc::ArrayView<const RtpExtension> audio_extensions, + rtc::ArrayView<const RtpExtension> video_extensions) + : DirectTransport(task_queue, + std::make_unique<FakeNetworkPipe>( + Clock::GetRealTimeClock(), + std::make_unique<SimulatedNetwork>( + BuiltInNetworkBehaviorConfig())), + sender_call, + payload_type_map, + audio_extensions, + video_extensions), + rtx_to_media_ssrcs_(ssrc_map), + rtx_padding_observed_(false), + retransmit_observed_(false), + started_(false) { + extensions_.Register<TransportSequenceNumber>( + kTransportSequenceNumberExtensionId); + } + virtual ~RtpExtensionHeaderObserver() {} + + bool SendRtp(rtc::ArrayView<const uint8_t> data, + const PacketOptions& options) override { + { + MutexLock lock(&lock_); + + if (IsDone()) + return false; + + if (started_) { + RtpPacket rtp_packet(&extensions_); + EXPECT_TRUE(rtp_packet.Parse(data)); + bool drop_packet = false; + + uint16_t transport_sequence_number = 0; + EXPECT_TRUE(rtp_packet.GetExtension<TransportSequenceNumber>( + &transport_sequence_number)); + EXPECT_EQ(options.packet_id, transport_sequence_number); + if (!streams_observed_.empty()) { + // Unwrap packet id and verify uniqueness. + int64_t packet_id = unwrapper_.Unwrap(options.packet_id); + EXPECT_TRUE(received_packed_ids_.insert(packet_id).second); + } + + // Drop (up to) every 17th packet, so we get retransmits. + // Only drop media, do not drop padding packets. + if (rtp_packet.PayloadType() != kSendRtxPayloadType && + rtp_packet.payload_size() > 0 && + transport_sequence_number % 17 == 0) { + dropped_seq_[rtp_packet.Ssrc()].insert(rtp_packet.SequenceNumber()); + drop_packet = true; + } + + if (rtp_packet.payload_size() == 0) { + // Ignore padding packets. + } else if (rtp_packet.PayloadType() == kSendRtxPayloadType) { + uint16_t original_sequence_number = + ByteReader<uint16_t>::ReadBigEndian( + rtp_packet.payload().data()); + uint32_t original_ssrc = + rtx_to_media_ssrcs_.find(rtp_packet.Ssrc())->second; + std::set<uint16_t>* seq_no_map = &dropped_seq_[original_ssrc]; + auto it = seq_no_map->find(original_sequence_number); + if (it != seq_no_map->end()) { + retransmit_observed_ = true; + seq_no_map->erase(it); + } else { + rtx_padding_observed_ = true; + } + } else { + streams_observed_.insert(rtp_packet.Ssrc()); + } + + if (IsDone()) + done_.Set(); + + if (drop_packet) + return true; + } + } + + return test::DirectTransport::SendRtp(data, options); + } + + bool IsDone() { + bool observed_types_ok = + streams_observed_.size() == MultiStreamTester::kNumStreams && + retransmit_observed_ && rtx_padding_observed_; + if (!observed_types_ok) + return false; + // We should not have any gaps in the sequence number range. + size_t seqno_range = + *received_packed_ids_.rbegin() - *received_packed_ids_.begin() + 1; + return seqno_range == received_packed_ids_.size(); + } + + bool Wait() { + { + // Can't be sure until this point that rtx_to_media_ssrcs_ etc have + // been initialized and are OK to read. + MutexLock lock(&lock_); + started_ = true; + } + return done_.Wait(kDefaultTimeout); + } + + private: + Mutex lock_; + rtc::Event done_; + RtpHeaderExtensionMap extensions_; + RtpSequenceNumberUnwrapper unwrapper_; + std::set<int64_t> received_packed_ids_; + std::set<uint32_t> streams_observed_; + std::map<uint32_t, std::set<uint16_t>> dropped_seq_; + const std::map<uint32_t, uint32_t>& rtx_to_media_ssrcs_; + bool rtx_padding_observed_; + bool retransmit_observed_; + bool started_; + }; + + class TransportSequenceNumberTester : public MultiStreamTester { + public: + TransportSequenceNumberTester() : observer_(nullptr) {} + ~TransportSequenceNumberTester() override = default; + + protected: + void Wait() override { + RTC_DCHECK(observer_); + EXPECT_TRUE(observer_->Wait()); + } + + void UpdateSendConfig( + size_t stream_index, + VideoSendStream::Config* send_config, + VideoEncoderConfig* encoder_config, + test::FrameGeneratorCapturer** frame_generator) override { + send_config->rtp.extensions.clear(); + send_config->rtp.extensions.push_back( + RtpExtension(RtpExtension::kTransportSequenceNumberUri, + kTransportSequenceNumberExtensionId)); + + // Force some padding to be sent. Note that since we do send media + // packets we can not guarantee that a padding only packet is sent. + // Instead, padding will most likely be send as an RTX packet. + const int kPaddingBitrateBps = 50000; + encoder_config->max_bitrate_bps = 200000; + encoder_config->min_transmit_bitrate_bps = + encoder_config->max_bitrate_bps + kPaddingBitrateBps; + + // Configure RTX for redundant payload padding. + send_config->rtp.nack.rtp_history_ms = kNackRtpHistoryMs; + send_config->rtp.rtx.ssrcs.push_back(kSendRtxSsrcs[stream_index]); + send_config->rtp.rtx.payload_type = kSendRtxPayloadType; + rtx_to_media_ssrcs_[kSendRtxSsrcs[stream_index]] = + send_config->rtp.ssrcs[0]; + } + + void UpdateReceiveConfig( + size_t stream_index, + VideoReceiveStreamInterface::Config* receive_config) override { + receive_config->rtp.nack.rtp_history_ms = kNackRtpHistoryMs; + receive_config->renderer = &fake_renderer_; + } + + std::unique_ptr<test::DirectTransport> CreateSendTransport( + TaskQueueBase* task_queue, + Call* sender_call) override { + std::map<uint8_t, MediaType> payload_type_map = + MultiStreamTester::payload_type_map_; + RTC_DCHECK(payload_type_map.find(kSendRtxPayloadType) == + payload_type_map.end()); + payload_type_map[kSendRtxPayloadType] = MediaType::VIDEO; + std::vector<RtpExtension> extensions = { + RtpExtension(RtpExtension::kTransportSequenceNumberUri, + kTransportSequenceNumberExtensionId)}; + auto observer = std::make_unique<RtpExtensionHeaderObserver>( + task_queue, sender_call, rtx_to_media_ssrcs_, payload_type_map, + extensions, extensions); + observer_ = observer.get(); + return observer; + } + + private: + test::FakeVideoRenderer fake_renderer_; + std::map<uint32_t, uint32_t> rtx_to_media_ssrcs_; + RtpExtensionHeaderObserver* observer_; + } tester; + + tester.RunTest(); +} + +class TransportFeedbackEndToEndTest : public test::CallTest { + public: + TransportFeedbackEndToEndTest() { + RegisterRtpExtension(RtpExtension(RtpExtension::kTransportSequenceNumberUri, + kTransportSequenceNumberExtensionId)); + } +}; + +class TransportFeedbackTester : public test::EndToEndTest { + public: + TransportFeedbackTester(size_t num_video_streams, size_t num_audio_streams) + : EndToEndTest(test::VideoTestConstants::kDefaultTimeout), + num_video_streams_(num_video_streams), + num_audio_streams_(num_audio_streams), + receiver_call_(nullptr) { + // Only one stream of each supported for now. + EXPECT_LE(num_video_streams, 1u); + EXPECT_LE(num_audio_streams, 1u); + } + + protected: + Action OnSendRtcp(rtc::ArrayView<const uint8_t> data) override { + EXPECT_FALSE(HasTransportFeedback(data)); + return SEND_PACKET; + } + + Action OnReceiveRtcp(rtc::ArrayView<const uint8_t> data) override { + if (HasTransportFeedback(data)) + observation_complete_.Set(); + return SEND_PACKET; + } + + bool HasTransportFeedback(rtc::ArrayView<const uint8_t> data) const { + test::RtcpPacketParser parser; + EXPECT_TRUE(parser.Parse(data)); + return parser.transport_feedback()->num_packets() > 0; + } + + void PerformTest() override { + EXPECT_TRUE( + observation_complete_.Wait(test::VideoTestConstants::kDefaultTimeout)); + } + + void OnCallsCreated(Call* sender_call, Call* receiver_call) override { + receiver_call_ = receiver_call; + } + + size_t GetNumVideoStreams() const override { return num_video_streams_; } + size_t GetNumAudioStreams() const override { return num_audio_streams_; } + + void ModifyAudioConfigs(AudioSendStream::Config* send_config, + std::vector<AudioReceiveStreamInterface::Config>* + receive_configs) override { + send_config->rtp.extensions.clear(); + send_config->rtp.extensions.push_back( + RtpExtension(RtpExtension::kTransportSequenceNumberUri, + kTransportSequenceNumberExtensionId)); + } + + private: + const size_t num_video_streams_; + const size_t num_audio_streams_; + Call* receiver_call_; +}; + +TEST_F(TransportFeedbackEndToEndTest, VideoReceivesTransportFeedback) { + TransportFeedbackTester test(1, 0); + RunBaseTest(&test); +} +TEST_F(TransportFeedbackEndToEndTest, AudioReceivesTransportFeedback) { + TransportFeedbackTester test(0, 1); + RunBaseTest(&test); +} + +TEST_F(TransportFeedbackEndToEndTest, AudioVideoReceivesTransportFeedback) { + TransportFeedbackTester test(1, 1); + RunBaseTest(&test); +} + +TEST_F(TransportFeedbackEndToEndTest, + StopsAndResumesMediaWhenCongestionWindowFull) { + test::ScopedFieldTrials override_field_trials( + "WebRTC-CongestionWindow/QueueSize:250/"); + + class TransportFeedbackTester : public test::EndToEndTest { + public: + TransportFeedbackTester(size_t num_video_streams, size_t num_audio_streams) + : EndToEndTest(test::VideoTestConstants::kDefaultTimeout), + num_video_streams_(num_video_streams), + num_audio_streams_(num_audio_streams), + media_sent_(0), + media_sent_before_(0), + padding_sent_(0) { + // Only one stream of each supported for now. + EXPECT_LE(num_video_streams, 1u); + EXPECT_LE(num_audio_streams, 1u); + } + + protected: + Action OnSendRtp(rtc::ArrayView<const uint8_t> packet) override { + RtpPacket rtp_packet; + EXPECT_TRUE(rtp_packet.Parse(packet)); + const bool only_padding = rtp_packet.payload_size() == 0; + MutexLock lock(&mutex_); + // Padding is expected in congested state to probe for connectivity when + // packets has been dropped. + if (only_padding) { + media_sent_before_ = media_sent_; + ++padding_sent_; + } else { + ++media_sent_; + if (padding_sent_ == 0) { + ++media_sent_before_; + EXPECT_LT(media_sent_, 40) + << "Media sent without feedback when congestion window is full."; + } else if (media_sent_ > media_sent_before_) { + observation_complete_.Set(); + } + } + return SEND_PACKET; + } + + Action OnReceiveRtcp(rtc::ArrayView<const uint8_t> data) override { + MutexLock lock(&mutex_); + // To fill up the congestion window we drop feedback on packets after 20 + // packets have been sent. This means that any packets that has not yet + // received feedback after that will be considered as oustanding data and + // therefore filling up the congestion window. In the congested state, the + // pacer should send padding packets to trigger feedback in case all + // feedback of previous traffic was lost. This test listens for the + // padding packets and when 2 padding packets have been received, feedback + // will be let trough again. This should cause the pacer to continue + // sending meadia yet again. + if (media_sent_ > 20 && HasTransportFeedback(data) && padding_sent_ < 2) { + return DROP_PACKET; + } + return SEND_PACKET; + } + + bool HasTransportFeedback(rtc::ArrayView<const uint8_t> data) const { + test::RtcpPacketParser parser; + EXPECT_TRUE(parser.Parse(data)); + return parser.transport_feedback()->num_packets() > 0; + } + void ModifySenderBitrateConfig( + BitrateConstraints* bitrate_config) override { + bitrate_config->max_bitrate_bps = 300000; + } + + void PerformTest() override { + constexpr TimeDelta kFailureTimeout = TimeDelta::Seconds(10); + EXPECT_TRUE(observation_complete_.Wait(kFailureTimeout)) + << "Stream not continued after congestion window full."; + } + + size_t GetNumVideoStreams() const override { return num_video_streams_; } + size_t GetNumAudioStreams() const override { return num_audio_streams_; } + + private: + const size_t num_video_streams_; + const size_t num_audio_streams_; + Mutex mutex_; + int media_sent_ RTC_GUARDED_BY(mutex_); + int media_sent_before_ RTC_GUARDED_BY(mutex_); + int padding_sent_ RTC_GUARDED_BY(mutex_); + } test(1, 0); + RunBaseTest(&test); +} + +TEST_F(TransportFeedbackEndToEndTest, TransportSeqNumOnAudioAndVideo) { + static constexpr size_t kMinPacketsToWaitFor = 50; + class TransportSequenceNumberTest : public test::EndToEndTest { + public: + TransportSequenceNumberTest() + : EndToEndTest(test::VideoTestConstants::kDefaultTimeout), + video_observed_(false), + audio_observed_(false) { + extensions_.Register<TransportSequenceNumber>( + kTransportSequenceNumberExtensionId); + } + + size_t GetNumVideoStreams() const override { return 1; } + size_t GetNumAudioStreams() const override { return 1; } + + void ModifyAudioConfigs(AudioSendStream::Config* send_config, + std::vector<AudioReceiveStreamInterface::Config>* + receive_configs) override { + send_config->rtp.extensions.clear(); + send_config->rtp.extensions.push_back( + RtpExtension(RtpExtension::kTransportSequenceNumberUri, + kTransportSequenceNumberExtensionId)); + } + + Action OnSendRtp(rtc::ArrayView<const uint8_t> packet) override { + RtpPacket rtp_packet(&extensions_); + EXPECT_TRUE(rtp_packet.Parse(packet)); + uint16_t transport_sequence_number = 0; + EXPECT_TRUE(rtp_packet.GetExtension<TransportSequenceNumber>( + &transport_sequence_number)); + // Unwrap packet id and verify uniqueness. + int64_t packet_id = unwrapper_.Unwrap(transport_sequence_number); + EXPECT_TRUE(received_packet_ids_.insert(packet_id).second); + + if (rtp_packet.Ssrc() == test::VideoTestConstants::kVideoSendSsrcs[0]) + video_observed_ = true; + if (rtp_packet.Ssrc() == test::VideoTestConstants::kAudioSendSsrc) + audio_observed_ = true; + if (audio_observed_ && video_observed_ && + received_packet_ids_.size() >= kMinPacketsToWaitFor) { + size_t packet_id_range = + *received_packet_ids_.rbegin() - *received_packet_ids_.begin() + 1; + EXPECT_EQ(received_packet_ids_.size(), packet_id_range); + observation_complete_.Set(); + } + return SEND_PACKET; + } + + void PerformTest() override { + EXPECT_TRUE(Wait()) << "Timed out while waiting for audio and video " + "packets with transport sequence number."; + } + + void ExpectSuccessful() { + EXPECT_TRUE(video_observed_); + EXPECT_TRUE(audio_observed_); + EXPECT_GE(received_packet_ids_.size(), kMinPacketsToWaitFor); + } + + private: + bool video_observed_; + bool audio_observed_; + RtpSequenceNumberUnwrapper unwrapper_; + std::set<int64_t> received_packet_ids_; + RtpHeaderExtensionMap extensions_; + } test; + + RunBaseTest(&test); + // Double check conditions for successful test to produce better error + // message when the test fail. + test.ExpectSuccessful(); +} +} // namespace webrtc |