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
qmacscreencapturekit.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/qmutex.h>
7
8#include <QtFFmpegMediaPluginImpl/private/qcvimagevideobuffer_p.h>
9#include <QtFFmpegMediaPluginImpl/private/qffmpegdarwinhwframehelpers_p.h>
10#define AVMediaType XAVMediaType
11#include <QtFFmpegMediaPluginImpl/private/qffmpeghwaccel_p.h>
12#include <QtFFmpegMediaPluginImpl/private/qffmpegvideobuffer_p.h>
13extern "C" {
14#include <libavutil/hwcontext_videotoolbox.h>
15}
16#undef AVMediaType
17
18#include <QtMultimedia/private/qavfcamerautility_p.h>
19#include <QtMultimedia/private/qavfhelpers_p.h>
20#include <QtMultimedia/private/qvideoframe_p.h>
21
22#include <CoreMedia/CMSync.h>
23#include <CoreMedia/CMTime.h>
24#include <ScreenCaptureKit/ScreenCaptureKit.h>
25
26#include <chrono>
27
28using namespace Qt::Literals::StringLiterals;
29using QMacScreenCaptureKit = QT_PREPEND_NAMESPACE(QFFmpeg::QMacScreenCaptureKit);
30
32 QT_PREPEND_NAMESPACE(QFFmpeg::qLcMacScreenCapture),
33 "qt.multimedia.screencapture.macscreencapturekit");
34
35namespace {
36
37struct QMacScreenCaptureStreamDelegateHelper : public QObject {
38 Q_OBJECT
39Q_SIGNALS:
40 void didStopWithError(QMacScreenCaptureKit::StreamId streamId, QString);
41};
42
43} // Anonymous namespace
44
45// Events are invoked on system background thread that we don't control.
46@implementation QT_MANGLE_NAMESPACE(QMacScreenCaptureStreamDelegate) {
47@public
48 QMacScreenCaptureKit::StreamId m_streamId;
49 QMacScreenCaptureStreamDelegateHelper m_helper;
50}
51
52- (void)stream:(SCStream *)stream didStopWithError:(NSError *)error
53{
54 Q_EMIT m_helper.didStopWithError(
55 m_streamId,
56 QString::fromNSString(error.localizedDescription));
57}
58
59@end
60
61QT_BEGIN_NAMESPACE
62
63namespace QFFmpeg {
64
65static void handleFrameOutput(
66 QMacScreenCaptureStreamOutput &scStreamOutput,
67 CMSampleBufferRef sampleBufferRef);
68} // namespace QFFmpeg
69
70QT_END_NAMESPACE
71
72// Invoked on background dispatch-queue.
73@implementation QT_MANGLE_NAMESPACE(QMacScreenCaptureStreamOutput) {
74@public
75 // Assigned at construction. We assume it is safe to never reset it, because
76 // we flush the background queue anytime we stop a stream.
77 QT_PREPEND_NAMESPACE(QFFmpeg::QMacScreenCaptureKit) *m_qScreenCaptureKit;
78
79 // Used to track when the underlying window size changed, in pixel-coordinates.
80 QSize m_previousFrameContentRect;
81
82 // Presentation timestamp of the first frame of the stream. Used to report
83 // frame times relative to the start of the stream.
84 std::optional<std::chrono::microseconds> m_baseTime;
85 std::unique_ptr<QT_PREPEND_NAMESPACE(QFFmpeg::HWAccel)> m_hwAccel;
86}
87
88- (void) stream:(SCStream *) stream
89didOutputSampleBuffer:(CMSampleBufferRef) sampleBufferRef
90 ofType:(SCStreamOutputType) type
91{
92 QT_USE_NAMESPACE
93 using namespace QFFmpeg;
94
95 // SCStreamOutputTypeScreen implies we are receiving video frames
96 // rather than audio samples. It doesn't exclude windows.
97 // Our stream is hardcoded to never report audio samples.
98 Q_ASSERT(type == SCStreamOutputTypeScreen);
99
100 handleFrameOutput(*self, sampleBufferRef);
101}
102
103@end
104
105QT_BEGIN_NAMESPACE
106
107namespace QFFmpeg {
108
109// Useful metadata grabbed from a CMSampleBufferRef.
111{
113
114 // The size of the captured content, in pixel-coordinates.
115 // Can be used to detect e.g. window size changes.
117};
118
119// Reads the frame status and content rect for the given CMSampleBufferRef.
120// The content rect usually means the window size at the time a frame was
121// outputted. The resolution is in pixel coordinates.
122// Error message is not user-facing.
123[[nodiscard]] static q23::expected<FrameInfo, QString> readFrameInfo(CMSampleBufferRef sampleBuffer)
124{
125 CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, false);
126 if (!attachments || CFArrayGetCount(attachments) == 0)
127 return q23::unexpected{ u"CMSampleBuffer has no attachments array"_s };
128
129 CFDictionaryRef attachment = (CFDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
130 NSDictionary *dict = (__bridge NSDictionary *)attachment;
131
132 NSNumber *statusNumber = dict[(id)SCStreamFrameInfoStatus];
133 if (!statusNumber)
134 return q23::unexpected{ u"CMSampleBuffer has no frame status"_s };
135
136 FrameInfo info;
137 info.status = static_cast<SCFrameStatus>(statusNumber.intValue);
138
139 CGRect contentRect = CGRectZero;
140 NSDictionary *frameInfo = dict[(id)SCStreamFrameInfoContentRect];
141 if (frameInfo) {
142 contentRect = CGRectMakeWithDictionaryRepresentation(
143 (__bridge CFDictionaryRef)frameInfo, &contentRect)
144 ? contentRect
145 : CGRectZero;
146 }
147
148 NSNumber *scaleNumber = dict[(id)SCStreamFrameInfoScaleFactor];
149 CGFloat scaleFactor = scaleNumber ? scaleNumber.doubleValue : 1.0;
150
151 NSNumber *contentScaleNumber = dict[(id)SCStreamFrameInfoContentScale];
152 CGFloat contentScale = contentScaleNumber ? contentScaleNumber.doubleValue : 1.0;
153 if (contentScale <= 0.0)
154 contentScale = 1.0;
155
156 info.contentRect = QSize{
157 static_cast<int>(std::lround(contentRect.size.width * scaleFactor / contentScale)),
158 static_cast<int>(std::lround(contentRect.size.height * scaleFactor / contentScale)), };
159
160 return info;
161}
162
163// Invoked on background dispatch-queue.
164// Error message is not user-facing.
166 QMacScreenCaptureStreamOutput &scStreamOutput,
167 CMSampleBufferRef sampleBufferRef)
168{
169 CVImageBufferRef imageBufferRef = CMSampleBufferGetImageBuffer(sampleBufferRef);
170 if (!imageBufferRef)
171 return q23::unexpected(u"Cannot get CVImageBufferRef from CMSampleBufferRef"_s);
172 if (CFGetTypeID(imageBufferRef) != CVPixelBufferGetTypeID())
173 return q23::unexpected(u"Grabbed CVImageBufferRef that is not of type CVPixelBuffer"_s);
174
175 auto pixelBuffer = QAVFHelpers::QSharedCVPixelBuffer(
176 imageBufferRef,
177 QAVFHelpers::QSharedCVPixelBuffer::RefMode::NeedsRef);
178
179 // ScreenCaptureKit hands us buffers from its internal pool (see queueDepth),
180 // so copy into a free-standing CVPixelBuffer to decouple the frame's
181 // lifetime from the stream's pool.
182 q23::expected<QAVFHelpers::QSharedCVPixelBuffer, QString> copyResult = deepCopyCvPixelBuffer(
183 pixelBuffer.get());
184 if (!copyResult)
185 return q23::unexpected(u"Failed to copy incoming pixel buffer: "_s + copyResult.error());
186 pixelBuffer = std::move(*copyResult);
187
188 // If the new incoming frames have a different size, update the FFmpeg frames context.
189 QSize incomingFrameSize = QAVFHelpers::CVPixelBufferSize(pixelBuffer.get());
190 Q_ASSERT(!incomingFrameSize.isEmpty());
191 CvPixelFormat incomingCvPixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer.get());
192 Q_ASSERT(scStreamOutput.m_hwAccel);
193 scStreamOutput.m_hwAccel->updateFramesContext(
194 av_map_videotoolbox_format_to_pixfmt(incomingCvPixelFormat),
195 incomingFrameSize);
196
197 // ScreenCaptureKit timestamps frames on the host clock. QVideoFrame times are
198 // relative to the start of the stream, so use the first frame as our zero point.
199 const std::chrono::microseconds frameTime =
200 QAVFHelpers::CMTimeToMicroseconds(CMSampleBufferGetPresentationTimeStamp(sampleBufferRef));
201 if (!scStreamOutput.m_baseTime)
202 scStreamOutput.m_baseTime = frameTime;
203 const std::chrono::microseconds presentationTime = frameTime - *scStreamOutput.m_baseTime;
204
205 QVideoFrameFormat format = QAVFHelpers::videoFormatForImageBuffer(pixelBuffer.get());
206 if (!format.isValid())
207 return q23::unexpected(u"Cannot get get video format for image buffer"_s);
208
209 format.setColorSpace(QMacScreenCaptureKit::colorSpace);
210 format.setColorRange(QMacScreenCaptureKit::colorRange);
211 format.setColorTransfer(QMacScreenCaptureKit::colorTransfer);
212
213 Q_ASSERT(scStreamOutput.m_hwAccel);
214 QVideoFrame frame;
215 q23::expected<QVideoFrame, QString> frameResult = QFFmpeg::qVideoFrameFromCvPixelBuffer(
216 *scStreamOutput.m_hwAccel,
217 presentationTime,
218 pixelBuffer,
219 format);
220 if (!frameResult)
221 qCWarning(qLcMacScreenCapture) << frameResult.error();
222 else
223 frame = *frameResult;
224
225 if (!frame.isValid()) {
226 frame = QVideoFramePrivate::createFrame(
227 std::make_unique<QFFmpeg::CVImageVideoBuffer>(std::move(pixelBuffer)),
228 std::move(format));
229 }
230
231 frame.setStartTime(presentationTime.count());
232
233 frame.setEndTime(-1);
234
235 return frame;
236}
237
238[[nodiscard]] static QVideoFrame createEndOfStreamFrame(std::chrono::microseconds baseTime)
239{
240 QVideoFrame frame{ QVideoFrameFormat{} };
241 Q_ASSERT(!frame.isValid());
242
243 // ScreenCaptureKit timestamps frames on the host clock, so we can use that clock
244 // to express the end of the stream relative to the start of the stream.
245 const std::chrono::microseconds hostTime =
246 QAVFHelpers::CMTimeToMicroseconds(CMClockGetTime(CMClockGetHostTimeClock()));
247 frame.setStartTime((hostTime - baseTime).count());
248
249 return frame;
250}
251
252// Main frame handler.
253// Invoked on background dispatch-queue.
255 QFFmpeg::QMacScreenCaptureStreamOutput &streamOutput,
256 CMSampleBufferRef sampleBufferRef)
257{
258 Q_ASSERT(streamOutput.m_qScreenCaptureKit);
259
260 q23::expected<FrameInfo, QString> frameInfoResult = readFrameInfo(sampleBufferRef);
261 if (!frameInfoResult) {
262 qCDebug(qLcMacScreenCapture)
263 << "Error while reading frame info of CMSampleBufferRef:"
264 << frameInfoResult.error();
265 return;
266 }
267
268 const FrameInfo &frameInfo = *frameInfoResult;
269
270 // ScreenCaptureKit only hands us a new hardware buffer when the frame status
271 // is complete. For other statuses (e.g. idle when the captured content is
272 if (frameInfo.status != SCFrameStatusComplete)
273 return;
274
275 // The content rect is the updated resolution of the window we are capturing.
276 // If the window size is different from our current stream configuration,
277 // issue a reconfiguration.
278 //
279 // If the content rect is empty, it's usually an indication that the window has
280 // been minimized while capturing it. We keep the stream unchanged so that it
281 // is automatically resumed when the window is restored.
282 if (!frameInfo.contentRect.isEmpty()) {
283 if (streamOutput.m_previousFrameContentRect != frameInfo.contentRect)
284 streamOutput.m_qScreenCaptureKit->updateStream(frameInfo.contentRect);
285
286 streamOutput.m_previousFrameContentRect = frameInfo.contentRect;
287 }
288
289 q23::expected<QVideoFrame, QString> videoFrameResult = createQVideoFrame(
290 streamOutput,
291 sampleBufferRef);
292 if (!videoFrameResult) {
293 qCWarning(qLcMacScreenCapture)
294 << "Failed to create qVideoFrame from CMSampleBufferRef:"
295 << videoFrameResult.error();
296 return;
297 }
298
299 Q_EMIT streamOutput.m_qScreenCaptureKit->newVideoFrameGenerated(
300 streamOutput.m_qScreenCaptureKit->streamId(),
301 std::move(*videoFrameResult));
302}
303
305 QSize resolutionPx,
306 QMacScreenCaptureKit::StreamSettings const& settings)
307{
308 // SCStreamConfiguration defines the output format, having zero resolution makes no sense.
309 Q_ASSERT(!resolutionPx.isEmpty());
310
311 // TODO: Possible improvements include specifying pixel format, HDR,
312 // capturing system audio...
313 auto scStreamConfig = AVFScopedPointer{ [[SCStreamConfiguration alloc] init] };
314 scStreamConfig.data().width = resolutionPx.width();
315 scStreamConfig.data().height = resolutionPx.height();
316 // We make a best-effort to always adjust our video output to match the window/screen size.
317 // So we leave scaling off to be pixel-perfect whenever we can.
318 scStreamConfig.data().scalesToFit = false;
319 scStreamConfig.data().queueDepth = QMacScreenCaptureKit::queueDepth;
320 scStreamConfig.data().pixelFormat = QMacScreenCaptureKit::cvPixelFormat;
321 scStreamConfig.data().colorSpaceName = QMacScreenCaptureKit::cgColorSpace();
322 scStreamConfig.data().ignoreShadowsSingleWindow = true;
323 scStreamConfig.data().captureResolution = SCCaptureResolutionBest;
324 if (@available(macOS 15.0, *))
325 scStreamConfig.data().captureDynamicRange = SCCaptureDynamicRangeSDR;
326
327 if (settings.frameRate) {
328 Q_ASSERT(settings.frameRate > 0);
329 scStreamConfig.data().minimumFrameInterval =
330 CMTimeMake(1, static_cast<int32_t>(std::round(*settings.frameRate)));
331 } else {
332 scStreamConfig.data().minimumFrameInterval = kCMTimeZero;
333 }
334
335 // These settings are meant to override whatever the default is.
336 if (settings.overrideIgnoreCursor)
337 scStreamConfig.data().showsCursor = false;
338
339 return scStreamConfig;
340}
341
343 QMacScreenCaptureStreamDelegate &streamDelegate,
344 QMacScreenCaptureKit::StreamId streamId,
345 const QMacScreenCaptureKit &macScreenCaptureKit)
346{
347 streamDelegate.m_streamId = streamId;
348 QObject::connect(
349 &streamDelegate.m_helper,
350 &QMacScreenCaptureStreamDelegateHelper::didStopWithError,
351 &macScreenCaptureKit,
352 &QMacScreenCaptureKit::streamStoppedWithError);
353}
354
357 QMacScreenCaptureKit &macScreenCaptureKit,
358 uint32_t cvPixelFormat,
359 QSize resolution)
360{
361 auto streamOutput = AVFScopedPointer{ [[QMacScreenCaptureStreamOutput alloc] init] };
362
363 streamOutput.data()->m_qScreenCaptureKit = &macScreenCaptureKit;
364
365 streamOutput.data()->m_previousFrameContentRect = resolution;
366
367 streamOutput.data()->m_hwAccel = HWAccel::create(AV_HWDEVICE_TYPE_VIDEOTOOLBOX);
368 if (!streamOutput.data()->m_hwAccel)
369 return q23::unexpected(
370 u"Unable to create FFmpeg HW context when starting ScreenCaptureKit stream"_s);
371
372 streamOutput.data()->m_hwAccel->createFramesContext(
373 av_map_videotoolbox_format_to_pixfmt(cvPixelFormat),
374 resolution);
375
376 if (!streamOutput.data()->m_hwAccel->hwFramesContextAsBuffer())
377 return q23::unexpected(
378 u"Unable to create FFmpeg HW context when starting ScreenCaptureKit stream"_s);
379
380 return streamOutput;
381}
382
384{
385 Q_ASSERT(scDisplay);
386 return AVFScopedPointer<SCContentFilter>{ [[SCContentFilter alloc]
387 initWithDisplay: scDisplay
388 excludingWindows: @[] ] };
389}
390
392{
393 Q_ASSERT(scWindow);
394 return AVFScopedPointer<SCContentFilter>{ [[SCContentFilter alloc]
395 initWithDesktopIndependentWindow: scWindow] };
396}
397
398// Returns the resolution of the filtered content, in pixel-coordinates.
399// Error message is not user-facing.
401 SCContentFilter *scContentFilter)
402{
403 // SCContentFilter.contentRect is in screen-points, not pixels. Multiply by
404 // pointPixelScale.
405 const CGRect contentRect = scContentFilter.contentRect;
406 const QSize resolutionPx =
407 (QSizeF::fromCGSize(contentRect.size) * scContentFilter.pointPixelScale).toSize();
408
409 // Rare edge cases have shown contentRect to sometimes be empty.
410 if (resolutionPx.isEmpty())
411 return q23::unexpected{ u"SCContentFilter contentRect reported as zero size"_s };
412
413 return resolutionPx;
414}
415
416// The strategy is to flush any remaining jobs on the background thread, and then
417// mark the end of the stream with a null-frame.
419{
420 if (!m_stream)
421 return;
422
423 // Issue a blocking stop command.
424 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
425 [m_stream.data() stopCaptureWithCompletionHandler:[semaphore](NSError *error) {
426 if (error) {
427 qCWarning(qLcMacScreenCapture)
428 << "Error while stopping ScreenCaptureKit stream during teardown:"
429 << QString::fromNSString(error.localizedDescription);
430 }
431 dispatch_semaphore_signal(semaphore);
432 }];
433 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
434 dispatch_release(semaphore);
435
436 // Mark the end of the stream with a null-frame. We emit it from the background
437 // queue so that it is ordered after, and serialized with, the normal frames.
438 // Note that dispatch_sync() may run its block on the calling thread, so the
439 // emit must use dispatch_async().
440 if (m_dispatchQueue) {
441 Q_ASSERT(m_streamOutput);
442 // m_baseTime is owned by the background queue, so it is only read from there.
443 // The queue is serial, so this runs after all frames that were already delivered.
444 dispatch_async(m_dispatchQueue.data(), [this] {
445 std::optional<std::chrono::microseconds> baseTime =
446 m_streamOutput.data()->m_baseTime;
447
448 // A stream that never delivered a frame never started, in which case there is
449 // no stream for consumers to see the end of.
450 if (!baseTime)
451 return;
452
453 emit newVideoFrameGenerated(m_streamId, createEndOfStreamFrame(*baseTime));
454 });
455
456 // Flush the dispatch_queue. After this we assume it's safe to tear everything down.
457 dispatch_sync(m_dispatchQueue.data(), []{});
458 }
459}
460
461// This will commonly fail if we are missing permissions for screen capturing.
462// It will also open the "Grant permissions" system dialog if we are missing
463// permissions.
464//
465// Thread-safe.
466//
467// Error-message is not user-facing
470{
471 // Block functions can only capture copyable types.
472 // Wrap the promise in a shared-ptr.
474
475 // This function call will open the permissions system dialog when applicable.
478 NSError *error)
479 {
480 if (error != nil) {
482 return;
483 }
484
486
490
494
496 }];
497
498 return promise->get_future();
499}
500
510
511// Note that we are using manual memory management here, because Obj-C block functions
512// do not support capturing move-only types.
517{
519
521 auto future = promise->get_future();
522
524 if (!resolutionResult) {
526 return future;
527 }
529
531
535
541 }
542
545 *captureKit,
548 if (!streamOutputResult) {
550 return future;
551 }
553
556
561
563 dispatch_queue_create("qt_screencapture", DISPATCH_QUEUE_SERIAL) };
564
565 NSError *addStreamError = nullptr;
570 if (addStreamError != nil) {
571 promise->set_value(q23::unexpected(u"Unable to add stream output to SCStream"_s));
572 return future;
573 }
574
579
580 // Block functions for the completion handler require
581 // that the callable is copyable. This means we can't capture
582 // move-only types. So we temporarily release the unique_ptr here,
583 // and switch to manual memory management and then adopt them
584 // back into AVFScopedPointer inside the callback.
585 // We assume the completion handler is always called, either with success or error.
589 (NSError *error)
590 {
592
593 if (error != nil) {
594 promise->set_value(q23::unexpected{ u"Error when starting screen capturing stream"_s });
595 return;
596 }
597
599 }];
600
601 return future;
602}
603
604// Frames may arrive on the background thread immediately after the
605// creation success event has been emitted, sometimes even out of order.
606// This function takes a function that allows us to establish
607// the connections on the newly constructed object before
608// the stream ever starts, so we never miss any frames.
618
619// We first switch the content filter, and then reconfigure the stream for the
620// resolution of the new content. Finally we flush the background queue, so the
621// frames that were queued up for the old content are emitted before we signal.
624{
625 using ResultType = q23::expected<void, QString>;
626
629
631 auto future = promise->get_future();
632
634 if (!resolutionResult) {
636 return future;
637 }
639
641 if (error != nil) {
643 u"Error when reconfiguring stream for new content: "_s
645 return;
646 }
647
649 // The stream is already configured for the new content,
650 // so the frame handler doesn't need to reconfigure it.
653 });
654 };
655
657 NSError *error)
658 {
659 if (error != nil) {
661 u"Error when switching stream content: "_s
663 return;
664 }
665
669
670 [m_stream.data()
673 };
674
675 [m_stream.data()
678
679 return future;
680}
681
687
693
694// Issues a stream configuration update, so that the stream will give us video frames
695// of a new resolution.
696void QMacScreenCaptureKit::startStreamReconfigure(
697 SCStream *scStream,
698 QSize resolutionPx,
699 StreamSettings const &streamSettings)
700{
701 Q_ASSERT(scStream);
702
703 AVFScopedPointer<SCStreamConfiguration> scStreamConfig = createStreamConfig(
704 resolutionPx,
705 streamSettings);
706
707 [scStream
708 updateConfiguration:scStreamConfig.data()
709 completionHandler:[](NSError *err) {
710 if (err) {
711 // TODO: Send potential error back to QMacScreenCaptureKit, but only
712 // if the error stops the stream.
713 qCWarning(qLcMacScreenCapture)
714 << "Error when reconfiguring ScreenCaptureKit stream:"
715 << QString::fromNSString(err.description);
716 return;
717 }
718 }];
719}
720
721// Reconfigures the stream with a new output resolution.
722// Does not stop the stream.
723// Input resolution is in pixel-coordinates.
724// Must be called from background dispatch_queue.
725void QMacScreenCaptureKit::updateStream(QSize resolutionPx)
726{
727 Q_ASSERT(m_dispatchQueue);
728 dispatch_assert_queue(m_dispatchQueue.data());
729
730 startStreamReconfigure(m_stream.data(), resolutionPx, m_streamSettings);
731}
732
733} // namespace QFFmpeg
734
735QT_END_NAMESPACE
736
737#include "moc_qmacscreencapturekit_p.cpp"
738#include "qmacscreencapturekit.moc"
void updateStream(QSize resolutionPx)
static AVFScopedPointer< SCStreamConfiguration > createStreamConfig(QSize resolutionPx, QMacScreenCaptureKit::StreamSettings const &settings)
static AVFScopedPointer< SCContentFilter > createDisplayFilter(SCDisplay *scDisplay)
Q_DECLARE_LOGGING_CATEGORY(qLcMacScreenCapture)
static void handleFrameOutput(QMacScreenCaptureStreamOutput &scStreamOutput, CMSampleBufferRef sampleBufferRef)
static QVideoFrame createEndOfStreamFrame(std::chrono::microseconds baseTime)
static q23::expected< QVideoFrame, QString > createQVideoFrame(QMacScreenCaptureStreamOutput &scStreamOutput, CMSampleBufferRef sampleBufferRef)
static q23::expected< AVFScopedPointer< QMacScreenCaptureStreamOutput >, QString > createStreamOutput(QMacScreenCaptureKit &macScreenCaptureKit, uint32_t cvPixelFormat, QSize resolution)
static AVFScopedPointer< SCContentFilter > createWindowFilter(SCWindow *scWindow)
static void configureStreamDelegate(QMacScreenCaptureStreamDelegate &streamDelegate, QMacScreenCaptureKit::StreamId streamId, const QMacScreenCaptureKit &macScreenCaptureKit)
static q23::expected< FrameInfo, QString > readFrameInfo(CMSampleBufferRef sampleBuffer)
static q23::expected< QSize, QString > resolutionForFilter(SCContentFilter *scContentFilter)
Q_LOGGING_CATEGORY_IMPL(QT_PREPEND_NAMESPACE(QFFmpeg::qLcMacScreenCapture), "qt.multimedia.screencapture.macscreencapturekit")