e74cbee792efe5da942417d7f4e295c7ee852079
[WebKit-https.git] / Source / WebCore / platform / mediastream / mac / DisplayCaptureSourceCocoa.cpp
1 /*
2  * Copyright (C) 2017 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  * 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.
12  *
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.
24  */
25
26 #include "config.h"
27 #include "DisplayCaptureSourceCocoa.h"
28
29 #if ENABLE(MEDIA_STREAM)
30
31 #include "Logging.h"
32 #include "RealtimeMediaSource.h"
33 #include "RealtimeMediaSourceCenter.h"
34 #include "RealtimeMediaSourceSettings.h"
35 #include "Timer.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>
41 #include <sys/time.h>
42 #include <wtf/MainThread.h>
43 #include <wtf/NeverDestroyed.h>
44
45 #include "CoreVideoSoftLink.h"
46
47 namespace WebCore {
48 using namespace PAL;
49
50 DisplayCaptureSourceCocoa::DisplayCaptureSourceCocoa(const String& name)
51     : RealtimeMediaSource("", Type::Video, name)
52     , m_timer(RunLoop::current(), this, &DisplayCaptureSourceCocoa::emitFrame)
53 {
54 }
55
56 DisplayCaptureSourceCocoa::~DisplayCaptureSourceCocoa()
57 {
58 #if PLATFORM(IOS)
59     RealtimeMediaSourceCenter::singleton().videoFactory().unsetActiveSource(*this);
60 #endif
61 }
62
63 const RealtimeMediaSourceCapabilities& DisplayCaptureSourceCocoa::capabilities() const
64 {
65     if (!m_capabilities) {
66         RealtimeMediaSourceCapabilities capabilities(settings().supportedConstraints());
67
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));
72
73         m_capabilities = WTFMove(capabilities);
74     }
75     return m_capabilities.value();
76 }
77
78 const RealtimeMediaSourceSettings& DisplayCaptureSourceCocoa::settings() const
79 {
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());
87         }
88
89         RealtimeMediaSourceSupportedConstraints supportedConstraints;
90         supportedConstraints.setSupportsFrameRate(true);
91         supportedConstraints.setSupportsWidth(true);
92         supportedConstraints.setSupportsHeight(true);
93         supportedConstraints.setSupportsAspectRatio(true);
94         settings.setSupportedConstraints(supportedConstraints);
95
96         m_currentSettings = WTFMove(settings);
97     }
98     return m_currentSettings.value();
99 }
100
101 void DisplayCaptureSourceCocoa::settingsDidChange()
102 {
103     m_currentSettings = std::nullopt;
104     RealtimeMediaSource::settingsDidChange();
105 }
106
107 void DisplayCaptureSourceCocoa::startProducingData()
108 {
109 #if PLATFORM(IOS)
110     RealtimeMediaSourceCenter::singleton().videoFactory().setActiveSource(*this);
111 #endif
112
113     m_startTime = MonotonicTime::now();
114     m_timer.startRepeating(1_ms * lround(1000 / frameRate()));
115 }
116
117 void DisplayCaptureSourceCocoa::stopProducingData()
118 {
119     m_timer.stop();
120     m_elapsedTime += MonotonicTime::now() - m_startTime;
121     m_startTime = MonotonicTime::nan();
122 }
123
124 Seconds DisplayCaptureSourceCocoa::elapsedTime()
125 {
126     if (std::isnan(m_startTime))
127         return m_elapsedTime;
128
129     return m_elapsedTime + (MonotonicTime::now() - m_startTime);
130 }
131
132 bool DisplayCaptureSourceCocoa::applySize(const IntSize& newSize)
133 {
134     if (size() == newSize)
135         return true;
136
137     m_bufferAttributes = nullptr;
138     return true;
139 }
140
141 bool DisplayCaptureSourceCocoa::applyFrameRate(double rate)
142 {
143     if (m_timer.isActive())
144         m_timer.startRepeating(1_ms * lround(1000 / rate));
145
146     return true;
147 }
148
149 void DisplayCaptureSourceCocoa::emitFrame()
150 {
151     if (muted())
152         return;
153
154     generateFrame();
155 }
156
157 RetainPtr<CMSampleBufferRef> DisplayCaptureSourceCocoa::sampleBufferFromPixelBuffer(CVPixelBufferRef pixelBuffer)
158 {
159     if (!pixelBuffer)
160         return nullptr;
161
162     CMTime sampleTime = CMTimeMake(((elapsedTime() + 100_ms) * 100).seconds(), 100);
163     CMSampleTimingInfo timingInfo = { kCMTimeInvalid, sampleTime, sampleTime };
164
165     CMVideoFormatDescriptionRef formatDescription = nullptr;
166     auto status = CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, (CVImageBufferRef)pixelBuffer, &formatDescription);
167     if (status) {
168         RELEASE_LOG(Media, "Failed to initialize CMVideoFormatDescription with error code: %d", static_cast<int>(status));
169         return nullptr;
170     }
171
172     CMSampleBufferRef sampleBuffer;
173     status = CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault, (CVImageBufferRef)pixelBuffer, formatDescription, &timingInfo, &sampleBuffer);
174     CFRelease(formatDescription);
175     if (status) {
176         RELEASE_LOG(Media, "Failed to initialize CMSampleBuffer with error code: %d", static_cast<int>(status));
177         return nullptr;
178     }
179
180     return adoptCF(sampleBuffer);
181 }
182
183 #if HAVE(IOSURFACE) && PLATFORM(MAC)
184 static int32_t roundUpToMacroblockMultiple(int32_t size)
185 {
186     return (size + 15) & ~15;
187 }
188
189 RetainPtr<CVPixelBufferRef> DisplayCaptureSourceCocoa::pixelBufferFromIOSurface(IOSurfaceRef surface)
190 {
191     if (!m_bufferAttributes) {
192         m_bufferAttributes = adoptCF(CFDictionaryCreateMutable(nullptr, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
193
194         auto format = IOSurfaceGetPixelFormat(surface);
195         if (format == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange || format == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
196
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;
202
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());
210             }
211         }
212
213         CFDictionarySetValue(m_bufferAttributes.get(), kCVPixelBufferOpenGLCompatibilityKey, kCFBooleanTrue);
214     }
215
216     CVPixelBufferRef pixelBuffer;
217     auto status = CVPixelBufferCreateWithIOSurface(kCFAllocatorDefault, surface, m_bufferAttributes.get(), &pixelBuffer);
218     if (status) {
219         RELEASE_LOG(Media, "CVPixelBufferCreateWithIOSurface failed with error code: %d", static_cast<int>(status));
220         return nullptr;
221     }
222
223     return adoptCF(pixelBuffer);
224 }
225 #endif
226
227 } // namespace WebCore
228
229 #endif // ENABLE(MEDIA_STREAM)