blob: d9712215658c5ed2c138cac8af2d6b48bc675ac0 [file] [log] [blame]
// 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/stream/media_stream_constraints_util_video_device.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include <utility>
#include <vector>
#include "content/renderer/media/stream/media_stream_constraints_util.h"
#include "content/renderer/media/stream/media_stream_constraints_util_sets.h"
#include "content/renderer/media/stream/media_stream_video_source.h"
#include "media/base/display_media_information.h"
#include "media/base/limits.h"
#include "third_party/blink/public/platform/web_media_constraints.h"
#include "third_party/blink/public/platform/web_string.h"
namespace content {
namespace {
using ResolutionSet = media_constraints::ResolutionSet;
using DoubleRangeSet = media_constraints::NumericRangeSet<double>;
// Number of default settings to be used as final tie-breaking criteria for
// settings that are equally good at satisfying constraints:
// device ID, power-line frequency, noise reduction, resolution and frame rate.
const int kNumDefaultDistanceEntries = 5;
// The minimum aspect ratio to be supported by sources.
const double kMinSourceAspectRatio = 0.05;
// VideoKind enum values. See https://w3c.github.io/mediacapture-depth.
const char kVideoKindColor[] = "color";
const char kVideoKindDepth[] = "depth";
blink::WebString ToWebString(media::VideoFacingMode facing_mode) {
switch (facing_mode) {
case media::MEDIA_VIDEO_FACING_USER:
return blink::WebString::FromASCII("user");
case media::MEDIA_VIDEO_FACING_ENVIRONMENT:
return blink::WebString::FromASCII("environment");
default:
return blink::WebString();
}
}
struct Candidate {
public:
Candidate(const std::string& device_id,
const std::string& group_id,
const media::VideoCaptureFormat& format,
media::VideoFacingMode facing_mode,
media::PowerLineFrequency power_line_frequency,
const base::Optional<bool>& noise_reduction)
: device_id_(device_id),
group_id_(group_id),
format_(format),
facing_mode_(facing_mode),
power_line_frequency_(power_line_frequency),
noise_reduction_(noise_reduction) {}
// These accessor-like methods transform types to what Blink constraint
// classes expect.
blink::WebString GetFacingMode() const { return ToWebString(facing_mode_); }
long GetPowerLineFrequency() const {
return static_cast<long>(power_line_frequency_);
}
blink::WebString GetDeviceId() const {
return blink::WebString::FromASCII(device_id_.data());
}
blink::WebString GetGroupId() const {
return blink::WebString::FromASCII(group_id_.data());
}
blink::WebString GetVideoKind() const {
return GetVideoKindForFormat(format_);
}
// Accessors.
const media::VideoCaptureFormat& format() const { return format_; }
const std::string& device_id() const { return device_id_; }
const std::string& group_id() const { return group_id_; }
media::VideoFacingMode facing_mode() const { return facing_mode_; }
media::PowerLineFrequency power_line_frequency() const {
return power_line_frequency_;
}
const base::Optional<bool>& noise_reduction() const {
return noise_reduction_;
}
private:
std::string device_id_;
std::string group_id_;
media::VideoCaptureFormat format_;
media::VideoFacingMode facing_mode_;
media::PowerLineFrequency power_line_frequency_;
base::Optional<bool> noise_reduction_;
};
// The ConstrainedFormat class keeps track of the effect of constraint sets on
// the range of values supported by a video-capture format while iterating over
// the constraint sets.
// For example, suppose a device supports a width of 1024. Then, in principle,
// it can support any width below 1024 using cropping.
// Suppose the first advanced constraint set requests a maximum width of 640,
// and the second advanced constraint set requests a minimum of 800.
// Separately, the camera supports both advanced sets. However, if the first
// set is supported, the second set can no longer be supported because width can
// no longer exceed 640. The ConstrainedFormat class keeps track of this.
class ConstrainedFormat {
public:
explicit ConstrainedFormat(const media::VideoCaptureFormat& format)
: native_height_(format.frame_size.height()),
native_width_(format.frame_size.width()),
native_frame_rate_(format.frame_rate),
constrained_resolution_(1,
format.frame_size.height(),
1,
format.frame_size.width(),
0.0,
HUGE_VAL) {}
long native_height() const { return native_height_; }
long native_width() const { return native_width_; }
double native_frame_rate() const { return native_frame_rate_; }
const ResolutionSet& constrained_resolution() const {
return constrained_resolution_;
}
const DoubleRangeSet& constrained_frame_rate() const {
return constrained_frame_rate_;
}
long MinHeight() const { return constrained_resolution_.min_height(); }
long MaxHeight() const { return constrained_resolution_.max_height(); }
long MinWidth() const { return constrained_resolution_.min_width(); }
long MaxWidth() const { return constrained_resolution_.max_width(); }
double MinAspectRatio() const {
return constrained_resolution_.min_aspect_ratio();
}
double MaxAspectRatio() const {
return constrained_resolution_.max_aspect_ratio();
}
const base::Optional<double>& MinFrameRate() const {
return constrained_frame_rate_.Min();
}
const base::Optional<double>& MaxFrameRate() const {
return constrained_frame_rate_.Max();
}
// Returns true if the application of |constraint_set| is successful and
// results in a nonempty set. Returns true otherwise.
bool ApplyConstraintSet(
const blink::WebMediaTrackConstraintSet& constraint_set) {
auto resolution_intersection = constrained_resolution_.Intersection(
ResolutionSet::FromConstraintSet(constraint_set));
auto frame_rate_intersection = constrained_frame_rate_.Intersection(
DoubleRangeSet::FromConstraint(constraint_set.frame_rate, 0.0,
media::limits::kMaxFramesPerSecond));
if (resolution_intersection.IsEmpty() ||
frame_rate_intersection.IsEmpty() ||
frame_rate_intersection.Min().value_or(0.0) > native_frame_rate_) {
return false;
}
constrained_resolution_ = resolution_intersection;
constrained_frame_rate_ = frame_rate_intersection;
return true;
}
private:
// Using long for compatibility with Blink constraint classes.
long native_height_;
long native_width_;
double native_frame_rate_;
ResolutionSet constrained_resolution_;
DoubleRangeSet constrained_frame_rate_;
};
VideoCaptureSettings ComputeVideoDeviceCaptureSettings(
const Candidate& candidate,
const ConstrainedFormat& constrained_format,
const blink::WebMediaTrackConstraintSet& basic_constraint_set) {
media::VideoCaptureParams capture_params;
capture_params.requested_format = candidate.format();
capture_params.power_line_frequency = candidate.power_line_frequency();
auto track_adapter_settings = SelectVideoTrackAdapterSettings(
basic_constraint_set, constrained_format.constrained_resolution(),
constrained_format.constrained_frame_rate(),
capture_params.requested_format);
return VideoCaptureSettings(
candidate.device_id(), capture_params, candidate.noise_reduction(),
track_adapter_settings, constrained_format.constrained_frame_rate().Min(),
constrained_format.constrained_frame_rate().Max());
}
// Returns a pair with the minimum and maximum aspect ratios supported by the
// candidate format |constrained_format|, subject to given width and height
// constraints.
void GetSourceAspectRatioRange(const ConstrainedFormat& constrained_format,
const blink::LongConstraint& height_constraint,
const blink::LongConstraint& width_constraint,
double* min_source_aspect_ratio,
double* max_source_aspect_ratio) {
int32_t min_height = constrained_format.MinHeight();
if (ConstraintHasMin(height_constraint))
min_height = std::max(min_height, ConstraintMin(height_constraint));
int32_t max_height = constrained_format.MaxHeight();
if (ConstraintHasMax(height_constraint))
max_height = std::min(max_height, ConstraintMax(height_constraint));
int32_t min_width = constrained_format.MinWidth();
if (ConstraintHasMin(width_constraint))
min_width = std::max(min_width, ConstraintMin(width_constraint));
int32_t max_width = constrained_format.MaxWidth();
if (ConstraintHasMax(width_constraint))
max_width = std::min(max_width, ConstraintMax(width_constraint));
*min_source_aspect_ratio = std::max(
constrained_format.MinAspectRatio(),
std::max(static_cast<double>(min_width) / static_cast<double>(max_height),
kMinSourceAspectRatio));
*max_source_aspect_ratio = std::min(
constrained_format.MaxAspectRatio(),
std::max(static_cast<double>(max_width) / static_cast<double>(min_height),
kMinSourceAspectRatio));
}
// Returns a custom distance between a string and a string constraint.
// Returns 0 if |value| satisfies |constraint|. HUGE_VAL otherwise.
double StringConstraintSourceDistance(const blink::WebString& value,
const blink::StringConstraint& constraint,
const char** failed_constraint_name) {
if (constraint.Matches(value))
return 0.0;
if (failed_constraint_name)
*failed_constraint_name = constraint.GetName();
return HUGE_VAL;
}
double FacingModeConstraintSourceDistance(
media::VideoFacingMode value,
const blink::StringConstraint& facing_mode_constraint,
const char** failed_constraint_name) {
blink::WebString string_value = ToWebString(value);
if (string_value.IsNull()) {
if (facing_mode_constraint.Exact().empty())
return 0.0;
if (failed_constraint_name)
*failed_constraint_name = facing_mode_constraint.GetName();
return HUGE_VAL;
}
double ret = StringConstraintSourceDistance(
string_value, facing_mode_constraint, failed_constraint_name);
return ret;
}
// Returns a custom distance between a source screen dimension and |constraint|.
// The source supports the range [|min_source_value|, |max_source_value|], using
// cropping if necessary. This range may differ from the native range of the
// source [1, |native_source_value|] due to the application of previous
// constraint sets.
// If the range supported by the source and the range specified by |constraint|
// are disjoint, the distance is infinite.
// If |constraint| has a maximum, penalize sources whose native resolution
// exceeds the maximum by returning
// Distance(|native_source_value|, |constraint.max()|). This is intended to
// prefer, among sources that satisfy the constraint, those that have lower
// resource usage. Otherwise, return zero.
double ResolutionConstraintSourceDistance(
int native_source_value,
int min_source_value,
int max_source_value,
const blink::LongConstraint& constraint,
const char** failed_constraint_name) {
DCHECK_GE(native_source_value, 0);
bool constraint_has_min = ConstraintHasMin(constraint);
long constraint_min = constraint_has_min ? ConstraintMin(constraint) : -1;
bool constraint_has_max = ConstraintHasMax(constraint);
long constraint_max = constraint_has_max ? ConstraintMax(constraint) : -1;
// If the intersection between the source range and the constraint range is
// empty, return HUGE_VAL.
if ((constraint_has_max && min_source_value > constraint_max) ||
(constraint_has_min && max_source_value < constraint_min) ||
(constraint_has_min && constraint_has_max &&
constraint_min > constraint_max)) {
if (failed_constraint_name)
*failed_constraint_name = constraint.GetName();
return HUGE_VAL;
}
// If the source value exceeds the maximum requested, penalize.
if (constraint_has_max && native_source_value > constraint_max)
return NumericConstraintFitnessDistance(native_source_value,
constraint_max);
return 0.0;
}
// Returns a custom distance function suitable for frame rate, given
// a |constraint| and a candidate format |constrained_format|.
// A source can support track frame rates in the interval
// [min_frame_rate, max_frame_rate], using frame-rate adjustments if
// necessary. If the candidate range and the constraint range are disjoint,
// return HUGE_VAL.
// The supported source range may differ from the native range of the source
// (0, native_source_range] due to the application of previous constraint sets.
// If |constraint| has a maximum, penalize native frame rates that exceed the
// maximum by returning Distance(native_frame_rate, maximum). This is intended
// to prefer, among sources that satisfy the constraint, those that have lower
// resource usage. Otherwise, return zero.
double FrameRateConstraintSourceDistance(
const ConstrainedFormat& constrained_format,
const blink::DoubleConstraint& constraint,
const char** failed_constraint_name) {
double constraint_min =
ConstraintHasMin(constraint) ? ConstraintMin(constraint) : -1.0;
double constraint_max = ConstraintHasMax(constraint)
? ConstraintMax(constraint)
: media::limits::kMaxFramesPerSecond;
bool constraint_min_out_of_range =
((constraint_min > constrained_format.native_frame_rate()) ||
(constraint_min > constrained_format.MaxFrameRate().value_or(
media::limits::kMaxFramesPerSecond) +
blink::DoubleConstraint::kConstraintEpsilon));
bool constraint_max_out_of_range =
((constraint_max < 1.0) ||
(constraint_max < constrained_format.MinFrameRate().value_or(0.0) -
blink::DoubleConstraint::kConstraintEpsilon));
bool constraint_self_contradicts = constraint_min > constraint_max;
if (constraint_min_out_of_range || constraint_max_out_of_range ||
constraint_self_contradicts) {
if (failed_constraint_name)
*failed_constraint_name = constraint.GetName();
return HUGE_VAL;
}
// Compute the cost using the native rate.
if (constrained_format.native_frame_rate() > constraint_max) {
return NumericConstraintFitnessDistance(
constrained_format.native_frame_rate(), constraint_max);
}
return 0.0;
}
// Returns a custom distance function suitable for aspect ratio, given
// the values for the aspect_ratio, width and height constraints, and a
// candidate format |constrained_format|.
// A source can support track resolutions that range from
// min_width x min_height to max_width x max_height
// where
// min_width = max(1, width_constraint.min)
// min_height = max(1, height_constraint.min)
// max_width = min(source_width, width_constraint.max)
// max_height = min(source_height, height_constraint.max)
// The aspect-ratio range supported by the source is determined by the extremes
// of those resolutions.
// min_ar = min_width / max_height.
// max_ar = max_width / min_height.
//
// If the supported range [min_ar, max_ar] and the range specified by the
// aspectRatio constraint are disjoint, return HUGE_VAL. Otherwise, return zero.
double AspectRatioConstraintSourceDistance(
const ConstrainedFormat& constrained_format,
const blink::LongConstraint& height_constraint,
const blink::LongConstraint& width_constraint,
const blink::DoubleConstraint& aspect_ratio_constraint,
const char** failed_constraint_name) {
bool ar_constraint_has_min = ConstraintHasMin(aspect_ratio_constraint);
double ar_constraint_min =
ar_constraint_has_min ? ConstraintMin(aspect_ratio_constraint) : -1.0;
bool ar_constraint_has_max = ConstraintHasMax(aspect_ratio_constraint);
double ar_constraint_max =
ar_constraint_has_max ? ConstraintMax(aspect_ratio_constraint) : -1.0;
double min_source_aspect_ratio;
double max_source_aspect_ratio;
GetSourceAspectRatioRange(constrained_format, height_constraint,
width_constraint, &min_source_aspect_ratio,
&max_source_aspect_ratio);
// If the supported range and the constraint rage are disjoint, return
// HUGE_VAL.
if ((ar_constraint_has_min &&
max_source_aspect_ratio <
ar_constraint_min - blink::DoubleConstraint::kConstraintEpsilon) ||
(ar_constraint_has_max &&
min_source_aspect_ratio >
ar_constraint_max + blink::DoubleConstraint::kConstraintEpsilon) ||
(ar_constraint_has_min && ar_constraint_has_max &&
ar_constraint_min > ar_constraint_max)) {
if (failed_constraint_name)
*failed_constraint_name = aspect_ratio_constraint.GetName();
return HUGE_VAL;
}
return 0.0;
}
// Returns a custom distance function suitable for the googPowerLineFrequency
// constraint, given a |constraint| and a candidate value |source_value|.
// The distance is HUGE_VAL if |source_value| cannot satisfy |constraint|.
// Otherwise, the distance is zero.
double PowerLineFrequencyConstraintSourceDistance(
const blink::LongConstraint& constraint,
media::PowerLineFrequency source_value,
const char** failed_constraint_name) {
bool constraint_has_min = ConstraintHasMin(constraint);
bool constraint_has_max = ConstraintHasMax(constraint);
long constraint_min = constraint_has_min ? ConstraintMin(constraint) : -1L;
long constraint_max = constraint_has_max ? ConstraintMax(constraint) : -1L;
long source_value_long = static_cast<long>(source_value);
if ((constraint_has_max && source_value_long > constraint_max) ||
(constraint_has_min && source_value_long < constraint_min) ||
(constraint_has_min && constraint_has_max &&
constraint_min > constraint_max)) {
if (failed_constraint_name)
*failed_constraint_name = constraint.GetName();
return HUGE_VAL;
}
return 0.0;
}
// Returns a custom distance function suitable for the googNoiseReduction
// constraint, given a |constraint| and a candidate value |value|.
// The distance is HUGE_VAL if |candidate_value| cannot satisfy |constraint|.
// Otherwise, the distance is zero.
double NoiseReductionConstraintSourceDistance(
const blink::BooleanConstraint& constraint,
const base::Optional<bool>& value,
const char** failed_constraint_name) {
if (!constraint.HasExact())
return 0.0;
if (value && *value == constraint.Exact())
return 0.0;
if (failed_constraint_name)
*failed_constraint_name = constraint.GetName();
return HUGE_VAL;
}
// Returns a custom distance for constraints that depend on the device
// characteristics that have a fixed value.
double DeviceSourceDistance(
const std::string& device_id,
const std::string& group_id,
media::VideoFacingMode facing_mode,
const blink::WebMediaTrackConstraintSet& constraint_set,
const char** failed_constraint_name) {
return StringConstraintSourceDistance(blink::WebString::FromASCII(device_id),
constraint_set.device_id,
failed_constraint_name) +
StringConstraintSourceDistance(blink::WebString::FromASCII(group_id),
constraint_set.group_id,
failed_constraint_name) +
FacingModeConstraintSourceDistance(
facing_mode, constraint_set.facing_mode, failed_constraint_name);
}
// Returns a custom distance between |constraint_set| and |format| given that
// the configuration is already constrained by |constrained_format|.
// |constrained_format| may cause the distance to be infinite if
// |constraint_set| cannot be satisfied together with previous constraint sets,
// but will not influence the numeric value returned if it is not infinite.
// Formats with lower distance satisfy |constraint_set| with lower resource
// usage.
double FormatSourceDistance(
const media::VideoCaptureFormat& format,
const ConstrainedFormat& constrained_format,
const blink::WebMediaTrackConstraintSet& constraint_set,
const char** failed_constraint_name) {
return ResolutionConstraintSourceDistance(
constrained_format.native_height(), constrained_format.MinHeight(),
constrained_format.MaxHeight(), constraint_set.height,
failed_constraint_name) +
ResolutionConstraintSourceDistance(
constrained_format.native_width(), constrained_format.MinWidth(),
constrained_format.MaxWidth(), constraint_set.width,
failed_constraint_name) +
AspectRatioConstraintSourceDistance(
constrained_format, constraint_set.height, constraint_set.width,
constraint_set.aspect_ratio, failed_constraint_name) +
FrameRateConstraintSourceDistance(constrained_format,
constraint_set.frame_rate,
failed_constraint_name) +
StringConstraintSourceDistance(GetVideoKindForFormat(format),
constraint_set.video_kind,
failed_constraint_name);
}
// Returns a custom distance between |constraint_set| and |candidate|, given
// that the configuration is already constrained by |constrained_format|.
// |constrained_format| may cause the distance to be infinite if
// |constraint_set| cannot be satisfied together with previous constraint sets,
// but will not influence the numeric value returned if it is not infinite.
// Candidates with lower distance satisfy |constraint_set| with lower resource
// usage.
double CandidateSourceDistance(
const Candidate& candidate,
const ConstrainedFormat& constrained_format,
const blink::WebMediaTrackConstraintSet& constraint_set,
const char** failed_constraint_name) {
return DeviceSourceDistance(candidate.device_id(), candidate.group_id(),
candidate.facing_mode(), constraint_set,
failed_constraint_name) +
FormatSourceDistance(candidate.format(), constrained_format,
constraint_set, failed_constraint_name) +
PowerLineFrequencyConstraintSourceDistance(
constraint_set.goog_power_line_frequency,
candidate.power_line_frequency(), failed_constraint_name) +
NoiseReductionConstraintSourceDistance(
constraint_set.goog_noise_reduction, candidate.noise_reduction(),
failed_constraint_name);
}
// Returns the fitness distance between |value| and |constraint| for
// resolution constraints (i.e., width and height).
// Based on https://w3c.github.io/mediacapture-main/#dfn-fitness-distance.
double ResolutionConstraintFitnessDistance(
long value,
const blink::LongConstraint& constraint) {
if (!constraint.HasIdeal())
return 0.0;
// Source resolutions greater than ideal support the ideal value with
// cropping.
if (value >= constraint.Ideal())
return 0.0;
return NumericConstraintFitnessDistance(value, constraint.Ideal());
}
// Returns the fitness distance between |value| and |constraint| for
// resolution constraints (i.e., width and height), ignoring cropping.
// This measures how well a native resolution supports the idea value.
// Based on https://w3c.github.io/mediacapture-main/#dfn-fitness-distance.
double ResolutionConstraintNativeFitnessDistance(
long value,
const blink::LongConstraint& constraint) {
return constraint.HasIdeal()
? NumericConstraintFitnessDistance(value, constraint.Ideal())
: 0.0;
}
// Returns the fitness distance between a source resolution settings
// and the aspectRatio constraint, taking into account resolution restrictions
// on the source imposed by the width and height constraints.
// Based on https://w3c.github.io/mediacapture-main/#dfn-fitness-distance.
double AspectRatioConstraintFitnessDistance(
const ConstrainedFormat& constrained_format,
const blink::LongConstraint& height_constraint,
const blink::LongConstraint& width_constraint,
const blink::DoubleConstraint& aspect_ratio_constraint) {
if (!aspect_ratio_constraint.HasIdeal())
return 0.0;
double min_source_aspect_ratio;
double max_source_aspect_ratio;
GetSourceAspectRatioRange(constrained_format, height_constraint,
width_constraint, &min_source_aspect_ratio,
&max_source_aspect_ratio);
// If the supported aspect ratio range does not include the ideal aspect
// ratio, compute fitness using the spec formula.
if (max_source_aspect_ratio <
aspect_ratio_constraint.Ideal() -
blink::DoubleConstraint::kConstraintEpsilon) {
return NumericConstraintFitnessDistance(max_source_aspect_ratio,
aspect_ratio_constraint.Ideal());
}
if (min_source_aspect_ratio >
aspect_ratio_constraint.Ideal() +
blink::DoubleConstraint::kConstraintEpsilon) {
return NumericConstraintFitnessDistance(min_source_aspect_ratio,
aspect_ratio_constraint.Ideal());
}
// Otherwise, the ideal aspect ratio can be supported and the fitness is 0.
return 0.0;
}
// Returns the fitness distance between |value| and |constraint| for the
// frameRate constraint.
// Based on https://w3c.github.io/mediacapture-main/#dfn-fitness-distance.
double FrameRateConstraintFitnessDistance(
double value,
const blink::DoubleConstraint& constraint) {
if (!constraint.HasIdeal())
return 0.0;
// Source frame rates greater than ideal support the ideal value using
// frame-rate adjustment.
if (value >=
constraint.Ideal() - blink::DoubleConstraint::kConstraintEpsilon) {
return 0.0;
}
return NumericConstraintFitnessDistance(value, constraint.Ideal());
}
// Returns the fitness distance between |value| and |constraint| for the
// frameRate constraint, ignoring frame-rate adjustment.
// It measures how well the native frame rate supports the ideal value.
// Based on https://w3c.github.io/mediacapture-main/#dfn-fitness-distance.
double FrameRateConstraintNativeFitnessDistance(
double value,
const blink::DoubleConstraint& constraint) {
return constraint.HasIdeal()
? NumericConstraintFitnessDistance(value, constraint.Ideal())
: 0.0;
}
// Returns the fitness distance between |value| and |constraint| for the
// googPowerLineFrequency constraint.
// Based on https://w3c.github.io/mediacapture-main/#dfn-fitness-distance.
double PowerLineFrequencyConstraintFitnessDistance(
long value,
const blink::LongConstraint& constraint) {
if (!constraint.HasIdeal())
return 0.0;
// This constraint is of type long, but it behaves as an enum. Thus, values
// equal to ideal have fitness 0.0 and any other values have fitness 1.0.
if (value == constraint.Ideal())
return 0.0;
return 1.0;
}
// Returns the fitness distance between |value| and |constraint| for the
// googNoiseReduction constraint.
// Based on https://w3c.github.io/mediacapture-main/#dfn-fitness-distance.
double NoiseReductionConstraintFitnessDistance(
const base::Optional<bool>& value,
const blink::BooleanConstraint& constraint) {
if (!constraint.HasIdeal())
return 0.0;
if (value && value == constraint.Ideal())
return 0.0;
return 1.0;
}
// Returns the fitness distance between |constraint_set| and |candidate| given
// that the configuration is already constrained by |constrained_format|.
// Based on https://w3c.github.io/mediacapture-main/#dfn-fitness-distance.
double CandidateFitnessDistance(
const Candidate& candidate,
const ConstrainedFormat& constrained_format,
const blink::WebMediaTrackConstraintSet& constraint_set) {
DCHECK(std::isfinite(CandidateSourceDistance(candidate, constrained_format,
constraint_set, nullptr)));
double fitness = 0.0;
fitness += AspectRatioConstraintFitnessDistance(
constrained_format, constraint_set.height, constraint_set.width,
constraint_set.aspect_ratio);
fitness += StringConstraintFitnessDistance(candidate.GetDeviceId(),
constraint_set.device_id);
fitness += StringConstraintFitnessDistance(candidate.GetGroupId(),
constraint_set.group_id);
fitness += StringConstraintFitnessDistance(candidate.GetFacingMode(),
constraint_set.facing_mode);
fitness += StringConstraintFitnessDistance(candidate.GetVideoKind(),
constraint_set.video_kind);
fitness += PowerLineFrequencyConstraintFitnessDistance(
candidate.GetPowerLineFrequency(),
constraint_set.goog_power_line_frequency);
fitness += NoiseReductionConstraintFitnessDistance(
candidate.noise_reduction(), constraint_set.goog_noise_reduction);
// No need to pass minimum value to compute fitness for range-based
// constraints because all candidates start out with the same minimum and are
// subject to the same constraints.
fitness += ResolutionConstraintFitnessDistance(constrained_format.MaxHeight(),
constraint_set.height);
fitness += ResolutionConstraintFitnessDistance(constrained_format.MaxWidth(),
constraint_set.width);
fitness += FrameRateConstraintFitnessDistance(
constrained_format.MaxFrameRate().value_or(
constrained_format.native_frame_rate()),
constraint_set.frame_rate);
return fitness;
}
// Returns the native fitness distance between a candidate format and a
// constraint set. The returned value is the sum of the fitness distances for
// the native values of settings that support a range of values (i.e., width,
// height and frame rate).
// Based on https://w3c.github.io/mediacapture-main/#dfn-fitness-distance.
double CandidateNativeFitnessDistance(
const ConstrainedFormat& constrained_format,
const blink::WebMediaTrackConstraintSet& constraint_set) {
double fitness = 0.0;
fitness += FrameRateConstraintNativeFitnessDistance(
constrained_format.native_frame_rate(), constraint_set.frame_rate);
fitness += ResolutionConstraintNativeFitnessDistance(
constrained_format.native_height(), constraint_set.height);
fitness += ResolutionConstraintNativeFitnessDistance(
constrained_format.native_width(), constraint_set.width);
return fitness;
}
using DistanceVector = std::vector<double>;
// This function appends additional entries to |distance_vector| based on
// custom distance metrics between |candidate| and some default settings.
// These entries are to be used as the final tie breaker for candidates that
// are equally good according to the spec and the custom distance functions
// between candidates and constraints.
void AppendDistanceFromDefault(
const Candidate& candidate,
const VideoDeviceCaptureCapabilities& capabilities,
int default_width,
int default_height,
double default_frame_rate,
DistanceVector* distance_vector) {
// Favor IDs that appear first in the enumeration.
for (size_t i = 0; i < capabilities.device_capabilities.size(); ++i) {
if (candidate.device_id() ==
capabilities.device_capabilities[i]->device_id) {
distance_vector->push_back(i);
break;
}
}
// Prefer default power-line frequency.
double power_line_frequency_distance =
candidate.power_line_frequency() ==
media::PowerLineFrequency::FREQUENCY_DEFAULT
? 0.0
: HUGE_VAL;
distance_vector->push_back(power_line_frequency_distance);
// Prefer not having a specific noise-reduction value and let the lower-layers
// implementation choose a noise-reduction strategy.
double noise_reduction_distance =
candidate.noise_reduction() ? HUGE_VAL : 0.0;
distance_vector->push_back(noise_reduction_distance);
// Prefer a resolution closest to the default.
double resolution_distance = ResolutionSet::Point::SquareEuclideanDistance(
ResolutionSet::Point(candidate.format().frame_size.height(),
candidate.format().frame_size.width()),
ResolutionSet::Point(default_height, default_width));
distance_vector->push_back(resolution_distance);
// Prefer a frame rate close to the default.
double frame_rate_distance =
candidate.format().frame_rate == default_frame_rate
? 0.0
: NumericConstraintFitnessDistance(candidate.format().frame_rate,
default_frame_rate);
distance_vector->push_back(frame_rate_distance);
}
} // namespace
blink::WebString GetVideoKindForFormat(
const media::VideoCaptureFormat& format) {
return (format.pixel_format == media::PIXEL_FORMAT_Y16)
? blink::WebString::FromASCII(kVideoKindDepth)
: blink::WebString::FromASCII(kVideoKindColor);
}
blink::WebMediaStreamTrack::FacingMode ToWebFacingMode(
media::VideoFacingMode video_facing) {
switch (video_facing) {
case media::MEDIA_VIDEO_FACING_NONE:
return blink::WebMediaStreamTrack::FacingMode::kNone;
case media::MEDIA_VIDEO_FACING_USER:
return blink::WebMediaStreamTrack::FacingMode::kUser;
case media::MEDIA_VIDEO_FACING_ENVIRONMENT:
return blink::WebMediaStreamTrack::FacingMode::kEnvironment;
default:
return blink::WebMediaStreamTrack::FacingMode::kNone;
}
}
blink::WebMediaStreamTrack::DisplayCaptureSurfaceType ToWebDisplaySurface(
media::DisplayCaptureSurfaceType display_surface) {
switch (display_surface) {
case media::DisplayCaptureSurfaceType::MONITOR:
return blink::WebMediaStreamTrack::DisplayCaptureSurfaceType::kMonitor;
case media::DisplayCaptureSurfaceType::WINDOW:
return blink::WebMediaStreamTrack::DisplayCaptureSurfaceType::kWindow;
case media::DisplayCaptureSurfaceType::APPLICATION:
return blink::WebMediaStreamTrack::DisplayCaptureSurfaceType::
kApplication;
case media::DisplayCaptureSurfaceType::BROWSER:
return blink::WebMediaStreamTrack::DisplayCaptureSurfaceType::kBrowser;
}
}
blink::WebMediaStreamTrack::CursorCaptureType ToWebCursorCaptureType(
media::CursorCaptureType cursor) {
switch (cursor) {
case media::CursorCaptureType::NEVER:
return blink::WebMediaStreamTrack::CursorCaptureType::kNever;
case media::CursorCaptureType::ALWAYS:
return blink::WebMediaStreamTrack::CursorCaptureType::kAlways;
case media::CursorCaptureType::MOTION:
return blink::WebMediaStreamTrack::CursorCaptureType::kMotion;
}
}
VideoDeviceCaptureCapabilities::VideoDeviceCaptureCapabilities() = default;
VideoDeviceCaptureCapabilities::VideoDeviceCaptureCapabilities(
VideoDeviceCaptureCapabilities&& other) = default;
VideoDeviceCaptureCapabilities::~VideoDeviceCaptureCapabilities() = default;
VideoDeviceCaptureCapabilities& VideoDeviceCaptureCapabilities::operator=(
VideoDeviceCaptureCapabilities&& other) = default;
VideoCaptureSettings SelectSettingsVideoDeviceCapture(
const VideoDeviceCaptureCapabilities& capabilities,
const blink::WebMediaConstraints& constraints,
int default_width,
int default_height,
double default_frame_rate) {
DCHECK_GT(default_width, 0);
DCHECK_GT(default_height, 0);
DCHECK_GE(default_frame_rate, 0.0);
// This function works only if infinity is defined for the double type.
static_assert(std::numeric_limits<double>::has_infinity, "Requires infinity");
// A distance vector contains:
// a) For each advanced constraint set, a 0/1 value indicating if the
// candidate satisfies the corresponding constraint set.
// b) Fitness distance for the candidate based on support for the ideal values
// of the basic constraint set.
// c) A custom distance value based on how "well" a candidate satisfies each
// constraint set, including basic and advanced sets.
// d) Native fitness distance for the candidate based on support for the
// ideal values of the basic constraint set using native values for
// settings that can support a range of values.
// e) A custom distance value based on how close the candidate is to default
// settings.
// Parts (a) and (b) are according to spec. Parts (c) to (e) are
// implementation specific and used to break ties.
DistanceVector best_distance(2 * constraints.Advanced().size() + 3 +
kNumDefaultDistanceEntries);
std::fill(best_distance.begin(), best_distance.end(), HUGE_VAL);
VideoCaptureSettings result;
const char* failed_constraint_name = result.failed_constraint_name();
for (auto& device : capabilities.device_capabilities) {
double basic_device_distance = DeviceSourceDistance(
device->device_id, device->group_id, device->facing_mode,
constraints.Basic(), &failed_constraint_name);
if (!std::isfinite(basic_device_distance))
continue;
for (auto& format : device->formats) {
ConstrainedFormat constrained_format(format);
double basic_format_distance =
FormatSourceDistance(format, constrained_format, constraints.Basic(),
&failed_constraint_name);
if (!std::isfinite(basic_format_distance))
continue;
if (!constrained_format.ApplyConstraintSet(constraints.Basic()))
continue;
for (auto& power_line_frequency : capabilities.power_line_capabilities) {
double basic_power_line_frequency_distance =
PowerLineFrequencyConstraintSourceDistance(
constraints.Basic().goog_power_line_frequency,
power_line_frequency, &failed_constraint_name);
if (!std::isfinite(basic_power_line_frequency_distance))
continue;
for (auto& noise_reduction :
capabilities.noise_reduction_capabilities) {
double basic_noise_reduction_distance =
NoiseReductionConstraintSourceDistance(
constraints.Basic().goog_noise_reduction, noise_reduction,
&failed_constraint_name);
if (!std::isfinite(basic_noise_reduction_distance))
continue;
// The candidate satisfies the basic constraint set.
double candidate_basic_custom_distance =
basic_device_distance + basic_format_distance +
basic_power_line_frequency_distance +
basic_noise_reduction_distance;
DCHECK(std::isfinite(candidate_basic_custom_distance));
// Temporary vector to save custom distances for advanced constraints.
// Custom distances must be added to the candidate distance vector
// after all the spec-mandated values.
DistanceVector advanced_custom_distance_vector;
Candidate candidate(device->device_id, device->group_id, format,
device->facing_mode, power_line_frequency,
noise_reduction);
DistanceVector candidate_distance_vector;
// First criteria for valid candidates is satisfaction of advanced
// constraint sets.
for (const auto& advanced_set : constraints.Advanced()) {
double custom_distance = CandidateSourceDistance(
candidate, constrained_format, advanced_set, nullptr);
if (!constrained_format.ApplyConstraintSet(advanced_set))
custom_distance = HUGE_VAL;
advanced_custom_distance_vector.push_back(custom_distance);
double spec_distance = std::isfinite(custom_distance) ? 0 : 1;
candidate_distance_vector.push_back(spec_distance);
}
// Second criterion is fitness distance.
candidate_distance_vector.push_back(CandidateFitnessDistance(
candidate, constrained_format, constraints.Basic()));
// Third criteria are custom distances to constraint sets.
candidate_distance_vector.push_back(candidate_basic_custom_distance);
std::copy(advanced_custom_distance_vector.begin(),
advanced_custom_distance_vector.end(),
std::back_inserter(candidate_distance_vector));
// Fourth criteria is native fitness distance.
candidate_distance_vector.push_back(CandidateNativeFitnessDistance(
constrained_format, constraints.Basic()));
// Final criteria are custom distances to default settings.
AppendDistanceFromDefault(candidate, capabilities, default_width,
default_height, default_frame_rate,
&candidate_distance_vector);
DCHECK_EQ(best_distance.size(), candidate_distance_vector.size());
if (candidate_distance_vector < best_distance) {
best_distance = candidate_distance_vector;
result = ComputeVideoDeviceCaptureSettings(
candidate, constrained_format, constraints.Basic());
}
}
}
}
}
if (!result.HasValue())
return VideoCaptureSettings(failed_constraint_name);
return result;
}
} // namespace content