[MediaStream] RealtimeMediaSource should be able to vend hashed IDs
[WebKit-https.git] / Source / WebCore / platform / mediastream / mac / ScreenDisplayCaptureSourceMac.mm
1 /*
2  * Copyright (C) 2017-2018 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(String&& deviceID, const MediaConstraints* constraints)
80 {
81     bool ok;
82     auto displayID = deviceID.toUIntStrict(&ok);
83     if (!ok) {
84         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::create: 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(), "Screen"_s)); // FIXME: figure out what title to use
93     if (constraints && source->applyConstraints(*constraints))
94         return { };
95
96     return CaptureSourceOrError(WTFMove(source));
97 }
98
99 ScreenDisplayCaptureSourceMac::ScreenDisplayCaptureSourceMac(uint32_t displayID, String&& title)
100     : DisplayCaptureSourceCocoa(WTFMove(title))
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: display ID changed to %d", static_cast<int>(m_displayID));
126     }
127
128     if (!m_displayStream) {
129         auto displayMode = adoptCF(CGDisplayCopyDisplayMode(m_displayID));
130         auto screenWidth = CGDisplayModeGetPixelsWide(displayMode.get());
131         auto screenHeight = CGDisplayModeGetPixelsHigh(displayMode.get());
132         if (!screenWidth || !screenHeight) {
133             RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::createDisplayStream: unable to get screen width/height");
134             captureFailed();
135             return false;
136         }
137         setIntrinsicSize(IntSize(screenWidth, screenHeight));
138
139         if (!m_captureQueue)
140             m_captureQueue = adoptOSObject(dispatch_queue_create("ScreenDisplayCaptureSourceMac Capture Queue", DISPATCH_QUEUE_SERIAL));
141
142         double frameTime = 1 / frameRate();
143         auto frameTimeCF = adoptCF(CFNumberCreate(nullptr,  kCFNumberDoubleType,  &frameTime));
144         int depth = screenQueueMaximumLength;
145         auto depthCF = adoptCF(CFNumberCreate(nullptr,  kCFNumberIntType,  &depth));
146         CFTypeRef keys[] = {
147             kCGDisplayStreamMinimumFrameTime,
148             kCGDisplayStreamQueueDepth,
149             kCGDisplayStreamColorSpace,
150             kCGDisplayStreamShowCursor,
151         };
152         CFTypeRef values[] = {
153             frameTimeCF.get(),
154             depthCF.get(),
155             sRGBColorSpaceRef(),
156             kCFBooleanTrue,
157         };
158         auto streamOptions = adoptCF(CFDictionaryCreate(kCFAllocatorDefault, keys, values, WTF_ARRAY_LENGTH(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
159
160         auto weakThis = makeWeakPtr(*this);
161         m_frameAvailableBlock = Block_copy(^(CGDisplayStreamFrameStatus status, uint64_t displayTime, IOSurfaceRef frameSurface, CGDisplayStreamUpdateRef updateRef) {
162             if (!weakThis)
163                 return;
164
165             weakThis->frameAvailable(status, displayTime, frameSurface, updateRef);
166         });
167
168         auto size = frameSize();
169         m_displayStream = adoptCF(CGDisplayStreamCreateWithDispatchQueue(m_displayID, size.width(), size.height(), kCVPixelFormatType_420YpCbCr8Planar, streamOptions.get(), m_captureQueue.get(), m_frameAvailableBlock));
170         if (!m_displayStream) {
171             RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::createDisplayStream: CGDisplayStreamCreate failed");
172             captureFailed();
173             return false;
174         }
175     }
176
177     if (!m_observingDisplayChanges) {
178         CGDisplayRegisterReconfigurationCallback(displayReconfigurationCallBack, this);
179         m_observingDisplayChanges = true;
180     }
181
182     return true;
183 }
184
185 void ScreenDisplayCaptureSourceMac::startProducingData()
186 {
187     DisplayCaptureSourceCocoa::startProducingData();
188
189     if (m_isRunning)
190         return;
191
192     startDisplayStream();
193 }
194
195 void ScreenDisplayCaptureSourceMac::stopProducingData()
196 {
197     DisplayCaptureSourceCocoa::stopProducingData();
198
199     if (!m_isRunning)
200         return;
201
202     if (m_displayStream)
203         CGDisplayStreamStop(m_displayStream.get());
204
205     m_isRunning = false;
206 }
207
208 RetainPtr<CVPixelBufferRef> ScreenDisplayCaptureSourceMac::generateFrame()
209 {
210     if (!m_currentFrame.ioSurface())
211         return nullptr;
212
213     DisplaySurface currentFrame;
214     {
215         LockHolder lock(m_currentFrameMutex);
216         currentFrame = m_currentFrame.ioSurface();
217     }
218
219     return pixelBufferFromIOSurface(currentFrame.ioSurface());
220 }
221
222 void ScreenDisplayCaptureSourceMac::startDisplayStream()
223 {
224     auto actualDisplayID = updateDisplayID(m_displayID);
225     if (!actualDisplayID)
226         return;
227
228     if (m_displayID != actualDisplayID.value()) {
229         m_displayID = actualDisplayID.value();
230         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::startDisplayStream: display ID changed to %d", static_cast<int>(m_displayID));
231     }
232
233     if (!m_displayStream && !createDisplayStream())
234         return;
235
236     auto err = CGDisplayStreamStart(m_displayStream.get());
237     if (err) {
238         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::startDisplayStream: CGDisplayStreamStart failed with error %d", static_cast<int>(err));
239         captureFailed();
240         return;
241     }
242
243     m_isRunning = true;
244 }
245
246 void ScreenDisplayCaptureSourceMac::settingsDidChange(OptionSet<RealtimeMediaSourceSettings::Flag> settings)
247 {
248     if (settings.containsAny({ RealtimeMediaSourceSettings::Flag::Width, RealtimeMediaSourceSettings::Flag::Height, RealtimeMediaSourceSettings::Flag::FrameRate }))
249         m_displayStream = nullptr;
250
251     return DisplayCaptureSourceCocoa::settingsDidChange(settings);
252 }
253
254 void ScreenDisplayCaptureSourceMac::commitConfiguration()
255 {
256     if (m_isRunning && !m_displayStream)
257         startDisplayStream();
258 }
259
260 void ScreenDisplayCaptureSourceMac::displayWasReconfigured(CGDirectDisplayID, CGDisplayChangeSummaryFlags)
261 {
262     // FIXME: implement!
263 }
264
265 void ScreenDisplayCaptureSourceMac::displayReconfigurationCallBack(CGDirectDisplayID display, CGDisplayChangeSummaryFlags flags, void *userInfo)
266 {
267     if (userInfo)
268         reinterpret_cast<ScreenDisplayCaptureSourceMac *>(userInfo)->displayWasReconfigured(display, flags);
269 }
270
271 void ScreenDisplayCaptureSourceMac::frameAvailable(CGDisplayStreamFrameStatus status, uint64_t displayTime, IOSurfaceRef frameSurface, CGDisplayStreamUpdateRef updateRef)
272 {
273     switch (status) {
274     case kCGDisplayStreamFrameStatusFrameComplete:
275         break;
276
277     case kCGDisplayStreamFrameStatusFrameIdle:
278         break;
279
280     case kCGDisplayStreamFrameStatusFrameBlank:
281         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::frameAvailable: kCGDisplayStreamFrameStatusFrameBlank");
282         break;
283
284     case kCGDisplayStreamFrameStatusStopped:
285         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::frameAvailable: kCGDisplayStreamFrameStatusStopped");
286         break;
287     }
288
289     if (!frameSurface || !displayTime)
290         return;
291
292     size_t count;
293     auto* rects = CGDisplayStreamUpdateGetRects(updateRef, kCGDisplayStreamUpdateDirtyRects, &count);
294     if (!rects || !count)
295         return;
296
297     LockHolder lock(m_currentFrameMutex);
298     m_currentFrame = frameSurface;
299 }
300
301 std::optional<CaptureDevice> ScreenDisplayCaptureSourceMac::screenCaptureDeviceWithPersistentID(const String& deviceID)
302 {
303     bool ok;
304     auto displayID = deviceID.toUIntStrict(&ok);
305     if (!ok) {
306         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::screenCaptureDeviceWithPersistentID: display ID does not convert to 32-bit integer");
307         return std::nullopt;
308     }
309
310     auto actualDisplayID = updateDisplayID(displayID);
311     if (!actualDisplayID)
312         return std::nullopt;
313
314     auto device = CaptureDevice(String::number(actualDisplayID.value()), CaptureDevice::DeviceType::Screen, "ScreenCaptureDevice"_s);
315     device.setEnabled(true);
316
317     return device;
318 }
319
320 void ScreenDisplayCaptureSourceMac::screenCaptureDevices(Vector<CaptureDevice>& displays)
321 {
322     uint32_t displayCount = 0;
323     auto err = CGGetActiveDisplayList(0, nullptr, &displayCount);
324     if (err) {
325         RELEASE_LOG(Media, "CGGetActiveDisplayList() returned error %d when trying to get display count", (int)err);
326         return;
327     }
328
329     if (!displayCount) {
330         RELEASE_LOG(Media, "CGGetActiveDisplayList() returned a display count of 0");
331         return;
332     }
333
334     CGDirectDisplayID activeDisplays[displayCount];
335     err = CGGetActiveDisplayList(displayCount, &(activeDisplays[0]), &displayCount);
336     if (err) {
337         RELEASE_LOG(Media, "CGGetActiveDisplayList() returned error %d when trying to get the active display list", (int)err);
338         return;
339     }
340
341     int count = 0;
342     for (auto displayID : activeDisplays) {
343         CaptureDevice displayDevice(String::number(displayID), CaptureDevice::DeviceType::Screen, makeString("Screen ", String::number(count++)));
344         displayDevice.setEnabled(CGDisplayIDToOpenGLDisplayMask(displayID));
345         displays.append(WTFMove(displayDevice));
346     }
347 }
348
349 } // namespace WebCore
350
351 #endif // ENABLE(MEDIA_STREAM) && PLATFORM(MAC)