OLD | NEW |
| (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 | |
OLD | NEW |