| /* |
| * Copyright (C) 2018-2019 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "RealtimeVideoCaptureSource.h" |
| |
| #if ENABLE(MEDIA_STREAM) |
| #include "CaptureDevice.h" |
| #include "Logging.h" |
| #include "RealtimeMediaSourceCenter.h" |
| #include "RealtimeMediaSourceSettings.h" |
| #include <wtf/JSONValues.h> |
| |
| namespace WebCore { |
| |
| RealtimeVideoCaptureSource::RealtimeVideoCaptureSource(AtomString&& name, String&& id, String&& hashSalt, PageIdentifier pageIdentifier) |
| : RealtimeMediaSource(Type::Video, WTFMove(name), WTFMove(id), WTFMove(hashSalt), pageIdentifier) |
| { |
| } |
| |
| RealtimeVideoCaptureSource::~RealtimeVideoCaptureSource() |
| { |
| } |
| |
| const Vector<Ref<VideoPreset>>& RealtimeVideoCaptureSource::presets() |
| { |
| if (m_presets.isEmpty()) |
| generatePresets(); |
| |
| ASSERT(!m_presets.isEmpty()); |
| return m_presets; |
| } |
| |
| Vector<VideoPresetData> RealtimeVideoCaptureSource::presetsData() |
| { |
| return WTF::map(presets(), [](auto& preset) -> VideoPresetData { |
| return { preset->size, preset->frameRateRanges }; |
| }); |
| } |
| |
| void RealtimeVideoCaptureSource::setSupportedPresets(Vector<VideoPresetData>&& presetData) |
| { |
| auto presets = WTF::map(WTFMove(presetData), [](auto&& data) { |
| return VideoPreset::create(WTFMove(data)); |
| }); |
| setSupportedPresets(WTFMove(presets)); |
| } |
| |
| void RealtimeVideoCaptureSource::setSupportedPresets(const Vector<Ref<VideoPreset>>& presets) |
| { |
| m_presets = WTF::map(presets, [](auto& preset) { |
| return preset.copyRef(); |
| }); |
| |
| for (auto& preset : m_presets) { |
| std::sort(preset->frameRateRanges.begin(), preset->frameRateRanges.end(), |
| [&] (const auto& a, const auto& b) -> bool { |
| return a.minimum < b.minimum; |
| }); |
| } |
| } |
| |
| Span<const IntSize> RealtimeVideoCaptureSource::standardVideoSizes() |
| { |
| static constexpr IntSize sizes[] = { |
| { 112, 112 }, |
| { 160, 160 }, |
| { 160, 120 }, // 4:3, QQVGA |
| { 176, 144 }, // 4:3, QCIF |
| { 192, 192 }, |
| { 192, 112 }, // 16:9 |
| { 192, 144 }, // 3:4 |
| { 240, 240 }, |
| { 240, 160 }, // 3:2, HQVGA |
| { 320, 320 }, |
| { 320, 180 }, // 16:9 |
| { 320, 240 }, // 4:3, QVGA |
| { 352, 288 }, // CIF |
| { 480, 272 }, // 16:9 |
| { 480, 360 }, // 4:3 |
| { 480, 480 }, |
| { 640, 640 }, |
| { 640, 360 }, // 16:9, 360p nHD |
| { 640, 480 }, // 4:3 |
| { 720, 720 }, |
| { 800, 600 }, // 4:3, SVGA |
| { 960, 540 }, // 16:9, qHD |
| { 1024, 600 }, // 16:9, WSVGA |
| { 1024, 768 }, // 4:3, XGA |
| { 1280, 960 }, // 4:3 |
| { 1280, 1024 }, // 5:4, SXGA |
| { 1280, 720 }, // 16:9, WXGA |
| { 1366, 768 }, // 16:9, HD |
| { 1600, 1200 }, // 4:3, UXGA |
| { 1920, 1080 }, // 16:9, 1080p FHD |
| { 2560, 1440 }, // 16:9, QHD |
| { 2592, 1936 }, |
| { 3264, 2448 }, // 3:4 |
| { 3840, 2160 }, // 16:9, 4K UHD |
| }; |
| return sizes; |
| } |
| |
| template <typename ValueType> |
| static void updateMinMax(ValueType& min, ValueType& max, ValueType value) |
| { |
| min = std::min<ValueType>(min, value); |
| max = std::max<ValueType>(max, value); |
| } |
| |
| void RealtimeVideoCaptureSource::updateCapabilities(RealtimeMediaSourceCapabilities& capabilities) |
| { |
| ASSERT(!presets().isEmpty()); |
| |
| int minimumWidth = std::numeric_limits<int>::max(); |
| int maximumWidth = 0; |
| int minimumHeight = std::numeric_limits<int>::max(); |
| int maximumHeight = 0; |
| double minimumAspectRatio = std::numeric_limits<double>::max(); |
| double maximumAspectRatio = 0; |
| // RealtimeVideoSource will decimate frame rate if the source cannot go below a given value. |
| double minimumFrameRate = 1; |
| double maximumFrameRate = 0; |
| for (const auto& preset : presets()) { |
| const auto& size = preset->size; |
| updateMinMax(minimumWidth, maximumWidth, size.width()); |
| updateMinMax(minimumHeight, maximumHeight, size.height()); |
| updateMinMax(minimumAspectRatio, maximumAspectRatio, static_cast<double>(size.width()) / size.height()); |
| |
| for (const auto& rate : preset->frameRateRanges) |
| maximumFrameRate = std::max(maximumFrameRate, rate.maximum); |
| } |
| |
| if (canResizeVideoFrames()) { |
| minimumWidth = 1; |
| minimumHeight = 1; |
| minimumAspectRatio = 1.0 / maximumHeight; |
| maximumAspectRatio = maximumWidth; |
| } |
| |
| capabilities.setWidth({ minimumWidth, maximumWidth }); |
| capabilities.setHeight({ minimumHeight, maximumHeight }); |
| capabilities.setAspectRatio({ minimumAspectRatio, maximumAspectRatio }); |
| capabilities.setFrameRate({ minimumFrameRate, maximumFrameRate }); |
| } |
| |
| bool RealtimeVideoCaptureSource::supportsSizeAndFrameRate(std::optional<int> width, std::optional<int> height, std::optional<double> frameRate) |
| { |
| if (!width && !height && !frameRate) |
| return true; |
| |
| return !!bestSupportedSizeAndFrameRate(width, height, frameRate); |
| } |
| |
| bool RealtimeVideoCaptureSource::frameRateRangeIncludesRate(const FrameRateRange& range, double frameRate) |
| { |
| const double epsilon = 0.001; |
| return frameRate + epsilon >= range.minimum && frameRate - epsilon <= range.maximum; |
| } |
| |
| bool RealtimeVideoCaptureSource::presetSupportsFrameRate(RefPtr<VideoPreset> preset, double frameRate) |
| { |
| for (const auto& range : preset->frameRateRanges) { |
| if (frameRateRangeIncludesRate(range, frameRate)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool RealtimeVideoCaptureSource::supportsCaptureSize(std::optional<int> width, std::optional<int> height, const Function<bool(const IntSize&)>&& function) |
| { |
| if (width && height) |
| return function({ width.value(), height.value() }); |
| |
| if (width) { |
| for (auto& size : standardVideoSizes()) { |
| if (width.value() == size.width() && function({ size.width(), size.height() })) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| for (auto& size : standardVideoSizes()) { |
| if (height.value() == size.height() && function({ size.width(), size.height() })) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool RealtimeVideoCaptureSource::shouldUsePreset(VideoPreset& current, VideoPreset& candidate) |
| { |
| return candidate.size.width() <= current.size.width() && candidate.size.height() <= current.size.height() && prefersPreset(candidate); |
| } |
| |
| static inline double frameRateFromPreset(const VideoPreset& preset, double currentFrameRate) |
| { |
| auto minFrameRate = preset.minFrameRate(); |
| auto maxFrameRate = preset.maxFrameRate(); |
| return currentFrameRate >= minFrameRate && currentFrameRate <= maxFrameRate ? currentFrameRate : maxFrameRate; |
| } |
| |
| std::optional<RealtimeVideoCaptureSource::CaptureSizeAndFrameRate> RealtimeVideoCaptureSource::bestSupportedSizeAndFrameRate(std::optional<int> requestedWidth, std::optional<int> requestedHeight, std::optional<double> requestedFrameRate) |
| { |
| if (!requestedWidth && !requestedHeight && !requestedFrameRate) |
| return { }; |
| |
| if (!requestedWidth && !requestedHeight && !size().isEmpty()) { |
| requestedWidth = size().width(); |
| requestedHeight = size().height(); |
| } |
| |
| RefPtr<VideoPreset> exactSizePreset; |
| RefPtr<VideoPreset> aspectRatioPreset; |
| IntSize aspectRatioMatchSize; |
| RefPtr<VideoPreset> resizePreset; |
| IntSize resizeSize; |
| |
| for (const auto& preset : presets()) { |
| const auto& presetSize = preset->size; |
| |
| if (requestedFrameRate && !presetSupportsFrameRate(&preset.get(), requestedFrameRate.value())) |
| continue; |
| |
| if (!requestedWidth && !requestedHeight) { |
| exactSizePreset = preset.ptr(); |
| break; |
| } |
| |
| // Don't look at presets smaller than the requested resolution because we never want to resize larger. |
| if ((requestedWidth && presetSize.width() < requestedWidth.value()) || (requestedHeight && presetSize.height() < requestedHeight.value())) |
| continue; |
| |
| auto lookForExactSizeMatch = [&] (const IntSize& size) -> bool { |
| return preset->size == size; |
| }; |
| if (supportsCaptureSize(requestedWidth, requestedHeight, WTFMove(lookForExactSizeMatch))) { |
| if (!exactSizePreset || prefersPreset(preset)) |
| exactSizePreset = &preset.get(); |
| continue; |
| } |
| |
| IntSize encodingSize; |
| auto lookForAspectRatioMatch = [this, &preset, &encodingSize] (const IntSize& size) -> bool { |
| auto aspectRatio = [] (const IntSize size) -> double { |
| return size.width() / static_cast<double>(size.height()); |
| }; |
| if (std::abs(aspectRatio(preset->size) - aspectRatio(size)) > 10e-7 || !canResizeVideoFrames()) |
| return false; |
| |
| encodingSize = size; |
| return true; |
| }; |
| if (supportsCaptureSize(requestedWidth, requestedHeight, WTFMove(lookForAspectRatioMatch))) { |
| if (!aspectRatioPreset || shouldUsePreset(*aspectRatioPreset, preset)) { |
| aspectRatioPreset = &preset.get(); |
| aspectRatioMatchSize = encodingSize; |
| } |
| } |
| |
| if (exactSizePreset || aspectRatioPreset) |
| continue; |
| |
| if ((requestedWidth && requestedWidth.value() > preset->size.width()) || (requestedHeight && requestedHeight.value() > preset->size.height())) |
| continue; |
| |
| if (requestedWidth && requestedHeight) { |
| if (!resizePreset || shouldUsePreset(*resizePreset, preset)) { |
| resizePreset = &preset.get(); |
| resizeSize = { requestedWidth.value(), requestedHeight.value() }; |
| } |
| } else { |
| for (auto& standardSize : standardVideoSizes()) { |
| if (standardSize.width() > preset->size.width() || standardSize.height() > preset->size.height()) |
| break; |
| if ((requestedWidth && requestedWidth.value() != standardSize.width()) || (requestedHeight && requestedHeight.value() != standardSize.height())) |
| continue; |
| |
| if (!resizePreset || shouldUsePreset(*resizePreset, preset)) { |
| resizePreset = &preset.get(); |
| resizeSize = standardSize; |
| } |
| } |
| |
| if (!resizePreset || shouldUsePreset(*resizePreset, preset)) { |
| resizePreset = &preset.get(); |
| if (requestedWidth) |
| resizeSize = { requestedWidth.value(), requestedWidth.value() * preset->size.height() / preset->size.width()}; |
| else |
| resizeSize = { requestedHeight.value() * preset->size.width() / preset->size.height(), requestedHeight.value() }; |
| } |
| } |
| } |
| |
| if (!exactSizePreset && !aspectRatioPreset && !resizePreset) { |
| WTFLogAlways("RealtimeVideoCaptureSource::bestSupportedSizeAndFrameRate failed supporting constraints %d %d %f", requestedWidth ? *requestedWidth : -1, requestedHeight ? *requestedHeight : -1, requestedFrameRate ? *requestedFrameRate : -1); |
| for (const auto& preset : presets()) |
| preset->log(); |
| |
| return { }; |
| } |
| |
| if (exactSizePreset) { |
| auto size = exactSizePreset->size; |
| auto captureFrameRate = requestedFrameRate ? *requestedFrameRate : frameRateFromPreset(*exactSizePreset, frameRate()); |
| return CaptureSizeAndFrameRate { WTFMove(exactSizePreset), size, captureFrameRate }; |
| } |
| |
| if (aspectRatioPreset) { |
| auto captureFrameRate = requestedFrameRate ? *requestedFrameRate : frameRateFromPreset(*aspectRatioPreset, frameRate()); |
| return CaptureSizeAndFrameRate { WTFMove(aspectRatioPreset), aspectRatioMatchSize, captureFrameRate }; |
| } |
| |
| auto captureFrameRate = requestedFrameRate ? *requestedFrameRate : frameRateFromPreset(*resizePreset, frameRate()); |
| return CaptureSizeAndFrameRate { WTFMove(resizePreset), resizeSize, captureFrameRate }; |
| } |
| |
| void RealtimeVideoCaptureSource::setSizeAndFrameRate(std::optional<int> width, std::optional<int> height, std::optional<double> frameRate) |
| { |
| ALWAYS_LOG_IF(loggerPtr(), LOGIDENTIFIER, SizeAndFrameRate { width, height, frameRate }); |
| |
| auto size = this->size(); |
| if (!width && !height && !size.isEmpty()) { |
| width = size.width(); |
| height = size.height(); |
| } |
| |
| auto match = bestSupportedSizeAndFrameRate(width, height, frameRate); |
| if (!match) { |
| match = bestSupportedSizeAndFrameRate(width, height, { }); |
| ASSERT(match); |
| if (!match) |
| return; |
| } |
| |
| m_currentPreset = match->encodingPreset; |
| setFrameRateWithPreset(match->requestedFrameRate, match->encodingPreset); |
| |
| if (!match->requestedSize.isEmpty()) |
| setSize(match->requestedSize); |
| setFrameRate(match->requestedFrameRate); |
| } |
| |
| void RealtimeVideoCaptureSource::dispatchVideoFrameToObservers(VideoFrame& videoFrame, WebCore::VideoFrameTimeMetadata metadata) |
| { |
| MediaTime sampleTime = videoFrame.presentationTime(); |
| |
| auto frameTime = sampleTime.toDouble(); |
| m_observedFrameTimeStamps.append(frameTime); |
| m_observedFrameTimeStamps.removeAllMatching([&](auto time) { |
| return time <= frameTime - 2; |
| }); |
| |
| auto interval = m_observedFrameTimeStamps.last() - m_observedFrameTimeStamps.first(); |
| if (interval > 1) |
| m_observedFrameRate = (m_observedFrameTimeStamps.size() / interval); |
| |
| videoFrameAvailable(videoFrame, metadata); |
| } |
| |
| void RealtimeVideoCaptureSource::clientUpdatedSizeAndFrameRate(std::optional<int> width, std::optional<int> height, std::optional<double> frameRate) |
| { |
| // FIXME: We only change settings if capture resolution is below requested one. We should get the best preset for all clients. |
| auto& settings = this->settings(); |
| if (width && *width <= static_cast<int>(settings.width())) |
| width = { }; |
| if (height && *height <= static_cast<int>(settings.height())) |
| height = { }; |
| if (frameRate && *frameRate <= static_cast<double>(settings.frameRate())) |
| frameRate = { }; |
| |
| if (!width && !height && !frameRate) |
| return; |
| |
| auto match = bestSupportedSizeAndFrameRate(width, height, frameRate); |
| ERROR_LOG_IF(loggerPtr() && !match, LOGIDENTIFIER, "unable to find a preset that would match the size and frame rate"); |
| if (!match) |
| return; |
| |
| m_currentPreset = match->encodingPreset; |
| setFrameRateWithPreset(match->requestedFrameRate, match->encodingPreset); |
| setSize(match->encodingPreset->size); |
| setFrameRate(match->requestedFrameRate); |
| } |
| |
| void RealtimeVideoCaptureSource::ensureIntrinsicSizeMaintainsAspectRatio() |
| { |
| auto intrinsicSize = this->intrinsicSize(); |
| auto frameSize = size(); |
| if (!frameSize.height()) |
| frameSize.setHeight(intrinsicSize.height()); |
| if (!frameSize.width()) |
| frameSize.setWidth(intrinsicSize.width()); |
| |
| auto maxHeight = std::min(frameSize.height(), intrinsicSize.height()); |
| auto maxWidth = std::min(frameSize.width(), intrinsicSize.width()); |
| |
| auto heightForMaxWidth = maxWidth * intrinsicSize.height() / intrinsicSize.width(); |
| auto widthForMaxHeight = maxHeight * intrinsicSize.width() / intrinsicSize.height(); |
| |
| if (heightForMaxWidth <= maxHeight) { |
| setSize({ maxWidth, heightForMaxWidth }); |
| return; |
| } |
| if (widthForMaxHeight <= maxWidth) { |
| setSize({ widthForMaxHeight, maxHeight }); |
| return; |
| } |
| |
| setSize(intrinsicSize); |
| } |
| |
| #if !RELEASE_LOG_DISABLED |
| Ref<JSON::Object> SizeAndFrameRate::toJSONObject() const |
| { |
| auto object = JSON::Object::create(); |
| |
| object->setDouble("width"_s, width ? width.value() : 0); |
| object->setDouble("height"_s, height ? height.value() : 0); |
| object->setDouble("frameRate"_s, frameRate ? frameRate.value() : 0); |
| |
| return object; |
| } |
| |
| String SizeAndFrameRate::toJSONString() const |
| { |
| return toJSONObject()->toJSONString(); |
| } |
| #endif |
| |
| } // namespace WebCore |
| |
| #endif // ENABLE(MEDIA_STREAM) |