Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright (c) 2010 The WebRTC project authors. All Rights Reserved. | 2 * Copyright (c) 2010 The WebRTC project authors. All Rights Reserved. |
| 3 * | 3 * |
| 4 * Use of this source code is governed by a BSD-style license | 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 | 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 | 6 * tree. An additional intellectual property rights grant can be found |
| 7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
| 8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
| 9 */ | 9 */ |
| 10 | 10 |
| 11 #include "webrtc/media/base/videoadapter.h" | 11 #include "webrtc/media/base/videoadapter.h" |
| 12 | 12 |
| 13 #include <algorithm> | 13 #include <algorithm> |
| 14 #include <limits> | 14 #include <limits> |
| 15 | 15 |
| 16 #include "webrtc/base/checks.h" | |
| 16 #include "webrtc/base/logging.h" | 17 #include "webrtc/base/logging.h" |
| 17 #include "webrtc/media/base/mediaconstants.h" | 18 #include "webrtc/media/base/mediaconstants.h" |
| 18 #include "webrtc/media/base/videocommon.h" | 19 #include "webrtc/media/base/videocommon.h" |
| 19 | 20 |
| 20 namespace { | 21 namespace { |
| 21 | 22 |
| 23 struct Fraction { | |
| 24 int numerator; | |
| 25 int denominator; | |
| 26 }; | |
| 27 | |
| 22 // Scale factors optimized for in libYUV that we accept. | 28 // Scale factors optimized for in libYUV that we accept. |
| 23 // Must be sorted in decreasing scale factors for FindScaleLargerThan to work. | 29 // Must be sorted in decreasing scale factors for FindScaleLargerThan to work. |
| 24 const float kScaleFactors[] = { | 30 const Fraction kScaleFractions[] = { |
| 25 1.f / 1.f, // Full size. | 31 {1, 1}, |
| 26 3.f / 4.f, // 3/4 scale. | 32 {3, 4}, |
| 27 1.f / 2.f, // 1/2 scale. | 33 {1, 2}, |
| 28 3.f / 8.f, // 3/8 scale. | 34 {3, 8}, |
| 29 1.f / 4.f, // 1/4 scale. | 35 {1, 4}, |
| 30 3.f / 16.f, // 3/16 scale. | 36 {3, 16}, |
| 31 }; | 37 }; |
| 32 | 38 |
| 33 float FindScaleLessThanOrEqual(int width, | 39 // Round |valueToRound| to a multiple of |multiple|. Prefer rounding upwards, |
| 34 int height, | 40 // but never more than |maxValue|. |
| 35 int target_num_pixels, | 41 int roundUp(int valueToRound, int multiple, int maxValue) { |
| 36 int* resulting_number_of_pixels) { | 42 const int roundedValue = (valueToRound + multiple - 1) / multiple * multiple; |
| 43 return roundedValue <= maxValue ? roundedValue | |
| 44 : (maxValue / multiple * multiple); | |
| 45 } | |
| 46 | |
| 47 Fraction FindScaleLessThanOrEqual(int input_num_pixels, int target_num_pixels) { | |
| 37 float best_distance = std::numeric_limits<float>::max(); | 48 float best_distance = std::numeric_limits<float>::max(); |
| 38 float best_scale = 0.0f; // Default to 0 if nothing matches. | 49 Fraction best_scale = {0, 1}; // Default to 0 if nothing matches. |
| 39 float pixels = width * height; | |
| 40 float best_number_of_pixels = 0.0f; | 50 float best_number_of_pixels = 0.0f; |
| 41 for (const auto& scale : kScaleFactors) { | 51 for (const auto& fraction : kScaleFractions) { |
| 42 float test_num_pixels = pixels * scale * scale; | 52 const float scale = |
| 53 fraction.numerator / static_cast<float>(fraction.denominator); | |
| 54 float test_num_pixels = input_num_pixels * scale * scale; | |
| 43 float diff = target_num_pixels - test_num_pixels; | 55 float diff = target_num_pixels - test_num_pixels; |
| 44 if (diff < 0) { | 56 if (diff < 0) { |
| 45 continue; | 57 continue; |
| 46 } | 58 } |
| 47 if (diff < best_distance) { | 59 if (diff < best_distance) { |
| 48 best_distance = diff; | 60 best_distance = diff; |
| 49 best_scale = scale; | 61 best_scale = fraction; |
| 50 best_number_of_pixels = test_num_pixels; | 62 best_number_of_pixels = test_num_pixels; |
| 51 if (best_distance == 0) { // Found exact match. | 63 if (best_distance == 0) { // Found exact match. |
| 52 break; | 64 break; |
| 53 } | 65 } |
| 54 } | 66 } |
| 55 } | 67 } |
| 56 if (resulting_number_of_pixels) { | |
| 57 *resulting_number_of_pixels = static_cast<int>(best_number_of_pixels + .5f); | |
| 58 } | |
| 59 return best_scale; | 68 return best_scale; |
| 60 } | 69 } |
| 61 | 70 |
| 62 float FindScaleLargerThan(int width, | 71 Fraction FindScaleLargerThan(int input_num_pixels, |
| 63 int height, | 72 int target_num_pixels, |
| 64 int target_num_pixels, | 73 int* resulting_number_of_pixels) { |
| 65 int* resulting_number_of_pixels) { | |
| 66 float best_distance = std::numeric_limits<float>::max(); | 74 float best_distance = std::numeric_limits<float>::max(); |
| 67 float best_scale = 1.f; // Default to unscaled if nothing matches. | 75 Fraction best_scale = {1, 1}; // Default to unscaled if nothing matches. |
| 68 float pixels = width * height; | 76 // Default to input number of pixels. |
| 69 float best_number_of_pixels = pixels; // Default to input number of pixels. | 77 float best_number_of_pixels = input_num_pixels; |
| 70 for (const auto& scale : kScaleFactors) { | 78 for (const auto& fraction : kScaleFractions) { |
| 71 float test_num_pixels = pixels * scale * scale; | 79 const float scale = |
| 80 fraction.numerator / static_cast<float>(fraction.denominator); | |
| 81 float test_num_pixels = input_num_pixels * scale * scale; | |
| 72 float diff = test_num_pixels - target_num_pixels; | 82 float diff = test_num_pixels - target_num_pixels; |
| 73 if (diff <= 0) { | 83 if (diff <= 0) { |
| 74 break; | 84 break; |
| 75 } | 85 } |
| 76 if (diff < best_distance) { | 86 if (diff < best_distance) { |
| 77 best_distance = diff; | 87 best_distance = diff; |
| 78 best_scale = scale; | 88 best_scale = fraction; |
| 79 best_number_of_pixels = test_num_pixels; | 89 best_number_of_pixels = test_num_pixels; |
| 80 } | 90 } |
| 81 } | 91 } |
| 82 | 92 |
| 83 *resulting_number_of_pixels = static_cast<int>(best_number_of_pixels + .5f); | 93 *resulting_number_of_pixels = static_cast<int>(best_number_of_pixels + .5f); |
| 84 return best_scale; | 94 return best_scale; |
| 85 } | 95 } |
| 86 | 96 |
| 97 Fraction FindScale(int input_num_pixels, | |
| 98 int max_pixel_count_step_up, | |
| 99 int max_pixel_count) { | |
| 100 // Try scale just above |max_pixel_count_step_up_|. | |
| 101 if (max_pixel_count_step_up > 0) { | |
| 102 int resulting_pixel_count; | |
| 103 const Fraction scale = FindScaleLargerThan( | |
| 104 input_num_pixels, max_pixel_count_step_up, &resulting_pixel_count); | |
| 105 if (resulting_pixel_count <= max_pixel_count) | |
| 106 return scale; | |
| 107 } | |
| 108 // Return largest scale below |max_pixel_count|. | |
| 109 return FindScaleLessThanOrEqual(input_num_pixels, max_pixel_count); | |
| 110 } | |
| 111 | |
| 87 } // namespace | 112 } // namespace |
| 88 | 113 |
| 89 namespace cricket { | 114 namespace cricket { |
| 90 | 115 |
| 91 VideoAdapter::VideoAdapter() | 116 VideoAdapter::VideoAdapter() |
| 92 : output_num_pixels_(std::numeric_limits<int>::max()), | 117 : frames_in_(0), |
| 93 frames_in_(0), | |
| 94 frames_out_(0), | 118 frames_out_(0), |
| 95 frames_scaled_(0), | 119 frames_scaled_(0), |
| 96 adaption_changes_(0), | 120 adaption_changes_(0), |
| 97 previous_width_(0), | 121 previous_width_(0), |
| 98 previous_height_(0), | 122 previous_height_(0), |
| 123 input_interval_(0), | |
| 99 interval_next_frame_(0), | 124 interval_next_frame_(0), |
| 100 format_request_max_pixel_count_(std::numeric_limits<int>::max()), | 125 resolution_request_max_pixel_count_(std::numeric_limits<int>::max()), |
| 101 resolution_request_max_pixel_count_(std::numeric_limits<int>::max()) {} | 126 resolution_request_max_pixel_count_step_up_(0) {} |
| 102 | 127 |
| 103 VideoAdapter::~VideoAdapter() {} | 128 VideoAdapter::~VideoAdapter() {} |
| 104 | 129 |
| 105 void VideoAdapter::SetExpectedInputFrameInterval(int64_t interval) { | 130 void VideoAdapter::SetExpectedInputFrameInterval(int64_t interval) { |
| 106 // TODO(perkj): Consider measuring input frame rate instead. | 131 // TODO(perkj): Consider measuring input frame rate instead. |
| 107 // Frame rate typically varies depending on lighting. | 132 // Frame rate typically varies depending on lighting. |
| 108 rtc::CritScope cs(&critical_section_); | 133 rtc::CritScope cs(&critical_section_); |
| 109 input_format_.interval = interval; | 134 input_interval_ = interval; |
| 110 } | 135 } |
| 111 | 136 |
| 112 void VideoAdapter::SetInputFormat(const VideoFormat& format) { | 137 void VideoAdapter::AdaptFrameResolution(int in_width, |
| 113 bool is_resolution_change = (input_format().width != format.width || | 138 int in_height, |
| 114 input_format().height != format.height); | 139 int* cropped_width, |
| 115 int64_t old_input_interval = input_format_.interval; | 140 int* cropped_height, |
| 116 input_format_ = format; | 141 int* out_width, |
| 117 output_format_.interval = | 142 int* out_height) { |
| 118 std::max(output_format_.interval, input_format_.interval); | |
| 119 if (old_input_interval != input_format_.interval) { | |
| 120 LOG(LS_INFO) << "VAdapt input interval changed from " | |
| 121 << old_input_interval << " to " << input_format_.interval; | |
| 122 } | |
| 123 if (is_resolution_change) { | |
| 124 // Trigger the adaptation logic again, to potentially reset the adaptation | |
| 125 // state for things like view requests that may not longer be capping | |
| 126 // output (or may now cap output). | |
| 127 Adapt(std::min(format_request_max_pixel_count_, | |
| 128 resolution_request_max_pixel_count_), | |
| 129 0); | |
| 130 } | |
| 131 } | |
| 132 | |
| 133 const VideoFormat& VideoAdapter::input_format() const { | |
| 134 rtc::CritScope cs(&critical_section_); | |
| 135 return input_format_; | |
| 136 } | |
| 137 | |
| 138 VideoFormat VideoAdapter::AdaptFrameResolution(int in_width, int in_height) { | |
| 139 rtc::CritScope cs(&critical_section_); | 143 rtc::CritScope cs(&critical_section_); |
| 140 ++frames_in_; | 144 ++frames_in_; |
| 141 | 145 |
| 142 SetInputFormat(VideoFormat( | 146 // The max output pixel count is the minimum of the requests from |
| 143 in_width, in_height, input_format_.interval, input_format_.fourcc)); | 147 // OnOutputFormatRequest and OnResolutionRequest. |
| 148 int max_pixel_count = resolution_request_max_pixel_count_; | |
| 149 if (requested_format_) { | |
| 150 max_pixel_count = std::min( | |
| 151 max_pixel_count, requested_format_->width * requested_format_->height); | |
| 152 } | |
| 144 | 153 |
| 145 // Drop the input frame if necessary. | 154 // Drop the input frame if necessary. |
| 146 bool should_drop = false; | 155 bool should_drop = false; |
| 147 if (!output_num_pixels_) { | 156 if (max_pixel_count == 0) { |
| 148 // Drop all frames as the output format is 0x0. | 157 // Drop all frames as the output format is 0x0. |
| 149 should_drop = true; | 158 should_drop = true; |
| 150 } else { | 159 } else if (requested_format_ && requested_format_->interval > 0) { |
| 151 // Drop some frames based on input fps and output fps. | 160 // Drop some frames based on input fps and output fps. |
| 152 // Normally output fps is less than input fps. | 161 // Normally output fps is less than input fps. |
| 153 interval_next_frame_ += input_format_.interval; | 162 interval_next_frame_ += input_interval_; |
| 154 if (output_format_.interval > 0) { | 163 if (interval_next_frame_ >= requested_format_->interval) { |
| 155 if (interval_next_frame_ >= output_format_.interval) { | 164 interval_next_frame_ %= requested_format_->interval; |
| 156 interval_next_frame_ %= output_format_.interval; | 165 } else { |
| 157 } else { | 166 should_drop = true; |
| 158 should_drop = true; | |
| 159 } | |
| 160 } | 167 } |
| 161 } | 168 } |
| 162 if (should_drop) { | 169 if (should_drop) { |
| 163 // Show VAdapt log every 90 frames dropped. (3 seconds) | 170 // Show VAdapt log every 90 frames dropped. (3 seconds) |
| 164 if ((frames_in_ - frames_out_) % 90 == 0) { | 171 if ((frames_in_ - frames_out_) % 90 == 0) { |
| 165 // TODO(fbarchard): Reduce to LS_VERBOSE when adapter info is not needed | 172 // TODO(fbarchard): Reduce to LS_VERBOSE when adapter info is not needed |
| 166 // in default calls. | 173 // in default calls. |
| 167 LOG(LS_INFO) << "VAdapt Drop Frame: scaled " << frames_scaled_ | 174 LOG(LS_INFO) << "VAdapt Drop Frame: scaled " << frames_scaled_ |
| 168 << " / out " << frames_out_ | 175 << " / out " << frames_out_ |
| 169 << " / in " << frames_in_ | 176 << " / in " << frames_in_ |
| 170 << " Changes: " << adaption_changes_ | 177 << " Changes: " << adaption_changes_ |
| 171 << " Input: " << in_width | 178 << " Input: " << in_width |
| 172 << "x" << in_height | 179 << "x" << in_height |
| 173 << " i" << input_format_.interval | 180 << " i" << input_interval_ |
| 174 << " Output: i" << output_format_.interval; | 181 << " Output: i" |
| 182 << (requested_format_ ? requested_format_->interval : 0); | |
| 175 } | 183 } |
| 176 | 184 |
| 177 return VideoFormat(); // Drop frame. | 185 // Drop frame. |
| 186 *cropped_width = 0; | |
| 187 *cropped_height = 0; | |
| 188 *out_width = 0; | |
| 189 *out_height = 0; | |
| 190 return; | |
| 178 } | 191 } |
| 179 | 192 |
| 180 const float scale = FindScaleLessThanOrEqual(in_width, in_height, | 193 // Calculate how the input should be cropped. |
| 181 output_num_pixels_, nullptr); | 194 if (!requested_format_ || |
| 182 const int output_width = static_cast<int>(in_width * scale + .5f); | 195 requested_format_->width == 0 || requested_format_->height == 0) { |
| 183 const int output_height = static_cast<int>(in_height * scale + .5f); | 196 *cropped_width = in_width; |
| 197 *cropped_height = in_height; | |
| 198 } else { | |
| 199 // Adjust |requested_format_| orientation to match input. | |
| 200 if ((in_width > in_height) != | |
| 201 (requested_format_->width > requested_format_->height)) { | |
| 202 std::swap(requested_format_->width, requested_format_->height); | |
| 203 } | |
| 204 const float requested_aspect = | |
| 205 requested_format_->width / | |
| 206 static_cast<float>(requested_format_->height); | |
| 207 *cropped_width = | |
| 208 std::min(in_width, static_cast<int>(in_height * requested_aspect)); | |
| 209 *cropped_height = | |
| 210 std::min(in_height, static_cast<int>(in_width / requested_aspect)); | |
| 211 } | |
| 212 | |
| 213 // Find best scale factor. | |
| 214 const Fraction scale = | |
| 215 FindScale(*cropped_width * *cropped_height, | |
| 216 resolution_request_max_pixel_count_step_up_, max_pixel_count); | |
| 217 | |
| 218 // Adjust cropping slightly to get even integer output size and a perfect | |
| 219 // scale factor. | |
| 220 *cropped_width = roundUp(*cropped_width, scale.denominator, in_width); | |
|
nisse-webrtc
2016/05/13 11:47:06
Looks nice and simple, and there's even a test cas
| |
| 221 *cropped_height = roundUp(*cropped_height, scale.denominator, in_height); | |
| 222 RTC_DCHECK_EQ(0, *cropped_width % scale.denominator); | |
| 223 RTC_DCHECK_EQ(0, *cropped_height % scale.denominator); | |
| 224 | |
| 225 // Calculate final output size. | |
| 226 *out_width = *cropped_width / scale.denominator * scale.numerator; | |
| 227 *out_height = *cropped_height / scale.denominator * scale.numerator; | |
| 184 | 228 |
| 185 ++frames_out_; | 229 ++frames_out_; |
| 186 if (scale != 1) | 230 if (scale.numerator != scale.denominator) |
| 187 ++frames_scaled_; | 231 ++frames_scaled_; |
| 188 | 232 |
| 189 if (previous_width_ && (previous_width_ != output_width || | 233 if (previous_width_ && (previous_width_ != *out_width || |
| 190 previous_height_ != output_height)) { | 234 previous_height_ != *out_height)) { |
| 191 ++adaption_changes_; | 235 ++adaption_changes_; |
| 192 LOG(LS_INFO) << "Frame size changed: scaled " << frames_scaled_ << " / out " | 236 LOG(LS_INFO) << "Frame size changed: scaled " << frames_scaled_ << " / out " |
| 193 << frames_out_ << " / in " << frames_in_ | 237 << frames_out_ << " / in " << frames_in_ |
| 194 << " Changes: " << adaption_changes_ << " Input: " << in_width | 238 << " Changes: " << adaption_changes_ << " Input: " << in_width |
| 195 << "x" << in_height << " i" << input_format_.interval | 239 << "x" << in_height << " i" << input_interval_ |
| 196 << " Scale: " << scale << " Output: " << output_width << "x" | 240 << " Scale: " << scale.numerator << "/" << scale.denominator |
| 197 << output_height << " i" << output_format_.interval; | 241 << " Output: " << *out_width << "x" << *out_height << " i" |
| 242 << (requested_format_ ? requested_format_->interval : 0); | |
| 198 } | 243 } |
| 199 | 244 |
| 200 output_format_.width = output_width; | 245 previous_width_ = *out_width; |
| 201 output_format_.height = output_height; | 246 previous_height_ = *out_height; |
| 202 previous_width_ = output_width; | |
| 203 previous_height_ = output_height; | |
| 204 | |
| 205 return output_format_; | |
| 206 } | 247 } |
| 207 | 248 |
| 208 void VideoAdapter::OnOutputFormatRequest(const VideoFormat& format) { | 249 void VideoAdapter::OnOutputFormatRequest(const VideoFormat& format) { |
| 209 rtc::CritScope cs(&critical_section_); | 250 rtc::CritScope cs(&critical_section_); |
| 210 format_request_max_pixel_count_ = format.width * format.height; | 251 requested_format_ = rtc::Optional<VideoFormat>(format); |
| 211 output_format_.interval = format.interval; | 252 interval_next_frame_ = 0; |
| 212 Adapt(std::min(format_request_max_pixel_count_, | |
| 213 resolution_request_max_pixel_count_), | |
| 214 0); | |
| 215 } | 253 } |
| 216 | 254 |
| 217 void VideoAdapter::OnResolutionRequest( | 255 void VideoAdapter::OnResolutionRequest( |
| 218 rtc::Optional<int> max_pixel_count, | 256 rtc::Optional<int> max_pixel_count, |
| 219 rtc::Optional<int> max_pixel_count_step_up) { | 257 rtc::Optional<int> max_pixel_count_step_up) { |
| 220 rtc::CritScope cs(&critical_section_); | 258 rtc::CritScope cs(&critical_section_); |
| 221 resolution_request_max_pixel_count_ = | 259 resolution_request_max_pixel_count_ = |
| 222 max_pixel_count.value_or(std::numeric_limits<int>::max()); | 260 max_pixel_count.value_or(std::numeric_limits<int>::max()); |
| 223 Adapt(std::min(format_request_max_pixel_count_, | 261 resolution_request_max_pixel_count_step_up_ = |
| 224 resolution_request_max_pixel_count_), | 262 max_pixel_count_step_up.value_or(0); |
| 225 max_pixel_count_step_up.value_or(0)); | |
| 226 } | |
| 227 | |
| 228 bool VideoAdapter::Adapt(int max_num_pixels, int max_pixel_count_step_up) { | |
| 229 float scale_lower = | |
| 230 FindScaleLessThanOrEqual(input_format_.width, input_format_.height, | |
| 231 max_num_pixels, &max_num_pixels); | |
| 232 float scale_upper = | |
| 233 max_pixel_count_step_up > 0 | |
| 234 ? FindScaleLargerThan(input_format_.width, input_format_.height, | |
| 235 max_pixel_count_step_up, | |
| 236 &max_pixel_count_step_up) | |
| 237 : 1.f; | |
| 238 | |
| 239 bool use_max_pixel_count_step_up = | |
| 240 max_pixel_count_step_up > 0 && max_num_pixels > max_pixel_count_step_up; | |
| 241 | |
| 242 int old_num_pixels = output_num_pixels_; | |
| 243 output_num_pixels_ = | |
| 244 use_max_pixel_count_step_up ? max_pixel_count_step_up : max_num_pixels; | |
| 245 // Log the new size. | |
| 246 float scale = use_max_pixel_count_step_up ? scale_upper : scale_lower; | |
| 247 int new_width = static_cast<int>(input_format_.width * scale + .5f); | |
| 248 int new_height = static_cast<int>(input_format_.height * scale + .5f); | |
| 249 | |
| 250 bool changed = output_num_pixels_ != old_num_pixels; | |
| 251 LOG(LS_INFO) << "OnResolutionRequest: " | |
| 252 << " Max pixels: " << max_num_pixels | |
| 253 << " Max pixels step up: " << max_pixel_count_step_up | |
| 254 << " Output Pixels: " << output_num_pixels_ | |
| 255 << " Input: " << input_format_.width << "x" | |
| 256 << input_format_.height << " Scale: " << scale | |
| 257 << " Resolution: " << new_width << "x" << new_height | |
| 258 << " Changed: " << (changed ? "true" : "false"); | |
| 259 | |
| 260 return changed; | |
| 261 } | 263 } |
| 262 | 264 |
| 263 } // namespace cricket | 265 } // namespace cricket |
| OLD | NEW |