Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(297)

Side by Side Diff: webrtc/modules/video_coding/main/source/qm_select.cc

Issue 1417283007: modules/video_coding refactorings (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: Fix the other copy of the mock include header Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "webrtc/modules/video_coding/main/source/qm_select.h"
12
13 #include <math.h>
14
15 #include "webrtc/modules/include/module_common_types.h"
16 #include "webrtc/modules/video_coding/main/interface/video_coding_defines.h"
17 #include "webrtc/modules/video_coding/main/source/internal_defines.h"
18 #include "webrtc/modules/video_coding/main/source/qm_select_data.h"
19 #include "webrtc/system_wrappers/include/trace.h"
20
21 namespace webrtc {
22
23 // QM-METHOD class
24
25 VCMQmMethod::VCMQmMethod()
26 : content_metrics_(NULL),
27 width_(0),
28 height_(0),
29 user_frame_rate_(0.0f),
30 native_width_(0),
31 native_height_(0),
32 native_frame_rate_(0.0f),
33 image_type_(kVGA),
34 framerate_level_(kFrameRateHigh),
35 init_(false) {
36 ResetQM();
37 }
38
39 VCMQmMethod::~VCMQmMethod() {
40 }
41
42 void VCMQmMethod::ResetQM() {
43 aspect_ratio_ = 1.0f;
44 motion_.Reset();
45 spatial_.Reset();
46 content_class_ = 0;
47 }
48
49 uint8_t VCMQmMethod::ComputeContentClass() {
50 ComputeMotionNFD();
51 ComputeSpatial();
52 return content_class_ = 3 * motion_.level + spatial_.level;
53 }
54
55 void VCMQmMethod::UpdateContent(const VideoContentMetrics* contentMetrics) {
56 content_metrics_ = contentMetrics;
57 }
58
59 void VCMQmMethod::ComputeMotionNFD() {
60 if (content_metrics_) {
61 motion_.value = content_metrics_->motion_magnitude;
62 }
63 // Determine motion level.
64 if (motion_.value < kLowMotionNfd) {
65 motion_.level = kLow;
66 } else if (motion_.value > kHighMotionNfd) {
67 motion_.level = kHigh;
68 } else {
69 motion_.level = kDefault;
70 }
71 }
72
73 void VCMQmMethod::ComputeSpatial() {
74 float spatial_err = 0.0;
75 float spatial_err_h = 0.0;
76 float spatial_err_v = 0.0;
77 if (content_metrics_) {
78 spatial_err = content_metrics_->spatial_pred_err;
79 spatial_err_h = content_metrics_->spatial_pred_err_h;
80 spatial_err_v = content_metrics_->spatial_pred_err_v;
81 }
82 // Spatial measure: take average of 3 prediction errors.
83 spatial_.value = (spatial_err + spatial_err_h + spatial_err_v) / 3.0f;
84
85 // Reduce thresholds for large scenes/higher pixel correlation.
86 float scale2 = image_type_ > kVGA ? kScaleTexture : 1.0;
87
88 if (spatial_.value > scale2 * kHighTexture) {
89 spatial_.level = kHigh;
90 } else if (spatial_.value < scale2 * kLowTexture) {
91 spatial_.level = kLow;
92 } else {
93 spatial_.level = kDefault;
94 }
95 }
96
97 ImageType VCMQmMethod::GetImageType(uint16_t width,
98 uint16_t height) {
99 // Get the image type for the encoder frame size.
100 uint32_t image_size = width * height;
101 if (image_size == kSizeOfImageType[kQCIF]) {
102 return kQCIF;
103 } else if (image_size == kSizeOfImageType[kHCIF]) {
104 return kHCIF;
105 } else if (image_size == kSizeOfImageType[kQVGA]) {
106 return kQVGA;
107 } else if (image_size == kSizeOfImageType[kCIF]) {
108 return kCIF;
109 } else if (image_size == kSizeOfImageType[kHVGA]) {
110 return kHVGA;
111 } else if (image_size == kSizeOfImageType[kVGA]) {
112 return kVGA;
113 } else if (image_size == kSizeOfImageType[kQFULLHD]) {
114 return kQFULLHD;
115 } else if (image_size == kSizeOfImageType[kWHD]) {
116 return kWHD;
117 } else if (image_size == kSizeOfImageType[kFULLHD]) {
118 return kFULLHD;
119 } else {
120 // No exact match, find closet one.
121 return FindClosestImageType(width, height);
122 }
123 }
124
125 ImageType VCMQmMethod::FindClosestImageType(uint16_t width, uint16_t height) {
126 float size = static_cast<float>(width * height);
127 float min = size;
128 int isel = 0;
129 for (int i = 0; i < kNumImageTypes; ++i) {
130 float dist = fabs(size - kSizeOfImageType[i]);
131 if (dist < min) {
132 min = dist;
133 isel = i;
134 }
135 }
136 return static_cast<ImageType>(isel);
137 }
138
139 FrameRateLevelClass VCMQmMethod::FrameRateLevel(float avg_framerate) {
140 if (avg_framerate <= kLowFrameRate) {
141 return kFrameRateLow;
142 } else if (avg_framerate <= kMiddleFrameRate) {
143 return kFrameRateMiddle1;
144 } else if (avg_framerate <= kHighFrameRate) {
145 return kFrameRateMiddle2;
146 } else {
147 return kFrameRateHigh;
148 }
149 }
150
151 // RESOLUTION CLASS
152
153 VCMQmResolution::VCMQmResolution()
154 : qm_(new VCMResolutionScale()) {
155 Reset();
156 }
157
158 VCMQmResolution::~VCMQmResolution() {
159 delete qm_;
160 }
161
162 void VCMQmResolution::ResetRates() {
163 sum_target_rate_ = 0.0f;
164 sum_incoming_framerate_ = 0.0f;
165 sum_rate_MM_ = 0.0f;
166 sum_rate_MM_sgn_ = 0.0f;
167 sum_packet_loss_ = 0.0f;
168 buffer_level_ = kInitBufferLevel * target_bitrate_;
169 frame_cnt_ = 0;
170 frame_cnt_delta_ = 0;
171 low_buffer_cnt_ = 0;
172 update_rate_cnt_ = 0;
173 }
174
175 void VCMQmResolution::ResetDownSamplingState() {
176 state_dec_factor_spatial_ = 1.0;
177 state_dec_factor_temporal_ = 1.0;
178 for (int i = 0; i < kDownActionHistorySize; i++) {
179 down_action_history_[i].spatial = kNoChangeSpatial;
180 down_action_history_[i].temporal = kNoChangeTemporal;
181 }
182 }
183
184 void VCMQmResolution::Reset() {
185 target_bitrate_ = 0.0f;
186 incoming_framerate_ = 0.0f;
187 buffer_level_ = 0.0f;
188 per_frame_bandwidth_ = 0.0f;
189 avg_target_rate_ = 0.0f;
190 avg_incoming_framerate_ = 0.0f;
191 avg_ratio_buffer_low_ = 0.0f;
192 avg_rate_mismatch_ = 0.0f;
193 avg_rate_mismatch_sgn_ = 0.0f;
194 avg_packet_loss_ = 0.0f;
195 encoder_state_ = kStableEncoding;
196 num_layers_ = 1;
197 ResetRates();
198 ResetDownSamplingState();
199 ResetQM();
200 }
201
202 EncoderState VCMQmResolution::GetEncoderState() {
203 return encoder_state_;
204 }
205
206 // Initialize state after re-initializing the encoder,
207 // i.e., after SetEncodingData() in mediaOpt.
208 int VCMQmResolution::Initialize(float bitrate,
209 float user_framerate,
210 uint16_t width,
211 uint16_t height,
212 int num_layers) {
213 if (user_framerate == 0.0f || width == 0 || height == 0) {
214 return VCM_PARAMETER_ERROR;
215 }
216 Reset();
217 target_bitrate_ = bitrate;
218 incoming_framerate_ = user_framerate;
219 UpdateCodecParameters(user_framerate, width, height);
220 native_width_ = width;
221 native_height_ = height;
222 native_frame_rate_ = user_framerate;
223 num_layers_ = num_layers;
224 // Initial buffer level.
225 buffer_level_ = kInitBufferLevel * target_bitrate_;
226 // Per-frame bandwidth.
227 per_frame_bandwidth_ = target_bitrate_ / user_framerate;
228 init_ = true;
229 return VCM_OK;
230 }
231
232 void VCMQmResolution::UpdateCodecParameters(float frame_rate, uint16_t width,
233 uint16_t height) {
234 width_ = width;
235 height_ = height;
236 // |user_frame_rate| is the target frame rate for VPM frame dropper.
237 user_frame_rate_ = frame_rate;
238 image_type_ = GetImageType(width, height);
239 }
240
241 // Update rate data after every encoded frame.
242 void VCMQmResolution::UpdateEncodedSize(size_t encoded_size) {
243 frame_cnt_++;
244 // Convert to Kbps.
245 float encoded_size_kbits = 8.0f * static_cast<float>(encoded_size) / 1000.0f;
246
247 // Update the buffer level:
248 // Note this is not the actual encoder buffer level.
249 // |buffer_level_| is reset to an initial value after SelectResolution is
250 // called, and does not account for frame dropping by encoder or VCM.
251 buffer_level_ += per_frame_bandwidth_ - encoded_size_kbits;
252
253 // Counter for occurrences of low buffer level:
254 // low/negative values means encoder is likely dropping frames.
255 if (buffer_level_ <= kPercBufferThr * kInitBufferLevel * target_bitrate_) {
256 low_buffer_cnt_++;
257 }
258 }
259
260 // Update various quantities after SetTargetRates in MediaOpt.
261 void VCMQmResolution::UpdateRates(float target_bitrate,
262 float encoder_sent_rate,
263 float incoming_framerate,
264 uint8_t packet_loss) {
265 // Sum the target bitrate: this is the encoder rate from previous update
266 // (~1sec), i.e, before the update for next ~1sec.
267 sum_target_rate_ += target_bitrate_;
268 update_rate_cnt_++;
269
270 // Sum the received (from RTCP reports) packet loss rates.
271 sum_packet_loss_ += static_cast<float>(packet_loss / 255.0);
272
273 // Sum the sequence rate mismatch:
274 // Mismatch here is based on the difference between the target rate
275 // used (in previous ~1sec) and the average actual encoding rate measured
276 // at previous ~1sec.
277 float diff = target_bitrate_ - encoder_sent_rate;
278 if (target_bitrate_ > 0.0)
279 sum_rate_MM_ += fabs(diff) / target_bitrate_;
280 int sgnDiff = diff > 0 ? 1 : (diff < 0 ? -1 : 0);
281 // To check for consistent under(+)/over_shooting(-) of target rate.
282 sum_rate_MM_sgn_ += sgnDiff;
283
284 // Update with the current new target and frame rate:
285 // these values are ones the encoder will use for the current/next ~1sec.
286 target_bitrate_ = target_bitrate;
287 incoming_framerate_ = incoming_framerate;
288 sum_incoming_framerate_ += incoming_framerate_;
289 // Update the per_frame_bandwidth:
290 // this is the per_frame_bw for the current/next ~1sec.
291 per_frame_bandwidth_ = 0.0f;
292 if (incoming_framerate_ > 0.0f) {
293 per_frame_bandwidth_ = target_bitrate_ / incoming_framerate_;
294 }
295 }
296
297 // Select the resolution factors: frame size and frame rate change (qm scales).
298 // Selection is for going down in resolution, or for going back up
299 // (if a previous down-sampling action was taken).
300
301 // In the current version the following constraints are imposed:
302 // 1) We only allow for one action, either down or up, at a given time.
303 // 2) The possible down-sampling actions are: spatial by 1/2x1/2, 3/4x3/4;
304 // temporal/frame rate reduction by 1/2 and 2/3.
305 // 3) The action for going back up is the reverse of last (spatial or temporal)
306 // down-sampling action. The list of down-sampling actions from the
307 // Initialize() state are kept in |down_action_history_|.
308 // 4) The total amount of down-sampling (spatial and/or temporal) from the
309 // Initialize() state (native resolution) is limited by various factors.
310 int VCMQmResolution::SelectResolution(VCMResolutionScale** qm) {
311 if (!init_) {
312 return VCM_UNINITIALIZED;
313 }
314 if (content_metrics_ == NULL) {
315 Reset();
316 *qm = qm_;
317 return VCM_OK;
318 }
319
320 // Check conditions on down-sampling state.
321 assert(state_dec_factor_spatial_ >= 1.0f);
322 assert(state_dec_factor_temporal_ >= 1.0f);
323 assert(state_dec_factor_spatial_ <= kMaxSpatialDown);
324 assert(state_dec_factor_temporal_ <= kMaxTempDown);
325 assert(state_dec_factor_temporal_ * state_dec_factor_spatial_ <=
326 kMaxTotalDown);
327
328 // Compute content class for selection.
329 content_class_ = ComputeContentClass();
330 // Compute various rate quantities for selection.
331 ComputeRatesForSelection();
332
333 // Get the encoder state.
334 ComputeEncoderState();
335
336 // Default settings: no action.
337 SetDefaultAction();
338 *qm = qm_;
339
340 // Check for going back up in resolution, if we have had some down-sampling
341 // relative to native state in Initialize().
342 if (down_action_history_[0].spatial != kNoChangeSpatial ||
343 down_action_history_[0].temporal != kNoChangeTemporal) {
344 if (GoingUpResolution()) {
345 *qm = qm_;
346 return VCM_OK;
347 }
348 }
349
350 // Check for going down in resolution.
351 if (GoingDownResolution()) {
352 *qm = qm_;
353 return VCM_OK;
354 }
355 return VCM_OK;
356 }
357
358 void VCMQmResolution::SetDefaultAction() {
359 qm_->codec_width = width_;
360 qm_->codec_height = height_;
361 qm_->frame_rate = user_frame_rate_;
362 qm_->change_resolution_spatial = false;
363 qm_->change_resolution_temporal = false;
364 qm_->spatial_width_fact = 1.0f;
365 qm_->spatial_height_fact = 1.0f;
366 qm_->temporal_fact = 1.0f;
367 action_.spatial = kNoChangeSpatial;
368 action_.temporal = kNoChangeTemporal;
369 }
370
371 void VCMQmResolution::ComputeRatesForSelection() {
372 avg_target_rate_ = 0.0f;
373 avg_incoming_framerate_ = 0.0f;
374 avg_ratio_buffer_low_ = 0.0f;
375 avg_rate_mismatch_ = 0.0f;
376 avg_rate_mismatch_sgn_ = 0.0f;
377 avg_packet_loss_ = 0.0f;
378 if (frame_cnt_ > 0) {
379 avg_ratio_buffer_low_ = static_cast<float>(low_buffer_cnt_) /
380 static_cast<float>(frame_cnt_);
381 }
382 if (update_rate_cnt_ > 0) {
383 avg_rate_mismatch_ = static_cast<float>(sum_rate_MM_) /
384 static_cast<float>(update_rate_cnt_);
385 avg_rate_mismatch_sgn_ = static_cast<float>(sum_rate_MM_sgn_) /
386 static_cast<float>(update_rate_cnt_);
387 avg_target_rate_ = static_cast<float>(sum_target_rate_) /
388 static_cast<float>(update_rate_cnt_);
389 avg_incoming_framerate_ = static_cast<float>(sum_incoming_framerate_) /
390 static_cast<float>(update_rate_cnt_);
391 avg_packet_loss_ = static_cast<float>(sum_packet_loss_) /
392 static_cast<float>(update_rate_cnt_);
393 }
394 // For selection we may want to weight some quantities more heavily
395 // with the current (i.e., next ~1sec) rate values.
396 avg_target_rate_ = kWeightRate * avg_target_rate_ +
397 (1.0 - kWeightRate) * target_bitrate_;
398 avg_incoming_framerate_ = kWeightRate * avg_incoming_framerate_ +
399 (1.0 - kWeightRate) * incoming_framerate_;
400 // Use base layer frame rate for temporal layers: this will favor spatial.
401 assert(num_layers_ > 0);
402 framerate_level_ = FrameRateLevel(
403 avg_incoming_framerate_ / static_cast<float>(1 << (num_layers_ - 1)));
404 }
405
406 void VCMQmResolution::ComputeEncoderState() {
407 // Default.
408 encoder_state_ = kStableEncoding;
409
410 // Assign stressed state if:
411 // 1) occurrences of low buffer levels is high, or
412 // 2) rate mis-match is high, and consistent over-shooting by encoder.
413 if ((avg_ratio_buffer_low_ > kMaxBufferLow) ||
414 ((avg_rate_mismatch_ > kMaxRateMisMatch) &&
415 (avg_rate_mismatch_sgn_ < -kRateOverShoot))) {
416 encoder_state_ = kStressedEncoding;
417 }
418 // Assign easy state if:
419 // 1) rate mis-match is high, and
420 // 2) consistent under-shooting by encoder.
421 if ((avg_rate_mismatch_ > kMaxRateMisMatch) &&
422 (avg_rate_mismatch_sgn_ > kRateUnderShoot)) {
423 encoder_state_ = kEasyEncoding;
424 }
425 }
426
427 bool VCMQmResolution::GoingUpResolution() {
428 // For going up, we check for undoing the previous down-sampling action.
429
430 float fac_width = kFactorWidthSpatial[down_action_history_[0].spatial];
431 float fac_height = kFactorHeightSpatial[down_action_history_[0].spatial];
432 float fac_temp = kFactorTemporal[down_action_history_[0].temporal];
433 // For going up spatially, we allow for going up by 3/4x3/4 at each stage.
434 // So if the last spatial action was 1/2x1/2 it would be undone in 2 stages.
435 // Modify the fac_width/height for this case.
436 if (down_action_history_[0].spatial == kOneQuarterSpatialUniform) {
437 fac_width = kFactorWidthSpatial[kOneQuarterSpatialUniform] /
438 kFactorWidthSpatial[kOneHalfSpatialUniform];
439 fac_height = kFactorHeightSpatial[kOneQuarterSpatialUniform] /
440 kFactorHeightSpatial[kOneHalfSpatialUniform];
441 }
442
443 // Check if we should go up both spatially and temporally.
444 if (down_action_history_[0].spatial != kNoChangeSpatial &&
445 down_action_history_[0].temporal != kNoChangeTemporal) {
446 if (ConditionForGoingUp(fac_width, fac_height, fac_temp,
447 kTransRateScaleUpSpatialTemp)) {
448 action_.spatial = down_action_history_[0].spatial;
449 action_.temporal = down_action_history_[0].temporal;
450 UpdateDownsamplingState(kUpResolution);
451 return true;
452 }
453 }
454 // Check if we should go up either spatially or temporally.
455 bool selected_up_spatial = false;
456 bool selected_up_temporal = false;
457 if (down_action_history_[0].spatial != kNoChangeSpatial) {
458 selected_up_spatial = ConditionForGoingUp(fac_width, fac_height, 1.0f,
459 kTransRateScaleUpSpatial);
460 }
461 if (down_action_history_[0].temporal != kNoChangeTemporal) {
462 selected_up_temporal = ConditionForGoingUp(1.0f, 1.0f, fac_temp,
463 kTransRateScaleUpTemp);
464 }
465 if (selected_up_spatial && !selected_up_temporal) {
466 action_.spatial = down_action_history_[0].spatial;
467 action_.temporal = kNoChangeTemporal;
468 UpdateDownsamplingState(kUpResolution);
469 return true;
470 } else if (!selected_up_spatial && selected_up_temporal) {
471 action_.spatial = kNoChangeSpatial;
472 action_.temporal = down_action_history_[0].temporal;
473 UpdateDownsamplingState(kUpResolution);
474 return true;
475 } else if (selected_up_spatial && selected_up_temporal) {
476 PickSpatialOrTemporal();
477 UpdateDownsamplingState(kUpResolution);
478 return true;
479 }
480 return false;
481 }
482
483 bool VCMQmResolution::ConditionForGoingUp(float fac_width,
484 float fac_height,
485 float fac_temp,
486 float scale_fac) {
487 float estimated_transition_rate_up = GetTransitionRate(fac_width, fac_height,
488 fac_temp, scale_fac);
489 // Go back up if:
490 // 1) target rate is above threshold and current encoder state is stable, or
491 // 2) encoder state is easy (encoder is significantly under-shooting target).
492 if (((avg_target_rate_ > estimated_transition_rate_up) &&
493 (encoder_state_ == kStableEncoding)) ||
494 (encoder_state_ == kEasyEncoding)) {
495 return true;
496 } else {
497 return false;
498 }
499 }
500
501 bool VCMQmResolution::GoingDownResolution() {
502 float estimated_transition_rate_down =
503 GetTransitionRate(1.0f, 1.0f, 1.0f, 1.0f);
504 float max_rate = kFrameRateFac[framerate_level_] * kMaxRateQm[image_type_];
505 // Resolution reduction if:
506 // (1) target rate is below transition rate, or
507 // (2) encoder is in stressed state and target rate below a max threshold.
508 if ((avg_target_rate_ < estimated_transition_rate_down ) ||
509 (encoder_state_ == kStressedEncoding && avg_target_rate_ < max_rate)) {
510 // Get the down-sampling action: based on content class, and how low
511 // average target rate is relative to transition rate.
512 uint8_t spatial_fact =
513 kSpatialAction[content_class_ +
514 9 * RateClass(estimated_transition_rate_down)];
515 uint8_t temp_fact =
516 kTemporalAction[content_class_ +
517 9 * RateClass(estimated_transition_rate_down)];
518
519 switch (spatial_fact) {
520 case 4: {
521 action_.spatial = kOneQuarterSpatialUniform;
522 break;
523 }
524 case 2: {
525 action_.spatial = kOneHalfSpatialUniform;
526 break;
527 }
528 case 1: {
529 action_.spatial = kNoChangeSpatial;
530 break;
531 }
532 default: {
533 assert(false);
534 }
535 }
536 switch (temp_fact) {
537 case 3: {
538 action_.temporal = kTwoThirdsTemporal;
539 break;
540 }
541 case 2: {
542 action_.temporal = kOneHalfTemporal;
543 break;
544 }
545 case 1: {
546 action_.temporal = kNoChangeTemporal;
547 break;
548 }
549 default: {
550 assert(false);
551 }
552 }
553 // Only allow for one action (spatial or temporal) at a given time.
554 assert(action_.temporal == kNoChangeTemporal ||
555 action_.spatial == kNoChangeSpatial);
556
557 // Adjust cases not captured in tables, mainly based on frame rate, and
558 // also check for odd frame sizes.
559 AdjustAction();
560
561 // Update down-sampling state.
562 if (action_.spatial != kNoChangeSpatial ||
563 action_.temporal != kNoChangeTemporal) {
564 UpdateDownsamplingState(kDownResolution);
565 return true;
566 }
567 }
568 return false;
569 }
570
571 float VCMQmResolution::GetTransitionRate(float fac_width,
572 float fac_height,
573 float fac_temp,
574 float scale_fac) {
575 ImageType image_type = GetImageType(
576 static_cast<uint16_t>(fac_width * width_),
577 static_cast<uint16_t>(fac_height * height_));
578
579 FrameRateLevelClass framerate_level =
580 FrameRateLevel(fac_temp * avg_incoming_framerate_);
581 // If we are checking for going up temporally, and this is the last
582 // temporal action, then use native frame rate.
583 if (down_action_history_[1].temporal == kNoChangeTemporal &&
584 fac_temp > 1.0f) {
585 framerate_level = FrameRateLevel(native_frame_rate_);
586 }
587
588 // The maximum allowed rate below which down-sampling is allowed:
589 // Nominal values based on image format (frame size and frame rate).
590 float max_rate = kFrameRateFac[framerate_level] * kMaxRateQm[image_type];
591
592 uint8_t image_class = image_type > kVGA ? 1: 0;
593 uint8_t table_index = image_class * 9 + content_class_;
594 // Scale factor for down-sampling transition threshold:
595 // factor based on the content class and the image size.
596 float scaleTransRate = kScaleTransRateQm[table_index];
597 // Threshold bitrate for resolution action.
598 return static_cast<float> (scale_fac * scaleTransRate * max_rate);
599 }
600
601 void VCMQmResolution::UpdateDownsamplingState(UpDownAction up_down) {
602 if (up_down == kUpResolution) {
603 qm_->spatial_width_fact = 1.0f / kFactorWidthSpatial[action_.spatial];
604 qm_->spatial_height_fact = 1.0f / kFactorHeightSpatial[action_.spatial];
605 // If last spatial action was 1/2x1/2, we undo it in two steps, so the
606 // spatial scale factor in this first step is modified as (4.0/3.0 / 2.0).
607 if (action_.spatial == kOneQuarterSpatialUniform) {
608 qm_->spatial_width_fact =
609 1.0f * kFactorWidthSpatial[kOneHalfSpatialUniform] /
610 kFactorWidthSpatial[kOneQuarterSpatialUniform];
611 qm_->spatial_height_fact =
612 1.0f * kFactorHeightSpatial[kOneHalfSpatialUniform] /
613 kFactorHeightSpatial[kOneQuarterSpatialUniform];
614 }
615 qm_->temporal_fact = 1.0f / kFactorTemporal[action_.temporal];
616 RemoveLastDownAction();
617 } else if (up_down == kDownResolution) {
618 ConstrainAmountOfDownSampling();
619 ConvertSpatialFractionalToWhole();
620 qm_->spatial_width_fact = kFactorWidthSpatial[action_.spatial];
621 qm_->spatial_height_fact = kFactorHeightSpatial[action_.spatial];
622 qm_->temporal_fact = kFactorTemporal[action_.temporal];
623 InsertLatestDownAction();
624 } else {
625 // This function should only be called if either the Up or Down action
626 // has been selected.
627 assert(false);
628 }
629 UpdateCodecResolution();
630 state_dec_factor_spatial_ = state_dec_factor_spatial_ *
631 qm_->spatial_width_fact * qm_->spatial_height_fact;
632 state_dec_factor_temporal_ = state_dec_factor_temporal_ * qm_->temporal_fact;
633 }
634
635 void VCMQmResolution::UpdateCodecResolution() {
636 if (action_.spatial != kNoChangeSpatial) {
637 qm_->change_resolution_spatial = true;
638 qm_->codec_width = static_cast<uint16_t>(width_ /
639 qm_->spatial_width_fact + 0.5f);
640 qm_->codec_height = static_cast<uint16_t>(height_ /
641 qm_->spatial_height_fact + 0.5f);
642 // Size should not exceed native sizes.
643 assert(qm_->codec_width <= native_width_);
644 assert(qm_->codec_height <= native_height_);
645 // New sizes should be multiple of 2, otherwise spatial should not have
646 // been selected.
647 assert(qm_->codec_width % 2 == 0);
648 assert(qm_->codec_height % 2 == 0);
649 }
650 if (action_.temporal != kNoChangeTemporal) {
651 qm_->change_resolution_temporal = true;
652 // Update the frame rate based on the average incoming frame rate.
653 qm_->frame_rate = avg_incoming_framerate_ / qm_->temporal_fact + 0.5f;
654 if (down_action_history_[0].temporal == 0) {
655 // When we undo the last temporal-down action, make sure we go back up
656 // to the native frame rate. Since the incoming frame rate may
657 // fluctuate over time, |avg_incoming_framerate_| scaled back up may
658 // be smaller than |native_frame rate_|.
659 qm_->frame_rate = native_frame_rate_;
660 }
661 }
662 }
663
664 uint8_t VCMQmResolution::RateClass(float transition_rate) {
665 return avg_target_rate_ < (kFacLowRate * transition_rate) ? 0:
666 (avg_target_rate_ >= transition_rate ? 2 : 1);
667 }
668
669 // TODO(marpan): Would be better to capture these frame rate adjustments by
670 // extending the table data (qm_select_data.h).
671 void VCMQmResolution::AdjustAction() {
672 // If the spatial level is default state (neither low or high), motion level
673 // is not high, and spatial action was selected, switch to 2/3 frame rate
674 // reduction if the average incoming frame rate is high.
675 if (spatial_.level == kDefault && motion_.level != kHigh &&
676 action_.spatial != kNoChangeSpatial &&
677 framerate_level_ == kFrameRateHigh) {
678 action_.spatial = kNoChangeSpatial;
679 action_.temporal = kTwoThirdsTemporal;
680 }
681 // If both motion and spatial level are low, and temporal down action was
682 // selected, switch to spatial 3/4x3/4 if the frame rate is not above the
683 // lower middle level (|kFrameRateMiddle1|).
684 if (motion_.level == kLow && spatial_.level == kLow &&
685 framerate_level_ <= kFrameRateMiddle1 &&
686 action_.temporal != kNoChangeTemporal) {
687 action_.spatial = kOneHalfSpatialUniform;
688 action_.temporal = kNoChangeTemporal;
689 }
690 // If spatial action is selected, and there has been too much spatial
691 // reduction already (i.e., 1/4), then switch to temporal action if the
692 // average frame rate is not low.
693 if (action_.spatial != kNoChangeSpatial &&
694 down_action_history_[0].spatial == kOneQuarterSpatialUniform &&
695 framerate_level_ != kFrameRateLow) {
696 action_.spatial = kNoChangeSpatial;
697 action_.temporal = kTwoThirdsTemporal;
698 }
699 // Never use temporal action if number of temporal layers is above 2.
700 if (num_layers_ > 2) {
701 if (action_.temporal != kNoChangeTemporal) {
702 action_.spatial = kOneHalfSpatialUniform;
703 }
704 action_.temporal = kNoChangeTemporal;
705 }
706 // If spatial action was selected, we need to make sure the frame sizes
707 // are multiples of two. Otherwise switch to 2/3 temporal.
708 if (action_.spatial != kNoChangeSpatial &&
709 !EvenFrameSize()) {
710 action_.spatial = kNoChangeSpatial;
711 // Only one action (spatial or temporal) is allowed at a given time, so need
712 // to check whether temporal action is currently selected.
713 action_.temporal = kTwoThirdsTemporal;
714 }
715 }
716
717 void VCMQmResolution::ConvertSpatialFractionalToWhole() {
718 // If 3/4 spatial is selected, check if there has been another 3/4,
719 // and if so, combine them into 1/2. 1/2 scaling is more efficient than 9/16.
720 // Note we define 3/4x3/4 spatial as kOneHalfSpatialUniform.
721 if (action_.spatial == kOneHalfSpatialUniform) {
722 bool found = false;
723 int isel = kDownActionHistorySize;
724 for (int i = 0; i < kDownActionHistorySize; ++i) {
725 if (down_action_history_[i].spatial == kOneHalfSpatialUniform) {
726 isel = i;
727 found = true;
728 break;
729 }
730 }
731 if (found) {
732 action_.spatial = kOneQuarterSpatialUniform;
733 state_dec_factor_spatial_ = state_dec_factor_spatial_ /
734 (kFactorWidthSpatial[kOneHalfSpatialUniform] *
735 kFactorHeightSpatial[kOneHalfSpatialUniform]);
736 // Check if switching to 1/2x1/2 (=1/4) spatial is allowed.
737 ConstrainAmountOfDownSampling();
738 if (action_.spatial == kNoChangeSpatial) {
739 // Not allowed. Go back to 3/4x3/4 spatial.
740 action_.spatial = kOneHalfSpatialUniform;
741 state_dec_factor_spatial_ = state_dec_factor_spatial_ *
742 kFactorWidthSpatial[kOneHalfSpatialUniform] *
743 kFactorHeightSpatial[kOneHalfSpatialUniform];
744 } else {
745 // Switching is allowed. Remove 3/4x3/4 from the history, and update
746 // the frame size.
747 for (int i = isel; i < kDownActionHistorySize - 1; ++i) {
748 down_action_history_[i].spatial =
749 down_action_history_[i + 1].spatial;
750 }
751 width_ = width_ * kFactorWidthSpatial[kOneHalfSpatialUniform];
752 height_ = height_ * kFactorHeightSpatial[kOneHalfSpatialUniform];
753 }
754 }
755 }
756 }
757
758 // Returns false if the new frame sizes, under the current spatial action,
759 // are not multiples of two.
760 bool VCMQmResolution::EvenFrameSize() {
761 if (action_.spatial == kOneHalfSpatialUniform) {
762 if ((width_ * 3 / 4) % 2 != 0 || (height_ * 3 / 4) % 2 != 0) {
763 return false;
764 }
765 } else if (action_.spatial == kOneQuarterSpatialUniform) {
766 if ((width_ * 1 / 2) % 2 != 0 || (height_ * 1 / 2) % 2 != 0) {
767 return false;
768 }
769 }
770 return true;
771 }
772
773 void VCMQmResolution::InsertLatestDownAction() {
774 if (action_.spatial != kNoChangeSpatial) {
775 for (int i = kDownActionHistorySize - 1; i > 0; --i) {
776 down_action_history_[i].spatial = down_action_history_[i - 1].spatial;
777 }
778 down_action_history_[0].spatial = action_.spatial;
779 }
780 if (action_.temporal != kNoChangeTemporal) {
781 for (int i = kDownActionHistorySize - 1; i > 0; --i) {
782 down_action_history_[i].temporal = down_action_history_[i - 1].temporal;
783 }
784 down_action_history_[0].temporal = action_.temporal;
785 }
786 }
787
788 void VCMQmResolution::RemoveLastDownAction() {
789 if (action_.spatial != kNoChangeSpatial) {
790 // If the last spatial action was 1/2x1/2 we replace it with 3/4x3/4.
791 if (action_.spatial == kOneQuarterSpatialUniform) {
792 down_action_history_[0].spatial = kOneHalfSpatialUniform;
793 } else {
794 for (int i = 0; i < kDownActionHistorySize - 1; ++i) {
795 down_action_history_[i].spatial = down_action_history_[i + 1].spatial;
796 }
797 down_action_history_[kDownActionHistorySize - 1].spatial =
798 kNoChangeSpatial;
799 }
800 }
801 if (action_.temporal != kNoChangeTemporal) {
802 for (int i = 0; i < kDownActionHistorySize - 1; ++i) {
803 down_action_history_[i].temporal = down_action_history_[i + 1].temporal;
804 }
805 down_action_history_[kDownActionHistorySize - 1].temporal =
806 kNoChangeTemporal;
807 }
808 }
809
810 void VCMQmResolution::ConstrainAmountOfDownSampling() {
811 // Sanity checks on down-sampling selection:
812 // override the settings for too small image size and/or frame rate.
813 // Also check the limit on current down-sampling states.
814
815 float spatial_width_fact = kFactorWidthSpatial[action_.spatial];
816 float spatial_height_fact = kFactorHeightSpatial[action_.spatial];
817 float temporal_fact = kFactorTemporal[action_.temporal];
818 float new_dec_factor_spatial = state_dec_factor_spatial_ *
819 spatial_width_fact * spatial_height_fact;
820 float new_dec_factor_temp = state_dec_factor_temporal_ * temporal_fact;
821
822 // No spatial sampling if current frame size is too small, or if the
823 // amount of spatial down-sampling is above maximum spatial down-action.
824 if ((width_ * height_) <= kMinImageSize ||
825 new_dec_factor_spatial > kMaxSpatialDown) {
826 action_.spatial = kNoChangeSpatial;
827 new_dec_factor_spatial = state_dec_factor_spatial_;
828 }
829 // No frame rate reduction if average frame rate is below some point, or if
830 // the amount of temporal down-sampling is above maximum temporal down-action.
831 if (avg_incoming_framerate_ <= kMinFrameRate ||
832 new_dec_factor_temp > kMaxTempDown) {
833 action_.temporal = kNoChangeTemporal;
834 new_dec_factor_temp = state_dec_factor_temporal_;
835 }
836 // Check if the total (spatial-temporal) down-action is above maximum allowed,
837 // if so, disallow the current selected down-action.
838 if (new_dec_factor_spatial * new_dec_factor_temp > kMaxTotalDown) {
839 if (action_.spatial != kNoChangeSpatial) {
840 action_.spatial = kNoChangeSpatial;
841 } else if (action_.temporal != kNoChangeTemporal) {
842 action_.temporal = kNoChangeTemporal;
843 } else {
844 // We only allow for one action (spatial or temporal) at a given time, so
845 // either spatial or temporal action is selected when this function is
846 // called. If the selected action is disallowed from one of the above
847 // 2 prior conditions (on spatial & temporal max down-action), then this
848 // condition "total down-action > |kMaxTotalDown|" would not be entered.
849 assert(false);
850 }
851 }
852 }
853
854 void VCMQmResolution::PickSpatialOrTemporal() {
855 // Pick the one that has had the most down-sampling thus far.
856 if (state_dec_factor_spatial_ > state_dec_factor_temporal_) {
857 action_.spatial = down_action_history_[0].spatial;
858 action_.temporal = kNoChangeTemporal;
859 } else {
860 action_.spatial = kNoChangeSpatial;
861 action_.temporal = down_action_history_[0].temporal;
862 }
863 }
864
865 // TODO(marpan): Update when we allow for directional spatial down-sampling.
866 void VCMQmResolution::SelectSpatialDirectionMode(float transition_rate) {
867 // Default is 4/3x4/3
868 // For bit rates well below transitional rate, we select 2x2.
869 if (avg_target_rate_ < transition_rate * kRateRedSpatial2X2) {
870 qm_->spatial_width_fact = 2.0f;
871 qm_->spatial_height_fact = 2.0f;
872 }
873 // Otherwise check prediction errors and aspect ratio.
874 float spatial_err = 0.0f;
875 float spatial_err_h = 0.0f;
876 float spatial_err_v = 0.0f;
877 if (content_metrics_) {
878 spatial_err = content_metrics_->spatial_pred_err;
879 spatial_err_h = content_metrics_->spatial_pred_err_h;
880 spatial_err_v = content_metrics_->spatial_pred_err_v;
881 }
882
883 // Favor 1x2 if aspect_ratio is 16:9.
884 if (aspect_ratio_ >= 16.0f / 9.0f) {
885 // Check if 1x2 has lowest prediction error.
886 if (spatial_err_h < spatial_err && spatial_err_h < spatial_err_v) {
887 qm_->spatial_width_fact = 2.0f;
888 qm_->spatial_height_fact = 1.0f;
889 }
890 }
891 // Check for 4/3x4/3 selection: favor 2x2 over 1x2 and 2x1.
892 if (spatial_err < spatial_err_h * (1.0f + kSpatialErr2x2VsHoriz) &&
893 spatial_err < spatial_err_v * (1.0f + kSpatialErr2X2VsVert)) {
894 qm_->spatial_width_fact = 4.0f / 3.0f;
895 qm_->spatial_height_fact = 4.0f / 3.0f;
896 }
897 // Check for 2x1 selection.
898 if (spatial_err_v < spatial_err_h * (1.0f - kSpatialErrVertVsHoriz) &&
899 spatial_err_v < spatial_err * (1.0f - kSpatialErr2X2VsVert)) {
900 qm_->spatial_width_fact = 1.0f;
901 qm_->spatial_height_fact = 2.0f;
902 }
903 }
904
905 // ROBUSTNESS CLASS
906
907 VCMQmRobustness::VCMQmRobustness() {
908 Reset();
909 }
910
911 VCMQmRobustness::~VCMQmRobustness() {
912 }
913
914 void VCMQmRobustness::Reset() {
915 prev_total_rate_ = 0.0f;
916 prev_rtt_time_ = 0;
917 prev_packet_loss_ = 0;
918 prev_code_rate_delta_ = 0;
919 ResetQM();
920 }
921
922 // Adjust the FEC rate based on the content and the network state
923 // (packet loss rate, total rate/bandwidth, round trip time).
924 // Note that packetLoss here is the filtered loss value.
925 float VCMQmRobustness::AdjustFecFactor(uint8_t code_rate_delta,
926 float total_rate,
927 float framerate,
928 int64_t rtt_time,
929 uint8_t packet_loss) {
930 // Default: no adjustment
931 float adjust_fec = 1.0f;
932 if (content_metrics_ == NULL) {
933 return adjust_fec;
934 }
935 // Compute class state of the content.
936 ComputeMotionNFD();
937 ComputeSpatial();
938
939 // TODO(marpan): Set FEC adjustment factor.
940
941 // Keep track of previous values of network state:
942 // adjustment may be also based on pattern of changes in network state.
943 prev_total_rate_ = total_rate;
944 prev_rtt_time_ = rtt_time;
945 prev_packet_loss_ = packet_loss;
946 prev_code_rate_delta_ = code_rate_delta;
947 return adjust_fec;
948 }
949
950 // Set the UEP (unequal-protection across packets) on/off for the FEC.
951 bool VCMQmRobustness::SetUepProtection(uint8_t code_rate_delta,
952 float total_rate,
953 uint8_t packet_loss,
954 bool frame_type) {
955 // Default.
956 return false;
957 }
958 } // namespace
OLDNEW
« no previous file with comments | « webrtc/modules/video_coding/main/source/qm_select.h ('k') | webrtc/modules/video_coding/main/source/qm_select_data.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698