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));
290 QMacScreenCaptureStreamDelegate &streamDelegate,
291 QMacScreenCaptureKit::StreamId streamId,
294 streamDelegate.m_streamId = streamId;
296 &streamDelegate.m_helper,
297 &QMacScreenCaptureStreamDelegateHelper::didStopWithError,
298 &macScreenCaptureKit,
299 &QMacScreenCaptureKit::streamStoppedWithError);
305 uint32_t cvPixelFormat,
308 auto streamOutput = AVFScopedPointer{ [[QMacScreenCaptureStreamOutput alloc] init] };
310 streamOutput.data()->m_qScreenCaptureKit = &macScreenCaptureKit;
312 streamOutput.data()->m_previousFrameContentRect = resolution;
314 streamOutput.data()->m_hwAccel = HWAccel::create(AV_HWDEVICE_TYPE_VIDEOTOOLBOX);
315 if (!streamOutput.data()->m_hwAccel)
316 return q23::unexpected(
317 u"Unable to create FFmpeg HW context when starting ScreenCaptureKit stream"_s);
319 streamOutput.data()->m_hwAccel->createFramesContext(
320 av_map_videotoolbox_format_to_pixfmt(cvPixelFormat),
323 if (!streamOutput.data()->m_hwAccel->hwFramesContextAsBuffer())
324 return q23::unexpected(
325 u"Unable to create FFmpeg HW context when starting ScreenCaptureKit stream"_s);
337 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
338 [m_stream.data() stopCaptureWithCompletionHandler:[semaphore](NSError *error) {
340 qCWarning(qLcMacScreenCapture)
341 <<
"Error while stopping ScreenCaptureKit stream during teardown:"
342 << QString::fromNSString(error.localizedDescription);
344 dispatch_semaphore_signal(semaphore);
346 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
347 dispatch_release(semaphore);
351 dispatch_sync(m_dispatchQueue.data(), []{});
539 std::optional<qreal> frameRate)
542 Q_ASSERT(!resolutionPx.isEmpty());
546 auto scStreamConfig = AVFScopedPointer{ [[SCStreamConfiguration alloc] init] };
547 scStreamConfig.data().width = resolutionPx.width();
548 scStreamConfig.data().height = resolutionPx.height();
551 scStreamConfig.data().scalesToFit =
false;
553 scStreamConfig.data().pixelFormat = QMacScreenCaptureKit::cvPixelFormat;
554 scStreamConfig.data().colorSpaceName = QMacScreenCaptureKit::cgColorSpace();
555 scStreamConfig.data().captureResolution = SCCaptureResolutionBest;
556 if (@available(macOS 15.0, *))
557 scStreamConfig.data().captureDynamicRange = SCCaptureDynamicRangeSDR;
560 Q_ASSERT(frameRate > 0);
561 scStreamConfig.data().minimumFrameInterval =
562 CMTimeMake(1,
static_cast<int32_t>(std::round(*frameRate)));
564 scStreamConfig.data().minimumFrameInterval = kCMTimeZero;
567 return scStreamConfig;