| 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 | |