/* 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 #include #include "ipc/IPCMessageUtilsSpecializations.h" #include "mozilla/dom/MediaStreamTrackBinding.h" namespace mozilla { class LocalMediaDevice; class MediaDevice; class MediaEnginePrefs; // Helper classes for orthogonal constraints without interdependencies. // Instead of constraining values, constrain the constraints themselves. class NormalizedConstraintSet { protected: class BaseRange { protected: BaseRange(const nsCString& aName) : mName(aName) {} virtual ~BaseRange() = default; public: bool operator==(const BaseRange& aOther) const noexcept { return mName == aOther.mName; } BaseRange& operator=(const BaseRange& aOther) { // We want all members assignable except mName. This allows e.g. // std::swap(c.mWidth, c.mHeight) without names going out of sync. return *this; } virtual bool Merge(const BaseRange& aOther) = 0; virtual void FinalizeMerge() = 0; const nsCString mName; }; public: template class Range : public BaseRange { public: ValueType mMin, mMax; Maybe mIdeal; Range(const nsCString& aName, ValueType aMin, ValueType aMax) : BaseRange(aName), mMin(aMin), mMax(aMax), mMergeDenominator(0) {} virtual ~Range() = default; bool operator==(const Range& aOther) const noexcept { return BaseRange::operator==(aOther) && mMin == aOther.mMin && mMax == aOther.mMax && mIdeal == aOther.mIdeal; } template void SetFrom(const ConstrainRange& aOther); /// Clamp n based on Range. The range must be valid (mMin <= mMax). ValueType Clamp(ValueType n) const { return std::clamp(n, mMin, mMax); } /// Get ideal value, if present, or defaultValue otherwise, clamped based on /// Range. The range must be valid (mMin <= 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) { mMin = std::max(mMin, aOther.mMin); if (Intersects(aOther)) { mMax = std::min(mMax, aOther.mMax); } else { // If there is no intersection, we will down-scale or drop frame mMax = std::max(mMax, aOther.mMax); } } bool Merge(const Range& aOther) { if (mName != "width" && mName != "height" && mName != "frameRate" && !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(aOther)); } uint32_t mMergeDenominator; }; struct LongRange final : public Range { LongRange(const nsCString& aName, const dom::Optional& aOther, bool advanced); }; struct LongLongRange final : public Range { LongLongRange(const nsCString& aName, const dom::Optional& aOther); }; struct DoubleRange final : public Range { DoubleRange( const nsCString& aName, const dom::Optional& aOther, bool advanced); }; struct BooleanRange final : public Range { BooleanRange( const nsCString& aName, const dom::Optional& aOther, bool advanced); BooleanRange(const nsCString& aName, const bool& aOther) : Range(aName, false, true) { mIdeal.emplace(aOther); } }; struct StringRange final : public BaseRange { using ValueType = std::set; ValueType mExact, mIdeal; StringRange( const nsCString& aName, const dom::Optional< dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters>& aOther, bool advanced); StringRange(const nsCString& aName, const dom::Optional& aOther) : BaseRange(aName) { if (aOther.WasPassed()) { mIdeal.insert(aOther.Value()); } } ~StringRange() = default; bool operator==(const StringRange& aOther) const noexcept { return BaseRange::operator==(aOther) && mExact == aOther.mExact && mIdeal == aOther.mIdeal; } void SetFrom(const dom::ConstrainDOMStringParameters& aOther); ValueType Clamp(const ValueType& n) const; ValueType Get(const ValueType& defaultValue) const { return Clamp(mIdeal.empty() ? defaultValue : mIdeal); } 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(aOther)); } }; // All new constraints should be added here whether they use flattening or not LongRange mWidth, mHeight; DoubleRange mFrameRate; StringRange mFacingMode; StringRange mResizeMode; StringRange mMediaSource; LongLongRange mBrowserWindow; StringRange mDeviceId; StringRange mGroupId; LongRange mViewportOffsetX, mViewportOffsetY, mViewportWidth, mViewportHeight; BooleanRange mEchoCancellation, mNoiseSuppression, mAutoGainControl; LongRange mChannelCount; public: NormalizedConstraintSet() : NormalizedConstraintSet(dom::MediaTrackConstraintSet(), /* advanced = */ false) {} NormalizedConstraintSet(const dom::MediaTrackConstraintSet& aOther, bool advanced) : mWidth("width"_ns, aOther.mWidth, advanced), mHeight("height"_ns, aOther.mHeight, advanced), mFrameRate("frameRate"_ns, aOther.mFrameRate, advanced), mFacingMode("facingMode"_ns, aOther.mFacingMode, advanced), mResizeMode("resizeMode"_ns, aOther.mResizeMode, advanced), mMediaSource("mediaSource"_ns, aOther.mMediaSource), mBrowserWindow("browserWindow"_ns, aOther.mBrowserWindow), mDeviceId("deviceId"_ns, aOther.mDeviceId, advanced), mGroupId("groupId"_ns, aOther.mGroupId, advanced), mViewportOffsetX("viewportOffsetX"_ns, aOther.mViewportOffsetX, advanced), mViewportOffsetY("viewportOffsetY"_ns, aOther.mViewportOffsetY, advanced), mViewportWidth("viewportWidth"_ns, aOther.mViewportWidth, advanced), mViewportHeight("viewportHeight"_ns, aOther.mViewportHeight, advanced), mEchoCancellation("echoCancellation"_ns, aOther.mEchoCancellation, advanced), mNoiseSuppression("noiseSuppression"_ns, aOther.mNoiseSuppression, advanced), mAutoGainControl("autoGainControl"_ns, aOther.mAutoGainControl, advanced), mChannelCount("channelCount"_ns, aOther.mChannelCount, advanced) {} bool operator==(const NormalizedConstraintSet& aOther) const noexcept { return mWidth == aOther.mWidth && mHeight == aOther.mHeight && mFrameRate == aOther.mFrameRate && mFacingMode == aOther.mFacingMode && mResizeMode == aOther.mResizeMode && mMediaSource == aOther.mMediaSource && mBrowserWindow == aOther.mBrowserWindow && mDeviceId == aOther.mDeviceId && mGroupId == aOther.mGroupId && mViewportOffsetX == aOther.mViewportOffsetX && mViewportOffsetY == aOther.mViewportOffsetY && mViewportWidth == aOther.mViewportWidth && mViewportHeight == aOther.mViewportHeight && mEchoCancellation == aOther.mEchoCancellation && mNoiseSuppression == aOther.mNoiseSuppression && mAutoGainControl == aOther.mAutoGainControl && mChannelCount == aOther.mChannelCount; } }; template <> bool NormalizedConstraintSet::Range::Merge(const Range& aOther); template <> void NormalizedConstraintSet::Range::FinalizeMerge(); // Used instead of MediaTrackConstraints in lower-level code. struct NormalizedConstraints : public NormalizedConstraintSet { NormalizedConstraints() = default; explicit NormalizedConstraints(const dom::MediaTrackConstraints& aOther); bool operator==(const NormalizedConstraints& aOther) const noexcept { return NormalizedConstraintSet::operator==(aOther) && mAdvanced == aOther.mAdvanced; } bool operator!=(const NormalizedConstraints& aOther) const noexcept { return !(*this == aOther); } std::vector mAdvanced; }; // Flattened version is used in low-level code with orthogonal constraints only. struct FlattenedConstraints : public NormalizedConstraintSet { FlattenedConstraints() = default; explicit FlattenedConstraints(const NormalizedConstraints& aOther); explicit FlattenedConstraints(const dom::MediaTrackConstraints& aOther) : FlattenedConstraints(NormalizedConstraints(aOther)) {} bool operator==(const FlattenedConstraints& aOther) const noexcept { return NormalizedConstraintSet::operator==(aOther); } bool operator!=(const FlattenedConstraints& aOther) const noexcept { return !(*this == aOther); } }; // A helper class for MediaEngineSources class MediaConstraintsHelper { public: template static uint32_t 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())))); } template static uint32_t FeasibilityDistance(ValueType aN, const NormalizedRange& aRange) { if (aRange.mMin > aN) { return UINT32_MAX; } // We prefer larger resolution because now we support downscaling if (aN == aRange.mIdeal.valueOr(aN)) { return 0; } if (aN > aRange.mIdeal.value()) { return uint32_t( ValueType((std::abs(aN - aRange.mIdeal.value()) * 1000) / std::max(std::abs(aN), std::abs(aRange.mIdeal.value())))); } return 10000 + uint32_t(ValueType( (std::abs(aN - aRange.mIdeal.value()) * 1000) / std::max(std::abs(aN), std::abs(aRange.mIdeal.value())))); } static uint32_t FitnessDistance( const Maybe& aN, const NormalizedConstraintSet::StringRange& aParams); protected: static bool SomeSettingsFit( const NormalizedConstraints& aConstraints, const MediaEnginePrefs& aPrefs, const nsTArray>& aDevices); public: static uint32_t GetMinimumFitnessDistance( const NormalizedConstraintSet& aConstraints, const nsString& aDeviceId, const nsString& aGroupId); // Apply constrains to a supplied list of devices (removes items from the // list) static const char* SelectSettings( const NormalizedConstraints& aConstraints, const MediaEnginePrefs& aPrefs, nsTArray>& aDevices, dom::CallerType aCallerType); static const char* FindBadConstraint( const NormalizedConstraints& aConstraints, const MediaEnginePrefs& aPrefs, const nsTArray>& aDevices); static const char* FindBadConstraint( const NormalizedConstraints& aConstraints, const MediaEnginePrefs& aPrefs, const MediaDevice* aMediaDevice); static void LogConstraints(const NormalizedConstraintSet& aConstraints); static Maybe GetResizeMode( const NormalizedConstraintSet& aConstraints, const MediaEnginePrefs& aPrefs); }; } // namespace mozilla #endif /* MEDIATRACKCONSTRAINTS_H_ */