/* * 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 "api/test/simulated_network.h" #include "call/fake_network_pipe.h" #include "call/simulated_network.h" #include "modules/rtp_rtcp/source/byte_io.h" #include "modules/video_coding/codecs/vp8/include/vp8.h" #include "test/call_test.h" #include "test/field_trial.h" #include "test/function_video_encoder_factory.h" #include "test/gtest.h" #include "test/rtcp_packet_parser.h" namespace webrtc { class FecEndToEndTest : public test::CallTest, public testing::WithParamInterface { public: FecEndToEndTest() : field_trial_(GetParam()) {} private: test::ScopedFieldTrials field_trial_; }; INSTANTIATE_TEST_CASE_P(RoundRobin, FecEndToEndTest, ::testing::Values("WebRTC-RoundRobinPacing/Disabled/", "WebRTC-RoundRobinPacing/Enabled/")); TEST_P(FecEndToEndTest, ReceivesUlpfec) { class UlpfecRenderObserver : public test::EndToEndTest, public rtc::VideoSinkInterface { public: UlpfecRenderObserver() : EndToEndTest(kDefaultTimeoutMs), encoder_factory_([]() { return VP8Encoder::Create(); }), random_(0xcafef00d1), num_packets_sent_(0) {} private: Action OnSendRtp(const uint8_t* packet, size_t length) override { rtc::CritScope lock(&crit_); RTPHeader header; EXPECT_TRUE(parser_->Parse(packet, length, &header)); EXPECT_TRUE(header.payloadType == kVideoSendPayloadType || header.payloadType == kRedPayloadType) << "Unknown payload type received."; EXPECT_EQ(kVideoSendSsrcs[0], header.ssrc) << "Unknown SSRC received."; // Parse RED header. int encapsulated_payload_type = -1; if (header.payloadType == kRedPayloadType) { encapsulated_payload_type = static_cast(packet[header.headerLength]); EXPECT_TRUE(encapsulated_payload_type == kVideoSendPayloadType || encapsulated_payload_type == kUlpfecPayloadType) << "Unknown encapsulated payload type received."; } // To minimize test flakiness, always let ULPFEC packets through. if (encapsulated_payload_type == 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 == kVideoSendPayloadType) { dropped_sequence_numbers_.insert(header.sequenceNumber); dropped_timestamps_.insert(header.timestamp); } return DROP_PACKET; } return SEND_PACKET; } void OnFrame(const VideoFrame& video_frame) override { rtc::CritScope lock(&crit_); // 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* 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 = kVideoSendPayloadType; encoder_config->codec_type = kVideoCodecVP8; VideoReceiveStream::Decoder decoder = test::CreateMatchingDecoder(*send_config); decoder_.reset(decoder.decoder); (*receive_configs)[0].decoders.clear(); (*receive_configs)[0].decoders.push_back(decoder); // Enable ULPFEC over RED. send_config->rtp.ulpfec.red_payload_type = kRedPayloadType; send_config->rtp.ulpfec.ulpfec_payload_type = kUlpfecPayloadType; (*receive_configs)[0].rtp.red_payload_type = kRedPayloadType; (*receive_configs)[0].rtp.ulpfec_payload_type = kUlpfecPayloadType; (*receive_configs)[0].renderer = this; } void PerformTest() override { EXPECT_TRUE(Wait()) << "Timed out waiting for dropped frames to be rendered."; } rtc::CriticalSection crit_; std::unique_ptr encoder_; test::FunctionVideoEncoderFactory encoder_factory_; std::unique_ptr decoder_; std::set dropped_sequence_numbers_ RTC_GUARDED_BY(crit_); // Several packets can have the same timestamp. std::multiset dropped_timestamps_ RTC_GUARDED_BY(crit_); Random random_; int num_packets_sent_ RTC_GUARDED_BY(crit_); } test; RunBaseTest(&test); } class FlexfecRenderObserver : public test::EndToEndTest, public rtc::VideoSinkInterface { 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::CallTest::kDefaultTimeoutMs), 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(const uint8_t* packet, size_t length) override { rtc::CritScope lock(&crit_); RTPHeader header; EXPECT_TRUE(parser_->Parse(packet, length, &header)); EXPECT_TRUE(header.payloadType == test::CallTest::kFakeVideoSendPayloadType || header.payloadType == test::CallTest::kFlexfecPayloadType || (enable_nack_ && header.payloadType == test::CallTest::kSendRtxPayloadType)) << "Unknown payload type received."; EXPECT_TRUE( header.ssrc == test::CallTest::kVideoSendSsrcs[0] || header.ssrc == test::CallTest::kFlexfecSendSsrc || (enable_nack_ && header.ssrc == test::CallTest::kSendRtxSsrcs[0])) << "Unknown SSRC received."; // To reduce test flakiness, always let FlexFEC packets through. if (header.payloadType == test::CallTest::kFlexfecPayloadType) { EXPECT_EQ(test::CallTest::kFlexfecSendSsrc, header.ssrc); return SEND_PACKET; } // To reduce test flakiness, always let RTX packets through. if (header.payloadType == test::CallTest::kSendRtxPayloadType) { EXPECT_EQ(test::CallTest::kSendRtxSsrcs[0], header.ssrc); // Parse RTX header. uint16_t original_sequence_number = ByteReader::ReadBigEndian(&packet[header.headerLength]); // 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(header.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::CallTest::kFakeVideoSendPayloadType, header.payloadType); EXPECT_EQ(test::CallTest::kVideoSendSsrcs[0], header.ssrc); dropped_sequence_numbers_.insert(header.sequenceNumber); dropped_timestamps_.insert(header.timestamp); return DROP_PACKET; } return SEND_PACKET; } Action OnReceiveRtcp(const uint8_t* data, size_t length) override { test::RtcpPacketParser parser; parser.Parse(data, length); if (parser.sender_ssrc() == kFlexfecLocalSsrc) { EXPECT_EQ(1, parser.receiver_report()->num_packets()); const std::vector& report_blocks = parser.receiver_report()->report_blocks(); if (!report_blocks.empty()) { EXPECT_EQ(1U, report_blocks.size()); EXPECT_EQ(test::CallTest::kFlexfecSendSsrc, report_blocks[0].source_ssrc()); rtc::CritScope lock(&crit_); received_flexfec_rtcp_ = true; } } return SEND_PACKET; } test::PacketTransport* CreateSendTransport( test::SingleThreadedTaskQueueForTesting* task_queue, Call* sender_call) override { // At low RTT (< kLowRttNackMs) -> NACK only, no FEC. const int kNetworkDelayMs = 100; DefaultNetworkSimulationConfig config; config.queue_delay_ms = kNetworkDelayMs; return new test::PacketTransport( task_queue, sender_call, this, test::PacketTransport::kSender, test::CallTest::payload_type_map_, absl::make_unique( Clock::GetRealTimeClock(), absl::make_unique(config))); } void OnFrame(const VideoFrame& video_frame) override { EXPECT_EQ(kVideoRotation_90, video_frame.rotation()); rtc::CritScope lock(&crit_); // 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* 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::CallTest::kNackRtpHistoryMs; send_config->rtp.rtx.ssrcs.push_back(test::CallTest::kSendRtxSsrcs[0]); send_config->rtp.rtx.payload_type = test::CallTest::kSendRtxPayloadType; (*receive_configs)[0].rtp.nack.rtp_history_ms = test::CallTest::kNackRtpHistoryMs; (*receive_configs)[0].rtp.rtx_ssrc = test::CallTest::kSendRtxSsrcs[0]; (*receive_configs)[0] .rtp .rtx_associated_payload_types[test::CallTest::kSendRtxPayloadType] = test::CallTest::kVideoSendPayloadType; } } void OnFrameGeneratorCapturerCreated( test::FrameGeneratorCapturer* frame_generator_capturer) override { frame_generator_capturer->SetFakeRotation(kVideoRotation_90); } void ModifyFlexfecConfigs( std::vector* receive_configs) override { (*receive_configs)[0].local_ssrc = kFlexfecLocalSsrc; } void PerformTest() override { EXPECT_TRUE(Wait()) << "Timed out waiting for dropped frames to be rendered."; } rtc::CriticalSection crit_; std::set dropped_sequence_numbers_ RTC_GUARDED_BY(crit_); // Several packets can have the same timestamp. std::multiset dropped_timestamps_ RTC_GUARDED_BY(crit_); const bool enable_nack_; const bool expect_flexfec_rtcp_; bool received_flexfec_rtcp_ RTC_GUARDED_BY(crit_); Random random_; int num_packets_sent_; }; TEST_P(FecEndToEndTest, RecoversWithFlexfec) { FlexfecRenderObserver test(false, false); RunBaseTest(&test); } TEST_P(FecEndToEndTest, RecoversWithFlexfecAndNack) { FlexfecRenderObserver test(true, false); RunBaseTest(&test); } TEST_P(FecEndToEndTest, RecoversWithFlexfecAndSendsCorrespondingRtcp) { FlexfecRenderObserver test(false, true); RunBaseTest(&test); } TEST_P(FecEndToEndTest, ReceivedUlpfecPacketsNotNacked) { class UlpfecNackObserver : public test::EndToEndTest { public: UlpfecNackObserver() : EndToEndTest(kDefaultTimeoutMs), state_(kFirstPacket), ulpfec_sequence_number_(0), has_last_sequence_number_(false), last_sequence_number_(0), encoder_factory_([]() { return VP8Encoder::Create(); }), decoder_(VP8Decoder::Create()) {} private: Action OnSendRtp(const uint8_t* packet, size_t length) override { rtc::CritScope lock_(&crit_); RTPHeader header; EXPECT_TRUE(parser_->Parse(packet, length, &header)); int encapsulated_payload_type = -1; if (header.payloadType == kRedPayloadType) { encapsulated_payload_type = static_cast(packet[header.headerLength]); if (encapsulated_payload_type != kFakeVideoSendPayloadType) EXPECT_EQ(kUlpfecPayloadType, encapsulated_payload_type); } else { EXPECT_EQ(kFakeVideoSendPayloadType, header.payloadType); } if (has_last_sequence_number_ && !IsNewerSequenceNumber(header.sequenceNumber, last_sequence_number_)) { // Drop retransmitted packets. return DROP_PACKET; } last_sequence_number_ = header.sequenceNumber; has_last_sequence_number_ = true; bool ulpfec_packet = encapsulated_payload_type == kUlpfecPayloadType; switch (state_) { case kFirstPacket: state_ = kDropEveryOtherPacketUntilUlpfec; break; case kDropEveryOtherPacketUntilUlpfec: if (ulpfec_packet) { state_ = kDropAllMediaPacketsUntilUlpfec; } else if (header.sequenceNumber % 2 == 0) { return DROP_PACKET; } break; case kDropAllMediaPacketsUntilUlpfec: if (!ulpfec_packet) return DROP_PACKET; ulpfec_sequence_number_ = header.sequenceNumber; state_ = kDropOneMediaPacket; break; case kDropOneMediaPacket: if (ulpfec_packet) return DROP_PACKET; state_ = kPassOneMediaPacket; return DROP_PACKET; break; 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 || header.sequenceNumber % 2 == 0) return DROP_PACKET; break; } return SEND_PACKET; } Action OnReceiveRtcp(const uint8_t* packet, size_t length) override { rtc::CritScope lock_(&crit_); if (state_ == kVerifyUlpfecPacketNotInNackList) { test::RtcpPacketParser rtcp_parser; rtcp_parser.Parse(packet, length); const std::vector& nacks = rtcp_parser.nack()->packet_ids(); EXPECT_TRUE(std::find(nacks.begin(), nacks.end(), ulpfec_sequence_number_) == nacks.end()) << "Got nack for ULPFEC packet"; if (!nacks.empty() && IsNewerSequenceNumber(nacks.back(), ulpfec_sequence_number_)) { observation_complete_.Set(); } } return SEND_PACKET; } test::PacketTransport* CreateSendTransport( test::SingleThreadedTaskQueueForTesting* task_queue, Call* sender_call) override { // At low RTT (< kLowRttNackMs) -> NACK only, no FEC. // Configure some network delay. const int kNetworkDelayMs = 50; DefaultNetworkSimulationConfig config; config.queue_delay_ms = kNetworkDelayMs; return new test::PacketTransport( task_queue, sender_call, this, test::PacketTransport::kSender, payload_type_map_, absl::make_unique( Clock::GetRealTimeClock(), absl::make_unique(config))); } // TODO(holmer): Investigate why we don't send FEC packets when the bitrate // is 10 kbps. void ModifySenderCallConfig(Call::Config* config) override { const int kMinBitrateBps = 30000; config->bitrate_config.min_bitrate_bps = kMinBitrateBps; } void ModifyVideoConfigs( VideoSendStream::Config* send_config, std::vector* receive_configs, VideoEncoderConfig* encoder_config) override { // Configure hybrid NACK/FEC. send_config->rtp.nack.rtp_history_ms = kNackRtpHistoryMs; send_config->rtp.ulpfec.red_payload_type = kRedPayloadType; send_config->rtp.ulpfec.ulpfec_payload_type = 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 = kFakeVideoSendPayloadType; encoder_config->codec_type = kVideoCodecVP8; (*receive_configs)[0].rtp.nack.rtp_history_ms = kNackRtpHistoryMs; (*receive_configs)[0].rtp.red_payload_type = kRedPayloadType; (*receive_configs)[0].rtp.ulpfec_payload_type = kUlpfecPayloadType; (*receive_configs)[0].decoders.resize(1); (*receive_configs)[0].decoders[0].payload_type = send_config->rtp.payload_type; (*receive_configs)[0].decoders[0].payload_name = send_config->rtp.payload_name; (*receive_configs)[0].decoders[0].decoder = decoder_.get(); } void PerformTest() override { EXPECT_TRUE(Wait()) << "Timed out while waiting for FEC packets to be received."; } enum { kFirstPacket, kDropEveryOtherPacketUntilUlpfec, kDropAllMediaPacketsUntilUlpfec, kDropOneMediaPacket, kPassOneMediaPacket, kVerifyUlpfecPacketNotInNackList, } state_; rtc::CriticalSection crit_; uint16_t ulpfec_sequence_number_ RTC_GUARDED_BY(&crit_); bool has_last_sequence_number_; uint16_t last_sequence_number_; std::unique_ptr encoder_; test::FunctionVideoEncoderFactory encoder_factory_; std::unique_ptr decoder_; } test; RunBaseTest(&test); } } // namespace webrtc