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
qsckwindowcapture.mm
Go to the documentation of this file.
1// Copyright (C) 2026 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#include <QtCore/private/qcore_mac_p.h>
8
9#include <QtFFmpegMediaPluginImpl/private/qffmpegmediacapturesession_p.h>
10#include <QtFFmpegMediaPluginImpl/private/qmacscreencapturekit_p.h>
11
12#include <QtMultimedia/private/qmultimedia_ranges_p.h>
13#include <QtMultimedia/private/qcapturablewindow_p.h>
14#include <QtMultimedia/private/qvideoframe_p.h>
15
16#define AVMediaType XAVMediaType
17extern "C" {
18#include <libavutil/hwcontext_videotoolbox.h>
19}
20#undef AVMediaType
21
22namespace ranges = QtMultimediaPrivate::ranges;
23using namespace Qt::Literals::StringLiterals;
24
25QT_BEGIN_NAMESPACE
26
27namespace QFFmpeg {
28
29namespace {
30
31[[nodiscard]] q23::expected<AVFScopedPointer<SCWindow>, QString> findScWindow(CGWindowID input)
32{
33 // Do a blocking enumeration of capturable items.
34 q23::expected<QMacScreenCaptureKit::CapturableItems, QString> enumerateResult =
35 QMacScreenCaptureKit::enumerateCapturableItems()
36 .get();
37
38 if (!enumerateResult)
39 return q23::unexpected(enumerateResult.error());
40
41 const std::vector<AVFScopedPointer<SCWindow>> &windows = enumerateResult->windows;
42 auto it = ranges::find_if(
43 windows,
44 [&](const AVFScopedPointer<SCWindow> &item) {
45 return item.data().windowID == input;
46 });
47
48 if (it == windows.end())
49 return q23::unexpected(u"Window not found"_s);
50
51 // AVFScopedPointer doesn't have shared-ptr semantics, force a reference increment.
52 return AVFScopedPointer<SCWindow>{ [it->data() retain] };
53}
54
55void setupQMacScreenCaptureKitConnections(
56 QSckWindowCapture &windowCapture,
57 const QMacScreenCaptureKit &macScreenCaptureKit)
58{
59 // Direct connection so application developers can respond to frame directly
60 // from background thread.
61 // Remaining frames are always flushed whenever we go inactive, as a result
62 // of QMacScreenCaptureKit doing so in the destructor. Because we flush,
63 // we trust the application developer to not block the background thread.
64 QObject::connect(
65 &macScreenCaptureKit,
66 &QMacScreenCaptureKit::newVideoFrameGenerated,
67 &windowCapture,
68 [&windowCapture](QMacScreenCaptureKit::StreamId, QVideoFrame videoFrame) {
69 emit windowCapture.newVideoFrame(videoFrame);
70 },
71 Qt::DirectConnection);
72
73 QObject::connect(
74 &macScreenCaptureKit,
75 &QMacScreenCaptureKit::newVideoFrameGenerated,
76 &windowCapture,
77 [&windowCapture](QMacScreenCaptureKit::StreamId streamId, QVideoFrame newFrame) {
78 windowCapture.onNewFrameFormatReceived(streamId, newFrame.surfaceFormat());
79 },
80 Qt::QueuedConnection);
81
82 QObject::connect(
83 &macScreenCaptureKit,
84 &QMacScreenCaptureKit::streamStoppedWithError,
85 &windowCapture,
86 &QSckWindowCapture::onStreamStoppedWithErrorEvent,
87 Qt::QueuedConnection);
88}
89
90} // Anonymous namespace
91
92QSckWindowCapture::QSckWindowCapture() : QPlatformSurfaceCapture(WindowSource{})
93{
94}
95
96void QSckWindowCapture::setCaptureSession(QPlatformMediaCaptureSession *sessionIn)
97{
98 if (!sessionIn) {
99 m_session = nullptr;
100 return;
101 }
102
103 auto session = qobject_cast<QFFmpegMediaCaptureSession *>(sessionIn);
104 Q_ASSERT(session);
105 m_session = session;
106}
107
109{
110 struct ErrorPair {
111 QPlatformSurfaceCapture::Error err;
112 QString msg;
113 };
114
115 using TryStartResult = q23::expected<std::unique_ptr<ActiveData>, ErrorPair>;
116
117 auto tryStartStream = [&]() -> TryStartResult {
118 QCapturableWindow capturableWindow = source<WindowSource>();
119 const QCapturableWindowPrivate *handle = QCapturableWindowPrivate::handle(capturableWindow);
120 if (!handle) {
121 return q23::unexpected{ ErrorPair{
122 QPlatformSurfaceCapture::Error::NotFound,
123 u"Selected window is null"_s } };
124 }
125 CGWindowID cgWindowId = static_cast<CGWindowID>(handle->id);
126
127 // Find the associated SCWindow we can use.
128 // This will trigger the system dialog for granting screen capture permissions.
129 q23::expected<AVFScopedPointer<SCWindow>, QString> scWindowResult = findScWindow(cgWindowId);
130 if (!scWindowResult) {
131 qCWarning(qLcMacScreenCapture)
132 << "Could not find associated SCWindow:"
133 << scWindowResult.error();
134 return q23::unexpected{ ErrorPair {
135 QPlatformSurfaceCapture::Error::NotFound,
136 u"Backend was unable to find selected QCapturableWindow"_s } };
137 }
138
139 QMacScreenCaptureKit::StreamId newStreamId = allocateStreamId();
140
141 AVFScopedPointer<SCWindow> &scWindow = *scWindowResult;
142
143 // Tell capturesession to setup initial connections ahead
144 // of time as to not drop initial frames.
145 if (m_session)
146 m_session->onSourceActivating(*this);
147
148 QMacScreenCaptureKit::CreateStreamInfo createInfo = {};
149 createInfo.streamId = newStreamId;
150 createInfo.connectionSetupFn = [&](QMacScreenCaptureKit &newObject) {
151 setupQMacScreenCaptureKitConnections(*this, newObject);
152 };
153 createInfo.streamSettings.frameRate = frameRate();
154 createInfo.streamSettings.overrideIgnoreCursor = ignoreCursor();
155 createInfo.streamSettings.overrideIgnoreDropShadow = ignoreDropShadow();
156
157 // Start and wait for the stream to start. Blocking operation.
158 using ResultType = q23::expected<std::unique_ptr<QMacScreenCaptureKit>, QString>;
159 std::future<ResultType> streamResultFuture = QMacScreenCaptureKit::createStreamFromWindow(
160 createInfo,
161 scWindow.data());
162
163 ResultType streamResult = streamResultFuture.get();
164 if (!streamResult) {
165 // Tell capturesession to restore previous state.
166 if (m_session)
167 m_session->onSourceActivationFailure(*this);
168
169 qCWarning(qLcMacScreenCapture)
170 << "Failed to start screen capture stream:"
171 << streamResult.error();
172 return q23::unexpected{ ErrorPair{
173 QPlatformSurfaceCapture::Error::CaptureFailed,
174 u"Failed to start stream due to unknown issue"_s } };
175 }
176
177 std::unique_ptr<QMacScreenCaptureKit> &macScreenCaptureKit = *streamResult;
178
179 auto newActiveData = std::make_unique<ActiveData>();
180 newActiveData->macScreenCaptureKit = std::move(macScreenCaptureKit);
181 newActiveData->scWindow = std::move(scWindow);
182 newActiveData->streamId = newStreamId;
183 return newActiveData;
184 };
185
186 if (active) {
187 Q_ASSERT(!m_activeData);
188
189 TryStartResult result = tryStartStream();
190 if (!result) {
191 const ErrorPair &error = result.error();
192 QPlatformSurfaceCapture::updateError(error.err, error.msg);
193 return false;
194 }
195
196 m_activeData = std::move(*result);
197 } else {
198 m_activeData.reset();
199 }
200
201 return true;
202}
203
205 QMacScreenCaptureKit::StreamId incomingStreamId,
206 QVideoFrameFormat const &format)
207{
208 Q_ASSERT(thread()->isCurrentThread());
209 if (activeStreamId() == incomingStreamId)
210 m_videoFrameFormat = format;
211}
212
214 QMacScreenCaptureKit::StreamId incomingStreamId,
215 const QString &err)
216{
217 Q_ASSERT(thread()->isCurrentThread());
218
219 qCDebug(qLcMacScreenCapture)
220 << "Stream with ID"
221 << static_cast<int64_t>(incomingStreamId)
222 << "stopped with error:"
223 << err;
224
225 if (activeStreamId() != incomingStreamId)
226 return;
227
228 // Possible improvement may be to propagate signal up to QVideoSource
229 // that we are no longer active.
230 m_activeData.reset();
231 m_videoFrameFormat.reset();
232
233 QPlatformSurfaceCapture::updateError(
234 Error::CaptureFailed,
235 u"The capture stream was closed by the system"_s);
236}
237
239{
240 Q_ASSERT(thread()->isCurrentThread());
241
242 if (m_videoFrameFormat)
243 return *m_videoFrameFormat;
244 return {};
245}
246
248{
249 Q_ASSERT(thread()->isCurrentThread());
250 return AV_PIX_FMT_VIDEOTOOLBOX;
251}
252
257
258} // namespace QFFmpeg
259
260QT_END_NAMESPACE
261
262#include "moc_qsckwindowcapture_p.cpp"
void onStreamStoppedWithErrorEvent(QMacScreenCaptureKit::StreamId streamId, const QString &)
void setCaptureSession(QPlatformMediaCaptureSession *) override
std::optional< int > ffmpegHWPixelFormat() const override
QVideoFrameFormat frameFormat() const override
void onNewFrameFormatReceived(QMacScreenCaptureKit::StreamId streamId, const QVideoFrameFormat &)
bool setActiveInternal(bool active) override
QT_MANGLE_NAMESPACE(QMacScreenCaptureStreamDelegate) QMacScreenCaptureStreamDelegate
std::unique_ptr< QPlatformSurfaceCapture > makeQSckWindowCapture()