Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 : * This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 : * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "MediaTrackConstraints.h"
7 : #include "nsIScriptError.h"
8 : #include "mozilla/dom/MediaStreamTrackBinding.h"
9 :
10 : #include <limits>
11 : #include <algorithm>
12 : #include <iterator>
13 :
14 : namespace mozilla {
15 :
16 : using dom::ConstrainBooleanParameters;
17 : using dom::OwningLongOrConstrainLongRange;
18 :
19 : template<class ValueType>
20 : template<class ConstrainRange>
21 : void
22 0 : NormalizedConstraintSet::Range<ValueType>::SetFrom(const ConstrainRange& aOther)
23 : {
24 0 : if (aOther.mIdeal.WasPassed()) {
25 0 : mIdeal.emplace(aOther.mIdeal.Value());
26 : }
27 0 : if (aOther.mExact.WasPassed()) {
28 0 : mMin = aOther.mExact.Value();
29 0 : mMax = aOther.mExact.Value();
30 : } else {
31 0 : if (aOther.mMin.WasPassed()) {
32 0 : mMin = aOther.mMin.Value();
33 : }
34 0 : if (aOther.mMax.WasPassed()) {
35 0 : mMax = aOther.mMax.Value();
36 : }
37 : }
38 0 : }
39 :
40 : // The Range code works surprisingly well for bool, except when averaging ideals.
41 : template<>
42 : bool
43 0 : NormalizedConstraintSet::Range<bool>::Merge(const Range& aOther) {
44 0 : if (!Intersects(aOther)) {
45 0 : return false;
46 : }
47 0 : Intersect(aOther);
48 :
49 : // To avoid "unsafe use of type 'bool'", we keep counter in mMergeDenominator
50 0 : uint32_t counter = mMergeDenominator >> 16;
51 0 : uint32_t denominator = mMergeDenominator & 0xffff;
52 :
53 0 : if (aOther.mIdeal.isSome()) {
54 0 : if (mIdeal.isNothing()) {
55 0 : mIdeal.emplace(aOther.Get(false));
56 0 : counter = aOther.Get(false);
57 0 : denominator = 1;
58 : } else {
59 0 : if (!denominator) {
60 0 : counter = Get(false);
61 0 : denominator = 1;
62 : }
63 0 : counter += aOther.Get(false);
64 0 : denominator++;
65 : }
66 : }
67 0 : mMergeDenominator = ((counter & 0xffff) << 16) + (denominator & 0xffff);
68 0 : return true;
69 : }
70 :
71 : template<>
72 : void
73 0 : NormalizedConstraintSet::Range<bool>::FinalizeMerge()
74 : {
75 0 : if (mMergeDenominator) {
76 0 : uint32_t counter = mMergeDenominator >> 16;
77 0 : uint32_t denominator = mMergeDenominator & 0xffff;
78 :
79 0 : *mIdeal = !!(counter / denominator);
80 0 : mMergeDenominator = 0;
81 : }
82 0 : }
83 :
84 0 : NormalizedConstraintSet::LongRange::LongRange(
85 : LongPtrType aMemberPtr,
86 : const char* aName,
87 : const dom::OwningLongOrConstrainLongRange& aOther,
88 : bool advanced,
89 0 : nsTArray<MemberPtrType>* aList)
90 : : Range<int32_t>((MemberPtrType)aMemberPtr, aName,
91 : 1 + INT32_MIN, INT32_MAX, // +1 avoids Windows compiler bug
92 0 : aList)
93 : {
94 0 : if (aOther.IsLong()) {
95 0 : if (advanced) {
96 0 : mMin = mMax = aOther.GetAsLong();
97 : } else {
98 0 : mIdeal.emplace(aOther.GetAsLong());
99 : }
100 : } else {
101 0 : SetFrom(aOther.GetAsConstrainLongRange());
102 : }
103 0 : }
104 :
105 0 : NormalizedConstraintSet::LongLongRange::LongLongRange(
106 : LongLongPtrType aMemberPtr,
107 : const char* aName,
108 : const long long& aOther,
109 0 : nsTArray<MemberPtrType>* aList)
110 : : Range<int64_t>((MemberPtrType)aMemberPtr, aName,
111 : 1 + INT64_MIN, INT64_MAX, // +1 avoids Windows compiler bug
112 0 : aList)
113 : {
114 0 : mIdeal.emplace(aOther);
115 0 : }
116 :
117 0 : NormalizedConstraintSet::DoubleRange::DoubleRange(
118 : DoublePtrType aMemberPtr,
119 : const char* aName,
120 : const dom::OwningDoubleOrConstrainDoubleRange& aOther, bool advanced,
121 0 : nsTArray<MemberPtrType>* aList)
122 : : Range<double>((MemberPtrType)aMemberPtr, aName,
123 0 : -std::numeric_limits<double>::infinity(),
124 0 : std::numeric_limits<double>::infinity(), aList)
125 : {
126 0 : if (aOther.IsDouble()) {
127 0 : if (advanced) {
128 0 : mMin = mMax = aOther.GetAsDouble();
129 : } else {
130 0 : mIdeal.emplace(aOther.GetAsDouble());
131 : }
132 : } else {
133 0 : SetFrom(aOther.GetAsConstrainDoubleRange());
134 : }
135 0 : }
136 :
137 0 : NormalizedConstraintSet::BooleanRange::BooleanRange(
138 : BooleanPtrType aMemberPtr,
139 : const char* aName,
140 : const dom::OwningBooleanOrConstrainBooleanParameters& aOther,
141 : bool advanced,
142 0 : nsTArray<MemberPtrType>* aList)
143 0 : : Range<bool>((MemberPtrType)aMemberPtr, aName, false, true, aList)
144 : {
145 0 : if (aOther.IsBoolean()) {
146 0 : if (advanced) {
147 0 : mMin = mMax = aOther.GetAsBoolean();
148 : } else {
149 0 : mIdeal.emplace(aOther.GetAsBoolean());
150 : }
151 : } else {
152 0 : const dom::ConstrainBooleanParameters& r = aOther.GetAsConstrainBooleanParameters();
153 0 : if (r.mIdeal.WasPassed()) {
154 0 : mIdeal.emplace(r.mIdeal.Value());
155 : }
156 0 : if (r.mExact.WasPassed()) {
157 0 : mMin = r.mExact.Value();
158 0 : mMax = r.mExact.Value();
159 : }
160 : }
161 0 : }
162 :
163 0 : NormalizedConstraintSet::StringRange::StringRange(
164 : StringPtrType aMemberPtr,
165 : const char* aName,
166 : const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aOther,
167 : bool advanced,
168 0 : nsTArray<MemberPtrType>* aList)
169 0 : : BaseRange((MemberPtrType)aMemberPtr, aName, aList)
170 : {
171 0 : if (aOther.IsString()) {
172 0 : if (advanced) {
173 0 : mExact.insert(aOther.GetAsString());
174 : } else {
175 0 : mIdeal.insert(aOther.GetAsString());
176 : }
177 0 : } else if (aOther.IsStringSequence()) {
178 0 : if (advanced) {
179 0 : mExact.clear();
180 0 : for (auto& str : aOther.GetAsStringSequence()) {
181 0 : mExact.insert(str);
182 : }
183 : } else {
184 0 : mIdeal.clear();
185 0 : for (auto& str : aOther.GetAsStringSequence()) {
186 0 : mIdeal.insert(str);
187 : }
188 : }
189 : } else {
190 0 : SetFrom(aOther.GetAsConstrainDOMStringParameters());
191 : }
192 0 : }
193 :
194 : void
195 0 : NormalizedConstraintSet::StringRange::SetFrom(
196 : const dom::ConstrainDOMStringParameters& aOther)
197 : {
198 0 : if (aOther.mIdeal.WasPassed()) {
199 0 : mIdeal.clear();
200 0 : if (aOther.mIdeal.Value().IsString()) {
201 0 : mIdeal.insert(aOther.mIdeal.Value().GetAsString());
202 : } else {
203 0 : for (auto& str : aOther.mIdeal.Value().GetAsStringSequence()) {
204 0 : mIdeal.insert(str);
205 : }
206 : }
207 : }
208 0 : if (aOther.mExact.WasPassed()) {
209 0 : mExact.clear();
210 0 : if (aOther.mExact.Value().IsString()) {
211 0 : mExact.insert(aOther.mExact.Value().GetAsString());
212 : } else {
213 0 : for (auto& str : aOther.mExact.Value().GetAsStringSequence()) {
214 0 : mIdeal.insert(str);
215 : }
216 : }
217 : }
218 0 : }
219 :
220 : auto
221 0 : NormalizedConstraintSet::StringRange::Clamp(const ValueType& n) const -> ValueType
222 : {
223 0 : if (!mExact.size()) {
224 0 : return n;
225 : }
226 0 : ValueType result;
227 0 : for (auto& entry : n) {
228 0 : if (mExact.find(entry) != mExact.end()) {
229 0 : result.insert(entry);
230 : }
231 : }
232 0 : return result;
233 : }
234 :
235 : bool
236 0 : NormalizedConstraintSet::StringRange::Intersects(const StringRange& aOther) const
237 : {
238 0 : if (!mExact.size() || !aOther.mExact.size()) {
239 0 : return true;
240 : }
241 :
242 0 : ValueType intersection;
243 : set_intersection(mExact.begin(), mExact.end(),
244 : aOther.mExact.begin(), aOther.mExact.end(),
245 0 : std::inserter(intersection, intersection.begin()));
246 0 : return !!intersection.size();
247 : }
248 :
249 : void
250 0 : NormalizedConstraintSet::StringRange::Intersect(const StringRange& aOther)
251 : {
252 0 : if (!aOther.mExact.size()) {
253 0 : return;
254 : }
255 :
256 0 : ValueType intersection;
257 : set_intersection(mExact.begin(), mExact.end(),
258 : aOther.mExact.begin(), aOther.mExact.end(),
259 0 : std::inserter(intersection, intersection.begin()));
260 0 : mExact = intersection;
261 : }
262 :
263 : bool
264 0 : NormalizedConstraintSet::StringRange::Merge(const StringRange& aOther)
265 : {
266 0 : if (!Intersects(aOther)) {
267 0 : return false;
268 : }
269 0 : Intersect(aOther);
270 :
271 0 : ValueType unioned;
272 : set_union(mIdeal.begin(), mIdeal.end(),
273 : aOther.mIdeal.begin(), aOther.mIdeal.end(),
274 0 : std::inserter(unioned, unioned.begin()));
275 0 : mIdeal = unioned;
276 0 : return true;
277 : }
278 :
279 0 : NormalizedConstraints::NormalizedConstraints(
280 : const dom::MediaTrackConstraints& aOther,
281 0 : nsTArray<MemberPtrType>* aList)
282 : : NormalizedConstraintSet(aOther, false, aList)
283 0 : , mBadConstraint(nullptr)
284 : {
285 0 : if (aOther.mAdvanced.WasPassed()) {
286 0 : for (auto& entry : aOther.mAdvanced.Value()) {
287 0 : mAdvanced.push_back(NormalizedConstraintSet(entry, true));
288 : }
289 : }
290 0 : }
291 :
292 : // Merge constructor. Create net constraints out of merging a set of others.
293 : // This is only used to resolve competing constraints from concurrent requests,
294 : // something the spec doesn't cover.
295 :
296 0 : NormalizedConstraints::NormalizedConstraints(
297 0 : const nsTArray<const NormalizedConstraints*>& aOthers)
298 0 : : NormalizedConstraintSet(*aOthers[0])
299 0 : , mBadConstraint(nullptr)
300 : {
301 0 : for (auto& entry : aOthers[0]->mAdvanced) {
302 0 : mAdvanced.push_back(entry);
303 : }
304 :
305 : // Create a list of member pointers.
306 0 : nsTArray<MemberPtrType> list;
307 0 : NormalizedConstraints dummy(dom::MediaTrackConstraints(), &list);
308 :
309 : // Do intersection of all required constraints, and average of ideals,
310 :
311 0 : for (uint32_t i = 1; i < aOthers.Length(); i++) {
312 0 : auto& other = *aOthers[i];
313 :
314 0 : for (auto& memberPtr : list) {
315 0 : auto& member = this->*memberPtr;
316 0 : auto& otherMember = other.*memberPtr;
317 :
318 0 : if (!member.Merge(otherMember)) {
319 0 : mBadConstraint = member.mName;
320 0 : return;
321 : }
322 : }
323 :
324 0 : for (auto& entry : other.mAdvanced) {
325 0 : mAdvanced.push_back(entry);
326 : }
327 : }
328 0 : for (auto& memberPtr : list) {
329 0 : (this->*memberPtr).FinalizeMerge();
330 : }
331 :
332 : // ...except for resolution and frame rate where we take the highest ideal.
333 : // This is a bit of a hack based on the perception that people would be more
334 : // surprised if they were to get lower resolution than they ideally requested.
335 : //
336 : // The spec gives browsers leeway here, saying they "SHOULD use the one with
337 : // the smallest fitness distance", and also does not directly address the
338 : // problem of competing constraints at all. There is no real web interop issue
339 : // here since this is more about interop with other tabs on the same browser.
340 : //
341 : // We should revisit this logic once we support downscaling of resolutions and
342 : // decimating of frame rates, per track.
343 :
344 0 : for (auto& other : aOthers) {
345 0 : mWidth.TakeHighestIdeal(other->mWidth);
346 0 : mHeight.TakeHighestIdeal(other->mHeight);
347 :
348 : // Consider implicit 30 fps default in comparison of competing constraints.
349 : // Avoids 160x90x10 and 640x480 becoming 1024x768x10 (fitness distance flaw)
350 : // This pretty much locks in 30 fps or higher, except for single-tab use.
351 0 : auto frameRate = other->mFrameRate;
352 0 : if (frameRate.mIdeal.isNothing()) {
353 0 : frameRate.mIdeal.emplace(30);
354 : }
355 0 : mFrameRate.TakeHighestIdeal(frameRate);
356 : }
357 : }
358 :
359 0 : FlattenedConstraints::FlattenedConstraints(const NormalizedConstraints& aOther)
360 0 : : NormalizedConstraintSet(aOther)
361 : {
362 0 : for (auto& set : aOther.mAdvanced) {
363 : // Must only apply compatible i.e. inherently non-overconstraining sets
364 : // This rule is pretty much why this code is centralized here.
365 0 : if (mWidth.Intersects(set.mWidth) &&
366 0 : mHeight.Intersects(set.mHeight) &&
367 0 : mFrameRate.Intersects(set.mFrameRate)) {
368 0 : mWidth.Intersect(set.mWidth);
369 0 : mHeight.Intersect(set.mHeight);
370 0 : mFrameRate.Intersect(set.mFrameRate);
371 : }
372 0 : if (mEchoCancellation.Intersects(set.mEchoCancellation)) {
373 0 : mEchoCancellation.Intersect(set.mEchoCancellation);
374 : }
375 0 : if (mNoiseSuppression.Intersects(set.mNoiseSuppression)) {
376 0 : mNoiseSuppression.Intersect(set.mNoiseSuppression);
377 : }
378 0 : if (mAutoGainControl.Intersects(set.mAutoGainControl)) {
379 0 : mAutoGainControl.Intersect(set.mAutoGainControl);
380 : }
381 0 : if (mChannelCount.Intersects(set.mChannelCount)) {
382 0 : mChannelCount.Intersect(set.mChannelCount);
383 : }
384 : }
385 0 : }
386 :
387 : // MediaEngine helper
388 : //
389 : // The full algorithm for all devices. Sources that don't list capabilities
390 : // need to fake it and hardcode some by populating mHardcodedCapabilities above.
391 : //
392 : // Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
393 :
394 : // First, all devices have a minimum distance based on their deviceId.
395 : // If you have no other constraints, use this one. Reused by all device types.
396 :
397 : uint32_t
398 0 : MediaConstraintsHelper::GetMinimumFitnessDistance(
399 : const NormalizedConstraintSet &aConstraints,
400 : const nsString& aDeviceId)
401 : {
402 0 : return FitnessDistance(aDeviceId, aConstraints.mDeviceId);
403 : }
404 :
405 : template<class ValueType, class NormalizedRange>
406 : /* static */ uint32_t
407 0 : MediaConstraintsHelper::FitnessDistance(ValueType aN,
408 : const NormalizedRange& aRange)
409 : {
410 0 : if (aRange.mMin > aN || aRange.mMax < aN) {
411 0 : return UINT32_MAX;
412 : }
413 0 : if (aN == aRange.mIdeal.valueOr(aN)) {
414 0 : return 0;
415 : }
416 0 : return uint32_t(ValueType((std::abs(aN - aRange.mIdeal.value()) * 1000) /
417 0 : std::max(std::abs(aN), std::abs(aRange.mIdeal.value()))));
418 : }
419 :
420 : // Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
421 :
422 : /* static */ uint32_t
423 0 : MediaConstraintsHelper::FitnessDistance(
424 : nsString aN,
425 : const NormalizedConstraintSet::StringRange& aParams)
426 : {
427 0 : if (aParams.mExact.size() && aParams.mExact.find(aN) == aParams.mExact.end()) {
428 0 : return UINT32_MAX;
429 : }
430 0 : if (aParams.mIdeal.size() && aParams.mIdeal.find(aN) == aParams.mIdeal.end()) {
431 0 : return 1000;
432 : }
433 0 : return 0;
434 : }
435 :
436 : template<class MediaEngineSourceType>
437 : const char*
438 0 : MediaConstraintsHelper::FindBadConstraint(
439 : const NormalizedConstraints& aConstraints,
440 : const MediaEngineSourceType& aMediaEngineSource,
441 : const nsString& aDeviceId)
442 : {
443 : class MockDevice
444 : {
445 : public:
446 0 : NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MockDevice);
447 :
448 0 : explicit MockDevice(const MediaEngineSourceType* aMediaEngineSource,
449 : const nsString& aDeviceId)
450 : : mMediaEngineSource(aMediaEngineSource),
451 : // The following dud code exists to avoid 'unused typedef' error on linux.
452 0 : mDeviceId(MockDevice::HasThreadSafeRefCnt::value ? aDeviceId : nsString()) {}
453 :
454 0 : uint32_t GetBestFitnessDistance(
455 : const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
456 : bool aIsChrome)
457 : {
458 0 : return mMediaEngineSource->GetBestFitnessDistance(aConstraintSets,
459 0 : mDeviceId);
460 : }
461 :
462 : private:
463 0 : ~MockDevice() {}
464 :
465 : const MediaEngineSourceType* mMediaEngineSource;
466 : nsString mDeviceId;
467 : };
468 :
469 0 : Unused << typename MockDevice::HasThreadSafeRefCnt();
470 :
471 0 : nsTArray<RefPtr<MockDevice>> devices;
472 0 : devices.AppendElement(new MockDevice(&aMediaEngineSource, aDeviceId));
473 0 : return FindBadConstraint(aConstraints, devices);
474 : }
475 :
476 : void
477 0 : MediaConstraintsHelper::ConvertOldWithWarning(
478 : const dom::OwningBooleanOrConstrainBooleanParameters& old,
479 : dom::OwningBooleanOrConstrainBooleanParameters& to,
480 : const char* aMessageName,
481 : nsPIDOMWindowInner* aWindow) {
482 0 : if ((old.IsBoolean() ||
483 0 : old.GetAsConstrainBooleanParameters().mExact.WasPassed() ||
484 0 : old.GetAsConstrainBooleanParameters().mIdeal.WasPassed()) &&
485 0 : !(to.IsBoolean() ||
486 0 : to.GetAsConstrainBooleanParameters().mExact.WasPassed() ||
487 0 : to.GetAsConstrainBooleanParameters().mIdeal.WasPassed())) {
488 0 : nsCOMPtr<nsIDocument> doc = aWindow->GetDoc();
489 0 : if (doc) {
490 0 : nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
491 0 : NS_LITERAL_CSTRING("DOM"), doc,
492 : nsContentUtils::eDOM_PROPERTIES,
493 0 : aMessageName);
494 : }
495 0 : if (old.IsBoolean()) {
496 0 : to.SetAsBoolean() = old.GetAsBoolean();
497 : } else {
498 0 : to.SetAsConstrainBooleanParameters() = old.GetAsConstrainBooleanParameters();
499 : }
500 : }
501 0 : }
502 :
503 : }
|