Chromium Code Reviews| 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..4449346aa389c5fc0818b65ec8e66d10401bf6aa |
| --- /dev/null |
| +++ b/content/renderer/media/media_stream_constraints_util_video_content.cc |
| @@ -0,0 +1,324 @@ |
| +// 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. |
|
hta - Chromium
2017/03/08 13:34:08
Nit: Dashes shouldn't be here. "Hard upper and low
Guido Urdaneta
2017/03/09 14:38:38
Done.
|
| +constexpr double kMaxScreenCastFrameRate = 120.0; |
| +constexpr double kMinScreenCastFrameRate = 1.0 / 60.0; |
| + |
| +constexpr double kDefaultFrameRate = MediaStreamVideoSource::kDefaultFrameRate; |
| + |
| +constexpr int kMinScreenCastDimension = 1; |
| +constexpr int kMaxScreenCastDimension = media::limits::kMaxDimension - 1; |
|
hta - Chromium
2017/03/08 13:34:08
Still wonder why you do -1 on this number so that
Guido Urdaneta
2017/03/09 14:38:38
The actual limit is kMaxDimension-1.
See https:/
|
| +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>; |
| +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( |
| + const ResolutionSet& resolution_set, |
| + const blink::WebMediaTrackConstraintSet& basic_constraint_set, |
| + int min_height_capability, |
| + int max_height_capability, |
| + int min_width_capability, |
| + int max_width_capability) { |
| + double min_aspect_ratio_capability = |
| + static_cast<double>(min_width_capability) / |
| + static_cast<double>(max_height_capability); |
| + double max_aspect_ratio_capability = |
| + static_cast<double>(max_width_capability) / |
| + static_cast<double>(min_height_capability); |
| + bool can_adjust_height = !basic_constraint_set.height.hasIdeal() && |
| + resolution_set.min_height() <= min_height_capability; |
| + |
| + bool can_adjust_width = !basic_constraint_set.width.hasIdeal() && |
| + resolution_set.min_width() <= min_width_capability; |
| + |
| + bool can_adjust_aspect_ratio = |
| + !basic_constraint_set.aspectRatio.hasIdeal() && |
|
hta - Chromium
2017/03/08 13:34:08
Here (and above) you're saying that if ideal is sp
Guido Urdaneta
2017/03/09 14:38:38
After looking at what the policies actually do, us
|
| + resolution_set.min_aspect_ratio() <= min_aspect_ratio_capability && |
| + resolution_set.max_aspect_ratio() >= max_aspect_ratio_capability; |
| + if (can_adjust_height && can_adjust_width) { |
| + return can_adjust_aspect_ratio |
| + ? media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT |
| + : media::RESOLUTION_POLICY_FIXED_ASPECT_RATIO; |
| + } |
| + |
| + 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, basic_constraint_set, kMinScreenCastDimension, |
| + params.requested_format.frame_size.height(), kMinScreenCastDimension, |
| + params.requested_format.frame_size.width()); |
| + DCHECK(params.IsValid()); |
| + |
| + return params; |
| +} |
| + |
| +std::string SelectDeviceIDFromCandidates( |
| + const StringSet& candidates, |
| + const blink::WebMediaTrackConstraintSet& basic_constraint_set) { |
| + DCHECK(!candidates.IsEmpty()); |
| + if (basic_constraint_set.deviceId.hasIdeal()) { |
| + std::string ideal_value = |
|
hta - Chromium
2017/03/08 13:34:08
Should you loop over deviceId.ideal() here?
for (
Guido Urdaneta
2017/03/09 14:38:38
Done. Also added test for this.
|
| + basic_constraint_set.deviceId.ideal().begin()->ascii(); |
| + if (candidates.Contains(ideal_value)) { |
| + return ideal_value; |
| + } |
| + } |
| + |
| + if (candidates.is_universal()) { |
| + return std::string(); |
| + } |
| + |
| + return candidates.FirstElement(); |
| +} |
| + |
| +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) { |
| + VideoContentCaptureSourceSelectionResult result; |
| + result.failed_constraint_name = nullptr; |
| + result.capture_params = |
| + SelectVideoCaptureParamsFromCandidates(candidates, basic_constraint_set); |
| + result.device_id = SelectDeviceIDFromCandidates(candidates.device_id_set, |
| + basic_constraint_set); |
| + result.noise_reduction = SelectNoiseReductionFromCandidates( |
| + candidates.noise_reduction_set, basic_constraint_set); |
| + return result; |
| +} |
| + |
| +VideoContentCaptureSourceSelectionResult UnsatisfiedConstraintsResult( |
| + const VideoContentCaptureCandidates& candidates, |
| + const blink::WebMediaTrackConstraintSet& constraint_set) { |
| + DCHECK(candidates.IsEmpty()); |
| + VideoContentCaptureSourceSelectionResult result; |
| + if (candidates.resolution_set.IsHeightEmpty()) { |
| + result.failed_constraint_name = constraint_set.height.name(); |
|
hta - Chromium
2017/03/08 13:34:08
Would it be as clear to have a constructor for res
Guido Urdaneta
2017/03/09 14:38:38
Done.
|
| + } else if (candidates.resolution_set.IsWidthEmpty()) { |
| + result.failed_constraint_name = constraint_set.width.name(); |
| + } else if (candidates.resolution_set.IsAspectRatioEmpty()) { |
| + result.failed_constraint_name = constraint_set.aspectRatio.name(); |
| + } else if (candidates.frame_rate_set.IsEmpty()) { |
| + result.failed_constraint_name = constraint_set.frameRate.name(); |
| + } else if (candidates.noise_reduction_set.IsEmpty()) { |
| + result.failed_constraint_name = constraint_set.googNoiseReduction.name(); |
| + } else { |
| + DCHECK(candidates.device_id_set.IsEmpty()); |
| + result.failed_constraint_name = constraint_set.frameRate.name(); |
|
hta - Chromium
2017/03/08 13:34:08
This choice seems odd - checking on device_id_iset
Guido Urdaneta
2017/03/09 14:38:38
Fixed. In practice this can't be reached because a
|
| + } |
| + |
| + return result; |
| +} |
| + |
| +} // namespace |
| + |
| +VideoContentCaptureSourceSelectionResult:: |
| + VideoContentCaptureSourceSelectionResult() |
| + : failed_constraint_name("") {} |
| +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; |
| + |
| +bool VideoContentCaptureSourceSelectionResult::HasValue() const { |
| + return failed_constraint_name == nullptr; |
| +} |
| + |
| +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 |