Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / media / capture / content / capture_resolution_chooser.cc
blob3c7f3e58515ac5abed5c2476edccbc6bb0407d0c
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/capture/content/capture_resolution_chooser.h"
7 #include <algorithm>
8 #include <limits>
10 #include "base/strings/string_util.h"
11 #include "media/base/limits.h"
12 #include "media/base/video_util.h"
14 namespace media {
16 namespace {
18 // Each snapped frame size is an integer multiple of this many lines apart.
19 // This is ideal for 16:9 content, but seems to also work well for many
20 // arbitrary aspect ratios.
21 const int kSnappedHeightStep = 90;
23 // The minimum amount of decrease in area between consecutive snapped frame
24 // sizes. This matters externally, where the end-to-end system is hunting for a
25 // capture size that works within all resource bottlenecks. If the snapped
26 // frame sizes are too-close together, the end-to-end system cannot stablize.
27 // If they are too-far apart, quality is being sacrificed.
28 const int kMinAreaDecreasePercent = 15;
30 // Compute the minimum frame size from the given |max_frame_size| and
31 // |resolution_change_policy|.
32 gfx::Size ComputeMinimumCaptureSize(
33 const gfx::Size& max_frame_size,
34 ResolutionChangePolicy resolution_change_policy) {
35 switch (resolution_change_policy) {
36 case RESOLUTION_POLICY_FIXED_RESOLUTION:
37 return max_frame_size;
38 case RESOLUTION_POLICY_FIXED_ASPECT_RATIO: {
39 // TODO(miu): This is a place-holder until "min constraints" are plumbed-
40 // in from the MediaStream framework. http://crbug.com/473336
41 const int kMinLines = 180;
42 if (max_frame_size.height() <= kMinLines)
43 return max_frame_size;
44 const gfx::Size result(
45 kMinLines * max_frame_size.width() / max_frame_size.height(),
46 kMinLines);
47 if (result.width() <= 0 || result.width() > limits::kMaxDimension)
48 return max_frame_size;
49 return result;
51 case RESOLUTION_POLICY_ANY_WITHIN_LIMIT:
52 return gfx::Size(1, 1);
54 NOTREACHED();
55 return gfx::Size(1, 1);
58 // Returns |size|, unless it exceeds |max_size| or is under |min_size|. When
59 // the bounds are exceeded, computes and returns an alternate size of similar
60 // aspect ratio that is within the bounds.
61 gfx::Size ComputeBoundedCaptureSize(const gfx::Size& size,
62 const gfx::Size& min_size,
63 const gfx::Size& max_size) {
64 if (size.width() > max_size.width() || size.height() > max_size.height()) {
65 gfx::Size result = ScaleSizeToFitWithinTarget(size, max_size);
66 result.SetToMax(min_size);
67 return result;
68 } else if (size.width() < min_size.width() ||
69 size.height() < min_size.height()) {
70 gfx::Size result = ScaleSizeToEncompassTarget(size, min_size);
71 result.SetToMin(max_size);
72 return result;
73 } else {
74 return size;
78 // Returns true if the area of |a| is less than that of |b|.
79 bool CompareByArea(const gfx::Size& a, const gfx::Size& b) {
80 return a.GetArea() < b.GetArea();
83 } // namespace
85 CaptureResolutionChooser::CaptureResolutionChooser(
86 const gfx::Size& max_frame_size,
87 ResolutionChangePolicy resolution_change_policy)
88 : max_frame_size_(max_frame_size),
89 min_frame_size_(
90 ComputeMinimumCaptureSize(max_frame_size, resolution_change_policy)),
91 resolution_change_policy_(resolution_change_policy),
92 target_area_(std::numeric_limits<decltype(target_area_)>::max()) {
93 DCHECK_LT(0, max_frame_size_.width());
94 DCHECK_LT(0, max_frame_size_.height());
95 DCHECK_LE(min_frame_size_.width(), max_frame_size_.width());
96 DCHECK_LE(min_frame_size_.height(), max_frame_size_.height());
97 DCHECK_LE(resolution_change_policy_, RESOLUTION_POLICY_LAST);
99 UpdateSnappedFrameSizes(max_frame_size_);
100 RecomputeCaptureSize();
103 CaptureResolutionChooser::~CaptureResolutionChooser() {
106 void CaptureResolutionChooser::SetSourceSize(const gfx::Size& source_size) {
107 if (source_size.IsEmpty())
108 return;
110 switch (resolution_change_policy_) {
111 case RESOLUTION_POLICY_FIXED_RESOLUTION:
112 // Source size changes do not affect the frame resolution. Frame
113 // resolution is always fixed to |max_frame_size_|.
114 break;
116 case RESOLUTION_POLICY_FIXED_ASPECT_RATIO:
117 UpdateSnappedFrameSizes(ComputeBoundedCaptureSize(
118 PadToMatchAspectRatio(source_size, max_frame_size_), min_frame_size_,
119 max_frame_size_));
120 RecomputeCaptureSize();
121 break;
123 case RESOLUTION_POLICY_ANY_WITHIN_LIMIT:
124 UpdateSnappedFrameSizes(ComputeBoundedCaptureSize(
125 source_size, min_frame_size_, max_frame_size_));
126 RecomputeCaptureSize();
127 break;
131 void CaptureResolutionChooser::SetTargetFrameArea(int area) {
132 DCHECK_GE(area, 0);
133 target_area_ = area;
134 RecomputeCaptureSize();
137 gfx::Size CaptureResolutionChooser::FindNearestFrameSize(int area) const {
138 const auto begin = snapped_sizes_.begin();
139 const auto end = snapped_sizes_.end();
140 DCHECK(begin != end);
141 const gfx::Size area_as_size(area, 1); // A facade for CompareByArea().
142 const auto p = std::lower_bound(begin, end, area_as_size, &CompareByArea);
143 if (p == end) {
144 // Boundary case: The target |area| is greater than or equal to the
145 // largest, so the largest size is closest.
146 return *(end - 1);
147 } else if (p == begin) {
148 // Boundary case: The target |area| is smaller than the smallest, so the
149 // smallest size is closest.
150 return *begin;
151 } else {
152 // |p| points to the smallest size whose area is greater than or equal to
153 // the target |area|. The next smaller size could be closer to the target
154 // |area|, so it must also be considered.
155 const auto q = p - 1;
156 return ((p->GetArea() - area) < (area - q->GetArea())) ? *p : *q;
160 gfx::Size CaptureResolutionChooser::FindLargerFrameSize(
161 int area,
162 int num_steps_up) const {
163 DCHECK_GT(num_steps_up, 0);
164 const auto begin = snapped_sizes_.begin();
165 const auto end = snapped_sizes_.end();
166 DCHECK(begin != end);
167 const gfx::Size area_as_size(area, 1); // A facade for CompareByArea().
168 auto p = std::upper_bound(begin, end, area_as_size, &CompareByArea);
169 // |p| is already pointing one step up.
170 const int additional_steps_up = num_steps_up - 1;
171 if ((end - p) > additional_steps_up)
172 return *(p + additional_steps_up);
173 else
174 return *(end - 1);
177 gfx::Size CaptureResolutionChooser::FindSmallerFrameSize(
178 int area,
179 int num_steps_down) const {
180 DCHECK_GT(num_steps_down, 0);
181 const auto begin = snapped_sizes_.begin();
182 const auto end = snapped_sizes_.end();
183 DCHECK(begin != end);
184 const gfx::Size area_as_size(area, 1); // A facade for CompareByArea().
185 const auto p = std::lower_bound(begin, end, area_as_size, &CompareByArea);
186 if ((p - begin) >= num_steps_down)
187 return *(p - num_steps_down);
188 else
189 return *begin;
192 void CaptureResolutionChooser::RecomputeCaptureSize() {
193 const gfx::Size old_capture_size = capture_size_;
194 capture_size_ = FindNearestFrameSize(target_area_);
195 VLOG_IF(1, capture_size_ != old_capture_size)
196 << "Recomputed capture size from " << old_capture_size.ToString()
197 << " to " << capture_size_.ToString() << " ("
198 << (100.0 * capture_size_.height() / snapped_sizes_.back().height())
199 << "% of ideal size)";
202 void CaptureResolutionChooser::UpdateSnappedFrameSizes(
203 const gfx::Size& constrained_size) {
204 // The |constrained_size| is always in the set of possible capture sizes and
205 // is the largest one.
206 snapped_sizes_.clear();
207 snapped_sizes_.push_back(constrained_size);
209 // Repeatedly decrease the size in steps, adding each to |snapped_sizes_|.
210 // However, skip the sizes that do not decrease in area by enough, relative to
211 // the prior size.
212 int last_area = constrained_size.GetArea();
213 for (int height = constrained_size.height() - kSnappedHeightStep;
214 height >= min_frame_size_.height(); height -= kSnappedHeightStep) {
215 const int width =
216 height * constrained_size.width() / constrained_size.height();
217 if (width < min_frame_size_.width())
218 break;
219 const int smaller_area = width * height;
220 const int percent_decrease = 100 * (last_area - smaller_area) / last_area;
221 if (percent_decrease >= kMinAreaDecreasePercent) {
222 snapped_sizes_.push_back(gfx::Size(width, height));
223 last_area = smaller_area;
227 // Reverse ordering, so that sizes are from smallest to largest.
228 std::reverse(snapped_sizes_.begin(), snapped_sizes_.end());
230 if (VLOG_IS_ON(1)) {
231 std::vector<std::string> stringified_sizes;
232 for (const gfx::Size& size : snapped_sizes_)
233 stringified_sizes.push_back(size.ToString());
234 VLOG_STREAM(1) << "Recomputed snapped frame sizes: "
235 << base::JoinString(stringified_sizes, " <--> ");
239 } // namespace media