mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 22:00:47 +01:00

This changes the default behavior to use pacing factor of 1.1x instead of 2.5x, it also sets libvpx rate controler as trusted, turns on the encoder pushback mechanism and sets spatial hysteresis to 1.2. The unused "dynamic rate" settings in libvpx is removed. The new settings matches what has been used in chromium since 2019. If needed, the legacy behavior can be enabled using the field trial WebRTC-VideoRateControl. Bug: webrtc:10155 Change-Id: I8186b491aa5bef61e8f568e96c980ca68f0c208f Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/186661 Commit-Queue: Erik Språng <sprang@webrtc.org> Reviewed-by: Per Kjellander <perkj@webrtc.org> Reviewed-by: Christoffer Rodbro <crodbro@webrtc.org> Cr-Commit-Position: refs/heads/master@{#32477}
824 lines
31 KiB
C++
824 lines
31 KiB
C++
/*
|
|
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include "modules/video_coding/utility/simulcast_rate_allocator.h"
|
|
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "api/video_codecs/vp8_frame_buffer_controller.h"
|
|
#include "api/video_codecs/vp8_frame_config.h"
|
|
#include "api/video_codecs/vp8_temporal_layers.h"
|
|
#include "rtc_base/checks.h"
|
|
#include "test/field_trial.h"
|
|
#include "test/gmock.h"
|
|
#include "test/gtest.h"
|
|
|
|
namespace webrtc {
|
|
namespace {
|
|
using ::testing::_;
|
|
|
|
constexpr uint32_t kFramerateFps = 5;
|
|
constexpr uint32_t kMinBitrateKbps = 50;
|
|
// These correspond to kLegacyScreenshareTl(0|1)BitrateKbps in cc.
|
|
constexpr uint32_t kLegacyScreenshareTargetBitrateKbps = 200;
|
|
constexpr uint32_t kLegacyScreenshareMaxBitrateKbps = 1000;
|
|
// Bitrates for upper simulcast screenshare layer.
|
|
constexpr uint32_t kSimulcastScreenshareMinBitrateKbps = 600;
|
|
constexpr uint32_t kSimulcastScreenshareMaxBitrateKbps = 1250;
|
|
// Default video hysteresis factor: allocatable bitrate for next layer must
|
|
// exceed 20% of min setting in order to be initially turned on.
|
|
const double kDefaultHysteresis = 1.2;
|
|
|
|
class MockTemporalLayers : public Vp8FrameBufferController {
|
|
public:
|
|
MOCK_METHOD(Vp8FrameConfig, NextFrameConfig, (size_t, uint32_t), (override));
|
|
MOCK_METHOD(void,
|
|
OnRatesUpdated,
|
|
(size_t, const std::vector<uint32_t>&, int),
|
|
(override));
|
|
MOCK_METHOD(Vp8EncoderConfig, UpdateConfiguration, (size_t), (override));
|
|
MOCK_METHOD(void,
|
|
OnEncodeDone,
|
|
(size_t, uint32_t, size_t, bool, int, CodecSpecificInfo*),
|
|
(override));
|
|
};
|
|
} // namespace
|
|
|
|
class SimulcastRateAllocatorTest : public ::testing::TestWithParam<bool> {
|
|
public:
|
|
SimulcastRateAllocatorTest() {
|
|
codec_.codecType = kVideoCodecVP8;
|
|
codec_.minBitrate = kMinBitrateKbps;
|
|
codec_.maxBitrate = kLegacyScreenshareMaxBitrateKbps;
|
|
codec_.active = true;
|
|
CreateAllocator();
|
|
}
|
|
virtual ~SimulcastRateAllocatorTest() {}
|
|
|
|
template <size_t S>
|
|
void ExpectEqual(uint32_t (&expected)[S],
|
|
const std::vector<uint32_t>& actual) {
|
|
EXPECT_EQ(S, actual.size());
|
|
for (size_t i = 0; i < S; ++i)
|
|
EXPECT_EQ(expected[i], actual[i]) << "Mismatch at index " << i;
|
|
}
|
|
|
|
template <size_t S>
|
|
void ExpectEqual(uint32_t (&expected)[S],
|
|
const VideoBitrateAllocation& actual) {
|
|
// EXPECT_EQ(S, actual.size());
|
|
uint32_t sum = 0;
|
|
for (size_t i = 0; i < S; ++i) {
|
|
uint32_t layer_bitrate = actual.GetSpatialLayerSum(i);
|
|
if (layer_bitrate == 0) {
|
|
EXPECT_FALSE(actual.IsSpatialLayerUsed(i));
|
|
}
|
|
EXPECT_EQ(expected[i] * 1000U, layer_bitrate)
|
|
<< "Mismatch at index " << i;
|
|
sum += layer_bitrate;
|
|
}
|
|
EXPECT_EQ(sum, actual.get_sum_bps());
|
|
}
|
|
|
|
void CreateAllocator(bool legacy_conference_mode = false) {
|
|
allocator_.reset(new SimulcastRateAllocator(codec_));
|
|
allocator_->SetLegacyConferenceMode(legacy_conference_mode);
|
|
}
|
|
|
|
void SetupCodec3SL3TL(const std::vector<bool>& active_streams) {
|
|
const size_t num_simulcast_layers = 3;
|
|
RTC_DCHECK_GE(active_streams.size(), num_simulcast_layers);
|
|
SetupCodec2SL3TL(active_streams);
|
|
codec_.numberOfSimulcastStreams = num_simulcast_layers;
|
|
codec_.simulcastStream[2].numberOfTemporalLayers = 3;
|
|
codec_.simulcastStream[2].maxBitrate = 4000;
|
|
codec_.simulcastStream[2].targetBitrate = 3000;
|
|
codec_.simulcastStream[2].minBitrate = 2000;
|
|
codec_.simulcastStream[2].active = active_streams[2];
|
|
}
|
|
|
|
void SetupCodec2SL3TL(const std::vector<bool>& active_streams) {
|
|
const size_t num_simulcast_layers = 2;
|
|
RTC_DCHECK_GE(active_streams.size(), num_simulcast_layers);
|
|
SetupCodec1SL3TL(active_streams);
|
|
codec_.numberOfSimulcastStreams = num_simulcast_layers;
|
|
codec_.simulcastStream[1].numberOfTemporalLayers = 3;
|
|
codec_.simulcastStream[1].maxBitrate = 1000;
|
|
codec_.simulcastStream[1].targetBitrate = 500;
|
|
codec_.simulcastStream[1].minBitrate = 50;
|
|
codec_.simulcastStream[1].active = active_streams[1];
|
|
}
|
|
|
|
void SetupCodec1SL3TL(const std::vector<bool>& active_streams) {
|
|
const size_t num_simulcast_layers = 2;
|
|
RTC_DCHECK_GE(active_streams.size(), num_simulcast_layers);
|
|
SetupCodec3TL();
|
|
codec_.numberOfSimulcastStreams = num_simulcast_layers;
|
|
codec_.simulcastStream[0].numberOfTemporalLayers = 3;
|
|
codec_.simulcastStream[0].maxBitrate = 500;
|
|
codec_.simulcastStream[0].targetBitrate = 100;
|
|
codec_.simulcastStream[0].minBitrate = 10;
|
|
codec_.simulcastStream[0].active = active_streams[0];
|
|
}
|
|
|
|
void SetupCodec3TL() {
|
|
codec_.maxBitrate = 0;
|
|
codec_.VP8()->numberOfTemporalLayers = 3;
|
|
}
|
|
|
|
VideoBitrateAllocation GetAllocation(uint32_t target_bitrate) {
|
|
return allocator_->Allocate(VideoBitrateAllocationParameters(
|
|
DataRate::KilobitsPerSec(target_bitrate), kDefaultFrameRate));
|
|
}
|
|
|
|
VideoBitrateAllocation GetAllocation(DataRate target_rate,
|
|
DataRate stable_rate) {
|
|
return allocator_->Allocate(VideoBitrateAllocationParameters(
|
|
target_rate, stable_rate, kDefaultFrameRate));
|
|
}
|
|
|
|
DataRate MinRate(size_t layer_index) const {
|
|
return DataRate::KilobitsPerSec(
|
|
codec_.simulcastStream[layer_index].minBitrate);
|
|
}
|
|
|
|
DataRate TargetRate(size_t layer_index) const {
|
|
return DataRate::KilobitsPerSec(
|
|
codec_.simulcastStream[layer_index].targetBitrate);
|
|
}
|
|
|
|
DataRate MaxRate(size_t layer_index) const {
|
|
return DataRate::KilobitsPerSec(
|
|
codec_.simulcastStream[layer_index].maxBitrate);
|
|
}
|
|
|
|
protected:
|
|
static const int kDefaultFrameRate = 30;
|
|
VideoCodec codec_;
|
|
std::unique_ptr<SimulcastRateAllocator> allocator_;
|
|
};
|
|
|
|
TEST_F(SimulcastRateAllocatorTest, NoSimulcastBelowMin) {
|
|
uint32_t expected[] = {codec_.minBitrate};
|
|
codec_.active = true;
|
|
ExpectEqual(expected, GetAllocation(codec_.minBitrate - 1));
|
|
ExpectEqual(expected, GetAllocation(1));
|
|
ExpectEqual(expected, GetAllocation(0));
|
|
}
|
|
|
|
TEST_F(SimulcastRateAllocatorTest, NoSimulcastAboveMax) {
|
|
uint32_t expected[] = {codec_.maxBitrate};
|
|
codec_.active = true;
|
|
ExpectEqual(expected, GetAllocation(codec_.maxBitrate + 1));
|
|
ExpectEqual(expected, GetAllocation(std::numeric_limits<uint32_t>::max()));
|
|
}
|
|
|
|
TEST_F(SimulcastRateAllocatorTest, NoSimulcastNoMax) {
|
|
const uint32_t kMax = VideoBitrateAllocation::kMaxBitrateBps / 1000;
|
|
codec_.active = true;
|
|
codec_.maxBitrate = 0;
|
|
CreateAllocator();
|
|
|
|
uint32_t expected[] = {kMax};
|
|
ExpectEqual(expected, GetAllocation(kMax));
|
|
}
|
|
|
|
TEST_F(SimulcastRateAllocatorTest, NoSimulcastWithinLimits) {
|
|
codec_.active = true;
|
|
for (uint32_t bitrate = codec_.minBitrate; bitrate <= codec_.maxBitrate;
|
|
++bitrate) {
|
|
uint32_t expected[] = {bitrate};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
}
|
|
|
|
// Tests that when we aren't using simulcast and the codec is marked inactive no
|
|
// bitrate will be allocated.
|
|
TEST_F(SimulcastRateAllocatorTest, NoSimulcastInactive) {
|
|
codec_.active = false;
|
|
uint32_t expected[] = {0};
|
|
CreateAllocator();
|
|
|
|
ExpectEqual(expected, GetAllocation(kMinBitrateKbps - 10));
|
|
ExpectEqual(expected, GetAllocation(kLegacyScreenshareTargetBitrateKbps));
|
|
ExpectEqual(expected, GetAllocation(kLegacyScreenshareMaxBitrateKbps + 10));
|
|
}
|
|
|
|
TEST_F(SimulcastRateAllocatorTest, SingleSimulcastBelowMin) {
|
|
// With simulcast, use the min bitrate from the ss spec instead of the global.
|
|
codec_.numberOfSimulcastStreams = 1;
|
|
const uint32_t kMin = codec_.minBitrate - 10;
|
|
codec_.simulcastStream[0].minBitrate = kMin;
|
|
codec_.simulcastStream[0].targetBitrate = kLegacyScreenshareTargetBitrateKbps;
|
|
codec_.simulcastStream[0].active = true;
|
|
CreateAllocator();
|
|
|
|
uint32_t expected[] = {kMin};
|
|
ExpectEqual(expected, GetAllocation(kMin - 1));
|
|
ExpectEqual(expected, GetAllocation(1));
|
|
ExpectEqual(expected, GetAllocation(0));
|
|
}
|
|
|
|
TEST_F(SimulcastRateAllocatorTest, SignalsBwLimited) {
|
|
// Enough to enable all layers.
|
|
const int kVeryBigBitrate = 100000;
|
|
|
|
// With simulcast, use the min bitrate from the ss spec instead of the global.
|
|
SetupCodec3SL3TL({true, true, true});
|
|
CreateAllocator();
|
|
|
|
EXPECT_TRUE(
|
|
GetAllocation(codec_.simulcastStream[0].minBitrate - 10).is_bw_limited());
|
|
EXPECT_TRUE(
|
|
GetAllocation(codec_.simulcastStream[0].targetBitrate).is_bw_limited());
|
|
EXPECT_TRUE(GetAllocation(codec_.simulcastStream[0].targetBitrate +
|
|
codec_.simulcastStream[1].minBitrate)
|
|
.is_bw_limited());
|
|
EXPECT_FALSE(
|
|
GetAllocation(
|
|
codec_.simulcastStream[0].targetBitrate +
|
|
codec_.simulcastStream[1].targetBitrate +
|
|
static_cast<uint32_t>(
|
|
codec_.simulcastStream[2].minBitrate * kDefaultHysteresis + 0.5))
|
|
.is_bw_limited());
|
|
EXPECT_FALSE(GetAllocation(kVeryBigBitrate).is_bw_limited());
|
|
}
|
|
|
|
TEST_F(SimulcastRateAllocatorTest, SingleSimulcastAboveMax) {
|
|
codec_.numberOfSimulcastStreams = 1;
|
|
codec_.simulcastStream[0].minBitrate = kMinBitrateKbps;
|
|
const uint32_t kMax = codec_.simulcastStream[0].maxBitrate + 1000;
|
|
codec_.simulcastStream[0].maxBitrate = kMax;
|
|
codec_.simulcastStream[0].active = true;
|
|
CreateAllocator();
|
|
|
|
uint32_t expected[] = {kMax};
|
|
ExpectEqual(expected, GetAllocation(kMax));
|
|
ExpectEqual(expected, GetAllocation(kMax + 1));
|
|
ExpectEqual(expected, GetAllocation(std::numeric_limits<uint32_t>::max()));
|
|
}
|
|
|
|
TEST_F(SimulcastRateAllocatorTest, SingleSimulcastWithinLimits) {
|
|
codec_.numberOfSimulcastStreams = 1;
|
|
codec_.simulcastStream[0].minBitrate = kMinBitrateKbps;
|
|
codec_.simulcastStream[0].targetBitrate = kLegacyScreenshareTargetBitrateKbps;
|
|
codec_.simulcastStream[0].maxBitrate = kLegacyScreenshareMaxBitrateKbps;
|
|
codec_.simulcastStream[0].active = true;
|
|
CreateAllocator();
|
|
|
|
for (uint32_t bitrate = kMinBitrateKbps;
|
|
bitrate <= kLegacyScreenshareMaxBitrateKbps; ++bitrate) {
|
|
uint32_t expected[] = {bitrate};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
}
|
|
|
|
TEST_F(SimulcastRateAllocatorTest, Regular3TLTemporalRateAllocation) {
|
|
SetupCodec3SL3TL({true, true, true});
|
|
CreateAllocator();
|
|
|
|
const VideoBitrateAllocation alloc = GetAllocation(kMinBitrateKbps);
|
|
// 40/20/40.
|
|
EXPECT_EQ(static_cast<uint32_t>(0.4 * kMinBitrateKbps),
|
|
alloc.GetBitrate(0, 0) / 1000);
|
|
EXPECT_EQ(static_cast<uint32_t>(0.2 * kMinBitrateKbps),
|
|
alloc.GetBitrate(0, 1) / 1000);
|
|
EXPECT_EQ(static_cast<uint32_t>(0.4 * kMinBitrateKbps),
|
|
alloc.GetBitrate(0, 2) / 1000);
|
|
}
|
|
|
|
TEST_F(SimulcastRateAllocatorTest, BaseHeavy3TLTemporalRateAllocation) {
|
|
test::ScopedFieldTrials field_trials(
|
|
"WebRTC-UseBaseHeavyVP8TL3RateAllocation/Enabled/");
|
|
|
|
SetupCodec3SL3TL({true, true, true});
|
|
CreateAllocator();
|
|
|
|
const VideoBitrateAllocation alloc = GetAllocation(kMinBitrateKbps);
|
|
// 60/20/20.
|
|
EXPECT_EQ(static_cast<uint32_t>(0.6 * kMinBitrateKbps),
|
|
alloc.GetBitrate(0, 0) / 1000);
|
|
EXPECT_EQ(static_cast<uint32_t>(0.2 * kMinBitrateKbps),
|
|
alloc.GetBitrate(0, 1) / 1000);
|
|
EXPECT_EQ(static_cast<uint32_t>(0.2 * kMinBitrateKbps),
|
|
alloc.GetBitrate(0, 2) / 1000);
|
|
}
|
|
|
|
TEST_F(SimulcastRateAllocatorTest, SingleSimulcastInactive) {
|
|
codec_.numberOfSimulcastStreams = 1;
|
|
codec_.simulcastStream[0].minBitrate = kMinBitrateKbps;
|
|
codec_.simulcastStream[0].targetBitrate = kLegacyScreenshareTargetBitrateKbps;
|
|
codec_.simulcastStream[0].maxBitrate = kLegacyScreenshareMaxBitrateKbps;
|
|
codec_.simulcastStream[0].active = false;
|
|
CreateAllocator();
|
|
|
|
uint32_t expected[] = {0};
|
|
ExpectEqual(expected, GetAllocation(kMinBitrateKbps - 10));
|
|
ExpectEqual(expected, GetAllocation(kLegacyScreenshareTargetBitrateKbps));
|
|
ExpectEqual(expected, GetAllocation(kLegacyScreenshareMaxBitrateKbps + 10));
|
|
}
|
|
|
|
TEST_F(SimulcastRateAllocatorTest, OneToThreeStreams) {
|
|
SetupCodec3SL3TL({true, true, true});
|
|
CreateAllocator();
|
|
|
|
{
|
|
// Single stream, min bitrate.
|
|
const uint32_t bitrate = codec_.simulcastStream[0].minBitrate;
|
|
uint32_t expected[] = {bitrate, 0, 0};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
|
|
{
|
|
// Single stream at target bitrate.
|
|
const uint32_t bitrate = codec_.simulcastStream[0].targetBitrate;
|
|
uint32_t expected[] = {bitrate, 0, 0};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
|
|
uint32_t kMinInitialRateTwoLayers =
|
|
codec_.simulcastStream[0].targetBitrate +
|
|
static_cast<uint32_t>(codec_.simulcastStream[1].minBitrate *
|
|
kDefaultHysteresis);
|
|
{
|
|
// Bitrate above target for first stream, but below min for the next one.
|
|
const uint32_t bitrate = kMinInitialRateTwoLayers - 1;
|
|
uint32_t expected[] = {bitrate, 0, 0};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
|
|
{
|
|
// Just enough for two streams.
|
|
const uint32_t bitrate = kMinInitialRateTwoLayers;
|
|
uint32_t expected[] = {
|
|
codec_.simulcastStream[0].targetBitrate,
|
|
kMinInitialRateTwoLayers - codec_.simulcastStream[0].targetBitrate, 0};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
|
|
{
|
|
// Second stream maxed out, but not enough for third.
|
|
const uint32_t bitrate = codec_.simulcastStream[0].targetBitrate +
|
|
codec_.simulcastStream[1].maxBitrate;
|
|
uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate,
|
|
codec_.simulcastStream[1].maxBitrate, 0};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
|
|
uint32_t kMinInitialRateThreeLayers =
|
|
codec_.simulcastStream[0].targetBitrate +
|
|
codec_.simulcastStream[1].targetBitrate +
|
|
static_cast<uint32_t>(codec_.simulcastStream[2].minBitrate *
|
|
kDefaultHysteresis);
|
|
{
|
|
// First two streams maxed out, but not enough for third. Nowhere to put
|
|
// remaining bits.
|
|
const uint32_t bitrate = kMinInitialRateThreeLayers - 1;
|
|
uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate,
|
|
codec_.simulcastStream[1].maxBitrate, 0};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
|
|
{
|
|
// Just enough for all three streams.
|
|
const uint32_t bitrate = kMinInitialRateThreeLayers;
|
|
uint32_t expected[] = {
|
|
codec_.simulcastStream[0].targetBitrate,
|
|
codec_.simulcastStream[1].targetBitrate,
|
|
static_cast<uint32_t>(codec_.simulcastStream[2].minBitrate *
|
|
kDefaultHysteresis)};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
|
|
{
|
|
// Third maxed out.
|
|
const uint32_t bitrate = codec_.simulcastStream[0].targetBitrate +
|
|
codec_.simulcastStream[1].targetBitrate +
|
|
codec_.simulcastStream[2].maxBitrate;
|
|
uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate,
|
|
codec_.simulcastStream[1].targetBitrate,
|
|
codec_.simulcastStream[2].maxBitrate};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
|
|
{
|
|
// Enough to max out all streams which will allocate the target amount to
|
|
// the lower streams.
|
|
const uint32_t bitrate = codec_.simulcastStream[0].maxBitrate +
|
|
codec_.simulcastStream[1].maxBitrate +
|
|
codec_.simulcastStream[2].maxBitrate;
|
|
uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate,
|
|
codec_.simulcastStream[1].targetBitrate,
|
|
codec_.simulcastStream[2].maxBitrate};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
}
|
|
|
|
// If three simulcast streams that are all inactive, none of them should be
|
|
// allocated bitrate.
|
|
TEST_F(SimulcastRateAllocatorTest, ThreeStreamsInactive) {
|
|
SetupCodec3SL3TL({false, false, false});
|
|
CreateAllocator();
|
|
|
|
// Just enough to allocate the min.
|
|
const uint32_t min_bitrate = codec_.simulcastStream[0].minBitrate +
|
|
codec_.simulcastStream[1].minBitrate +
|
|
codec_.simulcastStream[2].minBitrate;
|
|
// Enough bitrate to allocate target to all streams.
|
|
const uint32_t target_bitrate = codec_.simulcastStream[0].targetBitrate +
|
|
codec_.simulcastStream[1].targetBitrate +
|
|
codec_.simulcastStream[2].targetBitrate;
|
|
// Enough bitrate to allocate max to all streams.
|
|
const uint32_t max_bitrate = codec_.simulcastStream[0].maxBitrate +
|
|
codec_.simulcastStream[1].maxBitrate +
|
|
codec_.simulcastStream[2].maxBitrate;
|
|
uint32_t expected[] = {0, 0, 0};
|
|
ExpectEqual(expected, GetAllocation(0));
|
|
ExpectEqual(expected, GetAllocation(min_bitrate));
|
|
ExpectEqual(expected, GetAllocation(target_bitrate));
|
|
ExpectEqual(expected, GetAllocation(max_bitrate));
|
|
}
|
|
|
|
// If there are two simulcast streams, we expect the high active stream to be
|
|
// allocated as if it is a single active stream.
|
|
TEST_F(SimulcastRateAllocatorTest, TwoStreamsLowInactive) {
|
|
SetupCodec2SL3TL({false, true});
|
|
CreateAllocator();
|
|
|
|
const uint32_t kActiveStreamMinBitrate = codec_.simulcastStream[1].minBitrate;
|
|
const uint32_t kActiveStreamTargetBitrate =
|
|
codec_.simulcastStream[1].targetBitrate;
|
|
const uint32_t kActiveStreamMaxBitrate = codec_.simulcastStream[1].maxBitrate;
|
|
{
|
|
// Expect that the stream is always allocated its min bitrate.
|
|
uint32_t expected[] = {0, kActiveStreamMinBitrate};
|
|
ExpectEqual(expected, GetAllocation(0));
|
|
ExpectEqual(expected, GetAllocation(kActiveStreamMinBitrate - 10));
|
|
ExpectEqual(expected, GetAllocation(kActiveStreamMinBitrate));
|
|
}
|
|
|
|
{
|
|
// The stream should be allocated its target bitrate.
|
|
uint32_t expected[] = {0, kActiveStreamTargetBitrate};
|
|
ExpectEqual(expected, GetAllocation(kActiveStreamTargetBitrate));
|
|
}
|
|
|
|
{
|
|
// The stream should be allocated its max if the target input is sufficient.
|
|
uint32_t expected[] = {0, kActiveStreamMaxBitrate};
|
|
ExpectEqual(expected, GetAllocation(kActiveStreamMaxBitrate));
|
|
ExpectEqual(expected, GetAllocation(std::numeric_limits<uint32_t>::max()));
|
|
}
|
|
}
|
|
|
|
// If there are two simulcast streams, we expect the low active stream to be
|
|
// allocated as if it is a single active stream.
|
|
TEST_F(SimulcastRateAllocatorTest, TwoStreamsHighInactive) {
|
|
SetupCodec2SL3TL({true, false});
|
|
CreateAllocator();
|
|
|
|
const uint32_t kActiveStreamMinBitrate = codec_.simulcastStream[0].minBitrate;
|
|
const uint32_t kActiveStreamTargetBitrate =
|
|
codec_.simulcastStream[0].targetBitrate;
|
|
const uint32_t kActiveStreamMaxBitrate = codec_.simulcastStream[0].maxBitrate;
|
|
{
|
|
// Expect that the stream is always allocated its min bitrate.
|
|
uint32_t expected[] = {kActiveStreamMinBitrate, 0};
|
|
ExpectEqual(expected, GetAllocation(0));
|
|
ExpectEqual(expected, GetAllocation(kActiveStreamMinBitrate - 10));
|
|
ExpectEqual(expected, GetAllocation(kActiveStreamMinBitrate));
|
|
}
|
|
|
|
{
|
|
// The stream should be allocated its target bitrate.
|
|
uint32_t expected[] = {kActiveStreamTargetBitrate, 0};
|
|
ExpectEqual(expected, GetAllocation(kActiveStreamTargetBitrate));
|
|
}
|
|
|
|
{
|
|
// The stream should be allocated its max if the target input is sufficent.
|
|
uint32_t expected[] = {kActiveStreamMaxBitrate, 0};
|
|
ExpectEqual(expected, GetAllocation(kActiveStreamMaxBitrate));
|
|
ExpectEqual(expected, GetAllocation(std::numeric_limits<uint32_t>::max()));
|
|
}
|
|
}
|
|
|
|
// If there are three simulcast streams and the middle stream is inactive, the
|
|
// other two streams should be allocated bitrate the same as if they are two
|
|
// active simulcast streams.
|
|
TEST_F(SimulcastRateAllocatorTest, ThreeStreamsMiddleInactive) {
|
|
SetupCodec3SL3TL({true, false, true});
|
|
CreateAllocator();
|
|
|
|
{
|
|
const uint32_t kLowStreamMinBitrate = codec_.simulcastStream[0].minBitrate;
|
|
// The lowest stream should always be allocated its minimum bitrate.
|
|
uint32_t expected[] = {kLowStreamMinBitrate, 0, 0};
|
|
ExpectEqual(expected, GetAllocation(0));
|
|
ExpectEqual(expected, GetAllocation(kLowStreamMinBitrate - 10));
|
|
ExpectEqual(expected, GetAllocation(kLowStreamMinBitrate));
|
|
}
|
|
|
|
{
|
|
// The lowest stream gets its target bitrate.
|
|
uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate, 0, 0};
|
|
ExpectEqual(expected,
|
|
GetAllocation(codec_.simulcastStream[0].targetBitrate));
|
|
}
|
|
|
|
{
|
|
// The lowest stream gets its max bitrate, but not enough for the high
|
|
// stream.
|
|
const uint32_t bitrate = codec_.simulcastStream[0].targetBitrate +
|
|
codec_.simulcastStream[2].minBitrate - 1;
|
|
uint32_t expected[] = {codec_.simulcastStream[0].maxBitrate, 0, 0};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
|
|
{
|
|
// Both active streams get allocated target bitrate.
|
|
const uint32_t bitrate = codec_.simulcastStream[0].targetBitrate +
|
|
codec_.simulcastStream[2].targetBitrate;
|
|
uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate, 0,
|
|
codec_.simulcastStream[2].targetBitrate};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
|
|
{
|
|
// Lowest stream gets its target bitrate, high stream gets its max bitrate.
|
|
uint32_t bitrate = codec_.simulcastStream[0].targetBitrate +
|
|
codec_.simulcastStream[2].maxBitrate;
|
|
uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate, 0,
|
|
codec_.simulcastStream[2].maxBitrate};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
ExpectEqual(expected, GetAllocation(bitrate + 10));
|
|
ExpectEqual(expected, GetAllocation(std::numeric_limits<uint32_t>::max()));
|
|
}
|
|
}
|
|
|
|
TEST_F(SimulcastRateAllocatorTest, NonConferenceModeScreenshare) {
|
|
codec_.mode = VideoCodecMode::kScreensharing;
|
|
SetupCodec3SL3TL({true, true, true});
|
|
CreateAllocator();
|
|
|
|
// Make sure we have enough bitrate for all 3 simulcast layers
|
|
const uint32_t bitrate = codec_.simulcastStream[0].maxBitrate +
|
|
codec_.simulcastStream[1].maxBitrate +
|
|
codec_.simulcastStream[2].maxBitrate;
|
|
const VideoBitrateAllocation alloc = GetAllocation(bitrate);
|
|
|
|
EXPECT_EQ(alloc.GetTemporalLayerAllocation(0).size(), 3u);
|
|
EXPECT_EQ(alloc.GetTemporalLayerAllocation(1).size(), 3u);
|
|
EXPECT_EQ(alloc.GetTemporalLayerAllocation(2).size(), 3u);
|
|
}
|
|
|
|
TEST_F(SimulcastRateAllocatorTest, StableRate) {
|
|
webrtc::test::ScopedFieldTrials field_trials(
|
|
"WebRTC-StableTargetRate/"
|
|
"enabled:true,"
|
|
"video_hysteresis_factor:1.1/");
|
|
|
|
SetupCodec3SL3TL({true, true, true});
|
|
CreateAllocator();
|
|
|
|
// Let the volatile rate always be be enough for all streams, in this test we
|
|
// are only interested in how the stable rate affects enablement.
|
|
const DataRate volatile_rate =
|
|
(TargetRate(0) + TargetRate(1) + MinRate(2)) * 1.1;
|
|
|
|
{
|
|
// On the first call to a new SimulcastRateAllocator instance, hysteresis
|
|
// is disabled, but stable rate still caps layers.
|
|
uint32_t expected[] = {TargetRate(0).kbps<uint32_t>(),
|
|
MaxRate(1).kbps<uint32_t>()};
|
|
ExpectEqual(expected,
|
|
GetAllocation(volatile_rate, TargetRate(0) + MinRate(1)));
|
|
}
|
|
|
|
{
|
|
// Let stable rate go to a bitrate below what is needed for two streams.
|
|
uint32_t expected[] = {MaxRate(0).kbps<uint32_t>(), 0};
|
|
ExpectEqual(expected,
|
|
GetAllocation(volatile_rate, TargetRate(0) + MinRate(1) -
|
|
DataRate::BitsPerSec(1)));
|
|
}
|
|
|
|
{
|
|
// Don't enable stream as we need to get up above hysteresis threshold.
|
|
uint32_t expected[] = {MaxRate(0).kbps<uint32_t>(), 0};
|
|
ExpectEqual(expected,
|
|
GetAllocation(volatile_rate, TargetRate(0) + MinRate(1)));
|
|
}
|
|
|
|
{
|
|
// Above threshold with hysteresis, enable second stream.
|
|
uint32_t expected[] = {TargetRate(0).kbps<uint32_t>(),
|
|
MaxRate(1).kbps<uint32_t>()};
|
|
ExpectEqual(expected, GetAllocation(volatile_rate,
|
|
(TargetRate(0) + MinRate(1)) * 1.1));
|
|
}
|
|
|
|
{
|
|
// Enough to enable all thee layers.
|
|
uint32_t expected[] = {
|
|
TargetRate(0).kbps<uint32_t>(), TargetRate(1).kbps<uint32_t>(),
|
|
(volatile_rate - TargetRate(0) - TargetRate(1)).kbps<uint32_t>()};
|
|
ExpectEqual(expected, GetAllocation(volatile_rate, volatile_rate));
|
|
}
|
|
|
|
{
|
|
// Drop hysteresis, all three still on.
|
|
uint32_t expected[] = {
|
|
TargetRate(0).kbps<uint32_t>(), TargetRate(1).kbps<uint32_t>(),
|
|
(volatile_rate - TargetRate(0) - TargetRate(1)).kbps<uint32_t>()};
|
|
ExpectEqual(expected,
|
|
GetAllocation(volatile_rate,
|
|
TargetRate(0) + TargetRate(1) + MinRate(2)));
|
|
}
|
|
}
|
|
|
|
class ScreenshareRateAllocationTest : public SimulcastRateAllocatorTest {
|
|
public:
|
|
void SetupConferenceScreenshare(bool use_simulcast, bool active = true) {
|
|
codec_.mode = VideoCodecMode::kScreensharing;
|
|
codec_.minBitrate = kMinBitrateKbps;
|
|
codec_.maxBitrate =
|
|
kLegacyScreenshareMaxBitrateKbps + kSimulcastScreenshareMaxBitrateKbps;
|
|
if (use_simulcast) {
|
|
codec_.numberOfSimulcastStreams = 2;
|
|
codec_.simulcastStream[0].minBitrate = kMinBitrateKbps;
|
|
codec_.simulcastStream[0].targetBitrate =
|
|
kLegacyScreenshareTargetBitrateKbps;
|
|
codec_.simulcastStream[0].maxBitrate = kLegacyScreenshareMaxBitrateKbps;
|
|
codec_.simulcastStream[0].numberOfTemporalLayers = 2;
|
|
codec_.simulcastStream[0].active = active;
|
|
|
|
codec_.simulcastStream[1].minBitrate =
|
|
kSimulcastScreenshareMinBitrateKbps;
|
|
codec_.simulcastStream[1].targetBitrate =
|
|
kSimulcastScreenshareMaxBitrateKbps;
|
|
codec_.simulcastStream[1].maxBitrate =
|
|
kSimulcastScreenshareMaxBitrateKbps;
|
|
codec_.simulcastStream[1].numberOfTemporalLayers = 2;
|
|
codec_.simulcastStream[1].active = active;
|
|
} else {
|
|
codec_.numberOfSimulcastStreams = 0;
|
|
codec_.VP8()->numberOfTemporalLayers = 2;
|
|
codec_.active = active;
|
|
}
|
|
}
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(ScreenshareTest,
|
|
ScreenshareRateAllocationTest,
|
|
::testing::Bool());
|
|
|
|
TEST_P(ScreenshareRateAllocationTest, ConferenceBitrateBelowTl0) {
|
|
SetupConferenceScreenshare(GetParam());
|
|
CreateAllocator(true);
|
|
|
|
VideoBitrateAllocation allocation =
|
|
allocator_->Allocate(VideoBitrateAllocationParameters(
|
|
kLegacyScreenshareTargetBitrateKbps * 1000, kFramerateFps));
|
|
|
|
// All allocation should go in TL0.
|
|
EXPECT_EQ(kLegacyScreenshareTargetBitrateKbps, allocation.get_sum_kbps());
|
|
EXPECT_EQ(kLegacyScreenshareTargetBitrateKbps,
|
|
allocation.GetBitrate(0, 0) / 1000);
|
|
EXPECT_EQ(allocation.is_bw_limited(), GetParam());
|
|
}
|
|
|
|
TEST_P(ScreenshareRateAllocationTest, ConferenceBitrateAboveTl0) {
|
|
SetupConferenceScreenshare(GetParam());
|
|
CreateAllocator(true);
|
|
|
|
uint32_t target_bitrate_kbps =
|
|
(kLegacyScreenshareTargetBitrateKbps + kLegacyScreenshareMaxBitrateKbps) /
|
|
2;
|
|
VideoBitrateAllocation allocation =
|
|
allocator_->Allocate(VideoBitrateAllocationParameters(
|
|
target_bitrate_kbps * 1000, kFramerateFps));
|
|
|
|
// Fill TL0, then put the rest in TL1.
|
|
EXPECT_EQ(target_bitrate_kbps, allocation.get_sum_kbps());
|
|
EXPECT_EQ(kLegacyScreenshareTargetBitrateKbps,
|
|
allocation.GetBitrate(0, 0) / 1000);
|
|
EXPECT_EQ(target_bitrate_kbps - kLegacyScreenshareTargetBitrateKbps,
|
|
allocation.GetBitrate(0, 1) / 1000);
|
|
EXPECT_EQ(allocation.is_bw_limited(), GetParam());
|
|
}
|
|
|
|
TEST_F(ScreenshareRateAllocationTest, ConferenceBitrateAboveTl1) {
|
|
// This test is only for the non-simulcast case.
|
|
SetupConferenceScreenshare(false);
|
|
CreateAllocator(true);
|
|
|
|
VideoBitrateAllocation allocation =
|
|
allocator_->Allocate(VideoBitrateAllocationParameters(
|
|
kLegacyScreenshareMaxBitrateKbps * 2000, kFramerateFps));
|
|
|
|
// Fill both TL0 and TL1, but no more.
|
|
EXPECT_EQ(kLegacyScreenshareMaxBitrateKbps, allocation.get_sum_kbps());
|
|
EXPECT_EQ(kLegacyScreenshareTargetBitrateKbps,
|
|
allocation.GetBitrate(0, 0) / 1000);
|
|
EXPECT_EQ(
|
|
kLegacyScreenshareMaxBitrateKbps - kLegacyScreenshareTargetBitrateKbps,
|
|
allocation.GetBitrate(0, 1) / 1000);
|
|
EXPECT_FALSE(allocation.is_bw_limited());
|
|
}
|
|
|
|
// This tests when the screenshare is inactive it should be allocated 0 bitrate
|
|
// for all layers.
|
|
TEST_P(ScreenshareRateAllocationTest, InactiveScreenshare) {
|
|
SetupConferenceScreenshare(GetParam(), false);
|
|
CreateAllocator();
|
|
|
|
// Enough bitrate for TL0 and TL1.
|
|
uint32_t target_bitrate_kbps =
|
|
(kLegacyScreenshareTargetBitrateKbps + kLegacyScreenshareMaxBitrateKbps) /
|
|
2;
|
|
VideoBitrateAllocation allocation =
|
|
allocator_->Allocate(VideoBitrateAllocationParameters(
|
|
target_bitrate_kbps * 1000, kFramerateFps));
|
|
|
|
EXPECT_EQ(0U, allocation.get_sum_kbps());
|
|
}
|
|
|
|
TEST_F(ScreenshareRateAllocationTest, Hysteresis) {
|
|
// This test is only for the simulcast case.
|
|
SetupConferenceScreenshare(true);
|
|
CreateAllocator();
|
|
|
|
// The bitrate at which we would normally enable the upper simulcast stream.
|
|
const uint32_t default_enable_rate_bps =
|
|
codec_.simulcastStream[0].targetBitrate +
|
|
codec_.simulcastStream[1].minBitrate;
|
|
const uint32_t enable_rate_with_hysteresis_bps =
|
|
(default_enable_rate_bps * 135) / 100;
|
|
|
|
{
|
|
// On the first call to a new SimulcastRateAllocator instance, hysteresis
|
|
// is disabled.
|
|
const uint32_t bitrate = default_enable_rate_bps;
|
|
uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate,
|
|
codec_.simulcastStream[1].minBitrate};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
|
|
{
|
|
// Go down to a bitrate below what is needed for two streams.
|
|
const uint32_t bitrate = default_enable_rate_bps - 1;
|
|
uint32_t expected[] = {bitrate, 0};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
|
|
{
|
|
// Don't enable stream as we need to get up above hysteresis threshold.
|
|
const uint32_t bitrate = default_enable_rate_bps;
|
|
uint32_t expected[] = {bitrate, 0};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
|
|
{
|
|
// Above threshold, enable second stream.
|
|
const uint32_t bitrate = enable_rate_with_hysteresis_bps;
|
|
uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate,
|
|
enable_rate_with_hysteresis_bps -
|
|
codec_.simulcastStream[0].targetBitrate};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
|
|
{
|
|
// Go down again, still keep the second stream alive.
|
|
const uint32_t bitrate = default_enable_rate_bps;
|
|
uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate,
|
|
codec_.simulcastStream[1].minBitrate};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
|
|
{
|
|
// Go down below default enable, second stream is shut down again.
|
|
const uint32_t bitrate = default_enable_rate_bps - 1;
|
|
uint32_t expected[] = {bitrate, 0};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
|
|
{
|
|
// Go up, hysteresis is blocking us again.
|
|
const uint32_t bitrate = default_enable_rate_bps;
|
|
uint32_t expected[] = {bitrate, 0};
|
|
ExpectEqual(expected, GetAllocation(bitrate));
|
|
}
|
|
}
|
|
|
|
} // namespace webrtc
|