Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcgwindowcapture.mm
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <QtFFmpegMediaPluginImpl/private/qcgwindowcapture_p.h>
5
6#include <QtCore/qmutex.h>
7#include <QtCore/qwaitcondition.h>
8
9#include <QtFFmpegMediaPluginImpl/private/qffmpegsurfacecapturegrabber_p.h>
10
11#include <QtGui/qscreen.h>
12#include <QtGui/qguiapplication.h>
13
14#include <QtMultimedia/qabstractvideobuffer.h>
15#include <QtMultimedia/private/qcapturablewindow_p.h>
16#include <QtMultimedia/private/qvideoframe_p.h>
17
18#include <ApplicationServices/ApplicationServices.h>
19#include <IOKit/graphics/IOGraphicsLib.h>
20
21#import <AppKit/NSScreen.h>
22#import <AppKit/NSApplication.h>
23#import <AppKit/NSWindow.h>
24
25namespace {
26
27std::optional<qreal> frameRateForWindow(CGWindowID /*wid*/)
28{
29 // TODO: detect the frame rate
30 // if (window && window.screen) {
31 // CGDirectDisplayID displayID = [window.screen.deviceDescription[@"NSScreenNumber"]
32 // unsignedIntValue]; const auto displayRefreshRate =
33 // CGDisplayModeGetRefreshRate(CGDisplayCopyDisplayMode(displayID)); if (displayRefreshRate
34 // > 0 && displayRefreshRate < frameRate) frameRate = displayRefreshRate;
35 // }
36
37 return {};
38}
39
40}
41
42QT_BEGIN_NAMESPACE
43
44class QCGImageVideoBuffer : public QAbstractVideoBuffer
45{
46public:
47 QCGImageVideoBuffer(CGImageRef image)
48 {
49 auto provider = CGImageGetDataProvider(image);
50 m_data = CGDataProviderCopyData(provider);
51 m_bytesPerLine = CGImageGetBytesPerRow(image);
52 }
53
54 ~QCGImageVideoBuffer() override { CFRelease(m_data); }
55
56 MapData map(QVideoFrame::MapMode /*mode*/) override
57 {
58 MapData mapData;
59
60 mapData.planeCount = 1;
61 mapData.bytesPerLine[0] = static_cast<int>(m_bytesPerLine);
62 mapData.data[0] = (uchar *)CFDataGetBytePtr(m_data);
63 mapData.dataSize[0] = static_cast<int>(CFDataGetLength(m_data));
64
65 return mapData;
66 }
67
68 QVideoFrameFormat format() const override { return {}; }
69
70private:
71 CFDataRef m_data;
72 size_t m_bytesPerLine = 0;
73};
74
75class QCGWindowCapture::Grabber : public QFFmpegSurfaceCaptureGrabber
76{
77public:
78 Grabber(QCGWindowCapture &capture, CGWindowID wid) : m_capture(capture), m_wid(wid)
79 {
80 addFrameCallback(*this, &Grabber::onNewFrame);
81 connect(this, &Grabber::errorUpdated, &capture, &QCGWindowCapture::updateError);
82
83 if (auto screen = QGuiApplication::primaryScreen())
84 setFrameRate(screen->refreshRate());
85
86 start();
87 }
88
89 ~Grabber() override { stop(); }
90
91 QVideoFrameFormat frameFormat() const
92 {
93 QMutexLocker<QMutex> locker(&m_formatMutex);
94 while (!m_format)
95 m_waitForFormat.wait(&m_formatMutex);
96 return *m_format;
97 }
98
99protected:
100 QVideoFrame grabFrame() override
101 {
102 if (auto rate = frameRateForWindow(m_wid))
103 setFrameRate(*rate);
104
105 auto imageRef = CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow,
106 m_wid, kCGWindowImageBoundsIgnoreFraming);
107 if (!imageRef) {
108 updateError(QPlatformSurfaceCapture::CaptureFailed,
109 QLatin1String("Cannot create image by window"));
110 return {};
111 }
112
113 auto imageDeleter = qScopeGuard([imageRef]() { CGImageRelease(imageRef); });
114
115 if (CGImageGetBitsPerPixel(imageRef) != 32
116 || CGImageGetPixelFormatInfo(imageRef) != kCGImagePixelFormatPacked
117 || CGImageGetByteOrderInfo(imageRef) != kCGImageByteOrder32Little) {
118 qWarning() << "Unexpected image format. PixelFormatInfo:"
119 << CGImageGetPixelFormatInfo(imageRef)
120 << "BitsPerPixel:" << CGImageGetBitsPerPixel(imageRef) << "AlphaInfo"
121 << CGImageGetAlphaInfo(imageRef)
122 << "ByteOrderInfo:" << CGImageGetByteOrderInfo(imageRef);
123
124 updateError(QPlatformSurfaceCapture::CapturingNotSupported,
125 QLatin1String("Not supported pixel format"));
126 return {};
127 }
128
129 QVideoFrameFormat format(QSize(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)),
130 QVideoFrameFormat::Format_BGRA8888);
131 format.setStreamFrameRate(frameRate());
132
133 return QVideoFramePrivate::createFrame(std::make_unique<QCGImageVideoBuffer>(imageRef),
134 std::move(format));
135 }
136
137 void onNewFrame(QVideoFrame frame)
138 {
139 // Since writing of the format is supposed to be only from one thread,
140 // the read-only comparison without a mutex is thread-safe
141 if (!m_format || m_format != frame.surfaceFormat()) {
142 QMutexLocker<QMutex> locker(&m_formatMutex);
143
144 m_format = frame.surfaceFormat();
145
146 locker.unlock();
147
148 m_waitForFormat.notify_one();
149 }
150
151 emit m_capture.newVideoFrame(frame);
152 }
153
154private:
155 QCGWindowCapture &m_capture;
156 std::optional<QVideoFrameFormat> m_format;
157 mutable QMutex m_formatMutex;
158 mutable QWaitCondition m_waitForFormat;
159 CGWindowID m_wid;
160};
161
162QCGWindowCapture::QCGWindowCapture() : QPlatformSurfaceCapture(WindowSource{})
163{
164 CGRequestScreenCaptureAccess();
165}
166
167QCGWindowCapture::~QCGWindowCapture() = default;
168
169bool QCGWindowCapture::setActiveInternal(bool active)
170{
171 if (active) {
172 if (!CGPreflightScreenCaptureAccess()) {
173 updateError(QPlatformSurfaceCapture::CaptureFailed,
174 QLatin1String("Permissions denied"));
175 return false;
176 }
177
178 auto window = source<WindowSource>();
179
180 auto handle = QCapturableWindowPrivate::handle(window);
181 if (!handle || !handle->id)
182 updateError(QPlatformSurfaceCapture::NotFound, QLatin1String("Invalid window"));
183 else
184 m_grabber = std::make_unique<Grabber>(*this, handle->id);
185
186 } else {
187 m_grabber.reset();
188 }
189
190 return active == static_cast<bool>(m_grabber);
191}
192
193QVideoFrameFormat QCGWindowCapture::frameFormat() const
194{
195 return m_grabber ? m_grabber->frameFormat() : QVideoFrameFormat();
196}
197
198QT_END_NAMESPACE
199
200#include "moc_qcgwindowcapture_p.cpp"