Layering and rate allocation for VP9 screen sharing.

- Two quality layers (same resolution, different bitrate).
- Max bitrate of low layer is limited to 200kbps. The choice of the
limit is driven by VP8 screen sharing which limits max bitrate of low
temporal layer to 200kbps. Using the same value for VP9 guarantees
that there will be no regressions for participants with limited
bandwidth.
- Max bitrate of high layer is limited to 500kbps. According to test
results this value is enough to get up to +5dB higher PSNR than VP8
SS provides on 1.2Mbps (max total bitrate for VP8 SS) link.
- Max total sent bitrate is limited to 700kbps. It is 500kbps lower
than that in VP8 SS (1200kbps).

Bug: webrtc:9261
Change-Id: I7919cc3933064664567c39e380a44cad0c65f1e8
Reviewed-on: https://webrtc-review.googlesource.com/76380
Commit-Queue: Sergey Silkin <ssilkin@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#23226}
This commit is contained in:
Sergey Silkin
2018-05-14 19:31:07 +02:00
committed by Commit Bot
parent 0019ff76ba
commit dfe8ca0d43
9 changed files with 201 additions and 81 deletions

View File

@ -56,7 +56,7 @@ void ConfigureSvc(VideoCodec* codec_settings) {
const std::vector<SpatialLayer> layers =
GetSvcConfig(codec_settings->width, codec_settings->height,
codec_settings->VP9()->numberOfSpatialLayers,
codec_settings->VP9()->numberOfTemporalLayers);
codec_settings->VP9()->numberOfTemporalLayers, false);
for (size_t i = 0; i < layers.size(); ++i) {
codec_settings->spatialLayers[i] = layers[i];

View File

@ -19,18 +19,38 @@
namespace webrtc {
namespace {
const int kMinVp9SvcBitrateKbps = 30; // Lowest VP9 video rate in kbps.
const size_t kMinVp9SvcBitrateKbps = 30;
const size_t kMaxNumLayersForScreenSharing = 2;
const size_t kMaxScreenSharingLayerBitrateKbps[] = {200, 500};
} // namespace
std::vector<SpatialLayer> GetSvcConfig(size_t input_width,
size_t input_height,
size_t num_spatial_layers,
size_t num_temporal_layers) {
RTC_DCHECK_GT(input_width, 0);
RTC_DCHECK_GT(input_height, 0);
RTC_DCHECK_GT(num_spatial_layers, 0);
RTC_DCHECK_GT(num_temporal_layers, 0);
std::vector<SpatialLayer> ConfigureSvcScreenSharing(size_t input_width,
size_t input_height,
size_t num_spatial_layers) {
num_spatial_layers =
std::min(num_spatial_layers, kMaxNumLayersForScreenSharing);
std::vector<SpatialLayer> spatial_layers;
for (size_t sl_idx = 0; sl_idx < num_spatial_layers; ++sl_idx) {
SpatialLayer spatial_layer = {0};
spatial_layer.width = input_width;
spatial_layer.height = input_height;
spatial_layer.numberOfTemporalLayers = 1;
spatial_layer.minBitrate = static_cast<int>(kMinVp9SvcBitrateKbps);
spatial_layer.maxBitrate =
static_cast<int>(kMaxScreenSharingLayerBitrateKbps[sl_idx]);
spatial_layer.targetBitrate = spatial_layer.maxBitrate;
spatial_layers.push_back(spatial_layer);
}
return spatial_layers;
}
std::vector<SpatialLayer> ConfigureSvcNormalVideo(size_t input_width,
size_t input_height,
size_t num_spatial_layers,
size_t num_temporal_layers) {
std::vector<SpatialLayer> spatial_layers;
// Limit number of layers for given resolution.
@ -57,7 +77,7 @@ std::vector<SpatialLayer> GetSvcConfig(size_t input_width,
// TODO(ssilkin): Add to the comment PSNR/SSIM we get at encoding certain
// video to min/max bitrate specified by those formulas.
const size_t num_pixels = spatial_layer.width * spatial_layer.height;
const int min_bitrate =
const size_t min_bitrate =
static_cast<int>((600. * std::sqrt(num_pixels) - 95000.) / 1000.);
spatial_layer.minBitrate = std::max(min_bitrate, kMinVp9SvcBitrateKbps);
spatial_layer.maxBitrate =
@ -71,4 +91,23 @@ std::vector<SpatialLayer> GetSvcConfig(size_t input_width,
return spatial_layers;
}
std::vector<SpatialLayer> GetSvcConfig(size_t input_width,
size_t input_height,
size_t num_spatial_layers,
size_t num_temporal_layers,
bool is_screen_sharing) {
RTC_DCHECK_GT(input_width, 0);
RTC_DCHECK_GT(input_height, 0);
RTC_DCHECK_GT(num_spatial_layers, 0);
RTC_DCHECK_GT(num_temporal_layers, 0);
if (is_screen_sharing) {
return ConfigureSvcScreenSharing(input_width, input_height,
num_spatial_layers);
} else {
return ConfigureSvcNormalVideo(input_width, input_height,
num_spatial_layers, num_temporal_layers);
}
}
} // namespace webrtc

View File

@ -19,7 +19,8 @@ namespace webrtc {
std::vector<SpatialLayer> GetSvcConfig(size_t input_width,
size_t input_height,
size_t num_spatial_layers,
size_t num_temporal_layers);
size_t num_temporal_layers,
bool is_screen_sharing);
} // namespace webrtc

View File

@ -24,7 +24,7 @@ TEST(SvcConfig, NumSpatialLayers) {
std::vector<SpatialLayer> spatial_layers =
GetSvcConfig(kMinVp9SpatialLayerWidth << (num_spatial_layers - 1),
kMinVp9SpatialLayerHeight << (num_spatial_layers - 1),
max_num_spatial_layers, 1);
max_num_spatial_layers, 1, false);
EXPECT_EQ(spatial_layers.size(), num_spatial_layers);
}
@ -34,7 +34,7 @@ TEST(SvcConfig, BitrateThresholds) {
std::vector<SpatialLayer> spatial_layers =
GetSvcConfig(kMinVp9SpatialLayerWidth << (num_spatial_layers - 1),
kMinVp9SpatialLayerHeight << (num_spatial_layers - 1),
num_spatial_layers, 1);
num_spatial_layers, 1, false);
EXPECT_EQ(spatial_layers.size(), num_spatial_layers);
@ -44,4 +44,20 @@ TEST(SvcConfig, BitrateThresholds) {
EXPECT_LE(layer.targetBitrate, layer.maxBitrate);
}
}
TEST(SvcConfig, ScreenSharing) {
std::vector<SpatialLayer> spatial_layers =
GetSvcConfig(1920, 1080, 3, 3, true);
EXPECT_EQ(spatial_layers.size(), 2UL);
for (const SpatialLayer& layer : spatial_layers) {
EXPECT_EQ(layer.width, 1920);
EXPECT_EQ(layer.height, 1080);
EXPECT_EQ(layer.numberOfTemporalLayers, 1);
EXPECT_LE(layer.minBitrate, layer.maxBitrate);
EXPECT_LE(layer.minBitrate, layer.targetBitrate);
EXPECT_LE(layer.targetBitrate, layer.maxBitrate);
}
}
} // namespace webrtc

