summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/video/end_to_end_tests/fec_tests.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/video/end_to_end_tests/fec_tests.cc
parentInitial commit. (diff)
downloadfirefox-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/fec_tests.cc')
-rw-r--r--third_party/libwebrtc/video/end_to_end_tests/fec_tests.cc536
1 files changed, 536 insertions, 0 deletions
diff --git a/third_party/libwebrtc/video/end_to_end_tests/fec_tests.cc b/third_party/libwebrtc/video/end_to_end_tests/fec_tests.cc
new file mode 100644
index 0000000000..11d11dcc0d
--- /dev/null
+++ b/third_party/libwebrtc/video/end_to_end_tests/fec_tests.cc
@@ -0,0 +1,536 @@
+/*
+ * 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 "api/task_queue/task_queue_base.h"
+#include "api/test/simulated_network.h"
+#include "api/test/video/function_video_encoder_factory.h"
+#include "call/fake_network_pipe.h"
+#include "call/simulated_network.h"
+#include "media/engine/internal_decoder_factory.h"
+#include "modules/include/module_common_types_public.h"
+#include "modules/rtp_rtcp/source/byte_io.h"
+#include "modules/rtp_rtcp/source/rtp_packet.h"
+#include "modules/video_coding/codecs/vp8/include/vp8.h"
+#include "rtc_base/synchronization/mutex.h"
+#include "test/call_test.h"
+#include "test/field_trial.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+#include "test/rtcp_packet_parser.h"
+#include "test/video_test_constants.h"
+
+using ::testing::Contains;
+using ::testing::Not;
+
+namespace webrtc {
+namespace {
+enum : int { // The first valid value is 1.
+ kTransportSequenceNumberExtensionId = 1,
+ kVideoRotationExtensionId,
+};
+} // namespace
+
+class FecEndToEndTest : public test::CallTest {
+ public:
+ FecEndToEndTest() {
+ RegisterRtpExtension(RtpExtension(RtpExtension::kTransportSequenceNumberUri,
+ kTransportSequenceNumberExtensionId));
+ RegisterRtpExtension(RtpExtension(RtpExtension::kVideoRotationUri,
+ kVideoRotationExtensionId));
+ }
+};
+
+TEST_F(FecEndToEndTest, ReceivesUlpfec) {
+ class UlpfecRenderObserver : public test::EndToEndTest,
+ public rtc::VideoSinkInterface<VideoFrame> {
+ public:
+ UlpfecRenderObserver()
+ : EndToEndTest(test::VideoTestConstants::kDefaultTimeout),
+ encoder_factory_([]() { return VP8Encoder::Create(); }),
+ random_(0xcafef00d1),
+ num_packets_sent_(0) {}
+
+ private:
+ Action OnSendRtp(rtc::ArrayView<const uint8_t> packet) override {
+ MutexLock lock(&mutex_);
+ RtpPacket rtp_packet;
+ EXPECT_TRUE(rtp_packet.Parse(packet));
+
+ EXPECT_TRUE(rtp_packet.PayloadType() ==
+ test::VideoTestConstants::kVideoSendPayloadType ||
+ rtp_packet.PayloadType() ==
+ test::VideoTestConstants::kRedPayloadType)
+ << "Unknown payload type received.";
+ EXPECT_EQ(test::VideoTestConstants::kVideoSendSsrcs[0], rtp_packet.Ssrc())
+ << "Unknown SSRC received.";
+
+ // Parse RED header.
+ int encapsulated_payload_type = -1;
+ if (rtp_packet.PayloadType() ==
+ test::VideoTestConstants::kRedPayloadType) {
+ encapsulated_payload_type = rtp_packet.payload()[0];
+
+ EXPECT_TRUE(encapsulated_payload_type ==
+ test::VideoTestConstants::kVideoSendPayloadType ||
+ encapsulated_payload_type ==
+ test::VideoTestConstants::kUlpfecPayloadType)
+ << "Unknown encapsulated payload type received.";
+ }
+
+ // To minimize test flakiness, always let ULPFEC packets through.
+ if (encapsulated_payload_type ==
+ test::VideoTestConstants::kUlpfecPayloadType) {
+ return SEND_PACKET;
+ }
+
+ // Simulate 5% video packet loss after rampup period. Record the
+ // corresponding timestamps that were dropped.
+ if (num_packets_sent_++ > 100 && random_.Rand(1, 100) <= 5) {
+ if (encapsulated_payload_type ==
+ test::VideoTestConstants::kVideoSendPayloadType) {
+ dropped_sequence_numbers_.insert(rtp_packet.SequenceNumber());
+ dropped_timestamps_.insert(rtp_packet.Timestamp());
+ }
+ return DROP_PACKET;
+ }
+
+ return SEND_PACKET;
+ }
+
+ void OnFrame(const VideoFrame& video_frame) override {
+ MutexLock lock(&mutex_);
+ // Rendering frame with timestamp of packet that was dropped -> FEC
+ // protection worked.
+ auto it = dropped_timestamps_.find(video_frame.timestamp());
+ if (it != dropped_timestamps_.end()) {
+ observation_complete_.Set();
+ }
+ }
+
+ void ModifyVideoConfigs(
+ VideoSendStream::Config* send_config,
+ std::vector<VideoReceiveStreamInterface::Config>* receive_configs,
+ VideoEncoderConfig* encoder_config) override {
+ // Use VP8 instead of FAKE, since the latter does not have PictureID
+ // in the packetization headers.
+ send_config->encoder_settings.encoder_factory = &encoder_factory_;
+ send_config->rtp.payload_name = "VP8";
+ send_config->rtp.payload_type =
+ test::VideoTestConstants::kVideoSendPayloadType;
+ encoder_config->codec_type = kVideoCodecVP8;
+ VideoReceiveStreamInterface::Decoder decoder =
+ test::CreateMatchingDecoder(*send_config);
+ (*receive_configs)[0].decoder_factory = &decoder_factory_;
+ (*receive_configs)[0].decoders.clear();
+ (*receive_configs)[0].decoders.push_back(decoder);
+
+ // Enable ULPFEC over RED.
+ send_config->rtp.ulpfec.red_payload_type =
+ test::VideoTestConstants::kRedPayloadType;
+ send_config->rtp.ulpfec.ulpfec_payload_type =
+ test::VideoTestConstants::kUlpfecPayloadType;
+ (*receive_configs)[0].rtp.red_payload_type =
+ test::VideoTestConstants::kRedPayloadType;
+ (*receive_configs)[0].rtp.ulpfec_payload_type =
+ test::VideoTestConstants::kUlpfecPayloadType;
+
+ (*receive_configs)[0].renderer = this;
+ }
+
+ void PerformTest() override {
+ EXPECT_TRUE(Wait())
+ << "Timed out waiting for dropped frames to be rendered.";
+ }
+
+ Mutex mutex_;
+ std::unique_ptr<VideoEncoder> encoder_;
+ test::FunctionVideoEncoderFactory encoder_factory_;
+ InternalDecoderFactory decoder_factory_;
+ std::set<uint32_t> dropped_sequence_numbers_ RTC_GUARDED_BY(mutex_);
+ // Several packets can have the same timestamp.
+ std::multiset<uint32_t> dropped_timestamps_ RTC_GUARDED_BY(mutex_);
+ Random random_;
+ int num_packets_sent_ RTC_GUARDED_BY(mutex_);
+ } test;
+
+ RunBaseTest(&test);
+}
+
+class FlexfecRenderObserver : public test::EndToEndTest,
+ public rtc::VideoSinkInterface<VideoFrame> {
+ public:
+ static constexpr uint32_t kVideoLocalSsrc = 123;
+ static constexpr uint32_t kFlexfecLocalSsrc = 456;
+
+ explicit FlexfecRenderObserver(bool enable_nack, bool expect_flexfec_rtcp)
+ : test::EndToEndTest(test::VideoTestConstants::kLongTimeout),
+ enable_nack_(enable_nack),
+ expect_flexfec_rtcp_(expect_flexfec_rtcp),
+ received_flexfec_rtcp_(false),
+ random_(0xcafef00d1),
+ num_packets_sent_(0) {}
+
+ size_t GetNumFlexfecStreams() const override { return 1; }
+
+ private:
+ Action OnSendRtp(rtc::ArrayView<const uint8_t> packet) override {
+ MutexLock lock(&mutex_);
+ RtpPacket rtp_packet;
+ EXPECT_TRUE(rtp_packet.Parse(packet));
+
+ EXPECT_TRUE(
+ rtp_packet.PayloadType() ==
+ test::VideoTestConstants::kFakeVideoSendPayloadType ||
+ rtp_packet.PayloadType() ==
+ test::VideoTestConstants::kFlexfecPayloadType ||
+ (enable_nack_ && rtp_packet.PayloadType() ==
+ test::VideoTestConstants::kSendRtxPayloadType))
+ << "Unknown payload type received.";
+ EXPECT_TRUE(
+ rtp_packet.Ssrc() == test::VideoTestConstants::kVideoSendSsrcs[0] ||
+ rtp_packet.Ssrc() == test::VideoTestConstants::kFlexfecSendSsrc ||
+ (enable_nack_ &&
+ rtp_packet.Ssrc() == test::VideoTestConstants::kSendRtxSsrcs[0]))
+ << "Unknown SSRC received.";
+
+ // To reduce test flakiness, always let FlexFEC packets through.
+ if (rtp_packet.PayloadType() ==
+ test::VideoTestConstants::kFlexfecPayloadType) {
+ EXPECT_EQ(test::VideoTestConstants::kFlexfecSendSsrc, rtp_packet.Ssrc());
+
+ return SEND_PACKET;
+ }
+
+ // To reduce test flakiness, always let RTX packets through.
+ if (rtp_packet.PayloadType() ==
+ test::VideoTestConstants::kSendRtxPayloadType) {
+ EXPECT_EQ(test::VideoTestConstants::kSendRtxSsrcs[0], rtp_packet.Ssrc());
+
+ if (rtp_packet.payload_size() == 0) {
+ // Pure padding packet.
+ return SEND_PACKET;
+ }
+
+ // Parse RTX header.
+ uint16_t original_sequence_number =
+ ByteReader<uint16_t>::ReadBigEndian(rtp_packet.payload().data());
+
+ // From the perspective of FEC, a retransmitted packet is no longer
+ // dropped, so remove it from list of dropped packets.
+ auto seq_num_it =
+ dropped_sequence_numbers_.find(original_sequence_number);
+ if (seq_num_it != dropped_sequence_numbers_.end()) {
+ dropped_sequence_numbers_.erase(seq_num_it);
+ auto ts_it = dropped_timestamps_.find(rtp_packet.Timestamp());
+ EXPECT_NE(ts_it, dropped_timestamps_.end());
+ dropped_timestamps_.erase(ts_it);
+ }
+
+ return SEND_PACKET;
+ }
+
+ // Simulate 5% video packet loss after rampup period. Record the
+ // corresponding timestamps that were dropped.
+ if (num_packets_sent_++ > 100 && random_.Rand(1, 100) <= 5) {
+ EXPECT_EQ(test::VideoTestConstants::kFakeVideoSendPayloadType,
+ rtp_packet.PayloadType());
+ EXPECT_EQ(test::VideoTestConstants::kVideoSendSsrcs[0],
+ rtp_packet.Ssrc());
+
+ dropped_sequence_numbers_.insert(rtp_packet.SequenceNumber());
+ dropped_timestamps_.insert(rtp_packet.Timestamp());
+
+ return DROP_PACKET;
+ }
+
+ return SEND_PACKET;
+ }
+
+ Action OnReceiveRtcp(rtc::ArrayView<const uint8_t> data) override {
+ test::RtcpPacketParser parser;
+
+ parser.Parse(data);
+ if (parser.sender_ssrc() == kFlexfecLocalSsrc) {
+ EXPECT_EQ(1, parser.receiver_report()->num_packets());
+ const std::vector<rtcp::ReportBlock>& report_blocks =
+ parser.receiver_report()->report_blocks();
+ if (!report_blocks.empty()) {
+ EXPECT_EQ(1U, report_blocks.size());
+ EXPECT_EQ(test::VideoTestConstants::kFlexfecSendSsrc,
+ report_blocks[0].source_ssrc());
+ MutexLock lock(&mutex_);
+ received_flexfec_rtcp_ = true;
+ }
+ }
+
+ return SEND_PACKET;
+ }
+
+ BuiltInNetworkBehaviorConfig GetSendTransportConfig() const override {
+ // At low RTT (< kLowRttNackMs) -> NACK only, no FEC.
+ const int kNetworkDelayMs = 100;
+ BuiltInNetworkBehaviorConfig config;
+ config.queue_delay_ms = kNetworkDelayMs;
+ return config;
+ }
+
+ void OnFrame(const VideoFrame& video_frame) override {
+ EXPECT_EQ(kVideoRotation_90, video_frame.rotation());
+
+ MutexLock lock(&mutex_);
+ // Rendering frame with timestamp of packet that was dropped -> FEC
+ // protection worked.
+ auto it = dropped_timestamps_.find(video_frame.timestamp());
+ if (it != dropped_timestamps_.end()) {
+ if (!expect_flexfec_rtcp_ || received_flexfec_rtcp_) {
+ observation_complete_.Set();
+ }
+ }
+ }
+
+ void ModifyVideoConfigs(
+ VideoSendStream::Config* send_config,
+ std::vector<VideoReceiveStreamInterface::Config>* receive_configs,
+ VideoEncoderConfig* encoder_config) override {
+ (*receive_configs)[0].rtp.local_ssrc = kVideoLocalSsrc;
+ (*receive_configs)[0].renderer = this;
+
+ if (enable_nack_) {
+ send_config->rtp.nack.rtp_history_ms =
+ test::VideoTestConstants::kNackRtpHistoryMs;
+ send_config->rtp.rtx.ssrcs.push_back(
+ test::VideoTestConstants::kSendRtxSsrcs[0]);
+ send_config->rtp.rtx.payload_type =
+ test::VideoTestConstants::kSendRtxPayloadType;
+
+ (*receive_configs)[0].rtp.nack.rtp_history_ms =
+ test::VideoTestConstants::kNackRtpHistoryMs;
+ (*receive_configs)[0].rtp.rtx_ssrc =
+ test::VideoTestConstants::kSendRtxSsrcs[0];
+ (*receive_configs)[0].rtp.rtx_associated_payload_types
+ [test::VideoTestConstants::kSendRtxPayloadType] =
+ test::VideoTestConstants::kVideoSendPayloadType;
+ }
+ }
+
+ void OnFrameGeneratorCapturerCreated(
+ test::FrameGeneratorCapturer* frame_generator_capturer) override {
+ frame_generator_capturer->SetFakeRotation(kVideoRotation_90);
+ }
+
+ void ModifyFlexfecConfigs(
+ std::vector<FlexfecReceiveStream::Config>* receive_configs) override {
+ (*receive_configs)[0].rtp.local_ssrc = kFlexfecLocalSsrc;
+ }
+
+ void PerformTest() override {
+ EXPECT_TRUE(Wait())
+ << "Timed out waiting for dropped frames to be rendered.";
+ }
+
+ Mutex mutex_;
+ std::set<uint32_t> dropped_sequence_numbers_ RTC_GUARDED_BY(mutex_);
+ // Several packets can have the same timestamp.
+ std::multiset<uint32_t> dropped_timestamps_ RTC_GUARDED_BY(mutex_);
+ const bool enable_nack_;
+ const bool expect_flexfec_rtcp_;
+ bool received_flexfec_rtcp_ RTC_GUARDED_BY(mutex_);
+ Random random_;
+ int num_packets_sent_;
+};
+
+TEST_F(FecEndToEndTest, RecoversWithFlexfec) {
+ FlexfecRenderObserver test(false, false);
+ RunBaseTest(&test);
+}
+
+TEST_F(FecEndToEndTest, RecoversWithFlexfecAndNack) {
+ FlexfecRenderObserver test(true, false);
+ RunBaseTest(&test);
+}
+
+TEST_F(FecEndToEndTest, RecoversWithFlexfecAndSendsCorrespondingRtcp) {
+ FlexfecRenderObserver test(false, true);
+ RunBaseTest(&test);
+}
+
+TEST_F(FecEndToEndTest, ReceivedUlpfecPacketsNotNacked) {
+ class UlpfecNackObserver : public test::EndToEndTest {
+ public:
+ UlpfecNackObserver()
+ : EndToEndTest(test::VideoTestConstants::kDefaultTimeout),
+ state_(kFirstPacket),
+ ulpfec_sequence_number_(0),
+ has_last_sequence_number_(false),
+ last_sequence_number_(0),
+ encoder_factory_([]() { return VP8Encoder::Create(); }) {}
+
+ private:
+ Action OnSendRtp(rtc::ArrayView<const uint8_t> packet) override {
+ MutexLock lock_(&mutex_);
+ RtpPacket rtp_packet;
+ EXPECT_TRUE(rtp_packet.Parse(packet));
+
+ int encapsulated_payload_type = -1;
+ if (rtp_packet.PayloadType() ==
+ test::VideoTestConstants::kRedPayloadType) {
+ encapsulated_payload_type = rtp_packet.payload()[0];
+ if (encapsulated_payload_type !=
+ test::VideoTestConstants::kFakeVideoSendPayloadType)
+ EXPECT_EQ(test::VideoTestConstants::kUlpfecPayloadType,
+ encapsulated_payload_type);
+ } else {
+ EXPECT_EQ(test::VideoTestConstants::kFakeVideoSendPayloadType,
+ rtp_packet.PayloadType());
+ }
+
+ if (has_last_sequence_number_ &&
+ !IsNewerSequenceNumber(rtp_packet.SequenceNumber(),
+ last_sequence_number_)) {
+ // Drop retransmitted packets.
+ return DROP_PACKET;
+ }
+ last_sequence_number_ = rtp_packet.SequenceNumber();
+ has_last_sequence_number_ = true;
+
+ bool ulpfec_packet = encapsulated_payload_type ==
+ test::VideoTestConstants::kUlpfecPayloadType;
+ switch (state_) {
+ case kFirstPacket:
+ state_ = kDropEveryOtherPacketUntilUlpfec;
+ break;
+ case kDropEveryOtherPacketUntilUlpfec:
+ if (ulpfec_packet) {
+ state_ = kDropAllMediaPacketsUntilUlpfec;
+ } else if (rtp_packet.SequenceNumber() % 2 == 0) {
+ return DROP_PACKET;
+ }
+ break;
+ case kDropAllMediaPacketsUntilUlpfec:
+ if (!ulpfec_packet)
+ return DROP_PACKET;
+ ulpfec_sequence_number_ = rtp_packet.SequenceNumber();
+ state_ = kDropOneMediaPacket;
+ break;
+ case kDropOneMediaPacket:
+ if (ulpfec_packet)
+ return DROP_PACKET;
+ state_ = kPassOneMediaPacket;
+ return DROP_PACKET;
+ case kPassOneMediaPacket:
+ if (ulpfec_packet)
+ return DROP_PACKET;
+ // Pass one media packet after dropped packet after last FEC,
+ // otherwise receiver might never see a seq_no after
+ // `ulpfec_sequence_number_`
+ state_ = kVerifyUlpfecPacketNotInNackList;
+ break;
+ case kVerifyUlpfecPacketNotInNackList:
+ // Continue to drop packets. Make sure no frame can be decoded.
+ if (ulpfec_packet || rtp_packet.SequenceNumber() % 2 == 0)
+ return DROP_PACKET;
+ break;
+ }
+ return SEND_PACKET;
+ }
+
+ Action OnReceiveRtcp(rtc::ArrayView<const uint8_t> packet) override {
+ MutexLock lock_(&mutex_);
+ if (state_ == kVerifyUlpfecPacketNotInNackList) {
+ test::RtcpPacketParser rtcp_parser;
+ rtcp_parser.Parse(packet);
+ const std::vector<uint16_t>& nacks = rtcp_parser.nack()->packet_ids();
+ EXPECT_THAT(nacks, Not(Contains(ulpfec_sequence_number_)))
+ << "Got nack for ULPFEC packet";
+ if (!nacks.empty() &&
+ IsNewerSequenceNumber(nacks.back(), ulpfec_sequence_number_)) {
+ observation_complete_.Set();
+ }
+ }
+ return SEND_PACKET;
+ }
+
+ BuiltInNetworkBehaviorConfig GetSendTransportConfig() const override {
+ // At low RTT (< kLowRttNackMs) -> NACK only, no FEC.
+ // Configure some network delay.
+ const int kNetworkDelayMs = 50;
+ BuiltInNetworkBehaviorConfig config;
+ config.queue_delay_ms = kNetworkDelayMs;
+ return config;
+ }
+
+ // TODO(holmer): Investigate why we don't send FEC packets when the bitrate
+ // is 10 kbps.
+ void ModifySenderBitrateConfig(
+ BitrateConstraints* bitrate_config) override {
+ const int kMinBitrateBps = 30000;
+ bitrate_config->min_bitrate_bps = kMinBitrateBps;
+ }
+
+ void ModifyVideoConfigs(
+ VideoSendStream::Config* send_config,
+ std::vector<VideoReceiveStreamInterface::Config>* receive_configs,
+ VideoEncoderConfig* encoder_config) override {
+ // Configure hybrid NACK/FEC.
+ send_config->rtp.nack.rtp_history_ms =
+ test::VideoTestConstants::kNackRtpHistoryMs;
+ send_config->rtp.ulpfec.red_payload_type =
+ test::VideoTestConstants::kRedPayloadType;
+ send_config->rtp.ulpfec.ulpfec_payload_type =
+ test::VideoTestConstants::kUlpfecPayloadType;
+ // Set codec to VP8, otherwise NACK/FEC hybrid will be disabled.
+ send_config->encoder_settings.encoder_factory = &encoder_factory_;
+ send_config->rtp.payload_name = "VP8";
+ send_config->rtp.payload_type =
+ test::VideoTestConstants::kFakeVideoSendPayloadType;
+ encoder_config->codec_type = kVideoCodecVP8;
+
+ (*receive_configs)[0].rtp.nack.rtp_history_ms =
+ test::VideoTestConstants::kNackRtpHistoryMs;
+ (*receive_configs)[0].rtp.red_payload_type =
+ test::VideoTestConstants::kRedPayloadType;
+ (*receive_configs)[0].rtp.ulpfec_payload_type =
+ test::VideoTestConstants::kUlpfecPayloadType;
+
+ (*receive_configs)[0].decoders.resize(1);
+ (*receive_configs)[0].decoders[0].payload_type =
+ send_config->rtp.payload_type;
+ (*receive_configs)[0].decoders[0].video_format =
+ SdpVideoFormat(send_config->rtp.payload_name);
+ (*receive_configs)[0].decoder_factory = &decoder_factory_;
+ }
+
+ void PerformTest() override {
+ EXPECT_TRUE(Wait())
+ << "Timed out while waiting for FEC packets to be received.";
+ }
+
+ enum {
+ kFirstPacket,
+ kDropEveryOtherPacketUntilUlpfec,
+ kDropAllMediaPacketsUntilUlpfec,
+ kDropOneMediaPacket,
+ kPassOneMediaPacket,
+ kVerifyUlpfecPacketNotInNackList,
+ } state_;
+
+ Mutex mutex_;
+ uint16_t ulpfec_sequence_number_ RTC_GUARDED_BY(&mutex_);
+ bool has_last_sequence_number_;
+ uint16_t last_sequence_number_;
+ test::FunctionVideoEncoderFactory encoder_factory_;
+ InternalDecoderFactory decoder_factory_;
+ } test;
+
+ RunBaseTest(&test);
+}
+} // namespace webrtc