summaryrefslogtreecommitdiff
path: root/dom/media/MediaTrackConstraints.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/MediaTrackConstraints.h')
-rw-r--r--dom/media/MediaTrackConstraints.h449
1 files changed, 449 insertions, 0 deletions
diff --git a/dom/media/MediaTrackConstraints.h b/dom/media/MediaTrackConstraints.h
new file mode 100644
index 000000000..842fea0d2
--- /dev/null
+++ b/dom/media/MediaTrackConstraints.h
@@ -0,0 +1,449 @@
+/* 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_ */