| // 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/h264_rate_controller.h" |
| |
| #include <algorithm> |
| #include <array> |
| #include <limits> |
| #include <memory> |
| |
| #include "base/logging.h" |
| #include "base/time/time.h" |
| #include "media/gpu/h264_rate_control_util.h" |
| |
| namespace media { |
| namespace { |
| // Base temporal layer index. |
| constexpr size_t kBaseLayerIndex = 0; |
| |
| // Maximum FPS used in the tradeoff calculation between FPS and maximum QP. |
| constexpr float kFpsMax = 60; |
| |
| // Base layer to enhancement layer data rate ratio. It is used in fixed delta QP |
| // mode only. |
| constexpr float kLayerRateRatio = 0.8f; |
| |
| // Initial QP size value used in initialization of the estimators. The value is |
| // chosen arbitrarily bases on common values for QP and P frame size. |
| constexpr float kInitQPSize = 100000.0f; |
| |
| // The constant kIntraFrameMAD is the average MAD between the original and |
| // predicted pixels for intra frames in H.264 video. The average is calculated |
| // over a set of frames with a common complexity level. |
| constexpr float kIntraFrameMAD = 768.0f; |
| |
| // Arbitrarily chosen value for the minimum QP of the first encoded intra frame. |
| constexpr uint32_t kMinFirstFrameQP = 34u; |
| |
| // The constants kRDSlope and kRDYIntercept are the slope and Y-intercept of the |
| // linear approximation in the expression |
| // log2(bpp) = a * log2(mad / q_step) + b. |
| // a - kRDSlope |
| // b - kRDYIntercept |
| // The optimal values for kRDSlope and kRDYIntercept are derived from the |
| // analysis of rate and distortion values over a large set of data. |
| constexpr float kRDSlope = 0.91f; |
| constexpr float kRDYIntercept = -6.0f; |
| |
| // The arrays define line segments in the tradeoff function between FPS and |
| // maximum QP . |
| struct FPS2QPTradeoffs { |
| float fps; |
| float qp; |
| }; |
| constexpr auto kFPS2QPTradeoffs = std::to_array<FPS2QPTradeoffs>({ |
| {0.0f, 51.0f}, |
| {5.0f, 42.0f}, |
| {10.0f, 41.0f}, |
| {15.0f, 40.0f}, |
| {30.0f, 37.0f}, |
| {kFpsMax, 37.0f}, |
| {std::numeric_limits<float>::max(), 20.0f}, |
| }); |
| |
| // Window size in number of frames for the Moving Window. The average framerate |
| // is based on the last received frames within the window. |
| constexpr int kWindowFrameCount = 3; |
| |
| // Returns a budget in bytes per frame for the given frame rate and average |
| // bitrate. The budget represents the amount of data equally distributed among |
| // frames. |
| size_t GetRateBudget(float frame_rate, uint32_t avg_bitrate) { |
| return static_cast<size_t>(avg_bitrate / 8.0f / frame_rate); |
| } |
| |
| // Returns the FPS value related to the Max QP value. The function is |
| // represented by line segments defined in the array `kFPS2QPTradeoffs`. |
| float Fps2MaxQP(float fps) { |
| for (size_t i = 0; i < kFPS2QPTradeoffs.size() - 1; ++i) { |
| if (fps >= kFPS2QPTradeoffs[i].fps && fps < kFPS2QPTradeoffs[i + 1].fps) { |
| return h264_rate_control_util::ClampedLinearInterpolation( |
| fps, kFPS2QPTradeoffs[i].fps, kFPS2QPTradeoffs[i + 1].fps, |
| kFPS2QPTradeoffs[i].qp, kFPS2QPTradeoffs[i + 1].qp); |
| } |
| } |
| NOTREACHED(); |
| } |
| |
| // Returns the FPS value related to the Max QP value. The returned value is |
| // a constant value obtained from the `kFPS2QPTradeoffs` array. |
| float MaxQP2Fps(int max_qp) { |
| for (size_t i = 0; i < kFPS2QPTradeoffs.size() - 1; ++i) { |
| if (max_qp <= kFPS2QPTradeoffs[i].qp && |
| max_qp > kFPS2QPTradeoffs[i + 1].qp) { |
| // Do not use linear interpolation to be less aggressive on FPS changes. |
| return kFPS2QPTradeoffs[i + 1].fps; |
| } |
| } |
| NOTREACHED(); |
| } |
| } // namespace |
| |
| H264RateControllerSettings::H264RateControllerSettings() = default; |
| H264RateControllerSettings::~H264RateControllerSettings() = default; |
| H264RateControllerSettings::H264RateControllerSettings( |
| const H264RateControllerSettings&) = default; |
| H264RateControllerSettings& H264RateControllerSettings::operator=( |
| const H264RateControllerSettings&) = default; |
| |
| std::partial_ordering H264RateControllerSettings::operator<=>( |
| const H264RateControllerSettings& other) const { |
| if (auto res = frame_size.width() <=> other.frame_size.width(); res != 0) { |
| return res; |
| } |
| if (auto res = frame_size.height() <=> other.frame_size.height(); res != 0) { |
| return res; |
| } |
| if (auto res = fixed_delta_qp <=> other.fixed_delta_qp; res != 0) { |
| return res; |
| } |
| if (auto res = frame_rate_max <=> other.frame_rate_max; res != 0) { |
| return res; |
| } |
| if (auto res = num_temporal_layers <=> other.num_temporal_layers; res != 0) { |
| return res; |
| } |
| if (auto res = gop_max_duration.InMilliseconds() <=> |
| other.gop_max_duration.InMilliseconds(); |
| res != 0) { |
| return res; |
| } |
| return std::lexicographical_compare_three_way( |
| layer_settings.begin(), layer_settings.end(), |
| other.layer_settings.begin(), other.layer_settings.end()); |
| } |
| |
| H264RateController::Layer::Layer(H264RateControllerLayerSettings settings, |
| float expected_fps, |
| base::TimeDelta short_term_window_size, |
| base::TimeDelta long_term_window_size) |
| : hrd_buffer_(settings.hrd_buffer_size, settings.avg_bitrate), |
| src_frame_rate_(kWindowFrameCount), |
| expected_fps_(expected_fps), |
| min_qp_(settings.min_qp), |
| max_qp_(settings.max_qp), |
| short_term_estimator_(short_term_window_size, |
| kInitQPSize, |
| GetInitialSizeCorrection(settings)), |
| long_term_estimator_(long_term_window_size, |
| kInitQPSize, |
| GetInitialSizeCorrection(settings)), |
| estimator_error_(long_term_window_size) { |
| DCHECK_GT(settings.hrd_buffer_size, 0u); |
| DCHECK_GT(settings.avg_bitrate, 0u); |
| DCHECK_GT(expected_fps, 0.0f); |
| } |
| |
| H264RateController::Layer::~Layer() = default; |
| |
| void H264RateController::Layer::ShrinkHRDBuffer(base::TimeDelta timestamp) { |
| hrd_buffer_.Shrink(timestamp); |
| } |
| |
| void H264RateController::Layer::AddFrameBytes(size_t frame_bytes, |
| base::TimeDelta frame_timestamp) { |
| hrd_buffer_.AddFrameBytes(frame_bytes, frame_timestamp); |
| } |
| |
| void H264RateController::Layer::AddFrameTimestamp( |
| base::TimeDelta frame_timestamp) { |
| src_frame_rate_.AddSample(frame_timestamp); |
| } |
| |
| void H264RateController::Layer::SetBufferParameters(size_t buffer_size, |
| uint32_t avg_bitrate, |
| uint32_t peak_bitrate, |
| bool ease_hrd_reduction) { |
| hrd_buffer_.SetParameters(buffer_size, avg_bitrate, peak_bitrate, |
| ease_hrd_reduction); |
| } |
| |
| size_t H264RateController::Layer::GetBufferBytesAtTime( |
| base::TimeDelta timestamp) const { |
| return static_cast<size_t>(hrd_buffer_.GetBytesAtTime(timestamp)); |
| } |
| |
| size_t H264RateController::Layer::GetBufferBytesRemainingAtTime( |
| base::TimeDelta timestamp) const { |
| return static_cast<size_t>(hrd_buffer_.GetBytesRemainingAtTime(timestamp)); |
| } |
| |
| float H264RateController::Layer::GetFrameRateMean() const { |
| // Return the default value until the buffer is filled up. |
| if (src_frame_rate_.Count() < kWindowFrameCount) { |
| return expected_fps_; |
| } |
| |
| base::TimeDelta timestamp_min = src_frame_rate_.Min(); |
| base::TimeDelta timestamp_max = src_frame_rate_.Max(); |
| base::TimeDelta duration = timestamp_max - timestamp_min; |
| |
| // Return the default value if the duration is too small or too big. Limiting |
| // values are chosen arbitrarily. |
| if (duration <= base::Milliseconds(1) || duration > base::Minutes(5)) { |
| return expected_fps_; |
| } |
| |
| return (kWindowFrameCount - 1) / duration.InSecondsF(); |
| } |
| |
| size_t H264RateController::Layer::EstimateShortTermFrameSize( |
| uint32_t qp, |
| uint32_t qp_prev) const { |
| return short_term_estimator_.Estimate(qp, qp_prev); |
| } |
| |
| size_t H264RateController::Layer::EstimateLongTermFrameSize( |
| uint32_t qp, |
| uint32_t qp_prev) const { |
| return long_term_estimator_.Estimate(qp, qp_prev); |
| } |
| |
| uint32_t H264RateController::Layer::EstimateShortTermQP( |
| size_t target_frame_bytes, |
| uint32_t qp_prev) const { |
| return short_term_estimator_.InverseEstimate(target_frame_bytes, qp_prev); |
| } |
| |
| uint32_t H264RateController::Layer::EstimateLongTermQP( |
| size_t target_frame_bytes, |
| uint32_t qp_prev) const { |
| return long_term_estimator_.InverseEstimate(target_frame_bytes, qp_prev); |
| } |
| |
| size_t H264RateController::Layer::GetFrameSizeEstimatorError() const { |
| return static_cast<size_t>(estimator_error_.GetStdDeviation()); |
| } |
| |
| void H264RateController::Layer::UpdateFrameSizeEstimator( |
| size_t frame_bytes, |
| uint32_t qp, |
| uint32_t qp_prev, |
| base::TimeDelta elapsed_time) { |
| short_term_estimator_.Update(frame_bytes, qp, qp_prev, elapsed_time); |
| long_term_estimator_.Update(frame_bytes, qp, qp_prev, elapsed_time); |
| |
| // Compute the per-frame rate prediction error. |
| estimator_error_.AddValue( |
| pred_p_frame_size_ - static_cast<float>(frame_bytes), elapsed_time); |
| } |
| |
| float H264RateController::Layer::GetInitialSizeCorrection( |
| H264RateControllerLayerSettings settings) const { |
| // The initial size correction is set to 0.3 x frame budget. The multiplier is |
| // chosen arbitrarily. |
| float bytes_per_frame_avg = settings.avg_bitrate / (8 * settings.frame_rate); |
| return 0.3f * bytes_per_frame_avg; |
| } |
| |
| H264RateController::H264RateController(H264RateControllerSettings settings) |
| : target_fps_(GetTargetFps(settings)), |
| frame_rate_max_(settings.frame_rate_max), |
| frame_size_(settings.frame_size), |
| fixed_delta_qp_(settings.fixed_delta_qp), |
| num_temporal_layers_(settings.num_temporal_layers), |
| gop_max_duration_(settings.gop_max_duration), |
| content_type_(settings.content_type) { |
| DCHECK_GT(settings.num_temporal_layers, 0u); |
| DCHECK_LE(settings.num_temporal_layers, |
| h264_rate_control_util::kMaxNumTemporalLayers); |
| DCHECK_GT(target_fps_, 1.0f); |
| DCHECK_GT(frame_rate_max_, 1.0f); |
| // Short-term window is 5 x frame duration with the lowest value limited at |
| // 300 ms. The values are chosen arbitrarily. |
| base::TimeDelta short_term_window_size = base::Milliseconds(std::max( |
| static_cast<int>(5.0f * base::Time::kMillisecondsPerSecond / target_fps_), |
| 300)); |
| // Set long-term window to 3 x HRD buffer size. Use uint64_t, as it might |
| // overflow uint32_t. |
| base::TimeDelta long_term_window_size = base::Milliseconds( |
| 3 * |
| static_cast<uint64_t>( |
| settings.layer_settings[kBaseLayerIndex].hrd_buffer_size * 8) * |
| base::Time::kMillisecondsPerSecond / |
| settings.layer_settings[kBaseLayerIndex].avg_bitrate); |
| for (auto& tls : settings.layer_settings) { |
| temporal_layers_.emplace_back(std::make_unique<Layer>( |
| tls, target_fps_, short_term_window_size, long_term_window_size)); |
| } |
| } |
| |
| H264RateController::~H264RateController() = default; |
| |
| void H264RateController::EstimateIntraFrameQP(base::TimeDelta frame_timestamp) { |
| H264RateController::Layer& base_layer = *temporal_layers_[kBaseLayerIndex]; |
| |
| ++frame_number_; |
| |
| // Update the frame rate statistics. |
| base_layer.AddFrameTimestamp(frame_timestamp); |
| |
| if (0 == frame_number_) { |
| target_fps_ = std::min(target_fps_, frame_rate_max_); |
| } |
| |
| // Estimating the target intra frame encoded frame size. |
| size_t target_bytes_frame = GetTargetBytesForIntraFrame(frame_timestamp); |
| |
| // Applying Rate-Distortion model. |
| const float bpp = |
| target_bytes_frame * 8.0f / (frame_size_.width() * frame_size_.height()); |
| const float q_step = |
| kIntraFrameMAD / |
| (std::pow(bpp / std::pow(2, kRDYIntercept), 1 / kRDSlope)); |
| |
| uint32_t curr_qp = std::clamp(h264_rate_control_util::QStepSize2QP(q_step), |
| h264_rate_control_util::kQPMin, |
| h264_rate_control_util::kQPMax); |
| |
| if (0 == frame_number_) { |
| // The initial long term QP. The subtracted value is chosen arbitrarily. |
| base_layer.update_long_term_qp(curr_qp - 3); |
| |
| // Limit minimum QP value for the first IDR. |
| curr_qp = std::max(curr_qp, kMinFirstFrameQP); |
| } else if (frame_number_ > 0) { |
| // Prevent quality flickering. |
| // If the previous frame was dropped, make sure QP will increase. |
| if (base_layer.is_buffer_full()) { |
| // base_layer.curr_frame_qp should point to the QP used for dropped |
| // frame. |
| if (curr_qp > base_layer.curr_frame_qp() + 2) { |
| curr_qp = (curr_qp + base_layer.curr_frame_qp() + 2) / 2; |
| } else { |
| curr_qp = base_layer.curr_frame_qp() + 2; |
| } |
| } else if (base_layer.last_frame_type() == |
| H264RateController::FrameType::kPFrame) { |
| // Limit QP for IDR frames based on the QP estimated for the previous P |
| // frame. The offset for the minimum value is a constant, while the offset |
| // for the maximum value is calculated as a linear function of the frame |
| // rate. The constants are chosen arbitrarily, based on the analysis of |
| // the real use cases. |
| constexpr float kMinQPOffsetForIDR = -3.0f; |
| constexpr float kMaxQPOffsetForIDRLowerLimit = 6.0f; |
| constexpr float kMaxQPOffsetForIDRUpperLimit = 15.0f; |
| constexpr float kFrameRateToMaxQPSlope = -0.67f; |
| constexpr float kFrameRateToMaxQPYIntercept = 16.0f; |
| float max_qp_offset_for_idr = |
| kFrameRateToMaxQPSlope * base_layer.GetFrameRateMean() + |
| kFrameRateToMaxQPYIntercept; |
| max_qp_offset_for_idr = |
| std::clamp(max_qp_offset_for_idr, kMaxQPOffsetForIDRLowerLimit, |
| kMaxQPOffsetForIDRUpperLimit); |
| const float last_qp = |
| std::max(base_layer.long_term_qp(), base_layer.last_frame_qp()); |
| curr_qp = static_cast<uint32_t>( |
| std::clamp(static_cast<float>(curr_qp), last_qp + kMinQPOffsetForIDR, |
| last_qp + max_qp_offset_for_idr)); |
| } else if (base_layer.last_frame_type() == |
| H264RateController::FrameType::kIFrame) { |
| curr_qp = std::clamp(curr_qp, base_layer.last_frame_qp() - 1, |
| base_layer.last_frame_qp() + 3); |
| } |
| } |
| |
| // Limit highest possible quality. |
| base_layer.update_curr_frame_qp( |
| std::clamp(curr_qp, base_layer.min_qp(), base_layer.max_qp())); |
| |
| base_layer.update_long_term_qp(std::clamp(base_layer.long_term_qp(), |
| base_layer.min_qp(), |
| h264_rate_control_util::kQPMax)); |
| |
| last_idr_timestamp_ = frame_timestamp; |
| } |
| |
| void H264RateController::EstimateInterFrameQP(size_t temporal_id, |
| base::TimeDelta frame_timestamp) { |
| H264RateController::Layer& curr_layer = *temporal_layers_[temporal_id]; |
| H264RateController::Layer& base_layer = *temporal_layers_[kBaseLayerIndex]; |
| |
| ++frame_number_; |
| |
| // Update the frame rate statistics. |
| curr_layer.AddFrameTimestamp(frame_timestamp); |
| |
| // Compute a baselayer QP that together with layer delta QP's fit the channel |
| // rates. |
| if (frame_number_ > 2) { |
| base_layer.update_long_term_qp(GetInterFrameLongTermQP(temporal_id)); |
| } |
| |
| curr_layer.update_long_term_qp(std::clamp(base_layer.long_term_qp(), |
| curr_layer.min_qp(), |
| h264_rate_control_util::kQPMax)); |
| |
| // The enhancement layer QP in Fixed Delta QP mode is calculated by adding a |
| // fixed difference to the base layer's QP. In the case of buffer overflow, a |
| // statistical model is employed for QP estimation. |
| if (fixed_delta_qp_ && temporal_id > kBaseLayerIndex && |
| !curr_layer.is_buffer_full()) { |
| int delta_qp = fixed_delta_qp_.value(); |
| // delta_qp is reduced if the QP estimation for the last base layer frame is |
| // lower than the minimum QP. |
| if (base_layer.undershoot_delta_qp() > 0) { |
| delta_qp = std::max( |
| fixed_delta_qp_.value() - base_layer.undershoot_delta_qp(), 0); |
| } |
| curr_layer.update_curr_frame_qp(base_layer.curr_frame_qp() + delta_qp); |
| return; |
| } |
| |
| // For the fixed delta QP, take the buffer parameters from the topmost layer. |
| const size_t buffer_layer_id = |
| fixed_delta_qp_ ? num_temporal_layers_ - 1 : temporal_id; |
| uint32_t max_rate_bytes_per_sec = |
| temporal_layers_[buffer_layer_id]->average_bitrate() / 8; |
| size_t buffer_size = temporal_layers_[buffer_layer_id]->buffer_size(); |
| int buffer_level_current = |
| temporal_layers_[buffer_layer_id]->GetBufferBytesAtTime(frame_timestamp); |
| |
| size_t frame_size_target = GetTargetBytesForInterFrame( |
| temporal_id, max_rate_bytes_per_sec, buffer_size, buffer_level_current, |
| frame_timestamp); |
| curr_layer.update_last_frame_size_target(frame_size_target); |
| |
| uint32_t curr_qp = GetInterFrameShortTermQP(temporal_id, frame_size_target); |
| |
| curr_qp = ClipInterFrameQP(curr_qp, temporal_id, frame_timestamp); |
| |
| // Don't use post-fill here because estimated error can be inaccurate (scene |
| // change) and bias the decision. |
| const bool hrd_buffer_is_full = |
| buffer_level_current >= static_cast<int>(buffer_size); |
| if (hrd_buffer_is_full) { |
| // HRD buffer is already full: use max QP to limit the damage. |
| curr_qp = curr_layer.max_qp(); |
| } |
| |
| // Limit the quality. |
| curr_layer.update_curr_frame_qp( |
| std::clamp(curr_qp, curr_layer.min_qp(), curr_layer.max_qp())); |
| |
| curr_layer.update_pred_p_frame_size(curr_layer.EstimateShortTermFrameSize( |
| curr_layer.curr_frame_qp(), curr_layer.last_frame_qp())); |
| } |
| |
| void H264RateController::FinishIntraFrame(size_t access_unit_bytes, |
| base::TimeDelta frame_timestamp) { |
| FinishLayerData(kBaseLayerIndex, FrameType::kIFrame, access_unit_bytes, |
| frame_timestamp); |
| |
| FinishLayerPreviousFrameTimestamp(kBaseLayerIndex, frame_timestamp); |
| |
| last_idr_timestamp_ = frame_timestamp; |
| |
| if (0 == frame_number_) { |
| // To minimize risks of HRD violation on first P frames, first frame QP is |
| // used to readjust target FPS. |
| float buffer_level_norm = |
| static_cast<float>( |
| temporal_layers_[kBaseLayerIndex]->last_frame_buffer_bytes()) / |
| temporal_layers_[kBaseLayerIndex]->buffer_size(); |
| |
| if (0.5f < buffer_level_norm) { |
| const float max_qp_from_fps = Fps2MaxQP(target_fps_); |
| if (temporal_layers_[kBaseLayerIndex]->long_term_qp() > max_qp_from_fps) { |
| target_fps_ = MaxQP2Fps(static_cast<int>( |
| temporal_layers_[kBaseLayerIndex]->long_term_qp())); |
| } |
| } |
| } |
| |
| SetLastTsOvershootingFrame(kBaseLayerIndex, frame_timestamp); |
| } |
| |
| void H264RateController::FinishInterFrame(size_t temporal_id, |
| size_t access_unit_bytes, |
| base::TimeDelta frame_timestamp) { |
| FinishLayerData(temporal_id, FrameType::kPFrame, access_unit_bytes, |
| frame_timestamp); |
| |
| H264RateController::Layer& curr_layer = *temporal_layers_[temporal_id]; |
| |
| const base::TimeDelta elapsed_time = |
| h264_rate_control_util::ClampedTimestampDiff( |
| frame_timestamp, curr_layer.previous_frame_timestamp()); |
| |
| curr_layer.UpdateFrameSizeEstimator(access_unit_bytes, |
| curr_layer.curr_frame_qp(), |
| curr_layer.last_frame_qp(), elapsed_time); |
| |
| FinishLayerPreviousFrameTimestamp(temporal_id, frame_timestamp); |
| |
| SetLastTsOvershootingFrame(temporal_id, frame_timestamp); |
| } |
| |
| void H264RateController::UpdateFrameSize(const gfx::Size& frame_size) { |
| frame_size_ = frame_size; |
| } |
| |
| void H264RateController::GetHRDBufferFullness( |
| base::span<int> buffer_fullness, |
| base::TimeDelta frame_timestamp) const { |
| for (size_t tl = kBaseLayerIndex; |
| tl < std::min(buffer_fullness.size(), num_temporal_layers_); ++tl) { |
| buffer_fullness[tl] = |
| (100 * temporal_layers_[tl]->GetBufferBytesAtTime(frame_timestamp)) / |
| static_cast<int>(temporal_layers_[tl]->buffer_size()); |
| } |
| } |
| |
| void H264RateController::FinishLayerData(size_t temporal_id, |
| FrameType frame_type, |
| size_t frame_bytes, |
| base::TimeDelta frame_timestamp) { |
| // Update HRDs for all temporal layers. |
| for (size_t tl = temporal_id; tl < num_temporal_layers_; ++tl) { |
| temporal_layers_[tl]->AddFrameBytes(frame_bytes, frame_timestamp); |
| temporal_layers_[tl]->update_last_frame_qp( |
| temporal_layers_[tl]->curr_frame_qp()); |
| temporal_layers_[tl]->update_last_frame_type(frame_type); |
| } |
| } |
| |
| void H264RateController::FinishLayerPreviousFrameTimestamp( |
| size_t temporal_id, |
| base::TimeDelta frame_timestamp) { |
| // Update timestamps for all temporal layers. |
| for (size_t tl = temporal_id; tl < num_temporal_layers_; ++tl) { |
| temporal_layers_[tl]->update_previous_frame_timestamp(frame_timestamp); |
| } |
| } |
| |
| void H264RateController::SetLastTsOvershootingFrame( |
| size_t temporal_id, |
| base::TimeDelta frame_timestamp) { |
| for (size_t tl = temporal_id; tl < num_temporal_layers_; ++tl) { |
| bool check_overshoot = !fixed_delta_qp_ || tl == num_temporal_layers_ - 1; |
| if (!check_overshoot || !temporal_layers_[tl]->is_buffer_full()) { |
| last_ts_overshooting_frame_ = base::TimeDelta::Max(); |
| } else if (last_ts_overshooting_frame_ == base::TimeDelta::Max()) { |
| last_ts_overshooting_frame_ = frame_timestamp; |
| } |
| } |
| } |
| |
| size_t H264RateController::GetTargetBytesForIntraFrame( |
| base::TimeDelta frame_timestamp) const { |
| // Find the layer with the minimum buffer bytes remaining. The remaining |
| // bytes are used to estimate the target bytes for the intra frame. Since |
| // the intra frame is encoded in the base layer, the intra frame bytes are |
| // added to the buffers of all upper layers. Thats's why the intra encoded |
| // frame size is estimated based on the fullest buffer among all layers. |
| const size_t starting_layer_id = |
| fixed_delta_qp_ ? num_temporal_layers_ - 1 : kBaseLayerIndex; |
| size_t min_bytes_remaining_layer_id = kBaseLayerIndex; |
| int bytes_remaining = INT32_MAX; |
| for (size_t tl = starting_layer_id; tl < num_temporal_layers_; ++tl) { |
| int bytes_remaining_tl = |
| temporal_layers_[tl]->GetBufferBytesRemainingAtTime(frame_timestamp); |
| if (bytes_remaining > bytes_remaining_tl) { |
| bytes_remaining = bytes_remaining_tl; |
| min_bytes_remaining_layer_id = tl; |
| } |
| } |
| |
| const size_t buffer_bytes = |
| temporal_layers_[min_bytes_remaining_layer_id]->GetBufferBytesAtTime( |
| frame_timestamp); |
| const size_t hrd_buffer_size = |
| temporal_layers_[min_bytes_remaining_layer_id]->buffer_size(); |
| |
| // The minimum target intra frame fill up is 0.5 x HRD size. |
| size_t min_bytes_target = 0; |
| if (hrd_buffer_size / 2 >= buffer_bytes) { |
| min_bytes_target = hrd_buffer_size / 2 - buffer_bytes; |
| } |
| |
| // The target fill up should be above the minimum value. The minimum value is |
| // calculated by multiplying the average budget of the encoded frame by a |
| // value from the range 1 to 4. The multiplier is 4 x frame_budget for 15fps |
| // (and above) and 1x for 3.75 fps (and below). It is 4x for the desktop |
| // video source. The boundary values are chosen arbitrarily. |
| float intra_frame_multiplier = |
| (content_type_ == VideoEncodeAccelerator::Config::ContentType::kDisplay) |
| ? 4.0f |
| : std::clamp( |
| temporal_layers_[starting_layer_id]->GetFrameRateMean() / 3.75f, |
| 1.0f, 4.0f); |
| size_t bytes_target = |
| std::max(min_bytes_target, |
| static_cast<size_t>( |
| GetRateBudget( |
| temporal_layers_[starting_layer_id]->GetFrameRateMean(), |
| temporal_layers_[starting_layer_id]->average_bitrate()) * |
| intra_frame_multiplier)); |
| bytes_target = std::min(bytes_target, hrd_buffer_size); |
| |
| return bytes_target; |
| } |
| |
| size_t H264RateController::GetTargetBytesForInterFrame( |
| size_t temporal_id, |
| uint32_t max_rate_bytes_per_sec, |
| size_t buffer_size, |
| int buffer_level_current, |
| base::TimeDelta frame_timestamp) const { |
| // The long-term frame size is calculated based on short-term stats and |
| // long-term QP parameters. |
| size_t frame_size_long_term = |
| temporal_layers_[temporal_id]->EstimateShortTermFrameSize( |
| temporal_layers_[temporal_id]->long_term_qp(), |
| temporal_layers_[temporal_id]->last_frame_qp()); |
| |
| // Calculate bitrate allocated for the current layer. This value doesn't |
| // include the bitrate of the lower layers. In case of fixed delta QP, the |
| // the bitrate ratio between layers is fixed. |
| uint32_t curr_layer_bitrate; |
| if (fixed_delta_qp_ && num_temporal_layers_ > 1) { |
| DCHECK_EQ(num_temporal_layers_, |
| h264_rate_control_util::kMaxNumTemporalLayers); |
| curr_layer_bitrate = static_cast<uint32_t>( |
| temporal_layers_[kBaseLayerIndex + 1]->average_bitrate() * |
| kLayerRateRatio); |
| } else { |
| uint32_t lower_layer_bitrate = |
| temporal_id == kBaseLayerIndex |
| ? 0u |
| : temporal_layers_[temporal_id - 1]->average_bitrate(); |
| curr_layer_bitrate = |
| temporal_layers_[temporal_id]->average_bitrate() - lower_layer_bitrate; |
| } |
| |
| float frame_rate = std::clamp( |
| temporal_layers_[temporal_id]->GetFrameRateMean(), 1.0f, target_fps_); |
| |
| size_t frame_size_budget = |
| static_cast<size_t>(curr_layer_bitrate / 8 / frame_rate); |
| |
| float frame_size_deviation = |
| static_cast<float>(fabs(static_cast<int>(frame_size_long_term) - |
| static_cast<int>(frame_size_budget))) / |
| frame_size_budget; |
| float frame_size_compress = |
| h264_rate_control_util::ClampedLinearInterpolation( |
| frame_size_deviation, 0.5f, 3.0f, 0.1f, 0.9f); |
| |
| int frame_size_target = |
| static_cast<int>(static_cast<int>(frame_size_budget) + |
| (static_cast<int>(frame_size_long_term) - |
| static_cast<int>(frame_size_budget)) * |
| (1 - frame_size_compress)); |
| |
| DCHECK_GT(frame_size_target, 0); |
| |
| // Correct the target frame size based on current buffer level. |
| size_t buffer_target_low = frame_size_budget; |
| size_t buffer_target_high = std::max(frame_size_budget, buffer_size / 5); |
| |
| // The remaining time to the end of GOP. |
| base::TimeDelta frame_remaining_gop = base::Milliseconds(800); |
| if (gop_max_duration_ > base::TimeDelta() && |
| buffer_target_high > buffer_target_low) { |
| frame_remaining_gop = |
| last_idr_timestamp_ - frame_timestamp + gop_max_duration_; |
| } |
| |
| // Size correction window is a linear transformation of the remaining time in |
| // GOP. |
| uint32_t size_correction_window = |
| static_cast<uint32_t>(h264_rate_control_util::ClampedLinearInterpolation( |
| static_cast<float>(frame_remaining_gop.InMilliseconds()), 0.0f, |
| 2000.0f, 200.0f, 800.0f)); |
| |
| base::TimeDelta buffer_duration = base::Milliseconds( |
| static_cast<float>(buffer_size) / max_rate_bytes_per_sec * |
| base::Time::kMillisecondsPerSecond); |
| |
| int size_correction = 0; |
| if (buffer_level_current + frame_size_target > |
| static_cast<int>(buffer_target_high)) { |
| // Windowed overshoot prevention. |
| uint32_t win = buffer_duration.InMilliseconds() * 2; |
| size_correction_window = std::min(size_correction_window, win); |
| size_correction = static_cast<int>( |
| -(static_cast<int>(buffer_level_current) + frame_size_target - |
| static_cast<int>(buffer_target_high)) / |
| static_cast<float>(size_correction_window) / frame_rate * |
| base::Time::kMillisecondsPerSecond); |
| } else if (buffer_level_current + frame_size_target < |
| static_cast<int>(buffer_target_low)) { |
| // Windowed undershoot prevention. |
| uint32_t win = buffer_duration.InMilliseconds(); |
| size_correction_window = std::min(size_correction_window, win); |
| size_correction = |
| static_cast<int>((static_cast<int>(buffer_target_low) - |
| buffer_level_current - frame_size_target) / |
| static_cast<float>(size_correction_window) / |
| frame_rate * base::Time::kMillisecondsPerSecond); |
| } |
| |
| frame_size_target = std::clamp(frame_size_target + size_correction, |
| frame_size_target / 5, frame_size_target * 5); |
| |
| size_t frame_size_error = |
| temporal_layers_[temporal_id]->GetFrameSizeEstimatorError(); |
| |
| // Instantaneous undershoot prevention (buffer should not be empty after |
| // the frame is removed). |
| int buf_level_pre_fill_next_frame = buffer_level_current + frame_size_target - |
| static_cast<int>(frame_size_budget); |
| if (buf_level_pre_fill_next_frame - static_cast<int>(frame_size_error) < 0) { |
| frame_size_target -= buf_level_pre_fill_next_frame; |
| frame_size_target += frame_size_error; |
| } |
| |
| // Instantaneous overshoot prevention (buffer should not overshoot after |
| // the frame is added). |
| int buf_level_post_fill = buffer_level_current + frame_size_target; |
| |
| if (buf_level_post_fill + frame_size_error > buffer_size) { |
| frame_size_target -= buf_level_post_fill - static_cast<int>(buffer_size); |
| frame_size_target -= frame_size_error; |
| } |
| |
| frame_size_target = |
| std::max(frame_size_target, static_cast<int>(frame_size_budget / 5)); |
| |
| return static_cast<size_t>(frame_size_target); |
| } |
| |
| uint32_t H264RateController::GetInterFrameShortTermQP( |
| size_t temporal_id, |
| size_t frame_size_target) { |
| uint32_t curr_qp = temporal_layers_[temporal_id]->EstimateShortTermQP( |
| frame_size_target, temporal_layers_[temporal_id]->last_frame_qp()); |
| curr_qp = std::clamp(curr_qp, h264_rate_control_util::kQPMin, |
| h264_rate_control_util::kQPMax); |
| if (fixed_delta_qp_) { |
| temporal_layers_[temporal_id]->update_undershoot_delta_qp( |
| static_cast<int>(temporal_layers_[temporal_id]->min_qp()) - |
| static_cast<int>(curr_qp)); |
| } |
| |
| return curr_qp; |
| } |
| |
| uint32_t H264RateController::GetInterFrameLongTermQP(size_t temporal_id) { |
| float target_rate_bytes_per_sec = static_cast<float>( |
| temporal_layers_[kBaseLayerIndex]->average_bitrate() / 8); |
| float frame_rate = temporal_layers_[kBaseLayerIndex]->GetFrameRateMean(); |
| size_t target_frame_bytes = |
| static_cast<uint32_t>(target_rate_bytes_per_sec / frame_rate); |
| uint32_t long_term_qp = temporal_layers_[temporal_id]->EstimateLongTermQP( |
| target_frame_bytes, temporal_layers_[temporal_id]->last_frame_qp()); |
| |
| // Does this baselayer QP fit the channel rate? If not, increase it. |
| constexpr int kMaxQPIter = 10; |
| constexpr float kBitrateThreshold = 1.1f; |
| for (int i = 0; i < kMaxQPIter; i++) { |
| size_t layer_bytes = |
| temporal_layers_[temporal_id]->EstimateLongTermFrameSize( |
| long_term_qp, temporal_layers_[temporal_id]->last_frame_qp()); |
| float bitrate = 8 * layer_bytes * frame_rate; |
| if (bitrate > |
| temporal_layers_[temporal_id]->average_bitrate() * kBitrateThreshold) { |
| long_term_qp += 1; |
| } else { |
| break; |
| } |
| } |
| |
| return std::clamp(long_term_qp, h264_rate_control_util::kQPMin, |
| h264_rate_control_util::kQPMax); |
| } |
| |
| uint32_t H264RateController::ClipInterFrameQP(uint32_t curr_qp, |
| size_t temporal_id, |
| base::TimeDelta frame_timestamp) { |
| // Decrease the minimum QP limit by 1 when the frame rate falls below 3 fps. |
| constexpr float kMinQPFrameRateThreshold = 3.0f; |
| // Maximum Delta QP between consecutive layers. |
| constexpr int kMaxDeltaQP = 6; |
| |
| uint32_t min_qp = h264_rate_control_util::kQPMin, |
| max_qp = h264_rate_control_util::kQPMax; |
| |
| if (temporal_id == kBaseLayerIndex) { |
| if (temporal_layers_[kBaseLayerIndex]->last_frame_qp() > 0) { |
| min_qp = temporal_layers_[kBaseLayerIndex]->last_frame_qp() - |
| (temporal_layers_[kBaseLayerIndex]->GetFrameRateMean() < |
| kMinQPFrameRateThreshold |
| ? 2 |
| : 1); |
| max_qp = std::max(temporal_layers_[kBaseLayerIndex]->last_frame_qp() + 3, |
| (temporal_layers_[kBaseLayerIndex]->min_qp() + |
| temporal_layers_[kBaseLayerIndex]->max_qp()) / |
| 2); |
| } |
| } else { |
| min_qp = temporal_layers_[kBaseLayerIndex]->curr_frame_qp(); |
| max_qp = temporal_layers_[kBaseLayerIndex]->curr_frame_qp() + kMaxDeltaQP; |
| } |
| |
| // QP coupling between temporal layers. |
| // Raise base QP if enhancement layer buffer is in danger. |
| if (!fixed_delta_qp_ && num_temporal_layers_ > 1) { |
| std::array<int, 2> buffer_fullness_array = {0, 0}; |
| base::span<int> buffer_fullness_values(buffer_fullness_array); |
| GetHRDBufferFullness(buffer_fullness_values, frame_timestamp); |
| int enhance_buffer_fullness = buffer_fullness_values[kBaseLayerIndex + 1]; |
| if (limit_base_qp_ && temporal_id == kBaseLayerIndex) { |
| uint32_t enhance_qp = |
| temporal_layers_[kBaseLayerIndex + 1]->curr_frame_qp(); |
| uint32_t min_base_qp; |
| if (enhance_buffer_fullness > 95) { |
| min_base_qp = enhance_qp - 2; |
| } else if (enhance_buffer_fullness > 90) { |
| min_base_qp = enhance_qp - 3; |
| } else if (enhance_buffer_fullness > 80) { |
| min_base_qp = enhance_qp - 4; |
| } else if (enhance_buffer_fullness > 70) { |
| min_base_qp = enhance_qp - 5; |
| } else { |
| min_base_qp = enhance_qp - 6; |
| } |
| min_base_qp = std::max(min_base_qp, enhance_qp - kMaxDeltaQP); |
| min_qp = std::max(min_qp, min_base_qp); |
| } else if (temporal_id > kBaseLayerIndex) { |
| int layer_delta = |
| static_cast<int>(curr_qp) - |
| static_cast<int>(temporal_layers_[kBaseLayerIndex]->curr_frame_qp()); |
| int qp_trend = |
| static_cast<int>(curr_qp) - |
| static_cast<int>(temporal_layers_[temporal_id]->last_frame_qp()); |
| if (layer_delta >= kMaxDeltaQP) { |
| if (enhance_buffer_fullness > 60 && qp_trend > 0) { |
| limit_base_qp_ = true; |
| } |
| } else { |
| if (limit_base_qp_) { |
| if (enhance_buffer_fullness < 35 && qp_trend < 0) { |
| limit_base_qp_ = false; |
| } |
| } |
| } |
| } |
| } else if (num_temporal_layers_ > 1 && temporal_id == kBaseLayerIndex) { |
| if (temporal_layers_[kBaseLayerIndex + 1]->curr_frame_qp() > |
| temporal_layers_[kBaseLayerIndex]->curr_frame_qp() + |
| fixed_delta_qp_.value_or(0)) { |
| // Delta QP greater than `fixed_delta_qp_` const means enhancement layer |
| // QP has been raised due to HRD overflow. Make sure the following base |
| // layer QP follows. |
| min_qp = std::max(min_qp, |
| temporal_layers_[kBaseLayerIndex + 1]->curr_frame_qp() - |
| fixed_delta_qp_.value_or(0)); |
| } |
| } |
| |
| // Raise min QP if previous frame has been dropped. |
| if (temporal_layers_[temporal_id]->is_buffer_full()) { |
| // curr_frame_qp should point to the QP used for the dropped frame. |
| uint32_t lower_bound = |
| std::min(temporal_layers_[temporal_id]->curr_frame_qp() + 2, |
| h264_rate_control_util::kQPMax); |
| min_qp = std::clamp(min_qp, lower_bound, h264_rate_control_util::kQPMax); |
| } |
| |
| // Min QP may have been raised. Need to make sure max_qp >= min_qp. Also, |
| // avoid too low maximum QP value. The lowest maximum QP value is chosen |
| // arbitrarily. |
| constexpr uint32_t kMaxQPLowestValue = 28u; |
| max_qp = std::clamp(max_qp, min_qp, h264_rate_control_util::kQPMax); |
| max_qp = |
| std::clamp(max_qp, kMaxQPLowestValue, h264_rate_control_util::kQPMax); |
| |
| // QP range continues growing as long as frames overshoot. Out of order |
| // timestamps are ignored. |
| constexpr int kQPStepDuration = 33; |
| if (last_ts_overshooting_frame_ != base::TimeDelta::Max() && |
| frame_timestamp > last_ts_overshooting_frame_) { |
| base::TimeDelta delta_ts_overshooting_frame = |
| frame_timestamp - last_ts_overshooting_frame_; |
| uint32_t delta_qp = |
| std::max(delta_ts_overshooting_frame.InMilliseconds() / kQPStepDuration, |
| delta_ts_overshooting_frame.InMilliseconds() * |
| delta_ts_overshooting_frame.InMilliseconds() / |
| (kQPStepDuration * kQPStepDuration)); |
| max_qp += delta_qp; |
| min_qp += delta_qp; |
| } |
| |
| return std::clamp(curr_qp, min_qp, max_qp); |
| } |
| |
| float H264RateController::GetTargetFps( |
| H264RateControllerSettings settings) const { |
| DCHECK_EQ(settings.layer_settings.size(), settings.num_temporal_layers); |
| return settings.layer_settings[settings.num_temporal_layers - 1].frame_rate; |
| } |
| |
| } // namespace media |