[MediaStream] RealtimeMediaSource should be able to vend hashed IDs
[WebKit-https.git] / Source / WebCore / platform / mediastream / mac / WindowDisplayCaptureSourceMac.mm
1 /*
2  * Copyright (C) 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 #import "config.h"
27 #import "WindowDisplayCaptureSourceMac.h"
28
29 #if ENABLE(MEDIA_STREAM) && PLATFORM(MAC)
30
31 #import "GraphicsContextCG.h"
32 #import "ImageBuffer.h"
33 #import "Logging.h"
34 #import "MediaConstraints.h"
35 #import "MediaSampleAVFObjC.h"
36 #import "NotImplemented.h"
37 #import "PixelBufferConformerCV.h"
38 #import "PlatformLayer.h"
39 #import "RealtimeMediaSourceSettings.h"
40 #import <pal/cf/CoreMediaSoftLink.h>
41 #import <pal/spi/cg/CoreGraphicsSPI.h>
42 #import <wtf/cf/TypeCastsCF.h>
43
44 #import "CoreVideoSoftLink.h"
45
46 WTF_DECLARE_CF_TYPE_TRAIT(CGImage);
47
48 namespace WebCore {
49 using namespace PAL;
50
51 static bool anyOfCGWindow(const Function<bool(CFDictionaryRef info, unsigned id, const String& title)>& predicate)
52 {
53     auto windows = adoptCF(CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID));
54     if (!windows) {
55         RELEASE_LOG(Media, "CGWindowListCopyWindowInfo returned NULL");
56         return false;
57     }
58
59     auto windowCount = CFArrayGetCount(windows.get());
60     for (auto i = 0; i < windowCount; i++) {
61         auto windowInfo = checked_cf_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(windows.get(), i));
62         if (!windowInfo)
63             continue;
64
65         // Menus, the dock, etc have layers greater than 0, skip them.
66         auto windowLayerRef = checked_cf_cast<CFNumberRef>(CFDictionaryGetValue(windowInfo, kCGWindowLayer));
67         if (!windowLayerRef)
68             continue;
69         unsigned windowLayer;
70         CFNumberGetValue(windowLayerRef, kCFNumberIntType, &windowLayer);
71         if (windowLayer)
72             continue;
73
74         auto onScreen = checked_cf_cast<CFBooleanRef>(CFDictionaryGetValue(windowInfo, kCGWindowIsOnscreen));
75         if (!CFBooleanGetValue(onScreen))
76             continue;
77
78         auto windowIDRef = checked_cf_cast<CFNumberRef>(CFDictionaryGetValue(windowInfo, kCGWindowNumber));
79         if (!windowIDRef)
80             continue;
81         unsigned windowID;
82         CFNumberGetValue(windowIDRef, kCFNumberIntType, &windowID);
83         if (!windowID)
84             continue;
85
86         auto windowTitle = checked_cf_cast<CFStringRef>(CFDictionaryGetValue(windowInfo, kCGWindowName));
87
88         if (predicate(windowInfo, windowID, windowTitle))
89             return true;
90     }
91
92     return false;
93 }
94
95 static RetainPtr<CFDictionaryRef> windowDescription(CGWindowID id)
96 {
97     auto ids = adoptCF(CFArrayCreate(nullptr, reinterpret_cast<const void**>(&id), 1, nullptr));
98     auto windows = adoptCF(CGWindowListCreateDescriptionFromArray(ids.get()));
99     if (!windows)
100         return nullptr;
101
102     return checked_cf_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(windows.get(), 0));
103 }
104
105 CaptureSourceOrError WindowDisplayCaptureSourceMac::create(String&& windowID, const MediaConstraints* constraints)
106 {
107     bool ok;
108     auto actualID = windowID.toUIntStrict(&ok);
109     if (!ok) {
110         RELEASE_LOG(Media, "WindowDisplayCaptureSourceMac::create: window ID does not convert to 32-bit integer");
111         return { };
112     }
113
114     auto windowInfo = windowDescription(actualID);
115     if (!windowInfo) {
116         RELEASE_LOG(Media, "WindowDisplayCaptureSourceMac::create: invalid window ID");
117         return { };
118     }
119
120     auto source = adoptRef(*new WindowDisplayCaptureSourceMac(actualID, checked_cf_cast<CFStringRef>(CFDictionaryGetValue(windowInfo.get(), kCGWindowName))));
121     if (constraints && source->applyConstraints(*constraints))
122         return { };
123
124     return CaptureSourceOrError(WTFMove(source));
125 }
126
127 WindowDisplayCaptureSourceMac::WindowDisplayCaptureSourceMac(uint32_t windowID, String&& title)
128     : DisplayCaptureSourceCocoa(WTFMove(title))
129     , m_windowID(windowID)
130 {
131 }
132
133 RetainPtr<CGImageRef> WindowDisplayCaptureSourceMac::windowImage()
134 {
135     auto image = adoptCF(CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, m_windowID, kCGWindowImageBoundsIgnoreFraming | kCGWindowImageShouldBeOpaque));
136     if (!image)
137         RELEASE_LOG(Media, "WindowDisplayCaptureSourceMac::windowImage: failed to capture window image");
138
139     return image;
140 }
141
142 RetainPtr<CVPixelBufferRef> WindowDisplayCaptureSourceMac::generateFrame()
143 {
144     auto image = windowImage();
145     if (!image)
146         return nullptr;
147
148     if (m_lastImage && m_lastPixelBuffer && CFEqual(m_lastImage.get(), image.get()))
149         return m_lastPixelBuffer.get();
150
151     m_lastImage = WTFMove(image);
152     return pixelBufferFromCGImage(m_lastImage.get());
153 }
154
155 RetainPtr<CVPixelBufferRef> WindowDisplayCaptureSourceMac::pixelBufferFromCGImage(CGImageRef image)
156 {
157     static CGColorSpaceRef sRGBColorSpace = sRGBColorSpaceRef();
158
159     auto imageSize = IntSize(CGImageGetWidth(image), CGImageGetHeight(image));
160     if (imageSize != intrinsicSize()) {
161         m_bufferPool = nullptr;
162         m_lastImage = nullptr;
163         m_lastPixelBuffer = nullptr;
164     }
165
166     if (!m_bufferPool) {
167         CVPixelBufferPoolRef bufferPool;
168         CFDictionaryRef sourcePixelBufferOptions = (__bridge CFDictionaryRef) @{
169         (__bridge NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32ARGB),
170         (__bridge NSString *)kCVPixelBufferWidthKey : @(imageSize.width()),
171         (__bridge NSString *)kCVPixelBufferHeightKey : @(imageSize.height()),
172 #if PLATFORM(IOS)
173         (__bridge NSString *)kCVPixelFormatOpenGLESCompatibility : @(YES),
174 #else
175         (__bridge NSString *)kCVPixelBufferOpenGLCompatibilityKey : @(YES),
176 #endif
177         (__bridge NSString *)kCVPixelBufferIOSurfacePropertiesKey : @{ /*empty dictionary*/ }
178     };
179
180         CFDictionaryRef pixelBufferPoolOptions = (__bridge CFDictionaryRef) @{
181             (__bridge NSString *)kCVPixelBufferPoolMinimumBufferCountKey : @(3)
182         };
183
184         CVReturn status = CVPixelBufferPoolCreate(kCFAllocatorDefault, pixelBufferPoolOptions, sourcePixelBufferOptions, &bufferPool);
185         if (status != kCVReturnSuccess)
186             return nullptr;
187
188         m_bufferPool = adoptCF(bufferPool);
189         setIntrinsicSize(imageSize);
190     }
191
192     CVPixelBufferRef pixelBuffer;
193     CVReturn status = CVPixelBufferPoolCreatePixelBuffer(nullptr, m_bufferPool.get(), &pixelBuffer);
194     if (status != kCVReturnSuccess)
195         return nullptr;
196
197     CVPixelBufferLockBaseAddress(pixelBuffer, 0);
198     void* data = CVPixelBufferGetBaseAddress(pixelBuffer);
199     auto context = adoptCF(CGBitmapContextCreate(data, imageSize.width(), imageSize.height(), 8, CVPixelBufferGetBytesPerRow(pixelBuffer), sRGBColorSpace, (CGBitmapInfo) kCGImageAlphaNoneSkipFirst));
200     CGContextDrawImage(context.get(), CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image);
201     CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
202
203     m_lastPixelBuffer = adoptCF(pixelBuffer);
204     return m_lastPixelBuffer;
205 }
206
207 std::optional<CaptureDevice> WindowDisplayCaptureSourceMac::windowCaptureDeviceWithPersistentID(const String& idString)
208 {
209     bool ok;
210     auto windowID = idString.toUIntStrict(&ok);
211     if (!ok) {
212         RELEASE_LOG(Media, "WindowDisplayCaptureSourceMac::windowCaptureDeviceWithPersistentID: window ID does not convert to 32-bit integer");
213         return std::nullopt;
214     }
215
216     String windowTitle;
217     if (!anyOfCGWindow([&windowTitle, windowID] (CFDictionaryRef, CGWindowID id, const String& title) {
218         if (windowID != id)
219             return false;
220
221         windowTitle = title;
222         return true;
223
224     })) {
225         RELEASE_LOG(Media, "WindowDisplayCaptureSourceMac::windowCaptureDeviceWithPersistentID: window ID is not valid");
226         return std::nullopt;
227     }
228
229     auto device = CaptureDevice(String::number(windowID), CaptureDevice::DeviceType::Window, windowTitle);
230     device.setEnabled(true);
231
232     return device;
233 }
234
235 void WindowDisplayCaptureSourceMac::windowCaptureDevices(Vector<CaptureDevice>& windows)
236 {
237     anyOfCGWindow([&] (CFDictionaryRef, int id, const String& title) mutable {
238         CaptureDevice device(String::number(id), CaptureDevice::DeviceType::Window, title);
239         device.setEnabled(true);
240         windows.append(WTFMove(device));
241         return false;
242     });
243 }
244
245 } // namespace WebCore
246
247 #endif // ENABLE(MEDIA_STREAM) && PLATFORM(MAC)