OLD | NEW |
(Empty) | |
| 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 #include "content/renderer/media/media_stream_constraints_util_video_content.h" |
| 6 |
| 7 #include <cmath> |
| 8 #include <utility> |
| 9 #include <vector> |
| 10 |
| 11 #include "content/renderer/media/media_stream_constraints_util_sets.h" |
| 12 #include "content/renderer/media/media_stream_video_source.h" |
| 13 #include "media/base/limits.h" |
| 14 #include "third_party/WebKit/public/platform/WebMediaConstraints.h" |
| 15 #include "third_party/WebKit/public/platform/WebString.h" |
| 16 |
| 17 namespace content { |
| 18 |
| 19 namespace { |
| 20 |
| 21 using Point = ResolutionSet::Point; |
| 22 using StringSet = DiscreteSet<std::string>; |
| 23 using BoolSet = DiscreteSet<bool>; |
| 24 |
| 25 // Hard upper and lower bound frame rates for tab/desktop capture. |
| 26 constexpr double kMaxScreenCastFrameRate = 120.0; |
| 27 constexpr double kMinScreenCastFrameRate = 1.0 / 60.0; |
| 28 |
| 29 constexpr double kDefaultFrameRate = MediaStreamVideoSource::kDefaultFrameRate; |
| 30 |
| 31 constexpr int kMinScreenCastDimension = 1; |
| 32 constexpr int kMaxScreenCastDimension = media::limits::kMaxDimension - 1; |
| 33 constexpr double kMinScreenCastAspectRatio = |
| 34 static_cast<double>(kMinScreenCastDimension) / |
| 35 static_cast<double>(kMaxScreenCastDimension); |
| 36 constexpr double kMaxScreenCastAspectRatio = |
| 37 static_cast<double>(kMaxScreenCastDimension) / |
| 38 static_cast<double>(kMinScreenCastDimension); |
| 39 |
| 40 StringSet StringSetFromConstraint(const blink::StringConstraint& constraint) { |
| 41 if (!constraint.hasExact()) |
| 42 return StringSet::UniversalSet(); |
| 43 |
| 44 std::vector<std::string> elements; |
| 45 for (const auto& entry : constraint.exact()) |
| 46 elements.push_back(entry.ascii()); |
| 47 |
| 48 return StringSet(std::move(elements)); |
| 49 } |
| 50 |
| 51 BoolSet BoolSetFromConstraint(const blink::BooleanConstraint& constraint) { |
| 52 if (!constraint.hasExact()) |
| 53 return BoolSet::UniversalSet(); |
| 54 |
| 55 return BoolSet({constraint.exact()}); |
| 56 } |
| 57 |
| 58 using DoubleRangeSet = NumericRangeSet<double>; |
| 59 |
| 60 class VideoContentCaptureCandidates { |
| 61 public: |
| 62 VideoContentCaptureCandidates() |
| 63 : device_id_set(StringSet::UniversalSet()), |
| 64 noise_reduction_set(BoolSet::UniversalSet()) {} |
| 65 explicit VideoContentCaptureCandidates( |
| 66 const blink::WebMediaTrackConstraintSet& constraint_set) |
| 67 : resolution_set(ResolutionSet::FromConstraintSet(constraint_set)), |
| 68 frame_rate_set( |
| 69 DoubleRangeSet::FromConstraint(constraint_set.frameRate)), |
| 70 device_id_set(StringSetFromConstraint(constraint_set.deviceId)), |
| 71 noise_reduction_set( |
| 72 BoolSetFromConstraint(constraint_set.googNoiseReduction)) {} |
| 73 |
| 74 VideoContentCaptureCandidates(VideoContentCaptureCandidates&& other) = |
| 75 default; |
| 76 VideoContentCaptureCandidates& operator=( |
| 77 VideoContentCaptureCandidates&& other) = default; |
| 78 |
| 79 bool IsEmpty() const { |
| 80 return resolution_set.IsEmpty() || frame_rate_set.IsEmpty() || |
| 81 device_id_set.IsEmpty() || noise_reduction_set.IsEmpty(); |
| 82 } |
| 83 |
| 84 VideoContentCaptureCandidates Intersection( |
| 85 const VideoContentCaptureCandidates& other) { |
| 86 VideoContentCaptureCandidates intersection; |
| 87 intersection.resolution_set = |
| 88 resolution_set.Intersection(other.resolution_set); |
| 89 intersection.frame_rate_set = |
| 90 frame_rate_set.Intersection(other.frame_rate_set); |
| 91 intersection.device_id_set = |
| 92 device_id_set.Intersection(other.device_id_set); |
| 93 intersection.noise_reduction_set = |
| 94 noise_reduction_set.Intersection(other.noise_reduction_set); |
| 95 return intersection; |
| 96 } |
| 97 |
| 98 ResolutionSet resolution_set; |
| 99 DoubleRangeSet frame_rate_set; |
| 100 StringSet device_id_set; |
| 101 BoolSet noise_reduction_set; |
| 102 }; |
| 103 |
| 104 ResolutionSet ScreenCastResolutionCapabilities() { |
| 105 return ResolutionSet(kMinScreenCastDimension, kMaxScreenCastDimension, |
| 106 kMinScreenCastDimension, kMaxScreenCastDimension, |
| 107 kMinScreenCastAspectRatio, kMaxScreenCastAspectRatio); |
| 108 } |
| 109 |
| 110 // TODO(guidou): Update this policy to better match the way |
| 111 // WebContentsCaptureMachine::ComputeOptimalViewSize() interprets |
| 112 // resolution-change policies. See http://crbug.com/701302. |
| 113 media::ResolutionChangePolicy SelectResolutionPolicyFromCandidates( |
| 114 const ResolutionSet& resolution_set) { |
| 115 ResolutionSet capabilities = ScreenCastResolutionCapabilities(); |
| 116 bool can_adjust_resolution = |
| 117 resolution_set.min_height() <= capabilities.min_height() && |
| 118 resolution_set.max_height() >= capabilities.max_height() && |
| 119 resolution_set.min_width() <= capabilities.min_width() && |
| 120 resolution_set.max_width() >= capabilities.max_width() && |
| 121 resolution_set.min_aspect_ratio() <= capabilities.min_aspect_ratio() && |
| 122 resolution_set.max_aspect_ratio() >= capabilities.max_aspect_ratio(); |
| 123 |
| 124 return can_adjust_resolution ? media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT |
| 125 : media::RESOLUTION_POLICY_FIXED_RESOLUTION; |
| 126 } |
| 127 |
| 128 int RoundToInt(double d) { |
| 129 return static_cast<int>(std::round(d)); |
| 130 } |
| 131 |
| 132 gfx::Size ToGfxSize(const Point& point) { |
| 133 return gfx::Size(RoundToInt(point.width()), RoundToInt(point.height())); |
| 134 } |
| 135 |
| 136 double SelectFrameRateFromCandidates( |
| 137 const DoubleRangeSet& candidate_set, |
| 138 const blink::WebMediaTrackConstraintSet& basic_constraint_set) { |
| 139 double frame_rate = basic_constraint_set.frameRate.hasIdeal() |
| 140 ? basic_constraint_set.frameRate.ideal() |
| 141 : kDefaultFrameRate; |
| 142 if (frame_rate > candidate_set.Max()) |
| 143 frame_rate = candidate_set.Max(); |
| 144 else if (frame_rate < candidate_set.Min()) |
| 145 frame_rate = candidate_set.Min(); |
| 146 |
| 147 return frame_rate; |
| 148 } |
| 149 |
| 150 media::VideoCaptureParams SelectVideoCaptureParamsFromCandidates( |
| 151 const VideoContentCaptureCandidates& candidates, |
| 152 const blink::WebMediaTrackConstraintSet& basic_constraint_set) { |
| 153 double requested_frame_rate = SelectFrameRateFromCandidates( |
| 154 candidates.frame_rate_set, basic_constraint_set); |
| 155 Point requested_resolution = |
| 156 candidates.resolution_set.SelectClosestPointToIdeal(basic_constraint_set); |
| 157 media::VideoCaptureParams params; |
| 158 params.requested_format = media::VideoCaptureFormat( |
| 159 ToGfxSize(requested_resolution), static_cast<float>(requested_frame_rate), |
| 160 media::PIXEL_FORMAT_I420); |
| 161 params.resolution_change_policy = |
| 162 SelectResolutionPolicyFromCandidates(candidates.resolution_set); |
| 163 // Content capture always uses default power-line frequency. |
| 164 DCHECK(params.IsValid()); |
| 165 |
| 166 return params; |
| 167 } |
| 168 |
| 169 std::string SelectDeviceIDFromCandidates( |
| 170 const StringSet& candidates, |
| 171 const blink::WebMediaTrackConstraintSet& basic_constraint_set) { |
| 172 DCHECK(!candidates.IsEmpty()); |
| 173 if (basic_constraint_set.deviceId.hasIdeal()) { |
| 174 // If there are multiple elements specified by ideal, break ties by choosing |
| 175 // the first one that satisfies the constraints. |
| 176 for (const auto& ideal_entry : basic_constraint_set.deviceId.ideal()) { |
| 177 std::string ideal_value = ideal_entry.ascii(); |
| 178 if (candidates.Contains(ideal_value)) { |
| 179 return ideal_value; |
| 180 } |
| 181 } |
| 182 } |
| 183 |
| 184 // Return the empty string if nothing is specified in the constraints. |
| 185 // The empty string is treated as a default device ID by the browser. |
| 186 if (candidates.is_universal()) { |
| 187 return std::string(); |
| 188 } |
| 189 |
| 190 // If there are multiple elements that satisfy the constraints, break ties by |
| 191 // using the element that was specified first. |
| 192 return candidates.FirstElement(); |
| 193 } |
| 194 |
| 195 rtc::Optional<bool> SelectNoiseReductionFromCandidates( |
| 196 const BoolSet& candidates, |
| 197 const blink::WebMediaTrackConstraintSet& basic_constraint_set) { |
| 198 DCHECK(!candidates.IsEmpty()); |
| 199 if (basic_constraint_set.googNoiseReduction.hasIdeal() && |
| 200 candidates.Contains(basic_constraint_set.googNoiseReduction.ideal())) { |
| 201 return rtc::Optional<bool>(basic_constraint_set.googNoiseReduction.ideal()); |
| 202 } |
| 203 |
| 204 if (candidates.is_universal()) |
| 205 return rtc::Optional<bool>(); |
| 206 |
| 207 // A non-universal BoolSet can have at most one element. |
| 208 return rtc::Optional<bool>(candidates.FirstElement()); |
| 209 } |
| 210 |
| 211 VideoContentCaptureSourceSelectionResult SelectResultFromCandidates( |
| 212 const VideoContentCaptureCandidates& candidates, |
| 213 const blink::WebMediaTrackConstraintSet& basic_constraint_set) { |
| 214 std::string device_id = SelectDeviceIDFromCandidates(candidates.device_id_set, |
| 215 basic_constraint_set); |
| 216 media::VideoCaptureParams capture_params = |
| 217 SelectVideoCaptureParamsFromCandidates(candidates, basic_constraint_set); |
| 218 |
| 219 rtc::Optional<bool> noise_reduction = SelectNoiseReductionFromCandidates( |
| 220 candidates.noise_reduction_set, basic_constraint_set); |
| 221 |
| 222 return VideoContentCaptureSourceSelectionResult( |
| 223 std::move(device_id), noise_reduction, capture_params); |
| 224 } |
| 225 |
| 226 VideoContentCaptureSourceSelectionResult UnsatisfiedConstraintsResult( |
| 227 const VideoContentCaptureCandidates& candidates, |
| 228 const blink::WebMediaTrackConstraintSet& constraint_set) { |
| 229 DCHECK(candidates.IsEmpty()); |
| 230 if (candidates.resolution_set.IsHeightEmpty()) { |
| 231 return VideoContentCaptureSourceSelectionResult( |
| 232 constraint_set.height.name()); |
| 233 } else if (candidates.resolution_set.IsWidthEmpty()) { |
| 234 return VideoContentCaptureSourceSelectionResult( |
| 235 constraint_set.width.name()); |
| 236 } else if (candidates.resolution_set.IsAspectRatioEmpty()) { |
| 237 return VideoContentCaptureSourceSelectionResult( |
| 238 constraint_set.aspectRatio.name()); |
| 239 } else if (candidates.frame_rate_set.IsEmpty()) { |
| 240 return VideoContentCaptureSourceSelectionResult( |
| 241 constraint_set.frameRate.name()); |
| 242 } else if (candidates.noise_reduction_set.IsEmpty()) { |
| 243 return VideoContentCaptureSourceSelectionResult( |
| 244 constraint_set.googNoiseReduction.name()); |
| 245 } else { |
| 246 DCHECK(candidates.device_id_set.IsEmpty()); |
| 247 return VideoContentCaptureSourceSelectionResult( |
| 248 constraint_set.deviceId.name()); |
| 249 } |
| 250 } |
| 251 |
| 252 } // namespace |
| 253 |
| 254 VideoContentCaptureSourceSelectionResult:: |
| 255 VideoContentCaptureSourceSelectionResult(const char* failed_constraint_name) |
| 256 : failed_constraint_name_(failed_constraint_name) {} |
| 257 |
| 258 VideoContentCaptureSourceSelectionResult:: |
| 259 VideoContentCaptureSourceSelectionResult( |
| 260 std::string device_id, |
| 261 const rtc::Optional<bool>& noise_reduction, |
| 262 media::VideoCaptureParams capture_params) |
| 263 : failed_constraint_name_(nullptr), |
| 264 device_id_(std::move(device_id)), |
| 265 noise_reduction_(noise_reduction), |
| 266 capture_params_(capture_params) {} |
| 267 |
| 268 VideoContentCaptureSourceSelectionResult:: |
| 269 VideoContentCaptureSourceSelectionResult( |
| 270 const VideoContentCaptureSourceSelectionResult& other) = default; |
| 271 VideoContentCaptureSourceSelectionResult:: |
| 272 VideoContentCaptureSourceSelectionResult( |
| 273 VideoContentCaptureSourceSelectionResult&& other) = default; |
| 274 VideoContentCaptureSourceSelectionResult:: |
| 275 ~VideoContentCaptureSourceSelectionResult() = default; |
| 276 VideoContentCaptureSourceSelectionResult& |
| 277 VideoContentCaptureSourceSelectionResult::operator=( |
| 278 const VideoContentCaptureSourceSelectionResult& other) = default; |
| 279 VideoContentCaptureSourceSelectionResult& |
| 280 VideoContentCaptureSourceSelectionResult::operator=( |
| 281 VideoContentCaptureSourceSelectionResult&& other) = default; |
| 282 |
| 283 int VideoContentCaptureSourceSelectionResult::Height() const { |
| 284 DCHECK(HasValue()); |
| 285 return capture_params_.requested_format.frame_size.height(); |
| 286 } |
| 287 |
| 288 int VideoContentCaptureSourceSelectionResult::Width() const { |
| 289 DCHECK(HasValue()); |
| 290 return capture_params_.requested_format.frame_size.width(); |
| 291 } |
| 292 |
| 293 float VideoContentCaptureSourceSelectionResult::FrameRate() const { |
| 294 DCHECK(HasValue()); |
| 295 return capture_params_.requested_format.frame_rate; |
| 296 } |
| 297 |
| 298 media::ResolutionChangePolicy |
| 299 VideoContentCaptureSourceSelectionResult::ResolutionChangePolicy() const { |
| 300 DCHECK(HasValue()); |
| 301 return capture_params_.resolution_change_policy; |
| 302 } |
| 303 |
| 304 VideoContentCaptureSourceSelectionResult |
| 305 SelectVideoContentCaptureSourceSettings( |
| 306 const blink::WebMediaConstraints& constraints) { |
| 307 VideoContentCaptureCandidates candidates; |
| 308 candidates.resolution_set = ScreenCastResolutionCapabilities(); |
| 309 candidates.frame_rate_set = |
| 310 DoubleRangeSet(kMinScreenCastFrameRate, kMaxScreenCastFrameRate); |
| 311 // candidates.device_id_set and candidates.noise_reduction_set are |
| 312 // automatically initialized with the universal set. |
| 313 |
| 314 candidates = candidates.Intersection( |
| 315 VideoContentCaptureCandidates(constraints.basic())); |
| 316 if (candidates.IsEmpty()) |
| 317 return UnsatisfiedConstraintsResult(candidates, constraints.basic()); |
| 318 |
| 319 for (const auto& advanced_set : constraints.advanced()) { |
| 320 VideoContentCaptureCandidates advanced_candidates(advanced_set); |
| 321 VideoContentCaptureCandidates intersection = |
| 322 candidates.Intersection(advanced_candidates); |
| 323 if (!intersection.IsEmpty()) |
| 324 candidates = std::move(intersection); |
| 325 } |
| 326 |
| 327 DCHECK(!candidates.IsEmpty()); |
| 328 return SelectResultFromCandidates(candidates, constraints.basic()); |
| 329 } |
| 330 |
| 331 } // namespace content |
OLD | NEW |