122 CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer,
false);
123 if (!attachments || CFArrayGetCount(attachments) == 0)
124 return q23::unexpected{ u"CMSampleBuffer has no attachments array"_s };
126 CFDictionaryRef attachment = (CFDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
127 NSDictionary *dict = (__bridge NSDictionary *)attachment;
129 NSNumber *statusNumber = dict[(id)SCStreamFrameInfoStatus];
131 return q23::unexpected{ u"CMSampleBuffer has no frame status"_s };
134 info.status =
static_cast<SCFrameStatus>(statusNumber.intValue);
136 CGRect contentRect = CGRectZero;
137 NSDictionary *frameInfo = dict[(id)SCStreamFrameInfoContentRect];
139 contentRect = CGRectMakeWithDictionaryRepresentation(
140 (__bridge CFDictionaryRef)frameInfo, &contentRect)
145 NSNumber *scaleNumber = dict[(id)SCStreamFrameInfoScaleFactor];
146 CGFloat scaleFactor = scaleNumber ? scaleNumber.doubleValue : 1.0;
148 NSNumber *contentScaleNumber = dict[(id)SCStreamFrameInfoContentScale];
149 CGFloat contentScale = contentScaleNumber ? contentScaleNumber.doubleValue : 1.0;
150 if (contentScale <= 0.0)
153 info.contentRect = QSize{
154 static_cast<
int>(std::lround(contentRect.size.width * scaleFactor / contentScale)),
155 static_cast<
int>(std::lround(contentRect.size.height * scaleFactor / contentScale)), };
163 QMacScreenCaptureStreamOutput &scStreamOutput,
164 CMSampleBufferRef sampleBufferRef)
166 CVImageBufferRef imageBufferRef = CMSampleBufferGetImageBuffer(sampleBufferRef);
168 return q23::unexpected(u"Cannot get CVImageBufferRef from CMSampleBufferRef"_s);
169 if (CFGetTypeID(imageBufferRef) != CVPixelBufferGetTypeID())
170 return q23::unexpected(u"Grabbed CVImageBufferRef that is not of type CVPixelBuffer"_s);
172 auto pixelBuffer = QAVFHelpers::QSharedCVPixelBuffer(
174 QAVFHelpers::QSharedCVPixelBuffer::RefMode::NeedsRef);
179 q23::expected<QAVFHelpers::QSharedCVPixelBuffer, QString> copyResult = deepCopyCvPixelBuffer(
182 return q23::unexpected(u"Failed to copy incoming pixel buffer: "_s + copyResult.error());
183 pixelBuffer =
std::move(*copyResult);
186 QSize incomingFrameSize {
187 static_cast<
int>(CVPixelBufferGetWidth(pixelBuffer.get())),
188 static_cast<
int>(CVPixelBufferGetHeight(pixelBuffer.get())) };
189 Q_ASSERT(!incomingFrameSize.isEmpty());
190 CvPixelFormat incomingCvPixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer.get());
191 Q_ASSERT(scStreamOutput.m_hwAccel);
192 scStreamOutput.m_hwAccel->updateFramesContext(
193 av_map_videotoolbox_format_to_pixfmt(incomingCvPixelFormat),
197 std::chrono::microseconds frameTime =
198 QAVFHelpers::CMTimeToMicroseconds(CMSampleBufferGetPresentationTimeStamp(sampleBufferRef));
199 if (!scStreamOutput.m_baseTime) {
200 scStreamOutput.m_baseTime = frameTime;
201 scStreamOutput.m_startTime = frameTime;
204 QVideoFrameFormat format = QAVFHelpers::videoFormatForImageBuffer(pixelBuffer.get());
205 if (!format.isValid())
206 return q23::unexpected(u"Cannot get get video format for image buffer"_s);
208 format.setColorSpace(QMacScreenCaptureKit::colorSpace);
209 format.setColorRange(QMacScreenCaptureKit::colorRange);
210 format.setColorTransfer(QMacScreenCaptureKit::colorTransfer);
212 Q_ASSERT(scStreamOutput.m_hwAccel);
214 q23::expected<QVideoFrame, QString> frameResult = QFFmpeg::qVideoFrameFromCvPixelBuffer(
215 *scStreamOutput.m_hwAccel,
216 scStreamOutput.m_startTime - *scStreamOutput.m_baseTime,
220 qCWarning(qLcMacScreenCapture) << frameResult.error();
222 frame = *frameResult;
224 if (!frame.isValid()) {
225 frame = QVideoFramePrivate::createFrame(
226 std::make_unique<QFFmpeg::CVImageVideoBuffer>(std::move(pixelBuffer)),
230 frame.setStartTime((scStreamOutput.m_startTime - *scStreamOutput.m_baseTime).count());
231 frame.setEndTime((frameTime - *scStreamOutput.m_baseTime).count());
232 scStreamOutput.m_startTime = frameTime;
240 QFFmpeg::QMacScreenCaptureStreamOutput &streamOutput,
241 CMSampleBufferRef sampleBufferRef)
243 Q_ASSERT(streamOutput.m_qScreenCaptureKit);
245 q23::expected<FrameInfo, QString> frameInfoResult = readFrameInfo(sampleBufferRef);
246 if (!frameInfoResult) {
247 qCDebug(qLcMacScreenCapture)
248 <<
"Error while reading frame info of CMSampleBufferRef:"
249 << frameInfoResult.error();
253 const FrameInfo &frameInfo = *frameInfoResult;
257 if (frameInfo.status != SCFrameStatusComplete)
267 if (!frameInfo.contentRect.isEmpty()) {
268 if (streamOutput.m_previousFrameContentRect != frameInfo.contentRect)
269 streamOutput.m_qScreenCaptureKit->updateStream(frameInfo.contentRect);
271 streamOutput.m_previousFrameContentRect = frameInfo.contentRect;
274 q23::expected<QVideoFrame, QString> videoFrameResult = createQVideoFrame(
277 if (!videoFrameResult) {
278 qCWarning(qLcMacScreenCapture)
279 <<
"Failed to create qVideoFrame from CMSampleBufferRef:"
280 << videoFrameResult.error();
284 emit streamOutput.m_qScreenCaptureKit->newVideoFrameGenerated(
285 streamOutput.m_qScreenCaptureKit->streamId(),
286 std::move(*videoFrameResult));
294 Q_ASSERT(!resolutionPx.isEmpty());
298 auto scStreamConfig = AVFScopedPointer{ [[SCStreamConfiguration alloc] init] };
299 scStreamConfig.data().width = resolutionPx.width();
300 scStreamConfig.data().height = resolutionPx.height();
303 scStreamConfig.data().scalesToFit =
false;
305 scStreamConfig.data().pixelFormat = QMacScreenCaptureKit::cvPixelFormat;
306 scStreamConfig.data().colorSpaceName = QMacScreenCaptureKit::cgColorSpace();
307 scStreamConfig.data().captureResolution = SCCaptureResolutionBest;
308 if (@available(macOS 15.0, *))
309 scStreamConfig.data().captureDynamicRange = SCCaptureDynamicRangeSDR;
311 if (settings.frameRate) {
312 Q_ASSERT(settings.frameRate > 0);
313 scStreamConfig.data().minimumFrameInterval =
314 CMTimeMake(1,
static_cast<int32_t>(std::round(*settings.frameRate)));
316 scStreamConfig.data().minimumFrameInterval = kCMTimeZero;
321 scStreamConfig.data().showsCursor =
false;
323 scStreamConfig.data().ignoreShadowsSingleWindow =
true;
325 return scStreamConfig;
329 QMacScreenCaptureStreamDelegate &streamDelegate,
330 QMacScreenCaptureKit::StreamId streamId,
333 streamDelegate.m_streamId = streamId;
335 &streamDelegate.m_helper,
336 &QMacScreenCaptureStreamDelegateHelper::didStopWithError,
337 &macScreenCaptureKit,
338 &QMacScreenCaptureKit::streamStoppedWithError);
344 uint32_t cvPixelFormat,
347 auto streamOutput = AVFScopedPointer{ [[QMacScreenCaptureStreamOutput alloc] init] };
349 streamOutput.data()->m_qScreenCaptureKit = &macScreenCaptureKit;
351 streamOutput.data()->m_previousFrameContentRect = resolution;
353 streamOutput.data()->m_hwAccel = HWAccel::create(AV_HWDEVICE_TYPE_VIDEOTOOLBOX);
354 if (!streamOutput.data()->m_hwAccel)
355 return q23::unexpected(
356 u"Unable to create FFmpeg HW context when starting ScreenCaptureKit stream"_s);
358 streamOutput.data()->m_hwAccel->createFramesContext(
359 av_map_videotoolbox_format_to_pixfmt(cvPixelFormat),
362 if (!streamOutput.data()->m_hwAccel->hwFramesContextAsBuffer())
363 return q23::unexpected(
364 u"Unable to create FFmpeg HW context when starting ScreenCaptureKit stream"_s);
376 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
377 [m_stream.data() stopCaptureWithCompletionHandler:[semaphore](NSError *error) {
379 qCWarning(qLcMacScreenCapture)
380 <<
"Error while stopping ScreenCaptureKit stream during teardown:"
381 << QString::fromNSString(error.localizedDescription);
383 dispatch_semaphore_signal(semaphore);
385 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
386 dispatch_release(semaphore);
390 dispatch_sync(m_dispatchQueue.data(), []{});