-
Notifications
You must be signed in to change notification settings - Fork 6.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Encoded Frame Size Estimator component
The encoded frame size estimate is based on QP values of current and previous frames and stats of previously encoded frame data. The frame size estimator uses a weighted moving average filter to keep the statistics. Bug: 1234020 Change-Id: I2bffeb4e48060d69b4a147aa706c8077c3cb184a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4915782 Reviewed-by: Dale Curtis <dalecurtis@chromium.org> Commit-Queue: Mosa Morosev <mosamorosev@microsoft.com> Cr-Commit-Position: refs/heads/main@{#1211993}
- Loading branch information
1 parent
277a5bb
commit c7eed3f
Showing
7 changed files
with
471 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Copyright 2023 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#include "media/gpu/exponential_moving_average.h" | ||
|
||
#include "base/check.h" | ||
#include "base/check_op.h" | ||
#include "base/logging.h" | ||
|
||
namespace media { | ||
|
||
ExponentialMovingAverage::ExponentialMovingAverage( | ||
base::TimeDelta max_window_size) | ||
: max_window_size_(max_window_size) {} | ||
|
||
ExponentialMovingAverage::~ExponentialMovingAverage() = default; | ||
|
||
float ExponentialMovingAverage::GetStdDeviation() const { | ||
return std::sqrtf(std::max(mean_square_ - std::pow(mean_, 2), 0.0)); | ||
} | ||
|
||
} // namespace media |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// Copyright 2023 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#ifndef MEDIA_GPU_EXPONENTIAL_MOVING_AVERAGE_H_ | ||
#define MEDIA_GPU_EXPONENTIAL_MOVING_AVERAGE_H_ | ||
|
||
#include <algorithm> | ||
#include <memory> | ||
|
||
#include "base/time/time.h" | ||
#include "media/gpu/media_gpu_export.h" | ||
|
||
namespace media { | ||
|
||
// An Exponential Moving Average filter. | ||
// It is an implementation of exponential moving average filter with a time | ||
// constant. The effective window size equals to the time elapsed since the | ||
// first sample was added, until the maximum window size is reached. This makes | ||
// a difference to the standard exponential moving average filter | ||
// implementation. Alpha, the time constant, is calculated as a ratio between | ||
// the time period passed from the last added sample and the effective window | ||
// size. | ||
// mean += alpha * (value - mean) | ||
// mean_square += alpha * (value^2 - mean_square) | ||
// std_dev = sqrt(mean_square - mean^2) | ||
// alpha = elapsed_time / curr_window_size | ||
class MEDIA_GPU_EXPORT ExponentialMovingAverage { | ||
public: | ||
explicit ExponentialMovingAverage(base::TimeDelta max_window_size); | ||
~ExponentialMovingAverage(); | ||
|
||
ExponentialMovingAverage(const ExponentialMovingAverage& other) = delete; | ||
ExponentialMovingAverage& operator=(const ExponentialMovingAverage& other) = | ||
delete; | ||
|
||
base::TimeDelta curr_window_size() const { return curr_window_size_; } | ||
base::TimeDelta max_window_size() const { return max_window_size_; } | ||
|
||
float mean() const { return mean_; } | ||
|
||
void update_max_window_size(base::TimeDelta max_window_size) { | ||
max_window_size_ = max_window_size; | ||
} | ||
|
||
// Adds a new value to the filter. The T type is casted to the float value. | ||
// Elapsed time is the period between the current and previous sample. | ||
template <typename T> | ||
void AddValue(T value, base::TimeDelta elapsed_time) { | ||
float float_value = static_cast<float>(value); | ||
// The minimum window size is 1ms. This is to avoid division by zero. | ||
curr_window_size_ = std::clamp(curr_window_size_ + elapsed_time, | ||
base::Milliseconds(1), max_window_size_); | ||
float alpha = static_cast<float>(std::min( | ||
elapsed_time.InMillisecondsF() / curr_window_size_.InMillisecondsF(), | ||
1.0)); | ||
mean_ += alpha * (float_value - mean_); | ||
mean_square_ += alpha * (float_value * float_value - mean_square_); | ||
} | ||
|
||
float GetStdDeviation() const; | ||
|
||
private: | ||
// Mean of values. | ||
float mean_ = 0.0f; | ||
// Mean of squared values. | ||
float mean_square_ = 0.0f; | ||
|
||
// Effective window size. | ||
base::TimeDelta curr_window_size_; | ||
// Max window size. | ||
base::TimeDelta max_window_size_; | ||
}; | ||
|
||
} // namespace media | ||
|
||
#endif // MEDIA_GPU_EXPONENTIAL_MOVING_AVERAGE_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// Copyright 2023 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#include "media/gpu/exponential_moving_average.h" | ||
#include "testing/gtest/include/gtest/gtest.h" | ||
|
||
namespace media { | ||
namespace { | ||
|
||
// Test ExponentialMovingAverageTest adds the predefined values and checks | ||
// whether the correct mean and standard deviation values are produced. | ||
class ExponentialMovingAverageTest : public testing::Test { | ||
public: | ||
ExponentialMovingAverageTest() = default; | ||
|
||
void SetUp() override { | ||
moving_average_ = | ||
std::make_unique<ExponentialMovingAverage>(base::Milliseconds(100)); | ||
EXPECT_EQ(base::TimeDelta(), moving_average_->curr_window_size()); | ||
EXPECT_EQ(base::Milliseconds(100), moving_average_->max_window_size()); | ||
} | ||
|
||
protected: | ||
std::unique_ptr<ExponentialMovingAverage> moving_average_; | ||
}; | ||
|
||
// Test Cases | ||
|
||
// Adding predefined sequence to the moving average filter and checking | ||
// whether the stats are inside expected ranges. The filter is checked | ||
// with two sequences using different window sizes. | ||
TEST_F(ExponentialMovingAverageTest, RunBasicMovingAverageTest) { | ||
constexpr float kExpectedMeanMin1 = 94.73f; | ||
constexpr float kExpectedMeanMax1 = 94.74f; | ||
constexpr float kExpectedStdDevMin1 = 1.72f; | ||
constexpr float kExpectedStdDevMax1 = 1.73f; | ||
constexpr float kExpectedMeanMin2 = 103.16f; | ||
constexpr float kExpectedMeanMax2 = 103.17f; | ||
constexpr float kExpectedStdDevMin2 = 3.19f; | ||
constexpr float kExpectedStdDevMax2 = 3.20f; | ||
|
||
base::TimeDelta timestamp = base::Microseconds(0); | ||
moving_average_->AddValue(100, timestamp); | ||
timestamp += base::Milliseconds(10); | ||
moving_average_->AddValue(120, timestamp); | ||
timestamp += base::Milliseconds(8); | ||
moving_average_->AddValue(90, timestamp); | ||
timestamp += base::Milliseconds(12); | ||
moving_average_->AddValue(115, timestamp); | ||
timestamp += base::Milliseconds(11); | ||
moving_average_->AddValue(95, timestamp); | ||
timestamp += base::Milliseconds(9); | ||
moving_average_->AddValue(100, timestamp); | ||
timestamp += base::Milliseconds(10); | ||
moving_average_->AddValue(120, timestamp); | ||
timestamp += base::Milliseconds(11); | ||
moving_average_->AddValue(115, timestamp); | ||
timestamp += base::Milliseconds(7); | ||
moving_average_->AddValue(90, timestamp); | ||
timestamp += base::Milliseconds(11); | ||
moving_average_->AddValue(85, timestamp); | ||
timestamp += base::Milliseconds(8); | ||
moving_average_->AddValue(95, timestamp); | ||
|
||
EXPECT_LT(kExpectedMeanMin1, moving_average_->mean()); | ||
EXPECT_GT(kExpectedMeanMax1, moving_average_->mean()); | ||
EXPECT_LT(kExpectedStdDevMin1, moving_average_->GetStdDeviation()); | ||
EXPECT_GT(kExpectedStdDevMax1, moving_average_->GetStdDeviation()); | ||
|
||
moving_average_->update_max_window_size(base::Milliseconds(200)); | ||
EXPECT_EQ(base::Milliseconds(100), moving_average_->curr_window_size()); | ||
EXPECT_EQ(base::Milliseconds(200), moving_average_->max_window_size()); | ||
|
||
moving_average_->AddValue(105, timestamp); | ||
timestamp += base::Milliseconds(11); | ||
moving_average_->AddValue(90, timestamp); | ||
timestamp += base::Milliseconds(11); | ||
moving_average_->AddValue(100, timestamp); | ||
timestamp += base::Milliseconds(8); | ||
moving_average_->AddValue(100, timestamp); | ||
timestamp += base::Milliseconds(10); | ||
moving_average_->AddValue(105, timestamp); | ||
|
||
EXPECT_LT(kExpectedMeanMin2, moving_average_->mean()); | ||
EXPECT_GT(kExpectedMeanMax2, moving_average_->mean()); | ||
EXPECT_LT(kExpectedStdDevMin2, moving_average_->GetStdDeviation()); | ||
EXPECT_GT(kExpectedStdDevMax2, moving_average_->GetStdDeviation()); | ||
} | ||
|
||
} // namespace | ||
|
||
} // namespace media |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// Copyright 2023 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#include "media/gpu/frame_size_estimator.h" | ||
|
||
#include "base/check.h" | ||
#include "base/check_op.h" | ||
#include "base/logging.h" | ||
|
||
namespace media { | ||
namespace { | ||
|
||
// Maps QP to quantizer step size. 0.625 is Q-step value for QP=0 for H.26x | ||
// codecs. | ||
float Qp2QStepSize(uint32_t qp) { | ||
return 0.625f * std::powf(2, qp / 6.0f); | ||
} | ||
|
||
void CalculateQSteps(uint32_t qp, | ||
uint32_t qp_prev, | ||
float& q_step, | ||
float& q_step_prev, | ||
float& delta_q_step_factor) { | ||
q_step = Qp2QStepSize(qp); | ||
q_step_prev = Qp2QStepSize(qp_prev); | ||
delta_q_step_factor = q_step_prev / q_step; | ||
} | ||
|
||
} // namespace | ||
|
||
FrameSizeEstimator::FrameSizeEstimator(base::TimeDelta max_window_size, | ||
float initial_qp_size, | ||
float initial_size_correction) | ||
: qp_size_stats_(max_window_size), size_correction_stats_(max_window_size) { | ||
// The elapsed time is initially set to 1 millisecond to match the minimum | ||
// window size. | ||
qp_size_stats_.AddValue(initial_qp_size, base::Milliseconds(1)); | ||
size_correction_stats_.AddValue(initial_size_correction, | ||
base::Milliseconds(1)); | ||
} | ||
|
||
FrameSizeEstimator::~FrameSizeEstimator() = default; | ||
|
||
size_t FrameSizeEstimator::Estimate(uint32_t qp, uint32_t qp_prev) const { | ||
float q_step, q_step_prev, delta_q_step_factor; | ||
CalculateQSteps(qp, qp_prev, q_step, q_step_prev, delta_q_step_factor); | ||
float pred_frame_byte = qp_size_stats_.mean() * delta_q_step_factor / q_step + | ||
size_correction_stats_.mean(); | ||
return static_cast<size_t>(std::max(pred_frame_byte, 0.0f)); | ||
} | ||
|
||
void FrameSizeEstimator::Update(size_t frame_bytes, | ||
uint32_t qp, | ||
uint32_t qp_prev, | ||
base::TimeDelta elapsed_time) { | ||
float q_step, q_step_prev, delta_q_step_factor; | ||
CalculateQSteps(qp, qp_prev, q_step, q_step_prev, delta_q_step_factor); | ||
|
||
float qp_size = q_step * frame_bytes / delta_q_step_factor; | ||
qp_size_stats_.AddValue(qp_size, elapsed_time); | ||
|
||
float corr = | ||
frame_bytes - qp_size_stats_.mean() * delta_q_step_factor / q_step; | ||
size_correction_stats_.AddValue(corr, elapsed_time); | ||
} | ||
|
||
void FrameSizeEstimator::UpdateMaxWindowSize(base::TimeDelta max_window_size) { | ||
qp_size_stats_.update_max_window_size(max_window_size); | ||
size_correction_stats_.update_max_window_size(max_window_size); | ||
} | ||
|
||
} // namespace media |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// Copyright 2023 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#ifndef MEDIA_GPU_FRAME_SIZE_ESTIMATOR_H_ | ||
#define MEDIA_GPU_FRAME_SIZE_ESTIMATOR_H_ | ||
|
||
#include "base/time/time.h" | ||
#include "media/gpu/exponential_moving_average.h" | ||
#include "media/gpu/media_gpu_export.h" | ||
|
||
namespace media { | ||
|
||
// An encoded frame size estimator. | ||
// The estimator maintains the history of intermediate values (qp_size_value) | ||
// that are proportional to encoded frame size and QP, and inversely | ||
// proportional to the QP ratio of the previous and the current frame | ||
// (delta_q_step_factor). The QP is converted to Q step value that has linear | ||
// dependency to the encoded frame size. | ||
// | ||
// q_step = 5 / 8 * 2^(qp / 6) | ||
// | ||
// delta_q_step_factor = q_step_prev / q_step | ||
// | ||
// qp_size_value = q_step * frame_bytes / delta_q_step_factor | ||
// | ||
// The prediction of the encoded frame size is based on average values of | ||
// qp_size_value and qp_size_correction. The qp_size_correction is the | ||
// difference between actual encoded bytes and the predicted value. | ||
// | ||
// qp_size_correction = frame_bytes - | ||
// qp_size_value * delta_q_step_factor / q_step | ||
// | ||
// pred_frame_bytes = | ||
// qp_size_value * delta_q_step_factor / q_step + qp_size_correction | ||
class MEDIA_GPU_EXPORT FrameSizeEstimator { | ||
public: | ||
FrameSizeEstimator(base::TimeDelta max_window_size, | ||
float initial_qp_size, | ||
float initial_size_correction); | ||
~FrameSizeEstimator(); | ||
|
||
FrameSizeEstimator(const FrameSizeEstimator& other) = delete; | ||
FrameSizeEstimator& operator=(const FrameSizeEstimator& other) = delete; | ||
|
||
// Estimates encoded frame size for the given qp and qp_prev, based on the | ||
// stats of the previous frames. In usual encoding scenario, the current | ||
// QP is unknown at this point, but the estimate of the QP parameter is used | ||
// instead. | ||
size_t Estimate(uint32_t qp, uint32_t qp_prev) const; | ||
|
||
// Updates the frame size estimator state with the real encoded frame size and | ||
// with the parameters used for video frame encoding. | ||
void Update(size_t frame_bytes, | ||
uint32_t qp, | ||
uint32_t qp_prev, | ||
base::TimeDelta elapsed_time); | ||
|
||
float qp_size_mean() const { return qp_size_stats_.mean(); } | ||
float size_correction_mean() const { return size_correction_stats_.mean(); } | ||
void UpdateMaxWindowSize(base::TimeDelta max_window_size); | ||
|
||
private: | ||
ExponentialMovingAverage qp_size_stats_; | ||
ExponentialMovingAverage size_correction_stats_; | ||
}; | ||
|
||
} // namespace media | ||
|
||
#endif // MEDIA_GPU_FRAME_SIZE_ESTIMATOR_H_ |
Oops, something went wrong.