зеркало из https://github.com/mozilla/gecko-dev.git
504 строки
15 KiB
C++
504 строки
15 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "MediaTrackConstraints.h"
|
|
#include "nsIScriptError.h"
|
|
#include "mozilla/dom/MediaStreamTrackBinding.h"
|
|
|
|
#include <limits>
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
|
|
namespace mozilla {
|
|
|
|
using dom::ConstrainBooleanParameters;
|
|
using dom::OwningLongOrConstrainLongRange;
|
|
|
|
template<class ValueType>
|
|
template<class ConstrainRange>
|
|
void
|
|
NormalizedConstraintSet::Range<ValueType>::SetFrom(const ConstrainRange& aOther)
|
|
{
|
|
if (aOther.mIdeal.WasPassed()) {
|
|
mIdeal.emplace(aOther.mIdeal.Value());
|
|
}
|
|
if (aOther.mExact.WasPassed()) {
|
|
mMin = aOther.mExact.Value();
|
|
mMax = aOther.mExact.Value();
|
|
} else {
|
|
if (aOther.mMin.WasPassed()) {
|
|
mMin = aOther.mMin.Value();
|
|
}
|
|
if (aOther.mMax.WasPassed()) {
|
|
mMax = aOther.mMax.Value();
|
|
}
|
|
}
|
|
}
|
|
|
|
// The Range code works surprisingly well for bool, except when averaging ideals.
|
|
template<>
|
|
bool
|
|
NormalizedConstraintSet::Range<bool>::Merge(const Range& aOther) {
|
|
if (!Intersects(aOther)) {
|
|
return false;
|
|
}
|
|
Intersect(aOther);
|
|
|
|
// To avoid "unsafe use of type 'bool'", we keep counter in mMergeDenominator
|
|
uint32_t counter = mMergeDenominator >> 16;
|
|
uint32_t denominator = mMergeDenominator & 0xffff;
|
|
|
|
if (aOther.mIdeal.isSome()) {
|
|
if (mIdeal.isNothing()) {
|
|
mIdeal.emplace(aOther.Get(false));
|
|
counter = aOther.Get(false);
|
|
denominator = 1;
|
|
} else {
|
|
if (!denominator) {
|
|
counter = Get(false);
|
|
denominator = 1;
|
|
}
|
|
counter += aOther.Get(false);
|
|
denominator++;
|
|
}
|
|
}
|
|
mMergeDenominator = ((counter & 0xffff) << 16) + (denominator & 0xffff);
|
|
return true;
|
|
}
|
|
|
|
template<>
|
|
void
|
|
NormalizedConstraintSet::Range<bool>::FinalizeMerge()
|
|
{
|
|
if (mMergeDenominator) {
|
|
uint32_t counter = mMergeDenominator >> 16;
|
|
uint32_t denominator = mMergeDenominator & 0xffff;
|
|
|
|
*mIdeal = !!(counter / denominator);
|
|
mMergeDenominator = 0;
|
|
}
|
|
}
|
|
|
|
NormalizedConstraintSet::LongRange::LongRange(
|
|
LongPtrType aMemberPtr,
|
|
const char* aName,
|
|
const dom::OwningLongOrConstrainLongRange& aOther,
|
|
bool advanced,
|
|
nsTArray<MemberPtrType>* aList)
|
|
: Range<int32_t>((MemberPtrType)aMemberPtr, aName,
|
|
1 + INT32_MIN, INT32_MAX, // +1 avoids Windows compiler bug
|
|
aList)
|
|
{
|
|
if (aOther.IsLong()) {
|
|
if (advanced) {
|
|
mMin = mMax = aOther.GetAsLong();
|
|
} else {
|
|
mIdeal.emplace(aOther.GetAsLong());
|
|
}
|
|
} else {
|
|
SetFrom(aOther.GetAsConstrainLongRange());
|
|
}
|
|
}
|
|
|
|
NormalizedConstraintSet::LongLongRange::LongLongRange(
|
|
LongLongPtrType aMemberPtr,
|
|
const char* aName,
|
|
const long long& aOther,
|
|
nsTArray<MemberPtrType>* aList)
|
|
: Range<int64_t>((MemberPtrType)aMemberPtr, aName,
|
|
1 + INT64_MIN, INT64_MAX, // +1 avoids Windows compiler bug
|
|
aList)
|
|
{
|
|
mIdeal.emplace(aOther);
|
|
}
|
|
|
|
NormalizedConstraintSet::DoubleRange::DoubleRange(
|
|
DoublePtrType aMemberPtr,
|
|
const char* aName,
|
|
const dom::OwningDoubleOrConstrainDoubleRange& aOther, bool advanced,
|
|
nsTArray<MemberPtrType>* aList)
|
|
: Range<double>((MemberPtrType)aMemberPtr, aName,
|
|
-std::numeric_limits<double>::infinity(),
|
|
std::numeric_limits<double>::infinity(), aList)
|
|
{
|
|
if (aOther.IsDouble()) {
|
|
if (advanced) {
|
|
mMin = mMax = aOther.GetAsDouble();
|
|
} else {
|
|
mIdeal.emplace(aOther.GetAsDouble());
|
|
}
|
|
} else {
|
|
SetFrom(aOther.GetAsConstrainDoubleRange());
|
|
}
|
|
}
|
|
|
|
NormalizedConstraintSet::BooleanRange::BooleanRange(
|
|
BooleanPtrType aMemberPtr,
|
|
const char* aName,
|
|
const dom::OwningBooleanOrConstrainBooleanParameters& aOther,
|
|
bool advanced,
|
|
nsTArray<MemberPtrType>* aList)
|
|
: Range<bool>((MemberPtrType)aMemberPtr, aName, false, true, aList)
|
|
{
|
|
if (aOther.IsBoolean()) {
|
|
if (advanced) {
|
|
mMin = mMax = aOther.GetAsBoolean();
|
|
} else {
|
|
mIdeal.emplace(aOther.GetAsBoolean());
|
|
}
|
|
} else {
|
|
const dom::ConstrainBooleanParameters& r = aOther.GetAsConstrainBooleanParameters();
|
|
if (r.mIdeal.WasPassed()) {
|
|
mIdeal.emplace(r.mIdeal.Value());
|
|
}
|
|
if (r.mExact.WasPassed()) {
|
|
mMin = r.mExact.Value();
|
|
mMax = r.mExact.Value();
|
|
}
|
|
}
|
|
}
|
|
|
|
NormalizedConstraintSet::StringRange::StringRange(
|
|
StringPtrType aMemberPtr,
|
|
const char* aName,
|
|
const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aOther,
|
|
bool advanced,
|
|
nsTArray<MemberPtrType>* aList)
|
|
: BaseRange((MemberPtrType)aMemberPtr, aName, aList)
|
|
{
|
|
if (aOther.IsString()) {
|
|
if (advanced) {
|
|
mExact.insert(aOther.GetAsString());
|
|
} else {
|
|
mIdeal.insert(aOther.GetAsString());
|
|
}
|
|
} else if (aOther.IsStringSequence()) {
|
|
if (advanced) {
|
|
mExact.clear();
|
|
for (auto& str : aOther.GetAsStringSequence()) {
|
|
mExact.insert(str);
|
|
}
|
|
} else {
|
|
mIdeal.clear();
|
|
for (auto& str : aOther.GetAsStringSequence()) {
|
|
mIdeal.insert(str);
|
|
}
|
|
}
|
|
} else {
|
|
SetFrom(aOther.GetAsConstrainDOMStringParameters());
|
|
}
|
|
}
|
|
|
|
void
|
|
NormalizedConstraintSet::StringRange::SetFrom(
|
|
const dom::ConstrainDOMStringParameters& aOther)
|
|
{
|
|
if (aOther.mIdeal.WasPassed()) {
|
|
mIdeal.clear();
|
|
if (aOther.mIdeal.Value().IsString()) {
|
|
mIdeal.insert(aOther.mIdeal.Value().GetAsString());
|
|
} else {
|
|
for (auto& str : aOther.mIdeal.Value().GetAsStringSequence()) {
|
|
mIdeal.insert(str);
|
|
}
|
|
}
|
|
}
|
|
if (aOther.mExact.WasPassed()) {
|
|
mExact.clear();
|
|
if (aOther.mExact.Value().IsString()) {
|
|
mExact.insert(aOther.mExact.Value().GetAsString());
|
|
} else {
|
|
for (auto& str : aOther.mExact.Value().GetAsStringSequence()) {
|
|
mIdeal.insert(str);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto
|
|
NormalizedConstraintSet::StringRange::Clamp(const ValueType& n) const -> ValueType
|
|
{
|
|
if (mExact.empty()) {
|
|
return n;
|
|
}
|
|
ValueType result;
|
|
for (auto& entry : n) {
|
|
if (mExact.find(entry) != mExact.end()) {
|
|
result.insert(entry);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
NormalizedConstraintSet::StringRange::Intersects(const StringRange& aOther) const
|
|
{
|
|
if (mExact.empty() || aOther.mExact.empty()) {
|
|
return true;
|
|
}
|
|
|
|
ValueType intersection;
|
|
set_intersection(mExact.begin(), mExact.end(),
|
|
aOther.mExact.begin(), aOther.mExact.end(),
|
|
std::inserter(intersection, intersection.begin()));
|
|
return !intersection.empty();
|
|
}
|
|
|
|
void
|
|
NormalizedConstraintSet::StringRange::Intersect(const StringRange& aOther)
|
|
{
|
|
if (aOther.mExact.empty()) {
|
|
return;
|
|
}
|
|
|
|
ValueType intersection;
|
|
set_intersection(mExact.begin(), mExact.end(),
|
|
aOther.mExact.begin(), aOther.mExact.end(),
|
|
std::inserter(intersection, intersection.begin()));
|
|
mExact = intersection;
|
|
}
|
|
|
|
bool
|
|
NormalizedConstraintSet::StringRange::Merge(const StringRange& aOther)
|
|
{
|
|
if (!Intersects(aOther)) {
|
|
return false;
|
|
}
|
|
Intersect(aOther);
|
|
|
|
ValueType unioned;
|
|
set_union(mIdeal.begin(), mIdeal.end(),
|
|
aOther.mIdeal.begin(), aOther.mIdeal.end(),
|
|
std::inserter(unioned, unioned.begin()));
|
|
mIdeal = unioned;
|
|
return true;
|
|
}
|
|
|
|
NormalizedConstraints::NormalizedConstraints(
|
|
const dom::MediaTrackConstraints& aOther,
|
|
nsTArray<MemberPtrType>* aList)
|
|
: NormalizedConstraintSet(aOther, false, aList)
|
|
, mBadConstraint(nullptr)
|
|
{
|
|
if (aOther.mAdvanced.WasPassed()) {
|
|
for (auto& entry : aOther.mAdvanced.Value()) {
|
|
mAdvanced.push_back(NormalizedConstraintSet(entry, true));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Merge constructor. Create net constraints out of merging a set of others.
|
|
// This is only used to resolve competing constraints from concurrent requests,
|
|
// something the spec doesn't cover.
|
|
|
|
NormalizedConstraints::NormalizedConstraints(
|
|
const nsTArray<const NormalizedConstraints*>& aOthers)
|
|
: NormalizedConstraintSet(*aOthers[0])
|
|
, mBadConstraint(nullptr)
|
|
{
|
|
for (auto& entry : aOthers[0]->mAdvanced) {
|
|
mAdvanced.push_back(entry);
|
|
}
|
|
|
|
// Create a list of member pointers.
|
|
nsTArray<MemberPtrType> list;
|
|
NormalizedConstraints dummy(dom::MediaTrackConstraints(), &list);
|
|
|
|
// Do intersection of all required constraints, and average of ideals,
|
|
|
|
for (uint32_t i = 1; i < aOthers.Length(); i++) {
|
|
auto& other = *aOthers[i];
|
|
|
|
for (auto& memberPtr : list) {
|
|
auto& member = this->*memberPtr;
|
|
auto& otherMember = other.*memberPtr;
|
|
|
|
if (!member.Merge(otherMember)) {
|
|
mBadConstraint = member.mName;
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (auto& entry : other.mAdvanced) {
|
|
mAdvanced.push_back(entry);
|
|
}
|
|
}
|
|
for (auto& memberPtr : list) {
|
|
(this->*memberPtr).FinalizeMerge();
|
|
}
|
|
|
|
// ...except for resolution and frame rate where we take the highest ideal.
|
|
// This is a bit of a hack based on the perception that people would be more
|
|
// surprised if they were to get lower resolution than they ideally requested.
|
|
//
|
|
// The spec gives browsers leeway here, saying they "SHOULD use the one with
|
|
// the smallest fitness distance", and also does not directly address the
|
|
// problem of competing constraints at all. There is no real web interop issue
|
|
// here since this is more about interop with other tabs on the same browser.
|
|
//
|
|
// We should revisit this logic once we support downscaling of resolutions and
|
|
// decimating of frame rates, per track.
|
|
|
|
for (auto& other : aOthers) {
|
|
mWidth.TakeHighestIdeal(other->mWidth);
|
|
mHeight.TakeHighestIdeal(other->mHeight);
|
|
|
|
// Consider implicit 30 fps default in comparison of competing constraints.
|
|
// Avoids 160x90x10 and 640x480 becoming 1024x768x10 (fitness distance flaw)
|
|
// This pretty much locks in 30 fps or higher, except for single-tab use.
|
|
auto frameRate = other->mFrameRate;
|
|
if (frameRate.mIdeal.isNothing()) {
|
|
frameRate.mIdeal.emplace(30);
|
|
}
|
|
mFrameRate.TakeHighestIdeal(frameRate);
|
|
}
|
|
}
|
|
|
|
FlattenedConstraints::FlattenedConstraints(const NormalizedConstraints& aOther)
|
|
: NormalizedConstraintSet(aOther)
|
|
{
|
|
for (auto& set : aOther.mAdvanced) {
|
|
// Must only apply compatible i.e. inherently non-overconstraining sets
|
|
// This rule is pretty much why this code is centralized here.
|
|
if (mWidth.Intersects(set.mWidth) &&
|
|
mHeight.Intersects(set.mHeight) &&
|
|
mFrameRate.Intersects(set.mFrameRate)) {
|
|
mWidth.Intersect(set.mWidth);
|
|
mHeight.Intersect(set.mHeight);
|
|
mFrameRate.Intersect(set.mFrameRate);
|
|
}
|
|
if (mEchoCancellation.Intersects(set.mEchoCancellation)) {
|
|
mEchoCancellation.Intersect(set.mEchoCancellation);
|
|
}
|
|
if (mNoiseSuppression.Intersects(set.mNoiseSuppression)) {
|
|
mNoiseSuppression.Intersect(set.mNoiseSuppression);
|
|
}
|
|
if (mAutoGainControl.Intersects(set.mAutoGainControl)) {
|
|
mAutoGainControl.Intersect(set.mAutoGainControl);
|
|
}
|
|
if (mChannelCount.Intersects(set.mChannelCount)) {
|
|
mChannelCount.Intersect(set.mChannelCount);
|
|
}
|
|
}
|
|
}
|
|
|
|
// MediaEngine helper
|
|
//
|
|
// The full algorithm for all devices. Sources that don't list capabilities
|
|
// need to fake it and hardcode some by populating mHardcodedCapabilities above.
|
|
//
|
|
// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
|
|
|
|
// First, all devices have a minimum distance based on their deviceId.
|
|
// If you have no other constraints, use this one. Reused by all device types.
|
|
|
|
uint32_t
|
|
MediaConstraintsHelper::GetMinimumFitnessDistance(
|
|
const NormalizedConstraintSet &aConstraints,
|
|
const nsString& aDeviceId)
|
|
{
|
|
return FitnessDistance(aDeviceId, aConstraints.mDeviceId);
|
|
}
|
|
|
|
template<class ValueType, class NormalizedRange>
|
|
/* static */ uint32_t
|
|
MediaConstraintsHelper::FitnessDistance(ValueType aN,
|
|
const NormalizedRange& aRange)
|
|
{
|
|
if (aRange.mMin > aN || aRange.mMax < aN) {
|
|
return UINT32_MAX;
|
|
}
|
|
if (aN == aRange.mIdeal.valueOr(aN)) {
|
|
return 0;
|
|
}
|
|
return uint32_t(ValueType((std::abs(aN - aRange.mIdeal.value()) * 1000) /
|
|
std::max(std::abs(aN), std::abs(aRange.mIdeal.value()))));
|
|
}
|
|
|
|
// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
|
|
|
|
/* static */ uint32_t
|
|
MediaConstraintsHelper::FitnessDistance(
|
|
nsString aN,
|
|
const NormalizedConstraintSet::StringRange& aParams)
|
|
{
|
|
if (!aParams.mExact.empty() && aParams.mExact.find(aN) == aParams.mExact.end()) {
|
|
return UINT32_MAX;
|
|
}
|
|
if (!aParams.mIdeal.empty() && aParams.mIdeal.find(aN) == aParams.mIdeal.end()) {
|
|
return 1000;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
template<class MediaEngineSourceType>
|
|
const char*
|
|
MediaConstraintsHelper::FindBadConstraint(
|
|
const NormalizedConstraints& aConstraints,
|
|
const MediaEngineSourceType& aMediaEngineSource,
|
|
const nsString& aDeviceId)
|
|
{
|
|
class MockDevice
|
|
{
|
|
public:
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MockDevice);
|
|
|
|
explicit MockDevice(const MediaEngineSourceType* aMediaEngineSource,
|
|
const nsString& aDeviceId)
|
|
: mMediaEngineSource(aMediaEngineSource),
|
|
// The following dud code exists to avoid 'unused typedef' error on linux.
|
|
mDeviceId(MockDevice::HasThreadSafeRefCnt::value ? aDeviceId : nsString()) {}
|
|
|
|
uint32_t GetBestFitnessDistance(
|
|
const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
|
|
bool aIsChrome)
|
|
{
|
|
return mMediaEngineSource->GetBestFitnessDistance(aConstraintSets,
|
|
mDeviceId);
|
|
}
|
|
|
|
private:
|
|
~MockDevice() {}
|
|
|
|
const MediaEngineSourceType* mMediaEngineSource;
|
|
nsString mDeviceId;
|
|
};
|
|
|
|
Unused << typename MockDevice::HasThreadSafeRefCnt();
|
|
|
|
nsTArray<RefPtr<MockDevice>> devices;
|
|
devices.AppendElement(new MockDevice(&aMediaEngineSource, aDeviceId));
|
|
return FindBadConstraint(aConstraints, devices);
|
|
}
|
|
|
|
void
|
|
MediaConstraintsHelper::ConvertOldWithWarning(
|
|
const dom::OwningBooleanOrConstrainBooleanParameters& old,
|
|
dom::OwningBooleanOrConstrainBooleanParameters& to,
|
|
const char* aMessageName,
|
|
nsPIDOMWindowInner* aWindow) {
|
|
if ((old.IsBoolean() ||
|
|
old.GetAsConstrainBooleanParameters().mExact.WasPassed() ||
|
|
old.GetAsConstrainBooleanParameters().mIdeal.WasPassed()) &&
|
|
!(to.IsBoolean() ||
|
|
to.GetAsConstrainBooleanParameters().mExact.WasPassed() ||
|
|
to.GetAsConstrainBooleanParameters().mIdeal.WasPassed())) {
|
|
nsCOMPtr<nsIDocument> doc = aWindow->GetDoc();
|
|
if (doc) {
|
|
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
|
|
NS_LITERAL_CSTRING("DOM"), doc,
|
|
nsContentUtils::eDOM_PROPERTIES,
|
|
aMessageName);
|
|
}
|
|
if (old.IsBoolean()) {
|
|
to.SetAsBoolean() = old.GetAsBoolean();
|
|
} else {
|
|
to.SetAsConstrainBooleanParameters() = old.GetAsConstrainBooleanParameters();
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|