Fixes incorrect padding setting for VP9 SVC.

Unit test added to verify root cause is fixed.
Scenario test added to verify high-level behavior.

Bug: webrtc:11654
Change-Id: I1ad6e2750f5272e86b4198749edbbf5dfd8315c4
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/176564
Commit-Queue: Erik Språng <sprang@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Sebastian Jansson <srte@webrtc.org>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#31462}
This commit is contained in:
Erik Språng
2020-06-08 13:32:20 +02:00
committed by Commit Bot
parent 08a16c09bc
commit 576db1bf60
3 changed files with 195 additions and 107 deletions

View File

@ -169,5 +169,79 @@ TEST(VideoStreamTest, SendsFecWithFlexFec) {
VideoSendStream::Stats video_stats = video->send()->GetStats(); VideoSendStream::Stats video_stats = video->send()->GetStats();
EXPECT_GT(video_stats.substreams.begin()->second.rtp_stats.fec.packets, 0u); EXPECT_GT(video_stats.substreams.begin()->second.rtp_stats.fec.packets, 0u);
} }
TEST(VideoStreamTest, ResolutionAdaptsToAvailableBandwidth) {
// Declared before scenario to avoid use after free.
std::atomic<size_t> num_qvga_frames_(0);
std::atomic<size_t> num_vga_frames_(0);
Scenario s;
// Link has enough capacity for VGA.
NetworkSimulationConfig net_conf;
net_conf.bandwidth = DataRate::KilobitsPerSec(800);
net_conf.delay = TimeDelta::Millis(50);
auto* client = s.CreateClient("send", [&](CallClientConfig* c) {
c->transport.rates.start_rate = DataRate::KilobitsPerSec(800);
});
auto send_net = {s.CreateSimulationNode(net_conf)};
auto ret_net = {s.CreateSimulationNode(net_conf)};
auto* route = s.CreateRoutes(
client, send_net, s.CreateClient("return", CallClientConfig()), ret_net);
s.CreateVideoStream(route->forward(), [&](VideoStreamConfig* c) {
c->hooks.frame_pair_handlers = {[&](const VideoFramePair& info) {
if (info.decoded->width() == 640) {
++num_vga_frames_;
} else if (info.decoded->width() == 320) {
++num_qvga_frames_;
} else {
ADD_FAILURE() << "Unexpected resolution: " << info.decoded->width();
}
}};
c->source.framerate = 30;
// The resolution must be high enough to allow smaller layers to be
// created.
c->source.generator.width = 640;
c->source.generator.height = 480;
c->encoder.implementation = CodecImpl::kSoftware;
c->encoder.codec = Codec::kVideoCodecVP9;
// Enable SVC.
c->encoder.layers.spatial = 2;
});
// Run for a few seconds, until streams have stabilized,
// check that we are sending VGA.
s.RunFor(TimeDelta::Seconds(5));
EXPECT_GT(num_vga_frames_, 0u);
// Trigger cross traffic, run until we have seen 3 consecutive
// seconds with no VGA frames due to reduced available bandwidth.
auto cross_traffic =
s.net()->StartFakeTcpCrossTraffic(send_net, ret_net, FakeTcpConfig());
int num_seconds_without_vga = 0;
int num_iterations = 0;
do {
ASSERT_LE(++num_iterations, 100);
num_qvga_frames_ = 0;
num_vga_frames_ = 0;
s.RunFor(TimeDelta::Seconds(1));
if (num_qvga_frames_ > 0 && num_vga_frames_ == 0) {
++num_seconds_without_vga;
} else {
num_seconds_without_vga = 0;
}
} while (num_seconds_without_vga < 3);
// Stop cross traffic, make sure we recover and get VGA frames agian.
s.net()->StopCrossTraffic(cross_traffic);
num_qvga_frames_ = 0;
num_vga_frames_ = 0;
s.RunFor(TimeDelta::Seconds(40));
EXPECT_GT(num_qvga_frames_, 0u);
EXPECT_GT(num_vga_frames_, 0u);
}
} // namespace test } // namespace test
} // namespace webrtc } // namespace webrtc

