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/capture_resolution_chooser.h"
10 #include "base/strings/string_util.h"
11 #include "media/base/limits.h"
12 #include "media/base/video_util.h"
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(),
47 if (result
.width() <= 0 || result
.width() > limits::kMaxDimension
)
48 return max_frame_size
;
51 case RESOLUTION_POLICY_ANY_WITHIN_LIMIT
:
52 return gfx::Size(1, 1);
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
);
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
);
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();
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_(ComputeMinimumCaptureSize(max_frame_size
,
90 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() {}
105 void CaptureResolutionChooser::SetSourceSize(const gfx::Size
& source_size
) {
106 if (source_size
.IsEmpty())
109 switch (resolution_change_policy_
) {
110 case RESOLUTION_POLICY_FIXED_RESOLUTION
:
111 // Source size changes do not affect the frame resolution. Frame
112 // resolution is always fixed to |max_frame_size_|.
115 case RESOLUTION_POLICY_FIXED_ASPECT_RATIO
:
116 UpdateSnappedFrameSizes(ComputeBoundedCaptureSize(
117 PadToMatchAspectRatio(source_size
, max_frame_size_
),
120 RecomputeCaptureSize();
123 case RESOLUTION_POLICY_ANY_WITHIN_LIMIT
:
124 UpdateSnappedFrameSizes(ComputeBoundedCaptureSize(
125 source_size
, min_frame_size_
, max_frame_size_
));
126 RecomputeCaptureSize();
131 void CaptureResolutionChooser::SetTargetFrameArea(int 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
);
144 // Boundary case: The target |area| is greater than or equal to the
145 // largest, so the largest size is closest.
147 } else if (p
== begin
) {
148 // Boundary case: The target |area| is smaller than the smallest, so the
149 // smallest size is closest.
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
, int num_steps_up
) const {
162 DCHECK_GT(num_steps_up
, 0);
163 const auto begin
= snapped_sizes_
.begin();
164 const auto end
= snapped_sizes_
.end();
165 DCHECK(begin
!= end
);
166 const gfx::Size
area_as_size(area
, 1); // A facade for CompareByArea().
167 auto p
= std::upper_bound(begin
, end
, area_as_size
, &CompareByArea
);
168 // |p| is already pointing one step up.
169 const int additional_steps_up
= num_steps_up
- 1;
170 if ((end
- p
) > additional_steps_up
)
171 return *(p
+ additional_steps_up
);
176 gfx::Size
CaptureResolutionChooser::FindSmallerFrameSize(
177 int area
, int num_steps_down
) const {
178 DCHECK_GT(num_steps_down
, 0);
179 const auto begin
= snapped_sizes_
.begin();
180 const auto end
= snapped_sizes_
.end();
181 DCHECK(begin
!= end
);
182 const gfx::Size
area_as_size(area
, 1); // A facade for CompareByArea().
183 const auto p
= std::lower_bound(begin
, end
, area_as_size
, &CompareByArea
);
184 if ((p
- begin
) >= num_steps_down
)
185 return *(p
- num_steps_down
);
190 void CaptureResolutionChooser::RecomputeCaptureSize() {
191 const gfx::Size old_capture_size
= capture_size_
;
192 capture_size_
= FindNearestFrameSize(target_area_
);
193 VLOG_IF(1, capture_size_
!= old_capture_size
)
194 << "Recomputed capture size from " << old_capture_size
.ToString()
195 << " to " << capture_size_
.ToString() << " ("
196 << (100.0 * capture_size_
.height() / snapped_sizes_
.back().height())
197 << "% of ideal size)";
200 void CaptureResolutionChooser::UpdateSnappedFrameSizes(
201 const gfx::Size
& constrained_size
) {
202 // The |constrained_size| is always in the set of possible capture sizes and
203 // is the largest one.
204 snapped_sizes_
.clear();
205 snapped_sizes_
.push_back(constrained_size
);
207 // Repeatedly decrease the size in steps, adding each to |snapped_sizes_|.
208 // However, skip the sizes that do not decrease in area by enough, relative to
210 int last_area
= constrained_size
.GetArea();
211 for (int height
= constrained_size
.height() - kSnappedHeightStep
;
212 height
>= min_frame_size_
.height();
213 height
-= kSnappedHeightStep
) {
215 height
* constrained_size
.width() / constrained_size
.height();
216 if (width
< min_frame_size_
.width())
218 const int smaller_area
= width
* height
;
219 const int percent_decrease
= 100 * (last_area
- smaller_area
) / last_area
;
220 if (percent_decrease
>= kMinAreaDecreasePercent
) {
221 snapped_sizes_
.push_back(gfx::Size(width
, height
));
222 last_area
= smaller_area
;
226 // Reverse ordering, so that sizes are from smallest to largest.
227 std::reverse(snapped_sizes_
.begin(), snapped_sizes_
.end());
230 std::vector
<std::string
> stringified_sizes
;
231 for (const gfx::Size
& size
: snapped_sizes_
)
232 stringified_sizes
.push_back(size
.ToString());
233 VLOG_STREAM(1) << "Recomputed snapped frame sizes: "
234 << JoinString(stringified_sizes
, " <--> ");