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