diff options
Diffstat (limited to '')
-rw-r--r-- | third_party/libwebrtc/modules/video_coding/utility/simulcast_rate_allocator.cc | 343 |
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 |