[MediaStream] Restructure getDisplayMedia classes
[WebKit-https.git] / Source / WebCore / platform / mediastream / mac / ScreenDisplayCaptureSourceMac.mm
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 "ScreenDisplayCaptureSourceMac.h"
28
29 #if ENABLE(MEDIA_STREAM) && PLATFORM(MAC)
30
31 #include "GraphicsContextCG.h"
32 #include "ImageBuffer.h"
33 #include "Logging.h"
34 #include "MediaConstraints.h"
35 #include "MediaSampleAVFObjC.h"
36 #include "NotImplemented.h"
37 #include "PlatformLayer.h"
38 #include "RealtimeMediaSourceSettings.h"
39
40 #include "CoreVideoSoftLink.h"
41
42 extern "C" {
43 size_t CGDisplayModeGetPixelsWide(CGDisplayModeRef);
44 size_t CGDisplayModeGetPixelsHigh(CGDisplayModeRef);
45 }
46
47 namespace WebCore {
48
49 static std::optional<CGDirectDisplayID> updateDisplayID(CGDirectDisplayID displayID)
50 {
51     uint32_t displayCount = 0;
52     auto err = CGGetActiveDisplayList(0, nullptr, &displayCount);
53     if (err) {
54         RELEASE_LOG(Media, "CGGetActiveDisplayList() returned error %d when trying to get display count", static_cast<int>(err));
55         return std::nullopt;
56     }
57
58     if (!displayCount) {
59         RELEASE_LOG(Media, "CGGetActiveDisplayList() returned a display count of 0");
60         return std::nullopt;
61     }
62
63     CGDirectDisplayID activeDisplays[displayCount];
64     err = CGGetActiveDisplayList(displayCount, &(activeDisplays[0]), &displayCount);
65     if (err) {
66         RELEASE_LOG(Media, "CGGetActiveDisplayList() returned error %d when trying to get the active display list", static_cast<int>(err));
67         return std::nullopt;
68     }
69
70     auto displayMask = CGDisplayIDToOpenGLDisplayMask(displayID);
71     for (auto display : activeDisplays) {
72         if (displayMask == CGDisplayIDToOpenGLDisplayMask(display))
73             return display;
74     }
75
76     return std::nullopt;
77 }
78
79 CaptureSourceOrError ScreenDisplayCaptureSourceMac::create(const String& deviceID, const MediaConstraints* constraints)
80 {
81     bool ok;
82     auto displayID = deviceID.toUIntStrict(&ok);
83     if (!ok) {
84         RELEASE_LOG(Media, "Display ID does not convert to 32-bit integer");
85         return { };
86     }
87
88     auto actualDisplayID = updateDisplayID(displayID);
89     if (!actualDisplayID)
90         return { };
91
92     auto source = adoptRef(*new ScreenDisplayCaptureSourceMac(actualDisplayID.value()));
93     if (constraints && source->applyConstraints(*constraints))
94         return { };
95
96     return CaptureSourceOrError(WTFMove(source));
97 }
98
99 ScreenDisplayCaptureSourceMac::ScreenDisplayCaptureSourceMac(uint32_t displayID)
100     : DisplayCaptureSourceCocoa("Screen")
101     , m_displayID(displayID)
102 {
103 }
104
105 ScreenDisplayCaptureSourceMac::~ScreenDisplayCaptureSourceMac()
106 {
107     if (m_observingDisplayChanges)
108         CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallBack, this);
109
110     m_currentFrame = nullptr;
111 }
112
113 bool ScreenDisplayCaptureSourceMac::createDisplayStream()
114 {
115     static const int screenQueueMaximumLength = 6;
116
117     auto actualDisplayID = updateDisplayID(m_displayID);
118     if (!actualDisplayID) {
119         captureFailed();
120         return false;
121     }
122
123     if (m_displayID != actualDisplayID.value()) {
124         m_displayID = actualDisplayID.value();
125         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::createDisplayStream(%p), display ID changed to %d", this, static_cast<int>(m_displayID));
126     }
127
128     if (!m_displayStream) {
129         if (size().isEmpty()) {
130             RetainPtr<CGDisplayModeRef> displayMode = adoptCF(CGDisplayCopyDisplayMode(m_displayID));
131             auto screenWidth = CGDisplayModeGetPixelsWide(displayMode.get());
132             auto screenHeight = CGDisplayModeGetPixelsHigh(displayMode.get());
133             if (!screenWidth || !screenHeight) {
134                 RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::createDisplayStream(%p), unable to get screen width/height", this);
135                 captureFailed();
136                 return false;
137             }
138             setWidth(screenWidth);
139             setHeight(screenHeight);
140         }
141
142         if (!m_captureQueue)
143             m_captureQueue = adoptOSObject(dispatch_queue_create("ScreenDisplayCaptureSourceMac Capture Queue", DISPATCH_QUEUE_SERIAL));
144
145         double frameTime = 1 / frameRate();
146         auto frameTimeCF = adoptCF(CFNumberCreate(nullptr,  kCFNumberDoubleType,  &frameTime));
147         int depth = screenQueueMaximumLength;
148         auto depthCF = adoptCF(CFNumberCreate(nullptr,  kCFNumberIntType,  &depth));
149         CFTypeRef keys[] = {
150             kCGDisplayStreamMinimumFrameTime,
151             kCGDisplayStreamQueueDepth,
152             kCGDisplayStreamColorSpace,
153             kCGDisplayStreamShowCursor,
154         };
155         CFTypeRef values[] = {
156             frameTimeCF.get(),
157             depthCF.get(),
158             sRGBColorSpaceRef(),
159             kCFBooleanTrue,
160         };
161         auto streamOptions = adoptCF(CFDictionaryCreate(kCFAllocatorDefault, keys, values, WTF_ARRAY_LENGTH(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
162
163         auto weakThis = makeWeakPtr(*this);
164         m_frameAvailableBlock = Block_copy(^(CGDisplayStreamFrameStatus status, uint64_t displayTime, IOSurfaceRef frameSurface, CGDisplayStreamUpdateRef updateRef) {
165             if (!weakThis)
166                 return;
167
168             weakThis->frameAvailable(status, displayTime, frameSurface, updateRef);
169         });
170
171         m_displayStream = adoptCF(CGDisplayStreamCreateWithDispatchQueue(m_displayID, size().width(), size().height(), kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, streamOptions.get(), m_captureQueue.get(), m_frameAvailableBlock));
172         if (!m_displayStream) {
173             RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::createDisplayStream(%p), CGDisplayStreamCreate failed", this);
174             captureFailed();
175             return false;
176         }
177     }
178
179     if (!m_observingDisplayChanges) {
180         CGDisplayRegisterReconfigurationCallback(displayReconfigurationCallBack, this);
181         m_observingDisplayChanges = true;
182     }
183
184     return true;
185 }
186
187 void ScreenDisplayCaptureSourceMac::startProducingData()
188 {
189     DisplayCaptureSourceCocoa::startProducingData();
190
191     if (m_isRunning)
192         return;
193
194     startDisplayStream();
195 }
196
197 void ScreenDisplayCaptureSourceMac::stopProducingData()
198 {
199     DisplayCaptureSourceCocoa::stopProducingData();
200
201     if (!m_isRunning)
202         return;
203
204     if (m_displayStream)
205         CGDisplayStreamStop(m_displayStream.get());
206
207     m_isRunning = false;
208 }
209
210 void ScreenDisplayCaptureSourceMac::generateFrame()
211 {
212     if (!m_currentFrame.ioSurface())
213         return;
214
215     DisplaySurface currentFrame;
216     {
217         LockHolder lock(m_currentFrameMutex);
218         currentFrame = m_currentFrame.ioSurface();
219     }
220
221     auto pixelBuffer = pixelBufferFromIOSurface(currentFrame.ioSurface());
222     if (!pixelBuffer)
223         return;
224
225     auto sampleBuffer = sampleBufferFromPixelBuffer(pixelBuffer.get());
226     if (!sampleBuffer)
227         return;
228
229     videoSampleAvailable(MediaSampleAVFObjC::create(sampleBuffer.get()));
230 }
231
232 void ScreenDisplayCaptureSourceMac::startDisplayStream()
233 {
234     auto actualDisplayID = updateDisplayID(m_displayID);
235     if (!actualDisplayID)
236         return;
237
238     if (m_displayID != actualDisplayID.value()) {
239         m_displayID = actualDisplayID.value();
240         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::startDisplayStream(%p), display ID changed to %d", this, static_cast<int>(m_displayID));
241     }
242
243     if (!m_displayStream && !createDisplayStream())
244         return;
245
246     auto err = CGDisplayStreamStart(m_displayStream.get());
247     if (err) {
248         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::startDisplayStream(%p), CGDisplayStreamStart failed with error %d", this, static_cast<int>(err));
249         captureFailed();
250         return;
251     }
252
253     m_isRunning = true;
254 }
255
256 bool ScreenDisplayCaptureSourceMac::applySize(const IntSize& newSize)
257 {
258     if (!DisplayCaptureSourceCocoa::applySize(newSize))
259         return false;
260
261     m_displayStream = nullptr;
262     return true;
263 }
264
265 bool ScreenDisplayCaptureSourceMac::applyFrameRate(double rate)
266 {
267     if (frameRate() != rate)
268         m_displayStream = nullptr;
269
270     return DisplayCaptureSourceCocoa::applyFrameRate(rate);
271 }
272
273 void ScreenDisplayCaptureSourceMac::commitConfiguration()
274 {
275     if (m_isRunning && !m_displayStream)
276         startDisplayStream();
277 }
278
279 void ScreenDisplayCaptureSourceMac::displayWasReconfigured(CGDirectDisplayID, CGDisplayChangeSummaryFlags)
280 {
281     // FIXME: implement!
282 }
283
284 void ScreenDisplayCaptureSourceMac::displayReconfigurationCallBack(CGDirectDisplayID display, CGDisplayChangeSummaryFlags flags, void *userInfo)
285 {
286     if (userInfo)
287         reinterpret_cast<ScreenDisplayCaptureSourceMac *>(userInfo)->displayWasReconfigured(display, flags);
288 }
289
290 void ScreenDisplayCaptureSourceMac::frameAvailable(CGDisplayStreamFrameStatus status, uint64_t displayTime, IOSurfaceRef frameSurface, CGDisplayStreamUpdateRef updateRef)
291 {
292     switch (status) {
293     case kCGDisplayStreamFrameStatusFrameComplete:
294         break;
295
296     case kCGDisplayStreamFrameStatusFrameIdle:
297         break;
298
299     case kCGDisplayStreamFrameStatusFrameBlank:
300         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::frameAvailable(%p), kCGDisplayStreamFrameStatusFrameBlank", this);
301         break;
302
303     case kCGDisplayStreamFrameStatusStopped:
304         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::frameAvailable(%p), kCGDisplayStreamFrameStatusStopped", this);
305         break;
306     }
307
308     if (!frameSurface || !displayTime)
309         return;
310
311     size_t count;
312     auto* rects = CGDisplayStreamUpdateGetRects(updateRef, kCGDisplayStreamUpdateDirtyRects, &count);
313     if (!rects || !count)
314         return;
315
316     LockHolder lock(m_currentFrameMutex);
317     m_currentFrame = frameSurface;
318 }
319
320 std::optional<CaptureDevice> ScreenDisplayCaptureSourceMac::screenCaptureDeviceWithPersistentID(const String& deviceID)
321 {
322     bool ok;
323     auto displayID = deviceID.toUIntStrict(&ok);
324     if (!ok) {
325         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::screenCaptureDeviceWithPersistentID: display ID does not convert to 32-bit integer");
326         return std::nullopt;
327     }
328
329     auto actualDisplayID = updateDisplayID(displayID);
330     if (!actualDisplayID)
331         return std::nullopt;
332
333     auto device = CaptureDevice(String::number(actualDisplayID.value()), CaptureDevice::DeviceType::Screen, "ScreenCaptureDevice"_s);
334     device.setEnabled(true);
335
336     return device;
337 }
338
339 void ScreenDisplayCaptureSourceMac::screenCaptureDevices(Vector<CaptureDevice>& displays)
340 {
341     uint32_t displayCount = 0;
342     auto err = CGGetActiveDisplayList(0, nullptr, &displayCount);
343     if (err) {
344         RELEASE_LOG(Media, "CGGetActiveDisplayList() returned error %d when trying to get display count", (int)err);
345         return;
346     }
347
348     if (!displayCount) {
349         RELEASE_LOG(Media, "CGGetActiveDisplayList() returned a display count of 0");
350         return;
351     }
352
353     CGDirectDisplayID activeDisplays[displayCount];
354     err = CGGetActiveDisplayList(displayCount, &(activeDisplays[0]), &displayCount);
355     if (err) {
356         RELEASE_LOG(Media, "CGGetActiveDisplayList() returned error %d when trying to get the active display list", (int)err);
357         return;
358     }
359
360     int count = 0;
361     for (auto displayID : activeDisplays) {
362         CaptureDevice displayDevice(String::number(displayID), CaptureDevice::DeviceType::Screen, makeString("Screen ", String::number(count++)));
363         displayDevice.setEnabled(CGDisplayIDToOpenGLDisplayMask(displayID));
364         displays.append(WTFMove(displayDevice));
365     }
366 }
367
368 } // namespace WebCore
369
370 #endif // ENABLE(MEDIA_STREAM) && PLATFORM(MAC)