124 CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer,
false);
125 if (!attachments || CFArrayGetCount(attachments) == 0)
126 return q23::unexpected{ u"CMSampleBuffer has no attachments array"_s };
128 CFDictionaryRef attachment = (CFDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
129 NSDictionary *dict = (__bridge NSDictionary *)attachment;
131 NSNumber *statusNumber = dict[(id)SCStreamFrameInfoStatus];
133 return q23::unexpected{ u"CMSampleBuffer has no frame status"_s };
136 info.status =
static_cast<SCFrameStatus>(statusNumber.intValue);
138 CGRect contentRect = CGRectZero;
139 NSDictionary *frameInfo = dict[(id)SCStreamFrameInfoContentRect];
141 contentRect = CGRectMakeWithDictionaryRepresentation(
142 (__bridge CFDictionaryRef)frameInfo, &contentRect)
147 NSNumber *scaleNumber = dict[(id)SCStreamFrameInfoScaleFactor];
148 CGFloat scaleFactor = scaleNumber ? scaleNumber.doubleValue : 1.0;
150 NSNumber *contentScaleNumber = dict[(id)SCStreamFrameInfoContentScale];
151 CGFloat contentScale = contentScaleNumber ? contentScaleNumber.doubleValue : 1.0;
152 if (contentScale <= 0.0)
155 info.contentRect = QSize{
156 static_cast<
int>(std::lround(contentRect.size.width * scaleFactor / contentScale)),
157 static_cast<
int>(std::lround(contentRect.size.height * scaleFactor / contentScale)), };
165 QMacScreenCaptureStreamOutput &scStreamOutput,
166 CMSampleBufferRef sampleBufferRef)
168 CVImageBufferRef imageBufferRef = CMSampleBufferGetImageBuffer(sampleBufferRef);
170 return q23::unexpected(u"Cannot get CVImageBufferRef from CMSampleBufferRef"_s);
171 if (CFGetTypeID(imageBufferRef) != CVPixelBufferGetTypeID())
172 return q23::unexpected(u"Grabbed CVImageBufferRef that is not of type CVPixelBuffer"_s);
174 auto pixelBuffer = QAVFHelpers::QSharedCVPixelBuffer(
176 QAVFHelpers::QSharedCVPixelBuffer::RefMode::NeedsRef);
181 q23::expected<QAVFHelpers::QSharedCVPixelBuffer, QString> copyResult = deepCopyCvPixelBuffer(
184 return q23::unexpected(u"Failed to copy incoming pixel buffer: "_s + copyResult.error());
185 pixelBuffer =
std::move(*copyResult);
188 QSize incomingFrameSize {
189 static_cast<
int>(CVPixelBufferGetWidth(pixelBuffer.get())),
190 static_cast<
int>(CVPixelBufferGetHeight(pixelBuffer.get())) };
191 Q_ASSERT(!incomingFrameSize.isEmpty());
192 CvPixelFormat incomingCvPixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer.get());
193 Q_ASSERT(scStreamOutput.m_hwAccel);
194 scStreamOutput.m_hwAccel->updateFramesContext(
195 av_map_videotoolbox_format_to_pixfmt(incomingCvPixelFormat),
200 const std::chrono::microseconds frameTime =
201 QAVFHelpers::CMTimeToMicroseconds(CMSampleBufferGetPresentationTimeStamp(sampleBufferRef));
202 if (!scStreamOutput.m_baseTime)
203 scStreamOutput.m_baseTime = frameTime;
204 const std::chrono::microseconds presentationTime = frameTime - *scStreamOutput.m_baseTime;
206 QVideoFrameFormat format = QAVFHelpers::videoFormatForImageBuffer(pixelBuffer.get());
207 if (!format.isValid())
208 return q23::unexpected(u"Cannot get get video format for image buffer"_s);
210 format.setColorSpace(QMacScreenCaptureKit::colorSpace);
211 format.setColorRange(QMacScreenCaptureKit::colorRange);
212 format.setColorTransfer(QMacScreenCaptureKit::colorTransfer);
214 Q_ASSERT(scStreamOutput.m_hwAccel);
216 q23::expected<QVideoFrame, QString> frameResult = QFFmpeg::qVideoFrameFromCvPixelBuffer(
217 *scStreamOutput.m_hwAccel,
222 qCWarning(qLcMacScreenCapture) << frameResult.error();
224 frame = *frameResult;
226 if (!frame.isValid()) {
227 frame = QVideoFramePrivate::createFrame(
228 std::make_unique<QFFmpeg::CVImageVideoBuffer>(std::move(pixelBuffer)),
232 frame.setStartTime(presentationTime.count());
234 frame.setEndTime(-1);
242 QFFmpeg::QMacScreenCaptureStreamOutput &streamOutput,
243 CMSampleBufferRef sampleBufferRef)
245 Q_ASSERT(streamOutput.m_qScreenCaptureKit);
247 q23::expected<FrameInfo, QString> frameInfoResult = readFrameInfo(sampleBufferRef);
248 if (!frameInfoResult) {
249 qCDebug(qLcMacScreenCapture)
250 <<
"Error while reading frame info of CMSampleBufferRef:"
251 << frameInfoResult.error();
255 const FrameInfo &frameInfo = *frameInfoResult;
259 if (frameInfo.status != SCFrameStatusComplete)
269 if (!frameInfo.contentRect.isEmpty()) {
270 if (streamOutput.m_previousFrameContentRect != frameInfo.contentRect)
271 streamOutput.m_qScreenCaptureKit->updateStream(frameInfo.contentRect);
273 streamOutput.m_previousFrameContentRect = frameInfo.contentRect;
276 q23::expected<QVideoFrame, QString> videoFrameResult = createQVideoFrame(
279 if (!videoFrameResult) {
280 qCWarning(qLcMacScreenCapture)
281 <<
"Failed to create qVideoFrame from CMSampleBufferRef:"
282 << videoFrameResult.error();
286 emit streamOutput.m_qScreenCaptureKit->newVideoFrameGenerated(
287 streamOutput.m_qScreenCaptureKit->streamId(),
288 std::move(*videoFrameResult));
296 Q_ASSERT(!resolutionPx.isEmpty());
300 auto scStreamConfig = AVFScopedPointer{ [[SCStreamConfiguration alloc] init] };
301 scStreamConfig.data().width = resolutionPx.width();
302 scStreamConfig.data().height = resolutionPx.height();
305 scStreamConfig.data().scalesToFit =
false;
307 scStreamConfig.data().pixelFormat = QMacScreenCaptureKit::cvPixelFormat;
308 scStreamConfig.data().colorSpaceName = QMacScreenCaptureKit::cgColorSpace();
309 scStreamConfig.data().ignoreShadowsSingleWindow =
true;
310 scStreamConfig.data().captureResolution = SCCaptureResolutionBest;
311 if (@available(macOS 15.0, *))
312 scStreamConfig.data().captureDynamicRange = SCCaptureDynamicRangeSDR;
314 if (settings.frameRate) {
315 Q_ASSERT(settings.frameRate > 0);
316 scStreamConfig.data().minimumFrameInterval =
317 CMTimeMake(1,
static_cast<int32_t>(std::round(*settings.frameRate)));
319 scStreamConfig.data().minimumFrameInterval = kCMTimeZero;
324 scStreamConfig.data().showsCursor =
false;
326 return scStreamConfig;
330 QMacScreenCaptureStreamDelegate &streamDelegate,
331 QMacScreenCaptureKit::StreamId streamId,
334 streamDelegate.m_streamId = streamId;
336 &streamDelegate.m_helper,
337 &QMacScreenCaptureStreamDelegateHelper::didStopWithError,
338 &macScreenCaptureKit,
339 &QMacScreenCaptureKit::streamStoppedWithError);
345 uint32_t cvPixelFormat,
348 auto streamOutput = AVFScopedPointer{ [[QMacScreenCaptureStreamOutput alloc] init] };
350 streamOutput.data()->m_qScreenCaptureKit = &macScreenCaptureKit;
352 streamOutput.data()->m_previousFrameContentRect = resolution;
354 streamOutput.data()->m_hwAccel = HWAccel::create(AV_HWDEVICE_TYPE_VIDEOTOOLBOX);
355 if (!streamOutput.data()->m_hwAccel)
356 return q23::unexpected(
357 u"Unable to create FFmpeg HW context when starting ScreenCaptureKit stream"_s);
359 streamOutput.data()->m_hwAccel->createFramesContext(
360 av_map_videotoolbox_format_to_pixfmt(cvPixelFormat),
363 if (!streamOutput.data()->m_hwAccel->hwFramesContextAsBuffer())
364 return q23::unexpected(
365 u"Unable to create FFmpeg HW context when starting ScreenCaptureKit stream"_s);
377 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
378 [m_stream.data() stopCaptureWithCompletionHandler:[semaphore](NSError *error) {
380 qCWarning(qLcMacScreenCapture)
381 <<
"Error while stopping ScreenCaptureKit stream during teardown:"
382 << QString::fromNSString(error.localizedDescription);
384 dispatch_semaphore_signal(semaphore);
386 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
387 dispatch_release(semaphore);
391 dispatch_sync(m_dispatchQueue.data(), []{});