2 * Copyright (C) 2017 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "DisplayCaptureSourceCocoa.h"
29 #if ENABLE(MEDIA_STREAM)
32 #include "RealtimeMediaSource.h"
33 #include "RealtimeMediaSourceCenter.h"
34 #include "RealtimeMediaSourceSettings.h"
36 #include <CoreMedia/CMSync.h>
37 #include <mach/mach_time.h>
38 #include <pal/avfoundation/MediaTimeAVFoundation.h>
39 #include <pal/cf/CoreMediaSoftLink.h>
40 #include <pal/spi/cf/CoreAudioSPI.h>
42 #include <wtf/MainThread.h>
43 #include <wtf/NeverDestroyed.h>
45 #include "CoreVideoSoftLink.h"
50 DisplayCaptureSourceCocoa::DisplayCaptureSourceCocoa(const String& name)
51 : RealtimeMediaSource("", Type::Video, name)
52 , m_timer(RunLoop::current(), this, &DisplayCaptureSourceCocoa::emitFrame)
56 DisplayCaptureSourceCocoa::~DisplayCaptureSourceCocoa()
59 RealtimeMediaSourceCenter::singleton().videoFactory().unsetActiveSource(*this);
63 const RealtimeMediaSourceCapabilities& DisplayCaptureSourceCocoa::capabilities() const
65 if (!m_capabilities) {
66 RealtimeMediaSourceCapabilities capabilities(settings().supportedConstraints());
68 // FIXME: what should these be?
69 capabilities.setWidth(CapabilityValueOrRange(72, 2880));
70 capabilities.setHeight(CapabilityValueOrRange(45, 1800));
71 capabilities.setFrameRate(CapabilityValueOrRange(.01, 60.0));
73 m_capabilities = WTFMove(capabilities);
75 return m_capabilities.value();
78 const RealtimeMediaSourceSettings& DisplayCaptureSourceCocoa::settings() const
80 if (!m_currentSettings) {
81 RealtimeMediaSourceSettings settings;
82 settings.setFrameRate(frameRate());
83 auto size = this->size();
84 if (size.width() && size.height()) {
85 settings.setWidth(size.width());
86 settings.setHeight(size.height());
89 RealtimeMediaSourceSupportedConstraints supportedConstraints;
90 supportedConstraints.setSupportsFrameRate(true);
91 supportedConstraints.setSupportsWidth(true);
92 supportedConstraints.setSupportsHeight(true);
93 supportedConstraints.setSupportsAspectRatio(true);
94 settings.setSupportedConstraints(supportedConstraints);
96 m_currentSettings = WTFMove(settings);
98 return m_currentSettings.value();
101 void DisplayCaptureSourceCocoa::settingsDidChange()
103 m_currentSettings = std::nullopt;
104 RealtimeMediaSource::settingsDidChange();
107 void DisplayCaptureSourceCocoa::startProducingData()
110 RealtimeMediaSourceCenter::singleton().videoFactory().setActiveSource(*this);
113 m_startTime = MonotonicTime::now();
114 m_timer.startRepeating(1_ms * lround(1000 / frameRate()));
117 void DisplayCaptureSourceCocoa::stopProducingData()
120 m_elapsedTime += MonotonicTime::now() - m_startTime;
121 m_startTime = MonotonicTime::nan();
124 Seconds DisplayCaptureSourceCocoa::elapsedTime()
126 if (std::isnan(m_startTime))
127 return m_elapsedTime;
129 return m_elapsedTime + (MonotonicTime::now() - m_startTime);
132 bool DisplayCaptureSourceCocoa::applySize(const IntSize& newSize)
134 if (size() == newSize)
137 m_bufferAttributes = nullptr;
141 bool DisplayCaptureSourceCocoa::applyFrameRate(double rate)
143 if (m_timer.isActive())
144 m_timer.startRepeating(1_ms * lround(1000 / rate));
149 void DisplayCaptureSourceCocoa::emitFrame()
157 RetainPtr<CMSampleBufferRef> DisplayCaptureSourceCocoa::sampleBufferFromPixelBuffer(CVPixelBufferRef pixelBuffer)
162 CMTime sampleTime = CMTimeMake(((elapsedTime() + 100_ms) * 100).seconds(), 100);
163 CMSampleTimingInfo timingInfo = { kCMTimeInvalid, sampleTime, sampleTime };
165 CMVideoFormatDescriptionRef formatDescription = nullptr;
166 auto status = CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, (CVImageBufferRef)pixelBuffer, &formatDescription);
168 RELEASE_LOG(Media, "Failed to initialize CMVideoFormatDescription with error code: %d", static_cast<int>(status));
172 CMSampleBufferRef sampleBuffer;
173 status = CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault, (CVImageBufferRef)pixelBuffer, formatDescription, &timingInfo, &sampleBuffer);
174 CFRelease(formatDescription);
176 RELEASE_LOG(Media, "Failed to initialize CMSampleBuffer with error code: %d", static_cast<int>(status));
180 return adoptCF(sampleBuffer);
183 #if HAVE(IOSURFACE) && PLATFORM(MAC)
184 static int32_t roundUpToMacroblockMultiple(int32_t size)
186 return (size + 15) & ~15;
189 RetainPtr<CVPixelBufferRef> DisplayCaptureSourceCocoa::pixelBufferFromIOSurface(IOSurfaceRef surface)
191 if (!m_bufferAttributes) {
192 m_bufferAttributes = adoptCF(CFDictionaryCreateMutable(nullptr, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
194 auto format = IOSurfaceGetPixelFormat(surface);
195 if (format == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange || format == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
197 // If the width x height isn't a multiple of 16 x 16 and the surface has extra memory in the planes, set pixel buffer attributes to reflect it.
198 auto width = IOSurfaceGetWidth(surface);
199 auto height = IOSurfaceGetHeight(surface);
200 int32_t extendedRight = roundUpToMacroblockMultiple(width) - width;
201 int32_t extendedBottom = roundUpToMacroblockMultiple(height) - height;
203 if ((IOSurfaceGetBytesPerRowOfPlane(surface, 0) >= width + extendedRight)
204 && (IOSurfaceGetBytesPerRowOfPlane(surface, 1) >= width + extendedRight)
205 && (IOSurfaceGetAllocSize(surface) >= (height + extendedBottom) * IOSurfaceGetBytesPerRowOfPlane(surface, 0) * 3 / 2)) {
206 auto cfInt = adoptCF(CFNumberCreate(nullptr, kCFNumberIntType, &extendedRight));
207 CFDictionarySetValue(m_bufferAttributes.get(), kCVPixelBufferExtendedPixelsRightKey, cfInt.get());
208 cfInt = adoptCF(CFNumberCreate(nullptr, kCFNumberIntType, &extendedBottom));
209 CFDictionarySetValue(m_bufferAttributes.get(), kCVPixelBufferExtendedPixelsBottomKey, cfInt.get());
213 CFDictionarySetValue(m_bufferAttributes.get(), kCVPixelBufferOpenGLCompatibilityKey, kCFBooleanTrue);
216 CVPixelBufferRef pixelBuffer;
217 auto status = CVPixelBufferCreateWithIOSurface(kCFAllocatorDefault, surface, m_bufferAttributes.get(), &pixelBuffer);
219 RELEASE_LOG(Media, "CVPixelBufferCreateWithIOSurface failed with error code: %d", static_cast<int>(status));
223 return adoptCF(pixelBuffer);
227 } // namespace WebCore
229 #endif // ENABLE(MEDIA_STREAM)