Add field trial for rampup in quality based on available bandwidth.

Bug: none
Change-Id: I32e1ea6fb2f2e20fc631e09b02c8f3a11b6c9fac
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/158888
Reviewed-by: Niels Moller <nisse@webrtc.org>
Reviewed-by: Sergey Silkin <ssilkin@webrtc.org>
Commit-Queue: Åsa Persson <asapersson@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29751}
This commit is contained in:
Åsa Persson 2019-11-08 15:56:00 +01:00 committed by Commit Bot
parent a4c1aaad8d
commit e644a03195
12 changed files with 412 additions and 2 deletions

View File

@ -166,6 +166,21 @@ void QualityScaler::ReportQp(int qp, int64_t time_sent_us) {
qp_smoother_low_->Add(qp, time_sent_us);
}
bool QualityScaler::QpFastFilterLow() const {
RTC_DCHECK_RUN_ON(&task_checker_);
size_t num_frames = config_.use_all_drop_reasons
? framedrop_percent_all_.Size()
: framedrop_percent_media_opt_.Size();
const size_t kMinNumFrames = 10;
if (num_frames < kMinNumFrames) {
return false; // Wait for more frames before making a decision.
}
absl::optional<int> avg_qp_high = qp_smoother_high_
? qp_smoother_high_->GetAvg()
: average_qp_.GetAverageRoundedDown();
return (avg_qp_high) ? (avg_qp_high.value() <= thresholds_.low) : false;
}
void QualityScaler::CheckQp() {
RTC_DCHECK_RUN_ON(&task_checker_);
// Should be set through InitEncode -> Should be set by now.

View File

@ -64,6 +64,7 @@ class QualityScaler {
void ReportQp(int qp, int64_t time_sent_us);
void SetQpThresholds(VideoEncoder::QpThresholds thresholds);
bool QpFastFilterLow() const;
// The following members declared protected for testing purposes.
protected:

View File

@ -46,6 +46,21 @@ rtc_library("field_trial_parser") {
]
}
rtc_library("quality_rampup_experiment") {
sources = [
"quality_rampup_experiment.cc",
"quality_rampup_experiment.h",
]
deps = [
":field_trial_parser",
"../:rtc_base_approved",
"../../api/transport:field_trial_based_config",
"../../api/transport:webrtc_key_value_config",
"../../system_wrappers:field_trial",
"//third_party/abseil-cpp/absl/types:optional",
]
}
rtc_library("quality_scaler_settings") {
sources = [
"quality_scaler_settings.cc",
@ -221,6 +236,7 @@ if (rtc_include_tests) {
"keyframe_interval_settings_unittest.cc",
"min_video_bitrate_experiment_unittest.cc",
"normalize_simulcast_size_experiment_unittest.cc",
"quality_rampup_experiment_unittest.cc",
"quality_scaler_settings_unittest.cc",
"quality_scaling_experiment_unittest.cc",
"rate_control_settings_unittest.cc",
@ -235,6 +251,7 @@ if (rtc_include_tests) {
":keyframe_interval_settings_experiment",
":min_video_bitrate_experiment",
":normalize_simulcast_size_experiment",
":quality_rampup_experiment",
":quality_scaler_settings",
":quality_scaling_experiment",
":rate_control_settings",

View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 2019 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 "rtc_base/experiments/quality_rampup_experiment.h"
#include <algorithm>
#include "api/transport/field_trial_based_config.h"
#include "rtc_base/logging.h"
namespace webrtc {
QualityRampupExperiment::QualityRampupExperiment(
const WebRtcKeyValueConfig* const key_value_config)
: min_pixels_("min_pixels"),
min_duration_ms_("min_duration_ms"),
max_bitrate_factor_("max_bitrate_factor") {
ParseFieldTrial(
{&min_pixels_, &min_duration_ms_, &max_bitrate_factor_},
key_value_config->Lookup("WebRTC-Video-QualityRampupSettings"));
}
QualityRampupExperiment QualityRampupExperiment::ParseSettings() {
FieldTrialBasedConfig field_trial_config;
return QualityRampupExperiment(&field_trial_config);
}
absl::optional<int> QualityRampupExperiment::MinPixels() const {
return min_pixels_.GetOptional();
}
absl::optional<int> QualityRampupExperiment::MinDurationMs() const {
return min_duration_ms_.GetOptional();
}
absl::optional<double> QualityRampupExperiment::MaxBitrateFactor() const {
return max_bitrate_factor_.GetOptional();
}
void QualityRampupExperiment::SetMaxBitrate(int pixels,
uint32_t max_bitrate_kbps) {
if (!min_pixels_ || pixels < min_pixels_.Value() || max_bitrate_kbps == 0) {
return;
}
max_bitrate_kbps_ = std::max(max_bitrate_kbps_.value_or(0), max_bitrate_kbps);
}
bool QualityRampupExperiment::BwHigh(int64_t now_ms,
uint32_t available_bw_kbps) {
if (!min_pixels_ || !min_duration_ms_ || !max_bitrate_kbps_) {
return false;
}
if (available_bw_kbps <
max_bitrate_kbps_.value() * MaxBitrateFactor().value_or(1)) {
start_ms_.reset();
return false;
}
if (!start_ms_)
start_ms_ = now_ms;
return (now_ms - *start_ms_) >= min_duration_ms_.Value();
}
} // namespace webrtc

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2019 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.
*/
#ifndef RTC_BASE_EXPERIMENTS_QUALITY_RAMPUP_EXPERIMENT_H_
#define RTC_BASE_EXPERIMENTS_QUALITY_RAMPUP_EXPERIMENT_H_
#include "absl/types/optional.h"
#include "api/transport/webrtc_key_value_config.h"
#include "rtc_base/experiments/field_trial_parser.h"
namespace webrtc {
class QualityRampupExperiment final {
public:
static QualityRampupExperiment ParseSettings();
absl::optional<int> MinPixels() const;
absl::optional<int> MinDurationMs() const;
absl::optional<double> MaxBitrateFactor() const;
// Sets the max bitrate and the frame size.
// The call has no effect if the frame size is less than |min_pixels_|.
void SetMaxBitrate(int pixels, uint32_t max_bitrate_kbps);
// Returns true if the available bandwidth is a certain percentage
// (max_bitrate_factor_) above |max_bitrate_kbps_| for |min_duration_ms_|.
bool BwHigh(int64_t now_ms, uint32_t available_bw_kbps);
private:
explicit QualityRampupExperiment(
const WebRtcKeyValueConfig* const key_value_config);
FieldTrialOptional<int> min_pixels_;
FieldTrialOptional<int> min_duration_ms_;
FieldTrialOptional<double> max_bitrate_factor_;
absl::optional<int64_t> start_ms_;
absl::optional<uint32_t> max_bitrate_kbps_;
};
} // namespace webrtc
#endif // RTC_BASE_EXPERIMENTS_QUALITY_RAMPUP_EXPERIMENT_H_

View File

@ -0,0 +1,139 @@
/*
* Copyright 2019 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 "rtc_base/experiments/quality_rampup_experiment.h"
#include "test/field_trial.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
class QualityRampupExperimentTest : public ::testing::Test {
protected:
int64_t NowMs() const { return current_ms_; }
int64_t AdvanceMs(int64_t delta_ms) {
current_ms_ += delta_ms;
return current_ms_;
}
int64_t current_ms_ = 2345;
};
TEST_F(QualityRampupExperimentTest, ValuesNotSetByDefault) {
const auto settings = QualityRampupExperiment::ParseSettings();
EXPECT_FALSE(settings.MinPixels());
EXPECT_FALSE(settings.MinDurationMs());
EXPECT_FALSE(settings.MaxBitrateFactor());
}
TEST_F(QualityRampupExperimentTest, ParseMinPixels) {
test::ScopedFieldTrials field_trials(
"WebRTC-Video-QualityRampupSettings/min_pixels:10000/");
EXPECT_EQ(10000, QualityRampupExperiment::ParseSettings().MinPixels());
}
TEST_F(QualityRampupExperimentTest, ParseMinDuration) {
test::ScopedFieldTrials field_trials(
"WebRTC-Video-QualityRampupSettings/min_duration_ms:987/");
EXPECT_EQ(987, QualityRampupExperiment::ParseSettings().MinDurationMs());
}
TEST_F(QualityRampupExperimentTest, ParseMaxBitrateFactor) {
test::ScopedFieldTrials field_trials(
"WebRTC-Video-QualityRampupSettings/max_bitrate_factor:1.23/");
EXPECT_EQ(1.23, QualityRampupExperiment::ParseSettings().MaxBitrateFactor());
}
TEST_F(QualityRampupExperimentTest, ReportsBwHighWhenDurationPassed) {
test::ScopedFieldTrials field_trials(
"WebRTC-Video-QualityRampupSettings/"
"min_pixels:10000,min_duration_ms:2000/");
auto exp = QualityRampupExperiment::ParseSettings();
EXPECT_EQ(10000, exp.MinPixels());
EXPECT_EQ(2000, exp.MinDurationMs());
const uint32_t kMaxKbps = 800;
exp.SetMaxBitrate(/*pixels*/ 10000, kMaxKbps);
const uint32_t kAvailableKbps = kMaxKbps;
EXPECT_FALSE(exp.BwHigh(NowMs(), kAvailableKbps));
EXPECT_FALSE(exp.BwHigh(AdvanceMs(2000 - 1), kAvailableKbps));
EXPECT_TRUE(exp.BwHigh(AdvanceMs(1), kAvailableKbps));
}
TEST_F(QualityRampupExperimentTest, UsesMaxSetBitrate) {
test::ScopedFieldTrials field_trials(
"WebRTC-Video-QualityRampupSettings/"
"min_pixels:10000,min_duration_ms:2000/");
auto exp = QualityRampupExperiment::ParseSettings();
const uint32_t kMaxKbps = 800;
exp.SetMaxBitrate(/*pixels*/ 10000, kMaxKbps);
exp.SetMaxBitrate(/*pixels*/ 10000, kMaxKbps - 1);
EXPECT_FALSE(exp.BwHigh(NowMs(), kMaxKbps - 1));
EXPECT_FALSE(exp.BwHigh(AdvanceMs(2000), kMaxKbps - 1));
EXPECT_FALSE(exp.BwHigh(AdvanceMs(1), kMaxKbps));
EXPECT_TRUE(exp.BwHigh(AdvanceMs(2000), kMaxKbps));
}
TEST_F(QualityRampupExperimentTest, DoesNotReportBwHighIfBelowMinPixels) {
test::ScopedFieldTrials field_trials(
"WebRTC-Video-QualityRampupSettings/"
"min_pixels:10000,min_duration_ms:2000/");
auto exp = QualityRampupExperiment::ParseSettings();
const uint32_t kMaxKbps = 800;
exp.SetMaxBitrate(/*pixels*/ 9999, kMaxKbps);
const uint32_t kAvailableKbps = kMaxKbps;
EXPECT_FALSE(exp.BwHigh(NowMs(), kAvailableKbps));
EXPECT_FALSE(exp.BwHigh(AdvanceMs(2000), kAvailableKbps));
}
TEST_F(QualityRampupExperimentTest, ReportsBwHighWithMaxBitrateFactor) {
test::ScopedFieldTrials field_trials(
"WebRTC-Video-QualityRampupSettings/"
"min_pixels:10000,min_duration_ms:2000,max_bitrate_factor:1.5/");
auto exp = QualityRampupExperiment::ParseSettings();
EXPECT_EQ(10000, exp.MinPixels());
EXPECT_EQ(2000, exp.MinDurationMs());
EXPECT_EQ(1.5, exp.MaxBitrateFactor());
const uint32_t kMaxKbps = 800;
exp.SetMaxBitrate(/*pixels*/ 10000, kMaxKbps);
const uint32_t kAvailableKbps = kMaxKbps * 1.5;
EXPECT_FALSE(exp.BwHigh(NowMs(), kAvailableKbps - 1));
EXPECT_FALSE(exp.BwHigh(AdvanceMs(2000), kAvailableKbps - 1));
EXPECT_FALSE(exp.BwHigh(AdvanceMs(1), kAvailableKbps));
EXPECT_TRUE(exp.BwHigh(AdvanceMs(2000), kAvailableKbps));
}
TEST_F(QualityRampupExperimentTest, ReportsBwHigh) {
test::ScopedFieldTrials field_trials(
"WebRTC-Video-QualityRampupSettings/"
"min_pixels:10000,min_duration_ms:2000/");
auto exp = QualityRampupExperiment::ParseSettings();
const uint32_t kMaxKbps = 800;
exp.SetMaxBitrate(/*pixels*/ 10000, kMaxKbps);
const uint32_t kAvailableKbps = kMaxKbps;
EXPECT_FALSE(exp.BwHigh(NowMs(), kAvailableKbps));
EXPECT_FALSE(exp.BwHigh(AdvanceMs(2000 - 1), kAvailableKbps));
EXPECT_FALSE(exp.BwHigh(AdvanceMs(1), kAvailableKbps - 1)); // Below, reset.
EXPECT_FALSE(exp.BwHigh(AdvanceMs(1), kAvailableKbps));
EXPECT_FALSE(exp.BwHigh(AdvanceMs(2000 - 1), kAvailableKbps));
EXPECT_TRUE(exp.BwHigh(AdvanceMs(1), kAvailableKbps));
}
} // namespace
} // namespace webrtc

View File

@ -72,6 +72,11 @@ void FakeEncoder::SetMaxBitrate(int max_kbps) {
SetRates(current_rate_settings_);
}
void FakeEncoder::SetQp(int qp) {
rtc::CritScope cs(&crit_sect_);
qp_ = qp;
}
int32_t FakeEncoder::InitEncode(const VideoCodec* config,
const Settings& settings) {
rtc::CritScope cs(&crit_sect_);
@ -93,6 +98,7 @@ int32_t FakeEncoder::Encode(const VideoFrame& input_image,
VideoCodecMode mode;
bool keyframe;
uint32_t counter;
absl::optional<int> qp;
{
rtc::CritScope cs(&crit_sect_);
max_framerate = config_.maxFramerate;
@ -109,6 +115,7 @@ int32_t FakeEncoder::Encode(const VideoFrame& input_image,
keyframe = pending_keyframe_;
pending_keyframe_ = false;
counter = counter_++;
qp = qp_;
}
FrameInfo frame_info =
@ -134,6 +141,8 @@ int32_t FakeEncoder::Encode(const VideoFrame& input_image,
: VideoFrameType::kVideoFrameDelta;
encoded._encodedWidth = simulcast_streams[i].width;
encoded._encodedHeight = simulcast_streams[i].height;
if (qp)
encoded.qp_ = *qp;
encoded.SetSpatialIndex(i);
CodecSpecificInfo codec_specific;
std::unique_ptr<RTPFragmentationHeader> fragmentation =

View File

@ -41,6 +41,7 @@ class FakeEncoder : public VideoEncoder {
// Sets max bitrate. Not thread-safe, call before registering the encoder.
void SetMaxBitrate(int max_kbps);
void SetQp(int qp);
void SetFecControllerOverride(
FecControllerOverride* fec_controller_override) override;
@ -98,6 +99,7 @@ class FakeEncoder : public VideoEncoder {
uint32_t counter_ RTC_GUARDED_BY(crit_sect_);
rtc::CriticalSection crit_sect_;
bool used_layers_[kMaxSimulcastStreams];
absl::optional<int> qp_ RTC_GUARDED_BY(crit_sect_);
// Current byte debt to be payed over a number of frames.
// The debt is acquired by keyframes overshooting the bitrate target.

View File

@ -218,6 +218,7 @@ rtc_library("video_stream_encoder_impl") {
"../rtc_base/experiments:alr_experiment",
"../rtc_base/experiments:balanced_degradation_settings",
"../rtc_base/experiments:field_trial_parser",
"../rtc_base/experiments:quality_rampup_experiment",
"../rtc_base/experiments:quality_scaler_settings",
"../rtc_base/experiments:quality_scaling_experiment",
"../rtc_base/experiments:rate_control_settings",

View File

@ -483,6 +483,8 @@ VideoStreamEncoder::VideoStreamEncoder(
initial_framedrop_(0),
initial_framedrop_on_bwe_enabled_(
webrtc::field_trial::IsEnabled(kInitialFramedropFieldTrial)),
quality_rampup_done_(false),
quality_rampup_experiment_(QualityRampupExperiment::ParseSettings()),
quality_scaling_experiment_enabled_(QualityScalingExperiment::Enabled()),
source_proxy_(new VideoSourceProxy(this)),
sink_(nullptr),
@ -879,6 +881,8 @@ void VideoStreamEncoder::ReconfigureEncoder() {
send_codec_ = codec;
encoder_switch_experiment_.SetCodec(send_codec_.codecType);
quality_rampup_experiment_.SetMaxBitrate(
last_frame_info_->width * last_frame_info_->height, codec.maxBitrate);
// Keep the same encoder, as long as the video_format is unchanged.
// Encoder creation block is split in two since EncoderInfo needed to start
@ -1359,6 +1363,16 @@ void VideoStreamEncoder::MaybeEncodeVideoFrame(const VideoFrame& video_frame,
}
initial_framedrop_ = kMaxInitialFramedrop;
if (!quality_rampup_done_ && TryQualityRampup(now_ms) &&
GetConstAdaptCounter().ResolutionCount(kQuality) > 0 &&
GetConstAdaptCounter().TotalCount(kCpu) == 0) {
RTC_LOG(LS_INFO) << "Reset quality limitations.";
last_adaptation_request_.reset();
source_proxy_->ResetPixelFpsCount();
adapt_counters_.clear();
quality_rampup_done_ = true;
}
if (EncoderPaused()) {
// Storing references to a native buffer risks blocking frame capture.
if (video_frame.video_frame_buffer()->type() !=
@ -1900,6 +1914,25 @@ bool VideoStreamEncoder::DropDueToSize(uint32_t pixel_count) const {
return false;
}
bool VideoStreamEncoder::TryQualityRampup(int64_t now_ms) {
if (!quality_scaler_)
return false;
uint32_t bw_kbps = last_encoder_rate_settings_
? last_encoder_rate_settings_->rate_control
.bandwidth_allocation.kbps()
: 0;
if (quality_rampup_experiment_.BwHigh(now_ms, bw_kbps)) {
// Verify that encoder is at max bitrate and the QP is low.
if (encoder_start_bitrate_bps_ == send_codec_.maxBitrate * 1000 &&
quality_scaler_->QpFastFilterLow()) {
return true;
}
}
return false;
}
bool VideoStreamEncoder::AdaptDown(AdaptReason reason) {
RTC_DCHECK_RUN_ON(&encoder_queue_);
AdaptationRequest adaptation_request = {

View File

@ -31,6 +31,7 @@
#include "rtc_base/critical_section.h"
#include "rtc_base/event.h"
#include "rtc_base/experiments/balanced_degradation_settings.h"
#include "rtc_base/experiments/quality_rampup_experiment.h"
#include "rtc_base/experiments/quality_scaler_settings.h"
#include "rtc_base/experiments/rate_control_settings.h"
#include "rtc_base/numerics/exp_filter.h"
@ -158,6 +159,7 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface,
// Indicates wether frame should be dropped because the pixel count is too
// large for the current bitrate configuration.
bool DropDueToSize(uint32_t pixel_count) const RTC_RUN_ON(&encoder_queue_);
bool TryQualityRampup(int64_t now_ms) RTC_RUN_ON(&encoder_queue_);
// Implements EncodedImageCallback.
EncodedImageCallback::Result OnEncodedImage(
@ -238,6 +240,9 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface,
int initial_framedrop_;
const bool initial_framedrop_on_bwe_enabled_;
bool has_seen_first_significant_bwe_change_ = false;
bool quality_rampup_done_ RTC_GUARDED_BY(&encoder_queue_);
QualityRampupExperiment quality_rampup_experiment_
RTC_GUARDED_BY(&encoder_queue_);
const bool quality_scaling_experiment_enabled_;

View File

@ -55,6 +55,8 @@ using ::testing::StrictMock;
namespace {
const int kMinPixelsPerFrame = 320 * 180;
const int kQpLow = 1;
const int kQpHigh = 2;
const int kMinFramerateFps = 2;
const int kMinBalancedFramerateFps = 7;
const int64_t kFrameTimeoutMs = 100;
@ -683,8 +685,8 @@ class VideoStreamEncoderTest : public ::testing::Test {
EncoderInfo info;
if (initialized_ == EncoderState::kInitialized) {
if (quality_scaling_) {
info.scaling_settings =
VideoEncoder::ScalingSettings(1, 2, kMinPixelsPerFrame);
info.scaling_settings = VideoEncoder::ScalingSettings(
kQpLow, kQpHigh, kMinPixelsPerFrame);
}
info.is_hardware_accelerated = is_hardware_accelerated_;
for (int i = 0; i < kMaxSpatialLayers; ++i) {
@ -3704,6 +3706,69 @@ TEST_F(VideoStreamEncoderTest, InitialFrameDropActivatesWhenBweDrops) {
video_stream_encoder_->Stop();
}
TEST_F(VideoStreamEncoderTest, RampsUpInQualityWhenBwIsHigh) {
webrtc::test::ScopedFieldTrials field_trials(
"WebRTC-Video-QualityRampupSettings/min_pixels:1,min_duration_ms:2000/");
// Reset encoder for field trials to take effect.
VideoEncoderConfig config = video_encoder_config_.Copy();
config.max_bitrate_bps = kTargetBitrateBps;
ConfigureEncoder(std::move(config));
fake_encoder_.SetQp(kQpLow);
// Enable MAINTAIN_FRAMERATE preference.
AdaptingFrameForwarder source;
source.set_adaptation_enabled(true);
video_stream_encoder_->SetSource(&source,
DegradationPreference::MAINTAIN_FRAMERATE);
// Start at low bitrate.
const int kLowBitrateBps = 200000;
video_stream_encoder_->OnBitrateUpdated(DataRate::bps(kLowBitrateBps),
DataRate::bps(kLowBitrateBps),
DataRate::bps(kLowBitrateBps), 0, 0);
// Expect first frame to be dropped and resolution to be limited.
const int kWidth = 1280;
const int kHeight = 720;
const int64_t kFrameIntervalMs = 100;
int64_t timestamp_ms = kFrameIntervalMs;
source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
ExpectDroppedFrame();
EXPECT_LT(source.sink_wants().max_pixel_count, kWidth * kHeight);
// Increase bitrate to encoder max.
video_stream_encoder_->OnBitrateUpdated(DataRate::bps(config.max_bitrate_bps),
DataRate::bps(config.max_bitrate_bps),
DataRate::bps(config.max_bitrate_bps),
0, 0);
// Insert frames and advance |min_duration_ms|.
for (size_t i = 1; i <= 10; i++) {
timestamp_ms += kFrameIntervalMs;
source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
WaitForEncodedFrame(timestamp_ms);
}
EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution);
EXPECT_LT(source.sink_wants().max_pixel_count, kWidth * kHeight);
fake_clock_.AdvanceTime(TimeDelta::ms(2000));
// Insert frame should trigger high BW and release quality limitation.
timestamp_ms += kFrameIntervalMs;
source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
WaitForEncodedFrame(timestamp_ms);
VerifyFpsMaxResolutionMax(source.sink_wants());
// Frame should not be adapted.
timestamp_ms += kFrameIntervalMs;
source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
WaitForEncodedFrame(kWidth, kHeight);
EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution);
video_stream_encoder_->Stop();
}
TEST_F(VideoStreamEncoderTest,
ResolutionNotAdaptedForTooSmallFrame_MaintainFramerateMode) {
const int kTooSmallWidth = 10;