From 616b233688c5a1366480122c5cc4944be8161d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Spr=C3=A5ng?= Date: Mon, 11 Feb 2019 14:16:28 +0100 Subject: [PATCH] Add FullStackTest with simulated encoder overshooting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: webrtc:10302 Change-Id: I1d4b9ef22ba1ca9a221cc01e2c44775014c90d4f Reviewed-on: https://webrtc-review.googlesource.com/c/122082 Commit-Queue: Erik Språng Reviewed-by: Stefan Holmer Cr-Commit-Position: refs/heads/master@{#26673} --- api/test/video_quality_test_fixture.h | 1 + video/full_stack_tests.cc | 17 +++ video/video_quality_test.cc | 165 ++++++++++++++++++++++---- 3 files changed, 162 insertions(+), 21 deletions(-) diff --git a/api/test/video_quality_test_fixture.h b/api/test/video_quality_test_fixture.h index 79b6d672c9..c334a2c591 100644 --- a/api/test/video_quality_test_fixture.h +++ b/api/test/video_quality_test_fixture.h @@ -60,6 +60,7 @@ class VideoQualityTestFixtureInterface { std::string clip_name; // "Generator" to generate frames instead. size_t capture_device_index; SdpVideoFormat::Parameters sdp_params; + double encoder_overshoot_factor; } video[2]; struct Audio { bool enabled; diff --git a/video/full_stack_tests.cc b/video/full_stack_tests.cc index 5e08fcff86..c1848a76d6 100644 --- a/video/full_stack_tests.cc +++ b/video/full_stack_tests.cc @@ -279,6 +279,23 @@ TEST(FullStackTest, ForemanCifLink150kbpsWithoutPacketLoss) { fixture->RunWithAnalyzer(foreman_cif); } +// Restricted network and encoder overproducing by 30%. +TEST(FullStackTest, ForemanCifLink150kbpsBadRateController) { + auto fixture = CreateVideoQualityTestFixture(); + ParamsWithLogging foreman_cif; + foreman_cif.call.send_side_bwe = true; + foreman_cif.video[0] = {true, 352, 288, 30, 30000, 500000, 2000000, + false, "VP8", 1, 0, 0, false, false, + true, "foreman_cif", 0, {}, 1.30}; + foreman_cif.analyzer = { + "foreman_cif_link_150kbps_delay100ms_30pkts_queue_overshoot30", 0.0, 0.0, + kFullStackTestDurationSecs}; + foreman_cif.config->link_capacity_kbps = 150; + foreman_cif.config->queue_length_packets = 30; + foreman_cif.config->queue_delay_ms = 100; + fixture->RunWithAnalyzer(foreman_cif); +} + TEST_P(GenericDescriptorTest, ForemanCifPlr5) { auto fixture = CreateVideoQualityTestFixture(); ParamsWithLogging foreman_cif; diff --git a/video/video_quality_test.cc b/video/video_quality_test.cc index 1b2f9d6e0a..ad3224df0c 100644 --- a/video/video_quality_test.cc +++ b/video/video_quality_test.cc @@ -56,6 +56,27 @@ constexpr uint32_t kThumbnailRtxSsrcStart = 0xF0000; constexpr int kDefaultMaxQp = cricket::WebRtcVideoChannel::kDefaultQpMax; +std::pair GetMinMaxBitratesBps(const VideoCodec& codec, + size_t spatial_idx) { + uint32_t min_bitrate = codec.minBitrate; + uint32_t max_bitrate = codec.maxBitrate; + if (spatial_idx < codec.numberOfSimulcastStreams) { + min_bitrate = + std::max(min_bitrate, codec.simulcastStream[spatial_idx].minBitrate); + max_bitrate = + std::min(max_bitrate, codec.simulcastStream[spatial_idx].maxBitrate); + } + if (codec.codecType == VideoCodecType::kVideoCodecVP9 && + spatial_idx < codec.VP9().numberOfSpatialLayers) { + min_bitrate = + std::max(min_bitrate, codec.spatialLayers[spatial_idx].minBitrate); + max_bitrate = + std::min(max_bitrate, codec.spatialLayers[spatial_idx].maxBitrate); + } + max_bitrate = std::max(max_bitrate, min_bitrate); + return {min_bitrate * 1000, max_bitrate * 1000}; +} + class VideoStreamFactory : public VideoEncoderConfig::VideoStreamFactoryInterface { public: @@ -87,8 +108,11 @@ class QualityTestVideoEncoder : public VideoEncoder, public: QualityTestVideoEncoder(std::unique_ptr encoder, VideoAnalyzer* analyzer, - std::vector files) - : encoder_(std::move(encoder)), analyzer_(analyzer) { + std::vector files, + double overshoot_factor) + : encoder_(std::move(encoder)), + overshoot_factor_(overshoot_factor), + analyzer_(analyzer) { for (FileWrapper& file : files) { writers_.push_back( IvfFileWriter::Wrap(std::move(file), /* byte_limit= */ 100000000)); @@ -98,6 +122,7 @@ class QualityTestVideoEncoder : public VideoEncoder, int32_t InitEncode(const VideoCodec* codec_settings, int32_t number_of_cores, size_t max_payload_size) override { + codec_settings_ = *codec_settings; return encoder_->InitEncode(codec_settings, number_of_cores, max_payload_size); } @@ -115,15 +140,57 @@ class QualityTestVideoEncoder : public VideoEncoder, } return encoder_->Encode(frame, codec_specific_info, frame_types); } - int32_t SetRates(uint32_t bitrate, uint32_t framerate) override { - return encoder_->SetRates(bitrate, framerate); - } int32_t SetRateAllocation(const VideoBitrateAllocation& allocation, uint32_t framerate) override { - return encoder_->SetRateAllocation(allocation, framerate); + RTC_DCHECK_GT(overshoot_factor_, 0.0); + if (overshoot_factor_ == 1.0) { + return encoder_->SetRateAllocation(allocation, framerate); + } + + // Simulating encoder overshooting target bitrate, by configuring actual + // encoder too high. Take care not to adjust past limits of config, + // otherwise encoders may crash on DCHECK. + VideoBitrateAllocation overshot_allocation; + for (size_t si = 0; si < kMaxSpatialLayers; ++si) { + const uint32_t spatial_layer_bitrate_bps = + allocation.GetSpatialLayerSum(si); + if (spatial_layer_bitrate_bps == 0) { + continue; + } + + uint32_t min_bitrate_bps; + uint32_t max_bitrate_bps; + std::tie(min_bitrate_bps, max_bitrate_bps) = + GetMinMaxBitratesBps(codec_settings_, si); + double overshoot_factor = overshoot_factor_; + const uint32_t corrected_bitrate = rtc::checked_cast( + overshoot_factor * spatial_layer_bitrate_bps); + if (corrected_bitrate < min_bitrate_bps) { + overshoot_factor = min_bitrate_bps / spatial_layer_bitrate_bps; + } else if (corrected_bitrate > max_bitrate_bps) { + overshoot_factor = max_bitrate_bps / spatial_layer_bitrate_bps; + } + + for (size_t ti = 0; ti < kMaxTemporalStreams; ++ti) { + if (allocation.HasBitrate(si, ti)) { + overshot_allocation.SetBitrate( + si, ti, + rtc::checked_cast(overshoot_factor * + allocation.GetBitrate(si, ti))); + } + } + } + + return encoder_->SetRateAllocation(overshot_allocation, framerate); } EncoderInfo GetEncoderInfo() const override { - return encoder_->GetEncoderInfo(); + EncoderInfo info = encoder_->GetEncoderInfo(); + if (overshoot_factor_ != 1.0) { + // We're simulating bad encoder, don't forward trusted setting + // from eg libvpx. + info.has_trusted_rate_controller = false; + } + return info; } private: @@ -157,10 +224,12 @@ class QualityTestVideoEncoder : public VideoEncoder, callback_->OnDroppedFrame(reason); } - std::unique_ptr encoder_; - EncodedImageCallback* callback_ = nullptr; + const std::unique_ptr encoder_; + const double overshoot_factor_; VideoAnalyzer* const analyzer_; std::vector> writers_; + EncodedImageCallback* callback_ = nullptr; + VideoCodec codec_settings_; }; } // namespace @@ -202,22 +271,42 @@ std::unique_ptr VideoQualityTest::CreateVideoEncoder( } else { encoder = internal_encoder_factory_.CreateVideoEncoder(format); } + + std::vector encoded_frame_dump_files; if (!params_.logging.encoded_frame_base_path.empty()) { char ss_buf[100]; rtc::SimpleStringBuilder sb(ss_buf); sb << send_logs_++; std::string prefix = params_.logging.encoded_frame_base_path + "." + sb.str() + ".send."; - std::vector files; - files.push_back(FileWrapper::OpenWriteOnly(prefix + "1.ivf")); - files.push_back(FileWrapper::OpenWriteOnly(prefix + "2.ivf")); - files.push_back(FileWrapper::OpenWriteOnly(prefix + "3.ivf")); + encoded_frame_dump_files.push_back( + FileWrapper::OpenWriteOnly(prefix + "1.ivf")); + encoded_frame_dump_files.push_back( + FileWrapper::OpenWriteOnly(prefix + "2.ivf")); + encoded_frame_dump_files.push_back( + FileWrapper::OpenWriteOnly(prefix + "3.ivf")); + } + double overshoot_factor = 1.0; + // Match format to either of the streams in dual-stream mode in order to get + // the overshoot factor. This is not very robust but we can't know for sure + // which stream this encoder is meant for, from within the factory. + if (format == + SdpVideoFormat(params_.video[0].codec, params_.video[0].sdp_params)) { + overshoot_factor = params_.video[0].encoder_overshoot_factor; + } else if (format == SdpVideoFormat(params_.video[1].codec, + params_.video[1].sdp_params)) { + overshoot_factor = params_.video[1].encoder_overshoot_factor; + } + if (overshoot_factor == 0.0) { + // If params were zero-initialized, set to 1.0 instead. + overshoot_factor = 1.0; + } + + if (analyzer || !encoded_frame_dump_files.empty() || overshoot_factor > 1.0) { encoder = absl::make_unique( - std::move(encoder), analyzer, std::move(files)); - } else if (analyzer) { - encoder = absl::make_unique( - std::move(encoder), analyzer, std::vector()); + std::move(encoder), analyzer, std::move(encoded_frame_dump_files), + overshoot_factor); } return encoder; @@ -263,10 +352,44 @@ VideoQualityTest::VideoQualityTest( VideoQualityTest::Params::Params() : call({false, false, BitrateConstraints(), 0}), - video{{false, 640, 480, 30, 50, 800, 800, false, "VP8", 1, -1, 0, false, - false, false, ""}, - {false, 640, 480, 30, 50, 800, 800, false, "VP8", 1, -1, 0, false, - false, false, ""}}, + video{{false, + 640, + 480, + 30, + 50, + 800, + 800, + false, + "VP8", + 1, + -1, + 0, + false, + false, + false, + "", + 0, + {}, + 0.0}, + {false, + 640, + 480, + 30, + 50, + 800, + 800, + false, + "VP8", + 1, + -1, + 0, + false, + false, + false, + "", + 0, + {}, + 0.0}}, audio({false, false, false, false}), screenshare{{false, false, 10, 0}, {false, false, 10, 0}}, analyzer({"", 0.0, 0.0, 0, "", ""}),