1 // Copyright 2017 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.
4
5 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASTREAM_MEDIA_STREAM_CONSTRAINTS_UTIL_SETS_H_
6 #define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASTREAM_MEDIA_STREAM_CONSTRAINTS_UTIL_SETS_H_
7
8 #include <algorithm>
9 #include <limits>
10 #include <string>
11 #include <utility>
12
13 #include "base/check_op.h"
14 #include "base/gtest_prod_util.h"
15 #include "base/optional.h"
16 #include "third_party/blink/renderer/modules/modules_export.h"
17 #include "third_party/blink/renderer/platform/mediastream/media_constraints.h"
18 #include "third_party/blink/renderer/platform/wtf/vector.h"
19
20 namespace blink {
21
22 struct MediaTrackConstraintSetPlatform;
23
24 template <typename ConstraintType>
ConstraintHasMax(const ConstraintType & constraint)25 bool ConstraintHasMax(const ConstraintType& constraint) {
26 return constraint.HasMax() || constraint.HasExact();
27 }
28
29 template <typename ConstraintType>
ConstraintHasMin(const ConstraintType & constraint)30 bool ConstraintHasMin(const ConstraintType& constraint) {
31 return constraint.HasMin() || constraint.HasExact();
32 }
33
34 template <typename ConstraintType>
35 auto ConstraintMax(const ConstraintType& constraint)
36 -> decltype(constraint.Max()) {
37 DCHECK(ConstraintHasMax(constraint));
38 return constraint.HasExact() ? constraint.Exact() : constraint.Max();
39 }
40
41 template <typename ConstraintType>
42 auto ConstraintMin(const ConstraintType& constraint)
43 -> decltype(constraint.Min()) {
44 DCHECK(ConstraintHasMin(constraint));
45 return constraint.HasExact() ? constraint.Exact() : constraint.Min();
46 }
47
48 namespace media_constraints {
49
50 // This class template represents a set of candidates suitable for a numeric
51 // range-based constraint.
52 template <typename T>
53 class NumericRangeSet {
54 public:
55 NumericRangeSet() = default;
NumericRangeSet(base::Optional<T> min,base::Optional<T> max)56 NumericRangeSet(base::Optional<T> min, base::Optional<T> max)
57 : min_(std::move(min)), max_(std::move(max)) {}
58 NumericRangeSet(const NumericRangeSet& other) = default;
59 NumericRangeSet& operator=(const NumericRangeSet& other) = default;
60 ~NumericRangeSet() = default;
61
Min()62 const base::Optional<T>& Min() const { return min_; }
Max()63 const base::Optional<T>& Max() const { return max_; }
IsEmpty()64 bool IsEmpty() const { return max_ && min_ && *max_ < *min_; }
65
Intersection(const NumericRangeSet & other)66 NumericRangeSet Intersection(const NumericRangeSet& other) const {
67 base::Optional<T> min = min_;
68 if (other.Min())
69 min = min ? std::max(*min, *other.Min()) : other.Min();
70
71 base::Optional<T> max = max_;
72 if (other.Max())
73 max = max ? std::min(*max, *other.Max()) : other.Max();
74
75 return NumericRangeSet(min, max);
76 }
77
Contains(T value)78 bool Contains(T value) const {
79 return (!Min() || value >= *Min()) && (!Max() || value <= *Max());
80 }
81
82 // Creates a NumericRangeSet based on the minimum and maximum values of
83 // |constraint| and a client-provided range of valid values.
84 // If the range given in |constraint| has empty intersection with the range
85 // [|lower_bound|, |upper_bound|], an empty NumericRangeSet is returned.
86 // Otherwise, if the minimum or maximum value of |constraint| is outside
87 // [|lower_bound|, |upper_bound|], the value is ignored.
88 template <typename ConstraintType>
FromConstraint(ConstraintType constraint,T lower_bound,T upper_bound)89 static NumericRangeSet<T> FromConstraint(ConstraintType constraint,
90 T lower_bound,
91 T upper_bound) {
92 DCHECK_LE(lower_bound, upper_bound);
93 // Return an empty set if the constraint range does not overlap with the
94 // valid range.
95 if ((ConstraintHasMax(constraint) &&
96 ConstraintMax(constraint) < lower_bound) ||
97 (ConstraintHasMin(constraint) &&
98 ConstraintMin(constraint) > upper_bound)) {
99 return NumericRangeSet<decltype(constraint.Min())>(1, 0);
100 }
101
102 return NumericRangeSet<T>(
103 ConstraintHasMin(constraint) && ConstraintMin(constraint) >= lower_bound
104 ? ConstraintMin(constraint)
105 : base::Optional<T>(),
106 ConstraintHasMax(constraint) && ConstraintMax(constraint) <= upper_bound
107 ? ConstraintMax(constraint)
108 : base::Optional<T>());
109 }
110
111 // Creates a NumericRangeSet based on the minimum and maximum values of
112 // |constraint| and a client-provided range of valid values.
113 template <typename ConstraintType>
FromConstraint(ConstraintType constraint)114 static NumericRangeSet<T> FromConstraint(ConstraintType constraint) {
115 return NumericRangeSet<T>(
116 ConstraintHasMin(constraint) ? ConstraintMin(constraint)
117 : base::Optional<T>(),
118 ConstraintHasMax(constraint) ? ConstraintMax(constraint)
119 : base::Optional<T>());
120 }
121
122 // Creates a NumericRangeSet based on a single value representing both the
123 // minimum and the maximum values for this range.
FromValue(T value)124 static NumericRangeSet<T> FromValue(T value) {
125 return NumericRangeSet<T>(value, value);
126 }
127
EmptySet()128 static NumericRangeSet<T> EmptySet() { return NumericRangeSet(1, 0); }
129
130 private:
131 base::Optional<T> min_;
132 base::Optional<T> max_;
133 };
134
135 // This class defines a set of discrete elements suitable for resolving
136 // constraints with a countable number of choices not suitable to be constrained
137 // by range. Examples are strings, booleans and certain constraints of type
138 // long. A DiscreteSet can be empty, have their elements explicitly stated, or
139 // be the universal set. The universal set is a set that contains all possible
140 // elements. The specific definition of what elements are in the universal set
141 // is application defined (e.g., it could be all possible boolean values, all
142 // possible strings of length N, or anything that suits a particular
143 // application).
144 // TODO(guidou): Rename this class. https://crbug.com/731166
145 template <typename T>
146 class DiscreteSet {
147 public:
148 // Creates a universal set.
DiscreteSet()149 DiscreteSet() : is_universal_(true) {}
150 // Creates a set containing the elements in |elements|.
151 // It is the responsibility of the caller to ensure that |elements| is not
152 // equivalent to the universal set and that |elements| has no repeated
153 // values. Takes ownership of |elements|.
DiscreteSet(Vector<T> elements)154 explicit DiscreteSet(Vector<T> elements)
155 : is_universal_(false), elements_(std::move(elements)) {}
156 // Creates an empty set;
EmptySet()157 static DiscreteSet EmptySet() { return DiscreteSet(Vector<T>()); }
UniversalSet()158 static DiscreteSet UniversalSet() { return DiscreteSet(); }
159
160 DiscreteSet(const DiscreteSet& other) = default;
161 DiscreteSet& operator=(const DiscreteSet& other) = default;
162 DiscreteSet(DiscreteSet&& other) = default;
163 DiscreteSet& operator=(DiscreteSet&& other) = default;
164 ~DiscreteSet() = default;
165
Contains(const T & value)166 bool Contains(const T& value) const {
167 return is_universal_ || base::Contains(elements_, value);
168 }
169
IsEmpty()170 bool IsEmpty() const { return !is_universal_ && elements_.IsEmpty(); }
171
HasExplicitElements()172 bool HasExplicitElements() const { return !elements_.IsEmpty(); }
173
Intersection(const DiscreteSet & other)174 DiscreteSet Intersection(const DiscreteSet& other) const {
175 if (is_universal_)
176 return other;
177 if (other.is_universal_)
178 return *this;
179 if (IsEmpty() || other.IsEmpty())
180 return EmptySet();
181
182 // Both sets have explicit elements.
183 Vector<T> intersection;
184 for (const auto& entry : elements_) {
185 if (base::Contains(other.elements_, entry))
186 intersection.push_back(entry);
187 }
188 return DiscreteSet(std::move(intersection));
189 }
190
191 // Returns a copy of the first element in the set. This is useful as a simple
192 // tie-breaker rule. This applies only to constrained nonempty sets.
193 // Behavior is undefined if the set is empty or universal.
FirstElement()194 T FirstElement() const {
195 DCHECK(HasExplicitElements());
196 return elements_[0];
197 }
198
199 // Returns a reference to the list of elements in the set.
200 // Behavior is undefined if the set is universal.
elements()201 const Vector<T>& elements() const {
202 DCHECK(!is_universal_);
203 return elements_;
204 }
205
is_universal()206 bool is_universal() const { return is_universal_; }
207
208 private:
209 bool is_universal_;
210 Vector<T> elements_;
211 };
212
213 // Special case for DiscreteSet<bool> where it is easy to produce an explicit
214 // set that contains all possible elements.
215 template <>
is_universal()216 inline bool DiscreteSet<bool>::is_universal() const {
217 return Contains(true) && Contains(false);
218 }
219
220 MODULES_EXPORT DiscreteSet<std::string> StringSetFromConstraint(
221 const StringConstraint& constraint);
222 MODULES_EXPORT DiscreteSet<bool> BoolSetFromConstraint(
223 const BooleanConstraint& constraint);
224
225 // This class represents a set of (height, width) screen resolution candidates
226 // determined by width, height and aspect-ratio constraints.
227 // This class supports widths and heights from 0 to kMaxDimension, both
228 // inclusive and aspect ratios from 0.0 to positive infinity, both inclusive.
229 class MODULES_EXPORT ResolutionSet {
230 public:
231 static const int kMaxDimension = std::numeric_limits<int>::max();
232
233 // Helper class that represents (height, width) points on a plane.
234 // TODO(guidou): Use a generic point/vector class that uses double once it
235 // becomes available (e.g., a gfx::Vector2dD).
236 class MODULES_EXPORT Point {
237 public:
238 // Creates a (|height|, |width|) point. |height| and |width| must be finite.
239 Point(double height, double width);
240 Point(const Point& other);
241 Point& operator=(const Point& other);
242
243 // Accessors.
height()244 double height() const { return height_; }
width()245 double width() const { return width_; }
AspectRatio()246 double AspectRatio() const { return width_ / height_; }
247
248 // Exact equality/inequality operators.
249 bool operator==(const Point& other) const;
250 bool operator!=(const Point& other) const;
251
252 // Returns true if the coordinates of this point and |other| are
253 // approximately equal.
254 bool IsApproximatelyEqualTo(const Point& other) const;
255
256 // Vector-style addition and subtraction operators.
257 Point operator+(const Point& other) const;
258 Point operator-(const Point& other) const;
259
260 // Returns the dot product between |p1| and |p2|.
261 static double Dot(const Point& p1, const Point& p2);
262
263 // Returns the square Euclidean distance between |p1| and |p2|.
264 static double SquareEuclideanDistance(const Point& p1, const Point& p2);
265
266 // Returns the point in the line segment determined by |s1| and |s2| that
267 // is closest to |p|.
268 static Point ClosestPointInSegment(const Point& p,
269 const Point& s1,
270 const Point& s2);
271
272 private:
273 double height_;
274 double width_;
275 };
276
277 // Creates a set with the maximum supported ranges for width, height and
278 // aspect ratio.
279 ResolutionSet();
280 ResolutionSet(int min_height,
281 int max_height,
282 int min_width,
283 int max_width,
284 double min_aspect_ratio,
285 double max_aspect_ratio);
286 ResolutionSet(const ResolutionSet& other);
287 ResolutionSet& operator=(const ResolutionSet& other);
288
289 // Getters.
min_height()290 int min_height() const { return min_height_; }
max_height()291 int max_height() const { return max_height_; }
min_width()292 int min_width() const { return min_width_; }
max_width()293 int max_width() const { return max_width_; }
min_aspect_ratio()294 double min_aspect_ratio() const { return min_aspect_ratio_; }
max_aspect_ratio()295 double max_aspect_ratio() const { return max_aspect_ratio_; }
296
297 // Returns true if this set is empty.
298 bool IsEmpty() const;
299
300 // These functions return true if a particular variable causes the set to be
301 // empty.
302 bool IsHeightEmpty() const;
303 bool IsWidthEmpty() const;
304 bool IsAspectRatioEmpty() const;
305
306 // These functions return true if the given point is included in this set.
307 bool ContainsPoint(const Point& point) const;
308 bool ContainsPoint(int height, int width) const;
309
310 // Returns a new set with the intersection of |*this| and |other|.
311 ResolutionSet Intersection(const ResolutionSet& other) const;
312
313 // Returns a point in this (nonempty) set closest to the ideal values for the
314 // height, width and aspectRatio constraints in |constraint_set|.
315 // Note that this function ignores all the other data in |constraint_set|.
316 // Only the ideal height, width and aspect ratio are used, and from now on
317 // referred to as |ideal_height|, |ideal_width| and |ideal_aspect_ratio|
318 // respectively.
319 //
320 // * If all three ideal values are given, |ideal_aspect_ratio| is ignored and
321 // the point closest to (|ideal_height|, |ideal_width|) is returned.
322 // * If two ideal values are given, they are used to determine a single ideal
323 // point, which can be one of:
324 // - (|ideal_height|, |ideal_width|),
325 // - (|ideal_height|, |ideal_height|*|ideal_aspect_ratio|), or
326 // - (|ideal_width| / |ideal_aspect_ratio|, |ideal_width|).
327 // The point in the set closest to the ideal point is returned.
328 // * If a single ideal value is given, a point in the set closest to the line
329 // defined by the ideal value is returned. If there is more than one point
330 // closest to the ideal line, the following tie-breaker rules are used:
331 // - If |ideal_height| is provided, the point closest to
332 // (|ideal_height|, |ideal_height| * default_aspect_ratio), where
333 // default_aspect_ratio is the result of the floating-point division
334 // |default_width|/|default_height|.
335 // - If |ideal_width| is provided, the point closest to
336 // (|ideal_width| / default_aspect_ratio, |ideal_width|).
337 // - If |ideal_aspect_ratio| is provided, the point with largest area among
338 // the points closest to
339 // (|default_height|, |default_height| * aspect_ratio) and
340 // (|default_width| / aspect_ratio, |default_width|),
341 // where aspect_ratio is |ideal_aspect_ratio| if the ideal line intersects
342 // the set, and the closest aspect ratio to |ideal_aspect_ratio| among the
343 // points in the set if no point in the set has an aspect ratio equal to
344 // |ideal_aspect_ratio|.
345 // * If no ideal value is given, proceed as if only |ideal_aspect_ratio| was
346 // provided with a value of default_aspect_ratio.
347 //
348 // This is intended to support the implementation of the spec algorithm for
349 // selection of track settings, as defined in
350 // https://w3c.github.io/mediacapture-main/#dfn-selectsettings.
351 //
352 // The main difference between this algorithm and the spec is that when ideal
353 // values are provided, the spec mandates finding a point that minimizes the
354 // sum of custom relative distances for each provided ideal value, while this
355 // algorithm minimizes either the Euclidean distance (sum of square distances)
356 // on a height-width plane for the cases where two or three ideal values are
357 // provided, or the absolute distance for the case with one ideal value.
358 // Also, in the case with three ideal values, this algorithm ignores the
359 // distance to the ideal aspect ratio.
360 // In most cases the difference in the final result should be negligible.
361 // The reason to follow this approach is that optimization in the worst case
362 // is reduced to projection of a point on line segment, which is a simple
363 // operation that provides exact results. Using the distance function of the
364 // spec, which is not continuous, would require complex optimization methods
365 // that do not necessarily guarantee finding the real optimal value.
366 //
367 // This function has undefined behavior if this set is empty.
368 Point SelectClosestPointToIdeal(
369 const MediaTrackConstraintSetPlatform& constraint_set,
370 int default_height,
371 int default_width) const;
372
373 // Utilities that return ResolutionSets constrained on a specific variable.
374 static ResolutionSet FromHeight(int min, int max);
375 static ResolutionSet FromExactHeight(int value);
376 static ResolutionSet FromWidth(int min, int max);
377 static ResolutionSet FromExactWidth(int value);
378 static ResolutionSet FromAspectRatio(double min, double max);
379 static ResolutionSet FromExactAspectRatio(double value);
380
381 // Returns a ResolutionSet containing only the specified width and height.
382 static ResolutionSet FromExactResolution(int width, int height);
383
384 // Returns a ResolutionCandidateSet initialized with |constraint_set|'s
385 // width, height and aspectRatio constraints.
386 static ResolutionSet FromConstraintSet(
387 const MediaTrackConstraintSetPlatform& constraint_set);
388
389 private:
390 FRIEND_TEST_ALL_PREFIXES(MediaStreamConstraintsUtilSetsTest,
391 ResolutionPointSetClosestPoint);
392 FRIEND_TEST_ALL_PREFIXES(MediaStreamConstraintsUtilSetsTest,
393 ResolutionLineSetClosestPoint);
394 FRIEND_TEST_ALL_PREFIXES(MediaStreamConstraintsUtilSetsTest,
395 ResolutionGeneralSetClosestPoint);
396 FRIEND_TEST_ALL_PREFIXES(MediaStreamConstraintsUtilSetsTest,
397 ResolutionIdealOutsideSinglePoint);
398 FRIEND_TEST_ALL_PREFIXES(MediaStreamConstraintsUtilSetsTest,
399 ResolutionVertices);
400
401 // Returns the closest point in this set to |point|. If |point| is included in
402 // this set, Point is returned. If this set is empty, behavior is undefined.
403 Point ClosestPointTo(const Point& point) const;
404
405 // Returns a list of the vertices defined by the constraints on a height-width
406 // Cartesian plane.
407 // If the list is empty, the set is empty.
408 // If the list contains a single point, the set contains a single point.
409 // If the list contains two points, the set is composed of points on a line
410 // segment.
411 // If the list contains three to six points, they are the vertices of a
412 // convex polygon containing all valid points in the set. Each pair of
413 // consecutive vertices (modulo the size of the list) corresponds to a side of
414 // the polygon, with the vertices given in counterclockwise order.
415 // The list cannot contain more than six points.
416 Vector<Point> ComputeVertices() const;
417
418 private:
419 // Implements SelectClosestPointToIdeal() for the case when only the ideal
420 // aspect ratio is provided.
421 Point SelectClosestPointToIdealAspectRatio(double ideal_aspect_ratio,
422 int default_height,
423 int default_width) const;
424
425 // Returns the vertices of the set that have the property accessed
426 // by |accessor| closest to |value|. The returned vector always has one or two
427 // elements. Behavior is undefined if the set is empty.
428 Vector<Point> GetClosestVertices(double (Point::*accessor)() const,
429 double value) const;
430
431 // Adds |point| to |vertices| if |point| is included in this candidate set.
432 void TryAddVertex(Vector<ResolutionSet::Point>* vertices,
433 const ResolutionSet::Point& point) const;
434
435 int min_height_;
436 int max_height_;
437 int min_width_;
438 int max_width_;
439 double min_aspect_ratio_;
440 double max_aspect_ratio_;
441 };
442
443 // Scalar multiplication for Points.
444 MODULES_EXPORT ResolutionSet::Point operator*(double d,
445 const ResolutionSet::Point& p);
446
447 // This function returns a set of bools from a resizeMode StringConstraint.
448 // If |resize_mode_constraint| includes
449 // WebMediaStreamTrack::kResizeModeNone, false is included in the
450 // returned value. If |resize_mode_constraint| includes
451 // WebMediaStreamTrack::kResizeModeRescale, true is included in the
452 // returned value.
453 MODULES_EXPORT DiscreteSet<bool> RescaleSetFromConstraint(
454 const StringConstraint& resize_mode_constraint);
455
456 } // namespace media_constraints
457 } // namespace blink
458
459 #endif // THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASTREAM_MEDIA_STREAM_CONSTRAINTS_UTIL_SETS_H_
460