4086 lines
146 KiB
C++
4086 lines
146 KiB
C++
/*
|
|
* Copyright (c) 2013 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 <algorithm> // max
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include "absl/algorithm/container.h"
|
|
#include "api/sequence_checker.h"
|
|
#include "api/task_queue/default_task_queue_factory.h"
|
|
#include "api/task_queue/task_queue_base.h"
|
|
#include "api/test/simulated_network.h"
|
|
#include "api/video/builtin_video_bitrate_allocator_factory.h"
|
|
#include "api/video/encoded_image.h"
|
|
#include "api/video/video_bitrate_allocation.h"
|
|
#include "api/video_codecs/video_encoder.h"
|
|
#include "call/call.h"
|
|
#include "call/fake_network_pipe.h"
|
|
#include "call/rtp_transport_controller_send.h"
|
|
#include "call/simulated_network.h"
|
|
#include "call/video_send_stream.h"
|
|
#include "media/engine/internal_encoder_factory.h"
|
|
#include "media/engine/simulcast_encoder_adapter.h"
|
|
#include "media/engine/webrtc_video_engine.h"
|
|
#include "modules/rtp_rtcp/include/rtp_header_extension_map.h"
|
|
#include "modules/rtp_rtcp/source/create_video_rtp_depacketizer.h"
|
|
#include "modules/rtp_rtcp/source/rtcp_sender.h"
|
|
#include "modules/rtp_rtcp/source/rtp_header_extensions.h"
|
|
#include "modules/rtp_rtcp/source/rtp_packet.h"
|
|
#include "modules/rtp_rtcp/source/rtp_rtcp_impl2.h"
|
|
#include "modules/rtp_rtcp/source/rtp_util.h"
|
|
#include "modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.h"
|
|
#include "modules/video_coding/codecs/interface/common_constants.h"
|
|
#include "modules/video_coding/codecs/vp8/include/vp8.h"
|
|
#include "modules/video_coding/codecs/vp9/include/vp9.h"
|
|
#include "rtc_base/checks.h"
|
|
#include "rtc_base/event.h"
|
|
#include "rtc_base/experiments/alr_experiment.h"
|
|
#include "rtc_base/logging.h"
|
|
#include "rtc_base/platform_thread.h"
|
|
#include "rtc_base/rate_limiter.h"
|
|
#include "rtc_base/synchronization/mutex.h"
|
|
#include "rtc_base/task_queue_for_test.h"
|
|
#include "rtc_base/task_utils/to_queued_task.h"
|
|
#include "rtc_base/time_utils.h"
|
|
#include "rtc_base/unique_id_generator.h"
|
|
#include "system_wrappers/include/sleep.h"
|
|
#include "test/call_test.h"
|
|
#include "test/configurable_frame_size_encoder.h"
|
|
#include "test/fake_encoder.h"
|
|
#include "test/fake_texture_frame.h"
|
|
#include "test/field_trial.h"
|
|
#include "test/frame_forwarder.h"
|
|
#include "test/frame_generator_capturer.h"
|
|
#include "test/frame_utils.h"
|
|
#include "test/gmock.h"
|
|
#include "test/gtest.h"
|
|
#include "test/null_transport.h"
|
|
#include "test/rtcp_packet_parser.h"
|
|
#include "test/testsupport/perf_test.h"
|
|
#include "test/video_encoder_proxy_factory.h"
|
|
#include "video/send_statistics_proxy.h"
|
|
#include "video/transport_adapter.h"
|
|
#include "video/video_send_stream.h"
|
|
|
|
namespace webrtc {
|
|
namespace test {
|
|
class VideoSendStreamPeer {
|
|
public:
|
|
explicit VideoSendStreamPeer(webrtc::VideoSendStream* base_class_stream)
|
|
: internal_stream_(
|
|
static_cast<internal::VideoSendStream*>(base_class_stream)) {}
|
|
absl::optional<float> GetPacingFactorOverride() const {
|
|
return internal_stream_->GetPacingFactorOverride();
|
|
}
|
|
|
|
private:
|
|
internal::VideoSendStream const* const internal_stream_;
|
|
};
|
|
} // namespace test
|
|
|
|
namespace {
|
|
enum : int { // The first valid value is 1.
|
|
kAbsSendTimeExtensionId = 1,
|
|
kTimestampOffsetExtensionId,
|
|
kTransportSequenceNumberExtensionId,
|
|
kVideoContentTypeExtensionId,
|
|
kVideoRotationExtensionId,
|
|
kVideoTimingExtensionId,
|
|
};
|
|
|
|
// Readability convenience enum for `WaitBitrateChanged()`.
|
|
enum class WaitUntil : bool { kZero = false, kNonZero = true };
|
|
|
|
constexpr int64_t kRtcpIntervalMs = 1000;
|
|
|
|
enum VideoFormat {
|
|
kGeneric,
|
|
kVP8,
|
|
};
|
|
|
|
} // namespace
|
|
|
|
class VideoSendStreamTest : public test::CallTest {
|
|
public:
|
|
VideoSendStreamTest() {
|
|
RegisterRtpExtension(RtpExtension(RtpExtension::kTransportSequenceNumberUri,
|
|
kTransportSequenceNumberExtensionId));
|
|
}
|
|
|
|
protected:
|
|
void TestNackRetransmission(uint32_t retransmit_ssrc,
|
|
uint8_t retransmit_payload_type);
|
|
void TestPacketFragmentationSize(VideoFormat format, bool with_fec);
|
|
|
|
void TestVp9NonFlexMode(uint8_t num_temporal_layers,
|
|
uint8_t num_spatial_layers);
|
|
|
|
void TestRequestSourceRotateVideo(bool support_orientation_ext);
|
|
|
|
void TestTemporalLayers(VideoEncoderFactory* encoder_factory,
|
|
const std::string& payload_name,
|
|
const std::vector<int>& num_temporal_layers);
|
|
};
|
|
|
|
TEST_F(VideoSendStreamTest, CanStartStartedStream) {
|
|
SendTask(RTC_FROM_HERE, task_queue(), [this]() {
|
|
CreateSenderCall();
|
|
|
|
test::NullTransport transport;
|
|
CreateSendConfig(1, 0, 0, &transport);
|
|
CreateVideoStreams();
|
|
GetVideoSendStream()->Start();
|
|
GetVideoSendStream()->Start();
|
|
DestroyStreams();
|
|
DestroyCalls();
|
|
});
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, CanStopStoppedStream) {
|
|
SendTask(RTC_FROM_HERE, task_queue(), [this]() {
|
|
CreateSenderCall();
|
|
|
|
test::NullTransport transport;
|
|
CreateSendConfig(1, 0, 0, &transport);
|
|
CreateVideoStreams();
|
|
GetVideoSendStream()->Stop();
|
|
GetVideoSendStream()->Stop();
|
|
DestroyStreams();
|
|
DestroyCalls();
|
|
});
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsCName) {
|
|
static std::string kCName = "PjQatC14dGfbVwGPUOA9IH7RlsFDbWl4AhXEiDsBizo=";
|
|
class CNameObserver : public test::SendTest {
|
|
public:
|
|
CNameObserver() : SendTest(kDefaultTimeoutMs) {}
|
|
|
|
private:
|
|
Action OnSendRtcp(const uint8_t* packet, size_t length) override {
|
|
test::RtcpPacketParser parser;
|
|
EXPECT_TRUE(parser.Parse(packet, length));
|
|
if (parser.sdes()->num_packets() > 0) {
|
|
EXPECT_EQ(1u, parser.sdes()->chunks().size());
|
|
EXPECT_EQ(kCName, parser.sdes()->chunks()[0].cname);
|
|
|
|
observation_complete_.Set();
|
|
}
|
|
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
send_config->rtp.c_name = kCName;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait()) << "Timed out while waiting for RTCP with CNAME.";
|
|
}
|
|
} test;
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsAbsoluteSendTime) {
|
|
class AbsoluteSendTimeObserver : public test::SendTest {
|
|
public:
|
|
AbsoluteSendTimeObserver() : SendTest(kDefaultTimeoutMs) {
|
|
extensions_.Register<AbsoluteSendTime>(kAbsSendTimeExtensionId);
|
|
}
|
|
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
RtpPacket rtp_packet(&extensions_);
|
|
EXPECT_TRUE(rtp_packet.Parse(packet, length));
|
|
|
|
uint32_t abs_send_time = 0;
|
|
EXPECT_FALSE(rtp_packet.HasExtension<TransmissionOffset>());
|
|
EXPECT_TRUE(rtp_packet.GetExtension<AbsoluteSendTime>(&abs_send_time));
|
|
if (abs_send_time != 0) {
|
|
// Wait for at least one packet with a non-zero send time. The send time
|
|
// is a 16-bit value derived from the system clock, and it is valid
|
|
// for a packet to have a zero send time. To tell that from an
|
|
// unpopulated value we'll wait for a packet with non-zero send time.
|
|
observation_complete_.Set();
|
|
} else {
|
|
RTC_LOG(LS_WARNING)
|
|
<< "Got a packet with zero absoluteSendTime, waiting"
|
|
" for another packet...";
|
|
}
|
|
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
send_config->rtp.extensions.clear();
|
|
send_config->rtp.extensions.push_back(
|
|
RtpExtension(RtpExtension::kAbsSendTimeUri, kAbsSendTimeExtensionId));
|
|
}
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait()) << "Timed out while waiting for single RTP packet.";
|
|
}
|
|
|
|
private:
|
|
RtpHeaderExtensionMap extensions_;
|
|
} test;
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsTransmissionTimeOffset) {
|
|
static const int kEncodeDelayMs = 5;
|
|
class TransmissionTimeOffsetObserver : public test::SendTest {
|
|
public:
|
|
TransmissionTimeOffsetObserver()
|
|
: SendTest(kDefaultTimeoutMs), encoder_factory_([]() {
|
|
return std::make_unique<test::DelayedEncoder>(
|
|
Clock::GetRealTimeClock(), kEncodeDelayMs);
|
|
}) {
|
|
extensions_.Register<TransmissionOffset>(kTimestampOffsetExtensionId);
|
|
}
|
|
|
|
private:
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
RtpPacket rtp_packet(&extensions_);
|
|
EXPECT_TRUE(rtp_packet.Parse(packet, length));
|
|
|
|
int32_t toffset = 0;
|
|
EXPECT_TRUE(rtp_packet.GetExtension<TransmissionOffset>(&toffset));
|
|
EXPECT_FALSE(rtp_packet.HasExtension<AbsoluteSendTime>());
|
|
EXPECT_GT(toffset, 0);
|
|
observation_complete_.Set();
|
|
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
send_config->encoder_settings.encoder_factory = &encoder_factory_;
|
|
send_config->rtp.extensions.clear();
|
|
send_config->rtp.extensions.push_back(RtpExtension(
|
|
RtpExtension::kTimestampOffsetUri, kTimestampOffsetExtensionId));
|
|
}
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait()) << "Timed out while waiting for a single RTP packet.";
|
|
}
|
|
|
|
test::FunctionVideoEncoderFactory encoder_factory_;
|
|
RtpHeaderExtensionMap extensions_;
|
|
} test;
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsTransportWideSequenceNumbers) {
|
|
static const uint8_t kExtensionId = kTransportSequenceNumberExtensionId;
|
|
class TransportWideSequenceNumberObserver : public test::SendTest {
|
|
public:
|
|
TransportWideSequenceNumberObserver()
|
|
: SendTest(kDefaultTimeoutMs), encoder_factory_([]() {
|
|
return std::make_unique<test::FakeEncoder>(
|
|
Clock::GetRealTimeClock());
|
|
}) {
|
|
extensions_.Register<TransportSequenceNumber>(kExtensionId);
|
|
}
|
|
|
|
private:
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
RtpPacket rtp_packet(&extensions_);
|
|
EXPECT_TRUE(rtp_packet.Parse(packet, length));
|
|
|
|
EXPECT_TRUE(rtp_packet.HasExtension<TransportSequenceNumber>());
|
|
EXPECT_FALSE(rtp_packet.HasExtension<TransmissionOffset>());
|
|
EXPECT_FALSE(rtp_packet.HasExtension<AbsoluteSendTime>());
|
|
|
|
observation_complete_.Set();
|
|
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
send_config->encoder_settings.encoder_factory = &encoder_factory_;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait()) << "Timed out while waiting for a single RTP packet.";
|
|
}
|
|
|
|
test::FunctionVideoEncoderFactory encoder_factory_;
|
|
RtpHeaderExtensionMap extensions_;
|
|
} test;
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsVideoRotation) {
|
|
class VideoRotationObserver : public test::SendTest {
|
|
public:
|
|
VideoRotationObserver() : SendTest(kDefaultTimeoutMs) {
|
|
extensions_.Register<VideoOrientation>(kVideoRotationExtensionId);
|
|
}
|
|
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
RtpPacket rtp_packet(&extensions_);
|
|
EXPECT_TRUE(rtp_packet.Parse(packet, length));
|
|
// Only the last packet of the frame is required to have the extension.
|
|
if (!rtp_packet.Marker())
|
|
return SEND_PACKET;
|
|
EXPECT_EQ(rtp_packet.GetExtension<VideoOrientation>(), kVideoRotation_90);
|
|
observation_complete_.Set();
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
send_config->rtp.extensions.clear();
|
|
send_config->rtp.extensions.push_back(RtpExtension(
|
|
RtpExtension::kVideoRotationUri, kVideoRotationExtensionId));
|
|
}
|
|
|
|
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 single RTP packet.";
|
|
}
|
|
|
|
private:
|
|
RtpHeaderExtensionMap extensions_;
|
|
} test;
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsVideoContentType) {
|
|
class VideoContentTypeObserver : public test::SendTest {
|
|
public:
|
|
VideoContentTypeObserver()
|
|
: SendTest(kDefaultTimeoutMs), first_frame_sent_(false) {
|
|
extensions_.Register<VideoContentTypeExtension>(
|
|
kVideoContentTypeExtensionId);
|
|
}
|
|
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
RtpPacket rtp_packet(&extensions_);
|
|
EXPECT_TRUE(rtp_packet.Parse(packet, length));
|
|
// Only the last packet of the key-frame must have extension.
|
|
if (!rtp_packet.Marker() || first_frame_sent_)
|
|
return SEND_PACKET;
|
|
// First marker bit seen means that the first frame is sent.
|
|
first_frame_sent_ = true;
|
|
VideoContentType type;
|
|
EXPECT_TRUE(rtp_packet.GetExtension<VideoContentTypeExtension>(&type));
|
|
EXPECT_TRUE(videocontenttypehelpers::IsScreenshare(type));
|
|
observation_complete_.Set();
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
send_config->rtp.extensions.clear();
|
|
send_config->rtp.extensions.push_back(RtpExtension(
|
|
RtpExtension::kVideoContentTypeUri, kVideoContentTypeExtensionId));
|
|
encoder_config->content_type = VideoEncoderConfig::ContentType::kScreen;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait()) << "Timed out while waiting for single RTP packet.";
|
|
}
|
|
|
|
private:
|
|
bool first_frame_sent_;
|
|
RtpHeaderExtensionMap extensions_;
|
|
} test;
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsVideoTimingFrames) {
|
|
class VideoTimingObserver : public test::SendTest {
|
|
public:
|
|
VideoTimingObserver()
|
|
: SendTest(kDefaultTimeoutMs), first_frame_sent_(false) {
|
|
extensions_.Register<VideoTimingExtension>(kVideoTimingExtensionId);
|
|
}
|
|
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
RtpPacket rtp_packet(&extensions_);
|
|
EXPECT_TRUE(rtp_packet.Parse(packet, length));
|
|
// Only the last packet of the frame must have extension.
|
|
// Also don't check packets of the second frame if they happen to get
|
|
// through before the test terminates.
|
|
if (!rtp_packet.Marker() || first_frame_sent_)
|
|
return SEND_PACKET;
|
|
EXPECT_TRUE(rtp_packet.HasExtension<VideoTimingExtension>());
|
|
observation_complete_.Set();
|
|
first_frame_sent_ = true;
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
send_config->rtp.extensions.clear();
|
|
send_config->rtp.extensions.push_back(
|
|
RtpExtension(RtpExtension::kVideoTimingUri, kVideoTimingExtensionId));
|
|
}
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait()) << "Timed out while waiting for timing frames.";
|
|
}
|
|
|
|
private:
|
|
RtpHeaderExtensionMap extensions_;
|
|
bool first_frame_sent_;
|
|
} test;
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
class FakeReceiveStatistics : public ReceiveStatisticsProvider {
|
|
public:
|
|
FakeReceiveStatistics(uint32_t send_ssrc,
|
|
uint32_t last_sequence_number,
|
|
uint32_t cumulative_lost,
|
|
uint8_t fraction_lost) {
|
|
stat_.SetMediaSsrc(send_ssrc);
|
|
stat_.SetExtHighestSeqNum(last_sequence_number);
|
|
stat_.SetCumulativeLost(cumulative_lost);
|
|
stat_.SetFractionLost(fraction_lost);
|
|
}
|
|
|
|
std::vector<rtcp::ReportBlock> RtcpReportBlocks(size_t max_blocks) override {
|
|
EXPECT_GE(max_blocks, 1u);
|
|
return {stat_};
|
|
}
|
|
|
|
private:
|
|
rtcp::ReportBlock stat_;
|
|
};
|
|
|
|
class UlpfecObserver : public test::EndToEndTest {
|
|
public:
|
|
// Some of the test cases are expected to time out.
|
|
// Use a shorter timeout window than the default one for those.
|
|
static constexpr int kReducedTimeoutMs = 10000;
|
|
|
|
UlpfecObserver(bool header_extensions_enabled,
|
|
bool use_nack,
|
|
bool expect_red,
|
|
bool expect_ulpfec,
|
|
const std::string& codec,
|
|
VideoEncoderFactory* encoder_factory)
|
|
: EndToEndTest(expect_ulpfec ? VideoSendStreamTest::kDefaultTimeoutMs
|
|
: kReducedTimeoutMs),
|
|
encoder_factory_(encoder_factory),
|
|
payload_name_(codec),
|
|
use_nack_(use_nack),
|
|
expect_red_(expect_red),
|
|
expect_ulpfec_(expect_ulpfec),
|
|
sent_media_(false),
|
|
sent_ulpfec_(false),
|
|
header_extensions_enabled_(header_extensions_enabled) {
|
|
extensions_.Register<AbsoluteSendTime>(kAbsSendTimeExtensionId);
|
|
extensions_.Register<TransportSequenceNumber>(
|
|
kTransportSequenceNumberExtensionId);
|
|
}
|
|
|
|
private:
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
RtpPacket rtp_packet(&extensions_);
|
|
EXPECT_TRUE(rtp_packet.Parse(packet, length));
|
|
|
|
int encapsulated_payload_type = -1;
|
|
if (rtp_packet.PayloadType() == VideoSendStreamTest::kRedPayloadType) {
|
|
EXPECT_TRUE(expect_red_);
|
|
encapsulated_payload_type = rtp_packet.payload()[0];
|
|
if (encapsulated_payload_type !=
|
|
VideoSendStreamTest::kFakeVideoSendPayloadType) {
|
|
EXPECT_EQ(VideoSendStreamTest::kUlpfecPayloadType,
|
|
encapsulated_payload_type);
|
|
}
|
|
} else {
|
|
EXPECT_EQ(VideoSendStreamTest::kFakeVideoSendPayloadType,
|
|
rtp_packet.PayloadType());
|
|
if (rtp_packet.payload_size() > 0) {
|
|
// Not padding-only, media received outside of RED.
|
|
EXPECT_FALSE(expect_red_);
|
|
sent_media_ = true;
|
|
}
|
|
}
|
|
|
|
if (header_extensions_enabled_) {
|
|
uint32_t abs_send_time;
|
|
EXPECT_TRUE(rtp_packet.GetExtension<AbsoluteSendTime>(&abs_send_time));
|
|
uint16_t transport_seq_num;
|
|
EXPECT_TRUE(
|
|
rtp_packet.GetExtension<TransportSequenceNumber>(&transport_seq_num));
|
|
if (!first_packet_) {
|
|
uint32_t kHalf24BitsSpace = 0xFFFFFF / 2;
|
|
if (abs_send_time <= kHalf24BitsSpace &&
|
|
prev_abs_send_time_ > kHalf24BitsSpace) {
|
|
// 24 bits wrap.
|
|
EXPECT_GT(prev_abs_send_time_, abs_send_time);
|
|
} else {
|
|
EXPECT_GE(abs_send_time, prev_abs_send_time_);
|
|
}
|
|
|
|
uint16_t seq_num_diff = transport_seq_num - prev_transport_seq_num_;
|
|
EXPECT_EQ(1, seq_num_diff);
|
|
}
|
|
first_packet_ = false;
|
|
prev_abs_send_time_ = abs_send_time;
|
|
prev_transport_seq_num_ = transport_seq_num;
|
|
}
|
|
|
|
if (encapsulated_payload_type != -1) {
|
|
if (encapsulated_payload_type ==
|
|
VideoSendStreamTest::kUlpfecPayloadType) {
|
|
EXPECT_TRUE(expect_ulpfec_);
|
|
sent_ulpfec_ = true;
|
|
} else {
|
|
sent_media_ = true;
|
|
}
|
|
}
|
|
|
|
if (sent_media_ && sent_ulpfec_) {
|
|
observation_complete_.Set();
|
|
}
|
|
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
std::unique_ptr<test::PacketTransport> CreateSendTransport(
|
|
TaskQueueBase* task_queue,
|
|
Call* sender_call) override {
|
|
// At low RTT (< kLowRttNackMs) -> NACK only, no FEC.
|
|
// Configure some network delay.
|
|
const int kNetworkDelayMs = 100;
|
|
BuiltInNetworkBehaviorConfig config;
|
|
config.loss_percent = 5;
|
|
config.queue_delay_ms = kNetworkDelayMs;
|
|
return std::make_unique<test::PacketTransport>(
|
|
task_queue, sender_call, this, test::PacketTransport::kSender,
|
|
VideoSendStreamTest::payload_type_map_,
|
|
std::make_unique<FakeNetworkPipe>(
|
|
Clock::GetRealTimeClock(),
|
|
std::make_unique<SimulatedNetwork>(config)));
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
if (use_nack_) {
|
|
send_config->rtp.nack.rtp_history_ms =
|
|
(*receive_configs)[0].rtp.nack.rtp_history_ms =
|
|
VideoSendStreamTest::kNackRtpHistoryMs;
|
|
}
|
|
send_config->encoder_settings.encoder_factory = encoder_factory_;
|
|
send_config->rtp.payload_name = payload_name_;
|
|
send_config->rtp.ulpfec.red_payload_type =
|
|
VideoSendStreamTest::kRedPayloadType;
|
|
send_config->rtp.ulpfec.ulpfec_payload_type =
|
|
VideoSendStreamTest::kUlpfecPayloadType;
|
|
if (!header_extensions_enabled_) {
|
|
send_config->rtp.extensions.clear();
|
|
} else {
|
|
send_config->rtp.extensions.push_back(
|
|
RtpExtension(RtpExtension::kAbsSendTimeUri, kAbsSendTimeExtensionId));
|
|
}
|
|
(*receive_configs)[0].rtp.extensions = send_config->rtp.extensions;
|
|
encoder_config->codec_type = PayloadStringToCodecType(payload_name_);
|
|
(*receive_configs)[0].rtp.red_payload_type =
|
|
send_config->rtp.ulpfec.red_payload_type;
|
|
(*receive_configs)[0].rtp.ulpfec_payload_type =
|
|
send_config->rtp.ulpfec.ulpfec_payload_type;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
EXPECT_EQ(expect_ulpfec_, Wait())
|
|
<< "Timed out waiting for ULPFEC and/or media packets.";
|
|
}
|
|
|
|
VideoEncoderFactory* encoder_factory_;
|
|
RtpHeaderExtensionMap extensions_;
|
|
const std::string payload_name_;
|
|
const bool use_nack_;
|
|
const bool expect_red_;
|
|
const bool expect_ulpfec_;
|
|
bool sent_media_;
|
|
bool sent_ulpfec_;
|
|
const bool header_extensions_enabled_;
|
|
bool first_packet_ = true;
|
|
uint32_t prev_abs_send_time_ = 0;
|
|
uint16_t prev_transport_seq_num_ = 0;
|
|
};
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsUlpfecWithExtensions) {
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[]() { return VP8Encoder::Create(); });
|
|
UlpfecObserver test(true, false, true, true, "VP8", &encoder_factory);
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsUlpfecWithoutExtensions) {
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[]() { return VP8Encoder::Create(); });
|
|
UlpfecObserver test(false, false, true, true, "VP8", &encoder_factory);
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
class VideoSendStreamWithoutUlpfecTest : public test::CallTest {
|
|
protected:
|
|
VideoSendStreamWithoutUlpfecTest()
|
|
: field_trial_("WebRTC-DisableUlpFecExperiment/Enabled/") {}
|
|
|
|
test::ScopedFieldTrials field_trial_;
|
|
};
|
|
|
|
TEST_F(VideoSendStreamWithoutUlpfecTest, NoUlpfecIfDisabledThroughFieldTrial) {
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[]() { return VP8Encoder::Create(); });
|
|
UlpfecObserver test(false, false, false, false, "VP8", &encoder_factory);
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
// The FEC scheme used is not efficient for H264, so we should not use RED/FEC
|
|
// since we'll still have to re-request FEC packets, effectively wasting
|
|
// bandwidth since the receiver has to wait for FEC retransmissions to determine
|
|
// that the received state is actually decodable.
|
|
TEST_F(VideoSendStreamTest, DoesNotUtilizeUlpfecForH264WithNackEnabled) {
|
|
test::FunctionVideoEncoderFactory encoder_factory([]() {
|
|
return std::make_unique<test::FakeH264Encoder>(Clock::GetRealTimeClock());
|
|
});
|
|
UlpfecObserver test(false, true, false, false, "H264", &encoder_factory);
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
// Without retransmissions FEC for H264 is fine.
|
|
TEST_F(VideoSendStreamTest, DoesUtilizeUlpfecForH264WithoutNackEnabled) {
|
|
test::FunctionVideoEncoderFactory encoder_factory([]() {
|
|
return std::make_unique<test::FakeH264Encoder>(Clock::GetRealTimeClock());
|
|
});
|
|
UlpfecObserver test(false, false, true, true, "H264", &encoder_factory);
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, DoesUtilizeUlpfecForVp8WithNackEnabled) {
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[]() { return VP8Encoder::Create(); });
|
|
UlpfecObserver test(false, true, true, true, "VP8", &encoder_factory);
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
#if defined(RTC_ENABLE_VP9)
|
|
TEST_F(VideoSendStreamTest, DoesUtilizeUlpfecForVp9WithNackEnabled) {
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[]() { return VP9Encoder::Create(); });
|
|
UlpfecObserver test(false, true, true, true, "VP9", &encoder_factory);
|
|
RunBaseTest(&test);
|
|
}
|
|
#endif // defined(RTC_ENABLE_VP9)
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsUlpfecWithMultithreadedH264) {
|
|
std::unique_ptr<TaskQueueFactory> task_queue_factory =
|
|
CreateDefaultTaskQueueFactory();
|
|
test::FunctionVideoEncoderFactory encoder_factory([&]() {
|
|
return std::make_unique<test::MultithreadedFakeH264Encoder>(
|
|
Clock::GetRealTimeClock(), task_queue_factory.get());
|
|
});
|
|
UlpfecObserver test(false, false, true, true, "H264", &encoder_factory);
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
// TODO(brandtr): Move these FlexFEC tests when we have created
|
|
// FlexfecSendStream.
|
|
class FlexfecObserver : public test::EndToEndTest {
|
|
public:
|
|
FlexfecObserver(bool header_extensions_enabled,
|
|
bool use_nack,
|
|
const std::string& codec,
|
|
VideoEncoderFactory* encoder_factory,
|
|
size_t num_video_streams)
|
|
: EndToEndTest(VideoSendStreamTest::kDefaultTimeoutMs),
|
|
encoder_factory_(encoder_factory),
|
|
payload_name_(codec),
|
|
use_nack_(use_nack),
|
|
sent_media_(false),
|
|
sent_flexfec_(false),
|
|
header_extensions_enabled_(header_extensions_enabled),
|
|
num_video_streams_(num_video_streams) {
|
|
extensions_.Register<AbsoluteSendTime>(kAbsSendTimeExtensionId);
|
|
extensions_.Register<TransmissionOffset>(kTimestampOffsetExtensionId);
|
|
extensions_.Register<TransportSequenceNumber>(
|
|
kTransportSequenceNumberExtensionId);
|
|
}
|
|
|
|
size_t GetNumFlexfecStreams() const override { return 1; }
|
|
size_t GetNumVideoStreams() const override { return num_video_streams_; }
|
|
|
|
private:
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
RtpPacket rtp_packet(&extensions_);
|
|
EXPECT_TRUE(rtp_packet.Parse(packet, length));
|
|
|
|
if (rtp_packet.PayloadType() == VideoSendStreamTest::kFlexfecPayloadType) {
|
|
EXPECT_EQ(VideoSendStreamTest::kFlexfecSendSsrc, rtp_packet.Ssrc());
|
|
sent_flexfec_ = true;
|
|
} else {
|
|
EXPECT_EQ(VideoSendStreamTest::kFakeVideoSendPayloadType,
|
|
rtp_packet.PayloadType());
|
|
EXPECT_THAT(::testing::make_tuple(VideoSendStreamTest::kVideoSendSsrcs,
|
|
num_video_streams_),
|
|
::testing::Contains(rtp_packet.Ssrc()));
|
|
sent_media_ = true;
|
|
}
|
|
|
|
if (header_extensions_enabled_) {
|
|
EXPECT_TRUE(rtp_packet.HasExtension<AbsoluteSendTime>());
|
|
EXPECT_TRUE(rtp_packet.HasExtension<TransmissionOffset>());
|
|
EXPECT_TRUE(rtp_packet.HasExtension<TransportSequenceNumber>());
|
|
}
|
|
|
|
if (sent_media_ && sent_flexfec_) {
|
|
observation_complete_.Set();
|
|
}
|
|
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
std::unique_ptr<test::PacketTransport> CreateSendTransport(
|
|
TaskQueueBase* task_queue,
|
|
Call* sender_call) override {
|
|
// At low RTT (< kLowRttNackMs) -> NACK only, no FEC.
|
|
// Therefore we need some network delay.
|
|
const int kNetworkDelayMs = 100;
|
|
BuiltInNetworkBehaviorConfig config;
|
|
config.loss_percent = 5;
|
|
config.queue_delay_ms = kNetworkDelayMs;
|
|
return std::make_unique<test::PacketTransport>(
|
|
task_queue, sender_call, this, test::PacketTransport::kSender,
|
|
VideoSendStreamTest::payload_type_map_,
|
|
std::make_unique<FakeNetworkPipe>(
|
|
Clock::GetRealTimeClock(),
|
|
std::make_unique<SimulatedNetwork>(config)));
|
|
}
|
|
|
|
std::unique_ptr<test::PacketTransport> CreateReceiveTransport(
|
|
TaskQueueBase* task_queue) override {
|
|
// We need the RTT to be >200 ms to send FEC and the network delay for the
|
|
// send transport is 100 ms, so add 100 ms (but no loss) on the return link.
|
|
BuiltInNetworkBehaviorConfig config;
|
|
config.loss_percent = 0;
|
|
config.queue_delay_ms = 100;
|
|
return std::make_unique<test::PacketTransport>(
|
|
task_queue, nullptr, this, test::PacketTransport::kReceiver,
|
|
VideoSendStreamTest::payload_type_map_,
|
|
std::make_unique<FakeNetworkPipe>(
|
|
Clock::GetRealTimeClock(),
|
|
std::make_unique<SimulatedNetwork>(config)));
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
if (use_nack_) {
|
|
send_config->rtp.nack.rtp_history_ms =
|
|
(*receive_configs)[0].rtp.nack.rtp_history_ms =
|
|
VideoSendStreamTest::kNackRtpHistoryMs;
|
|
}
|
|
send_config->encoder_settings.encoder_factory = encoder_factory_;
|
|
send_config->rtp.payload_name = payload_name_;
|
|
if (header_extensions_enabled_) {
|
|
send_config->rtp.extensions.push_back(
|
|
RtpExtension(RtpExtension::kAbsSendTimeUri, kAbsSendTimeExtensionId));
|
|
send_config->rtp.extensions.push_back(RtpExtension(
|
|
RtpExtension::kTimestampOffsetUri, kTimestampOffsetExtensionId));
|
|
} else {
|
|
send_config->rtp.extensions.clear();
|
|
}
|
|
(*receive_configs)[0].rtp.extensions = send_config->rtp.extensions;
|
|
encoder_config->codec_type = PayloadStringToCodecType(payload_name_);
|
|
}
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait())
|
|
<< "Timed out waiting for FlexFEC and/or media packets.";
|
|
}
|
|
|
|
VideoEncoderFactory* encoder_factory_;
|
|
RtpHeaderExtensionMap extensions_;
|
|
const std::string payload_name_;
|
|
const bool use_nack_;
|
|
bool sent_media_;
|
|
bool sent_flexfec_;
|
|
const bool header_extensions_enabled_;
|
|
const size_t num_video_streams_;
|
|
};
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsFlexfecVp8) {
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[]() { return VP8Encoder::Create(); });
|
|
FlexfecObserver test(false, false, "VP8", &encoder_factory, 1);
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsFlexfecSimulcastVp8) {
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[]() { return VP8Encoder::Create(); });
|
|
FlexfecObserver test(false, false, "VP8", &encoder_factory, 2);
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsFlexfecWithNackVp8) {
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[]() { return VP8Encoder::Create(); });
|
|
FlexfecObserver test(false, true, "VP8", &encoder_factory, 1);
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsFlexfecWithRtpExtensionsVp8) {
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[]() { return VP8Encoder::Create(); });
|
|
FlexfecObserver test(true, false, "VP8", &encoder_factory, 1);
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
#if defined(RTC_ENABLE_VP9)
|
|
TEST_F(VideoSendStreamTest, SupportsFlexfecVp9) {
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[]() { return VP9Encoder::Create(); });
|
|
FlexfecObserver test(false, false, "VP9", &encoder_factory, 1);
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsFlexfecWithNackVp9) {
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[]() { return VP9Encoder::Create(); });
|
|
FlexfecObserver test(false, true, "VP9", &encoder_factory, 1);
|
|
RunBaseTest(&test);
|
|
}
|
|
#endif // defined(RTC_ENABLE_VP9)
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsFlexfecH264) {
|
|
test::FunctionVideoEncoderFactory encoder_factory([]() {
|
|
return std::make_unique<test::FakeH264Encoder>(Clock::GetRealTimeClock());
|
|
});
|
|
FlexfecObserver test(false, false, "H264", &encoder_factory, 1);
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsFlexfecWithNackH264) {
|
|
test::FunctionVideoEncoderFactory encoder_factory([]() {
|
|
return std::make_unique<test::FakeH264Encoder>(Clock::GetRealTimeClock());
|
|
});
|
|
FlexfecObserver test(false, true, "H264", &encoder_factory, 1);
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, SupportsFlexfecWithMultithreadedH264) {
|
|
std::unique_ptr<TaskQueueFactory> task_queue_factory =
|
|
CreateDefaultTaskQueueFactory();
|
|
test::FunctionVideoEncoderFactory encoder_factory([&]() {
|
|
return std::make_unique<test::MultithreadedFakeH264Encoder>(
|
|
Clock::GetRealTimeClock(), task_queue_factory.get());
|
|
});
|
|
|
|
FlexfecObserver test(false, false, "H264", &encoder_factory, 1);
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
void VideoSendStreamTest::TestNackRetransmission(
|
|
uint32_t retransmit_ssrc,
|
|
uint8_t retransmit_payload_type) {
|
|
class NackObserver : public test::SendTest {
|
|
public:
|
|
explicit NackObserver(uint32_t retransmit_ssrc,
|
|
uint8_t retransmit_payload_type)
|
|
: SendTest(kDefaultTimeoutMs),
|
|
send_count_(0),
|
|
retransmit_count_(0),
|
|
retransmit_ssrc_(retransmit_ssrc),
|
|
retransmit_payload_type_(retransmit_payload_type) {}
|
|
|
|
private:
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
RtpPacket rtp_packet;
|
|
EXPECT_TRUE(rtp_packet.Parse(packet, length));
|
|
|
|
// NACK packets two times at some arbitrary points.
|
|
const int kNackedPacketsAtOnceCount = 3;
|
|
const int kRetransmitTarget = kNackedPacketsAtOnceCount * 2;
|
|
|
|
// Skip padding packets because they will never be retransmitted.
|
|
if (rtp_packet.payload_size() == 0) {
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
++send_count_;
|
|
|
|
// NACK packets at arbitrary points.
|
|
if (send_count_ % 25 == 0) {
|
|
RTCPSender::Configuration config;
|
|
config.clock = Clock::GetRealTimeClock();
|
|
config.outgoing_transport = transport_adapter_.get();
|
|
config.rtcp_report_interval = TimeDelta::Millis(kRtcpIntervalMs);
|
|
config.local_media_ssrc = kReceiverLocalVideoSsrc;
|
|
RTCPSender rtcp_sender(config);
|
|
|
|
rtcp_sender.SetRTCPStatus(RtcpMode::kReducedSize);
|
|
rtcp_sender.SetRemoteSSRC(kVideoSendSsrcs[0]);
|
|
|
|
RTCPSender::FeedbackState feedback_state;
|
|
uint16_t nack_sequence_numbers[kNackedPacketsAtOnceCount];
|
|
int nack_count = 0;
|
|
for (uint16_t sequence_number :
|
|
sequence_numbers_pending_retransmission_) {
|
|
if (nack_count < kNackedPacketsAtOnceCount) {
|
|
nack_sequence_numbers[nack_count++] = sequence_number;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
EXPECT_EQ(0, rtcp_sender.SendRTCP(feedback_state, kRtcpNack, nack_count,
|
|
nack_sequence_numbers));
|
|
}
|
|
|
|
uint16_t sequence_number = rtp_packet.SequenceNumber();
|
|
if (rtp_packet.Ssrc() == retransmit_ssrc_ &&
|
|
retransmit_ssrc_ != kVideoSendSsrcs[0]) {
|
|
// Not kVideoSendSsrcs[0], assume correct RTX packet. Extract sequence
|
|
// number.
|
|
const uint8_t* rtx_header = rtp_packet.payload().data();
|
|
sequence_number = (rtx_header[0] << 8) + rtx_header[1];
|
|
}
|
|
|
|
auto it = sequence_numbers_pending_retransmission_.find(sequence_number);
|
|
if (it == sequence_numbers_pending_retransmission_.end()) {
|
|
// Not currently pending retransmission. Add it to retransmission queue
|
|
// if media and limit not reached.
|
|
if (rtp_packet.Ssrc() == kVideoSendSsrcs[0] &&
|
|
rtp_packet.payload_size() > 0 &&
|
|
retransmit_count_ +
|
|
sequence_numbers_pending_retransmission_.size() <
|
|
kRetransmitTarget) {
|
|
sequence_numbers_pending_retransmission_.insert(sequence_number);
|
|
}
|
|
} else {
|
|
// Packet is a retransmission, remove it from queue and check if done.
|
|
sequence_numbers_pending_retransmission_.erase(it);
|
|
if (++retransmit_count_ == kRetransmitTarget) {
|
|
EXPECT_EQ(retransmit_ssrc_, rtp_packet.Ssrc());
|
|
EXPECT_EQ(retransmit_payload_type_, rtp_packet.PayloadType());
|
|
observation_complete_.Set();
|
|
}
|
|
}
|
|
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
transport_adapter_.reset(
|
|
new internal::TransportAdapter(send_config->send_transport));
|
|
transport_adapter_->Enable();
|
|
send_config->rtp.nack.rtp_history_ms = kNackRtpHistoryMs;
|
|
send_config->rtp.rtx.payload_type = retransmit_payload_type_;
|
|
if (retransmit_ssrc_ != kVideoSendSsrcs[0])
|
|
send_config->rtp.rtx.ssrcs.push_back(retransmit_ssrc_);
|
|
}
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait()) << "Timed out while waiting for NACK retransmission.";
|
|
}
|
|
|
|
std::unique_ptr<internal::TransportAdapter> transport_adapter_;
|
|
int send_count_;
|
|
int retransmit_count_;
|
|
const uint32_t retransmit_ssrc_;
|
|
const uint8_t retransmit_payload_type_;
|
|
std::set<uint16_t> sequence_numbers_pending_retransmission_;
|
|
} test(retransmit_ssrc, retransmit_payload_type);
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, RetransmitsNack) {
|
|
// Normal NACKs should use the send SSRC.
|
|
TestNackRetransmission(kVideoSendSsrcs[0], kFakeVideoSendPayloadType);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, RetransmitsNackOverRtx) {
|
|
// NACKs over RTX should use a separate SSRC.
|
|
TestNackRetransmission(kSendRtxSsrcs[0], kSendRtxPayloadType);
|
|
}
|
|
|
|
void VideoSendStreamTest::TestPacketFragmentationSize(VideoFormat format,
|
|
bool with_fec) {
|
|
// Use a fake encoder to output a frame of every size in the range [90, 290],
|
|
// for each size making sure that the exact number of payload bytes received
|
|
// is correct and that packets are fragmented to respect max packet size.
|
|
static const size_t kMaxPacketSize = 128;
|
|
static const size_t start = 90;
|
|
static const size_t stop = 290;
|
|
|
|
// Observer that verifies that the expected number of packets and bytes
|
|
// arrive for each frame size, from start_size to stop_size.
|
|
class FrameFragmentationTest : public test::SendTest {
|
|
public:
|
|
FrameFragmentationTest(size_t max_packet_size,
|
|
size_t start_size,
|
|
size_t stop_size,
|
|
bool test_generic_packetization,
|
|
bool use_fec)
|
|
: SendTest(kLongTimeoutMs),
|
|
encoder_(stop),
|
|
encoder_factory_(&encoder_),
|
|
max_packet_size_(max_packet_size),
|
|
stop_size_(stop_size),
|
|
test_generic_packetization_(test_generic_packetization),
|
|
use_fec_(use_fec),
|
|
packet_count_(0),
|
|
packets_lost_(0),
|
|
last_packet_count_(0),
|
|
last_packets_lost_(0),
|
|
accumulated_size_(0),
|
|
accumulated_payload_(0),
|
|
fec_packet_received_(false),
|
|
current_size_rtp_(start_size),
|
|
current_size_frame_(static_cast<int>(start_size)) {
|
|
// Fragmentation required, this test doesn't make sense without it.
|
|
encoder_.SetFrameSize(start_size);
|
|
RTC_DCHECK_GT(stop_size, max_packet_size);
|
|
if (!test_generic_packetization_)
|
|
encoder_.SetCodecType(kVideoCodecVP8);
|
|
}
|
|
|
|
private:
|
|
Action OnSendRtp(const uint8_t* packet, size_t size) override {
|
|
size_t length = size;
|
|
RtpPacket rtp_packet;
|
|
EXPECT_TRUE(rtp_packet.Parse(packet, length));
|
|
|
|
EXPECT_LE(length, max_packet_size_);
|
|
|
|
if (use_fec_ && rtp_packet.payload_size() > 0) {
|
|
uint8_t payload_type = rtp_packet.payload()[0];
|
|
bool is_fec = rtp_packet.PayloadType() == kRedPayloadType &&
|
|
payload_type == kUlpfecPayloadType;
|
|
if (is_fec) {
|
|
fec_packet_received_ = true;
|
|
return SEND_PACKET;
|
|
}
|
|
}
|
|
|
|
accumulated_size_ += length;
|
|
|
|
if (use_fec_)
|
|
TriggerLossReport(rtp_packet);
|
|
|
|
if (test_generic_packetization_) {
|
|
size_t overhead = rtp_packet.headers_size() + rtp_packet.padding_size();
|
|
// Only remove payload header and RED header if the packet actually
|
|
// contains payload.
|
|
if (length > overhead) {
|
|
overhead += (1 /* Generic header */);
|
|
if (use_fec_)
|
|
overhead += 1; // RED for FEC header.
|
|
}
|
|
EXPECT_GE(length, overhead);
|
|
accumulated_payload_ += length - overhead;
|
|
}
|
|
|
|
// Marker bit set indicates last packet of a frame.
|
|
if (rtp_packet.Marker()) {
|
|
if (use_fec_ && accumulated_payload_ == current_size_rtp_ - 1) {
|
|
// With FEC enabled, frame size is incremented asynchronously, so
|
|
// "old" frames one byte too small may arrive. Accept, but don't
|
|
// increase expected frame size.
|
|
accumulated_size_ = 0;
|
|
accumulated_payload_ = 0;
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
EXPECT_GE(accumulated_size_, current_size_rtp_);
|
|
if (test_generic_packetization_) {
|
|
EXPECT_EQ(current_size_rtp_, accumulated_payload_);
|
|
}
|
|
|
|
// Last packet of frame; reset counters.
|
|
accumulated_size_ = 0;
|
|
accumulated_payload_ = 0;
|
|
if (current_size_rtp_ == stop_size_) {
|
|
// Done! (Don't increase size again, might arrive more @ stop_size).
|
|
observation_complete_.Set();
|
|
} else {
|
|
// Increase next expected frame size. If testing with FEC, make sure
|
|
// a FEC packet has been received for this frame size before
|
|
// proceeding, to make sure that redundancy packets don't exceed
|
|
// size limit.
|
|
if (!use_fec_) {
|
|
++current_size_rtp_;
|
|
} else if (fec_packet_received_) {
|
|
fec_packet_received_ = false;
|
|
++current_size_rtp_;
|
|
|
|
MutexLock lock(&mutex_);
|
|
++current_size_frame_;
|
|
}
|
|
}
|
|
}
|
|
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
void TriggerLossReport(const RtpPacket& rtp_packet) {
|
|
// Send lossy receive reports to trigger FEC enabling.
|
|
const int kLossPercent = 5;
|
|
if (++packet_count_ % (100 / kLossPercent) == 0) {
|
|
packets_lost_++;
|
|
int loss_delta = packets_lost_ - last_packets_lost_;
|
|
int packets_delta = packet_count_ - last_packet_count_;
|
|
last_packet_count_ = packet_count_;
|
|
last_packets_lost_ = packets_lost_;
|
|
uint8_t loss_ratio =
|
|
static_cast<uint8_t>(loss_delta * 255 / packets_delta);
|
|
FakeReceiveStatistics lossy_receive_stats(
|
|
kVideoSendSsrcs[0], rtp_packet.SequenceNumber(),
|
|
packets_lost_, // Cumulative lost.
|
|
loss_ratio); // Loss percent.
|
|
RTCPSender::Configuration config;
|
|
config.clock = Clock::GetRealTimeClock();
|
|
config.receive_statistics = &lossy_receive_stats;
|
|
config.outgoing_transport = transport_adapter_.get();
|
|
config.rtcp_report_interval = TimeDelta::Millis(kRtcpIntervalMs);
|
|
config.local_media_ssrc = kVideoSendSsrcs[0];
|
|
RTCPSender rtcp_sender(config);
|
|
|
|
rtcp_sender.SetRTCPStatus(RtcpMode::kReducedSize);
|
|
rtcp_sender.SetRemoteSSRC(kVideoSendSsrcs[0]);
|
|
|
|
RTCPSender::FeedbackState feedback_state;
|
|
|
|
EXPECT_EQ(0, rtcp_sender.SendRTCP(feedback_state, kRtcpRr));
|
|
}
|
|
}
|
|
|
|
void UpdateConfiguration() {
|
|
MutexLock lock(&mutex_);
|
|
// Increase frame size for next encoded frame, in the context of the
|
|
// encoder thread.
|
|
if (!use_fec_ && current_size_frame_ < static_cast<int32_t>(stop_size_)) {
|
|
++current_size_frame_;
|
|
}
|
|
encoder_.SetFrameSize(static_cast<size_t>(current_size_frame_));
|
|
}
|
|
void ModifySenderBitrateConfig(
|
|
BitrateConstraints* bitrate_config) override {
|
|
const int kMinBitrateBps = 300000;
|
|
bitrate_config->min_bitrate_bps = kMinBitrateBps;
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
transport_adapter_.reset(
|
|
new internal::TransportAdapter(send_config->send_transport));
|
|
transport_adapter_->Enable();
|
|
if (use_fec_) {
|
|
send_config->rtp.ulpfec.red_payload_type = kRedPayloadType;
|
|
send_config->rtp.ulpfec.ulpfec_payload_type = kUlpfecPayloadType;
|
|
}
|
|
|
|
if (!test_generic_packetization_)
|
|
send_config->rtp.payload_name = "VP8";
|
|
|
|
send_config->encoder_settings.encoder_factory = &encoder_factory_;
|
|
send_config->rtp.max_packet_size = kMaxPacketSize;
|
|
encoder_.RegisterPostEncodeCallback([this]() { UpdateConfiguration(); });
|
|
|
|
// Make sure there is at least one extension header, to make the RTP
|
|
// header larger than the base length of 12 bytes.
|
|
EXPECT_FALSE(send_config->rtp.extensions.empty());
|
|
|
|
// Setup screen content disables frame dropping which makes this easier.
|
|
EXPECT_EQ(1u, encoder_config->simulcast_layers.size());
|
|
encoder_config->simulcast_layers[0].num_temporal_layers = 2;
|
|
encoder_config->content_type = VideoEncoderConfig::ContentType::kScreen;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait()) << "Timed out while observing incoming RTP packets.";
|
|
}
|
|
|
|
std::unique_ptr<internal::TransportAdapter> transport_adapter_;
|
|
test::ConfigurableFrameSizeEncoder encoder_;
|
|
test::VideoEncoderProxyFactory encoder_factory_;
|
|
|
|
const size_t max_packet_size_;
|
|
const size_t stop_size_;
|
|
const bool test_generic_packetization_;
|
|
const bool use_fec_;
|
|
|
|
uint32_t packet_count_;
|
|
uint32_t packets_lost_;
|
|
uint32_t last_packet_count_;
|
|
uint32_t last_packets_lost_;
|
|
size_t accumulated_size_;
|
|
size_t accumulated_payload_;
|
|
bool fec_packet_received_;
|
|
|
|
size_t current_size_rtp_;
|
|
Mutex mutex_;
|
|
int current_size_frame_ RTC_GUARDED_BY(mutex_);
|
|
};
|
|
|
|
// Don't auto increment if FEC is used; continue sending frame size until
|
|
// a FEC packet has been received.
|
|
FrameFragmentationTest test(kMaxPacketSize, start, stop, format == kGeneric,
|
|
with_fec);
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
// TODO(sprang): Is there any way of speeding up these tests?
|
|
TEST_F(VideoSendStreamTest, FragmentsGenericAccordingToMaxPacketSize) {
|
|
TestPacketFragmentationSize(kGeneric, false);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, FragmentsGenericAccordingToMaxPacketSizeWithFec) {
|
|
TestPacketFragmentationSize(kGeneric, true);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, FragmentsVp8AccordingToMaxPacketSize) {
|
|
TestPacketFragmentationSize(kVP8, false);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, FragmentsVp8AccordingToMaxPacketSizeWithFec) {
|
|
TestPacketFragmentationSize(kVP8, true);
|
|
}
|
|
|
|
// This test that padding stops being send after a while if the Camera stops
|
|
// producing video frames and that padding resumes if the camera restarts.
|
|
TEST_F(VideoSendStreamTest, NoPaddingWhenVideoIsMuted) {
|
|
class NoPaddingWhenVideoIsMuted : public test::SendTest {
|
|
public:
|
|
NoPaddingWhenVideoIsMuted()
|
|
: SendTest(kDefaultTimeoutMs),
|
|
clock_(Clock::GetRealTimeClock()),
|
|
capturer_(nullptr) {}
|
|
|
|
private:
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
MutexLock lock(&mutex_);
|
|
last_packet_time_ms_ = clock_->TimeInMilliseconds();
|
|
|
|
RtpPacket rtp_packet;
|
|
rtp_packet.Parse(packet, length);
|
|
const bool only_padding = rtp_packet.payload_size() == 0;
|
|
|
|
if (test_state_ == kBeforeStopCapture) {
|
|
// Packets are flowing, stop camera.
|
|
capturer_->Stop();
|
|
test_state_ = kWaitingForPadding;
|
|
} else if (test_state_ == kWaitingForPadding && only_padding) {
|
|
// We're still getting padding, after stopping camera.
|
|
test_state_ = kWaitingForNoPackets;
|
|
} else if (test_state_ == kWaitingForMediaAfterCameraRestart &&
|
|
!only_padding) {
|
|
// Media packets are flowing again, stop camera a second time.
|
|
capturer_->Stop();
|
|
test_state_ = kWaitingForPaddingAfterCameraStopsAgain;
|
|
} else if (test_state_ == kWaitingForPaddingAfterCameraStopsAgain &&
|
|
only_padding) {
|
|
// Padding is still flowing, test ok.
|
|
observation_complete_.Set();
|
|
}
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
Action OnSendRtcp(const uint8_t* packet, size_t length) override {
|
|
MutexLock lock(&mutex_);
|
|
const int kNoPacketsThresholdMs = 2000;
|
|
if (test_state_ == kWaitingForNoPackets &&
|
|
(last_packet_time_ms_ &&
|
|
clock_->TimeInMilliseconds() - last_packet_time_ms_.value() >
|
|
kNoPacketsThresholdMs)) {
|
|
// No packets seen for `kNoPacketsThresholdMs`, restart camera.
|
|
capturer_->Start();
|
|
test_state_ = kWaitingForMediaAfterCameraRestart;
|
|
}
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
// Make sure padding is sent if encoder is not producing media.
|
|
encoder_config->min_transmit_bitrate_bps = 50000;
|
|
}
|
|
|
|
void OnFrameGeneratorCapturerCreated(
|
|
test::FrameGeneratorCapturer* frame_generator_capturer) override {
|
|
MutexLock lock(&mutex_);
|
|
capturer_ = frame_generator_capturer;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait())
|
|
<< "Timed out while waiting for RTP packets to stop being sent.";
|
|
}
|
|
|
|
enum TestState {
|
|
kBeforeStopCapture,
|
|
kWaitingForPadding,
|
|
kWaitingForNoPackets,
|
|
kWaitingForMediaAfterCameraRestart,
|
|
kWaitingForPaddingAfterCameraStopsAgain
|
|
};
|
|
|
|
TestState test_state_ = kBeforeStopCapture;
|
|
Clock* const clock_;
|
|
Mutex mutex_;
|
|
absl::optional<int64_t> last_packet_time_ms_ RTC_GUARDED_BY(mutex_);
|
|
test::FrameGeneratorCapturer* capturer_ RTC_GUARDED_BY(mutex_);
|
|
} test;
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, PaddingIsPrimarilyRetransmissions) {
|
|
const int kCapacityKbps = 10000; // 10 Mbps
|
|
class PaddingIsPrimarilyRetransmissions : public test::EndToEndTest {
|
|
public:
|
|
PaddingIsPrimarilyRetransmissions()
|
|
: EndToEndTest(kDefaultTimeoutMs),
|
|
clock_(Clock::GetRealTimeClock()),
|
|
padding_length_(0),
|
|
total_length_(0),
|
|
call_(nullptr) {}
|
|
|
|
private:
|
|
void OnCallsCreated(Call* sender_call, Call* receiver_call) override {
|
|
call_ = sender_call;
|
|
}
|
|
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
MutexLock lock(&mutex_);
|
|
|
|
RtpPacket rtp_packet;
|
|
rtp_packet.Parse(packet, length);
|
|
padding_length_ += rtp_packet.padding_size();
|
|
total_length_ += length;
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
std::unique_ptr<test::PacketTransport> CreateSendTransport(
|
|
TaskQueueBase* task_queue,
|
|
Call* sender_call) override {
|
|
const int kNetworkDelayMs = 50;
|
|
BuiltInNetworkBehaviorConfig config;
|
|
config.loss_percent = 10;
|
|
config.link_capacity_kbps = kCapacityKbps;
|
|
config.queue_delay_ms = kNetworkDelayMs;
|
|
return std::make_unique<test::PacketTransport>(
|
|
task_queue, sender_call, this, test::PacketTransport::kSender,
|
|
payload_type_map_,
|
|
std::make_unique<FakeNetworkPipe>(
|
|
Clock::GetRealTimeClock(),
|
|
std::make_unique<SimulatedNetwork>(config)));
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
// Turn on RTX.
|
|
send_config->rtp.rtx.payload_type = kFakeVideoSendPayloadType;
|
|
send_config->rtp.rtx.ssrcs.push_back(kSendRtxSsrcs[0]);
|
|
}
|
|
|
|
void PerformTest() override {
|
|
// TODO(isheriff): Some platforms do not ramp up as expected to full
|
|
// capacity due to packet scheduling delays. Fix that before getting
|
|
// rid of this.
|
|
SleepMs(5000);
|
|
{
|
|
MutexLock lock(&mutex_);
|
|
// Expect padding to be a small percentage of total bytes sent.
|
|
EXPECT_LT(padding_length_, .1 * total_length_);
|
|
}
|
|
}
|
|
|
|
Mutex mutex_;
|
|
Clock* const clock_;
|
|
size_t padding_length_ RTC_GUARDED_BY(mutex_);
|
|
size_t total_length_ RTC_GUARDED_BY(mutex_);
|
|
Call* call_;
|
|
} test;
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
// This test first observes "high" bitrate use at which point it sends a REMB to
|
|
// indicate that it should be lowered significantly. The test then observes that
|
|
// the bitrate observed is sinking well below the min-transmit-bitrate threshold
|
|
// to verify that the min-transmit bitrate respects incoming REMB.
|
|
//
|
|
// Note that the test starts at "high" bitrate and does not ramp up to "higher"
|
|
// bitrate since no receiver block or remb is sent in the initial phase.
|
|
TEST_F(VideoSendStreamTest, MinTransmitBitrateRespectsRemb) {
|
|
static const int kMinTransmitBitrateBps = 400000;
|
|
static const int kHighBitrateBps = 150000;
|
|
static const int kRembBitrateBps = 80000;
|
|
static const int kRembRespectedBitrateBps = 100000;
|
|
class BitrateObserver : public test::SendTest {
|
|
public:
|
|
explicit BitrateObserver(TaskQueueBase* task_queue)
|
|
: SendTest(kDefaultTimeoutMs),
|
|
task_queue_(task_queue),
|
|
retranmission_rate_limiter_(Clock::GetRealTimeClock(), 1000),
|
|
stream_(nullptr),
|
|
bitrate_capped_(false),
|
|
task_safety_flag_(PendingTaskSafetyFlag::CreateDetached()) {}
|
|
|
|
~BitrateObserver() override {
|
|
// Make sure we free `rtp_rtcp_` in the same context as we constructed it.
|
|
SendTask(RTC_FROM_HERE, task_queue_, [this]() { rtp_rtcp_ = nullptr; });
|
|
}
|
|
|
|
private:
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
if (IsRtcpPacket(rtc::MakeArrayView(packet, length)))
|
|
return DROP_PACKET;
|
|
|
|
RtpPacket rtp_packet;
|
|
RTC_CHECK(rtp_packet.Parse(packet, length));
|
|
const uint32_t ssrc = rtp_packet.Ssrc();
|
|
RTC_DCHECK(stream_);
|
|
|
|
task_queue_->PostTask(ToQueuedTask(task_safety_flag_, [this, ssrc]() {
|
|
VideoSendStream::Stats stats = stream_->GetStats();
|
|
if (!stats.substreams.empty()) {
|
|
EXPECT_EQ(1u, stats.substreams.size());
|
|
int total_bitrate_bps =
|
|
stats.substreams.begin()->second.total_bitrate_bps;
|
|
test::PrintResult(
|
|
"bitrate_stats_", "min_transmit_bitrate_low_remb", "bitrate_bps",
|
|
static_cast<size_t>(total_bitrate_bps), "bps", false);
|
|
if (total_bitrate_bps > kHighBitrateBps) {
|
|
rtp_rtcp_->SetRemb(kRembBitrateBps, {ssrc});
|
|
bitrate_capped_ = true;
|
|
} else if (bitrate_capped_ &&
|
|
total_bitrate_bps < kRembRespectedBitrateBps) {
|
|
observation_complete_.Set();
|
|
}
|
|
}
|
|
}));
|
|
|
|
// Packets don't have to be delivered since the test is the receiver.
|
|
return DROP_PACKET;
|
|
}
|
|
|
|
void OnVideoStreamsCreated(
|
|
VideoSendStream* send_stream,
|
|
const std::vector<VideoReceiveStream*>& receive_streams) override {
|
|
stream_ = send_stream;
|
|
RtpRtcpInterface::Configuration config;
|
|
config.clock = Clock::GetRealTimeClock();
|
|
config.outgoing_transport = feedback_transport_.get();
|
|
config.retransmission_rate_limiter = &retranmission_rate_limiter_;
|
|
rtp_rtcp_ = ModuleRtpRtcpImpl2::Create(config);
|
|
rtp_rtcp_->SetRTCPStatus(RtcpMode::kReducedSize);
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
feedback_transport_.reset(
|
|
new internal::TransportAdapter(send_config->send_transport));
|
|
feedback_transport_->Enable();
|
|
encoder_config->min_transmit_bitrate_bps = kMinTransmitBitrateBps;
|
|
}
|
|
|
|
void OnStreamsStopped() override { task_safety_flag_->SetNotAlive(); }
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait())
|
|
<< "Timeout while waiting for low bitrate stats after REMB.";
|
|
}
|
|
|
|
TaskQueueBase* const task_queue_;
|
|
std::unique_ptr<ModuleRtpRtcpImpl2> rtp_rtcp_;
|
|
std::unique_ptr<internal::TransportAdapter> feedback_transport_;
|
|
RateLimiter retranmission_rate_limiter_;
|
|
VideoSendStream* stream_;
|
|
bool bitrate_capped_;
|
|
rtc::scoped_refptr<PendingTaskSafetyFlag> task_safety_flag_;
|
|
} test(task_queue());
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, ChangingNetworkRoute) {
|
|
static const int kStartBitrateBps = 300000;
|
|
static const int kNewMaxBitrateBps = 1234567;
|
|
static const uint8_t kExtensionId = kTransportSequenceNumberExtensionId;
|
|
class ChangingNetworkRouteTest : public test::EndToEndTest {
|
|
public:
|
|
explicit ChangingNetworkRouteTest(TaskQueueBase* task_queue)
|
|
: EndToEndTest(test::CallTest::kDefaultTimeoutMs),
|
|
task_queue_(task_queue),
|
|
call_(nullptr) {
|
|
module_process_thread_.Detach();
|
|
task_queue_thread_.Detach();
|
|
extensions_.Register<TransportSequenceNumber>(kExtensionId);
|
|
}
|
|
|
|
~ChangingNetworkRouteTest() {
|
|
// Block until all already posted tasks run to avoid 'use after free'
|
|
// when such task accesses `this`.
|
|
SendTask(RTC_FROM_HERE, task_queue_, [] {});
|
|
}
|
|
|
|
void OnCallsCreated(Call* sender_call, Call* receiver_call) override {
|
|
RTC_DCHECK_RUN_ON(&task_queue_thread_);
|
|
RTC_DCHECK(!call_);
|
|
call_ = sender_call;
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
RTC_DCHECK_RUN_ON(&task_queue_thread_);
|
|
send_config->rtp.extensions.clear();
|
|
send_config->rtp.extensions.push_back(RtpExtension(
|
|
RtpExtension::kTransportSequenceNumberUri, kExtensionId));
|
|
(*receive_configs)[0].rtp.extensions = send_config->rtp.extensions;
|
|
(*receive_configs)[0].rtp.transport_cc = true;
|
|
}
|
|
|
|
void ModifyAudioConfigs(
|
|
AudioSendStream::Config* send_config,
|
|
std::vector<AudioReceiveStream::Config>* receive_configs) override {
|
|
RTC_DCHECK_RUN_ON(&task_queue_thread_);
|
|
send_config->rtp.extensions.clear();
|
|
send_config->rtp.extensions.push_back(RtpExtension(
|
|
RtpExtension::kTransportSequenceNumberUri, kExtensionId));
|
|
(*receive_configs)[0].rtp.extensions.clear();
|
|
(*receive_configs)[0].rtp.extensions = send_config->rtp.extensions;
|
|
(*receive_configs)[0].rtp.transport_cc = true;
|
|
}
|
|
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
RTC_DCHECK_RUN_ON(&module_process_thread_);
|
|
task_queue_->PostTask(ToQueuedTask([this]() {
|
|
RTC_DCHECK_RUN_ON(&task_queue_thread_);
|
|
if (!call_)
|
|
return;
|
|
Call::Stats stats = call_->GetStats();
|
|
if (stats.send_bandwidth_bps > kStartBitrateBps)
|
|
observation_complete_.Set();
|
|
}));
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
void OnStreamsStopped() override {
|
|
RTC_DCHECK_RUN_ON(&task_queue_thread_);
|
|
call_ = nullptr;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
rtc::NetworkRoute new_route;
|
|
new_route.connected = true;
|
|
new_route.local = rtc::RouteEndpoint::CreateWithNetworkId(10);
|
|
new_route.remote = rtc::RouteEndpoint::CreateWithNetworkId(20);
|
|
BitrateConstraints bitrate_config;
|
|
|
|
SendTask(RTC_FROM_HERE, task_queue_,
|
|
[this, &new_route, &bitrate_config]() {
|
|
RTC_DCHECK_RUN_ON(&task_queue_thread_);
|
|
call_->GetTransportControllerSend()->OnNetworkRouteChanged(
|
|
"transport", new_route);
|
|
bitrate_config.start_bitrate_bps = kStartBitrateBps;
|
|
call_->GetTransportControllerSend()->SetSdpBitrateParameters(
|
|
bitrate_config);
|
|
});
|
|
|
|
EXPECT_TRUE(Wait())
|
|
<< "Timed out while waiting for start bitrate to be exceeded.";
|
|
|
|
SendTask(
|
|
RTC_FROM_HERE, task_queue_, [this, &new_route, &bitrate_config]() {
|
|
RTC_DCHECK_RUN_ON(&task_queue_thread_);
|
|
bitrate_config.start_bitrate_bps = -1;
|
|
bitrate_config.max_bitrate_bps = kNewMaxBitrateBps;
|
|
call_->GetTransportControllerSend()->SetSdpBitrateParameters(
|
|
bitrate_config);
|
|
// TODO(holmer): We should set the last sent packet id here and
|
|
// verify that we correctly ignore any packet loss reported prior to
|
|
// that id.
|
|
new_route.local = rtc::RouteEndpoint::CreateWithNetworkId(
|
|
new_route.local.network_id() + 1);
|
|
call_->GetTransportControllerSend()->OnNetworkRouteChanged(
|
|
"transport", new_route);
|
|
EXPECT_GE(call_->GetStats().send_bandwidth_bps, kStartBitrateBps);
|
|
});
|
|
}
|
|
|
|
private:
|
|
webrtc::SequenceChecker module_process_thread_;
|
|
webrtc::SequenceChecker task_queue_thread_;
|
|
TaskQueueBase* const task_queue_;
|
|
RtpHeaderExtensionMap extensions_;
|
|
Call* call_ RTC_GUARDED_BY(task_queue_thread_);
|
|
} test(task_queue());
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
// Test that if specified, relay cap is lifted on transition to direct
|
|
// connection.
|
|
// TODO(https://bugs.webrtc.org/13353): Test disabled due to flakiness.
|
|
TEST_F(VideoSendStreamTest, DISABLED_RelayToDirectRoute) {
|
|
static const int kStartBitrateBps = 300000;
|
|
static const int kRelayBandwidthCapBps = 800000;
|
|
static const int kMinPacketsToSend = 100;
|
|
webrtc::test::ScopedFieldTrials field_trials(
|
|
std::string(field_trial::GetFieldTrialString()) +
|
|
"WebRTC-Bwe-NetworkRouteConstraints/relay_cap:" +
|
|
std::to_string(kRelayBandwidthCapBps) + "bps/");
|
|
|
|
class RelayToDirectRouteTest : public test::EndToEndTest {
|
|
public:
|
|
explicit RelayToDirectRouteTest(TaskQueueBase* task_queue)
|
|
: EndToEndTest(test::CallTest::kDefaultTimeoutMs),
|
|
task_queue_(task_queue),
|
|
call_(nullptr),
|
|
packets_sent_(0),
|
|
relayed_phase_(true) {
|
|
module_process_thread_.Detach();
|
|
task_queue_thread_.Detach();
|
|
}
|
|
|
|
~RelayToDirectRouteTest() {
|
|
// Block until all already posted tasks run to avoid 'use after free'
|
|
// when such task accesses `this`.
|
|
SendTask(RTC_FROM_HERE, task_queue_, [] {});
|
|
}
|
|
|
|
void OnCallsCreated(Call* sender_call, Call* receiver_call) override {
|
|
RTC_DCHECK_RUN_ON(&task_queue_thread_);
|
|
RTC_DCHECK(!call_);
|
|
call_ = sender_call;
|
|
}
|
|
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
RTC_DCHECK_RUN_ON(&module_process_thread_);
|
|
task_queue_->PostTask(ToQueuedTask([this]() {
|
|
RTC_DCHECK_RUN_ON(&task_queue_thread_);
|
|
if (!call_)
|
|
return;
|
|
bool had_time_to_exceed_cap_in_relayed_phase =
|
|
relayed_phase_ && ++packets_sent_ > kMinPacketsToSend;
|
|
bool did_exceed_cap =
|
|
call_->GetStats().send_bandwidth_bps > kRelayBandwidthCapBps;
|
|
if (did_exceed_cap || had_time_to_exceed_cap_in_relayed_phase)
|
|
observation_complete_.Set();
|
|
}));
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
void OnStreamsStopped() override {
|
|
RTC_DCHECK_RUN_ON(&task_queue_thread_);
|
|
call_ = nullptr;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
rtc::NetworkRoute route;
|
|
route.connected = true;
|
|
route.local = rtc::RouteEndpoint::CreateWithNetworkId(10);
|
|
route.remote = rtc::RouteEndpoint::CreateWithNetworkId(20);
|
|
|
|
SendTask(RTC_FROM_HERE, task_queue_, [this, &route]() {
|
|
RTC_DCHECK_RUN_ON(&task_queue_thread_);
|
|
relayed_phase_ = true;
|
|
route.remote = route.remote.CreateWithTurn(true);
|
|
call_->GetTransportControllerSend()->OnNetworkRouteChanged("transport",
|
|
route);
|
|
BitrateConstraints bitrate_config;
|
|
bitrate_config.start_bitrate_bps = kStartBitrateBps;
|
|
|
|
call_->GetTransportControllerSend()->SetSdpBitrateParameters(
|
|
bitrate_config);
|
|
});
|
|
|
|
EXPECT_TRUE(Wait())
|
|
<< "Timeout waiting for sufficient packets sent count.";
|
|
|
|
SendTask(RTC_FROM_HERE, task_queue_, [this, &route]() {
|
|
RTC_DCHECK_RUN_ON(&task_queue_thread_);
|
|
EXPECT_LE(call_->GetStats().send_bandwidth_bps, kRelayBandwidthCapBps);
|
|
|
|
route.remote = route.remote.CreateWithTurn(false);
|
|
call_->GetTransportControllerSend()->OnNetworkRouteChanged("transport",
|
|
route);
|
|
relayed_phase_ = false;
|
|
observation_complete_.Reset();
|
|
});
|
|
|
|
EXPECT_TRUE(Wait())
|
|
<< "Timeout while waiting for bandwidth to outgrow relay cap.";
|
|
}
|
|
|
|
private:
|
|
webrtc::SequenceChecker module_process_thread_;
|
|
webrtc::SequenceChecker task_queue_thread_;
|
|
TaskQueueBase* const task_queue_;
|
|
Call* call_ RTC_GUARDED_BY(task_queue_thread_);
|
|
int packets_sent_ RTC_GUARDED_BY(task_queue_thread_);
|
|
bool relayed_phase_ RTC_GUARDED_BY(task_queue_thread_);
|
|
} test(task_queue());
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, ChangingTransportOverhead) {
|
|
class ChangingTransportOverheadTest : public test::EndToEndTest {
|
|
public:
|
|
explicit ChangingTransportOverheadTest(TaskQueueBase* task_queue)
|
|
: EndToEndTest(test::CallTest::kDefaultTimeoutMs),
|
|
task_queue_(task_queue),
|
|
call_(nullptr),
|
|
packets_sent_(0),
|
|
transport_overhead_(0) {}
|
|
|
|
void OnCallsCreated(Call* sender_call, Call* receiver_call) override {
|
|
call_ = sender_call;
|
|
}
|
|
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
EXPECT_LE(length, kMaxRtpPacketSize);
|
|
MutexLock lock(&lock_);
|
|
if (++packets_sent_ < 100)
|
|
return SEND_PACKET;
|
|
observation_complete_.Set();
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
send_config->rtp.max_packet_size = kMaxRtpPacketSize;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
SendTask(RTC_FROM_HERE, task_queue_, [this]() {
|
|
transport_overhead_ = 100;
|
|
call_->GetTransportControllerSend()->OnTransportOverheadChanged(
|
|
transport_overhead_);
|
|
});
|
|
|
|
EXPECT_TRUE(Wait());
|
|
|
|
{
|
|
MutexLock lock(&lock_);
|
|
packets_sent_ = 0;
|
|
}
|
|
|
|
SendTask(RTC_FROM_HERE, task_queue_, [this]() {
|
|
transport_overhead_ = 500;
|
|
call_->GetTransportControllerSend()->OnTransportOverheadChanged(
|
|
transport_overhead_);
|
|
});
|
|
|
|
EXPECT_TRUE(Wait());
|
|
}
|
|
|
|
private:
|
|
TaskQueueBase* const task_queue_;
|
|
Call* call_;
|
|
Mutex lock_;
|
|
int packets_sent_ RTC_GUARDED_BY(lock_);
|
|
int transport_overhead_;
|
|
const size_t kMaxRtpPacketSize = 1000;
|
|
} test(task_queue());
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
// Test class takes takes as argument a switch selecting if type switch should
|
|
// occur and a function pointer to reset the send stream. This is necessary
|
|
// since you cannot change the content type of a VideoSendStream, you need to
|
|
// recreate it. Stopping and recreating the stream can only be done on the main
|
|
// thread and in the context of VideoSendStreamTest (not BaseTest).
|
|
template <typename T>
|
|
class MaxPaddingSetTest : public test::SendTest {
|
|
public:
|
|
static const uint32_t kMinTransmitBitrateBps = 400000;
|
|
static const uint32_t kActualEncodeBitrateBps = 40000;
|
|
static const uint32_t kMinPacketsToSend = 50;
|
|
|
|
MaxPaddingSetTest(bool test_switch_content_type,
|
|
T* stream_reset_fun,
|
|
TaskQueueBase* task_queue)
|
|
: SendTest(test::CallTest::kDefaultTimeoutMs),
|
|
running_without_padding_(test_switch_content_type),
|
|
stream_resetter_(stream_reset_fun),
|
|
task_queue_(task_queue) {
|
|
RTC_DCHECK(stream_resetter_);
|
|
module_process_thread_.Detach();
|
|
task_queue_thread_.Detach();
|
|
}
|
|
|
|
~MaxPaddingSetTest() {
|
|
// Block until all already posted tasks run to avoid 'use after free'
|
|
// when such task accesses `this`.
|
|
SendTask(RTC_FROM_HERE, task_queue_, [] {});
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
RTC_DCHECK_RUN_ON(&task_queue_thread_);
|
|
RTC_DCHECK_EQ(1, encoder_config->number_of_streams);
|
|
if (running_without_padding_) {
|
|
encoder_config->min_transmit_bitrate_bps = 0;
|
|
encoder_config->content_type =
|
|
VideoEncoderConfig::ContentType::kRealtimeVideo;
|
|
} else {
|
|
encoder_config->min_transmit_bitrate_bps = kMinTransmitBitrateBps;
|
|
encoder_config->content_type = VideoEncoderConfig::ContentType::kScreen;
|
|
}
|
|
send_stream_config_ = send_config->Copy();
|
|
encoder_config_ = encoder_config->Copy();
|
|
}
|
|
|
|
void OnCallsCreated(Call* sender_call, Call* receiver_call) override {
|
|
RTC_DCHECK_RUN_ON(&task_queue_thread_);
|
|
RTC_DCHECK(task_queue_->IsCurrent());
|
|
RTC_DCHECK(!call_);
|
|
RTC_DCHECK(sender_call);
|
|
call_ = sender_call;
|
|
}
|
|
|
|
// Called on the pacer thread.
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
RTC_DCHECK_RUN_ON(&module_process_thread_);
|
|
|
|
// Check the stats on the correct thread and signal the 'complete' flag
|
|
// once we detect that we're done.
|
|
|
|
task_queue_->PostTask(ToQueuedTask([this]() {
|
|
RTC_DCHECK_RUN_ON(&task_queue_thread_);
|
|
// In case we get a callback during teardown.
|
|
// When this happens, OnStreamsStopped() has been called already,
|
|
// `call_` is null and the streams are being torn down.
|
|
if (!call_)
|
|
return;
|
|
|
|
++packets_sent_;
|
|
|
|
Call::Stats stats = call_->GetStats();
|
|
if (running_without_padding_) {
|
|
EXPECT_EQ(0, stats.max_padding_bitrate_bps);
|
|
|
|
// Wait until at least kMinPacketsToSend frames have been encoded, so
|
|
// that we have reliable data.
|
|
if (packets_sent_ < kMinPacketsToSend)
|
|
return;
|
|
|
|
// We've sent kMinPacketsToSend packets with default configuration,
|
|
// switch to enabling screen content and setting min transmit bitrate.
|
|
// Note that we need to recreate the stream if changing content type.
|
|
packets_sent_ = 0;
|
|
|
|
encoder_config_.min_transmit_bitrate_bps = kMinTransmitBitrateBps;
|
|
encoder_config_.content_type = VideoEncoderConfig::ContentType::kScreen;
|
|
|
|
running_without_padding_ = false;
|
|
(*stream_resetter_)(send_stream_config_, encoder_config_);
|
|
} else {
|
|
// Make sure the pacer has been configured with a min transmit bitrate.
|
|
if (stats.max_padding_bitrate_bps > 0) {
|
|
observation_complete_.Set();
|
|
}
|
|
}
|
|
}));
|
|
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
// Called on `task_queue_`
|
|
void OnStreamsStopped() override {
|
|
RTC_DCHECK_RUN_ON(&task_queue_thread_);
|
|
RTC_DCHECK(task_queue_->IsCurrent());
|
|
call_ = nullptr;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
ASSERT_TRUE(Wait()) << "Timed out waiting for a valid padding bitrate.";
|
|
}
|
|
|
|
private:
|
|
webrtc::SequenceChecker task_queue_thread_;
|
|
Call* call_ RTC_GUARDED_BY(task_queue_thread_) = nullptr;
|
|
VideoSendStream::Config send_stream_config_{nullptr};
|
|
VideoEncoderConfig encoder_config_;
|
|
webrtc::SequenceChecker module_process_thread_;
|
|
uint32_t packets_sent_ RTC_GUARDED_BY(task_queue_thread_) = 0;
|
|
bool running_without_padding_ RTC_GUARDED_BY(task_queue_thread_);
|
|
T* const stream_resetter_;
|
|
TaskQueueBase* const task_queue_;
|
|
};
|
|
|
|
TEST_F(VideoSendStreamTest, RespectsMinTransmitBitrate) {
|
|
auto reset_fun = [](const VideoSendStream::Config& send_stream_config,
|
|
const VideoEncoderConfig& encoder_config) {};
|
|
MaxPaddingSetTest<decltype(reset_fun)> test(false, &reset_fun, task_queue());
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, RespectsMinTransmitBitrateAfterContentSwitch) {
|
|
// Function for removing and recreating the send stream with a new config.
|
|
auto reset_fun = [this](const VideoSendStream::Config& send_stream_config,
|
|
const VideoEncoderConfig& encoder_config) {
|
|
RTC_DCHECK(task_queue()->IsCurrent());
|
|
Stop();
|
|
DestroyVideoSendStreams();
|
|
SetVideoSendConfig(send_stream_config);
|
|
SetVideoEncoderConfig(encoder_config);
|
|
CreateVideoSendStreams();
|
|
SetVideoDegradation(DegradationPreference::MAINTAIN_RESOLUTION);
|
|
Start();
|
|
};
|
|
MaxPaddingSetTest<decltype(reset_fun)> test(true, &reset_fun, task_queue());
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
// This test verifies that new frame sizes reconfigures encoders even though not
|
|
// (yet) sending. The purpose of this is to permit encoding as quickly as
|
|
// possible once we start sending. Likely the frames being input are from the
|
|
// same source that will be sent later, which just means that we're ready
|
|
// earlier.
|
|
TEST_F(VideoSendStreamTest,
|
|
EncoderReconfigureOnResolutionChangeWhenNotSending) {
|
|
class EncoderObserver : public test::FakeEncoder {
|
|
public:
|
|
EncoderObserver()
|
|
: FakeEncoder(Clock::GetRealTimeClock()),
|
|
last_initialized_frame_width_(0),
|
|
last_initialized_frame_height_(0) {}
|
|
|
|
void WaitForResolution(int width, int height) {
|
|
{
|
|
MutexLock lock(&mutex_);
|
|
if (last_initialized_frame_width_ == width &&
|
|
last_initialized_frame_height_ == height) {
|
|
return;
|
|
}
|
|
}
|
|
EXPECT_TRUE(
|
|
init_encode_called_.Wait(VideoSendStreamTest::kDefaultTimeoutMs));
|
|
{
|
|
MutexLock lock(&mutex_);
|
|
EXPECT_EQ(width, last_initialized_frame_width_);
|
|
EXPECT_EQ(height, last_initialized_frame_height_);
|
|
}
|
|
}
|
|
|
|
private:
|
|
int32_t InitEncode(const VideoCodec* config,
|
|
const Settings& settings) override {
|
|
MutexLock lock(&mutex_);
|
|
last_initialized_frame_width_ = config->width;
|
|
last_initialized_frame_height_ = config->height;
|
|
init_encode_called_.Set();
|
|
return FakeEncoder::InitEncode(config, settings);
|
|
}
|
|
|
|
int32_t Encode(const VideoFrame& input_image,
|
|
const std::vector<VideoFrameType>* frame_types) override {
|
|
ADD_FAILURE()
|
|
<< "Unexpected Encode call since the send stream is not started";
|
|
return 0;
|
|
}
|
|
|
|
Mutex mutex_;
|
|
rtc::Event init_encode_called_;
|
|
int last_initialized_frame_width_ RTC_GUARDED_BY(&mutex_);
|
|
int last_initialized_frame_height_ RTC_GUARDED_BY(&mutex_);
|
|
};
|
|
|
|
test::NullTransport transport;
|
|
EncoderObserver encoder;
|
|
test::VideoEncoderProxyFactory encoder_factory(&encoder);
|
|
|
|
SendTask(RTC_FROM_HERE, task_queue(), [this, &transport, &encoder_factory]() {
|
|
CreateSenderCall();
|
|
CreateSendConfig(1, 0, 0, &transport);
|
|
GetVideoSendConfig()->encoder_settings.encoder_factory = &encoder_factory;
|
|
CreateVideoStreams();
|
|
CreateFrameGeneratorCapturer(kDefaultFramerate, kDefaultWidth,
|
|
kDefaultHeight);
|
|
frame_generator_capturer_->Start();
|
|
});
|
|
|
|
encoder.WaitForResolution(kDefaultWidth, kDefaultHeight);
|
|
|
|
SendTask(RTC_FROM_HERE, task_queue(), [this]() {
|
|
frame_generator_capturer_->ChangeResolution(kDefaultWidth * 2,
|
|
kDefaultHeight * 2);
|
|
});
|
|
|
|
encoder.WaitForResolution(kDefaultWidth * 2, kDefaultHeight * 2);
|
|
|
|
SendTask(RTC_FROM_HERE, task_queue(), [this]() {
|
|
DestroyStreams();
|
|
DestroyCalls();
|
|
});
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, CanReconfigureToUseStartBitrateAbovePreviousMax) {
|
|
class StartBitrateObserver : public test::FakeEncoder {
|
|
public:
|
|
StartBitrateObserver()
|
|
: FakeEncoder(Clock::GetRealTimeClock()), start_bitrate_kbps_(0) {}
|
|
int32_t InitEncode(const VideoCodec* config,
|
|
const Settings& settings) override {
|
|
MutexLock lock(&mutex_);
|
|
start_bitrate_kbps_ = config->startBitrate;
|
|
start_bitrate_changed_.Set();
|
|
return FakeEncoder::InitEncode(config, settings);
|
|
}
|
|
|
|
void SetRates(const RateControlParameters& parameters) override {
|
|
MutexLock lock(&mutex_);
|
|
start_bitrate_kbps_ = parameters.bitrate.get_sum_kbps();
|
|
start_bitrate_changed_.Set();
|
|
FakeEncoder::SetRates(parameters);
|
|
}
|
|
|
|
int GetStartBitrateKbps() const {
|
|
MutexLock lock(&mutex_);
|
|
return start_bitrate_kbps_;
|
|
}
|
|
|
|
bool WaitForStartBitrate() {
|
|
return start_bitrate_changed_.Wait(
|
|
VideoSendStreamTest::kDefaultTimeoutMs);
|
|
}
|
|
|
|
private:
|
|
mutable Mutex mutex_;
|
|
rtc::Event start_bitrate_changed_;
|
|
int start_bitrate_kbps_ RTC_GUARDED_BY(mutex_);
|
|
};
|
|
|
|
CreateSenderCall();
|
|
|
|
test::NullTransport transport;
|
|
CreateSendConfig(1, 0, 0, &transport);
|
|
|
|
BitrateConstraints bitrate_config;
|
|
bitrate_config.start_bitrate_bps =
|
|
2 * GetVideoEncoderConfig()->max_bitrate_bps;
|
|
sender_call_->GetTransportControllerSend()->SetSdpBitrateParameters(
|
|
bitrate_config);
|
|
|
|
StartBitrateObserver encoder;
|
|
test::VideoEncoderProxyFactory encoder_factory(&encoder);
|
|
GetVideoSendConfig()->encoder_settings.encoder_factory = &encoder_factory;
|
|
|
|
CreateVideoStreams();
|
|
|
|
// Start capturing and encoding frames to force encoder reconfiguration.
|
|
CreateFrameGeneratorCapturer(kDefaultFramerate, kDefaultWidth,
|
|
kDefaultHeight);
|
|
frame_generator_capturer_->Start();
|
|
// TODO(crbug/1255737): Added manual current thread message processing because
|
|
// the test code context is interpreted as the worker thread and we assume
|
|
// progress on it. The test should probably be ported to use simulated time
|
|
// instead (ported to a scenario test perhaps?).
|
|
rtc::Thread::Current()->ProcessMessages(5000);
|
|
|
|
EXPECT_TRUE(encoder.WaitForStartBitrate());
|
|
EXPECT_EQ(GetVideoEncoderConfig()->max_bitrate_bps / 1000,
|
|
encoder.GetStartBitrateKbps());
|
|
|
|
GetVideoEncoderConfig()->max_bitrate_bps =
|
|
2 * bitrate_config.start_bitrate_bps;
|
|
GetVideoSendStream()->ReconfigureVideoEncoder(
|
|
GetVideoEncoderConfig()->Copy());
|
|
// TODO(crbug/1255737): Added manual current thread message processing because
|
|
// the test code context is interpreted as the worker thread and we assume
|
|
// progress on it. The test should probably be ported to use simulated time
|
|
// instead (ported to a scenario test perhaps?).
|
|
rtc::Thread::Current()->ProcessMessages(5000);
|
|
|
|
// New bitrate should be reconfigured above the previous max. As there's no
|
|
// network connection this shouldn't be flaky, as no bitrate should've been
|
|
// reported in between.
|
|
EXPECT_TRUE(encoder.WaitForStartBitrate());
|
|
EXPECT_EQ(bitrate_config.start_bitrate_bps / 1000,
|
|
encoder.GetStartBitrateKbps());
|
|
|
|
DestroyStreams();
|
|
}
|
|
|
|
class StartStopBitrateObserver : public test::FakeEncoder {
|
|
public:
|
|
StartStopBitrateObserver() : FakeEncoder(Clock::GetRealTimeClock()) {}
|
|
int32_t InitEncode(const VideoCodec* config,
|
|
const Settings& settings) override {
|
|
MutexLock lock(&mutex_);
|
|
encoder_init_.Set();
|
|
return FakeEncoder::InitEncode(config, settings);
|
|
}
|
|
|
|
void SetRates(const RateControlParameters& parameters) override {
|
|
MutexLock lock(&mutex_);
|
|
bitrate_kbps_ = parameters.bitrate.get_sum_kbps();
|
|
bitrate_changed_.Set();
|
|
FakeEncoder::SetRates(parameters);
|
|
}
|
|
|
|
bool WaitForEncoderInit() {
|
|
return encoder_init_.Wait(VideoSendStreamTest::kDefaultTimeoutMs);
|
|
}
|
|
|
|
bool WaitBitrateChanged(WaitUntil until) {
|
|
do {
|
|
absl::optional<int> bitrate_kbps;
|
|
{
|
|
MutexLock lock(&mutex_);
|
|
bitrate_kbps = bitrate_kbps_;
|
|
}
|
|
if (!bitrate_kbps)
|
|
continue;
|
|
|
|
if ((until == WaitUntil::kNonZero && *bitrate_kbps > 0) ||
|
|
(until == WaitUntil::kZero && *bitrate_kbps == 0)) {
|
|
return true;
|
|
}
|
|
} while (bitrate_changed_.Wait(VideoSendStreamTest::kDefaultTimeoutMs));
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
Mutex mutex_;
|
|
rtc::Event encoder_init_;
|
|
rtc::Event bitrate_changed_;
|
|
absl::optional<int> bitrate_kbps_ RTC_GUARDED_BY(mutex_);
|
|
};
|
|
|
|
TEST_F(VideoSendStreamTest, EncoderIsProperlyInitializedAndDestroyed) {
|
|
class EncoderStateObserver : public test::SendTest, public VideoEncoder {
|
|
public:
|
|
explicit EncoderStateObserver(TaskQueueBase* task_queue)
|
|
: SendTest(kDefaultTimeoutMs),
|
|
task_queue_(task_queue),
|
|
stream_(nullptr),
|
|
initialized_(false),
|
|
callback_registered_(false),
|
|
num_releases_(0),
|
|
released_(false),
|
|
encoder_factory_(this) {}
|
|
|
|
bool IsReleased() RTC_LOCKS_EXCLUDED(mutex_) {
|
|
MutexLock lock(&mutex_);
|
|
return released_;
|
|
}
|
|
|
|
bool IsReadyForEncode() RTC_LOCKS_EXCLUDED(mutex_) {
|
|
MutexLock lock(&mutex_);
|
|
return IsReadyForEncodeLocked();
|
|
}
|
|
|
|
size_t num_releases() RTC_LOCKS_EXCLUDED(mutex_) {
|
|
MutexLock lock(&mutex_);
|
|
return num_releases_;
|
|
}
|
|
|
|
private:
|
|
bool IsReadyForEncodeLocked() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
|
|
return initialized_ && callback_registered_;
|
|
}
|
|
|
|
void SetFecControllerOverride(
|
|
FecControllerOverride* fec_controller_override) override {
|
|
// Ignored.
|
|
}
|
|
|
|
int32_t InitEncode(const VideoCodec* codecSettings,
|
|
const Settings& settings) override
|
|
RTC_LOCKS_EXCLUDED(mutex_) {
|
|
MutexLock lock(&mutex_);
|
|
EXPECT_FALSE(initialized_);
|
|
initialized_ = true;
|
|
released_ = false;
|
|
return 0;
|
|
}
|
|
|
|
int32_t Encode(const VideoFrame& inputImage,
|
|
const std::vector<VideoFrameType>* frame_types) override {
|
|
EXPECT_TRUE(IsReadyForEncode());
|
|
|
|
observation_complete_.Set();
|
|
return 0;
|
|
}
|
|
|
|
int32_t RegisterEncodeCompleteCallback(
|
|
EncodedImageCallback* callback) override RTC_LOCKS_EXCLUDED(mutex_) {
|
|
MutexLock lock(&mutex_);
|
|
EXPECT_TRUE(initialized_);
|
|
callback_registered_ = true;
|
|
return 0;
|
|
}
|
|
|
|
int32_t Release() override RTC_LOCKS_EXCLUDED(mutex_) {
|
|
MutexLock lock(&mutex_);
|
|
EXPECT_TRUE(IsReadyForEncodeLocked());
|
|
EXPECT_FALSE(released_);
|
|
initialized_ = false;
|
|
callback_registered_ = false;
|
|
released_ = true;
|
|
++num_releases_;
|
|
return 0;
|
|
}
|
|
|
|
void SetRates(const RateControlParameters& parameters) override {
|
|
EXPECT_TRUE(IsReadyForEncode());
|
|
}
|
|
|
|
void OnVideoStreamsCreated(
|
|
VideoSendStream* send_stream,
|
|
const std::vector<VideoReceiveStream*>& receive_streams) override {
|
|
stream_ = send_stream;
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
send_config->encoder_settings.encoder_factory = &encoder_factory_;
|
|
encoder_config_ = encoder_config->Copy();
|
|
}
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait()) << "Timed out while waiting for Encode.";
|
|
|
|
SendTask(RTC_FROM_HERE, task_queue_, [this]() {
|
|
EXPECT_EQ(0u, num_releases());
|
|
stream_->ReconfigureVideoEncoder(std::move(encoder_config_));
|
|
EXPECT_EQ(0u, num_releases());
|
|
stream_->Stop();
|
|
// Encoder should not be released before destroying the VideoSendStream.
|
|
EXPECT_FALSE(IsReleased());
|
|
EXPECT_TRUE(IsReadyForEncode());
|
|
stream_->Start();
|
|
});
|
|
|
|
// Sanity check, make sure we still encode frames with this encoder.
|
|
EXPECT_TRUE(Wait()) << "Timed out while waiting for Encode.";
|
|
}
|
|
|
|
TaskQueueBase* const task_queue_;
|
|
Mutex mutex_;
|
|
VideoSendStream* stream_;
|
|
bool initialized_ RTC_GUARDED_BY(mutex_);
|
|
bool callback_registered_ RTC_GUARDED_BY(mutex_);
|
|
size_t num_releases_ RTC_GUARDED_BY(mutex_);
|
|
bool released_ RTC_GUARDED_BY(mutex_);
|
|
test::VideoEncoderProxyFactory encoder_factory_;
|
|
VideoEncoderConfig encoder_config_;
|
|
} test_encoder(task_queue());
|
|
|
|
RunBaseTest(&test_encoder);
|
|
|
|
EXPECT_TRUE(test_encoder.IsReleased());
|
|
EXPECT_EQ(1u, test_encoder.num_releases());
|
|
}
|
|
|
|
static const size_t kVideoCodecConfigObserverNumberOfTemporalLayers = 3;
|
|
template <typename T>
|
|
class VideoCodecConfigObserver : public test::SendTest,
|
|
public test::FakeEncoder {
|
|
public:
|
|
VideoCodecConfigObserver(VideoCodecType video_codec_type,
|
|
TaskQueueBase* task_queue)
|
|
: SendTest(VideoSendStreamTest::kDefaultTimeoutMs),
|
|
FakeEncoder(Clock::GetRealTimeClock()),
|
|
video_codec_type_(video_codec_type),
|
|
stream_(nullptr),
|
|
encoder_factory_(this),
|
|
task_queue_(task_queue) {
|
|
InitCodecSpecifics();
|
|
}
|
|
|
|
private:
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
send_config->encoder_settings.encoder_factory = &encoder_factory_;
|
|
send_config->rtp.payload_name = CodecTypeToPayloadString(video_codec_type_);
|
|
|
|
encoder_config->codec_type = video_codec_type_;
|
|
encoder_config->encoder_specific_settings = GetEncoderSpecificSettings();
|
|
EXPECT_EQ(1u, encoder_config->simulcast_layers.size());
|
|
encoder_config->simulcast_layers[0].num_temporal_layers =
|
|
kVideoCodecConfigObserverNumberOfTemporalLayers;
|
|
encoder_config_ = encoder_config->Copy();
|
|
}
|
|
|
|
void OnVideoStreamsCreated(
|
|
VideoSendStream* send_stream,
|
|
const std::vector<VideoReceiveStream*>& receive_streams) override {
|
|
stream_ = send_stream;
|
|
}
|
|
|
|
int32_t InitEncode(const VideoCodec* config,
|
|
const Settings& settings) override {
|
|
EXPECT_EQ(video_codec_type_, config->codecType);
|
|
VerifyCodecSpecifics(*config);
|
|
int ret = FakeEncoder::InitEncode(config, settings);
|
|
init_encode_event_.Set();
|
|
return ret;
|
|
}
|
|
|
|
void InitCodecSpecifics();
|
|
void VerifyCodecSpecifics(const VideoCodec& config) const;
|
|
rtc::scoped_refptr<VideoEncoderConfig::EncoderSpecificSettings>
|
|
GetEncoderSpecificSettings() const;
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(
|
|
init_encode_event_.Wait(VideoSendStreamTest::kDefaultTimeoutMs));
|
|
ASSERT_EQ(1, FakeEncoder::GetNumInitializations())
|
|
<< "VideoEncoder not initialized.";
|
|
|
|
// Change encoder settings to actually trigger reconfiguration.
|
|
encoder_settings_.frameDroppingOn = !encoder_settings_.frameDroppingOn;
|
|
encoder_config_.encoder_specific_settings = GetEncoderSpecificSettings();
|
|
SendTask(RTC_FROM_HERE, task_queue_, [&]() {
|
|
stream_->ReconfigureVideoEncoder(std::move(encoder_config_));
|
|
});
|
|
ASSERT_TRUE(
|
|
init_encode_event_.Wait(VideoSendStreamTest::kDefaultTimeoutMs));
|
|
EXPECT_EQ(2, FakeEncoder::GetNumInitializations())
|
|
<< "ReconfigureVideoEncoder did not reinitialize the encoder with "
|
|
"new encoder settings.";
|
|
}
|
|
|
|
int32_t Encode(const VideoFrame& input_image,
|
|
const std::vector<VideoFrameType>* frame_types) override {
|
|
// Silently skip the encode, FakeEncoder::Encode doesn't produce VP8.
|
|
return 0;
|
|
}
|
|
|
|
T encoder_settings_;
|
|
const VideoCodecType video_codec_type_;
|
|
rtc::Event init_encode_event_;
|
|
VideoSendStream* stream_;
|
|
test::VideoEncoderProxyFactory encoder_factory_;
|
|
VideoEncoderConfig encoder_config_;
|
|
TaskQueueBase* task_queue_;
|
|
};
|
|
|
|
template <>
|
|
void VideoCodecConfigObserver<VideoCodecH264>::InitCodecSpecifics() {
|
|
encoder_settings_ = VideoEncoder::GetDefaultH264Settings();
|
|
}
|
|
|
|
template <>
|
|
void VideoCodecConfigObserver<VideoCodecH264>::VerifyCodecSpecifics(
|
|
const VideoCodec& config) const {
|
|
// Check that the number of temporal layers has propagated properly to
|
|
// VideoCodec.
|
|
EXPECT_EQ(kVideoCodecConfigObserverNumberOfTemporalLayers,
|
|
config.H264().numberOfTemporalLayers);
|
|
|
|
for (unsigned char i = 0; i < config.numberOfSimulcastStreams; ++i) {
|
|
EXPECT_EQ(kVideoCodecConfigObserverNumberOfTemporalLayers,
|
|
config.simulcastStream[i].numberOfTemporalLayers);
|
|
}
|
|
|
|
// Set expected temporal layers as they should have been set when
|
|
// reconfiguring the encoder and not match the set config.
|
|
VideoCodecH264 encoder_settings = encoder_settings_;
|
|
encoder_settings.numberOfTemporalLayers =
|
|
kVideoCodecConfigObserverNumberOfTemporalLayers;
|
|
EXPECT_EQ(
|
|
0, memcmp(&config.H264(), &encoder_settings, sizeof(encoder_settings_)));
|
|
}
|
|
|
|
template <>
|
|
rtc::scoped_refptr<VideoEncoderConfig::EncoderSpecificSettings>
|
|
VideoCodecConfigObserver<VideoCodecH264>::GetEncoderSpecificSettings() const {
|
|
return rtc::make_ref_counted<VideoEncoderConfig::H264EncoderSpecificSettings>(
|
|
encoder_settings_);
|
|
}
|
|
|
|
template <>
|
|
void VideoCodecConfigObserver<VideoCodecVP8>::InitCodecSpecifics() {
|
|
encoder_settings_ = VideoEncoder::GetDefaultVp8Settings();
|
|
}
|
|
|
|
template <>
|
|
void VideoCodecConfigObserver<VideoCodecVP8>::VerifyCodecSpecifics(
|
|
const VideoCodec& config) const {
|
|
// Check that the number of temporal layers has propagated properly to
|
|
// VideoCodec.
|
|
EXPECT_EQ(kVideoCodecConfigObserverNumberOfTemporalLayers,
|
|
config.VP8().numberOfTemporalLayers);
|
|
|
|
for (unsigned char i = 0; i < config.numberOfSimulcastStreams; ++i) {
|
|
EXPECT_EQ(kVideoCodecConfigObserverNumberOfTemporalLayers,
|
|
config.simulcastStream[i].numberOfTemporalLayers);
|
|
}
|
|
|
|
// Set expected temporal layers as they should have been set when
|
|
// reconfiguring the encoder and not match the set config.
|
|
VideoCodecVP8 encoder_settings = encoder_settings_;
|
|
encoder_settings.numberOfTemporalLayers =
|
|
kVideoCodecConfigObserverNumberOfTemporalLayers;
|
|
EXPECT_EQ(
|
|
0, memcmp(&config.VP8(), &encoder_settings, sizeof(encoder_settings_)));
|
|
}
|
|
|
|
template <>
|
|
rtc::scoped_refptr<VideoEncoderConfig::EncoderSpecificSettings>
|
|
VideoCodecConfigObserver<VideoCodecVP8>::GetEncoderSpecificSettings() const {
|
|
return rtc::make_ref_counted<VideoEncoderConfig::Vp8EncoderSpecificSettings>(
|
|
encoder_settings_);
|
|
}
|
|
|
|
template <>
|
|
void VideoCodecConfigObserver<VideoCodecVP9>::InitCodecSpecifics() {
|
|
encoder_settings_ = VideoEncoder::GetDefaultVp9Settings();
|
|
}
|
|
|
|
template <>
|
|
void VideoCodecConfigObserver<VideoCodecVP9>::VerifyCodecSpecifics(
|
|
const VideoCodec& config) const {
|
|
// Check that the number of temporal layers has propagated properly to
|
|
// VideoCodec.
|
|
EXPECT_EQ(kVideoCodecConfigObserverNumberOfTemporalLayers,
|
|
config.VP9().numberOfTemporalLayers);
|
|
|
|
for (unsigned char i = 0; i < config.numberOfSimulcastStreams; ++i) {
|
|
EXPECT_EQ(kVideoCodecConfigObserverNumberOfTemporalLayers,
|
|
config.simulcastStream[i].numberOfTemporalLayers);
|
|
}
|
|
|
|
// Set expected temporal layers as they should have been set when
|
|
// reconfiguring the encoder and not match the set config.
|
|
VideoCodecVP9 encoder_settings = encoder_settings_;
|
|
encoder_settings.numberOfTemporalLayers =
|
|
kVideoCodecConfigObserverNumberOfTemporalLayers;
|
|
EXPECT_EQ(
|
|
0, memcmp(&(config.VP9()), &encoder_settings, sizeof(encoder_settings_)));
|
|
}
|
|
|
|
template <>
|
|
rtc::scoped_refptr<VideoEncoderConfig::EncoderSpecificSettings>
|
|
VideoCodecConfigObserver<VideoCodecVP9>::GetEncoderSpecificSettings() const {
|
|
return rtc::make_ref_counted<VideoEncoderConfig::Vp9EncoderSpecificSettings>(
|
|
encoder_settings_);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, EncoderSetupPropagatesVp8Config) {
|
|
VideoCodecConfigObserver<VideoCodecVP8> test(kVideoCodecVP8, task_queue());
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, EncoderSetupPropagatesVp9Config) {
|
|
VideoCodecConfigObserver<VideoCodecVP9> test(kVideoCodecVP9, task_queue());
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
// Fails on MSAN: https://bugs.chromium.org/p/webrtc/issues/detail?id=11376.
|
|
#if defined(MEMORY_SANITIZER)
|
|
#define MAYBE_EncoderSetupPropagatesH264Config \
|
|
DISABLED_EncoderSetupPropagatesH264Config
|
|
#else
|
|
#define MAYBE_EncoderSetupPropagatesH264Config EncoderSetupPropagatesH264Config
|
|
#endif
|
|
TEST_F(VideoSendStreamTest, MAYBE_EncoderSetupPropagatesH264Config) {
|
|
VideoCodecConfigObserver<VideoCodecH264> test(kVideoCodecH264, task_queue());
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, RtcpSenderReportContainsMediaBytesSent) {
|
|
class RtcpSenderReportTest : public test::SendTest {
|
|
public:
|
|
RtcpSenderReportTest()
|
|
: SendTest(kDefaultTimeoutMs),
|
|
rtp_packets_sent_(0),
|
|
media_bytes_sent_(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));
|
|
++rtp_packets_sent_;
|
|
media_bytes_sent_ += rtp_packet.payload_size();
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
Action OnSendRtcp(const uint8_t* packet, size_t length) override {
|
|
MutexLock lock(&mutex_);
|
|
test::RtcpPacketParser parser;
|
|
EXPECT_TRUE(parser.Parse(packet, length));
|
|
|
|
if (parser.sender_report()->num_packets() > 0) {
|
|
// Only compare sent media bytes if SenderPacketCount matches the
|
|
// number of sent rtp packets (a new rtp packet could be sent before
|
|
// the rtcp packet).
|
|
if (parser.sender_report()->sender_octet_count() > 0 &&
|
|
parser.sender_report()->sender_packet_count() ==
|
|
rtp_packets_sent_) {
|
|
EXPECT_EQ(media_bytes_sent_,
|
|
parser.sender_report()->sender_octet_count());
|
|
observation_complete_.Set();
|
|
}
|
|
}
|
|
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait()) << "Timed out while waiting for RTCP sender report.";
|
|
}
|
|
|
|
Mutex mutex_;
|
|
size_t rtp_packets_sent_ RTC_GUARDED_BY(&mutex_);
|
|
size_t media_bytes_sent_ RTC_GUARDED_BY(&mutex_);
|
|
} test;
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, TranslatesTwoLayerScreencastToTargetBitrate) {
|
|
static const int kScreencastMaxTargetBitrateDeltaKbps = 1;
|
|
|
|
class VideoStreamFactory
|
|
: public VideoEncoderConfig::VideoStreamFactoryInterface {
|
|
public:
|
|
VideoStreamFactory() {}
|
|
|
|
private:
|
|
std::vector<VideoStream> CreateEncoderStreams(
|
|
int width,
|
|
int height,
|
|
const VideoEncoderConfig& encoder_config) override {
|
|
std::vector<VideoStream> streams =
|
|
test::CreateVideoStreams(width, height, encoder_config);
|
|
RTC_CHECK_GT(streams[0].max_bitrate_bps,
|
|
kScreencastMaxTargetBitrateDeltaKbps);
|
|
streams[0].target_bitrate_bps =
|
|
streams[0].max_bitrate_bps -
|
|
kScreencastMaxTargetBitrateDeltaKbps * 1000;
|
|
return streams;
|
|
}
|
|
};
|
|
|
|
class ScreencastTargetBitrateTest : public test::SendTest,
|
|
public test::FakeEncoder {
|
|
public:
|
|
ScreencastTargetBitrateTest()
|
|
: SendTest(kDefaultTimeoutMs),
|
|
test::FakeEncoder(Clock::GetRealTimeClock()),
|
|
encoder_factory_(this) {}
|
|
|
|
private:
|
|
int32_t InitEncode(const VideoCodec* config,
|
|
const Settings& settings) override {
|
|
EXPECT_EQ(config->numberOfSimulcastStreams, 1);
|
|
EXPECT_EQ(static_cast<unsigned int>(kScreencastMaxTargetBitrateDeltaKbps),
|
|
config->simulcastStream[0].maxBitrate -
|
|
config->simulcastStream[0].targetBitrate);
|
|
observation_complete_.Set();
|
|
return test::FakeEncoder::InitEncode(config, settings);
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
send_config->encoder_settings.encoder_factory = &encoder_factory_;
|
|
EXPECT_EQ(1u, encoder_config->number_of_streams);
|
|
encoder_config->video_stream_factory =
|
|
rtc::make_ref_counted<VideoStreamFactory>();
|
|
EXPECT_EQ(1u, encoder_config->simulcast_layers.size());
|
|
encoder_config->simulcast_layers[0].num_temporal_layers = 2;
|
|
encoder_config->content_type = VideoEncoderConfig::ContentType::kScreen;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait())
|
|
<< "Timed out while waiting for the encoder to be initialized.";
|
|
}
|
|
test::VideoEncoderProxyFactory encoder_factory_;
|
|
} test;
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, ReconfigureBitratesSetsEncoderBitratesCorrectly) {
|
|
// These are chosen to be "kind of odd" to not be accidentally checked against
|
|
// default values.
|
|
static const int kMinBitrateKbps = 137;
|
|
static const int kStartBitrateKbps = 345;
|
|
static const int kLowerMaxBitrateKbps = 312;
|
|
static const int kMaxBitrateKbps = 413;
|
|
static const int kIncreasedStartBitrateKbps = 451;
|
|
static const int kIncreasedMaxBitrateKbps = 597;
|
|
// TODO(bugs.webrtc.org/12058): If these fields trial are on, we get lower
|
|
// bitrates than expected by this test, due to encoder pushback and subtracted
|
|
// overhead.
|
|
webrtc::test::ScopedFieldTrials field_trials(
|
|
std::string(field_trial::GetFieldTrialString()) +
|
|
"WebRTC-VideoRateControl/bitrate_adjuster:false/"
|
|
"WebRTC-SendSideBwe-WithOverhead/Disabled/");
|
|
|
|
class EncoderBitrateThresholdObserver : public test::SendTest,
|
|
public VideoBitrateAllocatorFactory,
|
|
public test::FakeEncoder {
|
|
public:
|
|
explicit EncoderBitrateThresholdObserver(TaskQueueBase* task_queue)
|
|
: SendTest(kDefaultTimeoutMs),
|
|
FakeEncoder(Clock::GetRealTimeClock()),
|
|
task_queue_(task_queue),
|
|
target_bitrate_(0),
|
|
num_rate_allocator_creations_(0),
|
|
num_encoder_initializations_(0),
|
|
call_(nullptr),
|
|
send_stream_(nullptr),
|
|
encoder_factory_(this),
|
|
bitrate_allocator_factory_(
|
|
CreateBuiltinVideoBitrateAllocatorFactory()) {}
|
|
|
|
private:
|
|
std::unique_ptr<VideoBitrateAllocator> CreateVideoBitrateAllocator(
|
|
const VideoCodec& codec) override {
|
|
EXPECT_GE(codec.startBitrate, codec.minBitrate);
|
|
EXPECT_LE(codec.startBitrate, codec.maxBitrate);
|
|
if (num_rate_allocator_creations_ == 0) {
|
|
EXPECT_EQ(static_cast<unsigned int>(kMinBitrateKbps), codec.minBitrate);
|
|
EXPECT_EQ(static_cast<unsigned int>(kStartBitrateKbps),
|
|
codec.startBitrate);
|
|
EXPECT_EQ(static_cast<unsigned int>(kMaxBitrateKbps), codec.maxBitrate);
|
|
} else if (num_rate_allocator_creations_ == 1) {
|
|
EXPECT_EQ(static_cast<unsigned int>(kLowerMaxBitrateKbps),
|
|
codec.maxBitrate);
|
|
// The start bitrate should be kept (-1) and capped to the max bitrate.
|
|
// Since this is not an end-to-end call no receiver should have been
|
|
// returning a REMB that could lower this estimate.
|
|
EXPECT_EQ(codec.startBitrate, codec.maxBitrate);
|
|
} else if (num_rate_allocator_creations_ == 2) {
|
|
EXPECT_EQ(static_cast<unsigned int>(kIncreasedMaxBitrateKbps),
|
|
codec.maxBitrate);
|
|
// The start bitrate will be whatever the rate BitRateController has
|
|
// currently configured but in the span of the set max and min bitrate.
|
|
}
|
|
++num_rate_allocator_creations_;
|
|
create_rate_allocator_event_.Set();
|
|
|
|
return bitrate_allocator_factory_->CreateVideoBitrateAllocator(codec);
|
|
}
|
|
|
|
int32_t InitEncode(const VideoCodec* codecSettings,
|
|
const Settings& settings) override {
|
|
EXPECT_EQ(0, num_encoder_initializations_);
|
|
EXPECT_EQ(static_cast<unsigned int>(kMinBitrateKbps),
|
|
codecSettings->minBitrate);
|
|
EXPECT_EQ(static_cast<unsigned int>(kStartBitrateKbps),
|
|
codecSettings->startBitrate);
|
|
EXPECT_EQ(static_cast<unsigned int>(kMaxBitrateKbps),
|
|
codecSettings->maxBitrate);
|
|
|
|
++num_encoder_initializations_;
|
|
|
|
observation_complete_.Set();
|
|
init_encode_event_.Set();
|
|
|
|
return FakeEncoder::InitEncode(codecSettings, settings);
|
|
}
|
|
|
|
void SetRates(const RateControlParameters& parameters) override {
|
|
{
|
|
MutexLock lock(&mutex_);
|
|
if (target_bitrate_ == parameters.bitrate.get_sum_kbps()) {
|
|
FakeEncoder::SetRates(parameters);
|
|
return;
|
|
}
|
|
target_bitrate_ = parameters.bitrate.get_sum_kbps();
|
|
}
|
|
bitrate_changed_event_.Set();
|
|
FakeEncoder::SetRates(parameters);
|
|
}
|
|
|
|
void WaitForSetRates(uint32_t expected_bitrate) {
|
|
// Wait for the expected rate to be set. In some cases there can be
|
|
// more than one update pending, in which case we keep waiting
|
|
// until the correct value has been observed.
|
|
const int64_t start_time = rtc::TimeMillis();
|
|
do {
|
|
MutexLock lock(&mutex_);
|
|
if (target_bitrate_ == expected_bitrate) {
|
|
return;
|
|
}
|
|
} while (bitrate_changed_event_.Wait(
|
|
std::max(int64_t{1}, VideoSendStreamTest::kDefaultTimeoutMs -
|
|
(rtc::TimeMillis() - start_time))));
|
|
MutexLock lock(&mutex_);
|
|
EXPECT_EQ(target_bitrate_, expected_bitrate)
|
|
<< "Timed out while waiting encoder rate to be set.";
|
|
}
|
|
|
|
void ModifySenderBitrateConfig(
|
|
BitrateConstraints* bitrate_config) override {
|
|
bitrate_config->min_bitrate_bps = kMinBitrateKbps * 1000;
|
|
bitrate_config->start_bitrate_bps = kStartBitrateKbps * 1000;
|
|
bitrate_config->max_bitrate_bps = kMaxBitrateKbps * 1000;
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
send_config->encoder_settings.encoder_factory = &encoder_factory_;
|
|
send_config->encoder_settings.bitrate_allocator_factory = this;
|
|
// Set bitrates lower/higher than min/max to make sure they are properly
|
|
// capped.
|
|
encoder_config->max_bitrate_bps = kMaxBitrateKbps * 1000;
|
|
EXPECT_EQ(1u, encoder_config->simulcast_layers.size());
|
|
encoder_config->simulcast_layers[0].min_bitrate_bps =
|
|
kMinBitrateKbps * 1000;
|
|
encoder_config_ = encoder_config->Copy();
|
|
}
|
|
|
|
void OnCallsCreated(Call* sender_call, Call* receiver_call) override {
|
|
call_ = sender_call;
|
|
}
|
|
|
|
void OnVideoStreamsCreated(
|
|
VideoSendStream* send_stream,
|
|
const std::vector<VideoReceiveStream*>& receive_streams) override {
|
|
send_stream_ = send_stream;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
ASSERT_TRUE(create_rate_allocator_event_.Wait(
|
|
VideoSendStreamTest::kDefaultTimeoutMs))
|
|
<< "Timed out while waiting for rate allocator to be created.";
|
|
ASSERT_TRUE(
|
|
init_encode_event_.Wait(VideoSendStreamTest::kDefaultTimeoutMs))
|
|
<< "Timed out while waiting for encoder to be configured.";
|
|
WaitForSetRates(kStartBitrateKbps);
|
|
BitrateConstraints bitrate_config;
|
|
bitrate_config.start_bitrate_bps = kIncreasedStartBitrateKbps * 1000;
|
|
bitrate_config.max_bitrate_bps = kIncreasedMaxBitrateKbps * 1000;
|
|
SendTask(RTC_FROM_HERE, task_queue_, [this, &bitrate_config]() {
|
|
call_->GetTransportControllerSend()->SetSdpBitrateParameters(
|
|
bitrate_config);
|
|
});
|
|
// Encoder rate is capped by EncoderConfig max_bitrate_bps.
|
|
WaitForSetRates(kMaxBitrateKbps);
|
|
encoder_config_.max_bitrate_bps = kLowerMaxBitrateKbps * 1000;
|
|
SendTask(RTC_FROM_HERE, task_queue_, [&]() {
|
|
send_stream_->ReconfigureVideoEncoder(encoder_config_.Copy());
|
|
});
|
|
ASSERT_TRUE(create_rate_allocator_event_.Wait(
|
|
VideoSendStreamTest::kDefaultTimeoutMs));
|
|
EXPECT_EQ(2, num_rate_allocator_creations_)
|
|
<< "Rate allocator should have been recreated.";
|
|
|
|
WaitForSetRates(kLowerMaxBitrateKbps);
|
|
EXPECT_EQ(1, num_encoder_initializations_);
|
|
|
|
encoder_config_.max_bitrate_bps = kIncreasedMaxBitrateKbps * 1000;
|
|
SendTask(RTC_FROM_HERE, task_queue_, [&]() {
|
|
send_stream_->ReconfigureVideoEncoder(encoder_config_.Copy());
|
|
});
|
|
ASSERT_TRUE(create_rate_allocator_event_.Wait(
|
|
VideoSendStreamTest::kDefaultTimeoutMs));
|
|
EXPECT_EQ(3, num_rate_allocator_creations_)
|
|
<< "Rate allocator should have been recreated.";
|
|
|
|
// Expected target bitrate is the start bitrate set in the call to
|
|
// call_->GetTransportControllerSend()->SetSdpBitrateParameters.
|
|
WaitForSetRates(kIncreasedStartBitrateKbps);
|
|
EXPECT_EQ(1, num_encoder_initializations_);
|
|
}
|
|
|
|
TaskQueueBase* const task_queue_;
|
|
rtc::Event create_rate_allocator_event_;
|
|
rtc::Event init_encode_event_;
|
|
rtc::Event bitrate_changed_event_;
|
|
Mutex mutex_;
|
|
uint32_t target_bitrate_ RTC_GUARDED_BY(&mutex_);
|
|
|
|
int num_rate_allocator_creations_;
|
|
int num_encoder_initializations_;
|
|
webrtc::Call* call_;
|
|
webrtc::VideoSendStream* send_stream_;
|
|
test::VideoEncoderProxyFactory encoder_factory_;
|
|
std::unique_ptr<VideoBitrateAllocatorFactory> bitrate_allocator_factory_;
|
|
webrtc::VideoEncoderConfig encoder_config_;
|
|
} test(task_queue());
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, ReportsSentResolution) {
|
|
static const size_t kNumStreams = 3;
|
|
// Unusual resolutions to make sure that they are the ones being reported.
|
|
static const struct {
|
|
int width;
|
|
int height;
|
|
} kEncodedResolution[kNumStreams] = {{241, 181}, {300, 121}, {121, 221}};
|
|
class ScreencastTargetBitrateTest : public test::SendTest,
|
|
public test::FakeEncoder {
|
|
public:
|
|
explicit ScreencastTargetBitrateTest(TaskQueueBase* task_queue)
|
|
: SendTest(kDefaultTimeoutMs),
|
|
test::FakeEncoder(Clock::GetRealTimeClock()),
|
|
send_stream_(nullptr),
|
|
encoder_factory_(this),
|
|
task_queue_(task_queue) {}
|
|
|
|
private:
|
|
int32_t Encode(const VideoFrame& input_image,
|
|
const std::vector<VideoFrameType>* frame_types) override {
|
|
CodecSpecificInfo specifics;
|
|
specifics.codecType = kVideoCodecGeneric;
|
|
|
|
EncodedImage encoded;
|
|
auto buffer = EncodedImageBuffer::Create(16);
|
|
memset(buffer->data(), 0, 16);
|
|
encoded.SetEncodedData(buffer);
|
|
encoded.SetTimestamp(input_image.timestamp());
|
|
encoded.capture_time_ms_ = input_image.render_time_ms();
|
|
|
|
for (size_t i = 0; i < kNumStreams; ++i) {
|
|
encoded._frameType = (*frame_types)[i];
|
|
encoded._encodedWidth = kEncodedResolution[i].width;
|
|
encoded._encodedHeight = kEncodedResolution[i].height;
|
|
encoded.SetSpatialIndex(i);
|
|
EncodedImageCallback* callback;
|
|
{
|
|
MutexLock lock(&mutex_);
|
|
callback = callback_;
|
|
}
|
|
RTC_DCHECK(callback);
|
|
if (callback->OnEncodedImage(encoded, &specifics).error !=
|
|
EncodedImageCallback::Result::OK) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
observation_complete_.Set();
|
|
return 0;
|
|
}
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
send_config->encoder_settings.encoder_factory = &encoder_factory_;
|
|
EXPECT_EQ(kNumStreams, encoder_config->number_of_streams);
|
|
}
|
|
|
|
size_t GetNumVideoStreams() const override { return kNumStreams; }
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait())
|
|
<< "Timed out while waiting for the encoder to send one frame.";
|
|
VideoSendStream::Stats stats;
|
|
SendTask(RTC_FROM_HERE, task_queue_,
|
|
[&]() { stats = send_stream_->GetStats(); });
|
|
|
|
for (size_t i = 0; i < kNumStreams; ++i) {
|
|
ASSERT_TRUE(stats.substreams.find(kVideoSendSsrcs[i]) !=
|
|
stats.substreams.end())
|
|
<< "No stats for SSRC: " << kVideoSendSsrcs[i]
|
|
<< ", stats should exist as soon as frames have been encoded.";
|
|
VideoSendStream::StreamStats ssrc_stats =
|
|
stats.substreams[kVideoSendSsrcs[i]];
|
|
EXPECT_EQ(kEncodedResolution[i].width, ssrc_stats.width);
|
|
EXPECT_EQ(kEncodedResolution[i].height, ssrc_stats.height);
|
|
}
|
|
}
|
|
|
|
void OnVideoStreamsCreated(
|
|
VideoSendStream* send_stream,
|
|
const std::vector<VideoReceiveStream*>& receive_streams) override {
|
|
send_stream_ = send_stream;
|
|
}
|
|
|
|
VideoSendStream* send_stream_;
|
|
test::VideoEncoderProxyFactory encoder_factory_;
|
|
TaskQueueBase* const task_queue_;
|
|
} test(task_queue());
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
#if defined(RTC_ENABLE_VP9)
|
|
class Vp9HeaderObserver : public test::SendTest {
|
|
public:
|
|
Vp9HeaderObserver()
|
|
: SendTest(VideoSendStreamTest::kLongTimeoutMs),
|
|
encoder_factory_([]() { return VP9Encoder::Create(); }),
|
|
vp9_settings_(VideoEncoder::GetDefaultVp9Settings()),
|
|
packets_sent_(0),
|
|
frames_sent_(0),
|
|
expected_width_(0),
|
|
expected_height_(0) {}
|
|
|
|
virtual void ModifyVideoConfigsHook(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) {}
|
|
|
|
virtual void InspectHeader(const RTPVideoHeaderVP9& vp9) = 0;
|
|
|
|
private:
|
|
const int kVp9PayloadType = test::CallTest::kVideoSendPayloadType;
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
send_config->encoder_settings.encoder_factory = &encoder_factory_;
|
|
send_config->rtp.payload_name = "VP9";
|
|
send_config->rtp.payload_type = kVp9PayloadType;
|
|
ModifyVideoConfigsHook(send_config, receive_configs, encoder_config);
|
|
encoder_config->encoder_specific_settings =
|
|
rtc::make_ref_counted<VideoEncoderConfig::Vp9EncoderSpecificSettings>(
|
|
vp9_settings_);
|
|
EXPECT_EQ(1u, encoder_config->number_of_streams);
|
|
EXPECT_EQ(1u, encoder_config->simulcast_layers.size());
|
|
encoder_config->simulcast_layers[0].num_temporal_layers =
|
|
vp9_settings_.numberOfTemporalLayers;
|
|
encoder_config_ = encoder_config->Copy();
|
|
}
|
|
|
|
void ModifyVideoCaptureStartResolution(int* width,
|
|
int* height,
|
|
int* frame_rate) override {
|
|
expected_width_ = *width;
|
|
expected_height_ = *height;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
bool wait = Wait();
|
|
{
|
|
// In case of time out, OnSendRtp might still access frames_sent_;
|
|
MutexLock lock(&mutex_);
|
|
EXPECT_TRUE(wait) << "Test timed out waiting for VP9 packet, num frames "
|
|
<< frames_sent_;
|
|
}
|
|
}
|
|
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
RtpPacket rtp_packet;
|
|
EXPECT_TRUE(rtp_packet.Parse(packet, length));
|
|
|
|
EXPECT_EQ(kVp9PayloadType, rtp_packet.PayloadType());
|
|
rtc::ArrayView<const uint8_t> rtp_payload = rtp_packet.payload();
|
|
|
|
bool new_packet = packets_sent_ == 0 ||
|
|
IsNewerSequenceNumber(rtp_packet.SequenceNumber(),
|
|
last_packet_sequence_number_);
|
|
if (!rtp_payload.empty() && new_packet) {
|
|
RTPVideoHeader video_header;
|
|
EXPECT_NE(
|
|
VideoRtpDepacketizerVp9::ParseRtpPayload(rtp_payload, &video_header),
|
|
0);
|
|
EXPECT_EQ(VideoCodecType::kVideoCodecVP9, video_header.codec);
|
|
// Verify common fields for all configurations.
|
|
const auto& vp9_header =
|
|
absl::get<RTPVideoHeaderVP9>(video_header.video_type_header);
|
|
VerifyCommonHeader(vp9_header);
|
|
CompareConsecutiveFrames(rtp_packet, video_header);
|
|
// Verify configuration specific settings.
|
|
InspectHeader(vp9_header);
|
|
|
|
++packets_sent_;
|
|
if (rtp_packet.Marker()) {
|
|
MutexLock lock(&mutex_);
|
|
++frames_sent_;
|
|
}
|
|
last_packet_marker_ = rtp_packet.Marker();
|
|
last_packet_sequence_number_ = rtp_packet.SequenceNumber();
|
|
last_packet_timestamp_ = rtp_packet.Timestamp();
|
|
last_vp9_ = vp9_header;
|
|
}
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
protected:
|
|
bool ContinuousPictureId(const RTPVideoHeaderVP9& vp9) const {
|
|
if (last_vp9_.picture_id > vp9.picture_id) {
|
|
return vp9.picture_id == 0; // Wrap.
|
|
} else {
|
|
return vp9.picture_id == last_vp9_.picture_id + 1;
|
|
}
|
|
}
|
|
|
|
void VerifySpatialIdxWithinFrame(const RTPVideoHeaderVP9& vp9) const {
|
|
bool new_layer = vp9.spatial_idx != last_vp9_.spatial_idx;
|
|
EXPECT_EQ(new_layer, vp9.beginning_of_frame);
|
|
EXPECT_EQ(new_layer, last_vp9_.end_of_frame);
|
|
EXPECT_EQ(new_layer ? last_vp9_.spatial_idx + 1 : last_vp9_.spatial_idx,
|
|
vp9.spatial_idx);
|
|
}
|
|
|
|
void VerifyFixedTemporalLayerStructure(const RTPVideoHeaderVP9& vp9,
|
|
uint8_t num_layers) const {
|
|
switch (num_layers) {
|
|
case 0:
|
|
VerifyTemporalLayerStructure0(vp9);
|
|
break;
|
|
case 1:
|
|
VerifyTemporalLayerStructure1(vp9);
|
|
break;
|
|
case 2:
|
|
VerifyTemporalLayerStructure2(vp9);
|
|
break;
|
|
case 3:
|
|
VerifyTemporalLayerStructure3(vp9);
|
|
break;
|
|
default:
|
|
RTC_DCHECK_NOTREACHED();
|
|
}
|
|
}
|
|
|
|
void VerifyTemporalLayerStructure0(const RTPVideoHeaderVP9& vp9) const {
|
|
EXPECT_EQ(kNoTl0PicIdx, vp9.tl0_pic_idx);
|
|
EXPECT_EQ(kNoTemporalIdx, vp9.temporal_idx); // no tid
|
|
// Technically true, but layer indices not available.
|
|
EXPECT_FALSE(vp9.temporal_up_switch);
|
|
}
|
|
|
|
void VerifyTemporalLayerStructure1(const RTPVideoHeaderVP9& vp9) const {
|
|
EXPECT_NE(kNoTl0PicIdx, vp9.tl0_pic_idx);
|
|
EXPECT_EQ(0, vp9.temporal_idx); // 0,0,0,...
|
|
}
|
|
|
|
void VerifyTemporalLayerStructure2(const RTPVideoHeaderVP9& vp9) const {
|
|
EXPECT_NE(kNoTl0PicIdx, vp9.tl0_pic_idx);
|
|
EXPECT_GE(vp9.temporal_idx, 0); // 0,1,0,1,... (tid reset on I-frames).
|
|
EXPECT_LE(vp9.temporal_idx, 1);
|
|
EXPECT_TRUE(vp9.temporal_up_switch);
|
|
if (IsNewPictureId(vp9)) {
|
|
uint8_t expected_tid =
|
|
(!vp9.inter_pic_predicted || last_vp9_.temporal_idx == 1) ? 0 : 1;
|
|
EXPECT_EQ(expected_tid, vp9.temporal_idx);
|
|
}
|
|
}
|
|
|
|
void VerifyTemporalLayerStructure3(const RTPVideoHeaderVP9& vp9) const {
|
|
EXPECT_NE(kNoTl0PicIdx, vp9.tl0_pic_idx);
|
|
EXPECT_GE(vp9.temporal_idx, 0); // 0,2,1,2,... (tid reset on I-frames).
|
|
EXPECT_LE(vp9.temporal_idx, 2);
|
|
if (IsNewPictureId(vp9) && vp9.inter_pic_predicted) {
|
|
EXPECT_NE(vp9.temporal_idx, last_vp9_.temporal_idx);
|
|
EXPECT_TRUE(vp9.temporal_up_switch);
|
|
switch (vp9.temporal_idx) {
|
|
case 0:
|
|
EXPECT_EQ(last_vp9_.temporal_idx, 2);
|
|
break;
|
|
case 1:
|
|
EXPECT_EQ(last_vp9_.temporal_idx, 2);
|
|
break;
|
|
case 2:
|
|
EXPECT_LT(last_vp9_.temporal_idx, 2);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void VerifyTl0Idx(const RTPVideoHeaderVP9& vp9) const {
|
|
if (vp9.tl0_pic_idx == kNoTl0PicIdx)
|
|
return;
|
|
|
|
uint8_t expected_tl0_idx = last_vp9_.tl0_pic_idx;
|
|
if (vp9.temporal_idx == 0)
|
|
++expected_tl0_idx;
|
|
EXPECT_EQ(expected_tl0_idx, vp9.tl0_pic_idx);
|
|
}
|
|
|
|
bool IsNewPictureId(const RTPVideoHeaderVP9& vp9) const {
|
|
return frames_sent_ > 0 && (vp9.picture_id != last_vp9_.picture_id);
|
|
}
|
|
|
|
// Flexible mode (F=1): Non-flexible mode (F=0):
|
|
//
|
|
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
|
// |I|P|L|F|B|E|V|-| |I|P|L|F|B|E|V|-|
|
|
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
|
// I: |M| PICTURE ID | I: |M| PICTURE ID |
|
|
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
|
// M: | EXTENDED PID | M: | EXTENDED PID |
|
|
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
|
// L: | T |U| S |D| L: | T |U| S |D|
|
|
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
|
// P,F: | P_DIFF |X|N| | TL0PICIDX |
|
|
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
|
// X: |EXTENDED P_DIFF| V: | SS .. |
|
|
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
|
// V: | SS .. |
|
|
// +-+-+-+-+-+-+-+-+
|
|
void VerifyCommonHeader(const RTPVideoHeaderVP9& vp9) const {
|
|
EXPECT_EQ(kMaxTwoBytePictureId, vp9.max_picture_id); // M:1
|
|
EXPECT_NE(kNoPictureId, vp9.picture_id); // I:1
|
|
EXPECT_EQ(vp9_settings_.flexibleMode, vp9.flexible_mode); // F
|
|
|
|
if (vp9_settings_.numberOfSpatialLayers > 1) {
|
|
EXPECT_LT(vp9.spatial_idx, vp9_settings_.numberOfSpatialLayers);
|
|
} else if (vp9_settings_.numberOfTemporalLayers > 1) {
|
|
EXPECT_EQ(vp9.spatial_idx, 0);
|
|
} else {
|
|
EXPECT_EQ(vp9.spatial_idx, kNoSpatialIdx);
|
|
}
|
|
|
|
if (vp9_settings_.numberOfTemporalLayers > 1) {
|
|
EXPECT_LT(vp9.temporal_idx, vp9_settings_.numberOfTemporalLayers);
|
|
} else if (vp9_settings_.numberOfSpatialLayers > 1) {
|
|
EXPECT_EQ(vp9.temporal_idx, 0);
|
|
} else {
|
|
EXPECT_EQ(vp9.temporal_idx, kNoTemporalIdx);
|
|
}
|
|
|
|
if (vp9.ss_data_available) // V
|
|
VerifySsData(vp9);
|
|
|
|
if (frames_sent_ == 0)
|
|
EXPECT_FALSE(vp9.inter_pic_predicted); // P
|
|
|
|
if (!vp9.inter_pic_predicted) {
|
|
if (vp9.temporal_idx == kNoTemporalIdx) {
|
|
EXPECT_FALSE(vp9.temporal_up_switch);
|
|
} else {
|
|
EXPECT_EQ(vp9.temporal_idx, 0);
|
|
EXPECT_TRUE(vp9.temporal_up_switch);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Scalability structure (SS).
|
|
//
|
|
// +-+-+-+-+-+-+-+-+
|
|
// V: | N_S |Y|G|-|-|-|
|
|
// +-+-+-+-+-+-+-+-+
|
|
// Y: | WIDTH | N_S + 1 times
|
|
// +-+-+-+-+-+-+-+-+
|
|
// | HEIGHT |
|
|
// +-+-+-+-+-+-+-+-+
|
|
// G: | N_G |
|
|
// +-+-+-+-+-+-+-+-+
|
|
// N_G: | T |U| R |-|-| N_G times
|
|
// +-+-+-+-+-+-+-+-+
|
|
// | P_DIFF | R times
|
|
// +-+-+-+-+-+-+-+-+
|
|
void VerifySsData(const RTPVideoHeaderVP9& vp9) const {
|
|
EXPECT_TRUE(vp9.ss_data_available); // V
|
|
EXPECT_EQ(vp9_settings_.numberOfSpatialLayers, // N_S + 1
|
|
vp9.num_spatial_layers);
|
|
EXPECT_TRUE(vp9.spatial_layer_resolution_present); // Y:1
|
|
int expected_width = expected_width_;
|
|
int expected_height = expected_height_;
|
|
for (int i = static_cast<int>(vp9.num_spatial_layers) - 1; i >= 0; --i) {
|
|
EXPECT_EQ(expected_width, vp9.width[i]); // WIDTH
|
|
EXPECT_EQ(expected_height, vp9.height[i]); // HEIGHT
|
|
expected_width /= 2;
|
|
expected_height /= 2;
|
|
}
|
|
}
|
|
|
|
void CompareConsecutiveFrames(const RtpPacket& rtp_packet,
|
|
const RTPVideoHeader& video) const {
|
|
const auto& vp9_header =
|
|
absl::get<RTPVideoHeaderVP9>(video.video_type_header);
|
|
|
|
const bool new_temporal_unit =
|
|
packets_sent_ == 0 ||
|
|
IsNewerTimestamp(rtp_packet.Timestamp(), last_packet_timestamp_);
|
|
const bool new_frame =
|
|
new_temporal_unit || last_vp9_.spatial_idx != vp9_header.spatial_idx;
|
|
|
|
EXPECT_EQ(new_frame, video.is_first_packet_in_frame);
|
|
if (!new_temporal_unit) {
|
|
EXPECT_FALSE(last_packet_marker_);
|
|
EXPECT_EQ(last_packet_timestamp_, rtp_packet.Timestamp());
|
|
EXPECT_EQ(last_vp9_.picture_id, vp9_header.picture_id);
|
|
EXPECT_EQ(last_vp9_.temporal_idx, vp9_header.temporal_idx);
|
|
EXPECT_EQ(last_vp9_.tl0_pic_idx, vp9_header.tl0_pic_idx);
|
|
VerifySpatialIdxWithinFrame(vp9_header);
|
|
return;
|
|
}
|
|
// New frame.
|
|
EXPECT_TRUE(vp9_header.beginning_of_frame);
|
|
|
|
// Compare with last packet in previous frame.
|
|
if (frames_sent_ == 0)
|
|
return;
|
|
EXPECT_TRUE(last_vp9_.end_of_frame);
|
|
EXPECT_TRUE(last_packet_marker_);
|
|
EXPECT_TRUE(ContinuousPictureId(vp9_header));
|
|
VerifyTl0Idx(vp9_header);
|
|
}
|
|
|
|
test::FunctionVideoEncoderFactory encoder_factory_;
|
|
VideoCodecVP9 vp9_settings_;
|
|
webrtc::VideoEncoderConfig encoder_config_;
|
|
bool last_packet_marker_ = false;
|
|
uint16_t last_packet_sequence_number_ = 0;
|
|
uint32_t last_packet_timestamp_ = 0;
|
|
RTPVideoHeaderVP9 last_vp9_;
|
|
size_t packets_sent_;
|
|
Mutex mutex_;
|
|
size_t frames_sent_;
|
|
int expected_width_;
|
|
int expected_height_;
|
|
};
|
|
|
|
TEST_F(VideoSendStreamTest, Vp9NonFlexMode_1Tl1SLayers) {
|
|
const uint8_t kNumTemporalLayers = 1;
|
|
const uint8_t kNumSpatialLayers = 1;
|
|
TestVp9NonFlexMode(kNumTemporalLayers, kNumSpatialLayers);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, Vp9NonFlexMode_2Tl1SLayers) {
|
|
const uint8_t kNumTemporalLayers = 2;
|
|
const uint8_t kNumSpatialLayers = 1;
|
|
TestVp9NonFlexMode(kNumTemporalLayers, kNumSpatialLayers);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, Vp9NonFlexMode_3Tl1SLayers) {
|
|
const uint8_t kNumTemporalLayers = 3;
|
|
const uint8_t kNumSpatialLayers = 1;
|
|
TestVp9NonFlexMode(kNumTemporalLayers, kNumSpatialLayers);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, Vp9NonFlexMode_1Tl2SLayers) {
|
|
const uint8_t kNumTemporalLayers = 1;
|
|
const uint8_t kNumSpatialLayers = 2;
|
|
TestVp9NonFlexMode(kNumTemporalLayers, kNumSpatialLayers);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, Vp9NonFlexMode_2Tl2SLayers) {
|
|
const uint8_t kNumTemporalLayers = 2;
|
|
const uint8_t kNumSpatialLayers = 2;
|
|
TestVp9NonFlexMode(kNumTemporalLayers, kNumSpatialLayers);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, Vp9NonFlexMode_3Tl2SLayers) {
|
|
const uint8_t kNumTemporalLayers = 3;
|
|
const uint8_t kNumSpatialLayers = 2;
|
|
TestVp9NonFlexMode(kNumTemporalLayers, kNumSpatialLayers);
|
|
}
|
|
|
|
void VideoSendStreamTest::TestVp9NonFlexMode(uint8_t num_temporal_layers,
|
|
uint8_t num_spatial_layers) {
|
|
static const size_t kNumFramesToSend = 100;
|
|
// Set to < kNumFramesToSend and coprime to length of temporal layer
|
|
// structures to verify temporal id reset on key frame.
|
|
static const int kKeyFrameInterval = 31;
|
|
|
|
static const int kWidth = kMinVp9SpatialLayerLongSideLength;
|
|
static const int kHeight = kMinVp9SpatialLayerShortSideLength;
|
|
static const float kGoodBitsPerPixel = 0.1f;
|
|
class NonFlexibleMode : public Vp9HeaderObserver {
|
|
public:
|
|
NonFlexibleMode(uint8_t num_temporal_layers, uint8_t num_spatial_layers)
|
|
: num_temporal_layers_(num_temporal_layers),
|
|
num_spatial_layers_(num_spatial_layers),
|
|
l_field_(num_temporal_layers > 1 || num_spatial_layers > 1) {}
|
|
|
|
void ModifyVideoConfigsHook(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
encoder_config->codec_type = kVideoCodecVP9;
|
|
int bitrate_bps = 0;
|
|
for (int sl_idx = 0; sl_idx < num_spatial_layers_; ++sl_idx) {
|
|
const int width = kWidth << sl_idx;
|
|
const int height = kHeight << sl_idx;
|
|
const float bpp = kGoodBitsPerPixel / (1 << sl_idx);
|
|
bitrate_bps += static_cast<int>(width * height * bpp * 30);
|
|
}
|
|
encoder_config->max_bitrate_bps = bitrate_bps * 2;
|
|
|
|
vp9_settings_.flexibleMode = false;
|
|
vp9_settings_.frameDroppingOn = false;
|
|
vp9_settings_.automaticResizeOn = false;
|
|
vp9_settings_.keyFrameInterval = kKeyFrameInterval;
|
|
vp9_settings_.numberOfTemporalLayers = num_temporal_layers_;
|
|
vp9_settings_.numberOfSpatialLayers = num_spatial_layers_;
|
|
}
|
|
|
|
void ModifyVideoCaptureStartResolution(int* width,
|
|
int* height,
|
|
int* frame_rate) override {
|
|
expected_width_ = kWidth << (num_spatial_layers_ - 1);
|
|
expected_height_ = kHeight << (num_spatial_layers_ - 1);
|
|
*width = expected_width_;
|
|
*height = expected_height_;
|
|
}
|
|
|
|
void InspectHeader(const RTPVideoHeaderVP9& vp9) override {
|
|
bool ss_data_expected =
|
|
!vp9.inter_pic_predicted && vp9.beginning_of_frame &&
|
|
(vp9.spatial_idx == 0 || vp9.spatial_idx == kNoSpatialIdx);
|
|
EXPECT_EQ(ss_data_expected, vp9.ss_data_available);
|
|
if (num_spatial_layers_ > 1) {
|
|
EXPECT_EQ(vp9.spatial_idx > 0, vp9.inter_layer_predicted);
|
|
} else {
|
|
EXPECT_FALSE(vp9.inter_layer_predicted);
|
|
}
|
|
|
|
EXPECT_EQ(!vp9.inter_pic_predicted,
|
|
frames_sent_ % kKeyFrameInterval == 0);
|
|
|
|
if (IsNewPictureId(vp9)) {
|
|
if (num_temporal_layers_ == 1 && num_spatial_layers_ == 1) {
|
|
EXPECT_EQ(kNoSpatialIdx, vp9.spatial_idx);
|
|
} else {
|
|
EXPECT_EQ(0, vp9.spatial_idx);
|
|
}
|
|
if (num_spatial_layers_ > 1)
|
|
EXPECT_EQ(num_spatial_layers_ - 1, last_vp9_.spatial_idx);
|
|
}
|
|
|
|
VerifyFixedTemporalLayerStructure(vp9,
|
|
l_field_ ? num_temporal_layers_ : 0);
|
|
|
|
if (frames_sent_ > kNumFramesToSend)
|
|
observation_complete_.Set();
|
|
}
|
|
const uint8_t num_temporal_layers_;
|
|
const uint8_t num_spatial_layers_;
|
|
const bool l_field_;
|
|
|
|
private:
|
|
void ModifySenderBitrateConfig(
|
|
BitrateConstraints* bitrate_config) override {
|
|
const int kMinBitrateBps = 300000;
|
|
bitrate_config->min_bitrate_bps = kMinBitrateBps;
|
|
}
|
|
} test(num_temporal_layers, num_spatial_layers);
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, Vp9NonFlexModeSmallResolution) {
|
|
static const size_t kNumFramesToSend = 50;
|
|
static const int kWidth = 4;
|
|
static const int kHeight = 4;
|
|
class NonFlexibleModeResolution : public Vp9HeaderObserver {
|
|
void ModifyVideoConfigsHook(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
encoder_config->codec_type = kVideoCodecVP9;
|
|
vp9_settings_.flexibleMode = false;
|
|
vp9_settings_.numberOfTemporalLayers = 1;
|
|
vp9_settings_.numberOfSpatialLayers = 1;
|
|
|
|
EXPECT_EQ(1u, encoder_config->number_of_streams);
|
|
}
|
|
|
|
void InspectHeader(const RTPVideoHeaderVP9& vp9_header) override {
|
|
if (frames_sent_ > kNumFramesToSend)
|
|
observation_complete_.Set();
|
|
}
|
|
|
|
void ModifyVideoCaptureStartResolution(int* width,
|
|
int* height,
|
|
int* frame_rate) override {
|
|
expected_width_ = kWidth;
|
|
expected_height_ = kHeight;
|
|
*width = kWidth;
|
|
*height = kHeight;
|
|
}
|
|
} test;
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
#if defined(WEBRTC_ANDROID)
|
|
// Crashes on Android; bugs.webrtc.org/7401
|
|
#define MAYBE_Vp9FlexModeRefCount DISABLED_Vp9FlexModeRefCount
|
|
#else
|
|
// TODO(webrtc:9270): Support of flexible mode is temporarily disabled. Enable
|
|
// the test after webrtc:9270 is implemented.
|
|
#define MAYBE_Vp9FlexModeRefCount DISABLED_Vp9FlexModeRefCount
|
|
// #define MAYBE_Vp9FlexModeRefCount Vp9FlexModeRefCount
|
|
#endif
|
|
TEST_F(VideoSendStreamTest, MAYBE_Vp9FlexModeRefCount) {
|
|
class FlexibleMode : public Vp9HeaderObserver {
|
|
void ModifyVideoConfigsHook(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
encoder_config->codec_type = kVideoCodecVP9;
|
|
encoder_config->content_type = VideoEncoderConfig::ContentType::kScreen;
|
|
vp9_settings_.flexibleMode = true;
|
|
vp9_settings_.numberOfTemporalLayers = 1;
|
|
vp9_settings_.numberOfSpatialLayers = 2;
|
|
}
|
|
|
|
void InspectHeader(const RTPVideoHeaderVP9& vp9_header) override {
|
|
EXPECT_TRUE(vp9_header.flexible_mode);
|
|
EXPECT_EQ(kNoTl0PicIdx, vp9_header.tl0_pic_idx);
|
|
if (vp9_header.inter_pic_predicted) {
|
|
EXPECT_GT(vp9_header.num_ref_pics, 0u);
|
|
observation_complete_.Set();
|
|
}
|
|
}
|
|
} test;
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
#endif // defined(RTC_ENABLE_VP9)
|
|
|
|
void VideoSendStreamTest::TestRequestSourceRotateVideo(
|
|
bool support_orientation_ext) {
|
|
CreateSenderCall();
|
|
|
|
test::NullTransport transport;
|
|
CreateSendConfig(1, 0, 0, &transport);
|
|
GetVideoSendConfig()->rtp.extensions.clear();
|
|
if (support_orientation_ext) {
|
|
GetVideoSendConfig()->rtp.extensions.push_back(
|
|
RtpExtension(RtpExtension::kVideoRotationUri, 1));
|
|
}
|
|
|
|
CreateVideoStreams();
|
|
test::FrameForwarder forwarder;
|
|
GetVideoSendStream()->SetSource(&forwarder,
|
|
DegradationPreference::MAINTAIN_FRAMERATE);
|
|
|
|
EXPECT_TRUE(forwarder.sink_wants().rotation_applied !=
|
|
support_orientation_ext);
|
|
|
|
DestroyStreams();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest,
|
|
RequestSourceRotateIfVideoOrientationExtensionNotSupported) {
|
|
TestRequestSourceRotateVideo(false);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest,
|
|
DoNotRequestsRotationIfVideoOrientationExtensionSupported) {
|
|
TestRequestSourceRotateVideo(true);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, EncoderConfigMaxFramerateReportedToSource) {
|
|
static const int kMaxFps = 22;
|
|
class FpsObserver : public test::SendTest,
|
|
public test::FrameGeneratorCapturer::SinkWantsObserver {
|
|
public:
|
|
FpsObserver() : SendTest(kDefaultTimeoutMs) {}
|
|
|
|
void OnFrameGeneratorCapturerCreated(
|
|
test::FrameGeneratorCapturer* frame_generator_capturer) override {
|
|
frame_generator_capturer->SetSinkWantsObserver(this);
|
|
}
|
|
|
|
void OnSinkWantsChanged(rtc::VideoSinkInterface<VideoFrame>* sink,
|
|
const rtc::VideoSinkWants& wants) override {
|
|
if (wants.max_framerate_fps == kMaxFps)
|
|
observation_complete_.Set();
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
encoder_config->simulcast_layers[0].max_framerate = kMaxFps;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait()) << "Timed out while waiting for fps to be reported.";
|
|
}
|
|
} test;
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
// This test verifies that overhead is removed from the bandwidth estimate by
|
|
// testing that the maximum possible target payload rate is smaller than the
|
|
// maximum bandwidth estimate by the overhead rate.
|
|
TEST_F(VideoSendStreamTest, RemoveOverheadFromBandwidth) {
|
|
test::ScopedFieldTrials override_field_trials(
|
|
"WebRTC-SendSideBwe-WithOverhead/Enabled/");
|
|
class RemoveOverheadFromBandwidthTest : public test::EndToEndTest,
|
|
public test::FakeEncoder {
|
|
public:
|
|
explicit RemoveOverheadFromBandwidthTest(TaskQueueBase* task_queue)
|
|
: EndToEndTest(test::CallTest::kDefaultTimeoutMs),
|
|
FakeEncoder(Clock::GetRealTimeClock()),
|
|
task_queue_(task_queue),
|
|
encoder_factory_(this),
|
|
call_(nullptr),
|
|
max_bitrate_bps_(0),
|
|
first_packet_sent_(false) {}
|
|
|
|
void SetRates(const RateControlParameters& parameters) override {
|
|
MutexLock lock(&mutex_);
|
|
// Wait for the first sent packet so that videosendstream knows
|
|
// rtp_overhead.
|
|
if (first_packet_sent_) {
|
|
max_bitrate_bps_ = parameters.bitrate.get_sum_bps();
|
|
bitrate_changed_event_.Set();
|
|
}
|
|
return FakeEncoder::SetRates(parameters);
|
|
}
|
|
|
|
void OnCallsCreated(Call* sender_call, Call* receiver_call) override {
|
|
call_ = sender_call;
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
send_config->rtp.max_packet_size = 1200;
|
|
send_config->encoder_settings.encoder_factory = &encoder_factory_;
|
|
EXPECT_FALSE(send_config->rtp.extensions.empty());
|
|
}
|
|
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
MutexLock lock(&mutex_);
|
|
first_packet_sent_ = true;
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
BitrateConstraints bitrate_config;
|
|
constexpr int kStartBitrateBps = 60000;
|
|
constexpr int kMaxBitrateBps = 60000;
|
|
constexpr int kMinBitrateBps = 10000;
|
|
bitrate_config.start_bitrate_bps = kStartBitrateBps;
|
|
bitrate_config.max_bitrate_bps = kMaxBitrateBps;
|
|
bitrate_config.min_bitrate_bps = kMinBitrateBps;
|
|
SendTask(RTC_FROM_HERE, task_queue_, [this, &bitrate_config]() {
|
|
call_->GetTransportControllerSend()->SetSdpBitrateParameters(
|
|
bitrate_config);
|
|
call_->GetTransportControllerSend()->OnTransportOverheadChanged(40);
|
|
});
|
|
|
|
// At a bitrate of 60kbps with a packet size of 1200B video and an
|
|
// overhead of 40B per packet video produces 2240bps overhead.
|
|
// So the encoder BW should be set to 57760bps.
|
|
EXPECT_TRUE(
|
|
bitrate_changed_event_.Wait(VideoSendStreamTest::kDefaultTimeoutMs));
|
|
{
|
|
MutexLock lock(&mutex_);
|
|
EXPECT_LE(max_bitrate_bps_, 57760u);
|
|
}
|
|
}
|
|
|
|
private:
|
|
TaskQueueBase* const task_queue_;
|
|
test::VideoEncoderProxyFactory encoder_factory_;
|
|
Call* call_;
|
|
Mutex mutex_;
|
|
uint32_t max_bitrate_bps_ RTC_GUARDED_BY(&mutex_);
|
|
bool first_packet_sent_ RTC_GUARDED_BY(&mutex_);
|
|
rtc::Event bitrate_changed_event_;
|
|
} test(task_queue());
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
class PacingFactorObserver : public test::SendTest {
|
|
public:
|
|
PacingFactorObserver(bool configure_send_side,
|
|
absl::optional<float> expected_pacing_factor)
|
|
: test::SendTest(VideoSendStreamTest::kDefaultTimeoutMs),
|
|
configure_send_side_(configure_send_side),
|
|
expected_pacing_factor_(expected_pacing_factor) {}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
// Check if send-side bwe extension is already present, and remove it if
|
|
// it is not desired.
|
|
bool has_send_side = false;
|
|
for (auto it = send_config->rtp.extensions.begin();
|
|
it != send_config->rtp.extensions.end(); ++it) {
|
|
if (it->uri == RtpExtension::kTransportSequenceNumberUri) {
|
|
if (configure_send_side_) {
|
|
has_send_side = true;
|
|
} else {
|
|
send_config->rtp.extensions.erase(it);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (configure_send_side_ && !has_send_side) {
|
|
rtc::UniqueNumberGenerator<int> unique_id_generator;
|
|
unique_id_generator.AddKnownId(0); // First valid RTP extension ID is 1.
|
|
for (const RtpExtension& extension : send_config->rtp.extensions) {
|
|
unique_id_generator.AddKnownId(extension.id);
|
|
}
|
|
// Want send side, not present by default, so add it.
|
|
send_config->rtp.extensions.emplace_back(
|
|
RtpExtension::kTransportSequenceNumberUri, unique_id_generator());
|
|
}
|
|
|
|
// ALR only enabled for screenshare.
|
|
encoder_config->content_type = VideoEncoderConfig::ContentType::kScreen;
|
|
}
|
|
|
|
void OnVideoStreamsCreated(
|
|
VideoSendStream* send_stream,
|
|
const std::vector<VideoReceiveStream*>& receive_streams) override {
|
|
auto internal_send_peer = test::VideoSendStreamPeer(send_stream);
|
|
// Video streams created, check that pacing factor is correctly configured.
|
|
EXPECT_EQ(expected_pacing_factor_,
|
|
internal_send_peer.GetPacingFactorOverride());
|
|
observation_complete_.Set();
|
|
}
|
|
|
|
void PerformTest() override {
|
|
EXPECT_TRUE(Wait()) << "Timed out while waiting for stream creation.";
|
|
}
|
|
|
|
private:
|
|
const bool configure_send_side_;
|
|
const absl::optional<float> expected_pacing_factor_;
|
|
};
|
|
|
|
std::string GetAlrProbingExperimentString() {
|
|
return std::string(
|
|
AlrExperimentSettings::kScreenshareProbingBweExperimentName) +
|
|
"/1.0,2875,80,40,-60,3/";
|
|
}
|
|
const float kAlrProbingExperimentPaceMultiplier = 1.0f;
|
|
|
|
TEST_F(VideoSendStreamTest, AlrConfiguredWhenSendSideOn) {
|
|
test::ScopedFieldTrials alr_experiment(GetAlrProbingExperimentString());
|
|
// Send-side bwe on, use pacing factor from `kAlrProbingExperiment` above.
|
|
PacingFactorObserver test_with_send_side(true,
|
|
kAlrProbingExperimentPaceMultiplier);
|
|
RunBaseTest(&test_with_send_side);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, AlrNotConfiguredWhenSendSideOff) {
|
|
test::ScopedFieldTrials alr_experiment(GetAlrProbingExperimentString());
|
|
// Send-side bwe off, use configuration should not be overridden.
|
|
PacingFactorObserver test_without_send_side(false, absl::nullopt);
|
|
RunBaseTest(&test_without_send_side);
|
|
}
|
|
|
|
// Test class takes as argument a function pointer to reset the send
|
|
// stream and call OnVideoStreamsCreated. This is necessary since you cannot
|
|
// change the content type of a VideoSendStream, you need to recreate it.
|
|
// Stopping and recreating the stream can only be done on the main thread and in
|
|
// the context of VideoSendStreamTest (not BaseTest). The test switches from
|
|
// realtime to screenshare and back.
|
|
template <typename T>
|
|
class ContentSwitchTest : public test::SendTest {
|
|
public:
|
|
enum class StreamState {
|
|
kBeforeSwitch = 0,
|
|
kInScreenshare = 1,
|
|
kAfterSwitchBack = 2,
|
|
};
|
|
static const uint32_t kMinPacketsToSend = 50;
|
|
|
|
explicit ContentSwitchTest(T* stream_reset_fun, TaskQueueBase* task_queue)
|
|
: SendTest(test::CallTest::kDefaultTimeoutMs),
|
|
call_(nullptr),
|
|
state_(StreamState::kBeforeSwitch),
|
|
send_stream_(nullptr),
|
|
send_stream_config_(nullptr),
|
|
packets_sent_(0),
|
|
stream_resetter_(stream_reset_fun),
|
|
task_queue_(task_queue) {
|
|
RTC_DCHECK(stream_resetter_);
|
|
}
|
|
|
|
void OnVideoStreamsCreated(
|
|
VideoSendStream* send_stream,
|
|
const std::vector<VideoReceiveStream*>& receive_streams) override {
|
|
MutexLock lock(&mutex_);
|
|
send_stream_ = send_stream;
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
RTC_DCHECK_EQ(1, encoder_config->number_of_streams);
|
|
encoder_config->min_transmit_bitrate_bps = 0;
|
|
encoder_config->content_type =
|
|
VideoEncoderConfig::ContentType::kRealtimeVideo;
|
|
send_stream_config_ = send_config->Copy();
|
|
encoder_config_ = encoder_config->Copy();
|
|
}
|
|
|
|
void OnCallsCreated(Call* sender_call, Call* receiver_call) override {
|
|
call_ = sender_call;
|
|
}
|
|
|
|
void OnStreamsStopped() override {
|
|
MutexLock lock(&mutex_);
|
|
done_ = true;
|
|
}
|
|
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
task_queue_->PostTask(ToQueuedTask([this]() {
|
|
MutexLock lock(&mutex_);
|
|
if (done_)
|
|
return;
|
|
|
|
auto internal_send_peer = test::VideoSendStreamPeer(send_stream_);
|
|
float pacing_factor =
|
|
internal_send_peer.GetPacingFactorOverride().value_or(0.0f);
|
|
float expected_pacing_factor = 1.1; // Strict pacing factor.
|
|
VideoSendStream::Stats stats = send_stream_->GetStats();
|
|
if (stats.content_type == webrtc::VideoContentType::SCREENSHARE) {
|
|
expected_pacing_factor = 1.0f; // Currently used pacing factor in ALR.
|
|
}
|
|
|
|
EXPECT_NEAR(expected_pacing_factor, pacing_factor, 1e-6);
|
|
|
|
// Wait until at least kMinPacketsToSend packets to be sent, so that
|
|
// some frames would be encoded.
|
|
if (++packets_sent_ < kMinPacketsToSend)
|
|
return;
|
|
|
|
if (state_ != StreamState::kAfterSwitchBack) {
|
|
// We've sent kMinPacketsToSend packets, switch the content type and
|
|
// move move to the next state. Note that we need to recreate the stream
|
|
// if changing content type.
|
|
packets_sent_ = 0;
|
|
if (encoder_config_.content_type ==
|
|
VideoEncoderConfig::ContentType::kRealtimeVideo) {
|
|
encoder_config_.content_type =
|
|
VideoEncoderConfig::ContentType::kScreen;
|
|
} else {
|
|
encoder_config_.content_type =
|
|
VideoEncoderConfig::ContentType::kRealtimeVideo;
|
|
}
|
|
switch (state_) {
|
|
case StreamState::kBeforeSwitch:
|
|
state_ = StreamState::kInScreenshare;
|
|
break;
|
|
case StreamState::kInScreenshare:
|
|
state_ = StreamState::kAfterSwitchBack;
|
|
break;
|
|
case StreamState::kAfterSwitchBack:
|
|
RTC_DCHECK_NOTREACHED();
|
|
break;
|
|
}
|
|
content_switch_event_.Set();
|
|
return;
|
|
}
|
|
observation_complete_.Set();
|
|
}));
|
|
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
void PerformTest() override {
|
|
while (GetStreamState() != StreamState::kAfterSwitchBack) {
|
|
ASSERT_TRUE(
|
|
content_switch_event_.Wait(test::CallTest::kDefaultTimeoutMs));
|
|
(*stream_resetter_)(send_stream_config_, encoder_config_, this);
|
|
}
|
|
|
|
ASSERT_TRUE(Wait())
|
|
<< "Timed out waiting for a frame sent after switch back";
|
|
}
|
|
|
|
private:
|
|
StreamState GetStreamState() {
|
|
MutexLock lock(&mutex_);
|
|
return state_;
|
|
}
|
|
|
|
Mutex mutex_;
|
|
rtc::Event content_switch_event_;
|
|
Call* call_;
|
|
bool done_ RTC_GUARDED_BY(mutex_) = false;
|
|
StreamState state_ RTC_GUARDED_BY(mutex_);
|
|
VideoSendStream* send_stream_ RTC_GUARDED_BY(mutex_);
|
|
VideoSendStream::Config send_stream_config_;
|
|
VideoEncoderConfig encoder_config_;
|
|
uint32_t packets_sent_ RTC_GUARDED_BY(mutex_);
|
|
T* stream_resetter_;
|
|
TaskQueueBase* task_queue_;
|
|
};
|
|
|
|
TEST_F(VideoSendStreamTest, SwitchesToScreenshareAndBack) {
|
|
auto reset_fun = [this](const VideoSendStream::Config& send_stream_config,
|
|
const VideoEncoderConfig& encoder_config,
|
|
test::BaseTest* test) {
|
|
SendTask(RTC_FROM_HERE, task_queue(),
|
|
[this, &send_stream_config, &encoder_config, &test]() {
|
|
Stop();
|
|
DestroyVideoSendStreams();
|
|
SetVideoSendConfig(send_stream_config);
|
|
SetVideoEncoderConfig(encoder_config);
|
|
CreateVideoSendStreams();
|
|
SetVideoDegradation(DegradationPreference::MAINTAIN_RESOLUTION);
|
|
test->OnVideoStreamsCreated(GetVideoSendStream(),
|
|
video_receive_streams_);
|
|
Start();
|
|
});
|
|
};
|
|
ContentSwitchTest<decltype(reset_fun)> test(&reset_fun, task_queue());
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
void VideoSendStreamTest::TestTemporalLayers(
|
|
VideoEncoderFactory* encoder_factory,
|
|
const std::string& payload_name,
|
|
const std::vector<int>& num_temporal_layers) {
|
|
static constexpr int kMaxBitrateBps = 1000000;
|
|
static constexpr int kMinFramesToObservePerStream = 8;
|
|
|
|
class TemporalLayerObserver
|
|
: public test::EndToEndTest,
|
|
public test::FrameGeneratorCapturer::SinkWantsObserver {
|
|
public:
|
|
TemporalLayerObserver(VideoEncoderFactory* encoder_factory,
|
|
const std::string& payload_name,
|
|
const std::vector<int>& num_temporal_layers)
|
|
: EndToEndTest(kDefaultTimeoutMs),
|
|
encoder_factory_(encoder_factory),
|
|
payload_name_(payload_name),
|
|
num_temporal_layers_(num_temporal_layers),
|
|
depacketizer_(CreateVideoRtpDepacketizer(
|
|
PayloadStringToCodecType(payload_name))) {}
|
|
|
|
private:
|
|
void OnFrameGeneratorCapturerCreated(
|
|
test::FrameGeneratorCapturer* frame_generator_capturer) override {
|
|
frame_generator_capturer->ChangeResolution(640, 360);
|
|
}
|
|
|
|
void OnSinkWantsChanged(rtc::VideoSinkInterface<VideoFrame>* sink,
|
|
const rtc::VideoSinkWants& wants) override {}
|
|
|
|
void ModifySenderBitrateConfig(
|
|
BitrateConstraints* bitrate_config) override {
|
|
bitrate_config->start_bitrate_bps = kMaxBitrateBps / 2;
|
|
}
|
|
|
|
size_t GetNumVideoStreams() const override {
|
|
return num_temporal_layers_.size();
|
|
}
|
|
|
|
void ModifyVideoConfigs(
|
|
VideoSendStream::Config* send_config,
|
|
std::vector<VideoReceiveStream::Config>* receive_configs,
|
|
VideoEncoderConfig* encoder_config) override {
|
|
send_config->encoder_settings.encoder_factory = encoder_factory_;
|
|
send_config->rtp.payload_name = payload_name_;
|
|
send_config->rtp.payload_type = test::CallTest::kVideoSendPayloadType;
|
|
encoder_config->video_format.name = payload_name_;
|
|
encoder_config->codec_type = PayloadStringToCodecType(payload_name_);
|
|
encoder_config->video_stream_factory =
|
|
rtc::make_ref_counted<cricket::EncoderStreamFactory>(
|
|
payload_name_, /*max_qp=*/56, /*is_screenshare=*/false,
|
|
/*conference_mode=*/false);
|
|
encoder_config->max_bitrate_bps = kMaxBitrateBps;
|
|
|
|
for (size_t i = 0; i < num_temporal_layers_.size(); ++i) {
|
|
VideoStream& stream = encoder_config->simulcast_layers[i];
|
|
stream.num_temporal_layers = num_temporal_layers_[i];
|
|
configured_num_temporal_layers_[send_config->rtp.ssrcs[i]] =
|
|
num_temporal_layers_[i];
|
|
}
|
|
}
|
|
|
|
struct ParsedPacket {
|
|
uint32_t timestamp;
|
|
uint32_t ssrc;
|
|
int temporal_idx;
|
|
};
|
|
|
|
bool ParsePayload(const uint8_t* packet,
|
|
size_t length,
|
|
ParsedPacket& parsed) const {
|
|
RtpPacket rtp_packet;
|
|
EXPECT_TRUE(rtp_packet.Parse(packet, length));
|
|
|
|
if (rtp_packet.payload_size() == 0) {
|
|
return false; // Padding packet.
|
|
}
|
|
parsed.timestamp = rtp_packet.Timestamp();
|
|
parsed.ssrc = rtp_packet.Ssrc();
|
|
|
|
absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed_payload =
|
|
depacketizer_->Parse(rtp_packet.PayloadBuffer());
|
|
EXPECT_TRUE(parsed_payload);
|
|
|
|
if (const auto* vp8_header = absl::get_if<RTPVideoHeaderVP8>(
|
|
&parsed_payload->video_header.video_type_header)) {
|
|
parsed.temporal_idx = vp8_header->temporalIdx;
|
|
} else {
|
|
RTC_DCHECK_NOTREACHED();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
ParsedPacket parsed;
|
|
if (!ParsePayload(packet, length, parsed))
|
|
return SEND_PACKET;
|
|
|
|
uint32_t ssrc = parsed.ssrc;
|
|
int temporal_idx =
|
|
parsed.temporal_idx == kNoTemporalIdx ? 0 : parsed.temporal_idx;
|
|
max_observed_tl_idxs_[ssrc] =
|
|
std::max(temporal_idx, max_observed_tl_idxs_[ssrc]);
|
|
|
|
if (last_observed_packet_.count(ssrc) == 0 ||
|
|
parsed.timestamp != last_observed_packet_[ssrc].timestamp) {
|
|
num_observed_frames_[ssrc]++;
|
|
}
|
|
last_observed_packet_[ssrc] = parsed;
|
|
|
|
if (HighestTemporalLayerSentPerStream())
|
|
observation_complete_.Set();
|
|
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
bool HighestTemporalLayerSentPerStream() const {
|
|
if (num_observed_frames_.size() !=
|
|
configured_num_temporal_layers_.size()) {
|
|
return false;
|
|
}
|
|
for (const auto& num_frames : num_observed_frames_) {
|
|
if (num_frames.second < kMinFramesToObservePerStream) {
|
|
return false;
|
|
}
|
|
}
|
|
if (max_observed_tl_idxs_.size() !=
|
|
configured_num_temporal_layers_.size()) {
|
|
return false;
|
|
}
|
|
for (const auto& max_tl_idx : max_observed_tl_idxs_) {
|
|
uint32_t ssrc = max_tl_idx.first;
|
|
int configured_num_tls =
|
|
configured_num_temporal_layers_.find(ssrc)->second;
|
|
if (max_tl_idx.second != configured_num_tls - 1)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void PerformTest() override { EXPECT_TRUE(Wait()); }
|
|
|
|
VideoEncoderFactory* const encoder_factory_;
|
|
const std::string payload_name_;
|
|
const std::vector<int> num_temporal_layers_;
|
|
const std::unique_ptr<VideoRtpDepacketizer> depacketizer_;
|
|
// Mapped by SSRC.
|
|
std::map<uint32_t, int> configured_num_temporal_layers_;
|
|
std::map<uint32_t, int> max_observed_tl_idxs_;
|
|
std::map<uint32_t, int> num_observed_frames_;
|
|
std::map<uint32_t, ParsedPacket> last_observed_packet_;
|
|
} test(encoder_factory, payload_name, num_temporal_layers);
|
|
|
|
RunBaseTest(&test);
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, TestTemporalLayersVp8) {
|
|
InternalEncoderFactory internal_encoder_factory;
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[&internal_encoder_factory]() {
|
|
return std::make_unique<SimulcastEncoderAdapter>(
|
|
&internal_encoder_factory, SdpVideoFormat("VP8"));
|
|
});
|
|
|
|
TestTemporalLayers(&encoder_factory, "VP8",
|
|
/*num_temporal_layers=*/{2});
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, TestTemporalLayersVp8Simulcast) {
|
|
InternalEncoderFactory internal_encoder_factory;
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[&internal_encoder_factory]() {
|
|
return std::make_unique<SimulcastEncoderAdapter>(
|
|
&internal_encoder_factory, SdpVideoFormat("VP8"));
|
|
});
|
|
|
|
TestTemporalLayers(&encoder_factory, "VP8",
|
|
/*num_temporal_layers=*/{2, 2});
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, TestTemporalLayersVp8SimulcastWithDifferentNumTls) {
|
|
InternalEncoderFactory internal_encoder_factory;
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[&internal_encoder_factory]() {
|
|
return std::make_unique<SimulcastEncoderAdapter>(
|
|
&internal_encoder_factory, SdpVideoFormat("VP8"));
|
|
});
|
|
|
|
TestTemporalLayers(&encoder_factory, "VP8",
|
|
/*num_temporal_layers=*/{3, 1});
|
|
}
|
|
|
|
TEST_F(VideoSendStreamTest, TestTemporalLayersVp8SimulcastWithoutSimAdapter) {
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[]() { return VP8Encoder::Create(); });
|
|
|
|
TestTemporalLayers(&encoder_factory, "VP8",
|
|
/*num_temporal_layers=*/{2, 2});
|
|
}
|
|
|
|
} // namespace webrtc
|