summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/modules/video_coding/utility/simulcast_rate_allocator.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/modules/video_coding/utility/simulcast_rate_allocator.cc')
-rw-r--r--third_party/libwebrtc/modules/video_coding/utility/simulcast_rate_allocator.cc343
1 files changed, 343 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/video_coding/utility/simulcast_rate_allocator.cc b/third_party/libwebrtc/modules/video_coding/utility/simulcast_rate_allocator.cc
new file mode 100644
index 0000000000..1496934e1c
--- /dev/null
+++ b/third_party/libwebrtc/modules/video_coding/utility/simulcast_rate_allocator.cc
@@ -0,0 +1,343 @@
+/*
+ * 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 <stdio.h>
+
+#include <algorithm>
+#include <cmath>
+#include <cstdint>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "rtc_base/checks.h"
+#include "rtc_base/experiments/rate_control_settings.h"
+#include "system_wrappers/include/field_trial.h"
+
+namespace webrtc {
+namespace {
+// Ratio allocation between temporal streams:
+// Values as required for the VP8 codec (accumulating).
+static const float
+ kLayerRateAllocation[kMaxTemporalStreams][kMaxTemporalStreams] = {
+ {1.0f, 1.0f, 1.0f, 1.0f}, // 1 layer
+ {0.6f, 1.0f, 1.0f, 1.0f}, // 2 layers {60%, 40%}
+ {0.4f, 0.6f, 1.0f, 1.0f}, // 3 layers {40%, 20%, 40%}
+ {0.25f, 0.4f, 0.6f, 1.0f} // 4 layers {25%, 15%, 20%, 40%}
+};
+
+static const float kBaseHeavy3TlRateAllocation[kMaxTemporalStreams] = {
+ 0.6f, 0.8f, 1.0f, 1.0f // 3 layers {60%, 20%, 20%}
+};
+
+const uint32_t kLegacyScreenshareTl0BitrateKbps = 200;
+const uint32_t kLegacyScreenshareTl1BitrateKbps = 1000;
+} // namespace
+
+float SimulcastRateAllocator::GetTemporalRateAllocation(
+ int num_layers,
+ int temporal_id,
+ bool base_heavy_tl3_alloc) {
+ RTC_CHECK_GT(num_layers, 0);
+ RTC_CHECK_LE(num_layers, kMaxTemporalStreams);
+ RTC_CHECK_GE(temporal_id, 0);
+ RTC_CHECK_LT(temporal_id, num_layers);
+ if (num_layers == 3 && base_heavy_tl3_alloc) {
+ return kBaseHeavy3TlRateAllocation[temporal_id];
+ }
+ return kLayerRateAllocation[num_layers - 1][temporal_id];
+}
+
+SimulcastRateAllocator::SimulcastRateAllocator(const VideoCodec& codec)
+ : codec_(codec),
+ stable_rate_settings_(StableTargetRateExperiment::ParseFromFieldTrials()),
+ rate_control_settings_(RateControlSettings::ParseFromFieldTrials()),
+ legacy_conference_mode_(false) {}
+
+SimulcastRateAllocator::~SimulcastRateAllocator() = default;
+
+VideoBitrateAllocation SimulcastRateAllocator::Allocate(
+ VideoBitrateAllocationParameters parameters) {
+ VideoBitrateAllocation allocated_bitrates;
+ DataRate stable_rate = parameters.total_bitrate;
+ if (stable_rate_settings_.IsEnabled() &&
+ parameters.stable_bitrate > DataRate::Zero()) {
+ stable_rate = std::min(parameters.stable_bitrate, parameters.total_bitrate);
+ }
+ DistributeAllocationToSimulcastLayers(parameters.total_bitrate, stable_rate,
+ &allocated_bitrates);
+ DistributeAllocationToTemporalLayers(&allocated_bitrates);
+ return allocated_bitrates;
+}
+
+void SimulcastRateAllocator::DistributeAllocationToSimulcastLayers(
+ DataRate total_bitrate,
+ DataRate stable_bitrate,
+ VideoBitrateAllocation* allocated_bitrates) {
+ DataRate left_in_total_allocation = total_bitrate;
+ DataRate left_in_stable_allocation = stable_bitrate;
+
+ if (codec_.maxBitrate) {
+ DataRate max_rate = DataRate::KilobitsPerSec(codec_.maxBitrate);
+ left_in_total_allocation = std::min(left_in_total_allocation, max_rate);
+ left_in_stable_allocation = std::min(left_in_stable_allocation, max_rate);
+ }
+
+ if (codec_.numberOfSimulcastStreams == 0) {
+ // No simulcast, just set the target as this has been capped already.
+ if (codec_.active) {
+ allocated_bitrates->SetBitrate(
+ 0, 0,
+ std::max(DataRate::KilobitsPerSec(codec_.minBitrate),
+ left_in_total_allocation)
+ .bps());
+ }
+ return;
+ }
+
+ // Sort the layers by maxFramerate, they might not always be from smallest
+ // to biggest
+ std::vector<size_t> layer_index(codec_.numberOfSimulcastStreams);
+ std::iota(layer_index.begin(), layer_index.end(), 0);
+ std::stable_sort(layer_index.begin(), layer_index.end(),
+ [this](size_t a, size_t b) {
+ return std::tie(codec_.simulcastStream[a].maxBitrate) <
+ std::tie(codec_.simulcastStream[b].maxBitrate);
+ });
+
+ // Find the first active layer. We don't allocate to inactive layers.
+ size_t active_layer = 0;
+ for (; active_layer < codec_.numberOfSimulcastStreams; ++active_layer) {
+ if (codec_.simulcastStream[layer_index[active_layer]].active) {
+ // Found the first active layer.
+ break;
+ }
+ }
+ // All streams could be inactive, and nothing more to do.
+ if (active_layer == codec_.numberOfSimulcastStreams) {
+ return;
+ }
+
+ // Always allocate enough bitrate for the minimum bitrate of the first
+ // active layer. Suspending below min bitrate is controlled outside the
+ // codec implementation and is not overridden by this.
+ DataRate min_rate = DataRate::KilobitsPerSec(
+ codec_.simulcastStream[layer_index[active_layer]].minBitrate);
+ left_in_total_allocation = std::max(left_in_total_allocation, min_rate);
+ left_in_stable_allocation = std::max(left_in_stable_allocation, min_rate);
+
+ // Begin by allocating bitrate to simulcast streams, putting all bitrate in
+ // temporal layer 0. We'll then distribute this bitrate, across potential
+ // temporal layers, when stream allocation is done.
+
+ bool first_allocation = false;
+ if (stream_enabled_.empty()) {
+ // First time allocating, this means we should not include hysteresis in
+ // case this is a reconfiguration of an existing enabled stream.
+ first_allocation = true;
+ stream_enabled_.resize(codec_.numberOfSimulcastStreams, false);
+ }
+
+ size_t top_active_layer = active_layer;
+ // Allocate up to the target bitrate for each active simulcast layer.
+ for (; active_layer < codec_.numberOfSimulcastStreams; ++active_layer) {
+ const SimulcastStream& stream =
+ codec_.simulcastStream[layer_index[active_layer]];
+ if (!stream.active) {
+ stream_enabled_[layer_index[active_layer]] = false;
+ continue;
+ }
+ // If we can't allocate to the current layer we can't allocate to higher
+ // layers because they require a higher minimum bitrate.
+ DataRate min_bitrate = DataRate::KilobitsPerSec(stream.minBitrate);
+ DataRate target_bitrate = DataRate::KilobitsPerSec(stream.targetBitrate);
+ double hysteresis_factor =
+ codec_.mode == VideoCodecMode::kRealtimeVideo
+ ? stable_rate_settings_.GetVideoHysteresisFactor()
+ : stable_rate_settings_.GetScreenshareHysteresisFactor();
+ if (!first_allocation && !stream_enabled_[layer_index[active_layer]]) {
+ min_bitrate = std::min(hysteresis_factor * min_bitrate, target_bitrate);
+ }
+ if (left_in_stable_allocation < min_bitrate) {
+ allocated_bitrates->set_bw_limited(true);
+ break;
+ }
+
+ // We are allocating to this layer so it is the current active allocation.
+ top_active_layer = layer_index[active_layer];
+ stream_enabled_[layer_index[active_layer]] = true;
+ DataRate layer_rate = std::min(left_in_total_allocation, target_bitrate);
+ allocated_bitrates->SetBitrate(layer_index[active_layer], 0,
+ layer_rate.bps());
+ left_in_total_allocation -= layer_rate;
+ left_in_stable_allocation -=
+ std::min(left_in_stable_allocation, target_bitrate);
+ }
+
+ // All layers above this one are not active.
+ for (; active_layer < codec_.numberOfSimulcastStreams; ++active_layer) {
+ stream_enabled_[layer_index[active_layer]] = false;
+ }
+
+ // Next, try allocate remaining bitrate, up to max bitrate, in top active
+ // stream.
+ // TODO(sprang): Allocate up to max bitrate for all layers once we have a
+ // better idea of possible performance implications.
+ if (left_in_total_allocation > DataRate::Zero()) {
+ const SimulcastStream& stream = codec_.simulcastStream[top_active_layer];
+ DataRate initial_layer_rate = DataRate::BitsPerSec(
+ allocated_bitrates->GetSpatialLayerSum(top_active_layer));
+ DataRate additional_allocation = std::min(
+ left_in_total_allocation,
+ DataRate::KilobitsPerSec(stream.maxBitrate) - initial_layer_rate);
+ allocated_bitrates->SetBitrate(
+ top_active_layer, 0,
+ (initial_layer_rate + additional_allocation).bps());
+ }
+}
+
+void SimulcastRateAllocator::DistributeAllocationToTemporalLayers(
+ VideoBitrateAllocation* allocated_bitrates_bps) const {
+ const int num_spatial_streams =
+ std::max(1, static_cast<int>(codec_.numberOfSimulcastStreams));
+
+ // Finally, distribute the bitrate for the simulcast streams across the
+ // available temporal layers.
+ for (int simulcast_id = 0; simulcast_id < num_spatial_streams;
+ ++simulcast_id) {
+ uint32_t target_bitrate_kbps =
+ allocated_bitrates_bps->GetBitrate(simulcast_id, 0) / 1000;
+ if (target_bitrate_kbps == 0) {
+ continue;
+ }
+
+ const uint32_t expected_allocated_bitrate_kbps = target_bitrate_kbps;
+ RTC_DCHECK_EQ(
+ target_bitrate_kbps,
+ allocated_bitrates_bps->GetSpatialLayerSum(simulcast_id) / 1000);
+ const int num_temporal_streams = NumTemporalStreams(simulcast_id);
+ uint32_t max_bitrate_kbps;
+ // Legacy temporal-layered only screenshare, or simulcast screenshare
+ // with legacy mode for simulcast stream 0.
+ if (codec_.mode == VideoCodecMode::kScreensharing &&
+ legacy_conference_mode_ && simulcast_id == 0) {
+ // TODO(holmer): This is a "temporary" hack for screensharing, where we
+ // interpret the startBitrate as the encoder target bitrate. This is
+ // to allow for a different max bitrate, so if the codec can't meet
+ // the target we still allow it to overshoot up to the max before dropping
+ // frames. This hack should be improved.
+ max_bitrate_kbps =
+ std::min(kLegacyScreenshareTl1BitrateKbps, target_bitrate_kbps);
+ target_bitrate_kbps =
+ std::min(kLegacyScreenshareTl0BitrateKbps, target_bitrate_kbps);
+ } else if (num_spatial_streams == 1) {
+ max_bitrate_kbps = codec_.maxBitrate;
+ } else {
+ max_bitrate_kbps = codec_.simulcastStream[simulcast_id].maxBitrate;
+ }
+
+ std::vector<uint32_t> tl_allocation;
+ if (num_temporal_streams == 1) {
+ tl_allocation.push_back(target_bitrate_kbps);
+ } else {
+ if (codec_.mode == VideoCodecMode::kScreensharing &&
+ legacy_conference_mode_ && simulcast_id == 0) {
+ tl_allocation = ScreenshareTemporalLayerAllocation(
+ target_bitrate_kbps, max_bitrate_kbps, simulcast_id);
+ } else {
+ tl_allocation = DefaultTemporalLayerAllocation(
+ target_bitrate_kbps, max_bitrate_kbps, simulcast_id);
+ }
+ }
+ RTC_DCHECK_GT(tl_allocation.size(), 0);
+ RTC_DCHECK_LE(tl_allocation.size(), num_temporal_streams);
+
+ uint64_t tl_allocation_sum_kbps = 0;
+ for (size_t tl_index = 0; tl_index < tl_allocation.size(); ++tl_index) {
+ uint32_t layer_rate_kbps = tl_allocation[tl_index];
+ if (layer_rate_kbps > 0) {
+ allocated_bitrates_bps->SetBitrate(simulcast_id, tl_index,
+ layer_rate_kbps * 1000);
+ }
+ tl_allocation_sum_kbps += layer_rate_kbps;
+ }
+ RTC_DCHECK_LE(tl_allocation_sum_kbps, expected_allocated_bitrate_kbps);
+ }
+}
+
+std::vector<uint32_t> SimulcastRateAllocator::DefaultTemporalLayerAllocation(
+ int bitrate_kbps,
+ int max_bitrate_kbps,
+ int simulcast_id) const {
+ const size_t num_temporal_layers = NumTemporalStreams(simulcast_id);
+ std::vector<uint32_t> bitrates;
+ for (size_t i = 0; i < num_temporal_layers; ++i) {
+ float layer_bitrate =
+ bitrate_kbps *
+ GetTemporalRateAllocation(
+ num_temporal_layers, i,
+ rate_control_settings_.Vp8BaseHeavyTl3RateAllocation());
+ bitrates.push_back(static_cast<uint32_t>(layer_bitrate + 0.5));
+ }
+
+ // Allocation table is of aggregates, transform to individual rates.
+ uint32_t sum = 0;
+ for (size_t i = 0; i < num_temporal_layers; ++i) {
+ uint32_t layer_bitrate = bitrates[i];
+ RTC_DCHECK_LE(sum, bitrates[i]);
+ bitrates[i] -= sum;
+ sum = layer_bitrate;
+
+ if (sum >= static_cast<uint32_t>(bitrate_kbps)) {
+ // Sum adds up; any subsequent layers will be 0.
+ bitrates.resize(i + 1);
+ break;
+ }
+ }
+
+ return bitrates;
+}
+
+std::vector<uint32_t>
+SimulcastRateAllocator::ScreenshareTemporalLayerAllocation(
+ int bitrate_kbps,
+ int max_bitrate_kbps,
+ int simulcast_id) const {
+ if (simulcast_id > 0) {
+ return DefaultTemporalLayerAllocation(bitrate_kbps, max_bitrate_kbps,
+ simulcast_id);
+ }
+ std::vector<uint32_t> allocation;
+ allocation.push_back(bitrate_kbps);
+ if (max_bitrate_kbps > bitrate_kbps)
+ allocation.push_back(max_bitrate_kbps - bitrate_kbps);
+ return allocation;
+}
+
+const VideoCodec& webrtc::SimulcastRateAllocator::GetCodec() const {
+ return codec_;
+}
+
+int SimulcastRateAllocator::NumTemporalStreams(size_t simulcast_id) const {
+ return std::max<uint8_t>(
+ 1,
+ codec_.codecType == kVideoCodecVP8 && codec_.numberOfSimulcastStreams == 0
+ ? codec_.VP8().numberOfTemporalLayers
+ : codec_.simulcastStream[simulcast_id].numberOfTemporalLayers);
+}
+
+void SimulcastRateAllocator::SetLegacyConferenceMode(bool enabled) {
+ legacy_conference_mode_ = enabled;
+}
+
+} // namespace webrtc