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 auto setupConnections = [&](QMacScreenCaptureKit &newObject) {
149 setupQMacScreenCaptureKitConnections(*this, newObject);
150 };
151
152 // Start and wait for the stream to start. Blocking operation.
153 using ResultType = q23::expected<std::unique_ptr<QMacScreenCaptureKit>, QString>;
154 std::future<ResultType> streamResultFuture = QMacScreenCaptureKit::createStreamFromWindow(
155 newStreamId,
156 scWindow.data(),
157 frameRate(),
158 setupConnections);
159
160 ResultType streamResult = streamResultFuture.get();
161 if (!streamResult) {
162 // Tell capturesession to restore previous state.
163 if (m_session)
164 m_session->onSourceActivationFailure(*this);
165
166 qCWarning(qLcMacScreenCapture)
167 << "Failed to start screen capture stream:"
168 << streamResult.error();
169 return q23::unexpected{ ErrorPair{
170 QPlatformSurfaceCapture::Error::CaptureFailed,
171 u"Failed to start stream due to unknown issue"_s } };
172 }
173
174 std::unique_ptr<QMacScreenCaptureKit> &macScreenCaptureKit = *streamResult;
175
176 auto newActiveData = std::make_unique<ActiveData>();
177 newActiveData->macScreenCaptureKit = std::move(macScreenCaptureKit);
178 newActiveData->scWindow = std::move(scWindow);
179 newActiveData->streamId = newStreamId;
180 return newActiveData;
181 };
182
183 if (active) {
184 Q_ASSERT(!m_activeData);
185
186 TryStartResult result = tryStartStream();
187 if (!result) {
188 const ErrorPair &error = result.error();
189 QPlatformSurfaceCapture::updateError(error.err, error.msg);
190 return false;
191 }
192
193 m_activeData = std::move(*result);
194 } else {
195 m_activeData.reset();
196 }
197
198 return true;
199}
200
202 QMacScreenCaptureKit::StreamId incomingStreamId,
203 QVideoFrameFormat const &format)
204{
205 Q_ASSERT(thread()->isCurrentThread());
206 if (activeStreamId() == incomingStreamId)
207 m_videoFrameFormat = format;
208}
209
211 QMacScreenCaptureKit::StreamId incomingStreamId,
212 const QString &err)
213{
214 Q_ASSERT(thread()->isCurrentThread());
215
216 qCDebug(qLcMacScreenCapture)
217 << "Stream with ID"
218 << static_cast<int64_t>(incomingStreamId)
219 << "stopped with error:"
220 << err;
221
222 if (activeStreamId() != incomingStreamId)
223 return;
224
225 // Possible improvement may be to propagate signal up to QVideoSource
226 // that we are no longer active.
227 m_activeData.reset();
228 m_videoFrameFormat.reset();
229
230 QPlatformSurfaceCapture::updateError(
231 Error::CaptureFailed,
232 u"The capture stream was closed by the system"_s);
233}
234
236{
237 Q_ASSERT(thread()->isCurrentThread());
238
239 if (m_videoFrameFormat)
240 return *m_videoFrameFormat;
241 return {};
242}
243
245{
246 Q_ASSERT(thread()->isCurrentThread());
247 return AV_PIX_FMT_VIDEOTOOLBOX;
248}
249
254
255} // namespace QFFmpeg
256
257QT_END_NAMESPACE
258
259#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()