| /* |
| * 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 <algorithm> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "common_types.h" // NOLINT(build/include) |
| #include "rtc_base/checks.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 kShort3TlRateAllocation[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) { |
| 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 && |
| field_trial::IsEnabled("WebRTC-UseShortVP8TL3Pattern")) { |
| return kShort3TlRateAllocation[temporal_id]; |
| } |
| return kLayerRateAllocation[num_layers - 1][temporal_id]; |
| } |
| |
| SimulcastRateAllocator::SimulcastRateAllocator(const VideoCodec& codec) |
| : codec_(codec) {} |
| |
| VideoBitrateAllocation SimulcastRateAllocator::GetAllocation( |
| uint32_t total_bitrate_bps, |
| uint32_t framerate) { |
| VideoBitrateAllocation allocated_bitrates_bps; |
| DistributeAllocationToSimulcastLayers(total_bitrate_bps, |
| &allocated_bitrates_bps); |
| DistributeAllocationToTemporalLayers(framerate, &allocated_bitrates_bps); |
| return allocated_bitrates_bps; |
| } |
| |
| void SimulcastRateAllocator::DistributeAllocationToSimulcastLayers( |
| uint32_t total_bitrate_bps, |
| VideoBitrateAllocation* allocated_bitrates_bps) const { |
| uint32_t left_to_allocate = total_bitrate_bps; |
| if (codec_.maxBitrate && codec_.maxBitrate * 1000 < left_to_allocate) |
| left_to_allocate = codec_.maxBitrate * 1000; |
| |
| if (codec_.numberOfSimulcastStreams == 0) { |
| // No simulcast, just set the target as this has been capped already. |
| if (codec_.active) { |
| allocated_bitrates_bps->SetBitrate( |
| 0, 0, std::max(codec_.minBitrate * 1000, left_to_allocate)); |
| } |
| return; |
| } |
| // 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[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. |
| left_to_allocate = std::max( |
| codec_.simulcastStream[active_layer].minBitrate * 1000, left_to_allocate); |
| |
| // 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. |
| |
| 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[active_layer]; |
| if (!stream.active) { |
| continue; |
| } |
| // If we can't allocate to the current layer we can't allocate to higher |
| // layers because they require a higher minimum bitrate. |
| if (left_to_allocate < stream.minBitrate * 1000) { |
| break; |
| } |
| // We are allocating to this layer so it is the current active allocation. |
| top_active_layer = active_layer; |
| uint32_t allocation = |
| std::min(left_to_allocate, stream.targetBitrate * 1000); |
| allocated_bitrates_bps->SetBitrate(active_layer, 0, allocation); |
| RTC_DCHECK_LE(allocation, left_to_allocate); |
| left_to_allocate -= allocation; |
| } |
| |
| // 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_to_allocate > 0) { |
| const SimulcastStream& stream = codec_.simulcastStream[top_active_layer]; |
| uint32_t bitrate_bps = |
| allocated_bitrates_bps->GetSpatialLayerSum(top_active_layer); |
| uint32_t allocation = |
| std::min(left_to_allocate, stream.maxBitrate * 1000 - bitrate_bps); |
| bitrate_bps += allocation; |
| RTC_DCHECK_LE(allocation, left_to_allocate); |
| left_to_allocate -= allocation; |
| allocated_bitrates_bps->SetBitrate(top_active_layer, 0, bitrate_bps); |
| } |
| } |
| |
| void SimulcastRateAllocator::DistributeAllocationToTemporalLayers( |
| uint32_t framerate, |
| 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. |
| const bool conference_screenshare_mode = |
| codec_.mode == VideoCodecMode::kScreensharing && |
| ((num_spatial_streams == 1 && num_temporal_streams == 2) || // Legacy. |
| (num_spatial_streams > 1 && simulcast_id == 0)); // Simulcast. |
| if (conference_screenshare_mode) { |
| // 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 (conference_screenshare_mode) { |
| tl_allocation = ScreenshareTemporalLayerAllocation( |
| target_bitrate_kbps, max_bitrate_kbps, framerate, simulcast_id); |
| } else { |
| tl_allocation = DefaultTemporalLayerAllocation( |
| target_bitrate_kbps, max_bitrate_kbps, framerate, 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 framerate, |
| 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); |
| 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 framerate, |
| int simulcast_id) const { |
| if (simulcast_id > 0) { |
| return DefaultTemporalLayerAllocation(bitrate_kbps, max_bitrate_kbps, |
| framerate, 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); |
| } |
| |
| } // namespace webrtc |