Updates for resolution adaptation.
1) added support for two additional modes:
-3/4 spatial down-sampling
-2/3 frame rate reduction
2) updated unittest and added a few more tests
3) some code refactoring
Review URL: https://webrtc-codereview.appspot.com/429005
git-svn-id: http://webrtc.googlecode.com/svn/trunk@1854 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/src/modules/video_coding/main/source/media_optimization.cc b/src/modules/video_coding/main/source/media_optimization.cc
index cae4912..552aaab 100644
--- a/src/modules/video_coding/main/source/media_optimization.cc
+++ b/src/modules/video_coding/main/source/media_optimization.cc
@@ -180,7 +180,7 @@
// Update encoding rates following protection settings
_frameDropper->SetRates(static_cast<float>(_targetBitRate), 0);
- if (_enableQm && _numLayers == 1)
+ if (_enableQm)
{
// Update QM with rates
_qmResolution->UpdateRates((float)_targetBitRate, sent_video_rate,
@@ -291,7 +291,7 @@
_numLayers = (numLayers <= 1) ? 1 : numLayers; // Can also be zero.
WebRtc_Word32 ret = VCM_OK;
ret = _qmResolution->Initialize((float)_targetBitRate, _userFrameRate,
- _codecWidth, _codecHeight);
+ _codecWidth, _codecHeight, _numLayers);
return ret;
}
@@ -575,68 +575,44 @@
}
-bool
-VCMMediaOptimization::QMUpdate(VCMResolutionScale* qm)
-{
- // Check for no change
- if (qm->spatialHeightFact == 1 &&
- qm->spatialWidthFact == 1 &&
- qm->temporalFact == 1) {
- return false;
- }
+bool VCMMediaOptimization::QMUpdate(VCMResolutionScale* qm) {
+ // Check for no change
+ if (!qm->change_resolution) {
+ return false;
+ }
- // Temporal
- WebRtc_UWord32 frameRate = static_cast<WebRtc_UWord32>
- (_incomingFrameRate + 0.5f);
+ // Check for change in frame rate.
+ if (qm->temporal_fact != 1.0f) {
+ _incomingFrameRate = _incomingFrameRate / qm->temporal_fact + 0.5f;
+ memset(_incomingFrameTimes, -1, sizeof(_incomingFrameTimes));
+ }
- // Check if go back up in temporal resolution
- if (qm->temporalFact == 0) {
- // Currently only allow for 1/2 frame rate reduction per action.
- // TODO (marpan): allow for 2/3 reduction.
- frameRate = (WebRtc_UWord32) 2 * _incomingFrameRate;
- }
- // go down in temporal resolution
- else {
- frameRate = (WebRtc_UWord32)(_incomingFrameRate / qm->temporalFact + 1);
- }
- // Reset _incomingFrameRate if temporal action was selected.
- if (qm->temporalFact != 1) {
- memset(_incomingFrameTimes, -1, sizeof(_incomingFrameTimes));
- _incomingFrameRate = frameRate;
- }
-
- // Spatial
- WebRtc_UWord32 height = _codecHeight;
- WebRtc_UWord32 width = _codecWidth;
- // Check if go back up in spatial resolution, and update frame sizes.
- // Currently only allow for 2x2 spatial down-sampling.
- // TODO (marpan): allow for 1x2, 2x1, and 4/3x4/3 (or 3/2x3/2).
- if (qm->spatialHeightFact == 0 && qm->spatialWidthFact == 0) {
- width = _codecWidth * 2;
- height = _codecHeight * 2;
- } else {
- width = _codecWidth / qm->spatialWidthFact;
- height = _codecHeight / qm->spatialHeightFact;
- }
- _codecWidth = width;
- _codecHeight = height;
-
- // New frame sizes should never exceed the original sizes
- // from SetEncodingData().
+ // Check for change in frame size.
+ if (qm->spatial_height_fact != 1.0 || qm->spatial_width_fact != 1.0) {
+ _codecWidth = static_cast<uint16_t>(_codecWidth /
+ qm->spatial_width_fact);
+ _codecHeight = static_cast<uint16_t>(_codecHeight /
+ qm->spatial_height_fact);
+ // New frame sizes should not exceed original size from SetEncodingData().
assert(_codecWidth <= _initCodecWidth);
assert(_codecHeight <= _initCodecHeight);
+ // Check that new frame sizes are multiples of two.
+ assert(_codecWidth % 2 == 0);
+ assert(_codecHeight % 2 == 0);
+ }
- WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, _id,
+ WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, _id,
"Quality Mode Update: W = %d, H = %d, FR = %f",
- width, height, frameRate);
+ _codecWidth, _codecHeight, _incomingFrameRate);
- // Update VPM with new target frame rate and size
- _videoQMSettingsCallback->SetVideoQMSettings(frameRate, width, height);
+ // Update VPM with new target frame rate and size
+ _videoQMSettingsCallback->SetVideoQMSettings(_incomingFrameRate,
+ _codecWidth,
+ _codecHeight);
- _content->UpdateFrameRate(frameRate);
- _qmResolution->UpdateCodecFrameSize(width, height);
-
- return true;
+ _content->UpdateFrameRate(_incomingFrameRate);
+ _qmResolution->UpdateCodecFrameSize(_codecWidth, _codecHeight);
+ return true;
}
void
diff --git a/src/modules/video_coding/main/source/qm_select.cc b/src/modules/video_coding/main/source/qm_select.cc
index c4bd707..ef273c0 100644
--- a/src/modules/video_coding/main/source/qm_select.cc
+++ b/src/modules/video_coding/main/source/qm_select.cc
@@ -23,13 +23,13 @@
// QM-METHOD class
VCMQmMethod::VCMQmMethod()
- : _contentMetrics(NULL),
- _width(0),
- _height(0),
- _nativeWidth(0),
- _nativeHeight(0),
- _frameRateLevel(kDefault),
- _init(false) {
+ : content_metrics_(NULL),
+ width_(0),
+ height_(0),
+ native_width_(0),
+ native_height_(0),
+ framerate_level_(kDefault),
+ init_(false) {
ResetQM();
}
@@ -37,86 +37,107 @@
}
void VCMQmMethod::ResetQM() {
- _aspectRatio = 1.0f;
- _imageType = 2;
- _motion.Reset();
- _spatial.Reset();
- _contentClass = 0;
+ aspect_ratio_ = 1.0f;
+ image_type_ = kVGA;
+ motion_.Reset();
+ spatial_.Reset();
+ content_class_ = 0;
}
uint8_t VCMQmMethod::ComputeContentClass() {
ComputeMotionNFD();
ComputeSpatial();
- return _contentClass = 3 * _motion.level + _spatial.level;
+ return content_class_ = 3 * motion_.level + spatial_.level;
}
void VCMQmMethod::UpdateContent(const VideoContentMetrics* contentMetrics) {
- _contentMetrics = contentMetrics;
+ content_metrics_ = contentMetrics;
}
void VCMQmMethod::ComputeMotionNFD() {
- if (_contentMetrics) {
- _motion.value = _contentMetrics->motion_magnitude;
+ 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;
+ if (motion_.value < kLowMotionNfd) {
+ motion_.level = kLow;
+ } else if (motion_.value > kHighMotionNfd) {
+ motion_.level = kHigh;
} else {
- _motion.level = kDefault;
+ motion_.level = kDefault;
}
}
void VCMQmMethod::ComputeSpatial() {
- float spatialErr = 0.0;
- float spatialErrH = 0.0;
- float spatialErrV = 0.0;
- if (_contentMetrics) {
- spatialErr = _contentMetrics->spatial_pred_err;
- spatialErrH = _contentMetrics->spatial_pred_err_h;
- spatialErrV = _contentMetrics->spatial_pred_err_v;
+ 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 = (spatialErr + spatialErrH + spatialErrV) / 3.0f;
+ spatial_.value = (spatial_err + spatial_err_h + spatial_err_v) / 3.0f;
- // Reduce thresholds for large scenes/higher pixel correlation (~>=WHD).
- float scale2 = _imageType > 3 ? kScaleTexture : 1.0;
+ // 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;
+ if (spatial_.value > scale2 * kHighTexture) {
+ spatial_.level = kHigh;
+ } else if (spatial_.value < scale2 * kLowTexture) {
+ spatial_.level = kLow;
} else {
- _spatial.level = kDefault;
+ spatial_.level = kDefault;
}
}
-uint8_t VCMQmMethod::GetImageType(uint16_t width,
- uint16_t height) {
- // Get the closest image type for encoder frame size.
- uint32_t imageSize = width * height;
- if (imageSize < kFrameSizeTh[0]) {
- return 0; // QCIF
- } else if (imageSize < kFrameSizeTh[1]) {
- return 1; // CIF
- } else if (imageSize < kFrameSizeTh[2]) {
- return 2; // VGA
- } else if (imageSize < kFrameSizeTh[3]) {
- return 3; // 4CIF
- } else if (imageSize < kFrameSizeTh[4]) {
- return 4; // 720,4:3
- } else if (imageSize < kFrameSizeTh[5]) {
- return 5; // WHD
+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 {
- return 6; // HD
+ // No exact match, find closet one.
+ return FindClosestImageType(width, height);
}
}
-LevelClass VCMQmMethod::FrameRateLevel(float avgFrameRate) {
- if (avgFrameRate < kLowFrameRate) {
+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);
+}
+
+LevelClass VCMQmMethod::FrameRateLevel(float avg_framerate) {
+ if (avg_framerate < kLowFrameRate) {
return kLow;
- } else if (avgFrameRate > kHighFrameRate) {
+ } else if (avg_framerate > kHighFrameRate) {
return kHigh;
} else {
return kDefault;
@@ -126,139 +147,146 @@
// RESOLUTION CLASS
VCMQmResolution::VCMQmResolution()
- : _qm(new VCMResolutionScale()) {
+ : qm_(new VCMResolutionScale()) {
Reset();
}
VCMQmResolution::~VCMQmResolution() {
- delete _qm;
+ delete qm_;
}
void VCMQmResolution::ResetRates() {
- _sumTargetRate = 0.0f;
- _sumIncomingFrameRate = 0.0f;
- _sumRateMM = 0.0f;
- _sumRateMMSgn = 0;
- _sumPacketLoss = 0.0f;
- _frameCnt = 0;
- _frameCntDelta = 0;
- _lowBufferCnt = 0;
- _updateRateCnt = 0;
+ 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_ = kOptBufferLevel * target_bitrate_;
+ frame_cnt_ = 0;
+ frame_cnt_delta_ = 0;
+ low_buffer_cnt_ = 0;
+ update_rate_cnt_ = 0;
}
void VCMQmResolution::ResetDownSamplingState() {
- _stateDecFactorSpatial = 1;
- _stateDecFactorTemp = 1;
+ 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() {
- _targetBitRate = 0.0f;
- _userFrameRate = 0.0f;
- _incomingFrameRate = 0.0f;
- _perFrameBandwidth =0.0f;
- _bufferLevel = 0.0f;
- _avgTargetRate = 0.0f;
- _avgIncomingFrameRate = 0.0f;
- _avgRatioBufferLow = 0.0f;
- _avgRateMisMatch = 0.0f;
- _avgRateMisMatchSgn = 0.0f;
- _avgPacketLoss = 0.0f;
- _encoderState = kStableEncoding;
+ target_bitrate_ = 0.0f;
+ user_framerate_ = 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 _encoderState;
+ return encoder_state_;
}
// Initialize state after re-initializing the encoder,
// i.e., after SetEncodingData() in mediaOpt.
-int VCMQmResolution::Initialize(float bitRate,
- float userFrameRate,
+int VCMQmResolution::Initialize(float bitrate,
+ float user_framerate,
uint16_t width,
- uint16_t height) {
- if (userFrameRate == 0.0f || width == 0 || height == 0) {
+ uint16_t height,
+ int num_layers) {
+ if (user_framerate == 0.0f || width == 0 || height == 0) {
return VCM_PARAMETER_ERROR;
}
Reset();
- _targetBitRate = bitRate;
- _userFrameRate = userFrameRate;
- _incomingFrameRate = userFrameRate;
+ target_bitrate_ = bitrate;
+ user_framerate_ = user_framerate;
+ incoming_framerate_ = user_framerate;
UpdateCodecFrameSize(width, height);
- _nativeWidth = width;
- _nativeHeight = height;
+ native_width_ = width;
+ native_height_ = height;
+ num_layers_ = num_layers;
// Initial buffer level.
- _bufferLevel = kInitBufferLevel * _targetBitRate;
+ buffer_level_ = kOptBufferLevel * target_bitrate_;
// Per-frame bandwidth.
- _perFrameBandwidth = _targetBitRate / _userFrameRate;
- _init = true;
+ per_frame_bandwidth_ = target_bitrate_ / user_framerate_;
+ init_ = true;
return VCM_OK;
}
void VCMQmResolution::UpdateCodecFrameSize(uint16_t width, uint16_t height) {
- _width = width;
- _height = height;
- // Set the imageType for the encoder width/height.
- _imageType = GetImageType(width, height);
+ width_ = width;
+ height_ = height;
+ image_type_ = GetImageType(width, height);
}
// Update rate data after every encoded frame.
-void VCMQmResolution::UpdateEncodedSize(int encodedSize,
- FrameType encodedFrameType) {
- _frameCnt++;
+void VCMQmResolution::UpdateEncodedSize(int encoded_size,
+ FrameType encoded_frame_type) {
+ frame_cnt_++;
// Convert to Kbps.
- float encodedSizeKbits = static_cast<float>((encodedSize * 8.0) / 1000.0);
+ float encoded_size_kbits = static_cast<float>((encoded_size * 8.0) / 1000.0);
// Update the buffer level:
// Note this is not the actual encoder buffer level.
- // |_bufferLevel| is reset to 0 every time SelectResolution is called, and
- // does not account for frame dropping by encoder or VCM.
- _bufferLevel += _perFrameBandwidth - encodedSizeKbits;
+ // |buffer_level_| is reset to average value every time 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 (_bufferLevel <= kPercBufferThr * kOptBufferLevel * _targetBitRate) {
- _lowBufferCnt++;
+ if (buffer_level_ <= kPercBufferThr * kOptBufferLevel * target_bitrate_) {
+ low_buffer_cnt_++;
}
}
// Update various quantities after SetTargetRates in MediaOpt.
-void VCMQmResolution::UpdateRates(float targetBitRate,
- float encoderSentRate,
- float incomingFrameRate,
- uint8_t packetLoss) {
+void VCMQmResolution::UpdateRates(float target_bitrate,
+ float encoder_sent_rate,
+ float incoming_framerate,
+ uint8_t packet_loss) {
// Sum the target bitrate and incoming frame rate:
// these values are the encoder rates (from previous update ~1sec),
// i.e, before the update for next ~1sec.
- _sumTargetRate += _targetBitRate;
- _sumIncomingFrameRate += _incomingFrameRate;
- _updateRateCnt++;
+ sum_target_rate_ += target_bitrate_;
+ sum_incoming_framerate_ += incoming_framerate_;
+ update_rate_cnt_++;
// Sum the received (from RTCP reports) packet loss rates.
- _sumPacketLoss += static_cast<float>(packetLoss / 255.0);
+ 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 = _targetBitRate - encoderSentRate;
- if (_targetBitRate > 0.0)
- _sumRateMM += fabs(diff) / _targetBitRate;
+ 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.
- _sumRateMMSgn += sgnDiff;
+ 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
- _targetBitRate = targetBitRate;
- _incomingFrameRate = incomingFrameRate;
+ target_bitrate_ = target_bitrate;
+ incoming_framerate_ = incoming_framerate;
// Update the per_frame_bandwidth:
// this is the per_frame_bw for the current/next ~1sec
- _perFrameBandwidth = 0.0f;
- if (_incomingFrameRate > 0.0f) {
- _perFrameBandwidth = _targetBitRate / _incomingFrameRate;
+ per_frame_bandwidth_ = 0.0f;
+ if (incoming_framerate_ > 0.0f) {
+ per_frame_bandwidth_ = target_bitrate_ / incoming_framerate_;
}
}
@@ -267,31 +295,30 @@
// (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 back up) at a given time.
-// 2) the possible down-sampling actions are: 2x2 spatial and 1/2 temporal.
-// 3) the total amount of down-sampling (spatial and/or temporal) from the
-// initial (native) resolution is limited by various factors.
-
-// TODO(marpan): extend to allow options for: 4/3x4/3, 1x2, 2x1 spatial,
-// and 2/3 temporal (i.e., skip every third frame).
+// 1) We only allow for one action, either down or up, at a given time.
+// 2) The possible down-sampling actions are: spatial 1/2x1/2, 3/4x3/4;
+// temporal 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) {
+ if (!init_) {
return VCM_UNINITIALIZED;
}
- if (_contentMetrics == NULL) {
+ if (content_metrics_ == NULL) {
Reset();
- *qm = _qm;
+ *qm = qm_;
return VCM_OK;
}
// Default settings: no action.
- _qm->spatialWidthFact = 1;
- _qm->spatialHeightFact = 1;
- _qm->temporalFact = 1;
- *qm = _qm;
+ SetDefaultAction();
+ *qm = qm_;
// Compute content class for selection.
- _contentClass = ComputeContentClass();
+ content_class_ = ComputeContentClass();
// Compute various rate quantities for selection.
ComputeRatesForSelection();
@@ -300,133 +327,146 @@
ComputeEncoderState();
// Check for going back up in resolution, if we have had some down-sampling
- // relative to native state in Initialize (i.e., after SetEncodingData()
- // in mediaOpt.).
- if (_stateDecFactorSpatial > 1 || _stateDecFactorTemp > 1) {
+ // relative to native state in Initialize().
+ if (down_action_history_[0].spatial != kNoChangeSpatial ||
+ down_action_history_[0].temporal != kNoChangeTemporal) {
if (GoingUpResolution()) {
- *qm = _qm;
+ *qm = qm_;
return VCM_OK;
}
}
// Check for going down in resolution, only if current total amount of
// down-sampling state is below threshold.
- if (_stateDecFactorTemp * _stateDecFactorSpatial < kMaxDownSample) {
+ if (state_dec_factor_temporal_ * state_dec_factor_spatial_ < kMaxDownSample) {
if (GoingDownResolution()) {
- *qm = _qm;
+ *qm = qm_;
return VCM_OK;
}
}
return VCM_OK;
}
+void VCMQmResolution::SetDefaultAction() {
+ qm_->spatial_width_fact = 1.0f;
+ qm_->spatial_height_fact = 1.0f;
+ qm_->temporal_fact = 1.0f;
+ qm_->change_resolution = false;
+ action_.spatial = kNoChangeSpatial;
+ action_.temporal = kNoChangeTemporal;
+}
+
void VCMQmResolution::ComputeRatesForSelection() {
- _avgTargetRate = 0.0f;
- _avgIncomingFrameRate = 0.0f;
- _avgRatioBufferLow = 0.0f;
- _avgRateMisMatch = 0.0f;
- _avgRateMisMatchSgn = 0.0f;
- _avgPacketLoss = 0.0f;
- if (_frameCnt > 0) {
- _avgRatioBufferLow = static_cast<float>(_lowBufferCnt) /
- static_cast<float>(_frameCnt);
+ 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 (_updateRateCnt > 0) {
- _avgRateMisMatch = static_cast<float>(_sumRateMM) /
- static_cast<float>(_updateRateCnt);
- _avgRateMisMatchSgn = static_cast<float>(_sumRateMMSgn) /
- static_cast<float>(_updateRateCnt);
- _avgTargetRate = static_cast<float>(_sumTargetRate) /
- static_cast<float>(_updateRateCnt);
- _avgIncomingFrameRate = static_cast<float>(_sumIncomingFrameRate) /
- static_cast<float>(_updateRateCnt);
- _avgPacketLoss = static_cast<float>(_sumPacketLoss) /
- static_cast<float>(_updateRateCnt);
+ 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.
- float weight = 0.7f;
- _avgTargetRate = weight * _avgTargetRate + (1.0 - weight) * _targetBitRate;
- _avgIncomingFrameRate = weight * _avgIncomingFrameRate +
- (1.0 - weight) * _incomingFrameRate;
- _frameRateLevel = FrameRateLevel(_avgIncomingFrameRate);
+ avg_target_rate_ = kWeightRate * avg_target_rate_ +
+ (1.0 - kWeightRate) * target_bitrate_;
+ avg_incoming_framerate_ = kWeightRate * avg_incoming_framerate_ +
+ (1.0 - kWeightRate) * incoming_framerate_;
+ framerate_level_ = FrameRateLevel(avg_incoming_framerate_);
}
void VCMQmResolution::ComputeEncoderState() {
// Default.
- _encoderState = kStableEncoding;
+ 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 ((_avgRatioBufferLow > kMaxBufferLow) ||
- ((_avgRateMisMatch > kMaxRateMisMatch) &&
- (_avgRateMisMatchSgn < -kRateOverShoot))) {
- _encoderState = kStressedEncoding;
+ 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 ((_avgRateMisMatch > kMaxRateMisMatch) &&
- (_avgRateMisMatchSgn > kRateUnderShoot)) {
- _encoderState = kEasyEncoding;
+ 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];
+
// Check if we should go up both spatially and temporally.
- if (_stateDecFactorSpatial > 1 && _stateDecFactorTemp > 1) {
- if (ConditionForGoingUp(2, 2, 2, kTransRateScaleUpSpatialTemp)) {
- _qm->spatialHeightFact = 0;
- _qm->spatialWidthFact = 0;
- _qm->temporalFact = 0;
- UpdateDownsamplingState(kUpResolution);
- return true;
- }
- } else {
- // Check if we should go up either spatially or temporally.
- bool selectedUpS = false;
- bool selectedUpT = false;
- if (_stateDecFactorSpatial > 1) {
- selectedUpS = ConditionForGoingUp(2, 2, 1, kTransRateScaleUpSpatial);
- }
- if (_stateDecFactorTemp > 1) {
- selectedUpT = ConditionForGoingUp(1, 1, 2, kTransRateScaleUpTemp);
- }
- if (selectedUpS && !selectedUpT) {
- _qm->spatialHeightFact = 0;
- _qm->spatialWidthFact = 0;
- UpdateDownsamplingState(kUpResolution);
- return true;
- } else if (!selectedUpS && selectedUpT) {
- _qm->temporalFact = 0;
- UpdateDownsamplingState(kUpResolution);
- return true;
- } else if (selectedUpS && selectedUpT) {
- // TODO(marpan): which one to pick?
- // pickSpatialOrTemporal()
- // For now take spatial over temporal.
- _qm->spatialHeightFact = 0;
- _qm->spatialWidthFact = 0;
+ 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(uint8_t facWidth,
- uint8_t facHeight,
- uint8_t facTemp,
- float scaleFac) {
- float estimatedTransitionRateUp = GetTransitionRate(facWidth, facHeight,
- facTemp, scaleFac);
+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 (((_avgTargetRate > estimatedTransitionRateUp) &&
- (_encoderState == kStableEncoding)) ||
- (_encoderState == kEasyEncoding)) {
+ if (((avg_target_rate_ > estimated_transition_rate_up) &&
+ (encoder_state_ == kStableEncoding)) ||
+ (encoder_state_ == kEasyEncoding)) {
return true;
} else {
return false;
@@ -434,79 +474,69 @@
}
bool VCMQmResolution::GoingDownResolution() {
- float estimatedTransitionRateDown = GetTransitionRate(1, 1, 1, 1.0);
- float maxRate = kFrameRateFac[_frameRateLevel] * kMaxRateQm[_imageType];
-
- // TODO(marpan): Bias down-sampling based on packet loss conditions.
+ 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 ((_avgTargetRate < estimatedTransitionRateDown ) ||
- (_encoderState == kStressedEncoding && _avgTargetRate < maxRate)) {
- // Get the down-sampling action.
- uint8_t spatialFact = kSpatialAction[_contentClass];
- uint8_t tempFact = kTemporalAction[_contentClass];
+ 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 (spatialFact) {
+ switch (spatial_fact) {
case 4: {
- _qm->spatialWidthFact = 2;
- _qm->spatialHeightFact = 2;
+ action_.spatial = kOneQuarterSpatialUniform;
break;
}
case 2: {
- assert(false); // Currently not used.
- // Select 1x2,2x1, or 4/3x4/3.
- // SelectSpatialDirectionMode((float) estimatedTransitionRateDown);
+ action_.spatial = kOneHalfSpatialUniform;
break;
}
case 1: {
- _qm->spatialWidthFact = 1;
- _qm->spatialHeightFact = 1;
+ action_.spatial = kNoChangeSpatial;
break;
}
default: {
assert(false);
}
}
- switch (tempFact) {
+ switch (temp_fact) {
+ case 3: {
+ action_.temporal = kTwoThirdsTemporal;
+ break;
+ }
case 2: {
- _qm->temporalFact = 2;
+ action_.temporal = kOneHalfTemporal;
break;
}
case 1: {
- _qm->temporalFact = 1;
+ action_.temporal = kNoChangeTemporal;
break;
}
default: {
assert(false);
}
}
- // Adjust some cases based on frame rate.
- // TODO(marpan): will be modified when we add 1/2 spatial and 2/3 temporal.
+
+ // TODO(marpan): If num_layers_ > 1, adjust/favor spatial over temporal ?
+
+ // Adjust cases not captured in tables, mainly based on frame rate.
AdjustAction();
- // 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.
-
- // No spatial sampling if current frame size is too small (QCIF),
- // or if amount of spatial down-sampling is already too much.
- if ((_width * _height) <= kMinImageSize ||
- _stateDecFactorSpatial >= kMaxSpatialDown) {
- _qm->spatialWidthFact = 1;
- _qm->spatialHeightFact = 1;
- }
- // No frame rate reduction if average frame rate is below some point,
- // or if the amount of temporal down-sampling is already too much.
- if (_avgIncomingFrameRate <= kMinFrameRate ||
- _stateDecFactorTemp >= kMaxTempDown) {
- _qm->temporalFact = 1;
- }
+ CheckForEvenFrameSize();
// Update down-sampling state.
- if (_qm->spatialWidthFact != 1 || _qm->spatialHeightFact != 1 ||
- _qm->temporalFact != 1) {
+ if (action_.spatial != kNoChangeSpatial ||
+ action_.temporal != kNoChangeTemporal) {
UpdateDownsamplingState(kDownResolution);
return true;
}
@@ -514,97 +544,197 @@
return false;
}
-float VCMQmResolution::GetTransitionRate(uint8_t facWidth,
- uint8_t facHeight,
- uint8_t facTemp,
- float scaleFac) {
- uint8_t imageType = GetImageType(facWidth * _width,
- facHeight * _height);
- LevelClass frameRateLevel = FrameRateLevel(facTemp * _avgIncomingFrameRate);
+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_));
+
+ LevelClass framerate_level =
+ FrameRateLevel(fac_temp * avg_incoming_framerate_);
// The maximum allowed rate below which down-sampling is allowed:
// Nominal values based on image format (frame size and frame rate).
- float maxRate = kFrameRateFac[frameRateLevel] * kMaxRateQm[imageType];
+ float max_rate = kFrameRateFac[framerate_level] * kMaxRateQm[image_type];
- uint8_t imageClass = imageType > 3 ? 1: 0;
- uint8_t tableIndex = imageClass * 9 + _contentClass;
+ 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[tableIndex];
+ float scaleTransRate = kScaleTransRateQm[table_index];
// Threshold bitrate for resolution action.
- return static_cast<float> (scaleFac * facTemp * _incomingFrameRate *
- scaleTransRate * maxRate / 30);
+ return static_cast<float> (scale_fac * scaleTransRate * max_rate);
}
-void VCMQmResolution::UpdateDownsamplingState(ResolutionAction action) {
- // Assumes for now only actions are 1/2 frame rate of 2x2 spatial.
- if (action == kUpResolution) {
- if (_qm->spatialHeightFact == 0 && _qm->spatialWidthFact == 0) {
- _stateDecFactorSpatial = _stateDecFactorSpatial / 4;
- assert(_stateDecFactorSpatial >= 1);
- }
- if (_qm->temporalFact == 0) {
- _stateDecFactorTemp = _stateDecFactorTemp / 2;
- assert(_stateDecFactorTemp >= 1);
- }
- } else if (action == kDownResolution) {
- _stateDecFactorSpatial = _stateDecFactorSpatial * _qm->spatialWidthFact
- * _qm->spatialHeightFact;
- _stateDecFactorTemp = _stateDecFactorTemp * _qm->temporalFact;
- assert(_stateDecFactorSpatial >= 1);
- assert(_stateDecFactorTemp >= 1);
+void VCMQmResolution::UpdateDownsamplingState(UpDownAction up_down) {
+ qm_->change_resolution = true;
+ if (up_down == kUpResolution) {
+ qm_->spatial_width_fact = 1.0f / kFactorWidthSpatial[action_.spatial];
+ qm_->spatial_height_fact = 1.0f / kFactorHeightSpatial[action_.spatial];
+ qm_->temporal_fact = 1.0f / kFactorTemporal[action_.temporal];
+ RemoveLastDownAction();
+ } else if (up_down == kDownResolution) {
+ qm_->spatial_width_fact = kFactorWidthSpatial[action_.spatial];
+ qm_->spatial_height_fact = kFactorHeightSpatial[action_.spatial];
+ qm_->temporal_fact = kFactorTemporal[action_.temporal];
+ ConstrainAmountOfDownSampling();
+ InsertLatestDownAction();
} else {
+ // This function should only be called if either the Up or Down action
+ // has been selected.
assert(false);
}
+ 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;
+ assert(state_dec_factor_spatial_ >= 1.0f);
+ assert(state_dec_factor_spatial_ <= kMaxSpatialDown);
+ assert(state_dec_factor_temporal_ >= 1.0f);
+ assert(state_dec_factor_temporal_ <= kMaxTempDown);
+}
+
+uint8_t VCMQmResolution::RateClass(float transition_rate) {
+ return avg_target_rate_ < (kFacLowRate * transition_rate) ? 0:
+ (avg_target_rate_ >= transition_rate ? 2 : 1);
}
void VCMQmResolution::AdjustAction() {
- if (_spatial.level == kDefault && _motion.level != kHigh &&
- _frameRateLevel == kHigh) {
- _qm->temporalFact = 2;
- _qm->spatialWidthFact = 1;
- _qm->spatialHeightFact = 1;
+ // If the spatial level is default state (neither low or high) and motion
+ // is not high, then safer to take frame rate reduction if the
+ // average incoming frame rate is high.
+ if (spatial_.level == kDefault && motion_.level != kHigh &&
+ framerate_level_ == kHigh) {
+ action_.spatial = kNoChangeSpatial;
+ action_.temporal = kOneHalfTemporal;
+ }
+ // 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 low.
+ if (motion_.level == kLow && spatial_.level == kLow &&
+ framerate_level_ == kLow && action_.temporal != kNoChangeTemporal) {
+ action_.spatial = kOneHalfSpatialUniform;
+ action_.temporal = kNoChangeTemporal;
+ }
+}
+
+void VCMQmResolution::CheckForEvenFrameSize() {
+ // If 3/4 is selected, check that the new frame size is still multiple of 2,
+ // otherwise take 1/2.
+ if (action_.spatial == kOneHalfSpatialUniform) {
+ if ((width_ * 3 / 4)%2 != 0 || (height_ * 3 / 4)%2 != 0) {
+ action_.spatial = kOneQuarterSpatialUniform;
+ }
+ }
+}
+
+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) {
+ 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.
+
+ // No spatial sampling if current frame size is too small (QCIF),
+ // or if the amount of spatial down-sampling will be too much.
+ float new_dec_factor_spatial = state_dec_factor_spatial_ *
+ qm_->spatial_width_fact *
+ qm_->spatial_height_fact;
+ if ((width_ * height_) <= kMinImageSize ||
+ new_dec_factor_spatial > kMaxSpatialDown) {
+ action_.spatial = kNoChangeSpatial;
+ qm_->spatial_width_fact = 1.0f;
+ qm_->spatial_height_fact = 1.0f;
+ }
+ // No frame rate reduction if average frame rate is below some point,
+ // or if the amount of temporal down-sampling will be too much.
+ float new_dec_factor_temp = state_dec_factor_temporal_ * qm_->temporal_fact;
+ if (avg_incoming_framerate_ <= kMinFrameRate ||
+ new_dec_factor_temp >= kMaxTempDown) {
+ action_.temporal = kNoChangeTemporal;
+ qm_->temporal_fact = 1.0f;
+ }
+}
+
+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 this when we allow for 1/2 spatial down-sampling.
-void VCMQmResolution::SelectSpatialDirectionMode(float transRate) {
- // Default is 1x2 (H)
+void VCMQmResolution::SelectSpatialDirectionMode(float transition_rate) {
+ // Default is 4/3x4/3
// For bit rates well below transitional rate, we select 2x2.
- if (_targetBitRate < transRate * kRateRedSpatial2X2) {
- _qm->spatialWidthFact = 2;
- _qm->spatialHeightFact = 2;
+ 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 spatialErr = 0.0;
- float spatialErrH = 0.0;
- float spatialErrV = 0.0;
- if (_contentMetrics) {
- spatialErr = _contentMetrics->spatial_pred_err;
- spatialErrH = _contentMetrics->spatial_pred_err_h;
- spatialErrV = _contentMetrics->spatial_pred_err_v;
+ 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 (_aspectRatio >= 16.0f / 9.0f) {
+ if (aspect_ratio_ >= 16.0f / 9.0f) {
// Check if 1x2 has lowest prediction error.
- if (spatialErrH < spatialErr && spatialErrH < spatialErrV) {
- _qm->spatialWidthFact = 2;
- _qm->spatialHeightFact = 1;
+ 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 2x2 selection: favor 2x2 over 1x2 and 2x1.
- if (spatialErr < spatialErrH * (1.0f + kSpatialErr2x2VsHoriz) &&
- spatialErr < spatialErrV * (1.0f + kSpatialErr2X2VsVert)) {
- _qm->spatialWidthFact = 2;
- _qm->spatialHeightFact = 2;
+ // 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 (spatialErrV < spatialErrH * (1.0f - kSpatialErrVertVsHoriz) &&
- spatialErrV < spatialErr * (1.0f - kSpatialErr2X2VsVert)) {
- _qm->spatialWidthFact = 1;
- _qm->spatialHeightFact = 2;
+ 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;
}
}
@@ -618,25 +748,25 @@
}
void VCMQmRobustness::Reset() {
- _prevTotalRate = 0.0f;
- _prevRttTime = 0;
- _prevPacketLoss = 0;
- _prevCodeRateDelta = 0;
+ 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 codeRateDelta,
- float totalRate,
- float frameRate,
- uint32_t rttTime,
- uint8_t packetLoss) {
+float VCMQmRobustness::AdjustFecFactor(uint8_t code_rate_delta,
+ float total_rate,
+ float framerate,
+ uint32_t rtt_time,
+ uint8_t packet_loss) {
// Default: no adjustment
- float adjustFec = 1.0f;
- if (_contentMetrics == NULL) {
- return adjustFec;
+ float adjust_fec = 1.0f;
+ if (content_metrics_ == NULL) {
+ return adjust_fec;
}
// Compute class state of the content.
ComputeMotionNFD();
@@ -646,18 +776,18 @@
// Keep track of previous values of network state:
// adjustment may be also based on pattern of changes in network state.
- _prevTotalRate = totalRate;
- _prevRttTime = rttTime;
- _prevPacketLoss = packetLoss;
- _prevCodeRateDelta = codeRateDelta;
- return adjustFec;
+ 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 codeRateDelta,
- float totalRate,
- uint8_t packetLoss,
- bool frameType) {
+bool VCMQmRobustness::SetUepProtection(uint8_t code_rate_delta,
+ float total_rate,
+ uint8_t packet_loss,
+ bool frame_type) {
// Default.
return false;
}
diff --git a/src/modules/video_coding/main/source/qm_select.h b/src/modules/video_coding/main/source/qm_select.h
index 1859530..b76e32a 100644
--- a/src/modules/video_coding/main/source/qm_select.h
+++ b/src/modules/video_coding/main/source/qm_select.h
@@ -23,15 +23,33 @@
struct VCMResolutionScale {
VCMResolutionScale()
- : spatialWidthFact(1),
- spatialHeightFact(1),
- temporalFact(1) {
+ : spatial_width_fact(1.0f),
+ spatial_height_fact(1.0f),
+ temporal_fact(1.0f),
+ change_resolution(false) {
}
- uint8_t spatialWidthFact;
- uint8_t spatialHeightFact;
- uint8_t temporalFact;
+ float spatial_width_fact;
+ float spatial_height_fact;
+ float temporal_fact;
+ bool change_resolution;
};
+enum ImageType {
+ kQCIF = 0, // 176x144
+ kHCIF, // 264x216 = half(~3/4x3/4) CIF.
+ kQVGA, // 320x240 = quarter VGA.
+ kCIF, // 352x288
+ kHVGA, // 480x360 = half(~3/4x3/4) VGA.
+ kVGA, // 640x480
+ kQFULLHD, // 960x540 = quarter FULLHD, and half(~3/4x3/4) WHD.
+ kWHD, // 1280x720
+ kFULLHD, // 1920x1080
+ kNumImageTypes
+};
+
+const uint32_t kSizeOfImageType[kNumImageTypes] =
+{ 25344, 57024, 76800, 101376, 172800, 307200, 518400, 921600, 2073600 };
+
enum LevelClass {
kLow,
kHigh,
@@ -51,12 +69,44 @@
LevelClass level;
};
-enum ResolutionAction {
- kDownResolution,
+enum UpDownAction {
kUpResolution,
- kNoChangeResolution
+ kDownResolution
};
+enum SpatialAction {
+ kNoChangeSpatial,
+ kOneHalfSpatialUniform, // 3/4 x 3/4: 9/6 ~1/2 pixel reduction.
+ kOneQuarterSpatialUniform, // 1/2 x 1/2: 1/4 pixel reduction.
+ kNumModesSpatial
+};
+
+enum TemporalAction {
+ kNoChangeTemporal,
+ kTwoThirdsTemporal, // 2/3 frame rate reduction
+ kOneHalfTemporal, // 1/2 frame rate reduction
+ kNumModesTemporal
+};
+
+struct ResolutionAction {
+ ResolutionAction()
+ : spatial(kNoChangeSpatial),
+ temporal(kNoChangeTemporal) {
+ }
+ SpatialAction spatial;
+ TemporalAction temporal;
+};
+
+// Down-sampling factors for spatial (width and height), and temporal.
+const float kFactorWidthSpatial[kNumModesSpatial] =
+ { 1.0f, 4.0f / 3.0f, 2.0f };
+
+const float kFactorHeightSpatial[kNumModesSpatial] =
+ { 1.0f, 4.0f / 3.0f, 2.0f };
+
+const float kFactorTemporal[kNumModesTemporal] =
+ { 1.0f, 1.5f, 2.0f };
+
enum EncoderState {
kStableEncoding, // Low rate mis-match, stable buffer levels.
kStressedEncoding, // Significant over-shooting of target rate,
@@ -79,7 +129,7 @@
uint8_t ComputeContentClass();
// Update with the content metrics.
- void UpdateContent(const VideoContentMetrics* contentMetrics);
+ void UpdateContent(const VideoContentMetrics* content_metrics);
// Compute spatial texture magnitude and level.
// Spatial texture is a spatial prediction error measure.
@@ -90,29 +140,32 @@
void ComputeMotionNFD();
// Get the imageType (CIF, VGA, HD, etc) for the system width/height.
- uint8_t GetImageType(uint16_t width, uint16_t height);
+ ImageType GetImageType(uint16_t width, uint16_t height);
+
+ // Return the closest image type.
+ ImageType FindClosestImageType(uint16_t width, uint16_t height);
// Get the frame rate level.
LevelClass FrameRateLevel(float frame_rate);
protected:
// Content Data.
- const VideoContentMetrics* _contentMetrics;
+ const VideoContentMetrics* content_metrics_;
// Encoder frame sizes and native frame sizes.
- uint16_t _width;
- uint16_t _height;
- uint16_t _nativeWidth;
- uint16_t _nativeHeight;
- float _aspectRatio;
+ uint16_t width_;
+ uint16_t height_;
+ uint16_t native_width_;
+ uint16_t native_height_;
+ float aspect_ratio_;
// Image type and frame rate leve, for the current encoder resolution.
- uint8_t _imageType;
- LevelClass _frameRateLevel;
+ ImageType image_type_;
+ LevelClass framerate_level_;
// Content class data.
- VCMContFeature _motion;
- VCMContFeature _spatial;
- uint8_t _contentClass;
- bool _init;
+ VCMContFeature motion_;
+ VCMContFeature spatial_;
+ uint8_t content_class_;
+ bool init_;
};
// Resolution settings class
@@ -135,27 +188,35 @@
EncoderState GetEncoderState();
// Initialize after SetEncodingData in media_opt.
- int Initialize(float bitRate, float userFrameRate,
- uint16_t width, uint16_t height);
+ int Initialize(float bitrate,
+ float user_framerate,
+ uint16_t width,
+ uint16_t height,
+ int num_layers);
// Update the encoder frame size.
void UpdateCodecFrameSize(uint16_t width, uint16_t height);
// Update with actual bit rate (size of the latest encoded frame)
// and frame type, after every encoded frame.
- void UpdateEncodedSize(int encodedSize,
- FrameType encodedFrameType);
+ void UpdateEncodedSize(int encoded_size,
+ FrameType encoded_frame_type);
// Update with new target bitrate, actual encoder sent rate, frame_rate,
// loss rate: every ~1 sec from SetTargetRates in media_opt.
- void UpdateRates(float targetBitRate, float encoderSentRate,
- float incomingFrameRate, uint8_t packetLoss);
+ void UpdateRates(float target_bitrate,
+ float encoder_sent_rate,
+ float incoming_framerate,
+ uint8_t packet_loss);
// Extract ST (spatio-temporal) resolution action.
// Inputs: qm: Reference to the quality modes pointer.
// Output: the spatial and/or temporal scale change.
int SelectResolution(VCMResolutionScale** qm);
+ // Set the default resolution action.
+ void SetDefaultAction();
+
// Compute rates for the selection of down-sampling action.
void ComputeRatesForSelection();
@@ -171,57 +232,89 @@
// Check the condition for going up in resolution by the scale factors:
// |facWidth|, |facHeight|, |facTemp|.
// |scaleFac| is a scale factor for the transition rate.
- bool ConditionForGoingUp(uint8_t facWidth, uint8_t facHeight,
- uint8_t facTemp,
- float scaleFac);
+ bool ConditionForGoingUp(float fac_width,
+ float fac_height,
+ float fac_temp,
+ float scale_fac);
// Get the bitrate threshold for the resolution action.
// The case |facWidth|=|facHeight|=|facTemp|==1 is for down-sampling action.
// |scaleFac| is a scale factor for the transition rate.
- float GetTransitionRate(uint8_t facWidth, uint8_t facHeight,
- uint8_t facTemp, float scaleFac);
+ float GetTransitionRate(float fac_width,
+ float fac_height,
+ float fac_temp,
+ float scale_fac);
- // Update the downsampling state.
- void UpdateDownsamplingState(ResolutionAction action);
+ // Update the down-sampling state.
+ void UpdateDownsamplingState(UpDownAction up_down);
+ // Return a state based on average target rate relative transition rate.
+ uint8_t RateClass(float transition_rate);
+
+ // Adjust the action selected from the table.
void AdjustAction();
+ // Check if the new frame sizes are still divisible by 2.
+ void CheckForEvenFrameSize();
+
+ // Insert latest down-sampling action into the history list.
+ void InsertLatestDownAction();
+
+ // Remove the last (first element) down-sampling action from the list.
+ void RemoveLastDownAction();
+
+ // Check constraints on the amount of down-sampling allowed.
+ void ConstrainAmountOfDownSampling();
+
+ // For going up in resolution: pick spatial or temporal action,
+ // if both actions were separately selected.
+ void PickSpatialOrTemporal();
+
// Select the directional (1x2 or 2x1) spatial down-sampling action.
- void SelectSpatialDirectionMode(float transRate);
+ void SelectSpatialDirectionMode(float transition_rate);
private:
- VCMResolutionScale* _qm;
+ enum { kDownActionHistorySize = 10};
+
+ VCMResolutionScale* qm_;
// Encoder rate control parameters.
- float _targetBitRate;
- float _userFrameRate;
- float _incomingFrameRate;
- float _perFrameBandwidth;
- float _bufferLevel;
+ float target_bitrate_;
+ float user_framerate_;
+ float incoming_framerate_;
+ float per_frame_bandwidth_;
+ float buffer_level_;
// Data accumulated every ~1sec from MediaOpt.
- float _sumTargetRate;
- float _sumIncomingFrameRate;
- float _sumRateMM;
- float _sumRateMMSgn;
- float _sumPacketLoss;
+ float sum_target_rate_;
+ float sum_incoming_framerate_;
+ float sum_rate_MM_;
+ float sum_rate_MM_sgn_;
+ float sum_packet_loss_;
// Counters.
- uint32_t _frameCnt;
- uint32_t _frameCntDelta;
- uint32_t _updateRateCnt;
- uint32_t _lowBufferCnt;
+ uint32_t frame_cnt_;
+ uint32_t frame_cnt_delta_;
+ uint32_t update_rate_cnt_;
+ uint32_t low_buffer_cnt_;
// Resolution state parameters.
- uint8_t _stateDecFactorSpatial;
- uint8_t _stateDecFactorTemp;
+ float state_dec_factor_spatial_;
+ float state_dec_factor_temporal_;
// Quantities used for selection.
- float _avgTargetRate;
- float _avgIncomingFrameRate;
- float _avgRatioBufferLow;
- float _avgRateMisMatch;
- float _avgRateMisMatchSgn;
- float _avgPacketLoss;
- EncoderState _encoderState;
+ float avg_target_rate_;
+ float avg_incoming_framerate_;
+ float avg_ratio_buffer_low_;
+ float avg_rate_mismatch_;
+ float avg_rate_mismatch_sgn_;
+ float avg_packet_loss_;
+ EncoderState encoder_state_;
+ ResolutionAction action_;
+ // Short history of the down-sampling actions from the Initialize() state.
+ // This is needed for going up in resolution. Since the total amount of
+ // down-sampling actions are constrained, the length of the list need not be
+ // large: i.e., (4/3) ^{kDownActionHistorySize} <= kMaxDownSample.
+ ResolutionAction down_action_history_[kDownActionHistorySize];
+ int num_layers_;
};
// Robustness settings class.
@@ -235,24 +328,24 @@
// Adjust FEC rate based on content: every ~1 sec from SetTargetRates.
// Returns an adjustment factor.
- float AdjustFecFactor(uint8_t codeRateDelta,
- float totalRate,
- float frameRate,
- uint32_t rttTime,
- uint8_t packetLoss);
+ float AdjustFecFactor(uint8_t code_rate_delta,
+ float total_rate,
+ float framerate,
+ uint32_t rtt_time,
+ uint8_t packet_loss);
// Set the UEP protection on/off.
- bool SetUepProtection(uint8_t codeRateDelta,
- float totalRate,
- uint8_t packetLoss,
- bool frameType);
+ bool SetUepProtection(uint8_t code_rate_delta,
+ float total_rate,
+ uint8_t packet_loss,
+ bool frame_type);
private:
// Previous state of network parameters.
- float _prevTotalRate;
- uint32_t _prevRttTime;
- uint8_t _prevPacketLoss;
- uint8_t _prevCodeRateDelta;
+ float prev_total_rate_;
+ uint32_t prev_rtt_time_;
+ uint8_t prev_packet_loss_;
+ uint8_t prev_code_rate_delta_;
};
} // namespace webrtc
#endif // WEBRTC_MODULES_VIDEO_CODING_QM_SELECT_H_
diff --git a/src/modules/video_coding/main/source/qm_select_data.h b/src/modules/video_coding/main/source/qm_select_data.h
index d4af642..ec1e988 100644
--- a/src/modules/video_coding/main/source/qm_select_data.h
+++ b/src/modules/video_coding/main/source/qm_select_data.h
@@ -23,11 +23,8 @@
// PARAMETERS FOR RESOLUTION ADAPTATION
//
-// Initial level of buffer in secs: should corresponds to wrapper settings.
-const float kInitBufferLevel = 0.5f;
-
// Optimal level of buffer in secs: should corresponds to wrapper settings.
-const float kOptBufferLevel = 0.6f;
+const float kOptBufferLevel = 0.5f;
// Threshold of (max) buffer size below which we consider too low (underflow).
const float kPercBufferThr = 0.10f;
@@ -42,6 +39,9 @@
const float kRateOverShoot = 0.75f;
const float kRateUnderShoot = 0.75f;
+// Factor to favor weighting the average rates with the current/last data.
+const float kWeightRate = 0.70f;
+
// Factor for transitional rate for going back up in resolution.
const float kTransRateScaleUpSpatial = 1.25f;
const float kTransRateScaleUpTemp = 1.25f;
@@ -53,16 +53,19 @@
// Factor for reducing transitonal bitrate under packet loss.
const float kPacketLossRateFac = 1.0f;
+
// Maximum possible transitional rate for down-sampling:
// (units in kbps), for 30fps.
-const uint16_t kMaxRateQm[7] = {
- 100, // QCIF
+const uint16_t kMaxRateQm[9] = {
+ 50, // QCIF
+ 100, // kHCIF
+ 175, // kQVGA
250, // CIF
+ 350, // HVGA
500, // VGA
- 800, // 4CIF
- 1000, // 720 HD 4:3,
- 1500, // 720 HD 16:9
- 2000 // 1080HD
+ 1000, // QFULLHD
+ 1500, // WHD
+ 2000 // FULLHD
};
// Frame rate scale for maximum transition rate.
@@ -75,32 +78,37 @@
// Scale for transitional rate: based on content class
// motion=L/H/D,spatial==L/H/D: for low, high, middle levels
const float kScaleTransRateQm[18] = {
- // 4CIF and lower
+ // VGA and lower
0.50f, // L, L
0.50f, // L, H
0.50f, // L, D
0.50f, // H ,L
- 0.25f, // H, H
- 0.25f, // H, D
+ 0.35f, // H, H
+ 0.35f, // H, D
0.50f, // D, L
0.50f, // D, D
- 0.25f, // D, H
+ 0.35f, // D, H
- // over 4CIF: WHD, HD
+ // over VGA
0.50f, // L, L
0.50f, // L, H
0.50f, // L, D
0.50f, // H ,L
- 0.25f, // H, H
- 0.25f, // H, D
+ 0.35f, // H, H
+ 0.35f, // H, D
0.50f, // D, L
0.50f, // D, D
- 0.25f, // D, H
+ 0.35f, // D, H
};
+// Threshold on the target rate relative to transitional rate.
+const float kFacLowRate = 0.75f;
+
// Action for down-sampling:
-// motion=L/H/D,spatial==L/H/D: for low, high, middle levels
-const uint8_t kSpatialAction[9] = {
+// motion=L/H/D,spatial==L/H/D, for low, high, middle levels;
+// rate = 0/1/2, for target rate state relative to transition rate.
+const uint8_t kSpatialAction[27] = {
+// rateClass = 0:
1, // L, L
1, // L, H
1, // L, D
@@ -109,25 +117,70 @@
4, // H, D
4, // D, L
1, // D, H
- 1, // D, D
+ 2, // D, D
+
+// rateClass = 1:
+ 1, // L, L
+ 1, // L, H
+ 1, // L, D
+ 4, // H ,L
+ 1, // H, H
+ 2, // H, D
+ 2, // D, L
+ 1, // D, H
+ 2, // D, D
+
+// rateClass = 2:
+ 1, // L, L
+ 1, // L, H
+ 1, // L, D
+ 2, // H ,L
+ 1, // H, H
+ 2, // H, D
+ 2, // D, L
+ 1, // D, H
+ 2, // D, D
};
-const uint8_t kTemporalAction[9] = {
- 1, // L, L
+const uint8_t kTemporalAction[27] = {
+// rateClass = 0:
+ 3, // L, L
2, // L, H
2, // L, D
1, // H ,L
- 2, // H, H
+ 3, // H, H
1, // H, D
1, // D, L
2, // D, H
1, // D, D
+
+// rateClass = 1:
+ 3, // L, L
+ 2, // L, H
+ 3, // L, D
+ 1, // H ,L
+ 3, // H, H
+ 1, // H, D
+ 1, // D, L
+ 3, // D, H
+ 1, // D, D
+
+// rateClass = 2:
+ 1, // L, L
+ 3, // L, H
+ 3, // L, D
+ 1, // H ,L
+ 3, // H, H
+ 1, // H, D
+ 1, // D, L
+ 3, // D, H
+ 1, // D, D
};
// Control the total amount of down-sampling allowed.
-const int kMaxSpatialDown = 16;
-const int kMaxTempDown = 4;
-const int kMaxDownSample = 16;
+const float kMaxSpatialDown = 8.0f;
+const float kMaxTempDown = 4.0f;
+const float kMaxDownSample = 16.0f;
// Minimum image size for a spatial down-sampling.
const int kMinImageSize= 176 * 144;
@@ -136,16 +189,6 @@
// no frame rate reduction if incomingFrameRate <= MIN_FRAME_RATE
const int kMinFrameRate = 8;
-// Boundaries for the closest standard frame size
-const uint32_t kFrameSizeTh[6] = {
- 63360, // between 176*144 and 352*288
- 204288, // between 352*288 and 640*480
- 356352, // between 640*480 and 704*576
- 548352, // between 704*576 and 960*720
- 806400, // between 960*720 and 1280*720
- 1497600, // between 1280*720 and 1920*1080
-};
-
//
// PARAMETERS FOR FEC ADJUSTMENT: TODO (marpan)
//
diff --git a/src/modules/video_coding/main/source/qm_select_unittest.cc b/src/modules/video_coding/main/source/qm_select_unittest.cc
index e0ab7bf..5b670fb 100644
--- a/src/modules/video_coding/main/source/qm_select_unittest.cc
+++ b/src/modules/video_coding/main/source/qm_select_unittest.cc
@@ -20,6 +20,15 @@
namespace webrtc {
+// Representative values of content metrics for: low/high/medium(default) state,
+// based on parameters settings in qm_select_data.h.
+const float kSpatialLow = 0.01f;
+const float kSpatialMedium = 0.03f;
+const float kSpatialHigh = 0.1f;
+const float kTemporalLow = 0.01f;
+const float kTemporalMedium = 0.06f;
+const float kTemporalHigh = 0.1f;
+
class QmSelectTest : public ::testing::Test {
protected:
QmSelectTest()
@@ -31,8 +40,11 @@
VideoContentMetrics* content_metrics_;
VCMResolutionScale* qm_scale_;
- void InitQmNativeData(float initial_bit_rate, int user_frame_rate,
- int native_width, int native_height);
+ void InitQmNativeData(float initial_bit_rate,
+ int user_frame_rate,
+ int native_width,
+ int native_height,
+ int num_layers);
void UpdateQmEncodedFrame(int* encoded_size, int num_updates);
@@ -48,9 +60,9 @@
float spatial_metric_vert);
bool IsSelectedActionCorrect(VCMResolutionScale* qm_scale,
- uint8_t fac_width,
- uint8_t fac_height,
- uint8_t fac_temp);
+ float fac_width,
+ float fac_height,
+ float fac_temp);
void TearDown() {
delete qm_resolution_;
@@ -60,31 +72,31 @@
TEST_F(QmSelectTest, HandleInputs) {
// Expect parameter error. Initialize with invalid inputs.
- EXPECT_EQ(-4, qm_resolution_->Initialize(1000, 0, 640, 480));
- EXPECT_EQ(-4, qm_resolution_->Initialize(1000, 30, 640, 0));
- EXPECT_EQ(-4, qm_resolution_->Initialize(1000, 30, 0, 480));
+ EXPECT_EQ(-4, qm_resolution_->Initialize(1000, 0, 640, 480, 1));
+ EXPECT_EQ(-4, qm_resolution_->Initialize(1000, 30, 640, 0, 1));
+ EXPECT_EQ(-4, qm_resolution_->Initialize(1000, 30, 0, 480, 1));
// Expect uninitialized error.: No valid initialization before selection.
EXPECT_EQ(-7, qm_resolution_->SelectResolution(&qm_scale_));
VideoContentMetrics* content_metrics = NULL;
- EXPECT_EQ(0, qm_resolution_->Initialize(1000, 30, 640, 480));
+ EXPECT_EQ(0, qm_resolution_->Initialize(1000, 30, 640, 480, 1));
qm_resolution_->UpdateContent(content_metrics);
// Content metrics are NULL: Expect success and no down-sampling action.
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0, 1.0, 1.0));
}
// No down-sampling action at high rates.
TEST_F(QmSelectTest, NoActionHighRate) {
// Initialize with bitrate, frame rate, and native system width/height.
- InitQmNativeData(800, 30, 640, 480);
+ InitQmNativeData(800, 30, 640, 480, 1);
// Update with encoder frame size.
uint16_t codec_width = 640;
uint16_t codec_height = 480;
qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
- EXPECT_EQ(2, qm_resolution_->GetImageType(codec_width, codec_height));
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
// Update rates for a sequence of intervals.
int target_rate[] = {800, 800, 800};
@@ -95,24 +107,24 @@
fraction_lost, 3);
// Update content: motion level, and 3 spatial prediction errors.
- UpdateQmContentData(0.01f, 0.01f, 0.01f, 0.01f);
+ UpdateQmContentData(kTemporalLow, kSpatialLow, kSpatialLow, kSpatialLow);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(0, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 1.0f));
}
// Rate is well below transition, down-sampling action is taken,
// depending on the content state.
TEST_F(QmSelectTest, DownActionLowRate) {
// Initialize with bitrate, frame rate, and native system width/height.
- InitQmNativeData(100, 30, 640, 480);
+ InitQmNativeData(100, 30, 640, 480, 1);
// Update with encoder frame size.
uint16_t codec_width = 640;
uint16_t codec_height = 480;
qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
- EXPECT_EQ(2, qm_resolution_->GetImageType(codec_width, codec_height));
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
// Update rates for a sequence of intervals.
int target_rate[] = {100, 100, 100};
@@ -124,80 +136,84 @@
// Update content: motion level, and 3 spatial prediction errors.
// High motion, low spatial: 2x2 spatial expected.
- UpdateQmContentData(0.1f, 0.01f, 0.01f, 0.01f);
+ UpdateQmContentData(kTemporalHigh, kSpatialLow, kSpatialLow, kSpatialLow);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(3, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2, 2, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2.0f, 2.0f, 1.0f));
qm_resolution_->ResetDownSamplingState();
- // Low motion, low spatial: no action expected: content is too low.
- UpdateQmContentData(0.01f, 0.01f, 0.01f, 0.01f);
+ // Low motion, low spatial: 2/3 temporal is expected.
+ UpdateQmContentData(kTemporalLow, kSpatialLow, kSpatialLow, kSpatialLow);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(0, qm_resolution_->ComputeContentClass());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 1.5f));
qm_resolution_->ResetDownSamplingState();
// Medium motion, low spatial: 2x2 spatial expected.
- UpdateQmContentData(0.06f, 0.01f, 0.01f, 0.01f);
+ UpdateQmContentData(kTemporalMedium, kSpatialLow, kSpatialLow, kSpatialLow);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(6, qm_resolution_->ComputeContentClass());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2, 2, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2.0f, 2.0f, 1.0f));
qm_resolution_->ResetDownSamplingState();
// High motion, high spatial: 1/2 temporal expected.
- UpdateQmContentData(0.1f, 0.1f, 0.1f, 0.1f);
+ UpdateQmContentData(kTemporalHigh, kSpatialHigh, kSpatialHigh, kSpatialHigh);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(4, qm_resolution_->ComputeContentClass());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 2));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 1.5f));
qm_resolution_->ResetDownSamplingState();
// Low motion, high spatial: 1/2 temporal expected.
- UpdateQmContentData(0.01f, 0.1f, 0.1f, 0.1f);
+ UpdateQmContentData(kTemporalLow, kSpatialHigh, kSpatialHigh, kSpatialHigh);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(1, qm_resolution_->ComputeContentClass());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 2));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 2.0f));
qm_resolution_->ResetDownSamplingState();
// Medium motion, high spatial: 1/2 temporal expected.
- UpdateQmContentData(0.06f, 0.1f, 0.1f, 0.1f);
+ UpdateQmContentData(kTemporalMedium, kSpatialHigh, kSpatialHigh,
+ kSpatialHigh);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(7, qm_resolution_->ComputeContentClass());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 2));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 2.0f));
qm_resolution_->ResetDownSamplingState();
// High motion, medium spatial: 2x2 spatial expected.
- UpdateQmContentData(0.1f, 0.03f, 0.03f, 0.03f);
+ UpdateQmContentData(kTemporalHigh, kSpatialMedium, kSpatialMedium,
+ kSpatialMedium);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(5, qm_resolution_->ComputeContentClass());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2, 2, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2.0f, 2.0f, 1.0f));
qm_resolution_->ResetDownSamplingState();
// Low motion, medium spatial: high frame rate, so 1/2 temporal expected.
- UpdateQmContentData(0.01f, 0.03f, 0.03f, 0.03f);
+ UpdateQmContentData(kTemporalLow, kSpatialMedium, kSpatialMedium,
+ kSpatialMedium);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(2, qm_resolution_->ComputeContentClass());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 2));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 2.0f));
qm_resolution_->ResetDownSamplingState();
// Medium motion, medium spatial: high frame rate, so 1/2 temporal expected.
- UpdateQmContentData(0.06f, 0.03f, 0.03f, 0.03f);
+ UpdateQmContentData(kTemporalMedium, kSpatialMedium, kSpatialMedium,
+ kSpatialMedium);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(8, qm_resolution_->ComputeContentClass());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 2));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 2.0f));
}
// Rate mis-match is high, and we have over-shooting.
// since target rate is below max for down-sampling, down-sampling is selected.
TEST_F(QmSelectTest, DownActionHighRateMMOvershoot) {
// Initialize with bitrate, frame rate, and native system width/height.
- InitQmNativeData(450, 30, 640, 480);
+ InitQmNativeData(450, 30, 640, 480, 1);
// Update with encoder frame size.
uint16_t codec_width = 640;
uint16_t codec_height = 480;
qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
- EXPECT_EQ(2, qm_resolution_->GetImageType(codec_width, codec_height));
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
// Update rates for a sequence of intervals.
int target_rate[] = {450, 450, 450};
@@ -209,31 +225,32 @@
// Update content: motion level, and 3 spatial prediction errors.
// High motion, low spatial.
- UpdateQmContentData(0.1f, 0.01f, 0.01f, 0.01f);
+ UpdateQmContentData(kTemporalHigh, kSpatialLow, kSpatialLow, kSpatialLow);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(3, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStressedEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2, 2, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 4.0f / 3.0f, 4.0f / 3.0f,
+ 1.0f));
qm_resolution_->ResetDownSamplingState();
// Low motion, high spatial
- UpdateQmContentData(0.01f, 0.1f, 0.1f, 0.1f);
+ UpdateQmContentData(kTemporalLow, kSpatialHigh, kSpatialHigh, kSpatialHigh);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(1, qm_resolution_->ComputeContentClass());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 2));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 1.5f));
}
// Rate mis-match is high, target rate is below max for down-sampling,
// but since we have consistent under-shooting, no down-sampling action.
TEST_F(QmSelectTest, NoActionHighRateMMUndershoot) {
// Initialize with bitrate, frame rate, and native system width/height.
- InitQmNativeData(450, 30, 640, 480);
+ InitQmNativeData(450, 30, 640, 480, 1);
// Update with encoder frame size.
uint16_t codec_width = 640;
uint16_t codec_height = 480;
qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
- EXPECT_EQ(2, qm_resolution_->GetImageType(codec_width, codec_height));
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
// Update rates for a sequence of intervals.
int target_rate[] = {450, 450, 450};
@@ -245,31 +262,31 @@
// Update content: motion level, and 3 spatial prediction errors.
// High motion, low spatial.
- UpdateQmContentData(0.1f, 0.01f, 0.01f, 0.01f);
+ UpdateQmContentData(kTemporalHigh, kSpatialLow, kSpatialLow, kSpatialLow);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(3, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kEasyEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 1.0f));
qm_resolution_->ResetDownSamplingState();
// Low motion, high spatial
- UpdateQmContentData(0.01f, 0.1f, 0.1f, 0.1f);
+ UpdateQmContentData(kTemporalLow, kSpatialHigh, kSpatialHigh, kSpatialHigh);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(1, qm_resolution_->ComputeContentClass());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 1.0f));
}
// Buffer is underflowing, and target rate is below max for down-sampling,
// so action is taken.
TEST_F(QmSelectTest, DownActionBufferUnderflow) {
// Initialize with bitrate, frame rate, and native system width/height.
- InitQmNativeData(450, 30, 640, 480);
+ InitQmNativeData(450, 30, 640, 480, 1);
// Update with encoder frame size.
uint16_t codec_width = 640;
uint16_t codec_height = 480;
qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
- EXPECT_EQ(2, qm_resolution_->GetImageType(codec_width, codec_height));
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
// Update with encoded size over a number of frames.
// per-frame bandwidth = 15 = 450/30: simulate (decoder) buffer underflow:
@@ -286,31 +303,32 @@
// Update content: motion level, and 3 spatial prediction errors.
// High motion, low spatial.
- UpdateQmContentData(0.1f, 0.01f, 0.01f, 0.01f);
+ UpdateQmContentData(kTemporalHigh, kSpatialLow, kSpatialLow, kSpatialLow);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(3, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStressedEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2, 2, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 4.0f / 3.0f, 4.0f / 3.0f,
+ 1.0f));
qm_resolution_->ResetDownSamplingState();
// Low motion, high spatial
- UpdateQmContentData(0.01f, 0.1f, 0.1f, 0.1f);
+ UpdateQmContentData(kTemporalLow, kSpatialHigh, kSpatialHigh, kSpatialHigh);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(1, qm_resolution_->ComputeContentClass());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 2));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 1.5f));
}
// Target rate is below max for down-sampling, but buffer level is stable,
// so no action is taken.
TEST_F(QmSelectTest, NoActionBufferStable) {
// Initialize with bitrate, frame rate, and native system width/height.
- InitQmNativeData(450, 30, 640, 480);
+ InitQmNativeData(450, 30, 640, 480, 1);
// Update with encoder frame size.
uint16_t codec_width = 640;
uint16_t codec_height = 480;
qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
- EXPECT_EQ(2, qm_resolution_->GetImageType(codec_width, codec_height));
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
// Update with encoded size over a number of frames.
// per-frame bandwidth = 15 = 450/30: simulate stable (decoder) buffer levels.
@@ -327,24 +345,24 @@
// Update content: motion level, and 3 spatial prediction errors.
// High motion, low spatial.
- UpdateQmContentData(0.1f, 0.01f, 0.01f, 0.01f);
+ UpdateQmContentData(kTemporalHigh, kSpatialLow, kSpatialLow, kSpatialLow);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(3, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 1.0f));
qm_resolution_->ResetDownSamplingState();
// Low motion, high spatial
- UpdateQmContentData(0.01f, 0.1f, 0.1f, 0.1f);
+ UpdateQmContentData(kTemporalLow, kSpatialHigh, kSpatialHigh, kSpatialHigh);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(1, qm_resolution_->ComputeContentClass());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 1.0f));
}
// Very low rate, but no spatial down-sampling below some size (QCIF).
TEST_F(QmSelectTest, LimitDownSpatialAction) {
// Initialize with bitrate, frame rate, and native system width/height.
- InitQmNativeData(10, 30, 176, 144);
+ InitQmNativeData(10, 30, 176, 144, 1);
// Update with encoder frame size.
uint16_t codec_width = 176;
@@ -362,23 +380,23 @@
// Update content: motion level, and 3 spatial prediction errors.
// High motion, low spatial.
- UpdateQmContentData(0.1f, 0.01f, 0.01f, 0.01f);
+ UpdateQmContentData(kTemporalHigh, kSpatialLow, kSpatialLow, kSpatialLow);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(3, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 1.0f));
}
// Very low rate, but no frame reduction below some frame_rate (8fps).
TEST_F(QmSelectTest, LimitDownTemporalAction) {
// Initialize with bitrate, frame rate, and native system width/height.
- InitQmNativeData(10, 8, 640, 480);
+ InitQmNativeData(10, 8, 640, 480, 1);
// Update with encoder frame size.
uint16_t codec_width = 640;
uint16_t codec_height = 480;
qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
- EXPECT_EQ(2, qm_resolution_->GetImageType(codec_width, codec_height));
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
// Update rates for a sequence of intervals.
int target_rate[] = {10, 10, 10};
@@ -390,24 +408,25 @@
// Update content: motion level, and 3 spatial prediction errors.
// Low motion, medium spatial.
- UpdateQmContentData(0.01f, 0.03f, 0.03f, 0.03f);
+ UpdateQmContentData(kTemporalLow, kSpatialMedium, kSpatialMedium,
+ kSpatialMedium);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(2, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 1.0f));
}
// Two stages: spatial down-sample and then back up spatially,
// as rate as increased.
TEST_F(QmSelectTest, 2StageDownSpatialUpSpatial) {
// Initialize with bitrate, frame rate, and native system width/height.
- InitQmNativeData(100, 30, 640, 480);
+ InitQmNativeData(100, 30, 640, 480, 1);
// Update with encoder frame size.
uint16_t codec_width = 640;
uint16_t codec_height = 480;
qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
- EXPECT_EQ(2, qm_resolution_->GetImageType(codec_width, codec_height));
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
// Update rates for a sequence of intervals.
int target_rate[] = {100, 100, 100};
@@ -419,16 +438,16 @@
// Update content: motion level, and 3 spatial prediction errors.
// High motion, low spatial.
- UpdateQmContentData(0.1f, 0.01f, 0.01f, 0.01f);
+ UpdateQmContentData(kTemporalHigh, kSpatialLow, kSpatialLow, kSpatialLow);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(3, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2, 2, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2.0f, 2.0f, 1.0f));
// Reset and go up in rate: expected to go back up.
qm_resolution_->ResetRates();
qm_resolution_->UpdateCodecFrameSize(320, 240);
- EXPECT_EQ(1, qm_resolution_->GetImageType(320, 240));
+ EXPECT_EQ(2, qm_resolution_->GetImageType(320, 240));
// Update rates for a sequence of intervals.
int target_rate2[] = {400, 400, 400, 400, 400};
int encoder_sent_rate2[] = {400, 400, 400, 400, 400};
@@ -438,20 +457,20 @@
fraction_lost2, 5);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 0, 0, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 0.5f, 0.5f, 1.0f));
}
// Two stages: spatial down-sample and then back up spatially, since encoder
// is under-shooting target even though rate has not increased much.
TEST_F(QmSelectTest, 2StageDownSpatialUpSpatialUndershoot) {
// Initialize with bitrate, frame rate, and native system width/height.
- InitQmNativeData(100, 30, 640, 480);
+ InitQmNativeData(100, 30, 640, 480, 1);
// Update with encoder frame size.
uint16_t codec_width = 640;
uint16_t codec_height = 480;
qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
- EXPECT_EQ(2, qm_resolution_->GetImageType(codec_width, codec_height));
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
// Update rates for a sequence of intervals.
int target_rate[] = {100, 100, 100};
@@ -463,16 +482,16 @@
// Update content: motion level, and 3 spatial prediction errors.
// High motion, low spatial.
- UpdateQmContentData(0.1f, 0.01f, 0.01f, 0.01f);
+ UpdateQmContentData(kTemporalHigh, kSpatialLow, kSpatialLow, kSpatialLow);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(3, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2, 2, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2.0f, 2.0f, 1.0f));
// Reset rates and simulate under-shooting scenario.: expect to go back up.
qm_resolution_->ResetRates();
qm_resolution_->UpdateCodecFrameSize(320, 240);
- EXPECT_EQ(1, qm_resolution_->GetImageType(320, 240));
+ EXPECT_EQ(2, qm_resolution_->GetImageType(320, 240));
// Update rates for a sequence of intervals.
int target_rate2[] = {200, 200, 200, 200, 200};
int encoder_sent_rate2[] = {50, 50, 50, 50, 50};
@@ -482,20 +501,20 @@
fraction_lost2, 5);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(kEasyEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 0, 0, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 0.5f, 0.5f, 1.0f));
}
// Two stages: spatial down-sample and then no action to go up,
// as encoding rate mis-match is too high.
TEST_F(QmSelectTest, 2StageDownSpatialNoActionUp) {
// Initialize with bitrate, frame rate, and native system width/height.
- InitQmNativeData(100, 30, 640, 480);
+ InitQmNativeData(100, 30, 640, 480, 1);
// Update with encoder frame size.
uint16_t codec_width = 640;
uint16_t codec_height = 480;
qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
- EXPECT_EQ(2, qm_resolution_->GetImageType(codec_width, codec_height));
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
// Update rates for a sequence of intervals.
int target_rate[] = {100, 100, 100};
@@ -507,16 +526,16 @@
// Update content: motion level, and 3 spatial prediction errors.
// High motion, low spatial.
- UpdateQmContentData(0.1f, 0.01f, 0.01f, 0.01f);
+ UpdateQmContentData(kTemporalHigh, kSpatialLow, kSpatialLow, kSpatialLow);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(3, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2, 2, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2.0f, 2.0f, 1.0f));
// Reset and simulate large rate mis-match: expect no action to go back up.
qm_resolution_->ResetRates();
qm_resolution_->UpdateCodecFrameSize(320, 240);
- EXPECT_EQ(1, qm_resolution_->GetImageType(320, 240));
+ EXPECT_EQ(2, qm_resolution_->GetImageType(320, 240));
// Update rates for a sequence of intervals.
int target_rate2[] = {400, 400, 400, 400, 400};
int encoder_sent_rate2[] = {1000, 1000, 1000, 1000, 1000};
@@ -526,19 +545,19 @@
fraction_lost2, 5);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(kStressedEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 1.0f));
}
// Two stages: temporally down-sample and then back up temporally,
// as rate as increased.
TEST_F(QmSelectTest, 2StatgeDownTemporalUpTemporal) {
// Initialize with bitrate, frame rate, and native system width/height.
- InitQmNativeData(100, 30, 640, 480);
+ InitQmNativeData(100, 30, 640, 480, 1);
// Update with encoder frame size.
uint16_t codec_width = 640;
uint16_t codec_height = 480;
qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
- EXPECT_EQ(2, qm_resolution_->GetImageType(codec_width, codec_height));
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
// Update rates for a sequence of intervals.
int target_rate[] = {100, 100, 100};
@@ -550,11 +569,11 @@
// Update content: motion level, and 3 spatial prediction errors.
// Low motion, high spatial.
- UpdateQmContentData(0.01f, 0.1f, 0.1f, 0.1f);
+ UpdateQmContentData(kTemporalLow, kSpatialHigh, kSpatialHigh, kSpatialHigh);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(1, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 2));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 2.0f));
// Reset rates and go up in rate: expect to go back up.
qm_resolution_->ResetRates();
@@ -567,20 +586,20 @@
fraction_lost2, 5);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 0));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 0.5f));
}
// Two stages: temporal down-sample and then back up temporally, since encoder
// is under-shooting target even though rate has not increased much.
TEST_F(QmSelectTest, 2StatgeDownTemporalUpTemporalUndershoot) {
// Initialize with bitrate, frame rate, and native system width/height.
- InitQmNativeData(100, 30, 640, 480);
+ InitQmNativeData(100, 30, 640, 480, 1);
// Update with encoder frame size.
uint16_t codec_width = 640;
uint16_t codec_height = 480;
qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
- EXPECT_EQ(2, qm_resolution_->GetImageType(codec_width, codec_height));
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
// Update rates for a sequence of intervals.
int target_rate[] = {100, 100, 100};
@@ -592,11 +611,11 @@
// Update content: motion level, and 3 spatial prediction errors.
// Low motion, high spatial.
- UpdateQmContentData(0.01f, 0.1f, 0.1f, 0.1f);
+ UpdateQmContentData(kTemporalLow, kSpatialHigh, kSpatialHigh, kSpatialHigh);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(1, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 2));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 2.0f));
// Reset rates and simulate under-shooting scenario.: expect to go back up.
qm_resolution_->ResetRates();
@@ -609,20 +628,20 @@
fraction_lost2, 5);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(kEasyEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 0));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 0.5f));
}
// Two stages: temporal down-sample and then no action to go up,
// as encoding rate mis-match is too high.
TEST_F(QmSelectTest, 2StageDownTemporalNoActionUp) {
// Initialize with bitrate, frame rate, and native system width/height.
- InitQmNativeData(100, 30, 640, 480);
+ InitQmNativeData(100, 30, 640, 480, 1);
// Update with encoder frame size.
uint16_t codec_width = 640;
uint16_t codec_height = 480;
qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
- EXPECT_EQ(2, qm_resolution_->GetImageType(codec_width, codec_height));
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
// Update rates for a sequence of intervals.
int target_rate[] = {100, 100, 100};
@@ -634,7 +653,7 @@
// Update content: motion level, and 3 spatial prediction errors.
// Low motion, high spatial.
- UpdateQmContentData(0.01f, 0.1f, 0.1f, 0.1f);
+ UpdateQmContentData(kTemporalLow, kSpatialHigh, kSpatialHigh, kSpatialHigh);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(1, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
@@ -651,23 +670,23 @@
fraction_lost2, 5);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(kStressedEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 1.0f));
}
// 3 stages: spatial down-sample, followed by temporal down-sample,
// and then go up to full state, as encoding rate has increased.
TEST_F(QmSelectTest, 3StageDownSpatialTemporlaUpSpatialTemporal) {
// Initialize with bitrate, frame rate, and native system width/height.
- InitQmNativeData(100, 30, 640, 480);
+ InitQmNativeData(100, 30, 640, 480, 1);
// Update with encoder frame size.
uint16_t codec_width = 640;
uint16_t codec_height = 480;
qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
- EXPECT_EQ(2, qm_resolution_->GetImageType(codec_width, codec_height));
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
// Update rates for a sequence of intervals.
- int target_rate[] = {100, 100, 100};
- int encoder_sent_rate[] = {100, 100, 100};
+ int target_rate[] = {80, 80, 80};
+ int encoder_sent_rate[] = {80, 80, 80};
int incoming_frame_rate[] = {30, 30, 30};
uint8_t fraction_lost[] = {10, 10, 10};
UpdateQmRateData(target_rate, encoder_sent_rate, incoming_frame_rate,
@@ -675,24 +694,23 @@
// Update content: motion level, and 3 spatial prediction errors.
// High motion, low spatial.
- UpdateQmContentData(0.1f, 0.01f, 0.01f, 0.01f);
+ UpdateQmContentData(kTemporalHigh, kSpatialLow, kSpatialLow, kSpatialLow);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(3, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2, 2, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2.0f, 2.0f, 1.0f));
- // Reset rate and change content data: expect temporal down-sample.
- qm_resolution_->ResetRates();
+ // Change content data: expect temporal down-sample.
qm_resolution_->UpdateCodecFrameSize(320, 240);
- EXPECT_EQ(1, qm_resolution_->GetImageType(320, 240));
+ EXPECT_EQ(2, qm_resolution_->GetImageType(320, 240));
// Update content: motion level, and 3 spatial prediction errors.
// Low motion, high spatial.
- UpdateQmContentData(0.01f, 0.1f, 0.1f, 0.1f);
+ UpdateQmContentData(kTemporalLow, kSpatialHigh, kSpatialHigh, kSpatialHigh);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(1, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 2));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 2.0f));
// Reset rates and go high up in rate: expect to go back up both spatial
// and temporally.
@@ -708,57 +726,63 @@
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(1, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 0, 0, 0));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 0.5f, 0.5f, 0.5f));
}
-// No down-sampling below some totol amount (factor of 16)
+// No down-sampling below some total amount.
TEST_F(QmSelectTest, NoActionTooMuchDownSampling) {
// Initialize with bitrate, frame rate, and native system width/height.
- InitQmNativeData(400, 30, 1280, 720);
+ InitQmNativeData(400, 30, 1280, 720, 1);
// Update with encoder frame size.
uint16_t codec_width = 1280;
uint16_t codec_height = 720;
qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
- EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
+ EXPECT_EQ(7, qm_resolution_->GetImageType(codec_width, codec_height));
// Update rates for a sequence of intervals.
- int target_rate[] = {400, 400, 400};
- int encoder_sent_rate[] = {400, 400, 400};
+ int target_rate[] = {200, 200, 200};
+ int encoder_sent_rate[] = {200, 200, 200};
int incoming_frame_rate[] = {30, 30, 30};
uint8_t fraction_lost[] = {10, 10, 10};
UpdateQmRateData(target_rate, encoder_sent_rate, incoming_frame_rate,
fraction_lost, 3);
// Update content: motion level, and 3 spatial prediction errors.
- // High motion, low spatial: 2x2 spatial expected.
- UpdateQmContentData(0.1f, 0.01f, 0.01f, 0.01f);
+ // High motion, low spatial.
+ UpdateQmContentData(kTemporalHigh, kSpatialLow, kSpatialLow, kSpatialLow);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(3, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2, 2, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2.0f, 2.0f, 1.0f));
- // Reset and lower rates to get another spatial action.
+ // Reset and lower rates to get another spatial action (3/4x3/4)
qm_resolution_->ResetRates();
qm_resolution_->UpdateCodecFrameSize(640, 360);
- EXPECT_EQ(2, qm_resolution_->GetImageType(640, 360));
+ EXPECT_EQ(4, qm_resolution_->GetImageType(640, 360));
// Update rates for a sequence of intervals.
- int target_rate2[] = {100, 100, 100, 100, 100};
- int encoder_sent_rate2[] = {100, 100, 100, 100, 100};
+ int target_rate2[] = {80, 80, 80, 80, 80};
+ int encoder_sent_rate2[] = {80, 80, 80, 80, 80};
int incoming_frame_rate2[] = {30, 30, 30, 30, 30};
uint8_t fraction_lost2[] = {10, 10, 10, 10, 10};
UpdateQmRateData(target_rate2, encoder_sent_rate2, incoming_frame_rate2,
fraction_lost2, 5);
- EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
- EXPECT_EQ(3, qm_resolution_->ComputeContentClass());
- EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2, 2, 1));
- // Reset and go to low rate: no action should be taken,
+ // Update content: motion level, and 3 spatial prediction errors.
+ // High motion, medium spatial.
+ UpdateQmContentData(kTemporalHigh, kSpatialMedium, kSpatialMedium,
+ kSpatialMedium);
+ EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
+ EXPECT_EQ(5, qm_resolution_->ComputeContentClass());
+ EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 4.0f / 3.0f, 4.0f / 3.0f,
+ 1.0f));
+
+ // Reset and go to very low rate: no action should be taken,
// we went down too much already.
qm_resolution_->ResetRates();
qm_resolution_->UpdateCodecFrameSize(320, 180);
- EXPECT_EQ(0, qm_resolution_->GetImageType(320, 180));
+ EXPECT_EQ(1, qm_resolution_->GetImageType(320, 180));
// Update rates for a sequence of intervals.
int target_rate3[] = {10, 10, 10, 10, 10};
int encoder_sent_rate3[] = {10, 10, 10, 10, 10};
@@ -767,17 +791,331 @@
UpdateQmRateData(target_rate3, encoder_sent_rate3, incoming_frame_rate3,
fraction_lost3, 5);
EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
+ EXPECT_EQ(5, qm_resolution_->ComputeContentClass());
+ EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 1.0f));
+}
+
+// Multiple down-sampling stages and then undo all of them.
+// Spatial down-sample 3/4x3/4, followed by temporal down-sample 2/3,
+// followed by spatial 1/2x1/2. Then go up to full state,
+// as encoding rate has increased.
+TEST_F(QmSelectTest, MultipleStagesCheckActionHistory1) {
+ // Initialize with bitrate, frame rate, and native system width/height.
+ InitQmNativeData(200, 30, 640, 480, 1);
+
+ // Update with encoder frame size.
+ uint16_t codec_width = 640;
+ uint16_t codec_height = 480;
+ qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
+
+ // Go down spatial 3/4x3/4.
+ // Update rates for a sequence of intervals.
+ int target_rate[] = {200, 200, 200};
+ int encoder_sent_rate[] = {200, 200, 200};
+ int incoming_frame_rate[] = {30, 30, 30};
+ uint8_t fraction_lost[] = {10, 10, 10};
+ UpdateQmRateData(target_rate, encoder_sent_rate, incoming_frame_rate,
+ fraction_lost, 3);
+
+ // Update content: motion level, and 3 spatial prediction errors.
+ // Medium motion, low spatial.
+ UpdateQmContentData(kTemporalMedium, kSpatialLow, kSpatialLow, kSpatialLow);
+ EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
+ EXPECT_EQ(6, qm_resolution_->ComputeContentClass());
+ EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 4.0f / 3.0f, 4.0f / 3.0f,
+ 1.0f));
+ // Go down 1/2 temporal.
+ qm_resolution_->UpdateCodecFrameSize(480, 360);
+ EXPECT_EQ(4, qm_resolution_->GetImageType(480, 360));
+ qm_resolution_->ResetRates();
+ int target_rate2[] = {100, 100, 100, 100, 100};
+ int encoder_sent_rate2[] = {100, 100, 100, 100, 100};
+ int incoming_frame_rate2[] = {30, 30, 30, 30, 30};
+ uint8_t fraction_lost2[] = {10, 10, 10, 10, 10};
+ UpdateQmRateData(target_rate2, encoder_sent_rate2, incoming_frame_rate2,
+ fraction_lost2, 5);
+
+ // Update content: motion level, and 3 spatial prediction errors.
+ // Low motion, high spatial.
+ UpdateQmContentData(kTemporalLow, kSpatialHigh, kSpatialHigh, kSpatialHigh);
+ EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
+ EXPECT_EQ(1, qm_resolution_->ComputeContentClass());
+ EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 2.0f));
+
+ // Go down 1/2x1/2 spatial.
+ qm_resolution_->ResetRates();
+ int target_rate3[] = {50, 50, 50, 50, 50};
+ int encoder_sent_rate3[] = {50, 50, 50, 50, 50};
+ int incoming_frame_rate3[] = {15, 15, 15, 15, 15};
+ uint8_t fraction_lost3[] = {10, 10, 10, 10, 10};
+ UpdateQmRateData(target_rate3, encoder_sent_rate3, incoming_frame_rate3,
+ fraction_lost3, 5);
+
+ // Update content: motion level, and 3 spatial prediction errors.
+ // High motion, low spatial.
+ UpdateQmContentData(kTemporalHigh, kSpatialLow, kSpatialLow, kSpatialLow);
+ EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
EXPECT_EQ(3, qm_resolution_->ComputeContentClass());
EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
- EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1, 1, 1));
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2.0f, 2.0f, 1.0f));
+
+ // Reset rates and go high up in rate: expect to go up:
+ // should go up first: 1/2x1x2 and 1/2 temporally,
+ // and second: 3/4x3/4 spatial.
+
+ // Go up 1/2x1/2 spatially and 1/2 temporally
+ qm_resolution_->UpdateCodecFrameSize(240, 180);
+ EXPECT_EQ(1, qm_resolution_->GetImageType(240, 180));
+ qm_resolution_->ResetRates();
+ // Update rates for a sequence of intervals.
+ int target_rate4[] = {1000, 1000, 1000, 1000, 1000};
+ int encoder_sent_rate4[] = {1000, 1000, 1000, 1000, 1000};
+ int incoming_frame_rate4[] = {15, 15, 15, 15, 15};
+ uint8_t fraction_lost4[] = {10, 10, 10, 10, 10};
+ UpdateQmRateData(target_rate4, encoder_sent_rate4, incoming_frame_rate4,
+ fraction_lost4, 5);
+
+ EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
+ EXPECT_EQ(3, qm_resolution_->ComputeContentClass());
+ EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 0.5f, 0.5f, 0.5f));
+
+ // Go up 3/4x3/4 spatially.
+ qm_resolution_->UpdateCodecFrameSize(480, 360);
+ EXPECT_EQ(4, qm_resolution_->GetImageType(480, 360));
+ qm_resolution_->ResetRates();
+ // Update rates for a sequence of intervals.
+ int target_rate5[] = {1000, 1000, 1000, 1000, 1000};
+ int encoder_sent_rate5[] = {1000, 1000, 1000, 1000, 1000};
+ int incoming_frame_rate5[] = {30, 30, 30, 30, 30};
+ uint8_t fraction_lost5[] = {10, 10, 10, 10, 10};
+ UpdateQmRateData(target_rate5, encoder_sent_rate5, incoming_frame_rate5,
+ fraction_lost5, 5);
+
+ EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
+ EXPECT_EQ(3, qm_resolution_->ComputeContentClass());
+ EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 3.0f / 4.0f, 3.0f / 4.0f,
+ 1.0f));
+}
+
+// Multiple down-sampling and up-sample stages, with partial undoing.
+// Spatial down-sample 1/2x1/2, followed by temporal down-sample 2/3,
+// undo the spatial 1/2x1/2, then another temporal 1/2, and undo
+// the 1/2 temporal.
+TEST_F(QmSelectTest, MultipleStagesCheckActionHistory2) {
+ // Initialize with bitrate, frame rate, and native system width/height.
+ InitQmNativeData(100, 30, 640, 480, 1);
+
+ // Update with encoder frame size.
+ uint16_t codec_width = 640;
+ uint16_t codec_height = 480;
+ qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
+
+ // Go down 1/2x1/2 spatial.
+ // Update rates for a sequence of intervals.
+ int target_rate[] = {100, 100, 100};
+ int encoder_sent_rate[] = {100, 100, 100};
+ int incoming_frame_rate[] = {30, 30, 30};
+ uint8_t fraction_lost[] = {10, 10, 10};
+ UpdateQmRateData(target_rate, encoder_sent_rate, incoming_frame_rate,
+ fraction_lost, 3);
+
+ // Update content: motion level, and 3 spatial prediction errors.
+ // Medium motion, low spatial.
+ UpdateQmContentData(kTemporalMedium, kSpatialLow, kSpatialLow, kSpatialLow);
+ EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
+ EXPECT_EQ(6, qm_resolution_->ComputeContentClass());
+ EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 2.0f, 2.0f, 1.0f));
+
+ // Go down 2/3 temporal.
+ qm_resolution_->UpdateCodecFrameSize(320, 240);
+ EXPECT_EQ(2, qm_resolution_->GetImageType(320, 240));
+ qm_resolution_->ResetRates();
+ int target_rate2[] = {80, 80, 80, 80, 80};
+ int encoder_sent_rate2[] = {80, 80, 80, 80, 80};
+ int incoming_frame_rate2[] = {30, 30, 30, 30, 30};
+ uint8_t fraction_lost2[] = {10, 10, 10, 10, 10};
+ UpdateQmRateData(target_rate2, encoder_sent_rate2, incoming_frame_rate2,
+ fraction_lost2, 5);
+
+ // Update content: motion level, and 3 spatial prediction errors.
+ // Medium motion, high spatial.
+ UpdateQmContentData(kTemporalMedium, kSpatialHigh, kSpatialHigh,
+ kSpatialHigh);
+ EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
+ EXPECT_EQ(7, qm_resolution_->ComputeContentClass());
+ EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 1.5f));
+
+ // Go up 1/2x1/2 spatially.
+ qm_resolution_->ResetRates();
+ // Update rates for a sequence of intervals.
+ int target_rate3[] = {300, 300, 300, 300, 300};
+ int encoder_sent_rate3[] = {300, 300, 300, 300, 300};
+ int incoming_frame_rate3[] = {20, 20, 20, 20, 20};
+ uint8_t fraction_lost3[] = {10, 10, 10, 10, 10};
+ UpdateQmRateData(target_rate3, encoder_sent_rate3, incoming_frame_rate3,
+ fraction_lost3, 5);
+
+ EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
+ EXPECT_EQ(7, qm_resolution_->ComputeContentClass());
+ EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 0.5f, 0.5f, 1.0f));
+
+ // Go down 1/2 temporal.
+ qm_resolution_->UpdateCodecFrameSize(640, 480);
+ EXPECT_EQ(5, qm_resolution_->GetImageType(640, 480));
+ qm_resolution_->ResetRates();
+ int target_rate4[] = {100, 100, 100, 100, 100};
+ int encoder_sent_rate4[] = {100, 100, 100, 100, 100};
+ int incoming_frame_rate4[] = {20, 20, 20, 20, 20};
+ uint8_t fraction_lost4[] = {10, 10, 10, 10, 10};
+ UpdateQmRateData(target_rate4, encoder_sent_rate4, incoming_frame_rate4,
+ fraction_lost4, 5);
+
+ // Update content: motion level, and 3 spatial prediction errors.
+ // Low motion, high spatial.
+ UpdateQmContentData(kTemporalLow, kSpatialHigh, kSpatialHigh, kSpatialHigh);
+ EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
+ EXPECT_EQ(1, qm_resolution_->ComputeContentClass());
+ EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 2.0f));
+
+ // Go up 1/2 temporal.
+ qm_resolution_->ResetRates();
+ // Update rates for a sequence of intervals.
+ int target_rate5[] = {1000, 1000, 1000, 1000, 1000};
+ int encoder_sent_rate5[] = {1000, 1000, 1000, 1000, 1000};
+ int incoming_frame_rate5[] = {10, 10, 10, 10, 10};
+ uint8_t fraction_lost5[] = {10, 10, 10, 10, 10};
+ UpdateQmRateData(target_rate5, encoder_sent_rate5, incoming_frame_rate5,
+ fraction_lost5, 5);
+
+ EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
+ EXPECT_EQ(1, qm_resolution_->ComputeContentClass());
+ EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 0.5f));
+}
+
+// Multiple down-sampling and up-sample stages, with partial undoing.
+// Spatial down-sample 3/4x3/4, followed by temporal down-sample 1/2,
+// undo the temporal 1/2, then another temporal 2/3 down, and undo
+// the 2/3 temporal.
+TEST_F(QmSelectTest, MultipleStagesCheckActionHistory3) {
+ // Initialize with bitrate, frame rate, and native system width/height.
+ InitQmNativeData(200, 30, 640, 480, 1);
+
+ // Update with encoder frame size.
+ uint16_t codec_width = 640;
+ uint16_t codec_height = 480;
+ qm_resolution_->UpdateCodecFrameSize(codec_width, codec_height);
+ EXPECT_EQ(5, qm_resolution_->GetImageType(codec_width, codec_height));
+
+ // Go down 3/4x3/4 spatial.
+ // Update rates for a sequence of intervals.
+ int target_rate[] = {200, 200, 200};
+ int encoder_sent_rate[] = {200, 200, 200};
+ int incoming_frame_rate[] = {30, 30, 30};
+ uint8_t fraction_lost[] = {10, 10, 10};
+ UpdateQmRateData(target_rate, encoder_sent_rate, incoming_frame_rate,
+ fraction_lost, 3);
+
+ // Update content: motion level, and 3 spatial prediction errors.
+ // Medium motion, low spatial.
+ UpdateQmContentData(kTemporalMedium, kSpatialLow, kSpatialLow, kSpatialLow);
+ EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
+ EXPECT_EQ(6, qm_resolution_->ComputeContentClass());
+ EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 4.0f / 3.0f, 4.0f / 3.0f,
+ 1.0f));
+
+ // Go down 1/2 temporal.
+ qm_resolution_->UpdateCodecFrameSize(480, 360);
+ EXPECT_EQ(4, qm_resolution_->GetImageType(480, 360));
+ qm_resolution_->ResetRates();
+ int target_rate2[] = {100, 100, 100, 100, 100};
+ int encoder_sent_rate2[] = {100, 100, 100, 100, 100};
+ int incoming_frame_rate2[] = {30, 30, 30, 30, 30};
+ uint8_t fraction_lost2[] = {10, 10, 10, 10, 10};
+ UpdateQmRateData(target_rate2, encoder_sent_rate2, incoming_frame_rate2,
+ fraction_lost2, 5);
+
+ // Update content: motion level, and 3 spatial prediction errors.
+ // Low motion, high spatial.
+ UpdateQmContentData(kTemporalLow, kSpatialHigh, kSpatialHigh, kSpatialHigh);
+ EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
+ EXPECT_EQ(1, qm_resolution_->ComputeContentClass());
+ EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 2.0f));
+
+ // Go up 1/2 temporal.
+ qm_resolution_->ResetRates();
+ // Update rates for a sequence of intervals.
+ int target_rate3[] = {300, 300, 300, 300, 300};
+ int encoder_sent_rate3[] = {300, 300, 300, 300, 300};
+ int incoming_frame_rate3[] = {15, 15, 15, 15, 15};
+ uint8_t fraction_lost3[] = {10, 10, 10, 10, 10};
+ UpdateQmRateData(target_rate3, encoder_sent_rate3, incoming_frame_rate3,
+ fraction_lost3, 5);
+
+ EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
+ EXPECT_EQ(1, qm_resolution_->ComputeContentClass());
+ EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 0.5f));
+
+ // Go down 2/3 temporal.
+ qm_resolution_->UpdateCodecFrameSize(640, 480);
+ EXPECT_EQ(5, qm_resolution_->GetImageType(640, 480));
+ qm_resolution_->ResetRates();
+ int target_rate4[] = {150, 150, 150, 150, 150};
+ int encoder_sent_rate4[] = {150, 150, 150, 150, 150};
+ int incoming_frame_rate4[] = {20, 20, 20, 20, 20};
+ uint8_t fraction_lost4[] = {30, 30, 30, 30, 30};
+ UpdateQmRateData(target_rate4, encoder_sent_rate4, incoming_frame_rate4,
+ fraction_lost4, 5);
+
+ // Update content: motion level, and 3 spatial prediction errors.
+ // Medium motion, high spatial.
+ UpdateQmContentData(kTemporalMedium, kSpatialHigh, kSpatialHigh,
+ kSpatialHigh);
+ EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
+ EXPECT_EQ(7, qm_resolution_->ComputeContentClass());
+ EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 1.5f));
+
+ // Go up 2/3 temporal.
+ qm_resolution_->ResetRates();
+ // Update rates for a sequence of intervals.
+ int target_rate5[] = {500, 500, 500, 500, 500};
+ int encoder_sent_rate5[] = {500, 500, 500, 500, 500};
+ int incoming_frame_rate5[] = {20, 20, 20, 20, 20};
+ uint8_t fraction_lost5[] = {10, 10, 10, 10, 10};
+ UpdateQmRateData(target_rate5, encoder_sent_rate5, incoming_frame_rate5,
+ fraction_lost5, 5);
+
+ EXPECT_EQ(0, qm_resolution_->SelectResolution(&qm_scale_));
+ EXPECT_EQ(7, qm_resolution_->ComputeContentClass());
+ EXPECT_EQ(kStableEncoding, qm_resolution_->GetEncoderState());
+ EXPECT_TRUE(IsSelectedActionCorrect(qm_scale_, 1.0f, 1.0f, 2.0f / 3.0f));
}
void QmSelectTest::InitQmNativeData(float initial_bit_rate,
int user_frame_rate,
int native_width,
- int native_height) {
- EXPECT_EQ(0, qm_resolution_->Initialize(initial_bit_rate, user_frame_rate,
- native_width, native_height));
+ int native_height,
+ int num_layers) {
+ EXPECT_EQ(0, qm_resolution_->Initialize(initial_bit_rate,
+ user_frame_rate,
+ native_width,
+ native_height,
+ num_layers));
}
void QmSelectTest::UpdateQmContentData(float motion_metric,
@@ -793,7 +1131,7 @@
void QmSelectTest::UpdateQmEncodedFrame(int* encoded_size, int num_updates) {
FrameType frame_type = kVideoFrameDelta;
- for (int i = 0; i < num_updates; i++) {
+ for (int i = 0; i < num_updates; ++i) {
// Convert to bytes.
int32_t encoded_size_update = 1000 * encoded_size[i] / 8;
qm_resolution_->UpdateEncodedSize(encoded_size_update, frame_type);
@@ -805,7 +1143,7 @@
int* incoming_frame_rate,
uint8_t* fraction_lost,
int num_updates) {
- for (int i = 0; i < num_updates; i++) {
+ for (int i = 0; i < num_updates; ++i) {
float target_rate_update = target_rate[i];
float encoder_sent_rate_update = encoder_sent_rate[i];
float incoming_frame_rate_update = incoming_frame_rate[i];
@@ -820,12 +1158,12 @@
// Check is the selected action from the QmResolution class is the same
// as the expected scales from |fac_width|, |fac_height|, |fac_temp|.
bool QmSelectTest::IsSelectedActionCorrect(VCMResolutionScale* qm_scale,
- uint8_t fac_width,
- uint8_t fac_height,
- uint8_t fac_temp) {
- if (qm_scale->spatialWidthFact == fac_width &&
- qm_scale->spatialHeightFact == fac_height &&
- qm_scale->temporalFact == fac_temp) {
+ float fac_width,
+ float fac_height,
+ float fac_temp) {
+ if (qm_scale->spatial_width_fact == fac_width &&
+ qm_scale->spatial_height_fact == fac_height &&
+ qm_scale->temporal_fact == fac_temp) {
return true;
} else {
return false;