[Adaptation] Move Balanced MinFpsDiff logic to VideoStreamAdapter
This way can double adapt right away instead of relying on the qp scaler checking soon into the future. Bug: webrtc:11830 Change-Id: I8e878168303cf6a4c3edcf3997dd8ac2413a4479 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/181060 Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org> Reviewed-by: Åsa Persson <asapersson@webrtc.org> Commit-Queue: Evan Shrubsole <eshr@google.com> Cr-Commit-Position: refs/heads/master@{#31895}
This commit is contained in:
parent
8e95ea92b2
commit
a1c77f6d0d
@ -421,13 +421,43 @@ Adaptation VideoStreamAdapter::GetAdaptationDown() {
|
||||
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
||||
VideoStreamInputState input_state = input_state_provider_->InputState();
|
||||
++adaptation_validation_id_;
|
||||
return RestrictionsOrStateToAdaptation(GetAdaptationDownStep(input_state),
|
||||
input_state);
|
||||
RestrictionsOrState restrictions_or_state =
|
||||
GetAdaptationDownStep(input_state, current_restrictions_);
|
||||
|
||||
// Check for min_fps
|
||||
if (degradation_preference_ == DegradationPreference::BALANCED &&
|
||||
absl::holds_alternative<RestrictionsWithCounters>(
|
||||
restrictions_or_state)) {
|
||||
restrictions_or_state = AdaptIfFpsDiffInsufficient(
|
||||
input_state,
|
||||
absl::get<RestrictionsWithCounters>(restrictions_or_state));
|
||||
}
|
||||
return RestrictionsOrStateToAdaptation(restrictions_or_state, input_state);
|
||||
}
|
||||
|
||||
VideoStreamAdapter::RestrictionsOrState
|
||||
VideoStreamAdapter::AdaptIfFpsDiffInsufficient(
|
||||
const VideoStreamInputState& input_state,
|
||||
const RestrictionsWithCounters& restrictions) const {
|
||||
RTC_DCHECK_EQ(degradation_preference_, DegradationPreference::BALANCED);
|
||||
absl::optional<int> min_fps_diff =
|
||||
balanced_settings_.MinFpsDiff(input_state.frame_size_pixels().value());
|
||||
if (current_restrictions_.counters.fps_adaptations <
|
||||
restrictions.counters.fps_adaptations &&
|
||||
min_fps_diff && input_state.frames_per_second() > 0) {
|
||||
int fps_diff = input_state.frames_per_second() -
|
||||
restrictions.restrictions.max_frame_rate().value();
|
||||
if (fps_diff < min_fps_diff.value()) {
|
||||
return GetAdaptationDownStep(input_state, restrictions);
|
||||
}
|
||||
}
|
||||
return restrictions;
|
||||
}
|
||||
|
||||
VideoStreamAdapter::RestrictionsOrState
|
||||
VideoStreamAdapter::GetAdaptationDownStep(
|
||||
const VideoStreamInputState& input_state) const {
|
||||
const VideoStreamInputState& input_state,
|
||||
const RestrictionsWithCounters& current_restrictions) const {
|
||||
if (!HasSufficientInputForAdaptation(input_state)) {
|
||||
return Adaptation::Status::kInsufficientInput;
|
||||
}
|
||||
@ -445,7 +475,7 @@ VideoStreamAdapter::GetAdaptationDownStep(
|
||||
case DegradationPreference::BALANCED: {
|
||||
// Try scale down framerate, if lower.
|
||||
RestrictionsOrState decrease_frame_rate =
|
||||
DecreaseFramerate(input_state, current_restrictions_);
|
||||
DecreaseFramerate(input_state, current_restrictions);
|
||||
if (absl::holds_alternative<RestrictionsWithCounters>(
|
||||
decrease_frame_rate)) {
|
||||
return decrease_frame_rate;
|
||||
@ -454,10 +484,10 @@ VideoStreamAdapter::GetAdaptationDownStep(
|
||||
ABSL_FALLTHROUGH_INTENDED;
|
||||
}
|
||||
case DegradationPreference::MAINTAIN_FRAMERATE: {
|
||||
return DecreaseResolution(input_state, current_restrictions_);
|
||||
return DecreaseResolution(input_state, current_restrictions);
|
||||
}
|
||||
case DegradationPreference::MAINTAIN_RESOLUTION: {
|
||||
return DecreaseFramerate(input_state, current_restrictions_);
|
||||
return DecreaseFramerate(input_state, current_restrictions);
|
||||
}
|
||||
case DegradationPreference::DISABLED:
|
||||
return Adaptation::Status::kAdaptationDisabled;
|
||||
@ -608,7 +638,7 @@ VideoStreamAdapter::RestrictionsOrState
|
||||
VideoStreamAdapter::GetAdaptDownResolutionStepForBalanced(
|
||||
const VideoStreamInputState& input_state) const {
|
||||
// Adapt twice if the first adaptation did not decrease resolution.
|
||||
auto first_step = GetAdaptationDownStep(input_state);
|
||||
auto first_step = GetAdaptationDownStep(input_state, current_restrictions_);
|
||||
if (!absl::holds_alternative<RestrictionsWithCounters>(first_step)) {
|
||||
return first_step;
|
||||
}
|
||||
|
||||
@ -186,11 +186,16 @@ class VideoStreamAdapter {
|
||||
const VideoStreamInputState& input_state) const
|
||||
RTC_RUN_ON(&sequence_checker_);
|
||||
RestrictionsOrState GetAdaptationDownStep(
|
||||
const VideoStreamInputState& input_state) const
|
||||
const VideoStreamInputState& input_state,
|
||||
const RestrictionsWithCounters& current_restrictions) const
|
||||
RTC_RUN_ON(&sequence_checker_);
|
||||
RestrictionsOrState GetAdaptDownResolutionStepForBalanced(
|
||||
const VideoStreamInputState& input_state) const
|
||||
RTC_RUN_ON(&sequence_checker_);
|
||||
RestrictionsOrState AdaptIfFpsDiffInsufficient(
|
||||
const VideoStreamInputState& input_state,
|
||||
const RestrictionsWithCounters& restrictions) const
|
||||
RTC_RUN_ON(&sequence_checker_);
|
||||
|
||||
// TODO(https://crbug.com/webrtc/11771) |resource| is needed by the
|
||||
// AdaptationConstraint resources. Remove this parameter when it's removed.
|
||||
|
||||
@ -86,7 +86,6 @@ class QualityScaler::CheckQpTask {
|
||||
struct Result {
|
||||
bool observed_enough_frames = false;
|
||||
bool qp_usage_reported = false;
|
||||
bool clear_qp_samples = false;
|
||||
};
|
||||
|
||||
CheckQpTask(QualityScaler* quality_scaler, Result previous_task_result)
|
||||
@ -110,49 +109,36 @@ class QualityScaler::CheckQpTask {
|
||||
case QualityScaler::CheckQpResult::kInsufficientSamples: {
|
||||
result_.observed_enough_frames = false;
|
||||
// After this line, |this| may be deleted.
|
||||
DoCompleteTask();
|
||||
return;
|
||||
break;
|
||||
}
|
||||
case QualityScaler::CheckQpResult::kNormalQp: {
|
||||
result_.observed_enough_frames = true;
|
||||
// After this line, |this| may be deleted.
|
||||
DoCompleteTask();
|
||||
return;
|
||||
break;
|
||||
}
|
||||
case QualityScaler::CheckQpResult::kHighQp: {
|
||||
result_.observed_enough_frames = true;
|
||||
result_.qp_usage_reported = true;
|
||||
state_ = State::kAwaitingQpUsageHandled;
|
||||
rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface>
|
||||
callback = ConstructCallback();
|
||||
quality_scaler_->fast_rampup_ = false;
|
||||
// After this line, |this| may be deleted.
|
||||
quality_scaler_->handler_->OnReportQpUsageHigh(callback);
|
||||
return;
|
||||
quality_scaler_->handler_->OnReportQpUsageHigh();
|
||||
quality_scaler_->ClearSamples();
|
||||
break;
|
||||
}
|
||||
case QualityScaler::CheckQpResult::kLowQp: {
|
||||
result_.observed_enough_frames = true;
|
||||
result_.qp_usage_reported = true;
|
||||
state_ = State::kAwaitingQpUsageHandled;
|
||||
rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface>
|
||||
callback = ConstructCallback();
|
||||
// After this line, |this| may be deleted.
|
||||
quality_scaler_->handler_->OnReportQpUsageLow(callback);
|
||||
return;
|
||||
quality_scaler_->handler_->OnReportQpUsageLow();
|
||||
quality_scaler_->ClearSamples();
|
||||
break;
|
||||
}
|
||||
}
|
||||
state_ = State::kCompleted;
|
||||
// Starting the next task deletes the pending task. After this line,
|
||||
// |this| has been deleted.
|
||||
quality_scaler_->StartNextCheckQpTask();
|
||||
}),
|
||||
GetCheckingQpDelayMs());
|
||||
}
|
||||
|
||||
void OnQpUsageHandled(bool clear_qp_samples) {
|
||||
RTC_DCHECK_EQ(state_, State::kAwaitingQpUsageHandled);
|
||||
result_.clear_qp_samples = clear_qp_samples;
|
||||
if (clear_qp_samples)
|
||||
quality_scaler_->ClearSamples();
|
||||
DoCompleteTask();
|
||||
}
|
||||
|
||||
bool HasCompletedTask() const { return state_ == State::kCompleted; }
|
||||
|
||||
Result result() const {
|
||||
@ -164,15 +150,9 @@ class QualityScaler::CheckQpTask {
|
||||
enum class State {
|
||||
kNotStarted,
|
||||
kCheckingQp,
|
||||
kAwaitingQpUsageHandled,
|
||||
kCompleted,
|
||||
};
|
||||
|
||||
// Defined after the definition of QualityScaler::CheckQpTaskHandlerCallback.
|
||||
// Gets around a forward declaration issue.
|
||||
rtc::scoped_refptr<QualityScaler::CheckQpTaskHandlerCallback>
|
||||
ConstructCallback();
|
||||
|
||||
// Determines the sampling period of CheckQpTasks.
|
||||
int64_t GetCheckingQpDelayMs() const {
|
||||
RTC_DCHECK_RUN_ON(&quality_scaler_->task_checker_);
|
||||
@ -184,10 +164,6 @@ class QualityScaler::CheckQpTask {
|
||||
// Use half the interval while waiting for enough frames.
|
||||
return quality_scaler_->sampling_period_ms_ / 2;
|
||||
}
|
||||
if (!previous_task_result_.clear_qp_samples) {
|
||||
// Check shortly again.
|
||||
return quality_scaler_->sampling_period_ms_ / 8;
|
||||
}
|
||||
if (quality_scaler_->scale_factor_ &&
|
||||
!previous_task_result_.qp_usage_reported) {
|
||||
// Last CheckQp did not call AdaptDown/Up, possibly reduce interval.
|
||||
@ -198,15 +174,6 @@ class QualityScaler::CheckQpTask {
|
||||
quality_scaler_->initial_scale_factor_;
|
||||
}
|
||||
|
||||
void DoCompleteTask() {
|
||||
RTC_DCHECK(state_ == State::kCheckingQp ||
|
||||
state_ == State::kAwaitingQpUsageHandled);
|
||||
state_ = State::kCompleted;
|
||||
// Starting the next task deletes the pending task. After this line, |this|
|
||||
// has been deleted.
|
||||
quality_scaler_->StartNextCheckQpTask();
|
||||
}
|
||||
|
||||
QualityScaler* const quality_scaler_;
|
||||
State state_;
|
||||
const Result previous_task_result_;
|
||||
@ -215,39 +182,6 @@ class QualityScaler::CheckQpTask {
|
||||
rtc::WeakPtrFactory<CheckQpTask> weak_ptr_factory_;
|
||||
};
|
||||
|
||||
class QualityScaler::CheckQpTaskHandlerCallback
|
||||
: public QualityScalerQpUsageHandlerCallbackInterface {
|
||||
public:
|
||||
CheckQpTaskHandlerCallback(
|
||||
rtc::WeakPtr<QualityScaler::CheckQpTask> check_qp_task)
|
||||
: QualityScalerQpUsageHandlerCallbackInterface(),
|
||||
check_qp_task_(std::move(check_qp_task)),
|
||||
was_handled_(false) {}
|
||||
|
||||
~CheckQpTaskHandlerCallback() { RTC_DCHECK(was_handled_); }
|
||||
|
||||
void OnQpUsageHandled(bool clear_qp_samples) {
|
||||
RTC_DCHECK(!was_handled_);
|
||||
was_handled_ = true;
|
||||
if (!check_qp_task_) {
|
||||
// The task has been cancelled through destruction; the result of the
|
||||
// operation is ignored.
|
||||
return;
|
||||
}
|
||||
check_qp_task_->OnQpUsageHandled(clear_qp_samples);
|
||||
}
|
||||
|
||||
private:
|
||||
// The callback may outlive the QualityScaler and its task.
|
||||
rtc::WeakPtr<QualityScaler::CheckQpTask> const check_qp_task_;
|
||||
bool was_handled_;
|
||||
};
|
||||
|
||||
rtc::scoped_refptr<QualityScaler::CheckQpTaskHandlerCallback>
|
||||
QualityScaler::CheckQpTask::ConstructCallback() {
|
||||
return new CheckQpTaskHandlerCallback(weak_ptr_factory_.GetWeakPtr());
|
||||
}
|
||||
|
||||
QualityScaler::QualityScaler(QualityScalerQpUsageHandlerInterface* handler,
|
||||
VideoEncoder::QpThresholds thresholds)
|
||||
: QualityScaler(handler, thresholds, kMeasureMs) {}
|
||||
@ -401,10 +335,4 @@ void QualityScaler::ClearSamples() {
|
||||
|
||||
QualityScalerQpUsageHandlerInterface::~QualityScalerQpUsageHandlerInterface() {}
|
||||
|
||||
QualityScalerQpUsageHandlerCallbackInterface::
|
||||
QualityScalerQpUsageHandlerCallbackInterface() {}
|
||||
|
||||
QualityScalerQpUsageHandlerCallbackInterface::
|
||||
~QualityScalerQpUsageHandlerCallbackInterface() {}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -112,38 +112,8 @@ class QualityScalerQpUsageHandlerInterface {
|
||||
public:
|
||||
virtual ~QualityScalerQpUsageHandlerInterface();
|
||||
|
||||
// Reacts to QP usage being too high or too low. The |callback| MUST be
|
||||
// invoked when the handler is done, allowing the QualityScaler to resume
|
||||
// checking for QP.
|
||||
virtual void OnReportQpUsageHigh(
|
||||
rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface>
|
||||
callback) = 0;
|
||||
virtual void OnReportQpUsageLow(
|
||||
rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface>
|
||||
callback) = 0;
|
||||
};
|
||||
|
||||
// When QP is reported as high or low by the QualityScaler, it pauses checking
|
||||
// for QP until the QP usage has been handled. When OnQpUsageHandled() is
|
||||
// invoked, the QualityScaler resumes checking for QP. This ensures that if the
|
||||
// stream is reconfigured in response to QP usage we do not include QP samples
|
||||
// from before the reconfiguration the next time we check for QP.
|
||||
//
|
||||
// OnQpUsageHandled() MUST be invoked exactly once before this object is
|
||||
// destroyed.
|
||||
class QualityScalerQpUsageHandlerCallbackInterface
|
||||
: public rtc::RefCountedObject<rtc::RefCountInterface> {
|
||||
public:
|
||||
virtual ~QualityScalerQpUsageHandlerCallbackInterface();
|
||||
|
||||
// If |clear_qp_samples| is true, existing QP samples are cleared before the
|
||||
// next time QualityScaler checks for QP. This is usually a good idea when the
|
||||
// stream is reconfigured. If |clear_qp_samples| is false, samples are not
|
||||
// cleared and QualityScaler increases its frequency of checking for QP.
|
||||
virtual void OnQpUsageHandled(bool clear_qp_samples) = 0;
|
||||
|
||||
protected:
|
||||
QualityScalerQpUsageHandlerCallbackInterface();
|
||||
virtual void OnReportQpUsageHigh() = 0;
|
||||
virtual void OnReportQpUsageLow() = 0;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -28,37 +28,24 @@ static const int kMinFramesNeededToScale = 60; // From quality_scaler.cc.
|
||||
static const size_t kDefaultTimeoutMs = 150;
|
||||
} // namespace
|
||||
|
||||
class MockQpUsageHandler : public QualityScalerQpUsageHandlerInterface {
|
||||
class FakeQpUsageHandler : public QualityScalerQpUsageHandlerInterface {
|
||||
public:
|
||||
virtual ~MockQpUsageHandler() {}
|
||||
~FakeQpUsageHandler() override = default;
|
||||
|
||||
// QualityScalerQpUsageHandlerInterface implementation.
|
||||
void OnReportQpUsageHigh(
|
||||
rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface> callback)
|
||||
override {
|
||||
callback_ = callback;
|
||||
void OnReportQpUsageHigh() override {
|
||||
adapt_down_events_++;
|
||||
event.Set();
|
||||
if (synchronously_invoke_callback)
|
||||
callback_->OnQpUsageHandled(true);
|
||||
}
|
||||
|
||||
void OnReportQpUsageLow(
|
||||
rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface> callback)
|
||||
override {
|
||||
callback_ = callback;
|
||||
void OnReportQpUsageLow() override {
|
||||
adapt_up_events_++;
|
||||
event.Set();
|
||||
if (synchronously_invoke_callback)
|
||||
callback_->OnQpUsageHandled(true);
|
||||
}
|
||||
|
||||
rtc::Event event;
|
||||
int adapt_up_events_ = 0;
|
||||
int adapt_down_events_ = 0;
|
||||
bool synchronously_invoke_callback = true;
|
||||
rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface> callback_ =
|
||||
nullptr;
|
||||
};
|
||||
|
||||
// Pass a lower sampling period to speed up the tests.
|
||||
@ -83,7 +70,7 @@ class QualityScalerTest : public ::testing::Test,
|
||||
QualityScalerTest()
|
||||
: scoped_field_trial_(GetParam()),
|
||||
task_queue_("QualityScalerTestQueue"),
|
||||
handler_(new MockQpUsageHandler()) {
|
||||
handler_(std::make_unique<FakeQpUsageHandler>()) {
|
||||
task_queue_.SendTask(
|
||||
[this] {
|
||||
qs_ = std::unique_ptr<QualityScaler>(new QualityScalerUnderTest(
|
||||
@ -92,7 +79,7 @@ class QualityScalerTest : public ::testing::Test,
|
||||
RTC_FROM_HERE);
|
||||
}
|
||||
|
||||
~QualityScalerTest() {
|
||||
~QualityScalerTest() override {
|
||||
task_queue_.SendTask([this] { qs_ = nullptr; }, RTC_FROM_HERE);
|
||||
}
|
||||
|
||||
@ -121,7 +108,7 @@ class QualityScalerTest : public ::testing::Test,
|
||||
test::ScopedFieldTrials scoped_field_trial_;
|
||||
TaskQueueForTest task_queue_;
|
||||
std::unique_ptr<QualityScaler> qs_;
|
||||
std::unique_ptr<MockQpUsageHandler> handler_;
|
||||
std::unique_ptr<FakeQpUsageHandler> handler_;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
@ -282,34 +269,4 @@ TEST_P(QualityScalerTest, ScalesDownAndBackUpWithMinFramesNeeded) {
|
||||
EXPECT_EQ(1, handler_->adapt_up_events_);
|
||||
}
|
||||
|
||||
TEST_P(QualityScalerTest, CheckingQpAgainRequiresResolvingCallback) {
|
||||
handler_->synchronously_invoke_callback = false;
|
||||
task_queue_.SendTask([this] { TriggerScale(kScaleDown); }, RTC_FROM_HERE);
|
||||
EXPECT_TRUE(handler_->event.Wait(kDefaultTimeoutMs));
|
||||
EXPECT_EQ(1, handler_->adapt_down_events_);
|
||||
// Without invoking the callback, another downscale should not happen.
|
||||
handler_->event.Reset();
|
||||
rtc::Event event;
|
||||
task_queue_.SendTask(
|
||||
[this, &event] {
|
||||
TriggerScale(kScaleDown);
|
||||
event.Set();
|
||||
},
|
||||
RTC_FROM_HERE);
|
||||
EXPECT_TRUE(event.Wait(kDefaultTimeoutMs));
|
||||
EXPECT_FALSE(handler_->event.Wait(0));
|
||||
EXPECT_EQ(1, handler_->adapt_down_events_);
|
||||
// Resume checking for QP again by invoking the callback.
|
||||
task_queue_.SendTask(
|
||||
[this] {
|
||||
handler_->callback_->OnQpUsageHandled(true);
|
||||
TriggerScale(kScaleDown);
|
||||
},
|
||||
RTC_FROM_HERE);
|
||||
EXPECT_TRUE(handler_->event.Wait(kDefaultTimeoutMs));
|
||||
EXPECT_EQ(2, handler_->adapt_down_events_);
|
||||
task_queue_.SendTask([this] { handler_->callback_->OnQpUsageHandled(true); },
|
||||
RTC_FROM_HERE);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -74,13 +74,13 @@ if (rtc_include_tests) {
|
||||
deps = [
|
||||
":video_adaptation",
|
||||
"../../api:scoped_refptr",
|
||||
"../../api/task_queue:default_task_queue_factory",
|
||||
"../../api/task_queue:task_queue",
|
||||
"../../api/video:encoded_image",
|
||||
"../../api/video:video_adaptation",
|
||||
"../../api/video:video_frame_i420",
|
||||
"../../api/video_codecs:video_codecs_api",
|
||||
"../../call/adaptation:resource_adaptation",
|
||||
"../../call/adaptation:resource_adaptation_test_utilities",
|
||||
"../../modules/video_coding:video_coding_utility",
|
||||
"../../rtc_base:checks",
|
||||
"../../rtc_base:logging",
|
||||
|
||||
@ -37,16 +37,12 @@ QualityScalerResource::QualityScalerResource(
|
||||
: VideoStreamEncoderResource("QualityScalerResource"),
|
||||
quality_scaler_(nullptr),
|
||||
last_underuse_due_to_disabled_timestamp_ms_(absl::nullopt),
|
||||
num_handled_callbacks_(0),
|
||||
pending_callbacks_(),
|
||||
degradation_preference_provider_(degradation_preference_provider),
|
||||
clear_qp_samples_(false) {
|
||||
degradation_preference_provider_(degradation_preference_provider) {
|
||||
RTC_CHECK(degradation_preference_provider_);
|
||||
}
|
||||
|
||||
QualityScalerResource::~QualityScalerResource() {
|
||||
RTC_DCHECK(!quality_scaler_);
|
||||
RTC_DCHECK(pending_callbacks_.empty());
|
||||
}
|
||||
|
||||
bool QualityScalerResource::is_started() const {
|
||||
@ -66,7 +62,6 @@ void QualityScalerResource::StopCheckForOveruse() {
|
||||
RTC_DCHECK_RUN_ON(encoder_queue());
|
||||
// Ensure we have no pending callbacks. This makes it safe to destroy the
|
||||
// QualityScaler and even task queues with tasks in-flight.
|
||||
AbortPendingCallbacks();
|
||||
quality_scaler_.reset();
|
||||
}
|
||||
|
||||
@ -126,115 +121,29 @@ void QualityScalerResource::OnFrameDropped(
|
||||
}
|
||||
}
|
||||
|
||||
void QualityScalerResource::OnReportQpUsageHigh(
|
||||
rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface> callback) {
|
||||
void QualityScalerResource::OnReportQpUsageHigh() {
|
||||
RTC_DCHECK_RUN_ON(encoder_queue());
|
||||
size_t callback_id = QueuePendingCallback(callback);
|
||||
// Reference counting guarantees that this object is still alive by the time
|
||||
// the task is executed.
|
||||
MaybePostTaskToResourceAdaptationQueue(
|
||||
[this_ref = rtc::scoped_refptr<QualityScalerResource>(this),
|
||||
callback_id] {
|
||||
[this_ref = rtc::scoped_refptr<QualityScalerResource>(this)] {
|
||||
RTC_DCHECK_RUN_ON(this_ref->resource_adaptation_queue());
|
||||
this_ref->clear_qp_samples_ = false;
|
||||
// If this OnResourceUsageStateMeasured() triggers an adaptation,
|
||||
// OnAdaptationApplied() will occur between this line and the next. This
|
||||
// allows modifying |clear_qp_samples_| based on the adaptation.
|
||||
this_ref->OnResourceUsageStateMeasured(ResourceUsageState::kOveruse);
|
||||
this_ref->HandlePendingCallback(callback_id,
|
||||
this_ref->clear_qp_samples_);
|
||||
});
|
||||
}
|
||||
|
||||
void QualityScalerResource::OnReportQpUsageLow(
|
||||
rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface> callback) {
|
||||
void QualityScalerResource::OnReportQpUsageLow() {
|
||||
RTC_DCHECK_RUN_ON(encoder_queue());
|
||||
size_t callback_id = QueuePendingCallback(callback);
|
||||
// Reference counting guarantees that this object is still alive by the time
|
||||
// the task is executed.
|
||||
MaybePostTaskToResourceAdaptationQueue(
|
||||
[this_ref = rtc::scoped_refptr<QualityScalerResource>(this),
|
||||
callback_id] {
|
||||
[this_ref = rtc::scoped_refptr<QualityScalerResource>(this)] {
|
||||
RTC_DCHECK_RUN_ON(this_ref->resource_adaptation_queue());
|
||||
this_ref->OnResourceUsageStateMeasured(ResourceUsageState::kUnderuse);
|
||||
this_ref->HandlePendingCallback(callback_id, true);
|
||||
});
|
||||
}
|
||||
|
||||
void QualityScalerResource::OnAdaptationApplied(
|
||||
const VideoStreamInputState& input_state,
|
||||
const VideoSourceRestrictions& restrictions_before,
|
||||
const VideoSourceRestrictions& restrictions_after,
|
||||
rtc::scoped_refptr<Resource> reason_resource) {
|
||||
RTC_DCHECK_RUN_ON(resource_adaptation_queue());
|
||||
// We only clear QP samples on adaptations triggered by the QualityScaler.
|
||||
if (reason_resource != this)
|
||||
return;
|
||||
clear_qp_samples_ = true;
|
||||
// If we're in "balanced" and the frame rate before and after adaptation did
|
||||
// not differ that much, don't clear the QP samples and instead check for QP
|
||||
// again in a short amount of time. This may trigger adapting down again soon.
|
||||
// TODO(hbos): Can this be simplified by getting rid of special casing logic?
|
||||
// For example, we could decide whether or not to clear QP samples based on
|
||||
// how big the adaptation step was alone (regardless of degradation preference
|
||||
// or what resource triggered the adaptation) and the QualityScaler could
|
||||
// check for QP when it had enough QP samples rather than at a variable
|
||||
// interval whose delay is calculated based on events such as these. Now there
|
||||
// is much dependency on a specific OnReportQpUsageHigh() event and "balanced"
|
||||
// but adaptations happening might not align with QualityScaler's CheckQpTask.
|
||||
if (degradation_preference_provider_->degradation_preference() ==
|
||||
DegradationPreference::BALANCED &&
|
||||
DidDecreaseFrameRate(restrictions_before, restrictions_after)) {
|
||||
absl::optional<int> min_diff = BalancedDegradationSettings().MinFpsDiff(
|
||||
input_state.frame_size_pixels().value());
|
||||
if (min_diff && input_state.frames_per_second() > 0) {
|
||||
int fps_diff = input_state.frames_per_second() -
|
||||
restrictions_after.max_frame_rate().value();
|
||||
if (fps_diff < min_diff.value()) {
|
||||
clear_qp_samples_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t QualityScalerResource::QueuePendingCallback(
|
||||
rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface> callback) {
|
||||
RTC_DCHECK_RUN_ON(encoder_queue());
|
||||
pending_callbacks_.push(callback);
|
||||
// The ID of a callback is its sequence number (1, 2, 3...).
|
||||
return num_handled_callbacks_ + pending_callbacks_.size();
|
||||
}
|
||||
|
||||
void QualityScalerResource::HandlePendingCallback(size_t callback_id,
|
||||
bool clear_qp_samples) {
|
||||
RTC_DCHECK_RUN_ON(resource_adaptation_queue());
|
||||
// Reference counting guarantees that this object is still alive by the time
|
||||
// the task is executed.
|
||||
encoder_queue()->PostTask(
|
||||
ToQueuedTask([this_ref = rtc::scoped_refptr<QualityScalerResource>(this),
|
||||
callback_id, clear_qp_samples] {
|
||||
RTC_DCHECK_RUN_ON(this_ref->encoder_queue());
|
||||
if (this_ref->num_handled_callbacks_ >= callback_id) {
|
||||
// The callback with this ID has already been handled.
|
||||
// This happens if AbortPendingCallbacks() is called while the task is
|
||||
// in flight.
|
||||
return;
|
||||
}
|
||||
RTC_DCHECK(!this_ref->pending_callbacks_.empty());
|
||||
this_ref->pending_callbacks_.front()->OnQpUsageHandled(
|
||||
clear_qp_samples);
|
||||
++this_ref->num_handled_callbacks_;
|
||||
this_ref->pending_callbacks_.pop();
|
||||
}));
|
||||
}
|
||||
|
||||
void QualityScalerResource::AbortPendingCallbacks() {
|
||||
RTC_DCHECK_RUN_ON(encoder_queue());
|
||||
while (!pending_callbacks_.empty()) {
|
||||
pending_callbacks_.front()->OnQpUsageHandled(false);
|
||||
++num_handled_callbacks_;
|
||||
pending_callbacks_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -31,7 +31,6 @@ namespace webrtc {
|
||||
|
||||
// Handles interaction with the QualityScaler.
|
||||
class QualityScalerResource : public VideoStreamEncoderResource,
|
||||
public AdaptationListener,
|
||||
public QualityScalerQpUsageHandlerInterface {
|
||||
public:
|
||||
static rtc::scoped_refptr<QualityScalerResource> Create(
|
||||
@ -53,27 +52,10 @@ class QualityScalerResource : public VideoStreamEncoderResource,
|
||||
void OnFrameDropped(EncodedImageCallback::DropReason reason);
|
||||
|
||||
// QualityScalerQpUsageHandlerInterface implementation.
|
||||
void OnReportQpUsageHigh(
|
||||
rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface> callback)
|
||||
override;
|
||||
void OnReportQpUsageLow(
|
||||
rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface> callback)
|
||||
override;
|
||||
|
||||
// AdaptationListener implementation.
|
||||
void OnAdaptationApplied(
|
||||
const VideoStreamInputState& input_state,
|
||||
const VideoSourceRestrictions& restrictions_before,
|
||||
const VideoSourceRestrictions& restrictions_after,
|
||||
rtc::scoped_refptr<Resource> reason_resource) override;
|
||||
void OnReportQpUsageHigh() override;
|
||||
void OnReportQpUsageLow() override;
|
||||
|
||||
private:
|
||||
size_t QueuePendingCallback(
|
||||
rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface>
|
||||
callback);
|
||||
void HandlePendingCallback(size_t callback_id, bool clear_qp_samples);
|
||||
void AbortPendingCallbacks();
|
||||
|
||||
// Members accessed on the encoder queue.
|
||||
std::unique_ptr<QualityScaler> quality_scaler_
|
||||
RTC_GUARDED_BY(encoder_queue());
|
||||
@ -82,18 +64,7 @@ class QualityScalerResource : public VideoStreamEncoderResource,
|
||||
// make sure underuse reporting is not too spammy.
|
||||
absl::optional<int64_t> last_underuse_due_to_disabled_timestamp_ms_
|
||||
RTC_GUARDED_BY(encoder_queue());
|
||||
// Every OnReportQpUsageHigh/Low() operation has a callback that MUST be
|
||||
// invoked on the encoder_queue(). Because usage measurements are reported on
|
||||
// the encoder_queue() but handled by the processor on the the
|
||||
// resource_adaptation_queue_(), handling a measurement entails a task queue
|
||||
// "ping" round-trip. Multiple callbacks in-flight is thus possible.
|
||||
size_t num_handled_callbacks_ RTC_GUARDED_BY(encoder_queue());
|
||||
std::queue<rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface>>
|
||||
pending_callbacks_ RTC_GUARDED_BY(encoder_queue());
|
||||
DegradationPreferenceProvider* const degradation_preference_provider_;
|
||||
|
||||
// Members accessed on the adaptation queue.
|
||||
bool clear_qp_samples_ RTC_GUARDED_BY(resource_adaptation_queue());
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -13,58 +13,26 @@
|
||||
#include <memory>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/task_queue/default_task_queue_factory.h"
|
||||
#include "api/task_queue/task_queue_factory.h"
|
||||
#include "api/video_codecs/video_encoder.h"
|
||||
#include "call/adaptation/test/mock_resource_listener.h"
|
||||
#include "rtc_base/event.h"
|
||||
#include "rtc_base/location.h"
|
||||
#include "rtc_base/task_queue.h"
|
||||
#include "rtc_base/task_queue_for_test.h"
|
||||
#include "test/gmock.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
using testing::_;
|
||||
using testing::Eq;
|
||||
using testing::StrictMock;
|
||||
|
||||
namespace {
|
||||
|
||||
const int kDefaultTimeout = 5000;
|
||||
|
||||
class FakeQualityScalerQpUsageHandlerCallback
|
||||
: public QualityScalerQpUsageHandlerCallbackInterface {
|
||||
public:
|
||||
explicit FakeQualityScalerQpUsageHandlerCallback(
|
||||
rtc::TaskQueue* encoder_queue)
|
||||
: QualityScalerQpUsageHandlerCallbackInterface(),
|
||||
encoder_queue_(encoder_queue),
|
||||
is_handled_(false),
|
||||
qp_usage_handled_event_(true /* manual_reset */, false),
|
||||
clear_qp_samples_result_(absl::nullopt) {}
|
||||
~FakeQualityScalerQpUsageHandlerCallback() override {
|
||||
RTC_DCHECK(is_handled_)
|
||||
<< "The callback was destroyed without being invoked.";
|
||||
}
|
||||
|
||||
void OnQpUsageHandled(bool clear_qp_samples) override {
|
||||
ASSERT_TRUE(encoder_queue_->IsCurrent());
|
||||
RTC_DCHECK(!is_handled_);
|
||||
clear_qp_samples_result_ = clear_qp_samples;
|
||||
is_handled_ = true;
|
||||
qp_usage_handled_event_.Set();
|
||||
}
|
||||
|
||||
bool is_handled() const { return is_handled_; }
|
||||
rtc::Event* qp_usage_handled_event() { return &qp_usage_handled_event_; }
|
||||
absl::optional<bool> clear_qp_samples_result() const {
|
||||
return clear_qp_samples_result_;
|
||||
}
|
||||
|
||||
private:
|
||||
rtc::TaskQueue* const encoder_queue_;
|
||||
bool is_handled_;
|
||||
rtc::Event qp_usage_handled_event_;
|
||||
absl::optional<bool> clear_qp_samples_result_;
|
||||
};
|
||||
|
||||
class FakeDegradationPreferenceProvider : public DegradationPreferenceProvider {
|
||||
public:
|
||||
~FakeDegradationPreferenceProvider() override {}
|
||||
~FakeDegradationPreferenceProvider() override = default;
|
||||
|
||||
DegradationPreference degradation_preference() const override {
|
||||
return DegradationPreference::MAINTAIN_FRAMERATE;
|
||||
@ -76,95 +44,61 @@ class FakeDegradationPreferenceProvider : public DegradationPreferenceProvider {
|
||||
class QualityScalerResourceTest : public ::testing::Test {
|
||||
public:
|
||||
QualityScalerResourceTest()
|
||||
: task_queue_factory_(CreateDefaultTaskQueueFactory()),
|
||||
resource_adaptation_queue_(task_queue_factory_->CreateTaskQueue(
|
||||
"ResourceAdaptationQueue",
|
||||
TaskQueueFactory::Priority::NORMAL)),
|
||||
encoder_queue_(task_queue_factory_->CreateTaskQueue(
|
||||
"EncoderQueue",
|
||||
TaskQueueFactory::Priority::NORMAL)),
|
||||
degradation_preference_provider_(),
|
||||
: resource_adaptation_queue_("ResourceAdaptationQueue",
|
||||
TaskQueueFactory::Priority::NORMAL),
|
||||
encoder_queue_("EncoderQueue", TaskQueueFactory::Priority::NORMAL),
|
||||
quality_scaler_resource_(
|
||||
QualityScalerResource::Create(°radation_preference_provider_)) {
|
||||
quality_scaler_resource_->RegisterEncoderTaskQueue(encoder_queue_.Get());
|
||||
quality_scaler_resource_->RegisterAdaptationTaskQueue(
|
||||
resource_adaptation_queue_.Get());
|
||||
rtc::Event event;
|
||||
encoder_queue_.PostTask([this, &event] {
|
||||
quality_scaler_resource_->StartCheckForOveruse(
|
||||
VideoEncoder::QpThresholds());
|
||||
event.Set();
|
||||
});
|
||||
event.Wait(kDefaultTimeout);
|
||||
encoder_queue_.SendTask(
|
||||
[this] {
|
||||
quality_scaler_resource_->SetResourceListener(
|
||||
&fake_resource_listener_);
|
||||
quality_scaler_resource_->StartCheckForOveruse(
|
||||
VideoEncoder::QpThresholds());
|
||||
},
|
||||
RTC_FROM_HERE);
|
||||
}
|
||||
|
||||
~QualityScalerResourceTest() {
|
||||
rtc::Event event;
|
||||
encoder_queue_.PostTask([this, &event] {
|
||||
quality_scaler_resource_->StopCheckForOveruse();
|
||||
event.Set();
|
||||
});
|
||||
event.Wait(kDefaultTimeout);
|
||||
~QualityScalerResourceTest() override {
|
||||
encoder_queue_.SendTask(
|
||||
[this] {
|
||||
quality_scaler_resource_->StopCheckForOveruse();
|
||||
quality_scaler_resource_->SetResourceListener(nullptr);
|
||||
},
|
||||
RTC_FROM_HERE);
|
||||
}
|
||||
|
||||
protected:
|
||||
const std::unique_ptr<TaskQueueFactory> task_queue_factory_;
|
||||
rtc::TaskQueue resource_adaptation_queue_;
|
||||
rtc::TaskQueue encoder_queue_;
|
||||
TaskQueueForTest resource_adaptation_queue_;
|
||||
TaskQueueForTest encoder_queue_;
|
||||
StrictMock<MockResourceListener> fake_resource_listener_;
|
||||
FakeDegradationPreferenceProvider degradation_preference_provider_;
|
||||
rtc::scoped_refptr<QualityScalerResource> quality_scaler_resource_;
|
||||
};
|
||||
|
||||
TEST_F(QualityScalerResourceTest, ReportQpHigh) {
|
||||
rtc::scoped_refptr<FakeQualityScalerQpUsageHandlerCallback> callback =
|
||||
new FakeQualityScalerQpUsageHandlerCallback(&encoder_queue_);
|
||||
encoder_queue_.PostTask([this, callback] {
|
||||
quality_scaler_resource_->OnReportQpUsageHigh(callback);
|
||||
});
|
||||
callback->qp_usage_handled_event()->Wait(kDefaultTimeout);
|
||||
EXPECT_CALL(fake_resource_listener_,
|
||||
OnResourceUsageStateMeasured(Eq(quality_scaler_resource_),
|
||||
Eq(ResourceUsageState::kOveruse)));
|
||||
encoder_queue_.SendTask(
|
||||
[this] { quality_scaler_resource_->OnReportQpUsageHigh(); },
|
||||
RTC_FROM_HERE);
|
||||
// Wait for adapt queue to clear since that signals the resource listener.
|
||||
resource_adaptation_queue_.WaitForPreviouslyPostedTasks();
|
||||
}
|
||||
|
||||
TEST_F(QualityScalerResourceTest, ReportQpLow) {
|
||||
rtc::scoped_refptr<FakeQualityScalerQpUsageHandlerCallback> callback =
|
||||
new FakeQualityScalerQpUsageHandlerCallback(&encoder_queue_);
|
||||
encoder_queue_.PostTask([this, callback] {
|
||||
quality_scaler_resource_->OnReportQpUsageLow(callback);
|
||||
});
|
||||
callback->qp_usage_handled_event()->Wait(kDefaultTimeout);
|
||||
}
|
||||
|
||||
TEST_F(QualityScalerResourceTest, MultipleCallbacksInFlight) {
|
||||
rtc::scoped_refptr<FakeQualityScalerQpUsageHandlerCallback> callback1 =
|
||||
new FakeQualityScalerQpUsageHandlerCallback(&encoder_queue_);
|
||||
rtc::scoped_refptr<FakeQualityScalerQpUsageHandlerCallback> callback2 =
|
||||
new FakeQualityScalerQpUsageHandlerCallback(&encoder_queue_);
|
||||
rtc::scoped_refptr<FakeQualityScalerQpUsageHandlerCallback> callback3 =
|
||||
new FakeQualityScalerQpUsageHandlerCallback(&encoder_queue_);
|
||||
encoder_queue_.PostTask([this, callback1, callback2, callback3] {
|
||||
quality_scaler_resource_->OnReportQpUsageHigh(callback1);
|
||||
quality_scaler_resource_->OnReportQpUsageLow(callback2);
|
||||
quality_scaler_resource_->OnReportQpUsageHigh(callback3);
|
||||
});
|
||||
callback1->qp_usage_handled_event()->Wait(kDefaultTimeout);
|
||||
callback2->qp_usage_handled_event()->Wait(kDefaultTimeout);
|
||||
callback3->qp_usage_handled_event()->Wait(kDefaultTimeout);
|
||||
}
|
||||
|
||||
TEST_F(QualityScalerResourceTest, AbortPendingCallbacksAndStartAgain) {
|
||||
rtc::scoped_refptr<FakeQualityScalerQpUsageHandlerCallback> callback1 =
|
||||
new FakeQualityScalerQpUsageHandlerCallback(&encoder_queue_);
|
||||
rtc::scoped_refptr<FakeQualityScalerQpUsageHandlerCallback> callback2 =
|
||||
new FakeQualityScalerQpUsageHandlerCallback(&encoder_queue_);
|
||||
encoder_queue_.PostTask([this, callback1, callback2] {
|
||||
quality_scaler_resource_->OnReportQpUsageHigh(callback1);
|
||||
quality_scaler_resource_->StopCheckForOveruse();
|
||||
EXPECT_TRUE(callback1->qp_usage_handled_event()->Wait(0));
|
||||
quality_scaler_resource_->StartCheckForOveruse(
|
||||
VideoEncoder::QpThresholds());
|
||||
quality_scaler_resource_->OnReportQpUsageHigh(callback2);
|
||||
});
|
||||
callback1->qp_usage_handled_event()->Wait(kDefaultTimeout);
|
||||
callback2->qp_usage_handled_event()->Wait(kDefaultTimeout);
|
||||
EXPECT_CALL(fake_resource_listener_,
|
||||
OnResourceUsageStateMeasured(Eq(quality_scaler_resource_),
|
||||
Eq(ResourceUsageState::kUnderuse)));
|
||||
encoder_queue_.SendTask(
|
||||
[this] { quality_scaler_resource_->OnReportQpUsageLow(); },
|
||||
RTC_FROM_HERE);
|
||||
// Wait for adapt queue to clear since that signals the resource listener.
|
||||
resource_adaptation_queue_.WaitForPreviouslyPostedTasks();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -381,11 +381,6 @@ VideoStreamEncoderResourceManager::AdaptationConstraints() const {
|
||||
return {bitrate_constraint_, balanced_constraint_};
|
||||
}
|
||||
|
||||
std::vector<AdaptationListener*>
|
||||
VideoStreamEncoderResourceManager::AdaptationListeners() const {
|
||||
return {quality_scaler_resource_};
|
||||
}
|
||||
|
||||
rtc::scoped_refptr<QualityScalerResource>
|
||||
VideoStreamEncoderResourceManager::quality_scaler_resource_for_testing() {
|
||||
MutexLock lock(&resource_lock_);
|
||||
|
||||
@ -121,7 +121,6 @@ class VideoStreamEncoderResourceManager
|
||||
VideoAdaptationReason reason);
|
||||
std::vector<rtc::scoped_refptr<Resource>> MappedResources() const;
|
||||
std::vector<AdaptationConstraint*> AdaptationConstraints() const;
|
||||
std::vector<AdaptationListener*> AdaptationListeners() const;
|
||||
rtc::scoped_refptr<QualityScalerResource>
|
||||
quality_scaler_resource_for_testing();
|
||||
// If true, the VideoStreamEncoder should eexecute its logic to maybe drop
|
||||
|
||||
@ -392,9 +392,6 @@ VideoStreamEncoder::VideoStreamEncoder(
|
||||
for (auto* constraint : adaptation_constraints_) {
|
||||
video_stream_adapter_->AddAdaptationConstraint(constraint);
|
||||
}
|
||||
for (auto* listener : stream_resource_manager_.AdaptationListeners()) {
|
||||
video_stream_adapter_->AddAdaptationListener(listener);
|
||||
}
|
||||
initialize_processor_event.Set();
|
||||
});
|
||||
initialize_processor_event.Wait(rtc::Event::kForever);
|
||||
@ -426,9 +423,6 @@ void VideoStreamEncoder::Stop() {
|
||||
for (auto* constraint : adaptation_constraints_) {
|
||||
video_stream_adapter_->RemoveAdaptationConstraint(constraint);
|
||||
}
|
||||
for (auto* listener : stream_resource_manager_.AdaptationListeners()) {
|
||||
video_stream_adapter_->RemoveAdaptationListener(listener);
|
||||
}
|
||||
video_stream_adapter_->RemoveRestrictionsListener(this);
|
||||
video_stream_adapter_->RemoveRestrictionsListener(
|
||||
&stream_resource_manager_);
|
||||
|
||||
@ -161,34 +161,6 @@ class CpuOveruseDetectorProxy : public OveruseFrameDetector {
|
||||
rtc::Event framerate_updated_event_;
|
||||
};
|
||||
|
||||
class FakeQualityScalerQpUsageHandlerCallback
|
||||
: public QualityScalerQpUsageHandlerCallbackInterface {
|
||||
public:
|
||||
FakeQualityScalerQpUsageHandlerCallback()
|
||||
: QualityScalerQpUsageHandlerCallbackInterface(),
|
||||
qp_usage_handled_event_(/*manual_reset=*/true,
|
||||
/*initially_signaled=*/false),
|
||||
clear_qp_samples_result_(absl::nullopt) {}
|
||||
~FakeQualityScalerQpUsageHandlerCallback() override {
|
||||
RTC_DCHECK(clear_qp_samples_result_.has_value());
|
||||
}
|
||||
|
||||
void OnQpUsageHandled(bool clear_qp_samples) override {
|
||||
clear_qp_samples_result_ = clear_qp_samples;
|
||||
qp_usage_handled_event_.Set();
|
||||
}
|
||||
|
||||
bool WaitForQpUsageHandled() { return qp_usage_handled_event_.Wait(5000); }
|
||||
|
||||
absl::optional<bool> clear_qp_samples_result() const {
|
||||
return clear_qp_samples_result_;
|
||||
}
|
||||
|
||||
private:
|
||||
rtc::Event qp_usage_handled_event_;
|
||||
absl::optional<bool> clear_qp_samples_result_;
|
||||
};
|
||||
|
||||
class FakeVideoSourceRestrictionsListener
|
||||
: public VideoSourceRestrictionsListener {
|
||||
public:
|
||||
@ -413,22 +385,6 @@ class VideoStreamEncoderUnderTest : public VideoStreamEncoder {
|
||||
ASSERT_TRUE(event.Wait(5000));
|
||||
}
|
||||
|
||||
// Fakes high QP resource usage measurements on the real
|
||||
// QualityScalerResource. Returns whether or not QP samples would have been
|
||||
// cleared if this had been a real signal from the QualityScaler.
|
||||
bool TriggerQualityScalerHighQpAndReturnIfQpSamplesShouldBeCleared() {
|
||||
rtc::scoped_refptr<FakeQualityScalerQpUsageHandlerCallback> callback =
|
||||
new FakeQualityScalerQpUsageHandlerCallback();
|
||||
encoder_queue()->PostTask([this, callback] {
|
||||
// This will cause a "ping" between adaptation task queue and encoder
|
||||
// queue. When we have the result, the |callback| will be notified.
|
||||
quality_scaler_resource_for_testing()->OnReportQpUsageHigh(callback);
|
||||
});
|
||||
EXPECT_TRUE(callback->WaitForQpUsageHandled());
|
||||
EXPECT_TRUE(callback->clear_qp_samples_result().has_value());
|
||||
return callback->clear_qp_samples_result().value();
|
||||
}
|
||||
|
||||
CpuOveruseDetectorProxy* overuse_detector_proxy_;
|
||||
rtc::scoped_refptr<FakeResource> fake_cpu_resource_;
|
||||
rtc::scoped_refptr<FakeResource> fake_quality_resource_;
|
||||
@ -3282,7 +3238,7 @@ class BalancedDegradationTest : public VideoStreamEncoderTest {
|
||||
AdaptingFrameForwarder source_;
|
||||
};
|
||||
|
||||
TEST_F(BalancedDegradationTest, AdaptDownReturnsFalseIfFpsDiffLtThreshold) {
|
||||
TEST_F(BalancedDegradationTest, AdaptDownTwiceIfMinFpsDiffLtThreshold) {
|
||||
test::ScopedFieldTrials field_trials(
|
||||
"WebRTC-Video-BalancedDegradationSettings/"
|
||||
"pixels:57600|129600|230400,fps:7|10|24,fps_diff:1|1|1/");
|
||||
@ -3297,18 +3253,16 @@ TEST_F(BalancedDegradationTest, AdaptDownReturnsFalseIfFpsDiffLtThreshold) {
|
||||
InsertFrameAndWaitForEncoded();
|
||||
EXPECT_THAT(source_.sink_wants(), FpsMaxResolutionMax());
|
||||
|
||||
// Trigger adapt down, expect scaled down framerate (640x360@24fps).
|
||||
// Fps diff (input-requested:0) < threshold, expect adapting down not to clear
|
||||
// QP samples.
|
||||
EXPECT_FALSE(
|
||||
video_stream_encoder_
|
||||
->TriggerQualityScalerHighQpAndReturnIfQpSamplesShouldBeCleared());
|
||||
EXPECT_THAT(source_.sink_wants(), FpsMatchesResolutionMax(Eq(24)));
|
||||
// Trigger adapt down, expect scaled down framerate and resolution,
|
||||
// since Fps diff (input-requested:0) < threshold.
|
||||
video_stream_encoder_->TriggerQualityLow();
|
||||
EXPECT_THAT(source_.sink_wants(),
|
||||
AllOf(WantsFps(Eq(24)), WantsMaxPixels(Le(230400))));
|
||||
|
||||
video_stream_encoder_->Stop();
|
||||
}
|
||||
|
||||
TEST_F(BalancedDegradationTest, AdaptDownReturnsTrueIfFpsDiffGeThreshold) {
|
||||
TEST_F(BalancedDegradationTest, AdaptDownOnceIfFpsDiffGeThreshold) {
|
||||
test::ScopedFieldTrials field_trials(
|
||||
"WebRTC-Video-BalancedDegradationSettings/"
|
||||
"pixels:57600|129600|230400,fps:7|10|24,fps_diff:1|1|1/");
|
||||
@ -3323,12 +3277,9 @@ TEST_F(BalancedDegradationTest, AdaptDownReturnsTrueIfFpsDiffGeThreshold) {
|
||||
InsertFrameAndWaitForEncoded();
|
||||
EXPECT_THAT(source_.sink_wants(), FpsMaxResolutionMax());
|
||||
|
||||
// Trigger adapt down, expect scaled down framerate (640x360@24fps).
|
||||
// Fps diff (input-requested:1) == threshold, expect adapting down to clear QP
|
||||
// samples.
|
||||
EXPECT_TRUE(
|
||||
video_stream_encoder_
|
||||
->TriggerQualityScalerHighQpAndReturnIfQpSamplesShouldBeCleared());
|
||||
// Trigger adapt down, expect scaled down framerate only (640x360@24fps).
|
||||
// Fps diff (input-requested:1) == threshold.
|
||||
video_stream_encoder_->TriggerQualityLow();
|
||||
EXPECT_THAT(source_.sink_wants(), FpsMatchesResolutionMax(Eq(24)));
|
||||
|
||||
video_stream_encoder_->Stop();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user