Adds scenario test for transport wide feedback based retransmission.

This ensures more end to end test coverage of the feature and captures
a wider class of regression then the existing unit test.

Bug: webrtc:9883
Change-Id: I6e74e571500c5c5d74caf8f661cac08bee8934f6
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/164461
Commit-Queue: Sebastian Jansson <srte@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#30252}
This commit is contained in:
Sebastian Jansson 2020-01-14 14:25:41 +01:00 committed by Commit Bot
parent b2b2031457
commit 3d4d94a832
8 changed files with 109 additions and 1 deletions

View File

@ -433,6 +433,7 @@ if (rtc_include_tests) {
"../test:video_test_common",
"../test/time_controller:time_controller",
"../video",
"//test/scenario:scenario",
"//testing/gmock",
"//testing/gtest",
"//third_party/abseil-cpp/absl/container:inlined_vector",

View File

@ -10,8 +10,10 @@
#include "call/rtp_video_sender.h"
#include <atomic>
#include <memory>
#include <string>
#include "call/rtp_transport_controller_send.h"
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
#include "modules/rtp_rtcp/source/byte_io.h"
@ -25,6 +27,7 @@
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/mock_transport.h"
#include "test/scenario/scenario.h"
#include "test/time_controller/simulated_time_controller.h"
#include "video/call_stats.h"
#include "video/send_delay_stats.h"
@ -505,6 +508,62 @@ TEST(RtpVideoSenderTest, DoesNotRetrasmitAckedPackets) {
ASSERT_TRUE(event.Wait(kTimeoutMs));
}
// This tests that we utilize transport wide feedback to retransmit lost
// packets. This is tested by dropping all ordirary packets from a "lossy"
// stream send along with an secondary untouched stream. The transport wide
// feedback packets from the secondary stream allows the sending side to
// detect and retreansmit the lost packets from the lossy stream.
TEST(RtpVideoSenderTest, RetransmitsOnTransportWideLossInfo) {
int rtx_packets;
test::Scenario s(test_info_);
test::CallClientConfig call_conf;
// Keeping the bitrate fixed to avoid RTX due to probing.
call_conf.transport.rates.max_rate = DataRate::kbps(300);
call_conf.transport.rates.start_rate = DataRate::kbps(300);
test::NetworkSimulationConfig net_conf;
net_conf.bandwidth = DataRate::kbps(300);
auto send_node = s.CreateSimulationNode(net_conf);
auto* route = s.CreateRoutes(s.CreateClient("send", call_conf), {send_node},
s.CreateClient("return", call_conf),
{s.CreateSimulationNode(net_conf)});
test::VideoStreamConfig lossy_config;
lossy_config.source.framerate = 5;
auto* lossy = s.CreateVideoStream(route->forward(), lossy_config);
// The secondary stream acts a driver for transport feedback messages,
// ensuring that lost packets on the lossy stream are retransmitted.
s.CreateVideoStream(route->forward(), test::VideoStreamConfig());
send_node->router()->SetFilter([&](const EmulatedIpPacket& packet) {
RtpPacket rtp;
if (rtp.Parse(packet.data)) {
// Drops all regular packets for the lossy stream and counts all RTX
// packets. Since no packets are let trough, NACKs can't be triggered
// by the receiving side.
if (lossy->send()->UsingSsrc(rtp.Ssrc())) {
return false;
} else if (lossy->send()->UsingRtxSsrc(rtp.Ssrc())) {
++rtx_packets;
}
}
return true;
});
// Run for a short duration and reset counters to avoid counting RTX packets
// from initial probing.
s.RunFor(TimeDelta::seconds(1));
rtx_packets = 0;
int decoded_baseline = lossy->receive()->GetStats().frames_decoded;
s.RunFor(TimeDelta::seconds(1));
// We expect both that RTX packets were sent and that an appropriate number of
// frames were received. This is somewhat redundant but reduces the risk of
// false positives in future regressions (e.g. RTX is send due to probing).
EXPECT_GE(rtx_packets, 1);
int frames_decoded =
lossy->receive()->GetStats().frames_decoded - decoded_baseline;
EXPECT_EQ(frames_decoded, 5);
}
// Integration test verifying that retransmissions are sent for packets which
// can be detected as lost early, using transport wide feedback.
TEST(RtpVideoSenderTest, EarlyRetransmits) {

View File

@ -91,6 +91,10 @@ void NetworkRouterNode::OnPacketReceived(EmulatedIpPacket packet) {
if (watcher_) {
watcher_(packet);
}
if (filter_) {
if (!filter_(packet))
return;
}
auto receiver_it = routing_.find(packet.to.ipaddr());
if (receiver_it == routing_.end()) {
return;
@ -125,6 +129,14 @@ void NetworkRouterNode::SetWatcher(
});
}
void NetworkRouterNode::SetFilter(
std::function<bool(const EmulatedIpPacket&)> filter) {
task_queue_->PostTask([=] {
RTC_DCHECK_RUN_ON(task_queue_);
filter_ = filter;
});
}
EmulatedNetworkNode::EmulatedNetworkNode(
Clock* clock,
rtc::TaskQueue* task_queue,

View File

@ -73,6 +73,7 @@ class NetworkRouterNode : public EmulatedNetworkReceiverInterface {
EmulatedNetworkReceiverInterface* receiver);
void RemoveReceiver(const rtc::IPAddress& dest_ip);
void SetWatcher(std::function<void(const EmulatedIpPacket&)> watcher);
void SetFilter(std::function<bool(const EmulatedIpPacket&)> filter);
private:
rtc::TaskQueue* const task_queue_;
@ -80,6 +81,8 @@ class NetworkRouterNode : public EmulatedNetworkReceiverInterface {
RTC_GUARDED_BY(task_queue_);
std::function<void(const EmulatedIpPacket&)> watcher_
RTC_GUARDED_BY(task_queue_);
std::function<bool(const EmulatedIpPacket&)> filter_
RTC_GUARDED_BY(task_queue_);
};
// Represents node in the emulated network. Nodes can be connected with each

View File

@ -60,6 +60,10 @@ Scenario::Scenario()
: Scenario(std::unique_ptr<LogWriterFactoryInterface>(),
/*real_time=*/false) {}
Scenario::Scenario(const testing::TestInfo* test_info)
: Scenario(std::string(test_info->test_suite_name()) + "/" +
test_info->name()) {}
Scenario::Scenario(std::string file_name)
: Scenario(file_name, /*real_time=*/false) {}
@ -264,6 +268,10 @@ void Scenario::Every(TimeDelta interval, std::function<void()> function) {
});
}
void Scenario::Post(std::function<void()> function) {
task_queue_.PostTask(function);
}
void Scenario::At(TimeDelta offset, std::function<void()> function) {
RTC_DCHECK_GT(offset, TimeSinceStart());
task_queue_.PostDelayedTask(function, TimeUntilTarget(offset).ms());

View File

@ -19,6 +19,7 @@
#include "rtc_base/fake_clock.h"
#include "rtc_base/task_queue.h"
#include "rtc_base/task_utils/repeating_task.h"
#include "test/gtest.h"
#include "test/logging/log_writer.h"
#include "test/network/network_emulation_manager.h"
#include "test/scenario/audio_stream.h"
@ -41,6 +42,7 @@ namespace test {
class Scenario {
public:
Scenario();
explicit Scenario(const testing::TestInfo* test_info);
explicit Scenario(std::string file_name);
Scenario(std::string file_name, bool real_time);
Scenario(std::unique_ptr<LogWriterFactoryInterface> log_writer_manager,
@ -100,6 +102,10 @@ class Scenario {
void Every(TimeDelta interval, std::function<void(TimeDelta)> function);
void Every(TimeDelta interval, std::function<void()> function);
// Runs the provided function on the internal task queue. This ensure that
// it's run on the main thread for simulated time tests.
void Post(std::function<void()> function);
// Runs the provided function after given duration has passed. For real time
// tests, |function| is called after |target_time_since_start| from the call
// to Every().

View File

@ -486,6 +486,22 @@ void SendVideoStream::UpdateActiveLayers(std::vector<bool> active_layers) {
});
}
bool SendVideoStream::UsingSsrc(uint32_t ssrc) const {
for (uint32_t owned : ssrcs_) {
if (owned == ssrc)
return true;
}
return false;
}
bool SendVideoStream::UsingRtxSsrc(uint32_t ssrc) const {
for (uint32_t owned : rtx_ssrcs_) {
if (owned == ssrc)
return true;
}
return false;
}
void SendVideoStream::SetCaptureFramerate(int framerate) {
sender_->SendTask([&] { video_capturer_->ChangeFramerate(framerate); });
}
@ -520,7 +536,8 @@ ReceiveVideoStream::ReceiveVideoStream(CallClient* receiver,
VideoFrameMatcher* matcher)
: receiver_(receiver), config_(config) {
if (config.encoder.codec ==
VideoStreamConfig::Encoder::Codec::kVideoCodecGeneric) {
VideoStreamConfig::Encoder::Codec::kVideoCodecGeneric ||
config.encoder.implementation == VideoStreamConfig::Encoder::kFake) {
decoder_factory_ = std::make_unique<FunctionVideoDecoderFactory>(
[]() { return std::make_unique<FakeDecoder>(); });
} else {

View File

@ -40,6 +40,8 @@ class SendVideoStream {
void Stop();
void UpdateConfig(std::function<void(VideoStreamConfig*)> modifier);
void UpdateActiveLayers(std::vector<bool> active_layers);
bool UsingSsrc(uint32_t ssrc) const;
bool UsingRtxSsrc(uint32_t ssrc) const;
private:
friend class Scenario;