View File

@ -30,72 +30,49 @@ SvcRateAllocator::SvcRateAllocator(const VideoCodec& codec) : codec_(codec) {
VideoBitrateAllocation SvcRateAllocator::GetAllocation(
uint32_t total_bitrate_bps,
uint32_t framerate_fps) {
VideoBitrateAllocation bitrate_allocation;
if (codec_.maxBitrate != 0) {
total_bitrate_bps = std::min(total_bitrate_bps, codec_.maxBitrate * 1000);
}
if (codec_.spatialLayers[0].targetBitrate == 0) {
// Delegate rate distribution to VP9 encoder wrapper if bitrate thresholds
// are not initialized.
VideoBitrateAllocation bitrate_allocation;
bitrate_allocation.SetBitrate(0, 0, total_bitrate_bps);
return bitrate_allocation;
} else if (codec_.mode == kRealtimeVideo) {
return GetAllocationNormalVideo(total_bitrate_bps);
} else {
return GetAllocationScreenSharing(total_bitrate_bps);
}
}
VideoBitrateAllocation SvcRateAllocator::GetAllocationNormalVideo(
uint32_t total_bitrate_bps) const {
size_t num_spatial_layers = codec_.VP9().numberOfSpatialLayers;
RTC_CHECK(num_spatial_layers > 0);
size_t num_temporal_layers = codec_.VP9().numberOfTemporalLayers;
RTC_CHECK(num_temporal_layers > 0);
if (codec_.maxBitrate != 0) {
total_bitrate_bps = std::min(total_bitrate_bps, codec_.maxBitrate * 1000);
}
if (codec_.mode == kScreensharing) {
// At screen sharing bitrate allocation is handled by VP9 encoder wrapper.
bitrate_allocation.SetBitrate(0, 0, total_bitrate_bps);
return bitrate_allocation;
}
std::vector<size_t> spatial_layer_bitrate_bps;
if (codec_.spatialLayers[0].maxBitrate == 0) {
// Layers' parameters are not initialized. Do simple split.
// Distribute total bitrate across spatial layers. If there is not enough
// bitrate to provide all layers with at least minimum required bitrate
// then number of layers is reduced by one and distribution is repeated
// until that condition is met or if number of layers is reduced to one.
for (;; --num_spatial_layers) {
spatial_layer_bitrate_bps =
SplitBitrate(num_spatial_layers, total_bitrate_bps,
kSpatialLayeringRateScalingFactor);
} else {
// Distribute total bitrate across spatial layers. If there is not enough
// bitrate to provide all layers with at least minimum required bitrate
// then number of layers is reduced by one and distribution is repeated
// until that condition is met or if number of layers is reduced to one.
for (;; --num_spatial_layers) {
spatial_layer_bitrate_bps =
SplitBitrate(num_spatial_layers, total_bitrate_bps,
kSpatialLayeringRateScalingFactor);
bool enough_bitrate = true;
size_t excess_rate = 0;
for (size_t sl_idx = 0; sl_idx < num_spatial_layers; ++sl_idx) {
RTC_DCHECK_GT(codec_.spatialLayers[sl_idx].maxBitrate, 0);
RTC_DCHECK_GE(codec_.spatialLayers[sl_idx].maxBitrate,
codec_.spatialLayers[sl_idx].minBitrate);
const size_t min_bitrate_bps =
codec_.spatialLayers[sl_idx].minBitrate * 1000;
const size_t max_bitrate_bps =
codec_.spatialLayers[sl_idx].maxBitrate * 1000;
spatial_layer_bitrate_bps[sl_idx] += excess_rate;
if (spatial_layer_bitrate_bps[sl_idx] < max_bitrate_bps) {
excess_rate = 0;
} else {
excess_rate = spatial_layer_bitrate_bps[sl_idx] - max_bitrate_bps;
spatial_layer_bitrate_bps[sl_idx] = max_bitrate_bps;
}
if (spatial_layer_bitrate_bps[sl_idx] < min_bitrate_bps) {
enough_bitrate = false;
break;
}
}
if (enough_bitrate || num_spatial_layers == 1) {
break;
}
const bool enough_bitrate = AdjustAndVerify(&spatial_layer_bitrate_bps);
if (enough_bitrate || num_spatial_layers == 1) {
break;
}
}
VideoBitrateAllocation bitrate_allocation;
for (size_t sl_idx = 0; sl_idx < num_spatial_layers; ++sl_idx) {
std::vector<size_t> temporal_layer_bitrate_bps =
SplitBitrate(num_temporal_layers, spatial_layer_bitrate_bps[sl_idx],
@ -125,13 +102,72 @@ VideoBitrateAllocation SvcRateAllocator::GetAllocation(
return bitrate_allocation;
}
bool SvcRateAllocator::AdjustAndVerify(
std::vector<size_t>* spatial_layer_bitrate_bps) const {
bool enough_bitrate = true;
size_t excess_rate = 0;
for (size_t sl_idx = 0;
sl_idx < spatial_layer_bitrate_bps->size() && enough_bitrate; ++sl_idx) {
RTC_DCHECK_GT(codec_.spatialLayers[sl_idx].maxBitrate, 0);
RTC_DCHECK_GE(codec_.spatialLayers[sl_idx].maxBitrate,
codec_.spatialLayers[sl_idx].minBitrate);
const size_t min_bitrate_bps =
codec_.spatialLayers[sl_idx].minBitrate * 1000;
const size_t max_bitrate_bps =
codec_.spatialLayers[sl_idx].maxBitrate * 1000;
spatial_layer_bitrate_bps->at(sl_idx) += excess_rate;
if (spatial_layer_bitrate_bps->at(sl_idx) < max_bitrate_bps) {
excess_rate = 0;
} else {
excess_rate = spatial_layer_bitrate_bps->at(sl_idx) - max_bitrate_bps;
spatial_layer_bitrate_bps->at(sl_idx) = max_bitrate_bps;
}
enough_bitrate = (spatial_layer_bitrate_bps->at(sl_idx) >= min_bitrate_bps);
}
return enough_bitrate;
}
VideoBitrateAllocation SvcRateAllocator::GetAllocationScreenSharing(
uint32_t total_bitrate_bps) const {
const size_t num_spatial_layers = codec_.VP9().numberOfSpatialLayers;
RTC_CHECK(num_spatial_layers > 0);
RTC_CHECK_EQ(codec_.VP9().numberOfTemporalLayers, 1U);
VideoBitrateAllocation bitrate_allocation;
// Add next layer after bitrate of previous layer has reached its maximum.
size_t left_bitrate_bps = total_bitrate_bps;
for (size_t sl_idx = 0; sl_idx < num_spatial_layers; ++sl_idx) {
const size_t min_bitrate_bps =
codec_.spatialLayers[sl_idx].minBitrate * 1000;
const size_t max_bitrate_bps =
codec_.spatialLayers[sl_idx].maxBitrate * 1000;
const size_t bitrate_bps = std::min(left_bitrate_bps, max_bitrate_bps);
if (bitrate_bps >= min_bitrate_bps) {
bitrate_allocation.SetBitrate(sl_idx, 0, bitrate_bps);
} else {
break;
}
left_bitrate_bps -= bitrate_bps;
}
return bitrate_allocation;
}
uint32_t SvcRateAllocator::GetPreferredBitrateBps(uint32_t framerate) {
return GetAllocation(codec_.maxBitrate * 1000, framerate).get_sum_bps();
}
std::vector<size_t> SvcRateAllocator::SplitBitrate(size_t num_layers,
size_t total_bitrate,
float rate_scaling_factor) {
std::vector<size_t> SvcRateAllocator::SplitBitrate(
size_t num_layers,
size_t total_bitrate,
float rate_scaling_factor) const {
std::vector<size_t> bitrates;
double denominator = 0.0;

View File

@ -28,9 +28,14 @@ class SvcRateAllocator : public VideoBitrateAllocator {
uint32_t GetPreferredBitrateBps(uint32_t framerate_fps) override;
private:
VideoBitrateAllocation GetAllocationNormalVideo(
uint32_t total_bitrate_bps) const;
VideoBitrateAllocation GetAllocationScreenSharing(
uint32_t total_bitrate_bps) const;
std::vector<size_t> SplitBitrate(size_t num_layers,
size_t total_bitrate,
float rate_scaling_factor);
float rate_scaling_factor) const;
bool AdjustAndVerify(std::vector<size_t>* spatial_layer_bitrate_bps) const;
const VideoCodec codec_;
};

View File

@ -19,14 +19,17 @@ namespace {
static VideoCodec Configure(size_t width,
size_t height,
size_t num_spatial_layers,
size_t num_temporal_layers) {
size_t num_temporal_layers,
bool is_screen_sharing) {
VideoCodec codec;
codec.width = width;
codec.height = height;
codec.codecType = kVideoCodecVP9;
codec.mode = is_screen_sharing ? kScreensharing : kRealtimeVideo;
std::vector<SpatialLayer> spatial_layers =
GetSvcConfig(width, height, num_spatial_layers, num_temporal_layers);
GetSvcConfig(width, height, num_spatial_layers, num_temporal_layers,
is_screen_sharing);
RTC_CHECK_LE(spatial_layers.size(), kMaxSpatialLayers);
codec.VP9()->numberOfSpatialLayers =
@ -43,7 +46,7 @@ static VideoCodec Configure(size_t width,
} // namespace
TEST(SvcRateAllocatorTest, SingleLayerFor320x180Input) {
VideoCodec codec = Configure(320, 180, 3, 3);
VideoCodec codec = Configure(320, 180, 3, 3, false);
SvcRateAllocator allocator = SvcRateAllocator(codec);
VideoBitrateAllocation allocation = allocator.GetAllocation(1000 * 1000, 30);
@ -53,7 +56,7 @@ TEST(SvcRateAllocatorTest, SingleLayerFor320x180Input) {
}
TEST(SvcRateAllocatorTest, TwoLayersFor640x360Input) {
VideoCodec codec = Configure(640, 360, 3, 3);
VideoCodec codec = Configure(640, 360, 3, 3, false);
SvcRateAllocator allocator = SvcRateAllocator(codec);
VideoBitrateAllocation allocation = allocator.GetAllocation(1000 * 1000, 30);
@ -64,7 +67,7 @@ TEST(SvcRateAllocatorTest, TwoLayersFor640x360Input) {
}
TEST(SvcRateAllocatorTest, ThreeLayersFor1280x720Input) {
VideoCodec codec = Configure(1280, 720, 3, 3);
VideoCodec codec = Configure(1280, 720, 3, 3, false);
SvcRateAllocator allocator = SvcRateAllocator(codec);
VideoBitrateAllocation allocation = allocator.GetAllocation(1000 * 1000, 30);
@ -76,7 +79,7 @@ TEST(SvcRateAllocatorTest, ThreeLayersFor1280x720Input) {
TEST(SvcRateAllocatorTest,
BaseLayerNonZeroBitrateEvenIfTotalIfLessThanMinimum) {
VideoCodec codec = Configure(1280, 720, 3, 3);
VideoCodec codec = Configure(1280, 720, 3, 3, false);
SvcRateAllocator allocator = SvcRateAllocator(codec);
const SpatialLayer* layers = codec.spatialLayers;
@ -90,7 +93,7 @@ TEST(SvcRateAllocatorTest,
}
TEST(SvcRateAllocatorTest, Disable640x360Layer) {
VideoCodec codec = Configure(1280, 720, 3, 3);
VideoCodec codec = Configure(1280, 720, 3, 3, false);
SvcRateAllocator allocator = SvcRateAllocator(codec);
const SpatialLayer* layers = codec.spatialLayers;
@ -106,7 +109,7 @@ TEST(SvcRateAllocatorTest, Disable640x360Layer) {
}
TEST(SvcRateAllocatorTest, Disable1280x720Layer) {
VideoCodec codec = Configure(1280, 720, 3, 3);
VideoCodec codec = Configure(1280, 720, 3, 3, false);
SvcRateAllocator allocator = SvcRateAllocator(codec);
const SpatialLayer* layers = codec.spatialLayers;
@ -123,7 +126,7 @@ TEST(SvcRateAllocatorTest, Disable1280x720Layer) {
}
TEST(SvcRateAllocatorTest, BitrateIsCapped) {
VideoCodec codec = Configure(1280, 720, 3, 3);
VideoCodec codec = Configure(1280, 720, 3, 3, false);
SvcRateAllocator allocator = SvcRateAllocator(codec);
const SpatialLayer* layers = codec.spatialLayers;
@ -139,4 +142,23 @@ TEST(SvcRateAllocatorTest, BitrateIsCapped) {
EXPECT_EQ(allocation.GetSpatialLayerSum(2) / 1000, layers[2].maxBitrate);
}
TEST(SvcRateAllocatorTest, MinBitrateToGetQualityLayer) {
VideoCodec codec = Configure(1280, 720, 3, 1, true);
SvcRateAllocator allocator = SvcRateAllocator(codec);
const SpatialLayer* layers = codec.spatialLayers;
EXPECT_LE(codec.VP9()->numberOfSpatialLayers, 2U);
VideoBitrateAllocation allocation =
allocator.GetAllocation(layers[0].minBitrate * 1000, 30);
EXPECT_EQ(allocation.GetSpatialLayerSum(0) / 1000, layers[0].minBitrate);
EXPECT_EQ(allocation.GetSpatialLayerSum(1), 0UL);
allocation = allocator.GetAllocation(
(layers[0].maxBitrate + layers[1].minBitrate) * 1000, 30);
EXPECT_EQ(allocation.GetSpatialLayerSum(0) / 1000, layers[0].maxBitrate);
EXPECT_EQ(allocation.GetSpatialLayerSum(1) / 1000, layers[1].minBitrate);
}
} // namespace webrtc

View File

@ -53,8 +53,9 @@ class TestVp9Impl : public VideoCodecUnitTest {
codec_settings_.VP9()->numberOfTemporalLayers = 1;
codec_settings_.VP9()->frameDroppingOn = false;
std::vector<SpatialLayer> layers = GetSvcConfig(
codec_settings_.width, codec_settings_.height, num_spatial_layers, 1);
std::vector<SpatialLayer> layers =
GetSvcConfig(codec_settings_.width, codec_settings_.height,
num_spatial_layers, 1, false);
for (size_t i = 0; i < layers.size(); ++i) {
codec_settings_.spatialLayers[i] = layers[i];
}