1e8ac6c86e2f9fe7140b8acc99efc5961f2cf3a0
[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 #include "RealtimeVideoUtilities.h"
40
41 #include "CoreVideoSoftLink.h"
42
43 extern "C" {
44 size_t CGDisplayModeGetPixelsWide(CGDisplayModeRef);
45 size_t CGDisplayModeGetPixelsHigh(CGDisplayModeRef);
46 }
47
48 namespace WebCore {
49
50 static Optional<CGDirectDisplayID> updateDisplayID(CGDirectDisplayID displayID)
51 {
52     uint32_t displayCount = 0;
53     auto err = CGGetActiveDisplayList(0, nullptr, &displayCount);
54     if (err) {
55         RELEASE_LOG(Media, "CGGetActiveDisplayList() returned error %d when trying to get display count", static_cast<int>(err));
56         return WTF::nullopt;
57     }
58
59     if (!displayCount) {
60         RELEASE_LOG(Media, "CGGetActiveDisplayList() returned a display count of 0");
61         return WTF::nullopt;
62     }
63
64     CGDirectDisplayID activeDisplays[displayCount];
65     err = CGGetActiveDisplayList(displayCount, &(activeDisplays[0]), &displayCount);
66     if (err) {
67         RELEASE_LOG(Media, "CGGetActiveDisplayList() returned error %d when trying to get the active display list", static_cast<int>(err));
68         return WTF::nullopt;
69     }
70
71     auto displayMask = CGDisplayIDToOpenGLDisplayMask(displayID);
72     for (auto display : activeDisplays) {
73         if (displayMask == CGDisplayIDToOpenGLDisplayMask(display))
74             return display;
75     }
76
77     return WTF::nullopt;
78 }
79
80 CaptureSourceOrError ScreenDisplayCaptureSourceMac::create(String&& deviceID, const MediaConstraints* constraints)
81 {
82     bool ok;
83     auto displayID = deviceID.toUIntStrict(&ok);
84     if (!ok) {
85         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::create: Display ID does not convert to 32-bit integer");
86         return { };
87     }
88
89     auto actualDisplayID = updateDisplayID(displayID);
90     if (!actualDisplayID)
91         return { };
92
93     auto source = adoptRef(*new ScreenDisplayCaptureSourceMac(actualDisplayID.value(), "Screen"_s)); // FIXME: figure out what title to use
94     if (constraints && source->applyConstraints(*constraints))
95         return { };
96
97     return CaptureSourceOrError(WTFMove(source));
98 }
99
100 ScreenDisplayCaptureSourceMac::ScreenDisplayCaptureSourceMac(uint32_t displayID, String&& title)
101     : DisplayCaptureSourceCocoa(WTFMove(title))
102     , m_displayID(displayID)
103 {
104 }
105
106 ScreenDisplayCaptureSourceMac::~ScreenDisplayCaptureSourceMac()
107 {
108     if (m_observingDisplayChanges)
109         CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallBack, this);
110
111     m_currentFrame = nullptr;
112 }
113
114 bool ScreenDisplayCaptureSourceMac::createDisplayStream()
115 {
116     static const int screenQueueMaximumLength = 6;
117
118     auto actualDisplayID = updateDisplayID(m_displayID);
119     if (!actualDisplayID) {
120         captureFailed();
121         return false;
122     }
123
124     if (m_displayID != actualDisplayID.value()) {
125         m_displayID = actualDisplayID.value();
126         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::createDisplayStream: display ID changed to %d", static_cast<int>(m_displayID));
127         m_displayStream = nullptr;
128     }
129
130     if (!m_displayStream) {
131         auto displayMode = adoptCF(CGDisplayCopyDisplayMode(m_displayID));
132         auto screenWidth = CGDisplayModeGetPixelsWide(displayMode.get());
133         auto screenHeight = CGDisplayModeGetPixelsHigh(displayMode.get());
134         if (!screenWidth || !screenHeight) {
135             RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::createDisplayStream: unable to get screen width/height");
136             captureFailed();
137             return false;
138         }
139         setIntrinsicSize(IntSize(screenWidth, screenHeight));
140
141         if (!m_captureQueue)
142             m_captureQueue = adoptOSObject(dispatch_queue_create("ScreenDisplayCaptureSourceMac Capture Queue", DISPATCH_QUEUE_SERIAL));
143
144         NSDictionary* streamOptions = @{
145             (__bridge NSString *)kCGDisplayStreamMinimumFrameTime : @(1 / frameRate()),
146             (__bridge NSString *)kCGDisplayStreamQueueDepth : @(screenQueueMaximumLength),
147             (__bridge NSString *)kCGDisplayStreamColorSpace : (__bridge id)sRGBColorSpaceRef(),
148             (__bridge NSString *)kCGDisplayStreamShowCursor : @(YES),
149         };
150
151         auto weakThis = makeWeakPtr(*this);
152         auto frameAvailableBlock = ^(CGDisplayStreamFrameStatus status, uint64_t displayTime, IOSurfaceRef frameSurface, CGDisplayStreamUpdateRef updateRef) {
153             if (!weakThis)
154                 return;
155
156             weakThis->frameAvailable(status, displayTime, frameSurface, updateRef);
157         };
158
159         m_displayStream = adoptCF(CGDisplayStreamCreateWithDispatchQueue(m_displayID, screenWidth, screenHeight, preferedPixelBufferFormat(), (__bridge CFDictionaryRef)streamOptions, m_captureQueue.get(), frameAvailableBlock));
160         if (!m_displayStream) {
161             RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::createDisplayStream: CGDisplayStreamCreate failed");
162             captureFailed();
163             return false;
164         }
165     }
166
167     if (!m_observingDisplayChanges) {
168         CGDisplayRegisterReconfigurationCallback(displayReconfigurationCallBack, this);
169         m_observingDisplayChanges = true;
170     }
171
172     return true;
173 }
174
175 void ScreenDisplayCaptureSourceMac::startProducingData()
176 {
177     DisplayCaptureSourceCocoa::startProducingData();
178
179     if (m_isRunning)
180         return;
181
182     startDisplayStream();
183 }
184
185 void ScreenDisplayCaptureSourceMac::stopProducingData()
186 {
187     DisplayCaptureSourceCocoa::stopProducingData();
188
189     if (!m_isRunning)
190         return;
191
192     if (m_displayStream)
193         CGDisplayStreamStop(m_displayStream.get());
194
195     m_isRunning = false;
196 }
197
198 DisplayCaptureSourceCocoa::DisplayFrameType ScreenDisplayCaptureSourceMac::generateFrame()
199 {
200     DisplaySurface currentFrame;
201     {
202         LockHolder lock(m_currentFrameMutex);
203         currentFrame = m_currentFrame.ioSurface();
204     }
205
206     return DisplayCaptureSourceCocoa::DisplayFrameType { RetainPtr<IOSurfaceRef> { currentFrame.ioSurface() } };
207 }
208
209 void ScreenDisplayCaptureSourceMac::startDisplayStream()
210 {
211     auto actualDisplayID = updateDisplayID(m_displayID);
212     if (!actualDisplayID)
213         return;
214
215     if (m_displayID != actualDisplayID.value()) {
216         m_displayID = actualDisplayID.value();
217         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::startDisplayStream: display ID changed to %d", static_cast<int>(m_displayID));
218     }
219
220     if (!m_displayStream && !createDisplayStream())
221         return;
222
223     auto err = CGDisplayStreamStart(m_displayStream.get());
224     if (err) {
225         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::startDisplayStream: CGDisplayStreamStart failed with error %d", static_cast<int>(err));
226         captureFailed();
227         return;
228     }
229
230     m_isRunning = true;
231 }
232
233 void ScreenDisplayCaptureSourceMac::commitConfiguration()
234 {
235     if (m_isRunning && !m_displayStream)
236         startDisplayStream();
237 }
238
239 void ScreenDisplayCaptureSourceMac::displayWasReconfigured(CGDirectDisplayID, CGDisplayChangeSummaryFlags)
240 {
241     // FIXME: implement!
242 }
243
244 void ScreenDisplayCaptureSourceMac::displayReconfigurationCallBack(CGDirectDisplayID display, CGDisplayChangeSummaryFlags flags, void *userInfo)
245 {
246     if (userInfo)
247         reinterpret_cast<ScreenDisplayCaptureSourceMac *>(userInfo)->displayWasReconfigured(display, flags);
248 }
249
250 void ScreenDisplayCaptureSourceMac::frameAvailable(CGDisplayStreamFrameStatus status, uint64_t displayTime, IOSurfaceRef frameSurface, CGDisplayStreamUpdateRef updateRef)
251 {
252     switch (status) {
253     case kCGDisplayStreamFrameStatusFrameComplete:
254         break;
255
256     case kCGDisplayStreamFrameStatusFrameIdle:
257         break;
258
259     case kCGDisplayStreamFrameStatusFrameBlank:
260         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::frameAvailable: kCGDisplayStreamFrameStatusFrameBlank");
261         break;
262
263     case kCGDisplayStreamFrameStatusStopped:
264         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::frameAvailable: kCGDisplayStreamFrameStatusStopped");
265         break;
266     }
267
268     if (!frameSurface || !displayTime)
269         return;
270
271     size_t count;
272     auto* rects = CGDisplayStreamUpdateGetRects(updateRef, kCGDisplayStreamUpdateDirtyRects, &count);
273     if (!rects || !count)
274         return;
275
276     LockHolder lock(m_currentFrameMutex);
277     m_currentFrame = frameSurface;
278 }
279
280 Optional<CaptureDevice> ScreenDisplayCaptureSourceMac::screenCaptureDeviceWithPersistentID(const String& deviceID)
281 {
282     bool ok;
283     auto displayID = deviceID.toUIntStrict(&ok);
284     if (!ok) {
285         RELEASE_LOG(Media, "ScreenDisplayCaptureSourceMac::screenCaptureDeviceWithPersistentID: display ID does not convert to 32-bit integer");
286         return WTF::nullopt;
287     }
288
289     auto actualDisplayID = updateDisplayID(displayID);
290     if (!actualDisplayID)
291         return WTF::nullopt;
292
293     auto device = CaptureDevice(String::number(actualDisplayID.value()), CaptureDevice::DeviceType::Screen, "ScreenCaptureDevice"_s);
294     device.setEnabled(true);
295
296     return device;
297 }
298
299 void ScreenDisplayCaptureSourceMac::screenCaptureDevices(Vector<CaptureDevice>& displays)
300 {
301     uint32_t displayCount = 0;
302     auto err = CGGetActiveDisplayList(0, nullptr, &displayCount);
303     if (err) {
304         RELEASE_LOG(Media, "CGGetActiveDisplayList() returned error %d when trying to get display count", (int)err);
305         return;
306     }
307
308     if (!displayCount) {
309         RELEASE_LOG(Media, "CGGetActiveDisplayList() returned a display count of 0");
310         return;
311     }
312
313     CGDirectDisplayID activeDisplays[displayCount];
314     err = CGGetActiveDisplayList(displayCount, &(activeDisplays[0]), &displayCount);
315     if (err) {
316         RELEASE_LOG(Media, "CGGetActiveDisplayList() returned error %d when trying to get the active display list", (int)err);
317         return;
318     }
319
320     int count = 0;
321     for (auto displayID : activeDisplays) {
322         CaptureDevice displayDevice(String::number(displayID), CaptureDevice::DeviceType::Screen, makeString("Screen ", String::number(count++)));
323         displayDevice.setEnabled(CGDisplayIDToOpenGLDisplayMask(displayID));
324         displays.append(WTFMove(displayDevice));
325     }
326 }
327
328 } // namespace WebCore
329
330 #endif // ENABLE(MEDIA_STREAM) && PLATFORM(MAC)