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 | |
39 | namespace WebCore { |
40 | |
41 | const 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 | |
56 | double 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 | |
80 | double 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 | |
92 | void 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 | |
124 | void FlattenedConstraint::set(const MediaConstraint& constraint) |
125 | { |
126 | if (find(constraint.constraintType())) |
127 | return; |
128 | |
129 | append(constraint); |
130 | } |
131 | |
132 | void 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 | |
164 | void FlattenedConstraint::append(const MediaConstraint& constraint) |
165 | { |
166 | #ifndef NDEBUG |
167 | ++m_generation; |
168 | #endif |
169 | |
170 | m_variants.append(ConstraintHolder::create(constraint)); |
171 | } |
172 | |
173 | const 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 | |
183 | void 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 | |
191 | void 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 | |
220 | void 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 | |
251 | void 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 | |
280 | void 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 | |
309 | void 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 | |
340 | size_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 | |
350 | bool MediaTrackConstraintSetMap::isEmpty() const |
351 | { |
352 | return !size(); |
353 | } |
354 | |
355 | static 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 | |
378 | bool 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 | |
390 | void 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 | |