summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/video/end_to_end_tests/retransmission_tests.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/video/end_to_end_tests/retransmission_tests.cc')
-rw-r--r--third_party/libwebrtc/video/end_to_end_tests/retransmission_tests.cc513
1 files changed, 513 insertions, 0 deletions
diff --git a/third_party/libwebrtc/video/end_to_end_tests/retransmission_tests.cc b/third_party/libwebrtc/video/end_to_end_tests/retransmission_tests.cc
new file mode 100644
index 0000000000..45a9dae1e8
--- /dev/null
+++ b/third_party/libwebrtc/video/end_to_end_tests/retransmission_tests.cc
@@ -0,0 +1,513 @@
+/*
+ * 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 "absl/algorithm/container.h"
+#include "api/task_queue/task_queue_base.h"
+#include "api/test/simulated_network.h"
+#include "api/test/video/function_video_encoder_factory.h"
+#include "api/units/time_delta.h"
+#include "call/fake_network_pipe.h"
+#include "call/simulated_network.h"
+#include "modules/rtp_rtcp/source/rtp_packet.h"
+#include "modules/video_coding/codecs/vp8/include/vp8.h"
+#include "rtc_base/event.h"
+#include "rtc_base/synchronization/mutex.h"
+#include "rtc_base/task_queue_for_test.h"
+#include "test/call_test.h"
+#include "test/field_trial.h"
+#include "test/gtest.h"
+#include "test/rtcp_packet_parser.h"
+
+namespace webrtc {
+namespace {
+enum : int { // The first valid value is 1.
+ kVideoRotationExtensionId = 1,
+};
+} // namespace
+
+class RetransmissionEndToEndTest : public test::CallTest {
+ public:
+ RetransmissionEndToEndTest() {
+ RegisterRtpExtension(RtpExtension(RtpExtension::kVideoRotationUri,
+ kVideoRotationExtensionId));
+ }
+
+ protected:
+ void DecodesRetransmittedFrame(bool enable_rtx, bool enable_red);
+ void ReceivesPliAndRecovers(int rtp_history_ms);
+};
+
+TEST_F(RetransmissionEndToEndTest, ReceivesAndRetransmitsNack) {
+ static const int kNumberOfNacksToObserve = 2;
+ static const int kLossBurstSize = 2;
+ static const int kPacketsBetweenLossBursts = 9;
+ class NackObserver : public test::EndToEndTest {
+ public:
+ NackObserver()
+ : EndToEndTest(kLongTimeout),
+ sent_rtp_packets_(0),
+ packets_left_to_drop_(0),
+ nacks_left_(kNumberOfNacksToObserve) {}
+
+ private:
+ Action OnSendRtp(const uint8_t* packet, size_t length) override {
+ MutexLock lock(&mutex_);
+ RtpPacket rtp_packet;
+ EXPECT_TRUE(rtp_packet.Parse(packet, length));
+
+ // Never drop retransmitted packets.
+ if (dropped_packets_.find(rtp_packet.SequenceNumber()) !=
+ dropped_packets_.end()) {
+ retransmitted_packets_.insert(rtp_packet.SequenceNumber());
+ return SEND_PACKET;
+ }
+
+ if (nacks_left_ <= 0 &&
+ retransmitted_packets_.size() == dropped_packets_.size()) {
+ observation_complete_.Set();
+ }
+
+ ++sent_rtp_packets_;
+
+ // Enough NACKs received, stop dropping packets.
+ if (nacks_left_ <= 0)
+ return SEND_PACKET;
+
+ // Check if it's time for a new loss burst.
+ if (sent_rtp_packets_ % kPacketsBetweenLossBursts == 0)
+ packets_left_to_drop_ = kLossBurstSize;
+
+ // Never drop padding packets as those won't be retransmitted.
+ if (packets_left_to_drop_ > 0 && rtp_packet.padding_size() == 0) {
+ --packets_left_to_drop_;
+ dropped_packets_.insert(rtp_packet.SequenceNumber());
+ return DROP_PACKET;
+ }
+
+ return SEND_PACKET;
+ }
+
+ Action OnReceiveRtcp(const uint8_t* packet, size_t length) override {
+ MutexLock lock(&mutex_);
+ test::RtcpPacketParser parser;
+ EXPECT_TRUE(parser.Parse(packet, length));
+ nacks_left_ -= parser.nack()->num_packets();
+ return SEND_PACKET;
+ }
+
+ void ModifyVideoConfigs(
+ VideoSendStream::Config* send_config,
+ std::vector<VideoReceiveStreamInterface::Config>* receive_configs,
+ VideoEncoderConfig* encoder_config) override {
+ send_config->rtp.nack.rtp_history_ms = kNackRtpHistoryMs;
+ (*receive_configs)[0].rtp.nack.rtp_history_ms = kNackRtpHistoryMs;
+ }
+
+ void PerformTest() override {
+ EXPECT_TRUE(Wait())
+ << "Timed out waiting for packets to be NACKed, retransmitted and "
+ "rendered.";
+ }
+
+ Mutex mutex_;
+ std::set<uint16_t> dropped_packets_;
+ std::set<uint16_t> retransmitted_packets_;
+ uint64_t sent_rtp_packets_;
+ int packets_left_to_drop_;
+ int nacks_left_ RTC_GUARDED_BY(&mutex_);
+ } test;
+
+ RunBaseTest(&test);
+}
+
+TEST_F(RetransmissionEndToEndTest, ReceivesNackAndRetransmitsAudio) {
+ class NackObserver : public test::EndToEndTest {
+ public:
+ NackObserver()
+ : EndToEndTest(kLongTimeout),
+ local_ssrc_(0),
+ remote_ssrc_(0),
+ receive_transport_(nullptr) {}
+
+ private:
+ size_t GetNumVideoStreams() const override { return 0; }
+ size_t GetNumAudioStreams() const override { return 1; }
+
+ Action OnSendRtp(const uint8_t* packet, size_t length) override {
+ RtpPacket rtp_packet;
+ EXPECT_TRUE(rtp_packet.Parse(packet, length));
+
+ if (!sequence_number_to_retransmit_) {
+ sequence_number_to_retransmit_ = rtp_packet.SequenceNumber();
+ return DROP_PACKET;
+
+ // Don't ask for retransmission straight away, may be deduped in pacer.
+ } else if (rtp_packet.SequenceNumber() ==
+ *sequence_number_to_retransmit_) {
+ observation_complete_.Set();
+ } else {
+ // Send a NACK as often as necessary until retransmission is received.
+ rtcp::Nack nack;
+ nack.SetSenderSsrc(local_ssrc_);
+ nack.SetMediaSsrc(remote_ssrc_);
+ uint16_t nack_list[] = {*sequence_number_to_retransmit_};
+ nack.SetPacketIds(nack_list, 1);
+ rtc::Buffer buffer = nack.Build();
+
+ EXPECT_TRUE(receive_transport_->SendRtcp(buffer.data(), buffer.size()));
+ }
+
+ return SEND_PACKET;
+ }
+
+ void ModifyAudioConfigs(AudioSendStream::Config* send_config,
+ std::vector<AudioReceiveStreamInterface::Config>*
+ receive_configs) override {
+ (*receive_configs)[0].rtp.nack.rtp_history_ms = kNackRtpHistoryMs;
+ local_ssrc_ = (*receive_configs)[0].rtp.local_ssrc;
+ remote_ssrc_ = (*receive_configs)[0].rtp.remote_ssrc;
+ receive_transport_ = (*receive_configs)[0].rtcp_send_transport;
+ }
+
+ void PerformTest() override {
+ EXPECT_TRUE(Wait())
+ << "Timed out waiting for packets to be NACKed, retransmitted and "
+ "rendered.";
+ }
+
+ uint32_t local_ssrc_;
+ uint32_t remote_ssrc_;
+ Transport* receive_transport_;
+ absl::optional<uint16_t> sequence_number_to_retransmit_;
+ } test;
+
+ RunBaseTest(&test);
+}
+
+TEST_F(RetransmissionEndToEndTest,
+ StopSendingKeyframeRequestsForInactiveStream) {
+ class KeyframeRequestObserver : public test::EndToEndTest {
+ public:
+ explicit KeyframeRequestObserver(TaskQueueBase* task_queue)
+ : clock_(Clock::GetRealTimeClock()), task_queue_(task_queue) {}
+
+ void OnVideoStreamsCreated(VideoSendStream* send_stream,
+ const std::vector<VideoReceiveStreamInterface*>&
+ receive_streams) override {
+ RTC_DCHECK_EQ(1, receive_streams.size());
+ send_stream_ = send_stream;
+ receive_stream_ = receive_streams[0];
+ }
+
+ Action OnReceiveRtcp(const uint8_t* packet, size_t length) override {
+ test::RtcpPacketParser parser;
+ EXPECT_TRUE(parser.Parse(packet, length));
+ if (parser.pli()->num_packets() > 0)
+ task_queue_->PostTask([this] { Run(); });
+ return SEND_PACKET;
+ }
+
+ bool PollStats() {
+ if (receive_stream_->GetStats().frames_decoded > 0) {
+ frame_decoded_ = true;
+ } else if (clock_->TimeInMilliseconds() - start_time_ < 5000) {
+ task_queue_->PostDelayedTask([this] { Run(); }, TimeDelta::Millis(100));
+ return false;
+ }
+ return true;
+ }
+
+ void PerformTest() override {
+ start_time_ = clock_->TimeInMilliseconds();
+ task_queue_->PostTask([this] { Run(); });
+ test_done_.Wait(rtc::Event::kForever);
+ }
+
+ void Run() {
+ if (!frame_decoded_) {
+ if (PollStats()) {
+ send_stream_->Stop();
+ if (!frame_decoded_) {
+ test_done_.Set();
+ } else {
+ // Now we wait for the PLI packet. Once we receive it, a task
+ // will be posted (see OnReceiveRtcp) and we'll check the stats
+ // once more before signaling that we're done.
+ }
+ }
+ } else {
+ EXPECT_EQ(
+ 1U,
+ receive_stream_->GetStats().rtcp_packet_type_counts.pli_packets);
+ test_done_.Set();
+ }
+ }
+
+ private:
+ Clock* const clock_;
+ VideoSendStream* send_stream_;
+ VideoReceiveStreamInterface* receive_stream_;
+ TaskQueueBase* const task_queue_;
+ rtc::Event test_done_;
+ bool frame_decoded_ = false;
+ int64_t start_time_ = 0;
+ } test(task_queue());
+
+ RunBaseTest(&test);
+}
+
+void RetransmissionEndToEndTest::ReceivesPliAndRecovers(int rtp_history_ms) {
+ static const int kPacketsToDrop = 1;
+
+ class PliObserver : public test::EndToEndTest,
+ public rtc::VideoSinkInterface<VideoFrame> {
+ public:
+ explicit PliObserver(int rtp_history_ms)
+ : EndToEndTest(kLongTimeout),
+ rtp_history_ms_(rtp_history_ms),
+ nack_enabled_(rtp_history_ms > 0),
+ highest_dropped_timestamp_(0),
+ frames_to_drop_(0),
+ received_pli_(false) {}
+
+ private:
+ Action OnSendRtp(const uint8_t* packet, size_t length) override {
+ MutexLock lock(&mutex_);
+ RtpPacket rtp_packet;
+ EXPECT_TRUE(rtp_packet.Parse(packet, length));
+
+ // Drop all retransmitted packets to force a PLI.
+ if (rtp_packet.Timestamp() <= highest_dropped_timestamp_)
+ return DROP_PACKET;
+
+ if (frames_to_drop_ > 0) {
+ highest_dropped_timestamp_ = rtp_packet.Timestamp();
+ --frames_to_drop_;
+ return DROP_PACKET;
+ }
+
+ return SEND_PACKET;
+ }
+
+ Action OnReceiveRtcp(const uint8_t* packet, size_t length) override {
+ MutexLock lock(&mutex_);
+ test::RtcpPacketParser parser;
+ EXPECT_TRUE(parser.Parse(packet, length));
+ if (!nack_enabled_)
+ EXPECT_EQ(0, parser.nack()->num_packets());
+ if (parser.pli()->num_packets() > 0)
+ received_pli_ = true;
+ return SEND_PACKET;
+ }
+
+ void OnFrame(const VideoFrame& video_frame) override {
+ MutexLock lock(&mutex_);
+ if (received_pli_ &&
+ video_frame.timestamp() > highest_dropped_timestamp_) {
+ observation_complete_.Set();
+ }
+ if (!received_pli_)
+ frames_to_drop_ = kPacketsToDrop;
+ }
+
+ void ModifyVideoConfigs(
+ VideoSendStream::Config* send_config,
+ std::vector<VideoReceiveStreamInterface::Config>* receive_configs,
+ VideoEncoderConfig* encoder_config) override {
+ send_config->rtp.nack.rtp_history_ms = rtp_history_ms_;
+ (*receive_configs)[0].rtp.nack.rtp_history_ms = rtp_history_ms_;
+ (*receive_configs)[0].renderer = this;
+ }
+
+ void PerformTest() override {
+ EXPECT_TRUE(Wait()) << "Timed out waiting for PLI to be "
+ "received and a frame to be "
+ "rendered afterwards.";
+ }
+
+ Mutex mutex_;
+ int rtp_history_ms_;
+ bool nack_enabled_;
+ uint32_t highest_dropped_timestamp_ RTC_GUARDED_BY(&mutex_);
+ int frames_to_drop_ RTC_GUARDED_BY(&mutex_);
+ bool received_pli_ RTC_GUARDED_BY(&mutex_);
+ } test(rtp_history_ms);
+
+ RunBaseTest(&test);
+}
+
+TEST_F(RetransmissionEndToEndTest, ReceivesPliAndRecoversWithNack) {
+ ReceivesPliAndRecovers(1000);
+}
+
+TEST_F(RetransmissionEndToEndTest, ReceivesPliAndRecoversWithoutNack) {
+ ReceivesPliAndRecovers(0);
+}
+
+// This test drops second RTP packet with a marker bit set, makes sure it's
+// retransmitted and renders. Retransmission SSRCs are also checked.
+void RetransmissionEndToEndTest::DecodesRetransmittedFrame(bool enable_rtx,
+ bool enable_red) {
+ static const int kDroppedFrameNumber = 10;
+ class RetransmissionObserver : public test::EndToEndTest,
+ public rtc::VideoSinkInterface<VideoFrame> {
+ public:
+ RetransmissionObserver(bool enable_rtx, bool enable_red)
+ : EndToEndTest(kDefaultTimeout),
+ payload_type_(GetPayloadType(false, enable_red)),
+ retransmission_ssrc_(enable_rtx ? kSendRtxSsrcs[0]
+ : kVideoSendSsrcs[0]),
+ retransmission_payload_type_(GetPayloadType(enable_rtx, enable_red)),
+ encoder_factory_([]() { return VP8Encoder::Create(); }),
+ marker_bits_observed_(0),
+ retransmitted_timestamp_(0) {}
+
+ private:
+ Action OnSendRtp(const uint8_t* packet, size_t length) override {
+ MutexLock lock(&mutex_);
+ RtpPacket rtp_packet;
+ EXPECT_TRUE(rtp_packet.Parse(packet, length));
+
+ // Ignore padding-only packets over RTX.
+ if (rtp_packet.PayloadType() != payload_type_) {
+ EXPECT_EQ(retransmission_ssrc_, rtp_packet.Ssrc());
+ if (rtp_packet.payload_size() == 0)
+ return SEND_PACKET;
+ }
+
+ if (rtp_packet.Timestamp() == retransmitted_timestamp_) {
+ EXPECT_EQ(retransmission_ssrc_, rtp_packet.Ssrc());
+ EXPECT_EQ(retransmission_payload_type_, rtp_packet.PayloadType());
+ return SEND_PACKET;
+ }
+
+ // Found the final packet of the frame to inflict loss to, drop this and
+ // expect a retransmission.
+ if (rtp_packet.PayloadType() == payload_type_ && rtp_packet.Marker() &&
+ ++marker_bits_observed_ == kDroppedFrameNumber) {
+ // This should be the only dropped packet.
+ EXPECT_EQ(0u, retransmitted_timestamp_);
+ retransmitted_timestamp_ = rtp_packet.Timestamp();
+ return DROP_PACKET;
+ }
+
+ return SEND_PACKET;
+ }
+
+ void OnFrame(const VideoFrame& frame) override {
+ EXPECT_EQ(kVideoRotation_90, frame.rotation());
+ {
+ MutexLock lock(&mutex_);
+ if (frame.timestamp() == retransmitted_timestamp_)
+ observation_complete_.Set();
+ }
+ orig_renderer_->OnFrame(frame);
+ }
+
+ void ModifyVideoConfigs(
+ VideoSendStream::Config* send_config,
+ std::vector<VideoReceiveStreamInterface::Config>* receive_configs,
+ VideoEncoderConfig* encoder_config) override {
+ send_config->rtp.nack.rtp_history_ms = kNackRtpHistoryMs;
+
+ // Insert ourselves into the rendering pipeline.
+ RTC_DCHECK(!orig_renderer_);
+ orig_renderer_ = (*receive_configs)[0].renderer;
+ RTC_DCHECK(orig_renderer_);
+ // To avoid post-decode frame dropping, disable the prerender buffer.
+ (*receive_configs)[0].enable_prerenderer_smoothing = false;
+ (*receive_configs)[0].renderer = this;
+
+ (*receive_configs)[0].rtp.nack.rtp_history_ms = kNackRtpHistoryMs;
+
+ if (payload_type_ == kRedPayloadType) {
+ send_config->rtp.ulpfec.ulpfec_payload_type = kUlpfecPayloadType;
+ send_config->rtp.ulpfec.red_payload_type = kRedPayloadType;
+ if (retransmission_ssrc_ == kSendRtxSsrcs[0])
+ send_config->rtp.ulpfec.red_rtx_payload_type = kRtxRedPayloadType;
+ (*receive_configs)[0].rtp.ulpfec_payload_type =
+ send_config->rtp.ulpfec.ulpfec_payload_type;
+ (*receive_configs)[0].rtp.red_payload_type =
+ send_config->rtp.ulpfec.red_payload_type;
+ }
+
+ if (retransmission_ssrc_ == kSendRtxSsrcs[0]) {
+ send_config->rtp.rtx.ssrcs.push_back(kSendRtxSsrcs[0]);
+ send_config->rtp.rtx.payload_type = kSendRtxPayloadType;
+ (*receive_configs)[0].rtp.rtx_ssrc = kSendRtxSsrcs[0];
+ (*receive_configs)[0]
+ .rtp.rtx_associated_payload_types[(payload_type_ == kRedPayloadType)
+ ? kRtxRedPayloadType
+ : kSendRtxPayloadType] =
+ payload_type_;
+ }
+ // Configure encoding and decoding with VP8, since generic packetization
+ // doesn't support FEC with NACK.
+ RTC_DCHECK_EQ(1, (*receive_configs)[0].decoders.size());
+ send_config->encoder_settings.encoder_factory = &encoder_factory_;
+ send_config->rtp.payload_name = "VP8";
+ encoder_config->codec_type = kVideoCodecVP8;
+ (*receive_configs)[0].decoders[0].video_format = SdpVideoFormat("VP8");
+ }
+
+ void OnFrameGeneratorCapturerCreated(
+ test::FrameGeneratorCapturer* frame_generator_capturer) override {
+ frame_generator_capturer->SetFakeRotation(kVideoRotation_90);
+ }
+
+ void PerformTest() override {
+ EXPECT_TRUE(Wait())
+ << "Timed out while waiting for retransmission to render.";
+ }
+
+ int GetPayloadType(bool use_rtx, bool use_fec) {
+ if (use_fec) {
+ if (use_rtx)
+ return kRtxRedPayloadType;
+ return kRedPayloadType;
+ }
+ if (use_rtx)
+ return kSendRtxPayloadType;
+ return kFakeVideoSendPayloadType;
+ }
+
+ Mutex mutex_;
+ rtc::VideoSinkInterface<VideoFrame>* orig_renderer_ = nullptr;
+ const int payload_type_;
+ const uint32_t retransmission_ssrc_;
+ const int retransmission_payload_type_;
+ test::FunctionVideoEncoderFactory encoder_factory_;
+ const std::string payload_name_;
+ int marker_bits_observed_;
+ uint32_t retransmitted_timestamp_ RTC_GUARDED_BY(&mutex_);
+ } test(enable_rtx, enable_red);
+
+ RunBaseTest(&test);
+}
+
+TEST_F(RetransmissionEndToEndTest, DecodesRetransmittedFrame) {
+ DecodesRetransmittedFrame(false, false);
+}
+
+TEST_F(RetransmissionEndToEndTest, DecodesRetransmittedFrameOverRtx) {
+ DecodesRetransmittedFrame(true, false);
+}
+
+TEST_F(RetransmissionEndToEndTest, DecodesRetransmittedFrameByRed) {
+ DecodesRetransmittedFrame(false, true);
+}
+
+TEST_F(RetransmissionEndToEndTest, DecodesRetransmittedFrameByRedOverRtx) {
+ DecodesRetransmittedFrame(true, true);
+}
+
+} // namespace webrtc