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