| 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..e13c2439c3a497d45d988f851d942fcadf11b663
|
| --- /dev/null
|
| +++ b/content/renderer/media/media_stream_constraints_util_video_content.cc
|
| @@ -0,0 +1,331 @@
|
| +// 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;
|
| +using StringSet = DiscreteSet<std::string>;
|
| +using BoolSet = DiscreteSet<bool>;
|
| +
|
| +// Hard upper and lower bound frame rates for tab/desktop capture.
|
| +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;
|
| +constexpr double kMinScreenCastAspectRatio =
|
| + static_cast<double>(kMinScreenCastDimension) /
|
| + static_cast<double>(kMaxScreenCastDimension);
|
| +constexpr double kMaxScreenCastAspectRatio =
|
| + static_cast<double>(kMaxScreenCastDimension) /
|
| + static_cast<double>(kMinScreenCastDimension);
|
| +
|
| +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));
|
| +}
|
| +
|
| +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;
|
| +};
|
| +
|
| +ResolutionSet ScreenCastResolutionCapabilities() {
|
| + return ResolutionSet(kMinScreenCastDimension, kMaxScreenCastDimension,
|
| + kMinScreenCastDimension, kMaxScreenCastDimension,
|
| + kMinScreenCastAspectRatio, kMaxScreenCastAspectRatio);
|
| +}
|
| +
|
| +// TODO(guidou): Update this policy to better match the way
|
| +// WebContentsCaptureMachine::ComputeOptimalViewSize() interprets
|
| +// resolution-change policies. See http://crbug.com/701302.
|
| +media::ResolutionChangePolicy SelectResolutionPolicyFromCandidates(
|
| + const ResolutionSet& resolution_set) {
|
| + ResolutionSet capabilities = ScreenCastResolutionCapabilities();
|
| + bool can_adjust_resolution =
|
| + resolution_set.min_height() <= capabilities.min_height() &&
|
| + resolution_set.max_height() >= capabilities.max_height() &&
|
| + resolution_set.min_width() <= capabilities.min_width() &&
|
| + resolution_set.max_width() >= capabilities.max_width() &&
|
| + resolution_set.min_aspect_ratio() <= capabilities.min_aspect_ratio() &&
|
| + resolution_set.max_aspect_ratio() >= capabilities.max_aspect_ratio();
|
| +
|
| + return can_adjust_resolution ? media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT
|
| + : 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);
|
| + // Content capture always uses default power-line frequency.
|
| + 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()) {
|
| + // If there are multiple elements specified by ideal, break ties by choosing
|
| + // the first one that satisfies the constraints.
|
| + 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;
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Return the empty string if nothing is specified in the constraints.
|
| + // The empty string is treated as a default device ID by the browser.
|
| + if (candidates.is_universal()) {
|
| + return std::string();
|
| + }
|
| +
|
| + // If there are multiple elements that satisfy the constraints, break ties by
|
| + // using the element that was specified first.
|
| + 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>();
|
| +
|
| + // A non-universal BoolSet can have at most one element.
|
| + 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);
|
| +}
|
| +
|
| +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 = ScreenCastResolutionCapabilities();
|
| + 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
|
|
|