Index: content/renderer/media/media_stream_constraints_util_video_content.cc |
diff --git a/content/renderer/media/media_stream_constraints_util_video_content.cc b/content/renderer/media/media_stream_constraints_util_video_content.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b0082d66fa40d15ecb447b9462e2bcc7a6b32bf6 |
--- /dev/null |
+++ b/content/renderer/media/media_stream_constraints_util_video_content.cc |
@@ -0,0 +1,349 @@ |
+// Copyright 2017 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "content/renderer/media/media_stream_constraints_util_video_content.h" |
+ |
+#include <cmath> |
+#include <utility> |
+#include <vector> |
+ |
+#include "content/renderer/media/media_stream_constraints_util_sets.h" |
+#include "content/renderer/media/media_stream_video_source.h" |
+#include "media/base/limits.h" |
+#include "third_party/WebKit/public/platform/WebMediaConstraints.h" |
+#include "third_party/WebKit/public/platform/WebString.h" |
+ |
+namespace content { |
+ |
+namespace { |
+ |
+using Point = ResolutionSet::Point; |
+ |
+// Hard upper and lower bound frame rates for tab/desktop capture. |
+constexpr double kMaxScreenCastFrameRate = 120.0; |
+constexpr double kMinScreenCastFrameRate = 1.0 / 60.0; |
hbos_chromium
2017/03/09 16:51:10
This is what I call moving pictures! :)
Guido Urdaneta
2017/03/14 12:29:05
Acknowledged.
|
+ |
+constexpr double kDefaultFrameRate = MediaStreamVideoSource::kDefaultFrameRate; |
+ |
+constexpr int kMinScreenCastDimension = 1; |
+constexpr int kMaxScreenCastDimension = media::limits::kMaxDimension - 1; |
+constexpr double kMinScreenCastAspectRatio = |
+ static_cast<double>(kMinScreenCastDimension) / |
+ static_cast<double>(kMaxScreenCastDimension); |
+constexpr double kMaxScreenCastAspectRatio = |
+ static_cast<double>(kMaxScreenCastDimension) / |
+ static_cast<double>(kMinScreenCastDimension); |
+ |
+using StringSet = DiscreteSet<std::string>; |
hbos_chromium
2017/03/09 16:51:09
Move all using statements to before functions are
Guido Urdaneta
2017/03/14 12:29:06
Done.
|
+StringSet StringSetFromConstraint(const blink::StringConstraint& constraint) { |
+ if (!constraint.hasExact()) |
+ return StringSet::UniversalSet(); |
+ |
+ std::vector<std::string> elements; |
+ for (const auto& entry : constraint.exact()) |
+ elements.push_back(entry.ascii()); |
+ |
+ return StringSet(std::move(elements)); |
+} |
+ |
+using BoolSet = DiscreteSet<bool>; |
+BoolSet BoolSetFromConstraint(const blink::BooleanConstraint& constraint) { |
+ if (!constraint.hasExact()) |
+ return BoolSet::UniversalSet(); |
+ |
+ return BoolSet({constraint.exact()}); |
+} |
+ |
+using DoubleRangeSet = NumericRangeSet<double>; |
+ |
+class VideoContentCaptureCandidates { |
+ public: |
+ VideoContentCaptureCandidates() |
+ : device_id_set(StringSet::UniversalSet()), |
+ noise_reduction_set(BoolSet::UniversalSet()) {} |
+ explicit VideoContentCaptureCandidates( |
+ const blink::WebMediaTrackConstraintSet& constraint_set) |
+ : resolution_set(ResolutionSet::FromConstraintSet(constraint_set)), |
+ frame_rate_set( |
+ DoubleRangeSet::FromConstraint(constraint_set.frameRate)), |
+ device_id_set(StringSetFromConstraint(constraint_set.deviceId)), |
+ noise_reduction_set( |
+ BoolSetFromConstraint(constraint_set.googNoiseReduction)) {} |
+ |
+ VideoContentCaptureCandidates(VideoContentCaptureCandidates&& other) = |
+ default; |
+ VideoContentCaptureCandidates& operator=( |
+ VideoContentCaptureCandidates&& other) = default; |
+ |
+ bool IsEmpty() const { |
+ return resolution_set.IsEmpty() || frame_rate_set.IsEmpty() || |
+ device_id_set.IsEmpty() || noise_reduction_set.IsEmpty(); |
+ } |
+ |
+ VideoContentCaptureCandidates Intersection( |
+ const VideoContentCaptureCandidates& other) { |
+ VideoContentCaptureCandidates intersection; |
+ intersection.resolution_set = |
+ resolution_set.Intersection(other.resolution_set); |
+ intersection.frame_rate_set = |
+ frame_rate_set.Intersection(other.frame_rate_set); |
+ intersection.device_id_set = |
+ device_id_set.Intersection(other.device_id_set); |
+ intersection.noise_reduction_set = |
+ noise_reduction_set.Intersection(other.noise_reduction_set); |
+ return intersection; |
+ } |
+ |
+ ResolutionSet resolution_set; |
+ DoubleRangeSet frame_rate_set; |
+ StringSet device_id_set; |
+ BoolSet noise_reduction_set; |
+}; |
+ |
+media::ResolutionChangePolicy SelectResolutionPolicyFromCandidates( |
hbos_chromium
2017/03/09 16:51:09
Can you add a comment? I'm not sure I understand t
Guido Urdaneta
2017/03/14 12:29:06
Changed the policy to be simpler and added a TODO
|
+ const ResolutionSet& resolution_set, |
+ int chosen_height, |
+ int chosen_width) { |
+ // TODO(guidou): Most of this code is copied from |
+ // WebContentsCaptureMachine::ComputeOptimalViewSize(). Find a way to more |
+ // easily share this code. |
+ const auto HasIntendedAspectRatio = [chosen_height, chosen_width]( |
+ int width_units, int height_units) { |
+ const int a = height_units * chosen_width; |
+ const int b = width_units * chosen_height; |
+ const int percentage_diff = 100 * std::abs((a - b)) / b; |
+ return percentage_diff <= 1; // Effectively, anything strictly <2%. |
hbos_chromium
2017/03/09 16:51:09
Why <2%? Why not exactly (within numerical toleran
Guido Urdaneta
2017/03/14 12:29:06
See previous comment.
|
+ }; |
+ const auto RoundToExactAspectRatio = [chosen_height, chosen_width]( |
+ int width_step, int height_step) { |
+ const int adjusted_height = |
+ std::max(chosen_height - (chosen_height % height_step), height_step); |
+ DCHECK_EQ((adjusted_height * width_step) % height_step, 0); |
+ return gfx::Size(adjusted_height * width_step / height_step, |
+ adjusted_height); |
+ }; |
+ |
+ gfx::Size adjusted_size; |
+ if (HasIntendedAspectRatio(16, 9)) { |
+ adjusted_size = RoundToExactAspectRatio(160, 90); |
+ } else if (HasIntendedAspectRatio(4, 3)) { |
+ adjusted_size = RoundToExactAspectRatio(64, 48); |
hbos_chromium
2017/03/09 16:51:09
Why these step sizes and not the smallest possible
Guido Urdaneta
2017/03/14 12:29:06
See previous comment.
|
+ } else { |
+ // There will be no adjustment, so any policy value will do. |
+ return media::RESOLUTION_POLICY_FIXED_RESOLUTION; |
+ } |
+ |
+ double adjusted_aspect_ratio = static_cast<double>(adjusted_size.width()) / |
+ static_cast<double>(adjusted_size.height()); |
+ if (adjusted_aspect_ratio >= resolution_set.min_aspect_ratio() && |
+ adjusted_aspect_ratio <= resolution_set.max_aspect_ratio() && |
+ adjusted_size.width() >= resolution_set.min_width() && |
+ adjusted_size.width() <= resolution_set.max_width() && |
+ adjusted_size.height() >= resolution_set.min_height() && |
+ adjusted_size.height() <= resolution_set.max_height()) { |
+ return media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT; |
+ } |
+ return media::RESOLUTION_POLICY_FIXED_RESOLUTION; |
+} |
+ |
+int RoundToInt(double d) { |
+ return static_cast<int>(std::round(d)); |
+} |
+ |
+gfx::Size ToGfxSize(const Point& point) { |
+ return gfx::Size(RoundToInt(point.width()), RoundToInt(point.height())); |
+} |
+ |
+double SelectFrameRateFromCandidates( |
+ const DoubleRangeSet& candidate_set, |
+ const blink::WebMediaTrackConstraintSet& basic_constraint_set) { |
+ double frame_rate = basic_constraint_set.frameRate.hasIdeal() |
+ ? basic_constraint_set.frameRate.ideal() |
+ : kDefaultFrameRate; |
+ if (frame_rate > candidate_set.Max()) |
+ frame_rate = candidate_set.Max(); |
+ else if (frame_rate < candidate_set.Min()) |
+ frame_rate = candidate_set.Min(); |
+ |
+ return frame_rate; |
+} |
+ |
+media::VideoCaptureParams SelectVideoCaptureParamsFromCandidates( |
+ const VideoContentCaptureCandidates& candidates, |
+ const blink::WebMediaTrackConstraintSet& basic_constraint_set) { |
+ double requested_frame_rate = SelectFrameRateFromCandidates( |
+ candidates.frame_rate_set, basic_constraint_set); |
+ Point requested_resolution = |
+ candidates.resolution_set.SelectClosestPointToIdeal(basic_constraint_set); |
+ media::VideoCaptureParams params; |
+ params.requested_format = media::VideoCaptureFormat( |
+ ToGfxSize(requested_resolution), static_cast<float>(requested_frame_rate), |
+ media::PIXEL_FORMAT_I420); |
+ params.resolution_change_policy = SelectResolutionPolicyFromCandidates( |
+ candidates.resolution_set, params.requested_format.frame_size.height(), |
+ params.requested_format.frame_size.width()); |
+ DCHECK(params.IsValid()); |
hbos_chromium
2017/03/09 16:51:10
Note: Will always use default PowerLineFrequency.
Guido Urdaneta
2017/03/14 12:29:05
Yes. PowerLineFrequency is ignored for content cap
|
+ |
+ return params; |
+} |
+ |
+std::string SelectDeviceIDFromCandidates( |
+ const StringSet& candidates, |
+ const blink::WebMediaTrackConstraintSet& basic_constraint_set) { |
+ DCHECK(!candidates.IsEmpty()); |
+ if (basic_constraint_set.deviceId.hasIdeal()) { |
+ for (const auto& ideal_entry : basic_constraint_set.deviceId.ideal()) { |
+ std::string ideal_value = ideal_entry.ascii(); |
+ if (candidates.Contains(ideal_value)) { |
+ return ideal_value; |
+ } |
+ } |
+ } |
+ |
+ if (candidates.is_universal()) { |
+ return std::string(); |
hbos_chromium
2017/03/09 16:51:10
Should this return Optional<std::string> instead?
Guido Urdaneta
2017/03/14 12:29:05
No. The default value for device_id is the empty s
|
+ } |
+ |
+ return candidates.FirstElement(); |
hbos_chromium
2017/03/09 16:51:09
Is the preference for the first device id intentio
Guido Urdaneta
2017/03/14 12:29:06
Added comment here and in the other call to FirstE
|
+} |
+ |
+rtc::Optional<bool> SelectNoiseReductionFromCandidates( |
+ const BoolSet& candidates, |
+ const blink::WebMediaTrackConstraintSet& basic_constraint_set) { |
+ DCHECK(!candidates.IsEmpty()); |
+ if (basic_constraint_set.googNoiseReduction.hasIdeal() && |
+ candidates.Contains(basic_constraint_set.googNoiseReduction.ideal())) { |
+ return rtc::Optional<bool>(basic_constraint_set.googNoiseReduction.ideal()); |
+ } |
+ |
+ if (candidates.is_universal()) |
+ return rtc::Optional<bool>(); |
+ |
+ return rtc::Optional<bool>(candidates.FirstElement()); |
+} |
+ |
+VideoContentCaptureSourceSelectionResult SelectResultFromCandidates( |
+ const VideoContentCaptureCandidates& candidates, |
+ const blink::WebMediaTrackConstraintSet& basic_constraint_set) { |
+ std::string device_id = SelectDeviceIDFromCandidates(candidates.device_id_set, |
+ basic_constraint_set); |
+ media::VideoCaptureParams capture_params = |
+ SelectVideoCaptureParamsFromCandidates(candidates, basic_constraint_set); |
+ |
+ rtc::Optional<bool> noise_reduction = SelectNoiseReductionFromCandidates( |
+ candidates.noise_reduction_set, basic_constraint_set); |
+ |
+ return VideoContentCaptureSourceSelectionResult( |
+ std::move(device_id), noise_reduction, capture_params); |
hbos_chromium
2017/03/09 16:51:09
What if device_id is the empty string? Shouldn't t
Guido Urdaneta
2017/03/14 12:29:05
Empty string is treated like any other ID, just li
|
+} |
+ |
+VideoContentCaptureSourceSelectionResult UnsatisfiedConstraintsResult( |
+ const VideoContentCaptureCandidates& candidates, |
+ const blink::WebMediaTrackConstraintSet& constraint_set) { |
+ DCHECK(candidates.IsEmpty()); |
+ if (candidates.resolution_set.IsHeightEmpty()) { |
+ return VideoContentCaptureSourceSelectionResult( |
+ constraint_set.height.name()); |
+ } else if (candidates.resolution_set.IsWidthEmpty()) { |
+ return VideoContentCaptureSourceSelectionResult( |
+ constraint_set.width.name()); |
+ } else if (candidates.resolution_set.IsAspectRatioEmpty()) { |
+ return VideoContentCaptureSourceSelectionResult( |
+ constraint_set.aspectRatio.name()); |
+ } else if (candidates.frame_rate_set.IsEmpty()) { |
+ return VideoContentCaptureSourceSelectionResult( |
+ constraint_set.frameRate.name()); |
+ } else if (candidates.noise_reduction_set.IsEmpty()) { |
+ return VideoContentCaptureSourceSelectionResult( |
+ constraint_set.googNoiseReduction.name()); |
+ } else { |
+ DCHECK(candidates.device_id_set.IsEmpty()); |
+ return VideoContentCaptureSourceSelectionResult( |
+ constraint_set.deviceId.name()); |
+ } |
+} |
+ |
+} // namespace |
+ |
+VideoContentCaptureSourceSelectionResult:: |
+ VideoContentCaptureSourceSelectionResult(const char* failed_constraint_name) |
+ : failed_constraint_name_(failed_constraint_name) {} |
+ |
+VideoContentCaptureSourceSelectionResult:: |
+ VideoContentCaptureSourceSelectionResult( |
+ std::string device_id, |
+ const rtc::Optional<bool>& noise_reduction, |
+ media::VideoCaptureParams capture_params) |
+ : failed_constraint_name_(nullptr), |
+ device_id_(std::move(device_id)), |
+ noise_reduction_(noise_reduction), |
+ capture_params_(capture_params) {} |
+ |
+VideoContentCaptureSourceSelectionResult:: |
+ VideoContentCaptureSourceSelectionResult( |
+ const VideoContentCaptureSourceSelectionResult& other) = default; |
+VideoContentCaptureSourceSelectionResult:: |
+ VideoContentCaptureSourceSelectionResult( |
+ VideoContentCaptureSourceSelectionResult&& other) = default; |
+VideoContentCaptureSourceSelectionResult:: |
+ ~VideoContentCaptureSourceSelectionResult() = default; |
+VideoContentCaptureSourceSelectionResult& |
+VideoContentCaptureSourceSelectionResult::operator=( |
+ const VideoContentCaptureSourceSelectionResult& other) = default; |
+VideoContentCaptureSourceSelectionResult& |
+VideoContentCaptureSourceSelectionResult::operator=( |
+ VideoContentCaptureSourceSelectionResult&& other) = default; |
+ |
+int VideoContentCaptureSourceSelectionResult::Height() const { |
+ DCHECK(HasValue()); |
+ return capture_params_.requested_format.frame_size.height(); |
+} |
+ |
+int VideoContentCaptureSourceSelectionResult::Width() const { |
+ DCHECK(HasValue()); |
+ return capture_params_.requested_format.frame_size.width(); |
+} |
+ |
+float VideoContentCaptureSourceSelectionResult::FrameRate() const { |
+ DCHECK(HasValue()); |
+ return capture_params_.requested_format.frame_rate; |
+} |
+ |
+media::ResolutionChangePolicy |
+VideoContentCaptureSourceSelectionResult::ResolutionChangePolicy() const { |
+ DCHECK(HasValue()); |
+ return capture_params_.resolution_change_policy; |
+} |
+ |
+VideoContentCaptureSourceSelectionResult |
+SelectVideoContentCaptureSourceSettings( |
+ const blink::WebMediaConstraints& constraints) { |
+ VideoContentCaptureCandidates candidates; |
+ candidates.resolution_set = |
+ ResolutionSet(kMinScreenCastDimension, kMaxScreenCastDimension, |
+ kMinScreenCastDimension, kMaxScreenCastDimension, |
+ kMinScreenCastAspectRatio, kMaxScreenCastAspectRatio); |
+ candidates.frame_rate_set = |
+ DoubleRangeSet(kMinScreenCastFrameRate, kMaxScreenCastFrameRate); |
+ // candidates.device_id_set and candidates.noise_reduction_set are |
+ // automatically initialized with the universal set. |
+ |
+ candidates = candidates.Intersection( |
+ VideoContentCaptureCandidates(constraints.basic())); |
+ if (candidates.IsEmpty()) |
+ return UnsatisfiedConstraintsResult(candidates, constraints.basic()); |
+ |
+ for (const auto& advanced_set : constraints.advanced()) { |
+ VideoContentCaptureCandidates advanced_candidates(advanced_set); |
+ VideoContentCaptureCandidates intersection = |
+ candidates.Intersection(advanced_candidates); |
+ if (!intersection.IsEmpty()) |
+ candidates = std::move(intersection); |
+ } |
+ |
+ DCHECK(!candidates.IsEmpty()); |
+ return SelectResultFromCandidates(candidates, constraints.basic()); |
+} |
+ |
+} // namespace content |