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
qsckscreencapture.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
5
6#include <QtCore/qthread.h>
7
8#include <QtFFmpegMediaPluginImpl/private/qffmpegmediacapturesession_p.h>
9#include <QtFFmpegMediaPluginImpl/private/qmacscreencapturekit_p.h>
10
11#include <QtMultimedia/private/qmultimedia_ranges_p.h>
12
13#include <QtGui/qguiapplication.h>
14#include <QtGui/qscreen.h>
15
16#define AVMediaType XAVMediaType
17extern "C" {
18#include <libavutil/hwcontext_videotoolbox.h>
19}
20#undef AVMediaType
21
22#import <AppKit/NSScreen.h>
23
24#include <algorithm>
25
26namespace ranges = QtMultimediaPrivate::ranges;
27using namespace Qt::Literals::StringLiterals;
28
29QT_BEGIN_NAMESPACE
30
31namespace QFFmpeg {
32
34{
35#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(260000)
36 if (@available(macOS 26.0, *))
37 return screen.CGDirectDisplayID;
38#endif
39
40 NSNumber *screenNumber = screen.deviceDescription[@"NSScreenNumber"];
41 return static_cast<CGDirectDisplayID>(screenNumber.unsignedIntValue);
42}
43
44[[nodiscard]] static q23::expected<AVFScopedPointer<SCDisplay>, QString> findScDisplay(CGDirectDisplayID input)
45{
46 // Do a blocking enumeration of capturable items.
47 q23::expected<QMacScreenCaptureKit::CapturableItems, QString> enumerateResult =
48 QMacScreenCaptureKit::enumerateCapturableItems()
49 .get();
50
51 if (!enumerateResult)
52 return q23::unexpected{ std::move(enumerateResult.error()) };
53
54 const std::vector<AVFScopedPointer<SCDisplay>> &displays = enumerateResult->displays;
55 auto it = ranges::find_if(
56 displays,
57 [&](const AVFScopedPointer<SCDisplay> &item) {
58 return item.data().displayID == input;
59 });
60 if (it == displays.end())
61 return q23::unexpected(u"Display not found"_s);
62
63 // AVFScopedPointer doesn't have shared-ptr semantics, force a reference increment.
64 return AVFScopedPointer<SCDisplay>{ [it->data() retain] };
65}
66
67// Finds the SCDisplay to capture for the given, non-null QScreen.
68[[nodiscard]] static auto findScDisplayForScreen(QScreen &screen)
70{
71 using Error = QPlatformSurfaceCapture::Error;
72 using ErrorDetails = QPlatformSurfaceCapture::ErrorDetails;
73
74 auto *cocoaScreen = screen.nativeInterface<QNativeInterface::QCocoaScreen>();
75 if (!cocoaScreen) {
76 qCWarning(qLcMacScreenCapture)
77 << "Failed to grab QNativeInterface::QCocoaScreen from QScreen";
78 return q23::unexpected{ ErrorDetails{
79 Error::CaptureFailed,
80 u"Screen capture could not be started."_s } };
81 }
82
83 CGDirectDisplayID cgDisplayId = displayIDForScreen(cocoaScreen->nativeScreen());
84
85 q23::expected<AVFScopedPointer<SCDisplay>, QString> scDisplayResult = findScDisplay(cgDisplayId);
86 if (!scDisplayResult) {
87 qCWarning(qLcMacScreenCapture)
88 << "Could not find associated SCDisplay:"
89 << scDisplayResult.error();
90 return q23::unexpected{ ErrorDetails{
91 Error::NotFound,
92 u"The selected screen could not be found."_s } };
93 }
94
95 return std::move(*scDisplayResult);
96}
97
99 QSckScreenCapture &screenCapture,
100 const QMacScreenCaptureKit &macScreenCaptureKit)
101{
102 // Direct connection so application developers can respond to frame directly
103 // from background thread.
104 // Remaining frames are always flushed whenever we go inactive, as a result
105 // of QMacScreenCaptureKit doing so in the destructor. Because we flush,
106 // we trust the application developer to not block the background thread.
107 QObject::connect(
108 &macScreenCaptureKit,
109 &QMacScreenCaptureKit::newVideoFrameGenerated,
110 &screenCapture,
111 [&screenCapture](QMacScreenCaptureKit::StreamId, QVideoFrame videoFrame) {
112 Q_EMIT screenCapture.newVideoFrame(videoFrame);
113 },
114 Qt::DirectConnection);
115
116 QObject::connect(
117 &macScreenCaptureKit,
118 &QMacScreenCaptureKit::newVideoFrameGenerated,
119 &screenCapture,
120 [&screenCapture](QMacScreenCaptureKit::StreamId streamId, QVideoFrame newFrame) {
121 screenCapture.onNewFrameFormatReceived(streamId, newFrame.surfaceFormat());
122 },
123 Qt::QueuedConnection);
124
125 QObject::connect(
126 &macScreenCaptureKit,
127 &QMacScreenCaptureKit::streamStoppedWithError,
128 &screenCapture,
129 &QSckScreenCapture::onStreamStoppedWithErrorEvent,
130 Qt::QueuedConnection);
131}
132
133
134QSckScreenCapture::QSckScreenCapture() : QPlatformSurfaceCapture(ScreenSource{})
135{
136}
137
138void QSckScreenCapture::setCaptureSession(QPlatformMediaCaptureSession *sessionIn)
139{
140 if (!sessionIn) {
141 m_session = nullptr;
142 return;
143 }
144
145 auto *session = qobject_cast<QFFmpegMediaCaptureSession *>(sessionIn);
146 Q_ASSERT(session);
147 m_session = session;
148}
149
151{
152 using TryStartResult = q23::expected<std::unique_ptr<ActiveData>, ErrorDetails>;
153
154 auto tryStartStream = [&]() -> TryStartResult {
155 ScreenSource screen = QPlatformSurfaceCapture::source<ScreenSource>();
156 if (!QPlatformSurfaceCapture::checkScreenWithError(screen)) {
157 return q23::unexpected{ ErrorDetails{
158 QPlatformSurfaceCapture::Error::NotFound,
159 u"The selected screen could not be found. It may have been disconnected."_s } };
160 }
161
162 q23::expected<AVFScopedPointer<SCDisplay>, ErrorDetails> scDisplayResult =
163 findScDisplayForScreen(*screen);
164 if (!scDisplayResult)
165 return q23::unexpected{ std::move(scDisplayResult.error()) };
166
167 QMacScreenCaptureKit::StreamId newStreamId = allocateStreamId();
168
169 AVFScopedPointer<SCDisplay> &scDisplay = *scDisplayResult;
170
171 if (m_session)
172 m_session->onSourceActivating(*this);
173
174 // Start and wait for the stream to start. Blocking operation.
175 QMacScreenCaptureKit::CreateStreamInfo createInfo = {};
176 createInfo.streamId = newStreamId;
177 createInfo.connectionSetupFn = [&](QMacScreenCaptureKit &newObject) {
178 setupQMacScreenCaptureKitConnections(*this, newObject);
179 };
180 createInfo.streamSettings.frameRate = frameRate();
181 createInfo.streamSettings.overrideIgnoreCursor = ignoreCursor();
182 using ResultType = q23::expected<std::unique_ptr<QMacScreenCaptureKit>, QString>;
183 std::future<ResultType> streamResultFuture = QMacScreenCaptureKit::createStreamFromDisplay(
184 createInfo,
185 scDisplay.data());
186 ResultType streamResult = streamResultFuture.get();
187 if (!streamResult) {
188 if (m_session)
189 m_session->onSourceActivationFailure(*this);
190
191 qCWarning(qLcMacScreenCapture)
192 << "Failed to start screen capture stream: "
193 << streamResult.error();
194 return q23::unexpected{ ErrorDetails{
195 QPlatformSurfaceCapture::Error::CaptureFailed,
196 u"Screen capture could not be started."_s } };
197 }
198
199 std::unique_ptr<QMacScreenCaptureKit> &macScreenCaptureKit = *streamResult;
200
201 auto newActiveData = std::make_unique<ActiveData>();
202 newActiveData->macScreenCaptureKit = std::move(macScreenCaptureKit);
203 newActiveData->scDisplay = std::move(scDisplay);
204 newActiveData->streamId = newStreamId;
205 return newActiveData;
206 };
207
208 if (active) {
209 Q_ASSERT(!m_activeData);
210
211 TryStartResult result = tryStartStream();
212 if (!result) {
213 const ErrorDetails &error = result.error();
214 QPlatformSurfaceCapture::updateError(error.error, error.description);
215 return false;
216 }
217
218 m_activeData = std::move(*result);
219 } else {
220 m_activeData.reset();
221 }
222
223 return true;
224}
225
227QSckScreenCapture::switchActiveSource(const Source &newSource)
228{
229 Q_ASSERT(m_activeData);
230
231 // A null screen means the primary screen.
232 QScreen *screen = std::get<ScreenSource>(newSource);
233 if (!screen)
234 screen = QGuiApplication::primaryScreen();
235 if (!screen)
236 return SourceSwitchResult{ q23::unexpected{ ErrorDetails{
237 Error::NotFound,
238 u"The selected screen could not be found. It may have been disconnected."_s } } };
239
240 q23::expected<AVFScopedPointer<SCDisplay>, ErrorDetails> scDisplayResult =
241 findScDisplayForScreen(*screen);
242 if (!scDisplayResult)
243 return SourceSwitchResult{ q23::unexpected{ std::move(scDisplayResult.error()) } };
244
245 std::future<q23::expected<void, QString>> switchFuture =
246 m_activeData->macScreenCaptureKit->switchToDisplay(scDisplayResult->data());
247
248 const q23::expected<void, QString> result = switchFuture.get();
249 if (!result) {
250 qCWarning(qLcMacScreenCapture)
251 << "Failed to switch screen capture stream to new screen:"
252 << result.error();
253 return SourceSwitchResult{ q23::unexpected{ ErrorDetails{
254 Error::CaptureFailed,
255 u"Could not switch capture to the selected screen."_s } } };
256 }
257
258 m_activeData->scDisplay = std::move(*scDisplayResult);
259 return SourceSwitchResult{};
260}
261
263 QMacScreenCaptureKit::StreamId incomingStreamId,
264 QVideoFrameFormat const &format)
265{
266 Q_ASSERT(thread()->isCurrentThread());
267 if (activeStreamId() == incomingStreamId)
268 m_videoFrameFormat = format;
269}
270
272 QMacScreenCaptureKit::StreamId incomingStreamId,
273 const QString &err)
274{
275 Q_ASSERT(thread()->isCurrentThread());
276
277 qCDebug(qLcMacScreenCapture)
278 << "Stream with ID"
279 << static_cast<int64_t>(incomingStreamId)
280 << "stopped with error:"
281 << err;
282
283 if (activeStreamId() != incomingStreamId)
284 return;
285
286 // Possible improvement may be to propagate signal up to QVideoSource
287 // that we are no longer active.
288 m_activeData.reset();
289 m_videoFrameFormat.reset();
290
291 QPlatformSurfaceCapture::updateError(
292 Error::CaptureFailed,
293 u"The capture stream was closed by the system"_s);
294}
295
297{
298 Q_ASSERT(thread()->isCurrentThread());
299
300 if (m_videoFrameFormat)
301 return *m_videoFrameFormat;
302 return {};
303}
304
306{
307 Q_ASSERT(thread()->isCurrentThread());
308 return AV_PIX_FMT_VIDEOTOOLBOX;
309}
310
312{
313 return std::make_unique<QSckScreenCapture>();
314}
315
316} // namespace QFFmpeg
317
318QT_END_NAMESPACE
319
320#include "moc_qsckscreencapture_p.cpp"
std::optional< int > ffmpegHWPixelFormat() const override
void setCaptureSession(QPlatformMediaCaptureSession *) override
bool setActiveInternal(bool active) override
void onNewFrameFormatReceived(QMacScreenCaptureKit::StreamId streamId, const QVideoFrameFormat &)
std::optional< SourceSwitchResult > switchActiveSource(const Source &newSource) override
QVideoFrameFormat frameFormat() const override
void onStreamStoppedWithErrorEvent(QMacScreenCaptureKit::StreamId streamId, const QString &)
static q23::expected< AVFScopedPointer< SCDisplay >, QString > findScDisplay(CGDirectDisplayID input)
static void setupQMacScreenCaptureKitConnections(QSckScreenCapture &screenCapture, const QMacScreenCaptureKit &macScreenCaptureKit)
static CGDirectDisplayID displayIDForScreen(NSScreen *screen)
static auto findScDisplayForScreen(QScreen &screen) -> q23::expected< AVFScopedPointer< SCDisplay >, QPlatformSurfaceCapture::ErrorDetails >
std::unique_ptr< QPlatformSurfaceCapture > makeQSckScreenCapture()