Erik Språng | 7ca375c | 2019-02-06 16:20:17 +0100 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license |
| 5 | * that can be found in the LICENSE file in the root of the source |
| 6 | * tree. An additional intellectual property rights grant can be found |
| 7 | * in the file PATENTS. All contributing project authors may |
| 8 | * be found in the AUTHORS file in the root of the source tree. |
| 9 | */ |
| 10 | |
| 11 | #include "video/encoder_bitrate_adjuster.h" |
| 12 | |
| 13 | #include <vector> |
| 14 | |
| 15 | #include "absl/memory/memory.h" |
| 16 | #include "api/units/data_rate.h" |
| 17 | #include "rtc_base/fake_clock.h" |
| 18 | #include "rtc_base/numerics/safe_conversions.h" |
| 19 | #include "test/gtest.h" |
| 20 | |
| 21 | namespace webrtc { |
| 22 | |
| 23 | class EncoderBitrateAdjusterTest : public ::testing::Test { |
| 24 | public: |
| 25 | static constexpr int64_t kWindowSizeMs = 3000; |
| 26 | static constexpr int kDefaultBitrateBps = 300000; |
| 27 | static constexpr int kDefaultFrameRateFps = 30; |
| 28 | EncoderBitrateAdjusterTest() |
| 29 | : target_bitrate_(DataRate::bps(kDefaultBitrateBps)), |
| 30 | target_framerate_fps_(kDefaultFrameRateFps), |
| 31 | tl_pattern_idx_{} {} |
| 32 | |
| 33 | protected: |
| 34 | void SetUpAdjuster(size_t num_spatial_layers, |
| 35 | size_t num_temporal_layers, |
| 36 | bool vp9_svc) { |
| 37 | // Initialize some default VideoCodec instance with the given number of |
| 38 | // layers. |
| 39 | if (vp9_svc) { |
| 40 | codec_.codecType = VideoCodecType::kVideoCodecVP9; |
| 41 | codec_.numberOfSimulcastStreams = 1; |
| 42 | codec_.VP9()->numberOfSpatialLayers = num_spatial_layers; |
| 43 | codec_.VP9()->numberOfTemporalLayers = num_temporal_layers; |
| 44 | for (size_t si = 0; si < num_spatial_layers; ++si) { |
| 45 | codec_.spatialLayers[si].minBitrate = 100 * (1 << si); |
| 46 | codec_.spatialLayers[si].targetBitrate = 200 * (1 << si); |
| 47 | codec_.spatialLayers[si].maxBitrate = 300 * (1 << si); |
| 48 | codec_.spatialLayers[si].active = true; |
| 49 | codec_.spatialLayers[si].numberOfTemporalLayers = num_temporal_layers; |
| 50 | } |
| 51 | } else { |
| 52 | codec_.codecType = VideoCodecType::kVideoCodecVP8; |
| 53 | codec_.numberOfSimulcastStreams = num_spatial_layers; |
| 54 | codec_.VP8()->numberOfTemporalLayers = num_temporal_layers; |
| 55 | for (size_t si = 0; si < num_spatial_layers; ++si) { |
| 56 | codec_.simulcastStream[si].minBitrate = 100 * (1 << si); |
| 57 | codec_.simulcastStream[si].targetBitrate = 200 * (1 << si); |
| 58 | codec_.simulcastStream[si].maxBitrate = 300 * (1 << si); |
| 59 | codec_.simulcastStream[si].active = true; |
| 60 | codec_.simulcastStream[si].numberOfTemporalLayers = num_temporal_layers; |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | for (size_t si = 0; si < num_spatial_layers; ++si) { |
| 65 | encoder_info_.fps_allocation[si].resize(num_temporal_layers); |
| 66 | double fraction = 1.0; |
| 67 | for (int ti = num_temporal_layers - 1; ti >= 0; --ti) { |
| 68 | encoder_info_.fps_allocation[si][ti] = static_cast<uint8_t>( |
| 69 | VideoEncoder::EncoderInfo::kMaxFramerateFraction * fraction + 0.5); |
| 70 | fraction /= 2.0; |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | adjuster_ = absl::make_unique<EncoderBitrateAdjuster>(codec_); |
| 75 | adjuster_->OnEncoderInfo(encoder_info_); |
| 76 | current_adjusted_allocation_ = adjuster_->AdjustRateAllocation( |
| 77 | current_input_allocation_, target_framerate_fps_); |
| 78 | } |
| 79 | |
| 80 | void InsertFrames(std::vector<std::vector<double>> utilization_factors, |
| 81 | int64_t duration_ms) { |
| 82 | constexpr size_t kMaxFrameSize = 100000; |
| 83 | uint8_t buffer[kMaxFrameSize]; |
| 84 | |
| 85 | const int64_t start_us = rtc::TimeMicros(); |
| 86 | while (rtc::TimeMicros() < |
| 87 | start_us + (duration_ms * rtc::kNumMicrosecsPerMillisec)) { |
| 88 | clock_.AdvanceTimeMicros(rtc::kNumMicrosecsPerSec / |
| 89 | target_framerate_fps_); |
| 90 | for (size_t si = 0; si < NumSpatialLayers(); ++si) { |
| 91 | const std::vector<int>& tl_pattern = |
| 92 | kTlPatterns[NumTemporalLayers(si) - 1]; |
| 93 | const size_t ti = |
| 94 | tl_pattern[(tl_pattern_idx_[si]++) % tl_pattern.size()]; |
| 95 | |
| 96 | uint32_t layer_bitrate_bps = |
| 97 | current_adjusted_allocation_.GetBitrate(si, ti); |
| 98 | double layer_framerate_fps = target_framerate_fps_; |
| 99 | if (encoder_info_.fps_allocation[si].size() > ti) { |
| 100 | uint8_t layer_fps_fraction = encoder_info_.fps_allocation[si][ti]; |
| 101 | if (ti > 0) { |
| 102 | // We're interested in the frame rate for this layer only, not |
| 103 | // cumulative frame rate. |
| 104 | layer_fps_fraction -= encoder_info_.fps_allocation[si][ti - 1]; |
| 105 | } |
| 106 | layer_framerate_fps = |
| 107 | (target_framerate_fps_ * layer_fps_fraction) / |
| 108 | VideoEncoder::EncoderInfo::kMaxFramerateFraction; |
| 109 | } |
| 110 | double utilization_factor = 1.0; |
| 111 | if (utilization_factors.size() > si && |
| 112 | utilization_factors[si].size() > ti) { |
| 113 | utilization_factor = utilization_factors[si][ti]; |
| 114 | } |
| 115 | size_t frame_size_bytes = utilization_factor * |
| 116 | (layer_bitrate_bps / 8.0) / |
| 117 | layer_framerate_fps; |
| 118 | |
| 119 | EncodedImage image(buffer, 0, kMaxFrameSize); |
| 120 | image.set_size(frame_size_bytes); |
| 121 | image.SetSpatialIndex(si); |
| 122 | adjuster_->OnEncodedFrame(image, ti); |
| 123 | } |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | size_t NumSpatialLayers() const { |
| 128 | if (codec_.codecType == VideoCodecType::kVideoCodecVP9) { |
| 129 | return codec_.VP9().numberOfSpatialLayers; |
| 130 | } |
| 131 | return codec_.numberOfSimulcastStreams; |
| 132 | } |
| 133 | |
| 134 | size_t NumTemporalLayers(int spatial_index) { |
| 135 | if (codec_.codecType == VideoCodecType::kVideoCodecVP9) { |
| 136 | return codec_.spatialLayers[spatial_index].numberOfTemporalLayers; |
| 137 | } |
| 138 | return codec_.simulcastStream[spatial_index].numberOfTemporalLayers; |
| 139 | } |
| 140 | |
| 141 | void ExpectNear(const VideoBitrateAllocation& expected_allocation, |
| 142 | const VideoBitrateAllocation& actual_allocation, |
| 143 | double allowed_error_fraction) { |
| 144 | for (size_t si = 0; si < kMaxSpatialLayers; ++si) { |
| 145 | for (size_t ti = 0; ti < kMaxTemporalStreams; ++ti) { |
| 146 | if (expected_allocation.HasBitrate(si, ti)) { |
| 147 | EXPECT_TRUE(actual_allocation.HasBitrate(si, ti)); |
| 148 | uint32_t expected_layer_bitrate_bps = |
| 149 | expected_allocation.GetBitrate(si, ti); |
| 150 | EXPECT_NEAR(expected_layer_bitrate_bps, |
| 151 | actual_allocation.GetBitrate(si, ti), |
| 152 | static_cast<uint32_t>(expected_layer_bitrate_bps * |
| 153 | allowed_error_fraction)); |
| 154 | } else { |
| 155 | EXPECT_FALSE(actual_allocation.HasBitrate(si, ti)); |
| 156 | } |
| 157 | } |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | VideoBitrateAllocation MultiplyAllocation( |
| 162 | const VideoBitrateAllocation& allocation, |
| 163 | double factor) { |
| 164 | VideoBitrateAllocation multiplied_allocation; |
| 165 | for (size_t si = 0; si < kMaxSpatialLayers; ++si) { |
| 166 | for (size_t ti = 0; ti < kMaxTemporalStreams; ++ti) { |
| 167 | if (allocation.HasBitrate(si, ti)) { |
| 168 | multiplied_allocation.SetBitrate( |
| 169 | si, ti, |
| 170 | static_cast<uint32_t>(factor * allocation.GetBitrate(si, ti) + |
| 171 | 0.5)); |
| 172 | } |
| 173 | } |
| 174 | } |
| 175 | return multiplied_allocation; |
| 176 | } |
| 177 | |
| 178 | VideoCodec codec_; |
| 179 | VideoEncoder::EncoderInfo encoder_info_; |
| 180 | std::unique_ptr<EncoderBitrateAdjuster> adjuster_; |
| 181 | VideoBitrateAllocation current_input_allocation_; |
| 182 | VideoBitrateAllocation current_adjusted_allocation_; |
| 183 | rtc::ScopedFakeClock clock_; |
| 184 | DataRate target_bitrate_; |
| 185 | double target_framerate_fps_; |
| 186 | int tl_pattern_idx_[kMaxSpatialLayers]; |
| 187 | |
| 188 | const std::vector<int> kTlPatterns[kMaxTemporalStreams] = { |
| 189 | {0}, |
| 190 | {0, 1}, |
| 191 | {0, 2, 1, 2}, |
| 192 | {0, 3, 2, 3, 1, 3, 2, 3}}; |
| 193 | }; |
| 194 | |
| 195 | TEST_F(EncoderBitrateAdjusterTest, SingleLayerOptimal) { |
| 196 | // Single layer, well behaved encoder. |
| 197 | current_input_allocation_.SetBitrate(0, 0, 300000); |
| 198 | target_framerate_fps_ = 30; |
| 199 | SetUpAdjuster(1, 1, false); |
| 200 | InsertFrames({{1.0}}, kWindowSizeMs); |
| 201 | current_adjusted_allocation_ = adjuster_->AdjustRateAllocation( |
| 202 | current_input_allocation_, target_framerate_fps_); |
| 203 | // Adjusted allocation near input. Allow 1% error margin due to rounding |
| 204 | // errors etc. |
| 205 | ExpectNear(current_input_allocation_, current_adjusted_allocation_, 0.01); |
| 206 | } |
| 207 | |
| 208 | TEST_F(EncoderBitrateAdjusterTest, SingleLayerOveruse) { |
| 209 | // Single layer, well behaved encoder. |
| 210 | current_input_allocation_.SetBitrate(0, 0, 300000); |
| 211 | target_framerate_fps_ = 30; |
| 212 | SetUpAdjuster(1, 1, false); |
| 213 | InsertFrames({{1.2}}, kWindowSizeMs); |
| 214 | current_adjusted_allocation_ = adjuster_->AdjustRateAllocation( |
| 215 | current_input_allocation_, target_framerate_fps_); |
| 216 | // Adjusted allocation lowered by 20%. |
| 217 | ExpectNear(MultiplyAllocation(current_input_allocation_, 1 / 1.2), |
| 218 | current_adjusted_allocation_, 0.01); |
| 219 | } |
| 220 | |
| 221 | TEST_F(EncoderBitrateAdjusterTest, SingleLayerUnderuse) { |
| 222 | // Single layer, well behaved encoder. |
| 223 | current_input_allocation_.SetBitrate(0, 0, 300000); |
| 224 | target_framerate_fps_ = 30; |
| 225 | SetUpAdjuster(1, 1, false); |
| 226 | InsertFrames({{0.5}}, kWindowSizeMs); |
| 227 | current_adjusted_allocation_ = adjuster_->AdjustRateAllocation( |
| 228 | current_input_allocation_, target_framerate_fps_); |
| 229 | // Undershoot, adjusted should exactly match input. |
| 230 | ExpectNear(current_input_allocation_, current_adjusted_allocation_, 0.00); |
| 231 | } |
| 232 | |
| 233 | TEST_F(EncoderBitrateAdjusterTest, ThreeTemporalLayersOptimalSize) { |
| 234 | // Three temporal layers, 60%/20%/20% bps distro, well behaved encoder. |
| 235 | current_input_allocation_.SetBitrate(0, 0, 180000); |
| 236 | current_input_allocation_.SetBitrate(0, 1, 60000); |
| 237 | current_input_allocation_.SetBitrate(0, 2, 60000); |
| 238 | target_framerate_fps_ = 30; |
| 239 | SetUpAdjuster(1, 3, false); |
| 240 | InsertFrames({{1.0, 1.0, 1.0}}, kWindowSizeMs); |
| 241 | current_adjusted_allocation_ = adjuster_->AdjustRateAllocation( |
| 242 | current_input_allocation_, target_framerate_fps_); |
| 243 | ExpectNear(current_input_allocation_, current_adjusted_allocation_, 0.01); |
| 244 | } |
| 245 | |
| 246 | TEST_F(EncoderBitrateAdjusterTest, ThreeTemporalLayersOvershoot) { |
| 247 | // Three temporal layers, 60%/20%/20% bps distro. |
| 248 | // 10% overshoot on all layers. |
| 249 | current_input_allocation_.SetBitrate(0, 0, 180000); |
| 250 | current_input_allocation_.SetBitrate(0, 1, 60000); |
| 251 | current_input_allocation_.SetBitrate(0, 2, 60000); |
| 252 | target_framerate_fps_ = 30; |
| 253 | SetUpAdjuster(1, 3, false); |
| 254 | InsertFrames({{1.1, 1.1, 1.1}}, kWindowSizeMs); |
| 255 | current_adjusted_allocation_ = adjuster_->AdjustRateAllocation( |
| 256 | current_input_allocation_, target_framerate_fps_); |
| 257 | // Adjusted allocation lowered by 10%. |
| 258 | ExpectNear(MultiplyAllocation(current_input_allocation_, 1 / 1.1), |
| 259 | current_adjusted_allocation_, 0.01); |
| 260 | } |
| 261 | |
| 262 | TEST_F(EncoderBitrateAdjusterTest, ThreeTemporalLayersUndershoot) { |
| 263 | // Three temporal layers, 60%/20%/20% bps distro, undershoot all layers. |
| 264 | current_input_allocation_.SetBitrate(0, 0, 180000); |
| 265 | current_input_allocation_.SetBitrate(0, 1, 60000); |
| 266 | current_input_allocation_.SetBitrate(0, 2, 60000); |
| 267 | target_framerate_fps_ = 30; |
| 268 | SetUpAdjuster(1, 3, false); |
| 269 | InsertFrames({{0.8, 0.8, 0.8}}, kWindowSizeMs); |
| 270 | current_adjusted_allocation_ = adjuster_->AdjustRateAllocation( |
| 271 | current_input_allocation_, target_framerate_fps_); |
| 272 | // Adjusted allocation identical since we don't boost bitrates. |
| 273 | ExpectNear(current_input_allocation_, current_adjusted_allocation_, 0.0); |
| 274 | } |
| 275 | |
| 276 | TEST_F(EncoderBitrateAdjusterTest, ThreeTemporalLayersSkewedOvershoot) { |
| 277 | // Three temporal layers, 60%/20%/20% bps distro. |
| 278 | // 10% overshoot on base layer, 20% on higher layers. |
| 279 | current_input_allocation_.SetBitrate(0, 0, 180000); |
| 280 | current_input_allocation_.SetBitrate(0, 1, 60000); |
| 281 | current_input_allocation_.SetBitrate(0, 2, 60000); |
| 282 | target_framerate_fps_ = 30; |
| 283 | SetUpAdjuster(1, 3, false); |
| 284 | InsertFrames({{1.1, 1.2, 1.2}}, kWindowSizeMs); |
| 285 | current_adjusted_allocation_ = adjuster_->AdjustRateAllocation( |
| 286 | current_input_allocation_, target_framerate_fps_); |
| 287 | // Expected overshoot is weighted by bitrate: |
| 288 | // (0.6 * 1.1 + 0.2 * 1.2 + 0.2 * 1.2) = 1.14 |
| 289 | ExpectNear(MultiplyAllocation(current_input_allocation_, 1 / 1.14), |
| 290 | current_adjusted_allocation_, 0.01); |
| 291 | } |
| 292 | |
| 293 | TEST_F(EncoderBitrateAdjusterTest, FourTemporalLayersSkewedOvershoot) { |
| 294 | // Three temporal layers, 40%/30%/15%/15% bps distro. |
| 295 | // 10% overshoot on base layer, 20% on higher layers. |
| 296 | current_input_allocation_.SetBitrate(0, 0, 120000); |
| 297 | current_input_allocation_.SetBitrate(0, 1, 90000); |
| 298 | current_input_allocation_.SetBitrate(0, 2, 45000); |
| 299 | current_input_allocation_.SetBitrate(0, 3, 45000); |
| 300 | target_framerate_fps_ = 30; |
| 301 | SetUpAdjuster(1, 4, false); |
| 302 | InsertFrames({{1.1, 1.2, 1.2, 1.2}}, kWindowSizeMs); |
| 303 | current_adjusted_allocation_ = adjuster_->AdjustRateAllocation( |
| 304 | current_input_allocation_, target_framerate_fps_); |
| 305 | // Expected overshoot is weighted by bitrate: |
| 306 | // (0.4 * 1.1 + 0.3 * 1.2 + 0.15 * 1.2 + 0.15 * 1.2) = 1.16 |
| 307 | ExpectNear(MultiplyAllocation(current_input_allocation_, 1 / 1.16), |
| 308 | current_adjusted_allocation_, 0.01); |
| 309 | } |
| 310 | |
| 311 | TEST_F(EncoderBitrateAdjusterTest, ThreeTemporalLayersNonLayeredEncoder) { |
| 312 | // Three temporal layers, 60%/20%/20% bps allocation, 10% overshoot, |
| 313 | // encoder does not actually support temporal layers. |
| 314 | current_input_allocation_.SetBitrate(0, 0, 180000); |
| 315 | current_input_allocation_.SetBitrate(0, 1, 60000); |
| 316 | current_input_allocation_.SetBitrate(0, 2, 60000); |
| 317 | target_framerate_fps_ = 30; |
| 318 | SetUpAdjuster(1, 1, false); |
| 319 | InsertFrames({{1.1}}, kWindowSizeMs); |
| 320 | current_adjusted_allocation_ = adjuster_->AdjustRateAllocation( |
| 321 | current_input_allocation_, target_framerate_fps_); |
| 322 | // Expect the actual 10% overuse to be detected and the allocation to |
| 323 | // only contain the one entry. |
| 324 | VideoBitrateAllocation expected_allocation; |
| 325 | expected_allocation.SetBitrate( |
| 326 | 0, 0, |
| 327 | static_cast<uint32_t>(current_input_allocation_.get_sum_bps() / 1.10)); |
| 328 | ExpectNear(expected_allocation, current_adjusted_allocation_, 0.01); |
| 329 | } |
| 330 | |
| 331 | TEST_F(EncoderBitrateAdjusterTest, IgnoredStream) { |
| 332 | // Encoder with three temporal layers, but in a mode that does not support |
| 333 | // deterministic frame rate. Those are ignored, even if bitrate overshoots. |
| 334 | current_input_allocation_.SetBitrate(0, 0, 180000); |
| 335 | current_input_allocation_.SetBitrate(0, 1, 60000); |
| 336 | target_framerate_fps_ = 30; |
| 337 | SetUpAdjuster(1, 1, false); |
| 338 | encoder_info_.fps_allocation[0].clear(); |
| 339 | adjuster_->OnEncoderInfo(encoder_info_); |
| 340 | |
| 341 | InsertFrames({{1.1}}, kWindowSizeMs); |
| 342 | current_adjusted_allocation_ = adjuster_->AdjustRateAllocation( |
| 343 | current_input_allocation_, target_framerate_fps_); |
| 344 | |
| 345 | // Values passed through. |
| 346 | ExpectNear(current_input_allocation_, current_adjusted_allocation_, 0.00); |
| 347 | } |
| 348 | |
| 349 | TEST_F(EncoderBitrateAdjusterTest, DifferentSpatialOvershoots) { |
| 350 | // Two streams, both with three temporal layers. |
| 351 | // S0 has 5% overshoot, S1 has 25% overshoot. |
| 352 | current_input_allocation_.SetBitrate(0, 0, 180000); |
| 353 | current_input_allocation_.SetBitrate(0, 1, 60000); |
| 354 | current_input_allocation_.SetBitrate(0, 2, 60000); |
| 355 | current_input_allocation_.SetBitrate(1, 0, 400000); |
| 356 | current_input_allocation_.SetBitrate(1, 1, 150000); |
| 357 | current_input_allocation_.SetBitrate(1, 2, 150000); |
| 358 | target_framerate_fps_ = 30; |
| 359 | // Run twice, once configured as simulcast and once as VP9 SVC. |
| 360 | for (int i = 0; i < 2; ++i) { |
| 361 | SetUpAdjuster(2, 3, i == 0); |
| 362 | InsertFrames({{1.05, 1.05, 1.05}, {1.25, 1.25, 1.25}}, kWindowSizeMs); |
| 363 | current_adjusted_allocation_ = adjuster_->AdjustRateAllocation( |
| 364 | current_input_allocation_, target_framerate_fps_); |
| 365 | VideoBitrateAllocation expected_allocation; |
| 366 | for (size_t ti = 0; ti < 3; ++ti) { |
| 367 | expected_allocation.SetBitrate( |
| 368 | 0, ti, |
| 369 | static_cast<uint32_t>(current_input_allocation_.GetBitrate(0, ti) / |
| 370 | 1.05)); |
| 371 | expected_allocation.SetBitrate( |
| 372 | 1, ti, |
| 373 | static_cast<uint32_t>(current_input_allocation_.GetBitrate(1, ti) / |
| 374 | 1.25)); |
| 375 | } |
| 376 | ExpectNear(expected_allocation, current_adjusted_allocation_, 0.01); |
| 377 | } |
| 378 | } |
| 379 | |
| 380 | } // namespace webrtc |