| Index: webrtc/modules/video_coding/main/source/qm_select.cc
|
| diff --git a/webrtc/modules/video_coding/main/source/qm_select.cc b/webrtc/modules/video_coding/main/source/qm_select.cc
|
| deleted file mode 100644
|
| index be8fcfc122edabdbef58138304fc312d88decb90..0000000000000000000000000000000000000000
|
| --- a/webrtc/modules/video_coding/main/source/qm_select.cc
|
| +++ /dev/null
|
| @@ -1,958 +0,0 @@
|
| -/*
|
| - * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
| - *
|
| - * Use of this source code is governed by a BSD-style license
|
| - * that can be found in the LICENSE file in the root of the source
|
| - * tree. An additional intellectual property rights grant can be found
|
| - * in the file PATENTS. All contributing project authors may
|
| - * be found in the AUTHORS file in the root of the source tree.
|
| - */
|
| -
|
| -#include "webrtc/modules/video_coding/main/source/qm_select.h"
|
| -
|
| -#include <math.h>
|
| -
|
| -#include "webrtc/modules/include/module_common_types.h"
|
| -#include "webrtc/modules/video_coding/main/interface/video_coding_defines.h"
|
| -#include "webrtc/modules/video_coding/main/source/internal_defines.h"
|
| -#include "webrtc/modules/video_coding/main/source/qm_select_data.h"
|
| -#include "webrtc/system_wrappers/include/trace.h"
|
| -
|
| -namespace webrtc {
|
| -
|
| -// QM-METHOD class
|
| -
|
| -VCMQmMethod::VCMQmMethod()
|
| - : content_metrics_(NULL),
|
| - width_(0),
|
| - height_(0),
|
| - user_frame_rate_(0.0f),
|
| - native_width_(0),
|
| - native_height_(0),
|
| - native_frame_rate_(0.0f),
|
| - image_type_(kVGA),
|
| - framerate_level_(kFrameRateHigh),
|
| - init_(false) {
|
| - ResetQM();
|
| -}
|
| -
|
| -VCMQmMethod::~VCMQmMethod() {
|
| -}
|
| -
|
| -void VCMQmMethod::ResetQM() {
|
| - aspect_ratio_ = 1.0f;
|
| - motion_.Reset();
|
| - spatial_.Reset();
|
| - content_class_ = 0;
|
| -}
|
| -
|
| -uint8_t VCMQmMethod::ComputeContentClass() {
|
| - ComputeMotionNFD();
|
| - ComputeSpatial();
|
| - return content_class_ = 3 * motion_.level + spatial_.level;
|
| -}
|
| -
|
| -void VCMQmMethod::UpdateContent(const VideoContentMetrics* contentMetrics) {
|
| - content_metrics_ = contentMetrics;
|
| -}
|
| -
|
| -void VCMQmMethod::ComputeMotionNFD() {
|
| - if (content_metrics_) {
|
| - motion_.value = content_metrics_->motion_magnitude;
|
| - }
|
| - // Determine motion level.
|
| - if (motion_.value < kLowMotionNfd) {
|
| - motion_.level = kLow;
|
| - } else if (motion_.value > kHighMotionNfd) {
|
| - motion_.level = kHigh;
|
| - } else {
|
| - motion_.level = kDefault;
|
| - }
|
| -}
|
| -
|
| -void VCMQmMethod::ComputeSpatial() {
|
| - float spatial_err = 0.0;
|
| - float spatial_err_h = 0.0;
|
| - float spatial_err_v = 0.0;
|
| - if (content_metrics_) {
|
| - spatial_err = content_metrics_->spatial_pred_err;
|
| - spatial_err_h = content_metrics_->spatial_pred_err_h;
|
| - spatial_err_v = content_metrics_->spatial_pred_err_v;
|
| - }
|
| - // Spatial measure: take average of 3 prediction errors.
|
| - spatial_.value = (spatial_err + spatial_err_h + spatial_err_v) / 3.0f;
|
| -
|
| - // Reduce thresholds for large scenes/higher pixel correlation.
|
| - float scale2 = image_type_ > kVGA ? kScaleTexture : 1.0;
|
| -
|
| - if (spatial_.value > scale2 * kHighTexture) {
|
| - spatial_.level = kHigh;
|
| - } else if (spatial_.value < scale2 * kLowTexture) {
|
| - spatial_.level = kLow;
|
| - } else {
|
| - spatial_.level = kDefault;
|
| - }
|
| -}
|
| -
|
| -ImageType VCMQmMethod::GetImageType(uint16_t width,
|
| - uint16_t height) {
|
| - // Get the image type for the encoder frame size.
|
| - uint32_t image_size = width * height;
|
| - if (image_size == kSizeOfImageType[kQCIF]) {
|
| - return kQCIF;
|
| - } else if (image_size == kSizeOfImageType[kHCIF]) {
|
| - return kHCIF;
|
| - } else if (image_size == kSizeOfImageType[kQVGA]) {
|
| - return kQVGA;
|
| - } else if (image_size == kSizeOfImageType[kCIF]) {
|
| - return kCIF;
|
| - } else if (image_size == kSizeOfImageType[kHVGA]) {
|
| - return kHVGA;
|
| - } else if (image_size == kSizeOfImageType[kVGA]) {
|
| - return kVGA;
|
| - } else if (image_size == kSizeOfImageType[kQFULLHD]) {
|
| - return kQFULLHD;
|
| - } else if (image_size == kSizeOfImageType[kWHD]) {
|
| - return kWHD;
|
| - } else if (image_size == kSizeOfImageType[kFULLHD]) {
|
| - return kFULLHD;
|
| - } else {
|
| - // No exact match, find closet one.
|
| - return FindClosestImageType(width, height);
|
| - }
|
| -}
|
| -
|
| -ImageType VCMQmMethod::FindClosestImageType(uint16_t width, uint16_t height) {
|
| - float size = static_cast<float>(width * height);
|
| - float min = size;
|
| - int isel = 0;
|
| - for (int i = 0; i < kNumImageTypes; ++i) {
|
| - float dist = fabs(size - kSizeOfImageType[i]);
|
| - if (dist < min) {
|
| - min = dist;
|
| - isel = i;
|
| - }
|
| - }
|
| - return static_cast<ImageType>(isel);
|
| -}
|
| -
|
| -FrameRateLevelClass VCMQmMethod::FrameRateLevel(float avg_framerate) {
|
| - if (avg_framerate <= kLowFrameRate) {
|
| - return kFrameRateLow;
|
| - } else if (avg_framerate <= kMiddleFrameRate) {
|
| - return kFrameRateMiddle1;
|
| - } else if (avg_framerate <= kHighFrameRate) {
|
| - return kFrameRateMiddle2;
|
| - } else {
|
| - return kFrameRateHigh;
|
| - }
|
| -}
|
| -
|
| -// RESOLUTION CLASS
|
| -
|
| -VCMQmResolution::VCMQmResolution()
|
| - : qm_(new VCMResolutionScale()) {
|
| - Reset();
|
| -}
|
| -
|
| -VCMQmResolution::~VCMQmResolution() {
|
| - delete qm_;
|
| -}
|
| -
|
| -void VCMQmResolution::ResetRates() {
|
| - sum_target_rate_ = 0.0f;
|
| - sum_incoming_framerate_ = 0.0f;
|
| - sum_rate_MM_ = 0.0f;
|
| - sum_rate_MM_sgn_ = 0.0f;
|
| - sum_packet_loss_ = 0.0f;
|
| - buffer_level_ = kInitBufferLevel * target_bitrate_;
|
| - frame_cnt_ = 0;
|
| - frame_cnt_delta_ = 0;
|
| - low_buffer_cnt_ = 0;
|
| - update_rate_cnt_ = 0;
|
| -}
|
| -
|
| -void VCMQmResolution::ResetDownSamplingState() {
|
| - state_dec_factor_spatial_ = 1.0;
|
| - state_dec_factor_temporal_ = 1.0;
|
| - for (int i = 0; i < kDownActionHistorySize; i++) {
|
| - down_action_history_[i].spatial = kNoChangeSpatial;
|
| - down_action_history_[i].temporal = kNoChangeTemporal;
|
| - }
|
| -}
|
| -
|
| -void VCMQmResolution::Reset() {
|
| - target_bitrate_ = 0.0f;
|
| - incoming_framerate_ = 0.0f;
|
| - buffer_level_ = 0.0f;
|
| - per_frame_bandwidth_ = 0.0f;
|
| - avg_target_rate_ = 0.0f;
|
| - avg_incoming_framerate_ = 0.0f;
|
| - avg_ratio_buffer_low_ = 0.0f;
|
| - avg_rate_mismatch_ = 0.0f;
|
| - avg_rate_mismatch_sgn_ = 0.0f;
|
| - avg_packet_loss_ = 0.0f;
|
| - encoder_state_ = kStableEncoding;
|
| - num_layers_ = 1;
|
| - ResetRates();
|
| - ResetDownSamplingState();
|
| - ResetQM();
|
| -}
|
| -
|
| -EncoderState VCMQmResolution::GetEncoderState() {
|
| - return encoder_state_;
|
| -}
|
| -
|
| -// Initialize state after re-initializing the encoder,
|
| -// i.e., after SetEncodingData() in mediaOpt.
|
| -int VCMQmResolution::Initialize(float bitrate,
|
| - float user_framerate,
|
| - uint16_t width,
|
| - uint16_t height,
|
| - int num_layers) {
|
| - if (user_framerate == 0.0f || width == 0 || height == 0) {
|
| - return VCM_PARAMETER_ERROR;
|
| - }
|
| - Reset();
|
| - target_bitrate_ = bitrate;
|
| - incoming_framerate_ = user_framerate;
|
| - UpdateCodecParameters(user_framerate, width, height);
|
| - native_width_ = width;
|
| - native_height_ = height;
|
| - native_frame_rate_ = user_framerate;
|
| - num_layers_ = num_layers;
|
| - // Initial buffer level.
|
| - buffer_level_ = kInitBufferLevel * target_bitrate_;
|
| - // Per-frame bandwidth.
|
| - per_frame_bandwidth_ = target_bitrate_ / user_framerate;
|
| - init_ = true;
|
| - return VCM_OK;
|
| -}
|
| -
|
| -void VCMQmResolution::UpdateCodecParameters(float frame_rate, uint16_t width,
|
| - uint16_t height) {
|
| - width_ = width;
|
| - height_ = height;
|
| - // |user_frame_rate| is the target frame rate for VPM frame dropper.
|
| - user_frame_rate_ = frame_rate;
|
| - image_type_ = GetImageType(width, height);
|
| -}
|
| -
|
| -// Update rate data after every encoded frame.
|
| -void VCMQmResolution::UpdateEncodedSize(size_t encoded_size) {
|
| - frame_cnt_++;
|
| - // Convert to Kbps.
|
| - float encoded_size_kbits = 8.0f * static_cast<float>(encoded_size) / 1000.0f;
|
| -
|
| - // Update the buffer level:
|
| - // Note this is not the actual encoder buffer level.
|
| - // |buffer_level_| is reset to an initial value after SelectResolution is
|
| - // called, and does not account for frame dropping by encoder or VCM.
|
| - buffer_level_ += per_frame_bandwidth_ - encoded_size_kbits;
|
| -
|
| - // Counter for occurrences of low buffer level:
|
| - // low/negative values means encoder is likely dropping frames.
|
| - if (buffer_level_ <= kPercBufferThr * kInitBufferLevel * target_bitrate_) {
|
| - low_buffer_cnt_++;
|
| - }
|
| -}
|
| -
|
| -// Update various quantities after SetTargetRates in MediaOpt.
|
| -void VCMQmResolution::UpdateRates(float target_bitrate,
|
| - float encoder_sent_rate,
|
| - float incoming_framerate,
|
| - uint8_t packet_loss) {
|
| - // Sum the target bitrate: this is the encoder rate from previous update
|
| - // (~1sec), i.e, before the update for next ~1sec.
|
| - sum_target_rate_ += target_bitrate_;
|
| - update_rate_cnt_++;
|
| -
|
| - // Sum the received (from RTCP reports) packet loss rates.
|
| - sum_packet_loss_ += static_cast<float>(packet_loss / 255.0);
|
| -
|
| - // Sum the sequence rate mismatch:
|
| - // Mismatch here is based on the difference between the target rate
|
| - // used (in previous ~1sec) and the average actual encoding rate measured
|
| - // at previous ~1sec.
|
| - float diff = target_bitrate_ - encoder_sent_rate;
|
| - if (target_bitrate_ > 0.0)
|
| - sum_rate_MM_ += fabs(diff) / target_bitrate_;
|
| - int sgnDiff = diff > 0 ? 1 : (diff < 0 ? -1 : 0);
|
| - // To check for consistent under(+)/over_shooting(-) of target rate.
|
| - sum_rate_MM_sgn_ += sgnDiff;
|
| -
|
| - // Update with the current new target and frame rate:
|
| - // these values are ones the encoder will use for the current/next ~1sec.
|
| - target_bitrate_ = target_bitrate;
|
| - incoming_framerate_ = incoming_framerate;
|
| - sum_incoming_framerate_ += incoming_framerate_;
|
| - // Update the per_frame_bandwidth:
|
| - // this is the per_frame_bw for the current/next ~1sec.
|
| - per_frame_bandwidth_ = 0.0f;
|
| - if (incoming_framerate_ > 0.0f) {
|
| - per_frame_bandwidth_ = target_bitrate_ / incoming_framerate_;
|
| - }
|
| -}
|
| -
|
| -// Select the resolution factors: frame size and frame rate change (qm scales).
|
| -// Selection is for going down in resolution, or for going back up
|
| -// (if a previous down-sampling action was taken).
|
| -
|
| -// In the current version the following constraints are imposed:
|
| -// 1) We only allow for one action, either down or up, at a given time.
|
| -// 2) The possible down-sampling actions are: spatial by 1/2x1/2, 3/4x3/4;
|
| -// temporal/frame rate reduction by 1/2 and 2/3.
|
| -// 3) The action for going back up is the reverse of last (spatial or temporal)
|
| -// down-sampling action. The list of down-sampling actions from the
|
| -// Initialize() state are kept in |down_action_history_|.
|
| -// 4) The total amount of down-sampling (spatial and/or temporal) from the
|
| -// Initialize() state (native resolution) is limited by various factors.
|
| -int VCMQmResolution::SelectResolution(VCMResolutionScale** qm) {
|
| - if (!init_) {
|
| - return VCM_UNINITIALIZED;
|
| - }
|
| - if (content_metrics_ == NULL) {
|
| - Reset();
|
| - *qm = qm_;
|
| - return VCM_OK;
|
| - }
|
| -
|
| - // Check conditions on down-sampling state.
|
| - assert(state_dec_factor_spatial_ >= 1.0f);
|
| - assert(state_dec_factor_temporal_ >= 1.0f);
|
| - assert(state_dec_factor_spatial_ <= kMaxSpatialDown);
|
| - assert(state_dec_factor_temporal_ <= kMaxTempDown);
|
| - assert(state_dec_factor_temporal_ * state_dec_factor_spatial_ <=
|
| - kMaxTotalDown);
|
| -
|
| - // Compute content class for selection.
|
| - content_class_ = ComputeContentClass();
|
| - // Compute various rate quantities for selection.
|
| - ComputeRatesForSelection();
|
| -
|
| - // Get the encoder state.
|
| - ComputeEncoderState();
|
| -
|
| - // Default settings: no action.
|
| - SetDefaultAction();
|
| - *qm = qm_;
|
| -
|
| - // Check for going back up in resolution, if we have had some down-sampling
|
| - // relative to native state in Initialize().
|
| - if (down_action_history_[0].spatial != kNoChangeSpatial ||
|
| - down_action_history_[0].temporal != kNoChangeTemporal) {
|
| - if (GoingUpResolution()) {
|
| - *qm = qm_;
|
| - return VCM_OK;
|
| - }
|
| - }
|
| -
|
| - // Check for going down in resolution.
|
| - if (GoingDownResolution()) {
|
| - *qm = qm_;
|
| - return VCM_OK;
|
| - }
|
| - return VCM_OK;
|
| -}
|
| -
|
| -void VCMQmResolution::SetDefaultAction() {
|
| - qm_->codec_width = width_;
|
| - qm_->codec_height = height_;
|
| - qm_->frame_rate = user_frame_rate_;
|
| - qm_->change_resolution_spatial = false;
|
| - qm_->change_resolution_temporal = false;
|
| - qm_->spatial_width_fact = 1.0f;
|
| - qm_->spatial_height_fact = 1.0f;
|
| - qm_->temporal_fact = 1.0f;
|
| - action_.spatial = kNoChangeSpatial;
|
| - action_.temporal = kNoChangeTemporal;
|
| -}
|
| -
|
| -void VCMQmResolution::ComputeRatesForSelection() {
|
| - avg_target_rate_ = 0.0f;
|
| - avg_incoming_framerate_ = 0.0f;
|
| - avg_ratio_buffer_low_ = 0.0f;
|
| - avg_rate_mismatch_ = 0.0f;
|
| - avg_rate_mismatch_sgn_ = 0.0f;
|
| - avg_packet_loss_ = 0.0f;
|
| - if (frame_cnt_ > 0) {
|
| - avg_ratio_buffer_low_ = static_cast<float>(low_buffer_cnt_) /
|
| - static_cast<float>(frame_cnt_);
|
| - }
|
| - if (update_rate_cnt_ > 0) {
|
| - avg_rate_mismatch_ = static_cast<float>(sum_rate_MM_) /
|
| - static_cast<float>(update_rate_cnt_);
|
| - avg_rate_mismatch_sgn_ = static_cast<float>(sum_rate_MM_sgn_) /
|
| - static_cast<float>(update_rate_cnt_);
|
| - avg_target_rate_ = static_cast<float>(sum_target_rate_) /
|
| - static_cast<float>(update_rate_cnt_);
|
| - avg_incoming_framerate_ = static_cast<float>(sum_incoming_framerate_) /
|
| - static_cast<float>(update_rate_cnt_);
|
| - avg_packet_loss_ = static_cast<float>(sum_packet_loss_) /
|
| - static_cast<float>(update_rate_cnt_);
|
| - }
|
| - // For selection we may want to weight some quantities more heavily
|
| - // with the current (i.e., next ~1sec) rate values.
|
| - avg_target_rate_ = kWeightRate * avg_target_rate_ +
|
| - (1.0 - kWeightRate) * target_bitrate_;
|
| - avg_incoming_framerate_ = kWeightRate * avg_incoming_framerate_ +
|
| - (1.0 - kWeightRate) * incoming_framerate_;
|
| - // Use base layer frame rate for temporal layers: this will favor spatial.
|
| - assert(num_layers_ > 0);
|
| - framerate_level_ = FrameRateLevel(
|
| - avg_incoming_framerate_ / static_cast<float>(1 << (num_layers_ - 1)));
|
| -}
|
| -
|
| -void VCMQmResolution::ComputeEncoderState() {
|
| - // Default.
|
| - encoder_state_ = kStableEncoding;
|
| -
|
| - // Assign stressed state if:
|
| - // 1) occurrences of low buffer levels is high, or
|
| - // 2) rate mis-match is high, and consistent over-shooting by encoder.
|
| - if ((avg_ratio_buffer_low_ > kMaxBufferLow) ||
|
| - ((avg_rate_mismatch_ > kMaxRateMisMatch) &&
|
| - (avg_rate_mismatch_sgn_ < -kRateOverShoot))) {
|
| - encoder_state_ = kStressedEncoding;
|
| - }
|
| - // Assign easy state if:
|
| - // 1) rate mis-match is high, and
|
| - // 2) consistent under-shooting by encoder.
|
| - if ((avg_rate_mismatch_ > kMaxRateMisMatch) &&
|
| - (avg_rate_mismatch_sgn_ > kRateUnderShoot)) {
|
| - encoder_state_ = kEasyEncoding;
|
| - }
|
| -}
|
| -
|
| -bool VCMQmResolution::GoingUpResolution() {
|
| - // For going up, we check for undoing the previous down-sampling action.
|
| -
|
| - float fac_width = kFactorWidthSpatial[down_action_history_[0].spatial];
|
| - float fac_height = kFactorHeightSpatial[down_action_history_[0].spatial];
|
| - float fac_temp = kFactorTemporal[down_action_history_[0].temporal];
|
| - // For going up spatially, we allow for going up by 3/4x3/4 at each stage.
|
| - // So if the last spatial action was 1/2x1/2 it would be undone in 2 stages.
|
| - // Modify the fac_width/height for this case.
|
| - if (down_action_history_[0].spatial == kOneQuarterSpatialUniform) {
|
| - fac_width = kFactorWidthSpatial[kOneQuarterSpatialUniform] /
|
| - kFactorWidthSpatial[kOneHalfSpatialUniform];
|
| - fac_height = kFactorHeightSpatial[kOneQuarterSpatialUniform] /
|
| - kFactorHeightSpatial[kOneHalfSpatialUniform];
|
| - }
|
| -
|
| - // Check if we should go up both spatially and temporally.
|
| - if (down_action_history_[0].spatial != kNoChangeSpatial &&
|
| - down_action_history_[0].temporal != kNoChangeTemporal) {
|
| - if (ConditionForGoingUp(fac_width, fac_height, fac_temp,
|
| - kTransRateScaleUpSpatialTemp)) {
|
| - action_.spatial = down_action_history_[0].spatial;
|
| - action_.temporal = down_action_history_[0].temporal;
|
| - UpdateDownsamplingState(kUpResolution);
|
| - return true;
|
| - }
|
| - }
|
| - // Check if we should go up either spatially or temporally.
|
| - bool selected_up_spatial = false;
|
| - bool selected_up_temporal = false;
|
| - if (down_action_history_[0].spatial != kNoChangeSpatial) {
|
| - selected_up_spatial = ConditionForGoingUp(fac_width, fac_height, 1.0f,
|
| - kTransRateScaleUpSpatial);
|
| - }
|
| - if (down_action_history_[0].temporal != kNoChangeTemporal) {
|
| - selected_up_temporal = ConditionForGoingUp(1.0f, 1.0f, fac_temp,
|
| - kTransRateScaleUpTemp);
|
| - }
|
| - if (selected_up_spatial && !selected_up_temporal) {
|
| - action_.spatial = down_action_history_[0].spatial;
|
| - action_.temporal = kNoChangeTemporal;
|
| - UpdateDownsamplingState(kUpResolution);
|
| - return true;
|
| - } else if (!selected_up_spatial && selected_up_temporal) {
|
| - action_.spatial = kNoChangeSpatial;
|
| - action_.temporal = down_action_history_[0].temporal;
|
| - UpdateDownsamplingState(kUpResolution);
|
| - return true;
|
| - } else if (selected_up_spatial && selected_up_temporal) {
|
| - PickSpatialOrTemporal();
|
| - UpdateDownsamplingState(kUpResolution);
|
| - return true;
|
| - }
|
| - return false;
|
| -}
|
| -
|
| -bool VCMQmResolution::ConditionForGoingUp(float fac_width,
|
| - float fac_height,
|
| - float fac_temp,
|
| - float scale_fac) {
|
| - float estimated_transition_rate_up = GetTransitionRate(fac_width, fac_height,
|
| - fac_temp, scale_fac);
|
| - // Go back up if:
|
| - // 1) target rate is above threshold and current encoder state is stable, or
|
| - // 2) encoder state is easy (encoder is significantly under-shooting target).
|
| - if (((avg_target_rate_ > estimated_transition_rate_up) &&
|
| - (encoder_state_ == kStableEncoding)) ||
|
| - (encoder_state_ == kEasyEncoding)) {
|
| - return true;
|
| - } else {
|
| - return false;
|
| - }
|
| -}
|
| -
|
| -bool VCMQmResolution::GoingDownResolution() {
|
| - float estimated_transition_rate_down =
|
| - GetTransitionRate(1.0f, 1.0f, 1.0f, 1.0f);
|
| - float max_rate = kFrameRateFac[framerate_level_] * kMaxRateQm[image_type_];
|
| - // Resolution reduction if:
|
| - // (1) target rate is below transition rate, or
|
| - // (2) encoder is in stressed state and target rate below a max threshold.
|
| - if ((avg_target_rate_ < estimated_transition_rate_down ) ||
|
| - (encoder_state_ == kStressedEncoding && avg_target_rate_ < max_rate)) {
|
| - // Get the down-sampling action: based on content class, and how low
|
| - // average target rate is relative to transition rate.
|
| - uint8_t spatial_fact =
|
| - kSpatialAction[content_class_ +
|
| - 9 * RateClass(estimated_transition_rate_down)];
|
| - uint8_t temp_fact =
|
| - kTemporalAction[content_class_ +
|
| - 9 * RateClass(estimated_transition_rate_down)];
|
| -
|
| - switch (spatial_fact) {
|
| - case 4: {
|
| - action_.spatial = kOneQuarterSpatialUniform;
|
| - break;
|
| - }
|
| - case 2: {
|
| - action_.spatial = kOneHalfSpatialUniform;
|
| - break;
|
| - }
|
| - case 1: {
|
| - action_.spatial = kNoChangeSpatial;
|
| - break;
|
| - }
|
| - default: {
|
| - assert(false);
|
| - }
|
| - }
|
| - switch (temp_fact) {
|
| - case 3: {
|
| - action_.temporal = kTwoThirdsTemporal;
|
| - break;
|
| - }
|
| - case 2: {
|
| - action_.temporal = kOneHalfTemporal;
|
| - break;
|
| - }
|
| - case 1: {
|
| - action_.temporal = kNoChangeTemporal;
|
| - break;
|
| - }
|
| - default: {
|
| - assert(false);
|
| - }
|
| - }
|
| - // Only allow for one action (spatial or temporal) at a given time.
|
| - assert(action_.temporal == kNoChangeTemporal ||
|
| - action_.spatial == kNoChangeSpatial);
|
| -
|
| - // Adjust cases not captured in tables, mainly based on frame rate, and
|
| - // also check for odd frame sizes.
|
| - AdjustAction();
|
| -
|
| - // Update down-sampling state.
|
| - if (action_.spatial != kNoChangeSpatial ||
|
| - action_.temporal != kNoChangeTemporal) {
|
| - UpdateDownsamplingState(kDownResolution);
|
| - return true;
|
| - }
|
| - }
|
| - return false;
|
| -}
|
| -
|
| -float VCMQmResolution::GetTransitionRate(float fac_width,
|
| - float fac_height,
|
| - float fac_temp,
|
| - float scale_fac) {
|
| - ImageType image_type = GetImageType(
|
| - static_cast<uint16_t>(fac_width * width_),
|
| - static_cast<uint16_t>(fac_height * height_));
|
| -
|
| - FrameRateLevelClass framerate_level =
|
| - FrameRateLevel(fac_temp * avg_incoming_framerate_);
|
| - // If we are checking for going up temporally, and this is the last
|
| - // temporal action, then use native frame rate.
|
| - if (down_action_history_[1].temporal == kNoChangeTemporal &&
|
| - fac_temp > 1.0f) {
|
| - framerate_level = FrameRateLevel(native_frame_rate_);
|
| - }
|
| -
|
| - // The maximum allowed rate below which down-sampling is allowed:
|
| - // Nominal values based on image format (frame size and frame rate).
|
| - float max_rate = kFrameRateFac[framerate_level] * kMaxRateQm[image_type];
|
| -
|
| - uint8_t image_class = image_type > kVGA ? 1: 0;
|
| - uint8_t table_index = image_class * 9 + content_class_;
|
| - // Scale factor for down-sampling transition threshold:
|
| - // factor based on the content class and the image size.
|
| - float scaleTransRate = kScaleTransRateQm[table_index];
|
| - // Threshold bitrate for resolution action.
|
| - return static_cast<float> (scale_fac * scaleTransRate * max_rate);
|
| -}
|
| -
|
| -void VCMQmResolution::UpdateDownsamplingState(UpDownAction up_down) {
|
| - if (up_down == kUpResolution) {
|
| - qm_->spatial_width_fact = 1.0f / kFactorWidthSpatial[action_.spatial];
|
| - qm_->spatial_height_fact = 1.0f / kFactorHeightSpatial[action_.spatial];
|
| - // If last spatial action was 1/2x1/2, we undo it in two steps, so the
|
| - // spatial scale factor in this first step is modified as (4.0/3.0 / 2.0).
|
| - if (action_.spatial == kOneQuarterSpatialUniform) {
|
| - qm_->spatial_width_fact =
|
| - 1.0f * kFactorWidthSpatial[kOneHalfSpatialUniform] /
|
| - kFactorWidthSpatial[kOneQuarterSpatialUniform];
|
| - qm_->spatial_height_fact =
|
| - 1.0f * kFactorHeightSpatial[kOneHalfSpatialUniform] /
|
| - kFactorHeightSpatial[kOneQuarterSpatialUniform];
|
| - }
|
| - qm_->temporal_fact = 1.0f / kFactorTemporal[action_.temporal];
|
| - RemoveLastDownAction();
|
| - } else if (up_down == kDownResolution) {
|
| - ConstrainAmountOfDownSampling();
|
| - ConvertSpatialFractionalToWhole();
|
| - qm_->spatial_width_fact = kFactorWidthSpatial[action_.spatial];
|
| - qm_->spatial_height_fact = kFactorHeightSpatial[action_.spatial];
|
| - qm_->temporal_fact = kFactorTemporal[action_.temporal];
|
| - InsertLatestDownAction();
|
| - } else {
|
| - // This function should only be called if either the Up or Down action
|
| - // has been selected.
|
| - assert(false);
|
| - }
|
| - UpdateCodecResolution();
|
| - state_dec_factor_spatial_ = state_dec_factor_spatial_ *
|
| - qm_->spatial_width_fact * qm_->spatial_height_fact;
|
| - state_dec_factor_temporal_ = state_dec_factor_temporal_ * qm_->temporal_fact;
|
| -}
|
| -
|
| -void VCMQmResolution::UpdateCodecResolution() {
|
| - if (action_.spatial != kNoChangeSpatial) {
|
| - qm_->change_resolution_spatial = true;
|
| - qm_->codec_width = static_cast<uint16_t>(width_ /
|
| - qm_->spatial_width_fact + 0.5f);
|
| - qm_->codec_height = static_cast<uint16_t>(height_ /
|
| - qm_->spatial_height_fact + 0.5f);
|
| - // Size should not exceed native sizes.
|
| - assert(qm_->codec_width <= native_width_);
|
| - assert(qm_->codec_height <= native_height_);
|
| - // New sizes should be multiple of 2, otherwise spatial should not have
|
| - // been selected.
|
| - assert(qm_->codec_width % 2 == 0);
|
| - assert(qm_->codec_height % 2 == 0);
|
| - }
|
| - if (action_.temporal != kNoChangeTemporal) {
|
| - qm_->change_resolution_temporal = true;
|
| - // Update the frame rate based on the average incoming frame rate.
|
| - qm_->frame_rate = avg_incoming_framerate_ / qm_->temporal_fact + 0.5f;
|
| - if (down_action_history_[0].temporal == 0) {
|
| - // When we undo the last temporal-down action, make sure we go back up
|
| - // to the native frame rate. Since the incoming frame rate may
|
| - // fluctuate over time, |avg_incoming_framerate_| scaled back up may
|
| - // be smaller than |native_frame rate_|.
|
| - qm_->frame_rate = native_frame_rate_;
|
| - }
|
| - }
|
| -}
|
| -
|
| -uint8_t VCMQmResolution::RateClass(float transition_rate) {
|
| - return avg_target_rate_ < (kFacLowRate * transition_rate) ? 0:
|
| - (avg_target_rate_ >= transition_rate ? 2 : 1);
|
| -}
|
| -
|
| -// TODO(marpan): Would be better to capture these frame rate adjustments by
|
| -// extending the table data (qm_select_data.h).
|
| -void VCMQmResolution::AdjustAction() {
|
| - // If the spatial level is default state (neither low or high), motion level
|
| - // is not high, and spatial action was selected, switch to 2/3 frame rate
|
| - // reduction if the average incoming frame rate is high.
|
| - if (spatial_.level == kDefault && motion_.level != kHigh &&
|
| - action_.spatial != kNoChangeSpatial &&
|
| - framerate_level_ == kFrameRateHigh) {
|
| - action_.spatial = kNoChangeSpatial;
|
| - action_.temporal = kTwoThirdsTemporal;
|
| - }
|
| - // If both motion and spatial level are low, and temporal down action was
|
| - // selected, switch to spatial 3/4x3/4 if the frame rate is not above the
|
| - // lower middle level (|kFrameRateMiddle1|).
|
| - if (motion_.level == kLow && spatial_.level == kLow &&
|
| - framerate_level_ <= kFrameRateMiddle1 &&
|
| - action_.temporal != kNoChangeTemporal) {
|
| - action_.spatial = kOneHalfSpatialUniform;
|
| - action_.temporal = kNoChangeTemporal;
|
| - }
|
| - // If spatial action is selected, and there has been too much spatial
|
| - // reduction already (i.e., 1/4), then switch to temporal action if the
|
| - // average frame rate is not low.
|
| - if (action_.spatial != kNoChangeSpatial &&
|
| - down_action_history_[0].spatial == kOneQuarterSpatialUniform &&
|
| - framerate_level_ != kFrameRateLow) {
|
| - action_.spatial = kNoChangeSpatial;
|
| - action_.temporal = kTwoThirdsTemporal;
|
| - }
|
| - // Never use temporal action if number of temporal layers is above 2.
|
| - if (num_layers_ > 2) {
|
| - if (action_.temporal != kNoChangeTemporal) {
|
| - action_.spatial = kOneHalfSpatialUniform;
|
| - }
|
| - action_.temporal = kNoChangeTemporal;
|
| - }
|
| - // If spatial action was selected, we need to make sure the frame sizes
|
| - // are multiples of two. Otherwise switch to 2/3 temporal.
|
| - if (action_.spatial != kNoChangeSpatial &&
|
| - !EvenFrameSize()) {
|
| - action_.spatial = kNoChangeSpatial;
|
| - // Only one action (spatial or temporal) is allowed at a given time, so need
|
| - // to check whether temporal action is currently selected.
|
| - action_.temporal = kTwoThirdsTemporal;
|
| - }
|
| -}
|
| -
|
| -void VCMQmResolution::ConvertSpatialFractionalToWhole() {
|
| - // If 3/4 spatial is selected, check if there has been another 3/4,
|
| - // and if so, combine them into 1/2. 1/2 scaling is more efficient than 9/16.
|
| - // Note we define 3/4x3/4 spatial as kOneHalfSpatialUniform.
|
| - if (action_.spatial == kOneHalfSpatialUniform) {
|
| - bool found = false;
|
| - int isel = kDownActionHistorySize;
|
| - for (int i = 0; i < kDownActionHistorySize; ++i) {
|
| - if (down_action_history_[i].spatial == kOneHalfSpatialUniform) {
|
| - isel = i;
|
| - found = true;
|
| - break;
|
| - }
|
| - }
|
| - if (found) {
|
| - action_.spatial = kOneQuarterSpatialUniform;
|
| - state_dec_factor_spatial_ = state_dec_factor_spatial_ /
|
| - (kFactorWidthSpatial[kOneHalfSpatialUniform] *
|
| - kFactorHeightSpatial[kOneHalfSpatialUniform]);
|
| - // Check if switching to 1/2x1/2 (=1/4) spatial is allowed.
|
| - ConstrainAmountOfDownSampling();
|
| - if (action_.spatial == kNoChangeSpatial) {
|
| - // Not allowed. Go back to 3/4x3/4 spatial.
|
| - action_.spatial = kOneHalfSpatialUniform;
|
| - state_dec_factor_spatial_ = state_dec_factor_spatial_ *
|
| - kFactorWidthSpatial[kOneHalfSpatialUniform] *
|
| - kFactorHeightSpatial[kOneHalfSpatialUniform];
|
| - } else {
|
| - // Switching is allowed. Remove 3/4x3/4 from the history, and update
|
| - // the frame size.
|
| - for (int i = isel; i < kDownActionHistorySize - 1; ++i) {
|
| - down_action_history_[i].spatial =
|
| - down_action_history_[i + 1].spatial;
|
| - }
|
| - width_ = width_ * kFactorWidthSpatial[kOneHalfSpatialUniform];
|
| - height_ = height_ * kFactorHeightSpatial[kOneHalfSpatialUniform];
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -// Returns false if the new frame sizes, under the current spatial action,
|
| -// are not multiples of two.
|
| -bool VCMQmResolution::EvenFrameSize() {
|
| - if (action_.spatial == kOneHalfSpatialUniform) {
|
| - if ((width_ * 3 / 4) % 2 != 0 || (height_ * 3 / 4) % 2 != 0) {
|
| - return false;
|
| - }
|
| - } else if (action_.spatial == kOneQuarterSpatialUniform) {
|
| - if ((width_ * 1 / 2) % 2 != 0 || (height_ * 1 / 2) % 2 != 0) {
|
| - return false;
|
| - }
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -void VCMQmResolution::InsertLatestDownAction() {
|
| - if (action_.spatial != kNoChangeSpatial) {
|
| - for (int i = kDownActionHistorySize - 1; i > 0; --i) {
|
| - down_action_history_[i].spatial = down_action_history_[i - 1].spatial;
|
| - }
|
| - down_action_history_[0].spatial = action_.spatial;
|
| - }
|
| - if (action_.temporal != kNoChangeTemporal) {
|
| - for (int i = kDownActionHistorySize - 1; i > 0; --i) {
|
| - down_action_history_[i].temporal = down_action_history_[i - 1].temporal;
|
| - }
|
| - down_action_history_[0].temporal = action_.temporal;
|
| - }
|
| -}
|
| -
|
| -void VCMQmResolution::RemoveLastDownAction() {
|
| - if (action_.spatial != kNoChangeSpatial) {
|
| - // If the last spatial action was 1/2x1/2 we replace it with 3/4x3/4.
|
| - if (action_.spatial == kOneQuarterSpatialUniform) {
|
| - down_action_history_[0].spatial = kOneHalfSpatialUniform;
|
| - } else {
|
| - for (int i = 0; i < kDownActionHistorySize - 1; ++i) {
|
| - down_action_history_[i].spatial = down_action_history_[i + 1].spatial;
|
| - }
|
| - down_action_history_[kDownActionHistorySize - 1].spatial =
|
| - kNoChangeSpatial;
|
| - }
|
| - }
|
| - if (action_.temporal != kNoChangeTemporal) {
|
| - for (int i = 0; i < kDownActionHistorySize - 1; ++i) {
|
| - down_action_history_[i].temporal = down_action_history_[i + 1].temporal;
|
| - }
|
| - down_action_history_[kDownActionHistorySize - 1].temporal =
|
| - kNoChangeTemporal;
|
| - }
|
| -}
|
| -
|
| -void VCMQmResolution::ConstrainAmountOfDownSampling() {
|
| - // Sanity checks on down-sampling selection:
|
| - // override the settings for too small image size and/or frame rate.
|
| - // Also check the limit on current down-sampling states.
|
| -
|
| - float spatial_width_fact = kFactorWidthSpatial[action_.spatial];
|
| - float spatial_height_fact = kFactorHeightSpatial[action_.spatial];
|
| - float temporal_fact = kFactorTemporal[action_.temporal];
|
| - float new_dec_factor_spatial = state_dec_factor_spatial_ *
|
| - spatial_width_fact * spatial_height_fact;
|
| - float new_dec_factor_temp = state_dec_factor_temporal_ * temporal_fact;
|
| -
|
| - // No spatial sampling if current frame size is too small, or if the
|
| - // amount of spatial down-sampling is above maximum spatial down-action.
|
| - if ((width_ * height_) <= kMinImageSize ||
|
| - new_dec_factor_spatial > kMaxSpatialDown) {
|
| - action_.spatial = kNoChangeSpatial;
|
| - new_dec_factor_spatial = state_dec_factor_spatial_;
|
| - }
|
| - // No frame rate reduction if average frame rate is below some point, or if
|
| - // the amount of temporal down-sampling is above maximum temporal down-action.
|
| - if (avg_incoming_framerate_ <= kMinFrameRate ||
|
| - new_dec_factor_temp > kMaxTempDown) {
|
| - action_.temporal = kNoChangeTemporal;
|
| - new_dec_factor_temp = state_dec_factor_temporal_;
|
| - }
|
| - // Check if the total (spatial-temporal) down-action is above maximum allowed,
|
| - // if so, disallow the current selected down-action.
|
| - if (new_dec_factor_spatial * new_dec_factor_temp > kMaxTotalDown) {
|
| - if (action_.spatial != kNoChangeSpatial) {
|
| - action_.spatial = kNoChangeSpatial;
|
| - } else if (action_.temporal != kNoChangeTemporal) {
|
| - action_.temporal = kNoChangeTemporal;
|
| - } else {
|
| - // We only allow for one action (spatial or temporal) at a given time, so
|
| - // either spatial or temporal action is selected when this function is
|
| - // called. If the selected action is disallowed from one of the above
|
| - // 2 prior conditions (on spatial & temporal max down-action), then this
|
| - // condition "total down-action > |kMaxTotalDown|" would not be entered.
|
| - assert(false);
|
| - }
|
| - }
|
| -}
|
| -
|
| -void VCMQmResolution::PickSpatialOrTemporal() {
|
| - // Pick the one that has had the most down-sampling thus far.
|
| - if (state_dec_factor_spatial_ > state_dec_factor_temporal_) {
|
| - action_.spatial = down_action_history_[0].spatial;
|
| - action_.temporal = kNoChangeTemporal;
|
| - } else {
|
| - action_.spatial = kNoChangeSpatial;
|
| - action_.temporal = down_action_history_[0].temporal;
|
| - }
|
| -}
|
| -
|
| -// TODO(marpan): Update when we allow for directional spatial down-sampling.
|
| -void VCMQmResolution::SelectSpatialDirectionMode(float transition_rate) {
|
| - // Default is 4/3x4/3
|
| - // For bit rates well below transitional rate, we select 2x2.
|
| - if (avg_target_rate_ < transition_rate * kRateRedSpatial2X2) {
|
| - qm_->spatial_width_fact = 2.0f;
|
| - qm_->spatial_height_fact = 2.0f;
|
| - }
|
| - // Otherwise check prediction errors and aspect ratio.
|
| - float spatial_err = 0.0f;
|
| - float spatial_err_h = 0.0f;
|
| - float spatial_err_v = 0.0f;
|
| - if (content_metrics_) {
|
| - spatial_err = content_metrics_->spatial_pred_err;
|
| - spatial_err_h = content_metrics_->spatial_pred_err_h;
|
| - spatial_err_v = content_metrics_->spatial_pred_err_v;
|
| - }
|
| -
|
| - // Favor 1x2 if aspect_ratio is 16:9.
|
| - if (aspect_ratio_ >= 16.0f / 9.0f) {
|
| - // Check if 1x2 has lowest prediction error.
|
| - if (spatial_err_h < spatial_err && spatial_err_h < spatial_err_v) {
|
| - qm_->spatial_width_fact = 2.0f;
|
| - qm_->spatial_height_fact = 1.0f;
|
| - }
|
| - }
|
| - // Check for 4/3x4/3 selection: favor 2x2 over 1x2 and 2x1.
|
| - if (spatial_err < spatial_err_h * (1.0f + kSpatialErr2x2VsHoriz) &&
|
| - spatial_err < spatial_err_v * (1.0f + kSpatialErr2X2VsVert)) {
|
| - qm_->spatial_width_fact = 4.0f / 3.0f;
|
| - qm_->spatial_height_fact = 4.0f / 3.0f;
|
| - }
|
| - // Check for 2x1 selection.
|
| - if (spatial_err_v < spatial_err_h * (1.0f - kSpatialErrVertVsHoriz) &&
|
| - spatial_err_v < spatial_err * (1.0f - kSpatialErr2X2VsVert)) {
|
| - qm_->spatial_width_fact = 1.0f;
|
| - qm_->spatial_height_fact = 2.0f;
|
| - }
|
| -}
|
| -
|
| -// ROBUSTNESS CLASS
|
| -
|
| -VCMQmRobustness::VCMQmRobustness() {
|
| - Reset();
|
| -}
|
| -
|
| -VCMQmRobustness::~VCMQmRobustness() {
|
| -}
|
| -
|
| -void VCMQmRobustness::Reset() {
|
| - prev_total_rate_ = 0.0f;
|
| - prev_rtt_time_ = 0;
|
| - prev_packet_loss_ = 0;
|
| - prev_code_rate_delta_ = 0;
|
| - ResetQM();
|
| -}
|
| -
|
| -// Adjust the FEC rate based on the content and the network state
|
| -// (packet loss rate, total rate/bandwidth, round trip time).
|
| -// Note that packetLoss here is the filtered loss value.
|
| -float VCMQmRobustness::AdjustFecFactor(uint8_t code_rate_delta,
|
| - float total_rate,
|
| - float framerate,
|
| - int64_t rtt_time,
|
| - uint8_t packet_loss) {
|
| - // Default: no adjustment
|
| - float adjust_fec = 1.0f;
|
| - if (content_metrics_ == NULL) {
|
| - return adjust_fec;
|
| - }
|
| - // Compute class state of the content.
|
| - ComputeMotionNFD();
|
| - ComputeSpatial();
|
| -
|
| - // TODO(marpan): Set FEC adjustment factor.
|
| -
|
| - // Keep track of previous values of network state:
|
| - // adjustment may be also based on pattern of changes in network state.
|
| - prev_total_rate_ = total_rate;
|
| - prev_rtt_time_ = rtt_time;
|
| - prev_packet_loss_ = packet_loss;
|
| - prev_code_rate_delta_ = code_rate_delta;
|
| - return adjust_fec;
|
| -}
|
| -
|
| -// Set the UEP (unequal-protection across packets) on/off for the FEC.
|
| -bool VCMQmRobustness::SetUepProtection(uint8_t code_rate_delta,
|
| - float total_rate,
|
| - uint8_t packet_loss,
|
| - bool frame_type) {
|
| - // Default.
|
| - return false;
|
| -}
|
| -} // namespace
|
|
|