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:
parent
b2b2031457
commit
3d4d94a832
@ -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",
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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().
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user