6#include <QtCore/qmutex.h>
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>
14#include <libavutil/hwcontext_videotoolbox.h>
18#include <QtMultimedia/private/qavfcamerautility_p.h>
19#include <QtMultimedia/private/qavfhelpers_p.h>
20#include <QtMultimedia/private/qvideoframe_p.h>
22#include <CoreMedia/CMSync.h>
23#include <CoreMedia/CMTime.h>
24#include <ScreenCaptureKit/ScreenCaptureKit.h>
28using namespace Qt::Literals::StringLiterals;
29using QMacScreenCaptureKit = QT_PREPEND_NAMESPACE(QFFmpeg::QMacScreenCaptureKit);
33 "qt.multimedia.screencapture.macscreencapturekit");
37struct QMacScreenCaptureStreamDelegateHelper :
public QObject {
40 void didStopWithError(QMacScreenCaptureKit::StreamId streamId, QString);
46@implementation QT_MANGLE_NAMESPACE(QMacScreenCaptureStreamDelegate) {
48 QMacScreenCaptureKit::StreamId m_streamId;
49 QMacScreenCaptureStreamDelegateHelper m_helper;
52- (
void)stream:(SCStream *)stream didStopWithError:(NSError *)error
54 Q_EMIT m_helper.didStopWithError(
56 QString::fromNSString(error.localizedDescription));
66 QMacScreenCaptureStreamOutput &scStreamOutput,
67 CMSampleBufferRef sampleBufferRef);
73@implementation QT_MANGLE_NAMESPACE(QMacScreenCaptureStreamOutput) {
77 QT_PREPEND_NAMESPACE(QFFmpeg::QMacScreenCaptureKit) *m_qScreenCaptureKit;
80 QSize m_previousFrameContentRect;
84 std::optional<std::chrono::microseconds> m_baseTime;
85 std::unique_ptr<QT_PREPEND_NAMESPACE(QFFmpeg::HWAccel)> m_hwAccel;
88- (
void) stream:(SCStream *) stream
89didOutputSampleBuffer:(CMSampleBufferRef) sampleBufferRef
90 ofType:(SCStreamOutputType) type
93 using namespace QFFmpeg;
98 Q_ASSERT(type == SCStreamOutputTypeScreen);
100 handleFrameOutput(*self, sampleBufferRef);
125 CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer,
false);
126 if (!attachments || CFArrayGetCount(attachments) == 0)
127 return q23::unexpected{ u"CMSampleBuffer has no attachments array"_s };
129 CFDictionaryRef attachment = (CFDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
130 NSDictionary *dict = (__bridge NSDictionary *)attachment;
132 NSNumber *statusNumber = dict[(id)SCStreamFrameInfoStatus];
134 return q23::unexpected{ u"CMSampleBuffer has no frame status"_s };
137 info.status =
static_cast<SCFrameStatus>(statusNumber.intValue);
139 CGRect contentRect = CGRectZero;
140 NSDictionary *frameInfo = dict[(id)SCStreamFrameInfoContentRect];
142 contentRect = CGRectMakeWithDictionaryRepresentation(
143 (__bridge CFDictionaryRef)frameInfo, &contentRect)
148 NSNumber *scaleNumber = dict[(id)SCStreamFrameInfoScaleFactor];
149 CGFloat scaleFactor = scaleNumber ? scaleNumber.doubleValue : 1.0;
151 NSNumber *contentScaleNumber = dict[(id)SCStreamFrameInfoContentScale];
152 CGFloat contentScale = contentScaleNumber ? contentScaleNumber.doubleValue : 1.0;
153 if (contentScale <= 0.0)
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)), };
166 QMacScreenCaptureStreamOutput &scStreamOutput,
167 CMSampleBufferRef sampleBufferRef)
169 CVImageBufferRef imageBufferRef = CMSampleBufferGetImageBuffer(sampleBufferRef);
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);
175 auto pixelBuffer = QAVFHelpers::QSharedCVPixelBuffer(
177 QAVFHelpers::QSharedCVPixelBuffer::RefMode::NeedsRef);
182 q23::expected<QAVFHelpers::QSharedCVPixelBuffer, QString> copyResult = deepCopyCvPixelBuffer(
185 return q23::unexpected(u"Failed to copy incoming pixel buffer: "_s + copyResult.error());
186 pixelBuffer =
std::move(*copyResult);
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),
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;
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);
209 format.setColorSpace(QMacScreenCaptureKit::colorSpace);
210 format.setColorRange(QMacScreenCaptureKit::colorRange);
211 format.setColorTransfer(QMacScreenCaptureKit::colorTransfer);
213 Q_ASSERT(scStreamOutput.m_hwAccel);
215 q23::expected<QVideoFrame, QString> frameResult = QFFmpeg::qVideoFrameFromCvPixelBuffer(
216 *scStreamOutput.m_hwAccel,
221 qCWarning(qLcMacScreenCapture) << frameResult.error();
223 frame = *frameResult;
225 if (!frame.isValid()) {
226 frame = QVideoFramePrivate::createFrame(
227 std::make_unique<QFFmpeg::CVImageVideoBuffer>(std::move(pixelBuffer)),
231 frame.setStartTime(presentationTime.count());
233 frame.setEndTime(-1);
240 QVideoFrame frame{ QVideoFrameFormat{} };
241 Q_ASSERT(!frame.isValid());
245 const std::chrono::microseconds hostTime =
246 QAVFHelpers::CMTimeToMicroseconds(CMClockGetTime(CMClockGetHostTimeClock()));
247 frame.setStartTime((hostTime - baseTime).count());
255 QFFmpeg::QMacScreenCaptureStreamOutput &streamOutput,
256 CMSampleBufferRef sampleBufferRef)
258 Q_ASSERT(streamOutput.m_qScreenCaptureKit);
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();
268 const FrameInfo &frameInfo = *frameInfoResult;
272 if (frameInfo.status != SCFrameStatusComplete)
282 if (!frameInfo.contentRect.isEmpty()) {
283 if (streamOutput.m_previousFrameContentRect != frameInfo.contentRect)
284 streamOutput.m_qScreenCaptureKit->updateStream(frameInfo.contentRect);
286 streamOutput.m_previousFrameContentRect = frameInfo.contentRect;
289 q23::expected<QVideoFrame, QString> videoFrameResult = createQVideoFrame(
292 if (!videoFrameResult) {
293 qCWarning(qLcMacScreenCapture)
294 <<
"Failed to create qVideoFrame from CMSampleBufferRef:"
295 << videoFrameResult.error();
299 Q_EMIT streamOutput.m_qScreenCaptureKit->newVideoFrameGenerated(
300 streamOutput.m_qScreenCaptureKit->streamId(),
301 std::move(*videoFrameResult));
309 Q_ASSERT(!resolutionPx.isEmpty());
313 auto scStreamConfig = AVFScopedPointer{ [[SCStreamConfiguration alloc] init] };
314 scStreamConfig.data().width = resolutionPx.width();
315 scStreamConfig.data().height = resolutionPx.height();
318 scStreamConfig.data().scalesToFit =
false;
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;
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)));
332 scStreamConfig.data().minimumFrameInterval = kCMTimeZero;
337 scStreamConfig.data().showsCursor =
false;
339 return scStreamConfig;
343 QMacScreenCaptureStreamDelegate &streamDelegate,
344 QMacScreenCaptureKit::StreamId streamId,
347 streamDelegate.m_streamId = streamId;
349 &streamDelegate.m_helper,
350 &QMacScreenCaptureStreamDelegateHelper::didStopWithError,
351 &macScreenCaptureKit,
352 &QMacScreenCaptureKit::streamStoppedWithError);
358 uint32_t cvPixelFormat,
361 auto streamOutput = AVFScopedPointer{ [[QMacScreenCaptureStreamOutput alloc] init] };
363 streamOutput.data()->m_qScreenCaptureKit = &macScreenCaptureKit;
365 streamOutput.data()->m_previousFrameContentRect = resolution;
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);
372 streamOutput.data()->m_hwAccel->createFramesContext(
373 av_map_videotoolbox_format_to_pixfmt(cvPixelFormat),
376 if (!streamOutput.data()->m_hwAccel->hwFramesContextAsBuffer())
377 return q23::unexpected(
378 u"Unable to create FFmpeg HW context when starting ScreenCaptureKit stream"_s);
386 return AVFScopedPointer<SCContentFilter>{ [[SCContentFilter alloc]
387 initWithDisplay: scDisplay
388 excludingWindows: @[] ] };
394 return AVFScopedPointer<SCContentFilter>{ [[SCContentFilter alloc]
395 initWithDesktopIndependentWindow: scWindow] };
401 SCContentFilter *scContentFilter)
405 const CGRect contentRect = scContentFilter.contentRect;
406 const QSize resolutionPx =
407 (QSizeF::fromCGSize(contentRect.size) * scContentFilter.pointPixelScale).toSize();
410 if (resolutionPx.isEmpty())
411 return q23::unexpected{ u"SCContentFilter contentRect reported as zero size"_s };
424 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
425 [m_stream.data() stopCaptureWithCompletionHandler:[semaphore](NSError *error) {
427 qCWarning(qLcMacScreenCapture)
428 <<
"Error while stopping ScreenCaptureKit stream during teardown:"
429 << QString::fromNSString(error.localizedDescription);
431 dispatch_semaphore_signal(semaphore);
433 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
434 dispatch_release(semaphore);
440 if (m_dispatchQueue) {
441 Q_ASSERT(m_streamOutput);
444 dispatch_async(m_dispatchQueue.data(), [
this] {
445 std::optional<
std::chrono::microseconds> baseTime =
446 m_streamOutput.data()->m_baseTime;
453 emit newVideoFrameGenerated(m_streamId, createEndOfStreamFrame(*baseTime));
457 dispatch_sync(m_dispatchQueue.data(), []{});
643 u"Error when reconfiguring stream for new content: "_s
661 u"Error when switching stream content: "_s
703 AVFScopedPointer<SCStreamConfiguration> scStreamConfig = createStreamConfig(
708 updateConfiguration:scStreamConfig.data()
709 completionHandler:[](NSError *err) {
713 qCWarning(qLcMacScreenCapture)
714 <<
"Error when reconfiguring ScreenCaptureKit stream:"
715 << QString::fromNSString(err.description);
727 Q_ASSERT(m_dispatchQueue);
728 dispatch_assert_queue(m_dispatchQueue.data());
730 startStreamReconfigure(m_stream.data(), resolutionPx, m_streamSettings);
737#include "moc_qmacscreencapturekit_p.cpp"
738#include "qmacscreencapturekit.moc"
static constexpr int queueDepth
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")
bool overrideIgnoreCursor