1/*
2 * Copyright (C) 2016 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * 3. Neither the name of Google Inc. nor the names of its contributors
15 * may be used to endorse or promote products derived from this
16 * software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32#include "MediaConstraints.h"
33
34#if ENABLE(MEDIA_STREAM)
35#include "RealtimeMediaSourceCenter.h"
36#include "RealtimeMediaSourceSupportedConstraints.h"
37#include <wtf/StdLibExtras.h>
38
39namespace WebCore {
40
41const String& StringConstraint::find(const WTF::Function<bool(const String&)>& filter) const
42{
43 for (auto& constraint : m_exact) {
44 if (filter(constraint))
45 return constraint;
46 }
47
48 for (auto& constraint : m_ideal) {
49 if (filter(constraint))
50 return constraint;
51 }
52
53 return emptyString();
54}
55
56double StringConstraint::fitnessDistance(const String& value) const
57{
58 // https://w3c.github.io/mediacapture-main/#dfn-applyconstraints
59
60 // 1. If the constraint is not supported by the browser, the fitness distance is 0.
61 if (isEmpty())
62 return 0;
63
64 // 2. If the constraint is required ('min', 'max', or 'exact'), and the settings
65 // dictionary's value for the constraint does not satisfy the constraint, the
66 // fitness distance is positive infinity.
67 if (!m_exact.isEmpty() && m_exact.find(value) == notFound)
68 return std::numeric_limits<double>::infinity();
69
70 // 3. If no ideal value is specified, the fitness distance is 0.
71 if (m_ideal.isEmpty())
72 return 0;
73
74 // 5. For all string and enum non-required constraints (deviceId, groupId, facingMode,
75 // echoCancellation), the fitness distance is the result of the formula
76 // (actual == ideal) ? 0 : 1
77 return m_ideal.find(value) != notFound ? 0 : 1;
78}
79
80double StringConstraint::fitnessDistance(const Vector<String>& values) const
81{
82 if (isEmpty())
83 return 0;
84
85 double minimumDistance = std::numeric_limits<double>::infinity();
86 for (auto& value : values)
87 minimumDistance = std::min(minimumDistance, fitnessDistance(value));
88
89 return minimumDistance;
90}
91
92void StringConstraint::merge(const MediaConstraint& other)
93{
94 ASSERT(other.isString());
95 const StringConstraint& typedOther = downcast<StringConstraint>(other);
96
97 if (typedOther.isEmpty())
98 return;
99
100 Vector<String> values;
101 if (typedOther.getExact(values)) {
102 if (m_exact.isEmpty())
103 m_exact = values;
104 else {
105 for (auto& value : values) {
106 if (m_exact.find(value) == notFound)
107 m_exact.append(value);
108 }
109 }
110 }
111
112 if (typedOther.getIdeal(values)) {
113 if (m_ideal.isEmpty())
114 m_ideal = values;
115 else {
116 for (auto& value : values) {
117 if (m_ideal.find(value) == notFound)
118 m_ideal.append(value);
119 }
120 }
121 }
122}
123
124void FlattenedConstraint::set(const MediaConstraint& constraint)
125{
126 if (find(constraint.constraintType()))
127 return;
128
129 append(constraint);
130}
131
132void FlattenedConstraint::merge(const MediaConstraint& constraint)
133{
134 for (auto& variant : *this) {
135 if (variant.constraintType() != constraint.constraintType())
136 continue;
137
138 switch (variant.dataType()) {
139 case MediaConstraint::DataType::Integer:
140 ASSERT(constraint.isInt());
141 downcast<const IntConstraint>(variant).merge(downcast<const IntConstraint>(constraint));
142 return;
143 case MediaConstraint::DataType::Double:
144 ASSERT(constraint.isDouble());
145 downcast<const DoubleConstraint>(variant).merge(downcast<const DoubleConstraint>(constraint));
146 return;
147 case MediaConstraint::DataType::Boolean:
148 ASSERT(constraint.isBoolean());
149 downcast<const BooleanConstraint>(variant).merge(downcast<const BooleanConstraint>(constraint));
150 return;
151 case MediaConstraint::DataType::String:
152 ASSERT(constraint.isString());
153 downcast<const StringConstraint>(variant).merge(downcast<const StringConstraint>(constraint));
154 return;
155 case MediaConstraint::DataType::None:
156 ASSERT_NOT_REACHED();
157 return;
158 }
159 }
160
161 append(constraint);
162}
163
164void FlattenedConstraint::append(const MediaConstraint& constraint)
165{
166#ifndef NDEBUG
167 ++m_generation;
168#endif
169
170 m_variants.append(ConstraintHolder::create(constraint));
171}
172
173const MediaConstraint* FlattenedConstraint::find(MediaConstraintType type) const
174{
175 for (auto& variant : m_variants) {
176 if (variant.constraintType() == type)
177 return &variant.constraint();
178 }
179
180 return nullptr;
181}
182
183void MediaTrackConstraintSetMap::forEach(WTF::Function<void(const MediaConstraint&)>&& callback) const
184{
185 filter([callback = WTFMove(callback)] (const MediaConstraint& constraint) mutable {
186 callback(constraint);
187 return false;
188 });
189}
190
191void MediaTrackConstraintSetMap::filter(const WTF::Function<bool(const MediaConstraint&)>& callback) const
192{
193 if (m_width && !m_width->isEmpty() && callback(*m_width))
194 return;
195 if (m_height && !m_height->isEmpty() && callback(*m_height))
196 return;
197 if (m_sampleRate && !m_sampleRate->isEmpty() && callback(*m_sampleRate))
198 return;
199 if (m_sampleSize && !m_sampleSize->isEmpty() && callback(*m_sampleSize))
200 return;
201
202 if (m_aspectRatio && !m_aspectRatio->isEmpty() && callback(*m_aspectRatio))
203 return;
204 if (m_frameRate && !m_frameRate->isEmpty() && callback(*m_frameRate))
205 return;
206 if (m_volume && !m_volume->isEmpty() && callback(*m_volume))
207 return;
208
209 if (m_echoCancellation && !m_echoCancellation->isEmpty() && callback(*m_echoCancellation))
210 return;
211
212 if (m_facingMode && !m_facingMode->isEmpty() && callback(*m_facingMode))
213 return;
214 if (m_deviceId && !m_deviceId->isEmpty() && callback(*m_deviceId))
215 return;
216 if (m_groupId && !m_groupId->isEmpty() && callback(*m_groupId))
217 return;
218}
219
220void MediaTrackConstraintSetMap::set(MediaConstraintType constraintType, Optional<IntConstraint>&& constraint)
221{
222 switch (constraintType) {
223 case MediaConstraintType::Width:
224 m_width = WTFMove(constraint);
225 break;
226 case MediaConstraintType::Height:
227 m_height = WTFMove(constraint);
228 break;
229 case MediaConstraintType::SampleRate:
230 m_sampleRate = WTFMove(constraint);
231 break;
232 case MediaConstraintType::SampleSize:
233 m_sampleSize = WTFMove(constraint);
234 break;
235
236 case MediaConstraintType::AspectRatio:
237 case MediaConstraintType::FrameRate:
238 case MediaConstraintType::Volume:
239 case MediaConstraintType::EchoCancellation:
240 case MediaConstraintType::FacingMode:
241 case MediaConstraintType::DeviceId:
242 case MediaConstraintType::GroupId:
243 case MediaConstraintType::DisplaySurface:
244 case MediaConstraintType::LogicalSurface:
245 case MediaConstraintType::Unknown:
246 ASSERT_NOT_REACHED();
247 break;
248 }
249}
250
251void MediaTrackConstraintSetMap::set(MediaConstraintType constraintType, Optional<DoubleConstraint>&& constraint)
252{
253 switch (constraintType) {
254 case MediaConstraintType::AspectRatio:
255 m_aspectRatio = WTFMove(constraint);
256 break;
257 case MediaConstraintType::FrameRate:
258 m_frameRate = WTFMove(constraint);
259 break;
260 case MediaConstraintType::Volume:
261 m_volume = WTFMove(constraint);
262 break;
263
264 case MediaConstraintType::Width:
265 case MediaConstraintType::Height:
266 case MediaConstraintType::SampleRate:
267 case MediaConstraintType::SampleSize:
268 case MediaConstraintType::EchoCancellation:
269 case MediaConstraintType::FacingMode:
270 case MediaConstraintType::DeviceId:
271 case MediaConstraintType::GroupId:
272 case MediaConstraintType::DisplaySurface:
273 case MediaConstraintType::LogicalSurface:
274 case MediaConstraintType::Unknown:
275 ASSERT_NOT_REACHED();
276 break;
277 }
278}
279
280void MediaTrackConstraintSetMap::set(MediaConstraintType constraintType, Optional<BooleanConstraint>&& constraint)
281{
282 switch (constraintType) {
283 case MediaConstraintType::EchoCancellation:
284 m_echoCancellation = WTFMove(constraint);
285 break;
286 case MediaConstraintType::DisplaySurface:
287 m_displaySurface = WTFMove(constraint);
288 break;
289 case MediaConstraintType::LogicalSurface:
290 m_logicalSurface = WTFMove(constraint);
291 break;
292
293 case MediaConstraintType::Width:
294 case MediaConstraintType::Height:
295 case MediaConstraintType::SampleRate:
296 case MediaConstraintType::SampleSize:
297 case MediaConstraintType::AspectRatio:
298 case MediaConstraintType::FrameRate:
299 case MediaConstraintType::Volume:
300 case MediaConstraintType::FacingMode:
301 case MediaConstraintType::DeviceId:
302 case MediaConstraintType::GroupId:
303 case MediaConstraintType::Unknown:
304 ASSERT_NOT_REACHED();
305 break;
306 }
307}
308
309void MediaTrackConstraintSetMap::set(MediaConstraintType constraintType, Optional<StringConstraint>&& constraint)
310{
311 switch (constraintType) {
312 case MediaConstraintType::FacingMode:
313 m_facingMode = WTFMove(constraint);
314 break;
315 case MediaConstraintType::DeviceId:
316 if (constraint)
317 constraint->removeEmptyStringConstraint();
318 m_deviceId = WTFMove(constraint);
319 break;
320 case MediaConstraintType::GroupId:
321 m_groupId = WTFMove(constraint);
322 break;
323
324 case MediaConstraintType::Width:
325 case MediaConstraintType::Height:
326 case MediaConstraintType::SampleRate:
327 case MediaConstraintType::SampleSize:
328 case MediaConstraintType::AspectRatio:
329 case MediaConstraintType::FrameRate:
330 case MediaConstraintType::Volume:
331 case MediaConstraintType::EchoCancellation:
332 case MediaConstraintType::DisplaySurface:
333 case MediaConstraintType::LogicalSurface:
334 case MediaConstraintType::Unknown:
335 ASSERT_NOT_REACHED();
336 break;
337 }
338}
339
340size_t MediaTrackConstraintSetMap::size() const
341{
342 size_t count = 0;
343 forEach([&count] (const MediaConstraint&) mutable {
344 ++count;
345 });
346
347 return count;
348}
349
350bool MediaTrackConstraintSetMap::isEmpty() const
351{
352 return !size();
353}
354
355static inline void addDefaultVideoConstraints(MediaTrackConstraintSetMap& videoConstraints, bool addFrameRateConstraint, bool addSizeConstraint, bool addFacingModeConstraint)
356{
357 if (addFrameRateConstraint) {
358 DoubleConstraint frameRateConstraint({ }, MediaConstraintType::FrameRate);
359 frameRateConstraint.setIdeal(30);
360 videoConstraints.set(MediaConstraintType::FrameRate, WTFMove(frameRateConstraint));
361 }
362 if (addSizeConstraint) {
363 IntConstraint widthConstraint({ }, MediaConstraintType::Width);
364 widthConstraint.setIdeal(640);
365 videoConstraints.set(MediaConstraintType::Width, WTFMove(widthConstraint));
366
367 IntConstraint heightConstraint({ }, MediaConstraintType::Height);
368 heightConstraint.setIdeal(480);
369 videoConstraints.set(MediaConstraintType::Height, WTFMove(heightConstraint));
370 }
371 if (addFacingModeConstraint) {
372 StringConstraint facingModeConstraint({ }, MediaConstraintType::FacingMode);
373 facingModeConstraint.setIdeal("user"_s);
374 videoConstraints.set(MediaConstraintType::FacingMode, WTFMove(facingModeConstraint));
375 }
376}
377
378bool MediaConstraints::isConstraintSet(const WTF::Function<bool(const MediaTrackConstraintSetMap&)>& callback)
379{
380 if (callback(mandatoryConstraints))
381 return true;
382
383 for (const auto& constraint : advancedConstraints) {
384 if (callback(constraint))
385 return true;
386 }
387 return false;
388}
389
390void MediaConstraints::setDefaultVideoConstraints()
391{
392 // 640x480, 30fps, front-facing camera
393 bool needsFrameRateConstraints = !isConstraintSet([](const MediaTrackConstraintSetMap& constraint) {
394 return !!constraint.frameRate() || !!constraint.width() || !!constraint.height();
395 });
396
397 bool needsSizeConstraints = !isConstraintSet([](const MediaTrackConstraintSetMap& constraint) {
398 return !!constraint.width() || !!constraint.height();
399 });
400
401 bool needsFacingModeConstraints = !isConstraintSet([](const MediaTrackConstraintSetMap& constraint) {
402 return !!constraint.facingMode() || !!constraint.deviceId();
403 });
404
405 addDefaultVideoConstraints(mandatoryConstraints, needsFrameRateConstraints, needsSizeConstraints, needsFacingModeConstraints);
406}
407
408}
409
410#endif // ENABLE(MEDIA_STREAM)
411