450 lines
15 KiB
C++
450 lines
15 KiB
C++
/* 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/. */
|
|
|
|
// This file should not be included by other includes, as it contains code
|
|
|
|
#ifndef MEDIATRACKCONSTRAINTS_H_
|
|
#define MEDIATRACKCONSTRAINTS_H_
|
|
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/dom/MediaStreamTrackBinding.h"
|
|
#include "mozilla/dom/MediaTrackConstraintSetBinding.h"
|
|
#include "mozilla/dom/MediaTrackSupportedConstraintsBinding.h"
|
|
|
|
#include <map>
|
|
#include <set>
|
|
#include <vector>
|
|
|
|
namespace mozilla {
|
|
|
|
template<class EnumValuesStrings, class Enum>
|
|
static const char* EnumToASCII(const EnumValuesStrings& aStrings, Enum aValue) {
|
|
return aStrings[uint32_t(aValue)].value;
|
|
}
|
|
|
|
template<class EnumValuesStrings, class Enum>
|
|
static Enum StringToEnum(const EnumValuesStrings& aStrings,
|
|
const nsAString& aValue, Enum aDefaultValue) {
|
|
for (size_t i = 0; aStrings[i].value; i++) {
|
|
if (aValue.EqualsASCII(aStrings[i].value)) {
|
|
return Enum(i);
|
|
}
|
|
}
|
|
return aDefaultValue;
|
|
}
|
|
|
|
// Helper classes for orthogonal constraints without interdependencies.
|
|
// Instead of constraining values, constrain the constraints themselves.
|
|
|
|
class NormalizedConstraintSet
|
|
{
|
|
protected:
|
|
class BaseRange
|
|
{
|
|
protected:
|
|
typedef BaseRange NormalizedConstraintSet::* MemberPtrType;
|
|
|
|
BaseRange(MemberPtrType aMemberPtr, const char* aName,
|
|
nsTArray<MemberPtrType>* aList) : mName(aName) {
|
|
if (aList) {
|
|
aList->AppendElement(aMemberPtr);
|
|
}
|
|
}
|
|
virtual ~BaseRange() {}
|
|
public:
|
|
virtual bool Merge(const BaseRange& aOther) = 0;
|
|
virtual void FinalizeMerge() = 0;
|
|
|
|
const char* mName;
|
|
};
|
|
|
|
typedef BaseRange NormalizedConstraintSet::* MemberPtrType;
|
|
|
|
public:
|
|
template<class ValueType>
|
|
class Range : public BaseRange
|
|
{
|
|
public:
|
|
ValueType mMin, mMax;
|
|
Maybe<ValueType> mIdeal;
|
|
|
|
Range(MemberPtrType aMemberPtr, const char* aName, ValueType aMin,
|
|
ValueType aMax, nsTArray<MemberPtrType>* aList)
|
|
: BaseRange(aMemberPtr, aName, aList)
|
|
, mMin(aMin), mMax(aMax), mMergeDenominator(0) {}
|
|
virtual ~Range() {};
|
|
|
|
template<class ConstrainRange>
|
|
void SetFrom(const ConstrainRange& aOther);
|
|
ValueType Clamp(ValueType n) const { return std::max(mMin, std::min(n, mMax)); }
|
|
ValueType Get(ValueType defaultValue) const {
|
|
return Clamp(mIdeal.valueOr(defaultValue));
|
|
}
|
|
bool Intersects(const Range& aOther) const {
|
|
return mMax >= aOther.mMin && mMin <= aOther.mMax;
|
|
}
|
|
void Intersect(const Range& aOther) {
|
|
MOZ_ASSERT(Intersects(aOther));
|
|
mMin = std::max(mMin, aOther.mMin);
|
|
mMax = std::min(mMax, aOther.mMax);
|
|
}
|
|
bool Merge(const Range& aOther) {
|
|
if (!Intersects(aOther)) {
|
|
return false;
|
|
}
|
|
Intersect(aOther);
|
|
|
|
if (aOther.mIdeal.isSome()) {
|
|
// Ideal values, as stored, may be outside their min max range, so use
|
|
// clamped values in averaging, to avoid extreme outliers skewing results.
|
|
if (mIdeal.isNothing()) {
|
|
mIdeal.emplace(aOther.Get(0));
|
|
mMergeDenominator = 1;
|
|
} else {
|
|
if (!mMergeDenominator) {
|
|
*mIdeal = Get(0);
|
|
mMergeDenominator = 1;
|
|
}
|
|
*mIdeal += aOther.Get(0);
|
|
mMergeDenominator++;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
void FinalizeMerge() override
|
|
{
|
|
if (mMergeDenominator) {
|
|
*mIdeal /= mMergeDenominator;
|
|
mMergeDenominator = 0;
|
|
}
|
|
}
|
|
void TakeHighestIdeal(const Range& aOther) {
|
|
if (aOther.mIdeal.isSome()) {
|
|
if (mIdeal.isNothing()) {
|
|
mIdeal.emplace(aOther.Get(0));
|
|
} else {
|
|
*mIdeal = std::max(Get(0), aOther.Get(0));
|
|
}
|
|
}
|
|
}
|
|
private:
|
|
bool Merge(const BaseRange& aOther) override {
|
|
return Merge(static_cast<const Range&>(aOther));
|
|
}
|
|
|
|
uint32_t mMergeDenominator;
|
|
};
|
|
|
|
struct LongRange : public Range<int32_t>
|
|
{
|
|
typedef LongRange NormalizedConstraintSet::* LongPtrType;
|
|
|
|
LongRange(LongPtrType aMemberPtr, const char* aName,
|
|
const dom::OwningLongOrConstrainLongRange& aOther, bool advanced,
|
|
nsTArray<MemberPtrType>* aList);
|
|
};
|
|
|
|
struct LongLongRange : public Range<int64_t>
|
|
{
|
|
typedef LongLongRange NormalizedConstraintSet::* LongLongPtrType;
|
|
|
|
LongLongRange(LongLongPtrType aMemberPtr, const char* aName,
|
|
const long long& aOther,
|
|
nsTArray<MemberPtrType>* aList);
|
|
};
|
|
|
|
struct DoubleRange : public Range<double>
|
|
{
|
|
typedef DoubleRange NormalizedConstraintSet::* DoublePtrType;
|
|
|
|
DoubleRange(DoublePtrType aMemberPtr,
|
|
const char* aName,
|
|
const dom::OwningDoubleOrConstrainDoubleRange& aOther,
|
|
bool advanced,
|
|
nsTArray<MemberPtrType>* aList);
|
|
};
|
|
|
|
struct BooleanRange : public Range<bool>
|
|
{
|
|
typedef BooleanRange NormalizedConstraintSet::* BooleanPtrType;
|
|
|
|
BooleanRange(BooleanPtrType aMemberPtr, const char* aName,
|
|
const dom::OwningBooleanOrConstrainBooleanParameters& aOther,
|
|
bool advanced,
|
|
nsTArray<MemberPtrType>* aList);
|
|
|
|
BooleanRange(BooleanPtrType aMemberPtr, const char* aName, const bool& aOther,
|
|
nsTArray<MemberPtrType>* aList)
|
|
: Range<bool>((MemberPtrType)aMemberPtr, aName, false, true, aList) {
|
|
mIdeal.emplace(aOther);
|
|
}
|
|
};
|
|
|
|
struct StringRange : public BaseRange
|
|
{
|
|
typedef std::set<nsString> ValueType;
|
|
ValueType mExact, mIdeal;
|
|
|
|
typedef StringRange NormalizedConstraintSet::* StringPtrType;
|
|
|
|
StringRange(StringPtrType aMemberPtr, const char* aName,
|
|
const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aOther,
|
|
bool advanced,
|
|
nsTArray<MemberPtrType>* aList);
|
|
|
|
StringRange(StringPtrType aMemberPtr, const char* aName,
|
|
const nsString& aOther, nsTArray<MemberPtrType>* aList)
|
|
: BaseRange((MemberPtrType)aMemberPtr, aName, aList) {
|
|
mIdeal.insert(aOther);
|
|
}
|
|
|
|
~StringRange() {}
|
|
|
|
void SetFrom(const dom::ConstrainDOMStringParameters& aOther);
|
|
ValueType Clamp(const ValueType& n) const;
|
|
ValueType Get(const ValueType& defaultValue) const {
|
|
return Clamp(mIdeal.size() ? mIdeal : defaultValue);
|
|
}
|
|
bool Intersects(const StringRange& aOther) const;
|
|
void Intersect(const StringRange& aOther);
|
|
bool Merge(const StringRange& aOther);
|
|
void FinalizeMerge() override {}
|
|
private:
|
|
bool Merge(const BaseRange& aOther) override {
|
|
return Merge(static_cast<const StringRange&>(aOther));
|
|
}
|
|
};
|
|
|
|
// All new constraints should be added here whether they use flattening or not
|
|
LongRange mWidth, mHeight;
|
|
DoubleRange mFrameRate;
|
|
StringRange mFacingMode;
|
|
StringRange mMediaSource;
|
|
LongLongRange mBrowserWindow;
|
|
BooleanRange mScrollWithPage;
|
|
StringRange mDeviceId;
|
|
LongRange mViewportOffsetX, mViewportOffsetY, mViewportWidth, mViewportHeight;
|
|
BooleanRange mEchoCancellation, mMozNoiseSuppression, mMozAutoGainControl;
|
|
private:
|
|
typedef NormalizedConstraintSet T;
|
|
public:
|
|
NormalizedConstraintSet(const dom::MediaTrackConstraintSet& aOther,
|
|
bool advanced,
|
|
nsTArray<MemberPtrType>* aList = nullptr)
|
|
: mWidth(&T::mWidth, "width", aOther.mWidth, advanced, aList)
|
|
, mHeight(&T::mHeight, "height", aOther.mHeight, advanced, aList)
|
|
, mFrameRate(&T::mFrameRate, "frameRate", aOther.mFrameRate, advanced, aList)
|
|
, mFacingMode(&T::mFacingMode, "facingMode", aOther.mFacingMode, advanced, aList)
|
|
, mMediaSource(&T::mMediaSource, "mediaSource", aOther.mMediaSource, aList)
|
|
, mBrowserWindow(&T::mBrowserWindow, "browserWindow",
|
|
aOther.mBrowserWindow.WasPassed() ?
|
|
aOther.mBrowserWindow.Value() : 0, aList)
|
|
, mScrollWithPage(&T::mScrollWithPage, "scrollWithPage",
|
|
aOther.mScrollWithPage.WasPassed() ?
|
|
aOther.mScrollWithPage.Value() : false, aList)
|
|
, mDeviceId(&T::mDeviceId, "deviceId", aOther.mDeviceId, advanced, aList)
|
|
, mViewportOffsetX(&T::mViewportOffsetX, "viewportOffsetX",
|
|
aOther.mViewportOffsetX, advanced, aList)
|
|
, mViewportOffsetY(&T::mViewportOffsetY, "viewportOffsetY",
|
|
aOther.mViewportOffsetY, advanced, aList)
|
|
, mViewportWidth(&T::mViewportWidth, "viewportWidth",
|
|
aOther.mViewportWidth, advanced, aList)
|
|
, mViewportHeight(&T::mViewportHeight, "viewportHeight",
|
|
aOther.mViewportHeight, advanced, aList)
|
|
, mEchoCancellation(&T::mEchoCancellation, "echoCancellation",
|
|
aOther.mEchoCancellation, advanced, aList)
|
|
, mMozNoiseSuppression(&T::mMozNoiseSuppression, "mozNoiseSuppression",
|
|
aOther.mMozNoiseSuppression,
|
|
advanced, aList)
|
|
, mMozAutoGainControl(&T::mMozAutoGainControl, "mozAutoGainControl",
|
|
aOther.mMozAutoGainControl, advanced, aList) {}
|
|
};
|
|
|
|
template<> bool NormalizedConstraintSet::Range<bool>::Merge(const Range& aOther);
|
|
template<> void NormalizedConstraintSet::Range<bool>::FinalizeMerge();
|
|
|
|
// Used instead of MediaTrackConstraints in lower-level code.
|
|
struct NormalizedConstraints : public NormalizedConstraintSet
|
|
{
|
|
explicit NormalizedConstraints(const dom::MediaTrackConstraints& aOther,
|
|
nsTArray<MemberPtrType>* aList = nullptr);
|
|
|
|
// Merge constructor
|
|
explicit NormalizedConstraints(
|
|
const nsTArray<const NormalizedConstraints*>& aOthers);
|
|
|
|
std::vector<NormalizedConstraintSet> mAdvanced;
|
|
const char* mBadConstraint;
|
|
};
|
|
|
|
// Flattened version is used in low-level code with orthogonal constraints only.
|
|
struct FlattenedConstraints : public NormalizedConstraintSet
|
|
{
|
|
explicit FlattenedConstraints(const NormalizedConstraints& aOther);
|
|
|
|
explicit FlattenedConstraints(const dom::MediaTrackConstraints& aOther)
|
|
: FlattenedConstraints(NormalizedConstraints(aOther)) {}
|
|
};
|
|
|
|
// A helper class for MediaEngines
|
|
|
|
class MediaConstraintsHelper
|
|
{
|
|
protected:
|
|
template<class ValueType, class NormalizedRange>
|
|
static uint32_t FitnessDistance(ValueType aN, const NormalizedRange& aRange);
|
|
static uint32_t FitnessDistance(nsString aN,
|
|
const NormalizedConstraintSet::StringRange& aConstraint);
|
|
|
|
static uint32_t
|
|
GetMinimumFitnessDistance(const NormalizedConstraintSet &aConstraints,
|
|
const nsString& aDeviceId);
|
|
|
|
template<class DeviceType>
|
|
static bool
|
|
SomeSettingsFit(const NormalizedConstraints &aConstraints,
|
|
nsTArray<RefPtr<DeviceType>>& aDevices)
|
|
{
|
|
nsTArray<const NormalizedConstraintSet*> sets;
|
|
sets.AppendElement(&aConstraints);
|
|
|
|
MOZ_ASSERT(aDevices.Length());
|
|
for (auto& device : aDevices) {
|
|
if (device->GetBestFitnessDistance(sets, false) != UINT32_MAX) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public:
|
|
// Apply constrains to a supplied list of devices (removes items from the list)
|
|
|
|
template<class DeviceType>
|
|
static const char*
|
|
SelectSettings(const NormalizedConstraints &aConstraints,
|
|
nsTArray<RefPtr<DeviceType>>& aDevices,
|
|
bool aIsChrome)
|
|
{
|
|
auto& c = aConstraints;
|
|
|
|
// First apply top-level constraints.
|
|
|
|
// Stack constraintSets that pass, starting with the required one, because the
|
|
// whole stack must be re-satisfied each time a capability-set is ruled out
|
|
// (this avoids storing state or pushing algorithm into the lower-level code).
|
|
nsTArray<RefPtr<DeviceType>> unsatisfactory;
|
|
nsTArray<const NormalizedConstraintSet*> aggregateConstraints;
|
|
aggregateConstraints.AppendElement(&c);
|
|
|
|
std::multimap<uint32_t, RefPtr<DeviceType>> ordered;
|
|
|
|
for (uint32_t i = 0; i < aDevices.Length();) {
|
|
uint32_t distance = aDevices[i]->GetBestFitnessDistance(aggregateConstraints,
|
|
aIsChrome);
|
|
if (distance == UINT32_MAX) {
|
|
unsatisfactory.AppendElement(aDevices[i]);
|
|
aDevices.RemoveElementAt(i);
|
|
} else {
|
|
ordered.insert(std::pair<uint32_t, RefPtr<DeviceType>>(distance,
|
|
aDevices[i]));
|
|
++i;
|
|
}
|
|
}
|
|
if (!aDevices.Length()) {
|
|
return FindBadConstraint(c, unsatisfactory);
|
|
}
|
|
|
|
// Order devices by shortest distance
|
|
for (auto& ordinal : ordered) {
|
|
aDevices.RemoveElement(ordinal.second);
|
|
aDevices.AppendElement(ordinal.second);
|
|
}
|
|
|
|
// Then apply advanced constraints.
|
|
|
|
for (int i = 0; i < int(c.mAdvanced.size()); i++) {
|
|
aggregateConstraints.AppendElement(&c.mAdvanced[i]);
|
|
nsTArray<RefPtr<DeviceType>> rejects;
|
|
for (uint32_t j = 0; j < aDevices.Length();) {
|
|
if (aDevices[j]->GetBestFitnessDistance(aggregateConstraints,
|
|
aIsChrome) == UINT32_MAX) {
|
|
rejects.AppendElement(aDevices[j]);
|
|
aDevices.RemoveElementAt(j);
|
|
} else {
|
|
++j;
|
|
}
|
|
}
|
|
if (!aDevices.Length()) {
|
|
aDevices.AppendElements(Move(rejects));
|
|
aggregateConstraints.RemoveElementAt(aggregateConstraints.Length() - 1);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
template<class DeviceType>
|
|
static const char*
|
|
FindBadConstraint(const NormalizedConstraints& aConstraints,
|
|
nsTArray<RefPtr<DeviceType>>& aDevices)
|
|
{
|
|
// The spec says to report a constraint that satisfies NONE
|
|
// of the sources. Unfortunately, this is a bit laborious to find out, and
|
|
// requires updating as new constraints are added!
|
|
auto& c = aConstraints;
|
|
dom::MediaTrackConstraints empty;
|
|
|
|
if (!aDevices.Length() ||
|
|
!SomeSettingsFit(NormalizedConstraints(empty), aDevices)) {
|
|
return "";
|
|
}
|
|
{
|
|
NormalizedConstraints fresh(empty);
|
|
fresh.mDeviceId = c.mDeviceId;
|
|
if (!SomeSettingsFit(fresh, aDevices)) {
|
|
return "deviceId";
|
|
}
|
|
}
|
|
{
|
|
NormalizedConstraints fresh(empty);
|
|
fresh.mWidth = c.mWidth;
|
|
if (!SomeSettingsFit(fresh, aDevices)) {
|
|
return "width";
|
|
}
|
|
}
|
|
{
|
|
NormalizedConstraints fresh(empty);
|
|
fresh.mHeight = c.mHeight;
|
|
if (!SomeSettingsFit(fresh, aDevices)) {
|
|
return "height";
|
|
}
|
|
}
|
|
{
|
|
NormalizedConstraints fresh(empty);
|
|
fresh.mFrameRate = c.mFrameRate;
|
|
if (!SomeSettingsFit(fresh, aDevices)) {
|
|
return "frameRate";
|
|
}
|
|
}
|
|
{
|
|
NormalizedConstraints fresh(empty);
|
|
fresh.mFacingMode = c.mFacingMode;
|
|
if (!SomeSettingsFit(fresh, aDevices)) {
|
|
return "facingMode";
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
template<class MediaEngineSourceType>
|
|
static const char*
|
|
FindBadConstraint(const NormalizedConstraints& aConstraints,
|
|
const MediaEngineSourceType& aMediaEngineSource,
|
|
const nsString& aDeviceId);
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif /* MEDIATRACKCONSTRAINTS_H_ */
|