blob: 5a7b78b41e84e1d22e85d0e1aa3415b546f653d9 [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 "third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_audio.h"
#include <algorithm>
#include <cmath>
#include <string>
#include <tuple>
#include <utility>
#include "base/strings/string_number_conversions.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "media/audio/audio_features.h"
#include "media/base/audio_parameters.h"
#include "media/base/limits.h"
#include "media/webrtc/webrtc_switches.h"
#include "third_party/blink/public/common/mediastream/media_stream_controls.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/web/modules/mediastream/media_stream_video_source.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_sets.h"
#include "third_party/blink/renderer/modules/mediastream/processed_local_audio_source.h"
#include "third_party/blink/renderer/platform/mediastream/media_constraints.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_audio_source.h"
namespace blink {
using blink::AudioCaptureSettings;
using blink::AudioProcessingProperties;
using ConstraintSet = MediaTrackConstraintSetPlatform;
using BooleanConstraint = blink::BooleanConstraint;
using EchoCancellationType = AudioProcessingProperties::EchoCancellationType;
using ProcessingType = AudioCaptureSettings::ProcessingType;
using StringConstraint = blink::StringConstraint;
template <class T>
using NumericRangeSet = blink::media_constraints::NumericRangeSet<T>;
namespace {
using BoolSet = blink::media_constraints::DiscreteSet<bool>;
using DoubleRangeSet = blink::media_constraints::NumericRangeSet<double>;
using EchoCancellationTypeSet =
blink::media_constraints::DiscreteSet<EchoCancellationType>;
using IntRangeSet = blink::media_constraints::NumericRangeSet<int>;
using StringSet = blink::media_constraints::DiscreteSet<std::string>;
// The presence of a MediaStreamAudioSource object indicates whether the source
// in question is currently in use, or not. This convenience enum helps
// identifying whether a source is available and, if so, whether it has audio
// processing enabled or disabled.
enum class SourceType { kNone, kUnprocessed, kNoApmProcessed, kApmProcessed };
// The sample size is set to 16 due to the Signed-16 format representation.
int32_t GetSampleSize() {
return media::SampleFormatToBitsPerChannel(media::kSampleFormatS16);
}
// This class encapsulates two values that together build up the score of each
// processed candidate.
// - Fitness, similarly defined by the W3C specification
// (https://w3c.github.io/mediacapture-main/#dfn-fitness-distance);
// - Distance from the default device ID;
// - The priority associated to the echo cancellation type selected.
// - The priority of the associated processing-based container.
//
// Differently from the definition in the W3C specification, the present
// algorithm maximizes the score.
struct Score {
public:
enum class EcModeScore : int {
kDisabled = 1,
kSystem = 2,
kAec3 = 3,
};
explicit Score(double fitness,
bool is_default_device_id = false,
EcModeScore ec_mode_score = EcModeScore::kDisabled,
int processing_priority = -1) {
score = std::make_tuple(fitness, is_default_device_id, ec_mode_score,
processing_priority);
}
bool operator>(const Score& other) const { return score > other.score; }
Score& operator+=(const Score& other) {
std::get<0>(score) += std::get<0>(other.score);
std::get<1>(score) |= std::get<1>(other.score);
// Among the priorities in the two score objects, we store the highest one.
std::get<2>(score) = std::max(std::get<2>(score), std::get<2>(other.score));
// Select the highest processing priority.
std::get<3>(score) = std::max(std::get<3>(score), std::get<3>(other.score));
return *this;
}
Score& operator+=(double fitness) {
std::get<0>(score) += fitness;
return *this;
}
Score& operator+=(bool is_default_device) {
std::get<1>(score) |= is_default_device;
return *this;
}
void set_ec_mode_score(EcModeScore ec_mode_score) {
std::get<2>(score) = ec_mode_score;
}
void set_processing_priority(int priority) { std::get<3>(score) = priority; }
std::tuple<double, bool, EcModeScore, int> score;
};
// This class represents the output of DeviceContainer::InfoFromSource and is
// used to obtain information regarding an active source, if that exists.
class SourceInfo {
public:
SourceInfo(SourceType type,
const AudioProcessingProperties& properties,
base::Optional<int> channels,
base::Optional<int> sample_rate,
base::Optional<double> latency)
: type_(type),
properties_(properties),
channels_(std::move(channels)),
sample_rate_(std::move(sample_rate)),
latency_(latency) {}
bool HasActiveSource() { return type_ != SourceType::kNone; }
SourceType type() { return type_; }
const AudioProcessingProperties& properties() { return properties_; }
const base::Optional<int>& channels() { return channels_; }
const base::Optional<int>& sample_rate() { return sample_rate_; }
const base::Optional<double>& latency() { return latency_; }
private:
const SourceType type_;
const AudioProcessingProperties properties_;
const base::Optional<int> channels_;
const base::Optional<int> sample_rate_;
const base::Optional<double> latency_;
};
// Container for each independent boolean constrainable property.
class BooleanContainer {
public:
explicit BooleanContainer(BoolSet allowed_values = BoolSet())
: allowed_values_(std::move(allowed_values)) {}
const char* ApplyConstraintSet(const BooleanConstraint& constraint) {
allowed_values_ = allowed_values_.Intersection(
blink::media_constraints::BoolSetFromConstraint(constraint));
return allowed_values_.IsEmpty() ? constraint.GetName() : nullptr;
}
std::tuple<double, bool> SelectSettingsAndScore(
const BooleanConstraint& constraint,
bool default_setting) const {
DCHECK(!IsEmpty());
if (constraint.HasIdeal() && allowed_values_.Contains(constraint.Ideal()))
return std::make_tuple(1.0, constraint.Ideal());
if (allowed_values_.is_universal())
return std::make_tuple(0.0, default_setting);
DCHECK_EQ(allowed_values_.elements().size(), 1U);
return std::make_tuple(0.0, allowed_values_.FirstElement());
}
bool IsEmpty() const { return allowed_values_.IsEmpty(); }
private:
BoolSet allowed_values_;
};
// Container for each independent string constrainable property.
class StringContainer {
public:
explicit StringContainer(StringSet allowed_values = StringSet())
: allowed_values_(std::move(allowed_values)) {}
const char* ApplyConstraintSet(const StringConstraint& constraint) {
allowed_values_ = allowed_values_.Intersection(
blink::media_constraints::StringSetFromConstraint(constraint));
return allowed_values_.IsEmpty() ? constraint.GetName() : nullptr;
}
// Selects the best value from the nonempty |allowed_values_|, subject to
// |constraint_set.*constraint_member_| and determines the associated fitness.
// The first selection criteria is inclusion in the constraint's ideal value,
// followed by equality to |default_value|. There is always a single best
// value.
std::tuple<double, std::string> SelectSettingsAndScore(
const StringConstraint& constraint,
std::string default_setting) const {
DCHECK(!IsEmpty());
if (constraint.HasIdeal()) {
for (const WTF::String& ideal_candidate : constraint.Ideal()) {
std::string candidate = ideal_candidate.Utf8();
if (allowed_values_.Contains(candidate))
return std::make_tuple(1.0, candidate);
}
}
std::string setting = allowed_values_.Contains(default_setting)
? default_setting
: allowed_values_.FirstElement();
return std::make_tuple(0.0, setting);
}
bool IsEmpty() const { return allowed_values_.IsEmpty(); }
private:
StringSet allowed_values_;
};
// Container for each independent numeric constrainable property.
template <class T, class C>
class NumericContainer {
public:
explicit NumericContainer(
NumericRangeSet<T> allowed_values = NumericRangeSet<T>())
: allowed_values_(std::move(allowed_values)) {}
const char* ApplyConstraintSet(const C& constraint) {
auto constraint_set = NumericRangeSet<T>::FromConstraint(constraint);
allowed_values_ = allowed_values_.Intersection(constraint_set);
return IsEmpty() ? constraint.GetName() : nullptr;
}
// This function will return a fitness with the associated setting for channel
// count. The setting will be the ideal value, if such value is provided and
// admitted, or the closest value to it.
// When no ideal is available and |default_setting| is provided, the setting
// will be |default_setting| or the closest value to it.
// When |default_setting| is **not** provided, the setting will be a value iff
// |allowed_values_| contains only a single value, otherwise base::nullopt is
// returned to signal that it was not possible to make a decision.
std::tuple<double, base::Optional<T>> SelectSettingsAndScore(
const C& constraint,
const base::Optional<T>& default_setting = base::nullopt) const {
DCHECK(!IsEmpty());
if (constraint.HasIdeal()) {
if (allowed_values_.Contains(constraint.Ideal()))
return std::make_tuple(1.0, constraint.Ideal());
T value = SelectClosestValueTo(constraint.Ideal());
double fitness = 1.0 - blink::NumericConstraintFitnessDistance(
value, constraint.Ideal());
return std::make_tuple(fitness, value);
}
if (default_setting) {
if (allowed_values_.Contains(*default_setting))
return std::make_tuple(0.0, *default_setting);
// If the default value provided is not contained, select the value
// closest to it.
return std::make_tuple(0.0, SelectClosestValueTo(*default_setting));
}
if (allowed_values_.Min() && allowed_values_.Max() &&
*allowed_values_.Min() == *allowed_values_.Max()) {
return std::make_tuple(0.0, *allowed_values_.Min());
}
return std::make_tuple(0.0, base::nullopt);
}
bool IsEmpty() const { return allowed_values_.IsEmpty(); }
private:
T SelectClosestValueTo(T value) const {
DCHECK(allowed_values_.Min() || allowed_values_.Max());
DCHECK(!allowed_values_.Contains(value));
return allowed_values_.Min() && value < *allowed_values_.Min()
? *allowed_values_.Min()
: *allowed_values_.Max();
}
NumericRangeSet<T> allowed_values_;
};
using IntegerContainer = NumericContainer<int, blink::LongConstraint>;
using DoubleContainer = NumericContainer<double, blink::DoubleConstraint>;
// Container to manage the properties related to echo cancellation:
// echoCancellation, googEchoCancellation and echoCancellationType.
class EchoCancellationContainer {
public:
// Default constructor intended to temporarily create an empty object.
EchoCancellationContainer()
: ec_mode_allowed_values_(EchoCancellationTypeSet::EmptySet()),
device_parameters_(media::AudioParameters::UnavailableDeviceParams()),
is_device_capture_(true) {}
EchoCancellationContainer(Vector<EchoCancellationType> allowed_values,
bool has_active_source,
bool is_device_capture,
media::AudioParameters device_parameters,
AudioProcessingProperties properties,
bool is_reconfiguration_allowed)
: ec_mode_allowed_values_(EchoCancellationTypeSet({allowed_values})),
device_parameters_(device_parameters),
is_device_capture_(is_device_capture) {
if (!has_active_source) {
#if defined(OS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)
// If force system echo cancellation feature is enabled, only expose that
// type if available; otherwise expose no type.
if (base::FeatureList::IsEnabled(features::kForceEnableSystemAec)) {
ec_mode_allowed_values_ =
ec_mode_allowed_values_.Intersection(EchoCancellationTypeSet(
{EchoCancellationType::kEchoCancellationSystem,
EchoCancellationType::kEchoCancellationDisabled}));
}
#endif // defined(OS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)
return;
}
// If HW echo cancellation is used, reconfiguration is not supported and
// only the current values are allowed. Otherwise, allow all possible values
// for echo cancellation.
if (is_reconfiguration_allowed &&
properties.echo_cancellation_type !=
EchoCancellationType::kEchoCancellationSystem) {
return;
}
ec_mode_allowed_values_ =
EchoCancellationTypeSet({properties.echo_cancellation_type});
ec_allowed_values_ =
BoolSet({properties.echo_cancellation_type !=
EchoCancellationType::kEchoCancellationDisabled});
}
const char* ApplyConstraintSet(const ConstraintSet& constraint_set) {
// Convert the constraints into discrete sets.
BoolSet ec_set = blink::media_constraints::BoolSetFromConstraint(
constraint_set.echo_cancellation);
BoolSet goog_ec_set = blink::media_constraints::BoolSetFromConstraint(
constraint_set.goog_echo_cancellation);
// Apply echoCancellation constraint.
ec_allowed_values_ = ec_allowed_values_.Intersection(ec_set);
if (ec_allowed_values_.IsEmpty())
return constraint_set.echo_cancellation.GetName();
// Intersect echoCancellation with googEchoCancellation and determine if
// there is a contradiction.
auto ec_intersection = ec_allowed_values_.Intersection(goog_ec_set);
if (ec_intersection.IsEmpty())
return constraint_set.echo_cancellation.GetName();
// Translate the boolean values into EC modes.
ec_mode_allowed_values_ = ec_mode_allowed_values_.Intersection(
ToEchoCancellationTypes(ec_intersection));
// Finally, if this container is empty, fail due to contradiction of the
// resulting allowed values for goog_ec, ec, and/or ec_type.
return IsEmpty() ? constraint_set.echo_cancellation.GetName() : nullptr;
}
std::tuple<Score, EchoCancellationType> SelectSettingsAndScore(
const ConstraintSet& constraint_set) const {
EchoCancellationType selected_ec_mode = SelectBestEcMode(constraint_set);
double fitness =
Fitness(selected_ec_mode, constraint_set.echo_cancellation);
Score score(fitness);
score.set_ec_mode_score(GetEcModeScore(selected_ec_mode));
return std::make_tuple(score, selected_ec_mode);
}
bool IsEmpty() const { return ec_mode_allowed_values_.IsEmpty(); }
// Audio-processing properties are disabled by default for content capture,
// or if the |echo_cancellation| constraint is false.
void UpdateDefaultValues(
const BooleanConstraint& echo_cancellation_constraint,
AudioProcessingProperties* properties) const {
bool default_audio_processing_value =
GetDefaultValueForAudioProperties(echo_cancellation_constraint);
properties->goog_auto_gain_control &= default_audio_processing_value;
properties->goog_experimental_auto_gain_control &=
default_audio_processing_value;
properties->goog_experimental_echo_cancellation &=
default_audio_processing_value;
properties->goog_noise_suppression &= default_audio_processing_value;
properties->goog_experimental_noise_suppression &=
default_audio_processing_value;
properties->goog_highpass_filter &= default_audio_processing_value;
}
bool GetDefaultValueForAudioProperties(
const BooleanConstraint& ec_constraint) const {
DCHECK(!ec_mode_allowed_values_.is_universal());
if (ec_constraint.HasIdeal() &&
ec_allowed_values_.Contains(ec_constraint.Ideal()))
return is_device_capture_ && ec_constraint.Ideal();
if (ec_allowed_values_.Contains(true))
return is_device_capture_;
return false;
}
private:
static Score::EcModeScore GetEcModeScore(EchoCancellationType mode) {
switch (mode) {
case EchoCancellationType::kEchoCancellationDisabled:
return Score::EcModeScore::kDisabled;
case EchoCancellationType::kEchoCancellationSystem:
return Score::EcModeScore::kSystem;
case EchoCancellationType::kEchoCancellationAec3:
return Score::EcModeScore::kAec3;
}
}
static EchoCancellationTypeSet ToEchoCancellationTypes(const BoolSet ec_set) {
Vector<EchoCancellationType> types;
if (ec_set.Contains(false))
types.push_back(EchoCancellationType::kEchoCancellationDisabled);
if (ec_set.Contains(true)) {
types.push_back(EchoCancellationType::kEchoCancellationAec3);
types.push_back(EchoCancellationType::kEchoCancellationSystem);
}
return EchoCancellationTypeSet(types);
}
static bool ShouldUseExperimentalSystemEchoCanceller(
const media::AudioParameters& parameters) {
#if defined(OS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)
if (base::FeatureList::IsEnabled(features::kForceEnableSystemAec) &&
(parameters.effects() &
media::AudioParameters::EXPERIMENTAL_ECHO_CANCELLER)) {
return true;
}
#endif // defined(OS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)
return false;
}
EchoCancellationType SelectBestEcMode(
const ConstraintSet& constraint_set) const {
DCHECK(!IsEmpty());
DCHECK(!ec_mode_allowed_values_.is_universal());
// Try to use an ideal candidate, if supplied.
bool is_ec_preferred =
ShouldUseEchoCancellation(constraint_set.echo_cancellation,
constraint_set.goog_echo_cancellation);
if (!is_ec_preferred &&
ec_mode_allowed_values_.Contains(
EchoCancellationType::kEchoCancellationDisabled)) {
return EchoCancellationType::kEchoCancellationDisabled;
}
// If no ideal could be selected and the set contains only one value, pick
// that one.
if (ec_mode_allowed_values_.elements().size() == 1)
return ec_mode_allowed_values_.FirstElement();
// If no type has been selected, choose system if the device has the
// ECHO_CANCELLER flag set. Never automatically enable an experimental
// system echo canceller.
if (device_parameters_.IsValid() &&
ec_mode_allowed_values_.Contains(
EchoCancellationType::kEchoCancellationSystem) &&
(device_parameters_.effects() &
media::AudioParameters::ECHO_CANCELLER ||
ShouldUseExperimentalSystemEchoCanceller(device_parameters_))) {
return EchoCancellationType::kEchoCancellationSystem;
}
// At this point we have at least two elements, hence the only two options
// from which to select are either AEC3 or System, where AEC3 has higher
// priority.
if (ec_mode_allowed_values_.Contains(
EchoCancellationType::kEchoCancellationAec3)) {
return EchoCancellationType::kEchoCancellationAec3;
}
DCHECK(ec_mode_allowed_values_.Contains(
EchoCancellationType::kEchoCancellationDisabled));
return EchoCancellationType::kEchoCancellationDisabled;
}
// This function computes the fitness score of the given |ec_mode|. The
// fitness is determined by the ideal values of |ec_constraint|. If |ec_mode|
// satisfies the constraint, the fitness score results in a value of 1, and 0
// otherwise. If no ideal value is specified, the fitness is 1.
double Fitness(const EchoCancellationType& ec_mode,
const BooleanConstraint& ec_constraint) const {
return ec_constraint.HasIdeal()
? ((ec_constraint.Ideal() &&
ec_mode !=
EchoCancellationType::kEchoCancellationDisabled) ||
(!ec_constraint.Ideal() &&
ec_mode == EchoCancellationType::kEchoCancellationDisabled))
: 1.0;
}
bool EchoCancellationModeContains(bool ec) const {
DCHECK(!ec_mode_allowed_values_.is_universal());
if (ec) {
return ec_mode_allowed_values_.Contains(
EchoCancellationType::kEchoCancellationAec3) ||
ec_mode_allowed_values_.Contains(
EchoCancellationType::kEchoCancellationSystem);
}
return ec_mode_allowed_values_.Contains(
EchoCancellationType::kEchoCancellationDisabled);
}
bool ShouldUseEchoCancellation(
const BooleanConstraint& ec_constraint,
const BooleanConstraint& goog_ec_constraint) const {
DCHECK(!ec_mode_allowed_values_.is_universal());
if (ec_constraint.HasIdeal() &&
EchoCancellationModeContains(ec_constraint.Ideal()))
return ec_constraint.Ideal();
if (goog_ec_constraint.HasIdeal() &&
EchoCancellationModeContains(goog_ec_constraint.Ideal()))
return goog_ec_constraint.Ideal();
// Echo cancellation is enabled by default for device capture and disabled
// by default for content capture.
if (EchoCancellationModeContains(true) &&
EchoCancellationModeContains(false))
return is_device_capture_;
return EchoCancellationModeContains(true);
}
BoolSet ec_allowed_values_;
EchoCancellationTypeSet ec_mode_allowed_values_;
media::AudioParameters device_parameters_;
bool is_device_capture_;
};
class AutoGainControlContainer {
public:
explicit AutoGainControlContainer(BoolSet allowed_values = BoolSet())
: allowed_values_(std::move(allowed_values)) {}
const char* ApplyConstraintSet(const ConstraintSet& constraint_set) {
BoolSet agc_set = blink::media_constraints::BoolSetFromConstraint(
constraint_set.goog_auto_gain_control);
BoolSet experimentaL_agc_set =
blink::media_constraints::BoolSetFromConstraint(
constraint_set.goog_experimental_auto_gain_control);
// Apply autoGainControl/googAutoGainControl constraint.
allowed_values_ = allowed_values_.Intersection(agc_set);
if (IsEmpty())
return constraint_set.goog_auto_gain_control.GetName();
// Apply also googExperimentalAutoGainControl constraint.
allowed_values_ = allowed_values_.Intersection(experimentaL_agc_set);
return IsEmpty()
? constraint_set.goog_experimental_auto_gain_control.GetName()
: nullptr;
}
std::tuple<double, bool> SelectSettingsAndScore(
const ConstraintSet& constraint_set,
bool default_setting) const {
BooleanConstraint agc_constraint = constraint_set.goog_auto_gain_control;
BooleanConstraint experimental_agc_constraint =
constraint_set.goog_experimental_auto_gain_control;
if (agc_constraint.HasIdeal() || experimental_agc_constraint.HasIdeal()) {
bool agc_ideal =
agc_constraint.HasIdeal() ? agc_constraint.Ideal() : false;
bool experimentaL_agc_ideal = experimental_agc_constraint.HasIdeal()
? experimental_agc_constraint.Ideal()
: false;
bool combined_ideal = agc_ideal || experimentaL_agc_ideal;
if (allowed_values_.Contains(combined_ideal))
return std::make_tuple(1.0, combined_ideal);
}
if (allowed_values_.is_universal())
return std::make_tuple(0.0, default_setting);
return std::make_tuple(0.0, allowed_values_.FirstElement());
}
bool IsEmpty() const { return allowed_values_.IsEmpty(); }
private:
BoolSet allowed_values_;
};
// This container represents the supported audio settings for a given type of
// audio source. In practice, there are three types of sources: processed using
// APM, processed without APM, and unprocessed.
class ProcessingBasedContainer {
public:
// Creates an instance of ProcessingBasedContainer for the WebRTC processed
// source type. The source type allows (a) any type of echo cancellation,
// though the system echo cancellation type depends on the availability of the
// related |parameters.effects()|, and (b) any combination of processing
// properties settings.
static ProcessingBasedContainer CreateApmProcessedContainer(
const SourceInfo& source_info,
bool is_device_capture,
const media::AudioParameters& device_parameters,
bool is_reconfiguration_allowed) {
return ProcessingBasedContainer(
ProcessingType::kApmProcessed,
{EchoCancellationType::kEchoCancellationAec3,
EchoCancellationType::kEchoCancellationDisabled},
BoolSet(), /* auto_gain_control_set */
BoolSet(), /* goog_audio_mirroring_set */
BoolSet(), /* goog_experimental_echo_cancellation_set */
BoolSet(), /* goog_noise_suppression_set */
BoolSet(), /* goog_experimental_noise_suppression_set */
BoolSet(), /* goog_highpass_filter_set */
IntRangeSet::FromValue(GetSampleSize()), /* sample_size_range */
IntRangeSet::FromValue(1), /* channels_range */
IntRangeSet::FromValue(
blink::kAudioProcessingSampleRate), /* sample_rate_range */
source_info, is_device_capture, device_parameters,
is_reconfiguration_allowed);
}
// Creates an instance of ProcessingBasedContainer for the processed source
// type. The source type allows (a) either system echo cancellation, if
// allowed by the |parameters.effects()|, or none, (b) enabled or disabled
// audio mirroring, while (c) all other processing properties settings cannot
// be enabled.
static ProcessingBasedContainer CreateNoApmProcessedContainer(
const SourceInfo& source_info,
bool is_device_capture,
const media::AudioParameters& device_parameters,
bool is_reconfiguration_allowed) {
return ProcessingBasedContainer(
ProcessingType::kNoApmProcessed,
{EchoCancellationType::kEchoCancellationDisabled},
BoolSet({false}), /* auto_gain_control_set */
BoolSet(), /* goog_audio_mirroring_set */
BoolSet({false}), /* goog_experimental_echo_cancellation_set */
BoolSet({false}), /* goog_noise_suppression_set */
BoolSet({false}), /* goog_experimental_noise_suppression_set */
BoolSet({false}), /* goog_highpass_filter_set */
IntRangeSet::FromValue(GetSampleSize()), /* sample_size_range */
IntRangeSet::FromValue(
device_parameters.channels()), /* channels_range */
IntRangeSet::FromValue(
device_parameters.sample_rate()), /* sample_rate_range */
source_info, is_device_capture, device_parameters,
is_reconfiguration_allowed);
}
// Creates an instance of ProcessingBasedContainer for the unprocessed source
// type. The source type allows (a) either system echo cancellation, if
// allowed by the |parameters.effects()|, or none, while (c) all processing
// properties settings cannot be enabled.
static ProcessingBasedContainer CreateUnprocessedContainer(
const SourceInfo& source_info,
bool is_device_capture,
const media::AudioParameters& device_parameters,
bool is_reconfiguration_allowed) {
return ProcessingBasedContainer(
ProcessingType::kUnprocessed,
{EchoCancellationType::kEchoCancellationDisabled},
BoolSet({false}), /* auto_gain_control_set */
BoolSet({false}), /* goog_audio_mirroring_set */
BoolSet({false}), /* goog_experimental_echo_cancellation_set */
BoolSet({false}), /* goog_noise_suppression_set */
BoolSet({false}), /* goog_experimental_noise_suppression_set */
BoolSet({false}), /* goog_highpass_filter_set */
IntRangeSet::FromValue(GetSampleSize()), /* sample_size_range */
IntRangeSet::FromValue(
device_parameters.channels()), /* channels_range */
IntRangeSet::FromValue(
device_parameters.sample_rate()), /* sample_rate_range */
source_info, is_device_capture, device_parameters,
is_reconfiguration_allowed);
}
const char* ApplyConstraintSet(const ConstraintSet& constraint_set) {
const char* failed_constraint_name = nullptr;
failed_constraint_name =
echo_cancellation_container_.ApplyConstraintSet(constraint_set);
if (failed_constraint_name)
return failed_constraint_name;
failed_constraint_name =
auto_gain_control_container_.ApplyConstraintSet(constraint_set);
if (failed_constraint_name)
return failed_constraint_name;
failed_constraint_name =
sample_size_container_.ApplyConstraintSet(constraint_set.sample_size);
if (failed_constraint_name)
return failed_constraint_name;
failed_constraint_name =
channels_container_.ApplyConstraintSet(constraint_set.channel_count);
if (failed_constraint_name)
return failed_constraint_name;
failed_constraint_name =
sample_rate_container_.ApplyConstraintSet(constraint_set.sample_rate);
if (failed_constraint_name)
return failed_constraint_name;
failed_constraint_name =
latency_container_.ApplyConstraintSet(constraint_set.latency);
if (failed_constraint_name)
return failed_constraint_name;
for (auto& info : kBooleanPropertyContainerInfoMap) {
failed_constraint_name =
boolean_containers_[info.index].ApplyConstraintSet(
constraint_set.*(info.constraint_member));
if (failed_constraint_name)
return failed_constraint_name;
}
return failed_constraint_name;
}
std::tuple<Score, AudioProcessingProperties, base::Optional<int>>
SelectSettingsAndScore(const ConstraintSet& constraint_set,
bool should_disable_hardware_noise_suppression,
const media::AudioParameters& parameters) const {
DCHECK(!IsEmpty());
Score score(0.0);
double sub_score(0.0);
std::tie(sub_score, std::ignore) =
sample_size_container_.SelectSettingsAndScore(
constraint_set.sample_size, GetSampleSize());
score += sub_score;
base::Optional<int> channels;
std::tie(sub_score, channels) = channels_container_.SelectSettingsAndScore(
constraint_set.channel_count);
DCHECK(channels);
score += sub_score;
base::Optional<int> sample_size;
std::tie(sub_score, sample_size) =
sample_rate_container_.SelectSettingsAndScore(
constraint_set.sample_rate);
DCHECK(sample_size != base::nullopt);
score += sub_score;
base::Optional<double> latency;
std::tie(sub_score, latency) =
latency_container_.SelectSettingsAndScore(constraint_set.latency);
score += sub_score;
// Only request an explicit change to the buffer size for the unprocessed
// container, and only if it's based on a specific user constraint.
base::Optional<int> requested_buffer_size;
if (processing_type_ == ProcessingType::kUnprocessed && latency &&
!constraint_set.latency.IsUnconstrained()) {
int min_buffer_size, max_buffer_size;
std::tie(min_buffer_size, max_buffer_size) =
GetMinMaxBufferSizesForAudioParameters(parameters);
requested_buffer_size = media::AudioLatency::GetExactBufferSize(
base::TimeDelta::FromSecondsD(*latency), parameters.sample_rate(),
parameters.frames_per_buffer(), min_buffer_size, max_buffer_size,
max_buffer_size);
}
AudioProcessingProperties properties;
Score ec_score(0.0);
std::tie(ec_score, properties.echo_cancellation_type) =
echo_cancellation_container_.SelectSettingsAndScore(constraint_set);
score += ec_score;
// Update the default settings for each audio-processing properties
// according to |echo_cancellation| and whether the source considered is
// device capture.
echo_cancellation_container_.UpdateDefaultValues(
constraint_set.echo_cancellation, &properties);
std::tie(sub_score, properties.goog_auto_gain_control) =
auto_gain_control_container_.SelectSettingsAndScore(
constraint_set, properties.goog_auto_gain_control);
score += sub_score;
// Let goog_experimental_auto_gain_control match the value decided for
// goog_auto_gain_control.
// TODO(crbug.com/924485): entirely remove
// goog_experimental_auto_gain_control in the AudioProcessingProperties
// object since no longer needed.
properties.goog_experimental_auto_gain_control =
properties.goog_auto_gain_control;
for (size_t i = 0; i < kNumBooleanContainerIds; ++i) {
auto& info = kBooleanPropertyContainerInfoMap[i];
std::tie(sub_score, properties.*(info.property_member)) =
boolean_containers_[info.index].SelectSettingsAndScore(
constraint_set.*(info.constraint_member),
properties.*(info.property_member));
score += sub_score;
}
score.set_processing_priority(
GetProcessingPriority(constraint_set.echo_cancellation));
return std::make_tuple(score, properties, requested_buffer_size);
}
// The ProcessingBasedContainer is considered empty if at least one of the
// containers owned by it is empty.
bool IsEmpty() const {
DCHECK(!boolean_containers_.empty());
for (auto& container : boolean_containers_) {
if (container.IsEmpty())
return true;
}
return echo_cancellation_container_.IsEmpty() ||
auto_gain_control_container_.IsEmpty() ||
sample_size_container_.IsEmpty() || channels_container_.IsEmpty() ||
sample_rate_container_.IsEmpty() || latency_container_.IsEmpty();
}
ProcessingType processing_type() const { return processing_type_; }
private:
enum BooleanContainerId {
kGoogAudioMirroring,
kGoogExperimentalEchoCancellation,
kGoogNoiseSuppression,
kGoogExperimentalNoiseSuppression,
kGoogHighpassFilter,
kNumBooleanContainerIds
};
// This struct groups related fields or entries from
// AudioProcessingProperties,
// ProcessingBasedContainer::boolean_containers_, and
// MediaTrackConstraintSetPlatform.
struct BooleanPropertyContainerInfo {
BooleanContainerId index;
BooleanConstraint ConstraintSet::*constraint_member;
bool AudioProcessingProperties::*property_member;
};
static constexpr BooleanPropertyContainerInfo
kBooleanPropertyContainerInfoMap[] = {
{kGoogAudioMirroring, &ConstraintSet::goog_audio_mirroring,
&AudioProcessingProperties::goog_audio_mirroring},
{kGoogExperimentalEchoCancellation,
&ConstraintSet::goog_experimental_echo_cancellation,
&AudioProcessingProperties::goog_experimental_echo_cancellation},
{kGoogNoiseSuppression, &ConstraintSet::goog_noise_suppression,
&AudioProcessingProperties::goog_noise_suppression},
{kGoogExperimentalNoiseSuppression,
&ConstraintSet::goog_experimental_noise_suppression,
&AudioProcessingProperties::goog_experimental_noise_suppression},
{kGoogHighpassFilter, &ConstraintSet::goog_highpass_filter,
&AudioProcessingProperties::goog_highpass_filter}};
// Private constructor intended to instantiate different variants of this
// class based on the initial values provided. The appropriate way to
// instantiate this class is via the three factory methods provided.
// System echo cancellation should not be explicitly included in
// |echo_cancellation_type|. It is added automatically based on the value of
// |device_parameters|.
ProcessingBasedContainer(ProcessingType processing_type,
Vector<EchoCancellationType> echo_cancellation_types,
BoolSet auto_gain_control_set,
BoolSet goog_audio_mirroring_set,
BoolSet goog_experimental_echo_cancellation_set,
BoolSet goog_noise_suppression_set,
BoolSet goog_experimental_noise_suppression_set,
BoolSet goog_highpass_filter_set,
IntRangeSet sample_size_range,
IntRangeSet channels_range,
IntRangeSet sample_rate_range,
SourceInfo source_info,
bool is_device_capture,
media::AudioParameters device_parameters,
bool is_reconfiguration_allowed)
: processing_type_(processing_type),
sample_size_container_(sample_size_range),
channels_container_(channels_range),
sample_rate_container_(sample_rate_range),
latency_container_(
GetAllowedLatency(processing_type, device_parameters)) {
// If the parameters indicate that system echo cancellation is available, we
// add such value in the allowed values for the EC type.
if (device_parameters.effects() & media::AudioParameters::ECHO_CANCELLER ||
device_parameters.effects() &
media::AudioParameters::EXPERIMENTAL_ECHO_CANCELLER) {
echo_cancellation_types.push_back(
EchoCancellationType::kEchoCancellationSystem);
}
echo_cancellation_container_ = EchoCancellationContainer(
echo_cancellation_types, source_info.HasActiveSource(),
is_device_capture, device_parameters, source_info.properties(),
is_reconfiguration_allowed);
auto_gain_control_container_ =
AutoGainControlContainer(auto_gain_control_set);
boolean_containers_[kGoogAudioMirroring] =
BooleanContainer(goog_audio_mirroring_set);
boolean_containers_[kGoogExperimentalEchoCancellation] =
BooleanContainer(goog_experimental_echo_cancellation_set);
boolean_containers_[kGoogNoiseSuppression] =
BooleanContainer(goog_noise_suppression_set);
boolean_containers_[kGoogExperimentalNoiseSuppression] =
BooleanContainer(goog_experimental_noise_suppression_set);
boolean_containers_[kGoogHighpassFilter] =
BooleanContainer(goog_highpass_filter_set);
// Allow the full set of supported values when the device is not open or
// when the candidate settings would open the device using an unprocessed
// source.
if (!source_info.HasActiveSource() ||
(is_reconfiguration_allowed &&
processing_type_ == ProcessingType::kUnprocessed)) {
return;
}
// If the device is already opened, restrict supported values for
// non-reconfigurable settings to what is already configured. The rationale
// for this is that opening multiple instances of the APM is costly.
// TODO(crbug.com/1147928): Consider removing this restriction.
auto_gain_control_container_ = AutoGainControlContainer(
BoolSet({source_info.properties().goog_auto_gain_control}));
for (size_t i = 0; i < kNumBooleanContainerIds; ++i) {
auto& info = kBooleanPropertyContainerInfoMap[i];
boolean_containers_[info.index] = BooleanContainer(
BoolSet({source_info.properties().*(info.property_member)}));
}
DCHECK(source_info.channels());
channels_container_ =
IntegerContainer(IntRangeSet::FromValue(*source_info.channels()));
DCHECK(source_info.sample_rate() != base::nullopt);
sample_rate_container_ =
IntegerContainer(IntRangeSet::FromValue(*source_info.sample_rate()));
DCHECK(source_info.latency() != base::nullopt);
latency_container_ =
DoubleContainer(DoubleRangeSet::FromValue(*source_info.latency()));
}
// The allowed latency is expressed in a range latencies in seconds.
static const DoubleRangeSet GetAllowedLatency(
ProcessingType processing_type,
const media::AudioParameters& device_parameters) {
double fallback_latency =
static_cast<double>(blink::kFallbackAudioLatencyMs) / 1000;
double device_latency = device_parameters.GetBufferDuration().InSecondsF();
double allowed_latency = device_parameters.frames_per_buffer() > 0
? device_latency
: fallback_latency;
switch (processing_type) {
case ProcessingType::kApmProcessed:
return DoubleRangeSet::FromValue(fallback_latency);
case ProcessingType::kNoApmProcessed:
return DoubleRangeSet::FromValue(allowed_latency);
case ProcessingType::kUnprocessed:
double min_latency, max_latency;
std::tie(min_latency, max_latency) =
GetMinMaxLatenciesForAudioParameters(device_parameters);
return DoubleRangeSet(min_latency, max_latency);
}
}
// The priority of each processing-based container depends on the default
// value assigned to the audio processing properties. When the value is true
// the preference gives higher priority to the WebRTC processing.
// On the contrary, if the value is false the preference is flipped towards
// the option without processing.
int GetProcessingPriority(const BooleanConstraint& ec_constraint) const {
bool use_processing_by_default =
echo_cancellation_container_.GetDefaultValueForAudioProperties(
ec_constraint);
switch (processing_type_) {
case ProcessingType::kUnprocessed:
return use_processing_by_default ? 1 : 3;
case ProcessingType::kNoApmProcessed:
return 2;
case ProcessingType::kApmProcessed:
return use_processing_by_default ? 3 : 1;
}
}
ProcessingType processing_type_;
std::array<BooleanContainer, kNumBooleanContainerIds> boolean_containers_;
EchoCancellationContainer echo_cancellation_container_;
AutoGainControlContainer auto_gain_control_container_;
IntegerContainer sample_size_container_;
IntegerContainer channels_container_;
IntegerContainer sample_rate_container_;
DoubleContainer latency_container_;
};
constexpr ProcessingBasedContainer::BooleanPropertyContainerInfo
ProcessingBasedContainer::kBooleanPropertyContainerInfoMap[];
// Container for the constrainable properties of a single audio device.
class DeviceContainer {
public:
DeviceContainer(const AudioDeviceCaptureCapability& capability,
bool is_device_capture,
bool is_reconfiguration_allowed)
: device_parameters_(capability.Parameters()) {
if (!capability.DeviceID().IsEmpty()) {
device_id_container_ =
StringContainer(StringSet({capability.DeviceID().Utf8()}));
}
if (!capability.GroupID().IsEmpty()) {
group_id_container_ =
StringContainer(StringSet({capability.GroupID().Utf8()}));
}
// If the device is in use, a source will be provided and all containers
// must be initialized such that their only supported values correspond to
// the source settings. Otherwise, the containers are initialized to contain
// all possible values.
SourceInfo source_info =
InfoFromSource(capability.source(), device_parameters_.effects());
// Three variations of the processing-based container. Each variant is
// associated to a different type of audio processing configuration, namely
// unprocessed, processed by WebRTC, or processed by other means.
processing_based_containers_.push_back(
ProcessingBasedContainer::CreateUnprocessedContainer(
source_info, is_device_capture, device_parameters_,
is_reconfiguration_allowed));
processing_based_containers_.push_back(
ProcessingBasedContainer::CreateNoApmProcessedContainer(
source_info, is_device_capture, device_parameters_,
is_reconfiguration_allowed));
processing_based_containers_.push_back(
ProcessingBasedContainer::CreateApmProcessedContainer(
source_info, is_device_capture, device_parameters_,
is_reconfiguration_allowed));
DCHECK_EQ(processing_based_containers_.size(), 3u);
if (source_info.type() == SourceType::kNone)
return;
blink::MediaStreamAudioSource* source = capability.source();
boolean_containers_[kDisableLocalEcho] =
BooleanContainer(BoolSet({source->disable_local_echo()}));
boolean_containers_[kRenderToAssociatedSink] =
BooleanContainer(BoolSet({source->RenderToAssociatedSinkEnabled()}));
#if DCHECK_IS_ON()
for (const auto& container : boolean_containers_)
DCHECK(!container.IsEmpty());
#endif
}
const char* ApplyConstraintSet(const ConstraintSet& constraint_set) {
const char* failed_constraint_name;
failed_constraint_name =
device_id_container_.ApplyConstraintSet(constraint_set.device_id);
if (failed_constraint_name)
return failed_constraint_name;
failed_constraint_name =
group_id_container_.ApplyConstraintSet(constraint_set.group_id);
if (failed_constraint_name)
return failed_constraint_name;
for (size_t i = 0; i < kNumBooleanContainerIds; ++i) {
auto& info = kBooleanPropertyContainerInfoMap[i];
failed_constraint_name =
boolean_containers_[info.index].ApplyConstraintSet(
constraint_set.*(info.constraint_member));
if (failed_constraint_name)
return failed_constraint_name;
}
// For each processing based container, apply the constraints and only fail
// if all of them failed.
for (auto* it = processing_based_containers_.begin();
it != processing_based_containers_.end();) {
DCHECK(!it->IsEmpty());
failed_constraint_name = it->ApplyConstraintSet(constraint_set);
if (failed_constraint_name)
processing_based_containers_.erase(it);
else
++it;
}
if (processing_based_containers_.IsEmpty()) {
DCHECK_NE(failed_constraint_name, nullptr);
return failed_constraint_name;
}
return nullptr;
}
std::tuple<Score, AudioCaptureSettings> SelectSettingsAndScore(
const ConstraintSet& constraint_set,
bool is_destkop_source,
bool should_disable_hardware_noise_suppression,
std::string default_device_id) const {
DCHECK(!IsEmpty());
Score score(0.0);
double sub_score = 0.0;
std::string device_id;
std::tie(sub_score, device_id) =
device_id_container_.SelectSettingsAndScore(constraint_set.device_id,
default_device_id);
score += sub_score;
std::tie(sub_score, std::ignore) =
group_id_container_.SelectSettingsAndScore(constraint_set.group_id,
std::string());
score += sub_score;
bool disable_local_echo;
std::tie(sub_score, disable_local_echo) =
boolean_containers_[kDisableLocalEcho].SelectSettingsAndScore(
constraint_set.disable_local_echo, !is_destkop_source);
score += sub_score;
bool render_to_associated_sink;
std::tie(sub_score, render_to_associated_sink) =
boolean_containers_[kRenderToAssociatedSink].SelectSettingsAndScore(
constraint_set.render_to_associated_sink, false);
score += sub_score;
// To determine which properties to use, we have to compare and compute the
// scores of each properties set and use the best performing one. In this
// loop we are also determining the best settings that should be applied to
// the best performing candidate.
Score best_score(-1.0);
AudioProcessingProperties best_properties;
const ProcessingBasedContainer* best_container = nullptr;
base::Optional<int> best_requested_buffer_size;
for (const auto& container : processing_based_containers_) {
if (container.IsEmpty())
continue;
Score container_score(0.0);
AudioProcessingProperties container_properties;
base::Optional<int> requested_buffer_size;
std::tie(container_score, container_properties, requested_buffer_size) =
container.SelectSettingsAndScore(
constraint_set, should_disable_hardware_noise_suppression,
device_parameters_);
if (container_score > best_score) {
best_score = container_score;
best_properties = container_properties;
best_container = &container;
best_requested_buffer_size = requested_buffer_size;
}
}
DCHECK_NE(best_container, nullptr);
score += best_score;
// Update |properties.disable_hw_noise_suppression| depending on a related
// experiment that can force-disable HW noise suppression.
best_properties.disable_hw_noise_suppression =
should_disable_hardware_noise_suppression &&
best_properties.echo_cancellation_type ==
EchoCancellationType::kEchoCancellationDisabled;
// The score at this point can be considered complete only when the settings
// are compared against the default device id, which is used as arbitrator
// in case multiple candidates are available.
return std::make_tuple(
score, AudioCaptureSettings(
device_id, best_requested_buffer_size, disable_local_echo,
render_to_associated_sink, best_container->processing_type(),
best_properties));
}
// The DeviceContainer is considered empty if at least one of the
// containers owned is empty.
bool IsEmpty() const {
DCHECK(!boolean_containers_.empty());
for (auto& container : boolean_containers_) {
if (container.IsEmpty())
return true;
}
return device_id_container_.IsEmpty() || group_id_container_.IsEmpty();
}
private:
enum BooleanContainerId {
kDisableLocalEcho,
kRenderToAssociatedSink,
kNumBooleanContainerIds
};
// This struct groups related fields or entries from
// DeviceContainer::boolean_containers_ and MediaTrackConstraintSetPlatform.
struct BooleanPropertyContainerInfo {
BooleanContainerId index;
BooleanConstraint ConstraintSet::*constraint_member;
};
static constexpr BooleanPropertyContainerInfo
kBooleanPropertyContainerInfoMap[] = {
{kDisableLocalEcho, &ConstraintSet::disable_local_echo},
{kRenderToAssociatedSink, &ConstraintSet::render_to_associated_sink}};
// Utility function to determine which version of this class should be
// allocated depending on the |source| provided.
static SourceInfo InfoFromSource(blink::MediaStreamAudioSource* source,
int effects) {
SourceType source_type;
AudioProcessingProperties properties;
auto* processed_source = ProcessedLocalAudioSource::From(source);
base::Optional<int> channels;
base::Optional<int> sample_rate;
base::Optional<double> latency;
if (!source) {
source_type = SourceType::kNone;
} else {
media::AudioParameters source_parameters = source->GetAudioParameters();
channels = source_parameters.channels();
sample_rate = source_parameters.sample_rate();
latency = source_parameters.GetBufferDuration().InSecondsF();
properties = *(source->GetAudioProcessingProperties());
if (!processed_source) {
source_type = SourceType::kUnprocessed;
properties.DisableDefaultProperties();
// It is possible, however, that the HW echo canceller is enabled. In
// such case the property for echo cancellation type should be updated
// accordingly.
if (effects & media::AudioParameters::ECHO_CANCELLER) {
properties.echo_cancellation_type =
EchoCancellationType::kEchoCancellationSystem;
}
} else {
source_type = properties.EchoCancellationIsWebRtcProvided()
? SourceType::kApmProcessed
: SourceType::kNoApmProcessed;
properties = processed_source->audio_processing_properties();
}
}
return SourceInfo(source_type, properties, channels, sample_rate, latency);
}
media::AudioParameters device_parameters_;
StringContainer device_id_container_;
StringContainer group_id_container_;
std::array<BooleanContainer, kNumBooleanContainerIds> boolean_containers_;
Vector<ProcessingBasedContainer> processing_based_containers_;
};
constexpr DeviceContainer::BooleanPropertyContainerInfo
DeviceContainer::kBooleanPropertyContainerInfoMap[];
// This class represents a set of possible candidate settings. The
// SelectSettings algorithm starts with a set containing all possible candidates
// based on system/hardware capabilities and/or allowed values for supported
// properties. The set is then reduced progressively as the basic and advanced
// constraint sets are applied. In the end, if the set of candidates is empty,
// SelectSettings fails. If not, the ideal values (if any) or tie breaker rules
// are used to select the final settings based on the candidates that survived
// the application of the constraint sets. This class is implemented as a
// collection of more specific sets for the various supported properties. If any
// of the specific sets is empty, the whole CandidatesContainer is considered
// empty as well.
class CandidatesContainer {
public:
CandidatesContainer(const AudioDeviceCaptureCapabilities& capabilities,
std::string& media_stream_source,
std::string& default_device_id,
bool is_reconfiguration_allowed)
: default_device_id_(default_device_id) {
for (const auto& capability : capabilities) {
devices_.emplace_back(capability, media_stream_source.empty(),
is_reconfiguration_allowed);
DCHECK(!devices_.back().IsEmpty());
}
}
const char* ApplyConstraintSet(const ConstraintSet& constraint_set) {
const char* latest_failed_constraint_name = nullptr;
for (auto* it = devices_.begin(); it != devices_.end();) {
DCHECK(!it->IsEmpty());
auto* failed_constraint_name = it->ApplyConstraintSet(constraint_set);
if (failed_constraint_name) {
latest_failed_constraint_name = failed_constraint_name;
devices_.erase(it);
} else {
++it;
}
}
return IsEmpty() ? latest_failed_constraint_name : nullptr;
}
std::tuple<Score, AudioCaptureSettings> SelectSettingsAndScore(
const ConstraintSet& constraint_set,
bool is_desktop_source,
bool should_disable_hardware_noise_suppression) const {
DCHECK(!IsEmpty());
// Make a copy of the settings initially provided, to track the default
// settings.
AudioCaptureSettings best_settings;
Score best_score(-1.0);
for (const auto& candidate : devices_) {
Score score(0.0);
AudioCaptureSettings settings;
std::tie(score, settings) = candidate.SelectSettingsAndScore(
constraint_set, is_desktop_source,
should_disable_hardware_noise_suppression, default_device_id_);
score += default_device_id_ == settings.device_id();
if (score > best_score) {
best_score = score;
best_settings = std::move(settings);
}
}
return std::make_tuple(best_score, best_settings);
}
bool IsEmpty() const { return devices_.IsEmpty(); }
private:
std::string default_device_id_;
Vector<DeviceContainer> devices_;
};
std::string GetMediaStreamSource(const MediaConstraints& constraints) {
std::string source;
if (constraints.Basic().media_stream_source.HasIdeal() &&
constraints.Basic().media_stream_source.Ideal().size() > 0) {
source = constraints.Basic().media_stream_source.Ideal()[0].Utf8();
}
if (constraints.Basic().media_stream_source.HasExact() &&
constraints.Basic().media_stream_source.Exact().size() > 0) {
source = constraints.Basic().media_stream_source.Exact()[0].Utf8();
}
return source;
}
} // namespace
AudioDeviceCaptureCapability::AudioDeviceCaptureCapability()
: parameters_(media::AudioParameters::UnavailableDeviceParams()) {}
AudioDeviceCaptureCapability::AudioDeviceCaptureCapability(
blink::MediaStreamAudioSource* source)
: source_(source) {}
AudioDeviceCaptureCapability::AudioDeviceCaptureCapability(
String device_id,
String group_id,
const media::AudioParameters& parameters)
: device_id_(std::move(device_id)),
group_id_(std::move(group_id)),
parameters_(parameters) {
DCHECK(!device_id_.IsEmpty());
}
AudioDeviceCaptureCapability::AudioDeviceCaptureCapability(
const AudioDeviceCaptureCapability& other) = default;
String AudioDeviceCaptureCapability::DeviceID() const {
return source_ ? String(source_->device().id.data()) : device_id_;
}
String AudioDeviceCaptureCapability::GroupID() const {
return source_ && source_->device().group_id
? String(source_->device().group_id->data())
: group_id_;
}
const media::AudioParameters& AudioDeviceCaptureCapability::Parameters() const {
return source_ ? source_->device().input : parameters_;
}
AudioCaptureSettings SelectSettingsAudioCapture(
const AudioDeviceCaptureCapabilities& capabilities,
const MediaConstraints& constraints,
bool should_disable_hardware_noise_suppression,
bool is_reconfiguration_allowed) {
if (capabilities.IsEmpty())
return AudioCaptureSettings();
std::string media_stream_source = GetMediaStreamSource(constraints);
std::string default_device_id;
bool is_device_capture = media_stream_source.empty();
if (is_device_capture)
default_device_id = capabilities.begin()->DeviceID().Utf8();
CandidatesContainer candidates(capabilities, media_stream_source,
default_device_id, is_reconfiguration_allowed);
DCHECK(!candidates.IsEmpty());
auto* failed_constraint_name =
candidates.ApplyConstraintSet(constraints.Basic());
if (failed_constraint_name)
return AudioCaptureSettings(failed_constraint_name);
for (const auto& advanced_set : constraints.Advanced()) {
CandidatesContainer copy = candidates;
failed_constraint_name = candidates.ApplyConstraintSet(advanced_set);
if (failed_constraint_name)
candidates = std::move(copy);
}
DCHECK(!candidates.IsEmpty());
// Score is ignored as it is no longer needed.
AudioCaptureSettings settings;
std::tie(std::ignore, settings) = candidates.SelectSettingsAndScore(
constraints.Basic(),
media_stream_source == blink::kMediaStreamSourceDesktop,
should_disable_hardware_noise_suppression);
DCHECK_EQ(settings.audio_processing_properties().goog_auto_gain_control,
settings.audio_processing_properties()
.goog_experimental_auto_gain_control);
return settings;
}
AudioCaptureSettings SelectSettingsAudioCapture(
blink::MediaStreamAudioSource* source,
const MediaConstraints& constraints) {
DCHECK(source);
if (source->device().type !=
blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE &&
source->device().type !=
blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE &&
source->device().type !=
blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE) {
return AudioCaptureSettings();
}
std::string media_stream_source = GetMediaStreamSource(constraints);
if (source->device().type ==
blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE &&
!media_stream_source.empty()) {
return AudioCaptureSettings(
constraints.Basic().media_stream_source.GetName());
}
if (source->device().type ==
blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE &&
!media_stream_source.empty() &&
media_stream_source != blink::kMediaStreamSourceTab) {
return AudioCaptureSettings(
constraints.Basic().media_stream_source.GetName());
}
if (source->device().type ==
blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE &&
!media_stream_source.empty() &&
media_stream_source != blink::kMediaStreamSourceSystem &&
media_stream_source != blink::kMediaStreamSourceDesktop) {
return AudioCaptureSettings(
constraints.Basic().media_stream_source.GetName());
}
AudioDeviceCaptureCapabilities capabilities = {
AudioDeviceCaptureCapability(source)};
bool should_disable_hardware_noise_suppression =
!(source->device().input.effects() &
media::AudioParameters::NOISE_SUPPRESSION);
return SelectSettingsAudioCapture(capabilities, constraints,
should_disable_hardware_noise_suppression);
}
std::tuple<int, int> GetMinMaxBufferSizesForAudioParameters(
const media::AudioParameters& parameters) {
const int default_buffer_size = parameters.frames_per_buffer();
DCHECK_GT(default_buffer_size, 0);
const base::Optional<media::AudioParameters::HardwareCapabilities>
hardware_capabilities = parameters.hardware_capabilities();
// Only support platforms where we have both fixed min and max buffer size
// values in order to simplify comparison logic.
DCHECK(!hardware_capabilities ||
(hardware_capabilities &&
// Windows returns a HardwareCapabilities with both values set to 0 if
// they're unknown rather than returning null.
((hardware_capabilities->min_frames_per_buffer == 0 &&
hardware_capabilities->max_frames_per_buffer == 0) ||
(hardware_capabilities->min_frames_per_buffer > 0 &&
hardware_capabilities->max_frames_per_buffer > 0))))
<< "Variable input latency requires both a min and max to be set";
return (hardware_capabilities &&
hardware_capabilities->min_frames_per_buffer > 0 &&
hardware_capabilities->max_frames_per_buffer > 0)
? std::make_tuple(hardware_capabilities->min_frames_per_buffer,
hardware_capabilities->max_frames_per_buffer)
: std::make_tuple(default_buffer_size, default_buffer_size);
}
std::tuple<double, double> GetMinMaxLatenciesForAudioParameters(
const media::AudioParameters& parameters) {
int min_buffer_size, max_buffer_size;
std::tie(min_buffer_size, max_buffer_size) =
GetMinMaxBufferSizesForAudioParameters(parameters);
// Doing the microseconds conversion to match what is done in
// AudioParameters::GetBufferDuration() so that values reported to the user
// are truncated consistently to the microseconds decimal place.
return std::make_tuple(
base::TimeDelta::FromMicroseconds(
static_cast<int64_t>(min_buffer_size *
base::Time::kMicrosecondsPerSecond /
static_cast<float>(parameters.sample_rate())))
.InSecondsF(),
base::TimeDelta::FromMicroseconds(
static_cast<int64_t>(max_buffer_size *
base::Time::kMicrosecondsPerSecond /
static_cast<float>(parameters.sample_rate())))
.InSecondsF());
}
} // namespace blink