View File

@ -92,17 +92,26 @@ int CalculateMaxPadBitrateBps(const std::vector<VideoStream>& streams,
const double hysteresis_factor = const double hysteresis_factor =
RateControlSettings::ParseFromFieldTrials() RateControlSettings::ParseFromFieldTrials()
.GetSimulcastHysteresisFactor(content_type); .GetSimulcastHysteresisFactor(content_type);
const size_t top_active_stream_idx = active_streams.size() - 1; if (is_svc) {
pad_up_to_bitrate_bps = std::min( // For SVC, since there is only one "stream", the padding bitrate
static_cast<int>( // needed to enable the top spatial layer is stored in the
hysteresis_factor * // |target_bitrate_bps| field.
active_streams[top_active_stream_idx].min_bitrate_bps + // TODO(sprang): This behavior needs to die.
0.5), pad_up_to_bitrate_bps = static_cast<int>(
active_streams[top_active_stream_idx].target_bitrate_bps); hysteresis_factor * active_streams[0].target_bitrate_bps + 0.5);
} else {
const size_t top_active_stream_idx = active_streams.size() - 1;
pad_up_to_bitrate_bps = std::min(
static_cast<int>(
hysteresis_factor *
active_streams[top_active_stream_idx].min_bitrate_bps +
0.5),
active_streams[top_active_stream_idx].target_bitrate_bps);
// Add target_bitrate_bps of the lower active streams. // Add target_bitrate_bps of the lower active streams.
for (size_t i = 0; i < top_active_stream_idx; ++i) { for (size_t i = 0; i < top_active_stream_idx; ++i) {
pad_up_to_bitrate_bps += active_streams[i].target_bitrate_bps; pad_up_to_bitrate_bps += active_streams[i].target_bitrate_bps;
}
} }
} }
} else if (!active_streams.empty() && pad_to_min_bitrate) { } else if (!active_streams.empty() && pad_to_min_bitrate) {

View File

@ -10,6 +10,7 @@
#include "video/video_send_stream_impl.h" #include "video/video_send_stream_impl.h"
#include <algorithm>
#include <memory> #include <memory>
#include <string> #include <string>
@ -43,6 +44,8 @@ bool operator==(const BitrateAllocationUpdate& a,
namespace internal { namespace internal {
namespace { namespace {
using ::testing::_; using ::testing::_;
using ::testing::AllOf;
using ::testing::Field;
using ::testing::Invoke; using ::testing::Invoke;
using ::testing::NiceMock; using ::testing::NiceMock;
using ::testing::Return; using ::testing::Return;
@ -907,112 +910,114 @@ TEST_F(VideoSendStreamImplTest, KeepAliveOnDroppedFrame) {
ASSERT_TRUE(done.Wait(5000)); ASSERT_TRUE(done.Wait(5000));
} }
TEST_F(VideoSendStreamImplTest, ConfiguresBitratesForSvcWithAlr) { TEST_F(VideoSendStreamImplTest, ConfiguresBitratesForSvc) {
test_queue_.SendTask( struct TestConfig {
[this] { bool screenshare = false;
const bool kSuspend = false; bool alr = false;
config_.suspend_below_min_bitrate = kSuspend; int min_padding_bitrate_bps = 0;
config_.rtp.extensions.emplace_back( };
RtpExtension::kTransportSequenceNumberUri, 1);
config_.periodic_alr_bandwidth_probing = true;
auto vss_impl = CreateVideoSendStreamImpl(
kDefaultInitialBitrateBps, kDefaultBitratePriority,
VideoEncoderConfig::ContentType::kScreen);
vss_impl->Start();
// Svc std::vector<TestConfig> test_variants;
VideoStream stream; for (bool screenshare : {false, true}) {
stream.width = 1920; for (bool alr : {false, true}) {
stream.height = 1080; for (int min_padding : {0, 400000}) {
stream.max_framerate = 30; test_variants.push_back({screenshare, alr, min_padding});
stream.min_bitrate_bps = 60000; }
stream.target_bitrate_bps = 6000000; }
stream.max_bitrate_bps = 1250000; }
stream.num_temporal_layers = 2;
stream.max_qp = 56;
stream.bitrate_priority = 1;
int min_transmit_bitrate_bps = 400000; for (const TestConfig& test_config : test_variants) {
test_queue_.SendTask(
[this, test_config] {
const bool kSuspend = false;
config_.suspend_below_min_bitrate = kSuspend;
config_.rtp.extensions.emplace_back(
RtpExtension::kTransportSequenceNumberUri, 1);
config_.periodic_alr_bandwidth_probing = test_config.alr;
auto vss_impl = CreateVideoSendStreamImpl(
kDefaultInitialBitrateBps, kDefaultBitratePriority,
test_config.screenshare
? VideoEncoderConfig::ContentType::kScreen
: VideoEncoderConfig::ContentType::kRealtimeVideo);
vss_impl->Start();
config_.rtp.ssrcs.emplace_back(1); // Svc
config_.rtp.ssrcs.emplace_back(2); VideoStream stream;
stream.width = 1920;
stream.height = 1080;
stream.max_framerate = 30;
stream.min_bitrate_bps = 60000;
stream.target_bitrate_bps = 6000000;
stream.max_bitrate_bps = 1250000;
stream.num_temporal_layers = 2;
stream.max_qp = 56;
stream.bitrate_priority = 1;
EXPECT_CALL(bitrate_allocator_, AddObserver(vss_impl.get(), _)) config_.rtp.ssrcs.emplace_back(1);
.WillRepeatedly(Invoke([&](BitrateAllocatorObserver*, config_.rtp.ssrcs.emplace_back(2);
MediaStreamAllocationConfig config) {
EXPECT_EQ(config.min_bitrate_bps,
static_cast<uint32_t>(stream.min_bitrate_bps));
EXPECT_EQ(config.max_bitrate_bps,
static_cast<uint32_t>(stream.max_bitrate_bps));
if (config.pad_up_bitrate_bps != 0) {
EXPECT_EQ(config.pad_up_bitrate_bps,
static_cast<uint32_t>(min_transmit_bitrate_bps));
}
EXPECT_EQ(config.enforce_min_bitrate, !kSuspend);
}));
static_cast<VideoStreamEncoderInterface::EncoderSink*>(vss_impl.get()) EXPECT_CALL(
->OnEncoderConfigurationChanged( bitrate_allocator_,
std::vector<VideoStream>{stream}, true, AddObserver(
VideoEncoderConfig::ContentType::kScreen, vss_impl.get(),
min_transmit_bitrate_bps); AllOf(Field(&MediaStreamAllocationConfig::min_bitrate_bps,
vss_impl->Stop(); static_cast<uint32_t>(stream.min_bitrate_bps)),
}, Field(&MediaStreamAllocationConfig::max_bitrate_bps,
RTC_FROM_HERE); static_cast<uint32_t>(stream.max_bitrate_bps)),
} // Stream not yet active - no padding.
Field(&MediaStreamAllocationConfig::pad_up_bitrate_bps,
0u),
Field(&MediaStreamAllocationConfig::enforce_min_bitrate,
!kSuspend))));
TEST_F(VideoSendStreamImplTest, ConfiguresBitratesForSvcNoAlr) { static_cast<VideoStreamEncoderInterface::EncoderSink*>(vss_impl.get())
test_queue_.SendTask( ->OnEncoderConfigurationChanged(
[this] { std::vector<VideoStream>{stream}, true,
const bool kSuspend = false; test_config.screenshare
config_.suspend_below_min_bitrate = kSuspend; ? VideoEncoderConfig::ContentType::kScreen
config_.rtp.extensions.emplace_back( : VideoEncoderConfig::ContentType::kRealtimeVideo,
RtpExtension::kTransportSequenceNumberUri, 1); test_config.min_padding_bitrate_bps);
config_.periodic_alr_bandwidth_probing = false; ::testing::Mock::VerifyAndClearExpectations(&bitrate_allocator_);
auto vss_impl = CreateVideoSendStreamImpl(
kDefaultInitialBitrateBps, kDefaultBitratePriority,
VideoEncoderConfig::ContentType::kScreen);
vss_impl->Start();
// Svc // Simulate an encoded image, this will turn the stream active and
VideoStream stream; // enable padding.
stream.width = 1920; EncodedImage encoded_image;
stream.height = 1080; CodecSpecificInfo codec_specific;
stream.max_framerate = 30; EXPECT_CALL(rtp_video_sender_, OnEncodedImage)
stream.min_bitrate_bps = 60000; .WillRepeatedly(Return(EncodedImageCallback::Result(
stream.target_bitrate_bps = 6000000; EncodedImageCallback::Result::OK)));
stream.max_bitrate_bps = 1250000;
stream.num_temporal_layers = 2;
stream.max_qp = 56;
stream.bitrate_priority = 1;
int min_transmit_bitrate_bps = 400000; // Screensharing implicitly forces ALR.
const bool using_alr = test_config.alr || test_config.screenshare;
// If ALR is used, pads only to min bitrate as rampup is handled by
// probing. Otherwise target_bitrate contains the padding target.
int expected_padding =
using_alr ? stream.min_bitrate_bps : stream.target_bitrate_bps;
// Min padding bitrate may override padding target.
expected_padding =
std::max(expected_padding, test_config.min_padding_bitrate_bps);
EXPECT_CALL(
bitrate_allocator_,
AddObserver(
vss_impl.get(),
AllOf(Field(&MediaStreamAllocationConfig::min_bitrate_bps,
static_cast<uint32_t>(stream.min_bitrate_bps)),
Field(&MediaStreamAllocationConfig::max_bitrate_bps,
static_cast<uint32_t>(stream.max_bitrate_bps)),
// Stream now active - min bitrate use as padding target
// when ALR is active.
Field(&MediaStreamAllocationConfig::pad_up_bitrate_bps,
expected_padding),
Field(&MediaStreamAllocationConfig::enforce_min_bitrate,
!kSuspend))));
static_cast<EncodedImageCallback*>(vss_impl.get())
->OnEncodedImage(encoded_image, &codec_specific, nullptr);
::testing::Mock::VerifyAndClearExpectations(&bitrate_allocator_);
config_.rtp.ssrcs.emplace_back(1); vss_impl->Stop();
config_.rtp.ssrcs.emplace_back(2); },
RTC_FROM_HERE);
EXPECT_CALL(bitrate_allocator_, AddObserver(vss_impl.get(), _)) }
.WillRepeatedly(Invoke([&](BitrateAllocatorObserver*,
MediaStreamAllocationConfig config) {
EXPECT_EQ(config.min_bitrate_bps,
static_cast<uint32_t>(stream.min_bitrate_bps));
EXPECT_EQ(config.max_bitrate_bps,
static_cast<uint32_t>(stream.max_bitrate_bps));
if (config.pad_up_bitrate_bps != 0) {
EXPECT_EQ(config.pad_up_bitrate_bps,
static_cast<uint32_t>(stream.target_bitrate_bps));
}
EXPECT_EQ(config.enforce_min_bitrate, !kSuspend);
}));
static_cast<VideoStreamEncoderInterface::EncoderSink*>(vss_impl.get())
->OnEncoderConfigurationChanged(
std::vector<VideoStream>{stream}, true,
VideoEncoderConfig::ContentType::kScreen,
min_transmit_bitrate_bps);
vss_impl->Stop();
},
RTC_FROM_HERE);
} }
} // namespace internal } // namespace internal
} // namespace webrtc } // namespace webrtc