| /* |
| * Copyright (c) 2018 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/svc/svc_rate_allocator.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <cstddef> |
| #include <numeric> |
| #include <vector> |
| |
| #include "absl/container/inlined_vector.h" |
| #include "modules/video_coding/svc/create_scalability_structure.h" |
| #include "rtc_base/checks.h" |
| |
| namespace webrtc { |
| namespace { |
| |
| constexpr float kSpatialLayeringRateScalingFactor = 0.55f; |
| constexpr float kTemporalLayeringRateScalingFactor = 0.55f; |
| |
| struct ActiveSpatialLayers { |
| size_t first = 0; |
| size_t num = 0; |
| }; |
| |
| ActiveSpatialLayers GetActiveSpatialLayers(const VideoCodec& codec, |
| size_t num_spatial_layers) { |
| ActiveSpatialLayers active; |
| for (active.first = 0; active.first < num_spatial_layers; ++active.first) { |
| if (codec.spatialLayers[active.first].active) { |
| break; |
| } |
| } |
| |
| size_t last_active_layer = active.first; |
| for (; last_active_layer < num_spatial_layers; ++last_active_layer) { |
| if (!codec.spatialLayers[last_active_layer].active) { |
| break; |
| } |
| } |
| active.num = last_active_layer - active.first; |
| |
| return active; |
| } |
| |
| std::vector<DataRate> AdjustAndVerify( |
| const VideoCodec& codec, |
| size_t first_active_layer, |
| const std::vector<DataRate>& spatial_layer_rates) { |
| std::vector<DataRate> adjusted_spatial_layer_rates; |
| // Keep track of rate that couldn't be applied to the previous layer due to |
| // max bitrate constraint, try to pass it forward to the next one. |
| DataRate excess_rate = DataRate::Zero(); |
| for (size_t sl_idx = 0; sl_idx < spatial_layer_rates.size(); ++sl_idx) { |
| DataRate min_rate = DataRate::KilobitsPerSec( |
| codec.spatialLayers[first_active_layer + sl_idx].minBitrate); |
| DataRate max_rate = DataRate::KilobitsPerSec( |
| codec.spatialLayers[first_active_layer + sl_idx].maxBitrate); |
| |
| DataRate layer_rate = spatial_layer_rates[sl_idx] + excess_rate; |
| if (layer_rate < min_rate) { |
| // Not enough rate to reach min bitrate for desired number of layers, |
| // abort allocation. |
| if (spatial_layer_rates.size() == 1) { |
| return spatial_layer_rates; |
| } |
| return adjusted_spatial_layer_rates; |
| } |
| |
| if (layer_rate <= max_rate) { |
| excess_rate = DataRate::Zero(); |
| adjusted_spatial_layer_rates.push_back(layer_rate); |
| } else { |
| excess_rate = layer_rate - max_rate; |
| adjusted_spatial_layer_rates.push_back(max_rate); |
| } |
| } |
| |
| return adjusted_spatial_layer_rates; |
| } |
| |
| static std::vector<DataRate> SplitBitrate(size_t num_layers, |
| DataRate total_bitrate, |
| float rate_scaling_factor) { |
| std::vector<DataRate> bitrates; |
| |
| double denominator = 0.0; |
| for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) { |
| denominator += std::pow(rate_scaling_factor, layer_idx); |
| } |
| |
| double numerator = std::pow(rate_scaling_factor, num_layers - 1); |
| for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) { |
| bitrates.push_back(numerator * total_bitrate / denominator); |
| numerator /= rate_scaling_factor; |
| } |
| |
| const DataRate sum = |
| std::accumulate(bitrates.begin(), bitrates.end(), DataRate::Zero()); |
| |
| // Keep the sum of split bitrates equal to the total bitrate by adding or |
| // subtracting bits, which were lost due to rounding, to the latest layer. |
| if (total_bitrate > sum) { |
| bitrates.back() += total_bitrate - sum; |
| } else if (total_bitrate < sum) { |
| bitrates.back() -= sum - total_bitrate; |
| } |
| |
| return bitrates; |
| } |
| |
| // Returns the minimum bitrate needed for `num_active_layers` spatial layers to |
| // become active using the configuration specified by `codec`. |
| DataRate FindLayerTogglingThreshold(const VideoCodec& codec, |
| size_t first_active_layer, |
| size_t num_active_layers) { |
| if (num_active_layers == 1) { |
| return DataRate::KilobitsPerSec(codec.spatialLayers[0].minBitrate); |
| } |
| |
| if (codec.mode == VideoCodecMode::kRealtimeVideo) { |
| DataRate lower_bound = DataRate::Zero(); |
| DataRate upper_bound = DataRate::Zero(); |
| if (num_active_layers > 1) { |
| for (size_t i = 0; i < num_active_layers - 1; ++i) { |
| lower_bound += DataRate::KilobitsPerSec( |
| codec.spatialLayers[first_active_layer + i].minBitrate); |
| upper_bound += DataRate::KilobitsPerSec( |
| codec.spatialLayers[first_active_layer + i].maxBitrate); |
| } |
| } |
| upper_bound += DataRate::KilobitsPerSec( |
| codec.spatialLayers[first_active_layer + num_active_layers - 1] |
| .minBitrate); |
| |
| // Do a binary search until upper and lower bound is the highest bitrate for |
| // `num_active_layers` - 1 layers and lowest bitrate for `num_active_layers` |
| // layers respectively. |
| while (upper_bound - lower_bound > DataRate::BitsPerSec(1)) { |
| DataRate try_rate = (lower_bound + upper_bound) / 2; |
| if (AdjustAndVerify(codec, first_active_layer, |
| SplitBitrate(num_active_layers, try_rate, |
| kSpatialLayeringRateScalingFactor)) |
| .size() == num_active_layers) { |
| upper_bound = try_rate; |
| } else { |
| lower_bound = try_rate; |
| } |
| } |
| return upper_bound; |
| } else { |
| DataRate toggling_rate = DataRate::Zero(); |
| for (size_t i = 0; i < num_active_layers - 1; ++i) { |
| toggling_rate += DataRate::KilobitsPerSec( |
| codec.spatialLayers[first_active_layer + i].targetBitrate); |
| } |
| toggling_rate += DataRate::KilobitsPerSec( |
| codec.spatialLayers[first_active_layer + num_active_layers - 1] |
| .minBitrate); |
| return toggling_rate; |
| } |
| } |
| |
| } // namespace |
| |
| SvcRateAllocator::NumLayers SvcRateAllocator::GetNumLayers( |
| const VideoCodec& codec) { |
| NumLayers layers; |
| if (absl::optional<ScalabilityMode> scalability_mode = |
| codec.GetScalabilityMode(); |
| scalability_mode.has_value()) { |
| if (auto structure = CreateScalabilityStructure(*scalability_mode)) { |
| ScalableVideoController::StreamLayersConfig config = |
| structure->StreamConfig(); |
| layers.spatial = config.num_spatial_layers; |
| layers.temporal = config.num_temporal_layers; |
| return layers; |
| } |
| } |
| if (codec.codecType == kVideoCodecVP9) { |
| layers.spatial = codec.VP9().numberOfSpatialLayers; |
| layers.temporal = codec.VP9().numberOfTemporalLayers; |
| return layers; |
| } |
| layers.spatial = 1; |
| layers.temporal = 1; |
| return layers; |
| } |
| |
| SvcRateAllocator::SvcRateAllocator(const VideoCodec& codec) |
| : codec_(codec), |
| num_layers_(GetNumLayers(codec)), |
| experiment_settings_(StableTargetRateExperiment::ParseFromFieldTrials()), |
| cumulative_layer_start_bitrates_(GetLayerStartBitrates(codec)), |
| last_active_layer_count_(0) { |
| RTC_DCHECK_GT(num_layers_.spatial, 0); |
| RTC_DCHECK_LE(num_layers_.spatial, kMaxSpatialLayers); |
| RTC_DCHECK_GT(num_layers_.temporal, 0); |
| RTC_DCHECK_LE(num_layers_.temporal, 3); |
| for (size_t layer_idx = 0; layer_idx < num_layers_.spatial; ++layer_idx) { |
| // Verify min <= target <= max. |
| if (codec.spatialLayers[layer_idx].active) { |
| RTC_DCHECK_GT(codec.spatialLayers[layer_idx].maxBitrate, 0); |
| RTC_DCHECK_GE(codec.spatialLayers[layer_idx].maxBitrate, |
| codec.spatialLayers[layer_idx].minBitrate); |
| RTC_DCHECK_GE(codec.spatialLayers[layer_idx].targetBitrate, |
| codec.spatialLayers[layer_idx].minBitrate); |
| RTC_DCHECK_GE(codec.spatialLayers[layer_idx].maxBitrate, |
| codec.spatialLayers[layer_idx].targetBitrate); |
| } |
| } |
| } |
| |
| VideoBitrateAllocation SvcRateAllocator::Allocate( |
| VideoBitrateAllocationParameters parameters) { |
| DataRate total_bitrate = parameters.total_bitrate; |
| if (codec_.maxBitrate != 0) { |
| total_bitrate = |
| std::min(total_bitrate, DataRate::KilobitsPerSec(codec_.maxBitrate)); |
| } |
| |
| if (codec_.spatialLayers[0].targetBitrate == 0) { |
| // Delegate rate distribution to encoder wrapper if bitrate thresholds |
| // are not set. |
| VideoBitrateAllocation bitrate_allocation; |
| bitrate_allocation.SetBitrate(0, 0, total_bitrate.bps()); |
| return bitrate_allocation; |
| } |
| |
| const ActiveSpatialLayers active_layers = |
| GetActiveSpatialLayers(codec_, num_layers_.spatial); |
| size_t num_spatial_layers = active_layers.num; |
| |
| if (num_spatial_layers == 0) { |
| return VideoBitrateAllocation(); // All layers are deactivated. |
| } |
| |
| // Figure out how many spatial layers should be active. |
| if (experiment_settings_.IsEnabled() && |
| parameters.stable_bitrate > DataRate::Zero()) { |
| double hysteresis_factor; |
| if (codec_.mode == VideoCodecMode::kScreensharing) { |
| hysteresis_factor = experiment_settings_.GetScreenshareHysteresisFactor(); |
| } else { |
| hysteresis_factor = experiment_settings_.GetVideoHysteresisFactor(); |
| } |
| |
| DataRate stable_rate = std::min(total_bitrate, parameters.stable_bitrate); |
| // First check if bitrate has grown large enough to enable new layers. |
| size_t num_enabled_with_hysteresis = |
| FindNumEnabledLayers(stable_rate / hysteresis_factor); |
| if (num_enabled_with_hysteresis >= last_active_layer_count_) { |
| num_spatial_layers = num_enabled_with_hysteresis; |
| } else { |
| // We could not enable new layers, check if any should be disabled. |
| num_spatial_layers = |
| std::min(last_active_layer_count_, FindNumEnabledLayers(stable_rate)); |
| } |
| } else { |
| num_spatial_layers = FindNumEnabledLayers(total_bitrate); |
| } |
| last_active_layer_count_ = num_spatial_layers; |
| |
| VideoBitrateAllocation allocation; |
| if (codec_.mode == VideoCodecMode::kRealtimeVideo) { |
| allocation = GetAllocationNormalVideo(total_bitrate, active_layers.first, |
| num_spatial_layers); |
| } else { |
| allocation = GetAllocationScreenSharing(total_bitrate, active_layers.first, |
| num_spatial_layers); |
| } |
| allocation.set_bw_limited(num_spatial_layers < active_layers.num); |
| return allocation; |
| } |
| |
| VideoBitrateAllocation SvcRateAllocator::GetAllocationNormalVideo( |
| DataRate total_bitrate, |
| size_t first_active_layer, |
| size_t num_spatial_layers) const { |
| std::vector<DataRate> spatial_layer_rates; |
| if (num_spatial_layers == 0) { |
| // Not enough rate for even the base layer. Force allocation at the total |
| // bitrate anyway. |
| num_spatial_layers = 1; |
| spatial_layer_rates.push_back(total_bitrate); |
| } else { |
| spatial_layer_rates = |
| AdjustAndVerify(codec_, first_active_layer, |
| SplitBitrate(num_spatial_layers, total_bitrate, |
| kSpatialLayeringRateScalingFactor)); |
| RTC_DCHECK_EQ(spatial_layer_rates.size(), num_spatial_layers); |
| } |
| |
| VideoBitrateAllocation bitrate_allocation; |
| |
| for (size_t sl_idx = 0; sl_idx < num_spatial_layers; ++sl_idx) { |
| std::vector<DataRate> temporal_layer_rates = |
| SplitBitrate(num_layers_.temporal, spatial_layer_rates[sl_idx], |
| kTemporalLayeringRateScalingFactor); |
| |
| // Distribute rate across temporal layers. Allocate more bits to lower |
| // layers since they are used for prediction of higher layers and their |
| // references are far apart. |
| if (num_layers_.temporal == 1) { |
| bitrate_allocation.SetBitrate(sl_idx + first_active_layer, 0, |
| temporal_layer_rates[0].bps()); |
| } else if (num_layers_.temporal == 2) { |
| bitrate_allocation.SetBitrate(sl_idx + first_active_layer, 0, |
| temporal_layer_rates[1].bps()); |
| bitrate_allocation.SetBitrate(sl_idx + first_active_layer, 1, |
| temporal_layer_rates[0].bps()); |
| } else { |
| RTC_CHECK_EQ(num_layers_.temporal, 3); |
| // In case of three temporal layers the high layer has two frames and the |
| // middle layer has one frame within GOP (in between two consecutive low |
| // layer frames). Thus high layer requires more bits (comparing pure |
| // bitrate of layer, excluding bitrate of base layers) to keep quality on |
| // par with lower layers. |
| bitrate_allocation.SetBitrate(sl_idx + first_active_layer, 0, |
| temporal_layer_rates[2].bps()); |
| bitrate_allocation.SetBitrate(sl_idx + first_active_layer, 1, |
| temporal_layer_rates[0].bps()); |
| bitrate_allocation.SetBitrate(sl_idx + first_active_layer, 2, |
| temporal_layer_rates[1].bps()); |
| } |
| } |
| |
| return bitrate_allocation; |
| } |
| |
| // Bit-rate is allocated in such a way, that the highest enabled layer will have |
| // between min and max bitrate, and all others will have exactly target |
| // bit-rate allocated. |
| VideoBitrateAllocation SvcRateAllocator::GetAllocationScreenSharing( |
| DataRate total_bitrate, |
| size_t first_active_layer, |
| size_t num_spatial_layers) const { |
| VideoBitrateAllocation bitrate_allocation; |
| |
| if (num_spatial_layers == 0 || |
| total_bitrate < |
| DataRate::KilobitsPerSec( |
| codec_.spatialLayers[first_active_layer].minBitrate)) { |
| // Always enable at least one layer. |
| bitrate_allocation.SetBitrate(first_active_layer, 0, total_bitrate.bps()); |
| return bitrate_allocation; |
| } |
| |
| DataRate allocated_rate = DataRate::Zero(); |
| DataRate top_layer_rate = DataRate::Zero(); |
| size_t sl_idx; |
| for (sl_idx = first_active_layer; |
| sl_idx < first_active_layer + num_spatial_layers; ++sl_idx) { |
| const DataRate min_rate = |
| DataRate::KilobitsPerSec(codec_.spatialLayers[sl_idx].minBitrate); |
| const DataRate target_rate = |
| DataRate::KilobitsPerSec(codec_.spatialLayers[sl_idx].targetBitrate); |
| |
| if (allocated_rate + min_rate > total_bitrate) { |
| // Use stable rate to determine if layer should be enabled. |
| break; |
| } |
| |
| top_layer_rate = std::min(target_rate, total_bitrate - allocated_rate); |
| bitrate_allocation.SetBitrate(sl_idx, 0, top_layer_rate.bps()); |
| allocated_rate += top_layer_rate; |
| } |
| |
| if (sl_idx > 0 && total_bitrate - allocated_rate > DataRate::Zero()) { |
| // Add leftover to the last allocated layer. |
| top_layer_rate = std::min( |
| top_layer_rate + (total_bitrate - allocated_rate), |
| DataRate::KilobitsPerSec(codec_.spatialLayers[sl_idx - 1].maxBitrate)); |
| bitrate_allocation.SetBitrate(sl_idx - 1, 0, top_layer_rate.bps()); |
| } |
| |
| return bitrate_allocation; |
| } |
| |
| size_t SvcRateAllocator::FindNumEnabledLayers(DataRate target_rate) const { |
| if (cumulative_layer_start_bitrates_.empty()) { |
| return 0; |
| } |
| |
| size_t num_enabled_layers = 0; |
| for (DataRate start_rate : cumulative_layer_start_bitrates_) { |
| // First layer is always enabled. |
| if (num_enabled_layers == 0 || start_rate <= target_rate) { |
| ++num_enabled_layers; |
| } else { |
| break; |
| } |
| } |
| |
| return num_enabled_layers; |
| } |
| |
| DataRate SvcRateAllocator::GetMaxBitrate(const VideoCodec& codec) { |
| const NumLayers num_layers = GetNumLayers(codec); |
| const ActiveSpatialLayers active_layers = |
| GetActiveSpatialLayers(codec, num_layers.spatial); |
| |
| DataRate max_bitrate = DataRate::Zero(); |
| for (size_t sl_idx = 0; sl_idx < active_layers.num; ++sl_idx) { |
| max_bitrate += DataRate::KilobitsPerSec( |
| codec.spatialLayers[active_layers.first + sl_idx].maxBitrate); |
| } |
| |
| if (codec.maxBitrate != 0) { |
| max_bitrate = |
| std::min(max_bitrate, DataRate::KilobitsPerSec(codec.maxBitrate)); |
| } |
| |
| return max_bitrate; |
| } |
| |
| DataRate SvcRateAllocator::GetPaddingBitrate(const VideoCodec& codec) { |
| auto start_bitrate = GetLayerStartBitrates(codec); |
| if (start_bitrate.empty()) { |
| return DataRate::Zero(); // All layers are deactivated. |
| } |
| |
| return start_bitrate.back(); |
| } |
| |
| absl::InlinedVector<DataRate, kMaxSpatialLayers> |
| SvcRateAllocator::GetLayerStartBitrates(const VideoCodec& codec) { |
| absl::InlinedVector<DataRate, kMaxSpatialLayers> start_bitrates; |
| const NumLayers num_layers = GetNumLayers(codec); |
| const ActiveSpatialLayers active_layers = |
| GetActiveSpatialLayers(codec, num_layers.spatial); |
| DataRate last_rate = DataRate::Zero(); |
| for (size_t i = 1; i <= active_layers.num; ++i) { |
| DataRate layer_toggling_rate = |
| FindLayerTogglingThreshold(codec, active_layers.first, i); |
| start_bitrates.push_back(layer_toggling_rate); |
| RTC_DCHECK_LE(last_rate, layer_toggling_rate); |
| last_rate = layer_toggling_rate; |
| } |
| return start_bitrates; |
| } |
| |
| } // namespace webrtc |