blob: 4493bde019ee94d749ee628fe416c50ea49890dc [file] [log] [blame]
/*
* 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 "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<std::string> {
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<VideoFrame> {
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<int>(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<VideoReceiveStream::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 = 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<VideoEncoder> encoder_;
test::FunctionVideoEncoderFactory encoder_factory_;
std::unique_ptr<VideoDecoder> decoder_;
std::set<uint32_t> dropped_sequence_numbers_ RTC_GUARDED_BY(crit_);
// Several packets can have the same timestamp.
std::multiset<uint32_t> 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<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::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<uint16_t>::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<rtcp::ReportBlock>& 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;
FakeNetworkPipe::Config config;
config.queue_delay_ms = kNetworkDelayMs;
return new test::PacketTransport(task_queue, sender_call, this,
test::PacketTransport::kSender,
test::CallTest::payload_type_map_, 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<VideoReceiveStream::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::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<FlexfecReceiveStream::Config>* 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<uint32_t> dropped_sequence_numbers_ RTC_GUARDED_BY(crit_);
// Several packets can have the same timestamp.
std::multiset<uint32_t> 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<int>(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<uint16_t>& 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;
FakeNetworkPipe::Config config;
config.queue_delay_ms = kNetworkDelayMs;
return new test::PacketTransport(task_queue, sender_call, this,
test::PacketTransport::kSender,
payload_type_map_, 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<VideoReceiveStream::Config>* 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<webrtc::VideoEncoder> encoder_;
test::FunctionVideoEncoderFactory encoder_factory_;
std::unique_ptr<webrtc::VideoDecoder> decoder_;
} test;
RunBaseTest(&test);
}
} // namespace webrtc